mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-13 12:23:38 +01:00
feat(scrolling): lay out the scrolling layout with the scrolling model
The Scrolling layout now keeps a ScrollingSpace per workspace, keyed by container id, instead of recomputing equal-width columns from the previous layout on every update. Column widths follow layout_options.scrolling.columns and persist as windows come and go.
This commit is contained in:
1 parent
6c4eb621d3
commit
3b93035d48
4 files changed
+150
-13
No files matched your search
@@ -16,6 +16,7 @@ pub mod process_command;
|
||||
pub mod process_event;
|
||||
pub mod process_movement;
|
||||
pub mod reaper;
|
||||
pub mod scrolling;
|
||||
pub mod set_window_position;
|
||||
pub mod splash;
|
||||
pub mod stackbar_manager;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
//! The glue between a [`Workspace`] and the platform-free model of a strip of columns in
|
||||
//! `komorebi-scrolling`.
|
||||
//!
|
||||
//! A container is a tile in the strip, and is known to it by the id of the container.
|
||||
|
||||
use crate::core::DefaultLayout;
|
||||
use crate::core::Layout;
|
||||
use crate::core::LayoutOptions;
|
||||
use crate::core::Rect;
|
||||
use crate::workspace::Workspace;
|
||||
use komorebi_scrolling::CenterFocusedColumn;
|
||||
use komorebi_scrolling::ColumnWidth;
|
||||
use komorebi_scrolling::Options;
|
||||
use komorebi_scrolling::ScrollingSpace;
|
||||
use komorebi_scrolling::Viewport;
|
||||
use komorebi_scrolling::Visibility;
|
||||
|
||||
/// How many columns fit in the viewport when nothing says otherwise.
|
||||
const DEFAULT_COLUMNS: usize = 3;
|
||||
|
||||
/// Where a container goes, and how much of it is in view.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) struct ScrollingPlacement {
|
||||
/// Where the container is in the strip, in the coordinates of the screen. A container that is
|
||||
/// partly or wholly out of view has the rectangle that it really has, which reaches past the
|
||||
/// work area.
|
||||
pub rect: Rect,
|
||||
#[allow(dead_code)]
|
||||
pub visibility: Visibility,
|
||||
}
|
||||
|
||||
/// What the layout options of a workspace come to for the model.
|
||||
///
|
||||
/// `columns` says how many columns fit in the viewport, which is to say that a new column is a
|
||||
/// `columns`th of its width. It does not say how many columns there are, and it does not stretch
|
||||
/// a lone column to fill the viewport.
|
||||
pub(crate) fn scrolling_options(layout_options: Option<LayoutOptions>) -> Options {
|
||||
let scrolling = layout_options.and_then(|options| options.scrolling);
|
||||
let columns = scrolling
|
||||
.map_or(DEFAULT_COLUMNS, |scrolling| scrolling.columns)
|
||||
.max(1);
|
||||
let centering = match scrolling.and_then(|scrolling| scrolling.center_focused_column) {
|
||||
Some(true) => CenterFocusedColumn::Always,
|
||||
_ => CenterFocusedColumn::Never,
|
||||
};
|
||||
|
||||
Options {
|
||||
default_width: ColumnWidth::Proportion(1.0 / columns as f64),
|
||||
center_focused_column: centering,
|
||||
..Options::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// Whether the workspace is laid out as a strip of columns.
|
||||
pub(crate) fn is_scrolling(&self) -> bool {
|
||||
matches!(self.layout, Layout::Default(DefaultLayout::Scrolling))
|
||||
}
|
||||
|
||||
/// Make the space match the containers, and the focus, as they are now.
|
||||
fn sync_scrolling(&mut self, viewport: Viewport) {
|
||||
let options = scrolling_options(self.effective_layout_options());
|
||||
let ids: Vec<String> = self
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|container| container.id.clone())
|
||||
.collect();
|
||||
let focused = self
|
||||
.focused_container()
|
||||
.map(|container| container.id.clone());
|
||||
|
||||
let space = self
|
||||
.scrolling
|
||||
.get_or_insert_with(|| ScrollingSpace::new(options.clone(), viewport));
|
||||
|
||||
if *space.options() != options {
|
||||
space.set_options(options);
|
||||
}
|
||||
|
||||
if space.viewport() != viewport {
|
||||
space.set_viewport(viewport);
|
||||
}
|
||||
|
||||
space.reconcile(&ids, focused.as_ref());
|
||||
}
|
||||
|
||||
/// Where every container goes in `work_area`, from left to right, with `container_padding`
|
||||
/// taken off every side of each.
|
||||
pub(crate) fn layout_scrolling(
|
||||
&mut self,
|
||||
work_area: &Rect,
|
||||
container_padding: i32,
|
||||
) -> Vec<ScrollingPlacement> {
|
||||
self.sync_scrolling(Viewport {
|
||||
width: work_area.right,
|
||||
height: work_area.bottom,
|
||||
});
|
||||
|
||||
let Some(space) = &self.scrolling else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
space
|
||||
.layout()
|
||||
.into_iter()
|
||||
.map(|placement| {
|
||||
let mut rect = Rect {
|
||||
left: work_area.left + placement.rect.x,
|
||||
top: work_area.top + placement.rect.y,
|
||||
right: placement.rect.width,
|
||||
bottom: placement.rect.height,
|
||||
};
|
||||
rect.add_padding(container_padding);
|
||||
|
||||
ScrollingPlacement {
|
||||
rect,
|
||||
visibility: placement.visibility,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -286,6 +286,7 @@ impl From<&WindowManager> for State {
|
||||
initial_window_placement_rules: workspace
|
||||
.initial_window_placement_rules
|
||||
.clone(),
|
||||
scrolling: None,
|
||||
})
|
||||
.collect::<VecDeque<_>>();
|
||||
ws.focus(monitor.workspaces.focused_idx());
|
||||
|
||||
+26
-13
@@ -46,6 +46,7 @@ use crate::window::WindowDetails;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use komorebi_scrolling::ScrollingSpace;
|
||||
use komorebi_themes::Base16ColourPalette;
|
||||
use komorebi_themes::KomorebiThemeCustom as Custom;
|
||||
use serde::Deserialize;
|
||||
@@ -102,6 +103,10 @@ pub struct Workspace {
|
||||
/// Initial window placement rules that determine where new tiled windows are placed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub initial_window_placement_rules: Option<InitialWindowPlacementRules>,
|
||||
/// The strip of columns that the Scrolling layout lays containers out in
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub(crate) scrolling: Option<ScrollingSpace<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -159,6 +164,7 @@ impl Default for Workspace {
|
||||
preselected_container_idx: None,
|
||||
promotion_swap_container_idx: None,
|
||||
initial_window_placement_rules: None,
|
||||
scrolling: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +370,7 @@ impl Workspace {
|
||||
/// 1. Try threshold match from rules (highest matching threshold wins)
|
||||
/// 2. If a rule matches -> use it (full replacement of base)
|
||||
/// 3. Else -> use the base `layout_options`
|
||||
fn effective_layout_options(&self) -> Option<LayoutOptions> {
|
||||
pub(crate) fn effective_layout_options(&self) -> Option<LayoutOptions> {
|
||||
let container_count = self.containers().len();
|
||||
|
||||
let has_workspace_overrides =
|
||||
@@ -715,18 +721,25 @@ impl Workspace {
|
||||
self.layout_options,
|
||||
self.layout_options_rules.len(),
|
||||
);
|
||||
let mut layouts = self.layout.as_boxed_arrangement().calculate(
|
||||
&adjusted_work_area,
|
||||
NonZeroUsize::new(self.containers().len()).ok_or_eyre(
|
||||
"there must be at least one container to calculate a workspace layout",
|
||||
)?,
|
||||
Some(container_padding),
|
||||
self.layout_flip,
|
||||
&self.resize_dimensions,
|
||||
self.focused_container_idx(),
|
||||
effective_layout_options,
|
||||
&self.latest_layout,
|
||||
);
|
||||
let mut layouts = if self.is_scrolling() {
|
||||
self.layout_scrolling(&adjusted_work_area, container_padding)
|
||||
.into_iter()
|
||||
.map(|placement| placement.rect)
|
||||
.collect()
|
||||
} else {
|
||||
self.layout.as_boxed_arrangement().calculate(
|
||||
&adjusted_work_area,
|
||||
NonZeroUsize::new(self.containers().len()).ok_or_eyre(
|
||||
"there must be at least one container to calculate a workspace layout",
|
||||
)?,
|
||||
Some(container_padding),
|
||||
self.layout_flip,
|
||||
&self.resize_dimensions,
|
||||
self.focused_container_idx(),
|
||||
effective_layout_options,
|
||||
&self.latest_layout,
|
||||
)
|
||||
};
|
||||
|
||||
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
||||
let no_titlebar = NO_TITLEBAR.lock().clone();
|
||||
|
||||
Reference in new issue
Block a user