feat(scrolling): add commands and config for the scrolling layout

Focus, move, resize, retile and closing a window now go through the strip of
columns in the Scrolling layout. New commands: consume, expel,
toggle-column-stack, scrolling-layout-full-width and center-focus. A top
level scrolling section configures centering, centring a lone column, and
the minimum height of a window in a column of several.
This commit is contained in:
LGUG2Z committed 2026-10-30 15:45:37 -07:00
1 parent 8c7327fd31
commit 598390d9cb
5 files changed
+102 -46

No files matched your search

+7
View File
@@ -41,6 +41,7 @@ pub use komorebi_layouts::OperationDirection;
pub use komorebi_layouts::Rect;
pub use komorebi_layouts::ScrollingLayoutOptions;
pub use komorebi_layouts::Sizing;
pub use komorebi_layouts::scrolling::ScrollingCentering;
pub use komorebi_layouts::validate_ratios;
// Local modules and exports
@@ -125,6 +126,12 @@ pub enum SocketMessage {
CycleLayout(CycleDirection),
LayoutRatios(Option<Vec<f32>>, Option<Vec<f32>>),
ScrollingLayoutColumns(NonZeroUsize),
ScrollingLayoutFullWidth,
CenterFocus,
Expand,
Consume(OperationDirection),
Expel,
ToggleColumnStack,
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
FlipLayout(Axis),
ToggleWorkspaceWindowContainerBehaviour,
+25 -21
View File
@@ -64,7 +64,6 @@ use crate::core::MonocleFocusBehaviour;
use crate::core::MoveBehaviour;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::core::ScrollingLayoutOptions;
use crate::core::Sizing;
use crate::core::SocketMessage;
use crate::core::StateQuery;
@@ -900,28 +899,33 @@ impl WindowManager {
}
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
SocketMessage::ScrollingLayoutColumns(count) => {
let focused_workspace = self.focused_workspace_mut()?;
let options = match focused_workspace.layout_options {
Some(mut opts) => {
if let Some(scrolling) = &mut opts.scrolling {
scrolling.columns = count.into();
self.focused_workspace_mut()?
.set_scrolling_columns(count.into());
self.update_focused_workspace(false, false)?;
}
opts
SocketMessage::ScrollingLayoutFullWidth => {
self.focused_workspace_mut()?.toggle_scrolling_full_width();
self.update_focused_workspace(false, false)?;
}
None => LayoutOptions {
scrolling: Some(ScrollingLayoutOptions {
columns: count.into(),
center_focused_column: Default::default(),
}),
grid: None,
column_ratios: None,
row_ratios: None,
},
};
focused_workspace.layout_options = Some(options);
SocketMessage::CenterFocus => {
self.focused_workspace_mut()?.center_scrolling_column();
self.update_focused_workspace(false, false)?;
}
SocketMessage::Expand => {
self.focused_workspace_mut()?.expand_scrolling_column();
self.update_focused_workspace(false, false)?;
}
SocketMessage::Consume(direction) => {
self.focused_workspace_mut()?.consume_scrolling(direction);
self.update_focused_workspace(false, false)?;
}
SocketMessage::ToggleColumnStack => {
self.focused_workspace_mut()?
.toggle_scrolling_column_stack()?;
self.update_focused_workspace(false, false)?;
}
SocketMessage::Expel => {
self.focused_workspace_mut()?.expel_scrolling();
self.update_focused_workspace(false, false)?;
}
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
+19
View File
@@ -771,6 +771,10 @@ impl WindowManager {
// Reset any resize adjustments if we want to force a retile
if !preserve_resize_dimensions {
workspace.resize_dimensions.fill(None);
// and the widths and heights of the Scrolling layout, which are resize
// adjustments too
workspace.reset_scrolling_sizes();
}
if (workspace.wallpaper.is_some() || monitor_wp.is_some())
@@ -1271,6 +1275,16 @@ impl WindowManager {
}
WorkspaceLayer::Tiling => {
match workspace.layout {
Layout::Default(DefaultLayout::Scrolling) => {
tracing::info!("resizing column");
workspace.resize_scrolling_column(direction, sizing, delta);
return if update {
self.update_focused_workspace(false, false)
} else {
Ok(())
};
}
Layout::Default(layout) => {
tracing::info!("resizing window");
let len = NonZeroUsize::new(workspace.containers().len())
@@ -2608,10 +2622,15 @@ impl WindowManager {
}
Some(new_idx) => {
let workspace = self.focused_workspace_mut()?;
// a column of the Scrolling layout moves whole, and its tiles move within it,
// which is not a swap of two containers
if !(workspace.is_scrolling() && workspace.move_scrolling(direction)) {
workspace.swap_containers(origin_container_idx, new_idx);
workspace.focus_container(new_idx);
}
}
}
self.update_focused_workspace(self.mouse_follows_focus, true)?;
+15 -25
View File
@@ -1064,6 +1064,9 @@ impl Workspace {
// this fn respects locked container indexes - we should use it for pretty much everything
// except monocle and maximize toggles
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
// wherever it goes next, it goes as wide as it was
self.note_scrolling_departure(idx);
let container = self.containers_mut().remove_respecting_locks(idx);
if idx < self.resize_dimensions.len() {
@@ -1143,8 +1146,12 @@ impl Workspace {
.ok_or_eyre("there is no window")?;
if container.windows().is_empty() {
let container_id = container.id.clone();
self.remove_container_by_idx(container_idx);
if !self.focus_after_scrolling_removal(&container_id) {
self.focus_previous_container();
}
} else {
container.load_focused_window();
if let Some(window) = container.focused_window() {
@@ -1183,6 +1190,12 @@ impl Workspace {
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
// in a strip, left and right are the neighbouring columns and up and down are the tiles of
// the column, which the strip is the one to say
if self.is_scrolling() && self.scrolling.is_some() {
return self.scrolling_idx_for_direction(direction);
}
let len = NonZeroUsize::new(self.containers().len())?;
direction.destination(
@@ -1288,6 +1301,8 @@ impl Workspace {
next
} else if self.containers().is_empty() {
0
} else if let Some(idx) = self.scrolling_insertion_idx() {
idx
} else {
self.resolve_placement_index(&window)
};
@@ -1475,9 +1490,6 @@ impl Workspace {
Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
self.enforce_resize_for_ultrawide();
}
Layout::Default(DefaultLayout::Scrolling) => {
self.enforce_resize_for_scrolling();
}
_ => self.enforce_no_resize(),
}
}
@@ -1705,28 +1717,6 @@ impl Workspace {
}
}
fn enforce_resize_for_scrolling(&mut self) {
let resize_dimensions = &mut self.resize_dimensions;
match resize_dimensions.len() {
0 | 1 => self.enforce_no_resize(),
_ => {
let len = resize_dimensions.len();
for (i, rect) in resize_dimensions.iter_mut().enumerate() {
if let Some(rect) = rect {
rect.top = 0;
rect.bottom = 0;
if i == 0 {
rect.left = 0;
} else if i == len - 1 {
rect.right = 0;
}
}
}
}
}
}
fn enforce_no_resize(&mut self) {
for rect in self.resize_dimensions.iter_mut().flatten() {
rect.left = 0;
+36
View File
@@ -151,6 +151,7 @@ macro_rules! gen_enum_subcommand_args {
gen_enum_subcommand_args! {
Focus: OperationDirection,
Move: OperationDirection,
Consume: OperationDirection,
PreselectDirection: OperationDirection,
CycleFocus: CycleDirection,
CycleMove: CycleDirection,
@@ -1284,6 +1285,23 @@ enum SubCommand {
/// Set the number of visible columns for the Scrolling layout on the focused workspace
#[clap(arg_required_else_help = true)]
ScrollingLayoutColumns(ScrollingLayoutColumns),
/// Make the focused column of the Scrolling layout as wide as the screen, or put it back to
/// the width that it had
ScrollingLayoutFullWidth,
/// Scroll the Scrolling layout so that the focused column is in the middle of the screen, once
CenterFocus,
/// Make the focused column of the Scrolling layout fill the room that the columns in view leave
/// on the screen, which does nothing in any other layout
Expand,
/// Take the neighbouring window on one side into the focused column of the Scrolling layout
#[clap(arg_required_else_help = true)]
Consume(Consume),
/// Take the focused window out of its column in the Scrolling layout and put it in a column
/// of its own on the right
Expel,
/// Stack the windows of the focused column of the Scrolling layout, or spread the focused
/// stack out into a column with a window in each slot
ToggleColumnStack,
/// Set the layout column and row ratios for the focused workspace
LayoutRatios(LayoutRatios),
/// Load a custom layout from file for the focused workspace
@@ -2956,6 +2974,24 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::ScrollingLayoutColumns(args) => {
send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;
}
SubCommand::ScrollingLayoutFullWidth => {
send_message(&SocketMessage::ScrollingLayoutFullWidth)?;
}
SubCommand::CenterFocus => {
send_message(&SocketMessage::CenterFocus)?;
}
SubCommand::Expand => {
send_message(&SocketMessage::Expand)?;
}
SubCommand::Consume(args) => {
send_message(&SocketMessage::Consume(args.operation_direction))?;
}
SubCommand::Expel => {
send_message(&SocketMessage::Expel)?;
}
SubCommand::ToggleColumnStack => {
send_message(&SocketMessage::ToggleColumnStack)?;
}
SubCommand::LayoutRatios(args) => {
if args.columns.is_none() && args.rows.is_none() {
println!(