diff options
| author | Said Kadrioski <said@kadrioski.de> | 2025-08-28 03:42:04 +0200 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-08-29 09:13:52 +0300 |
| commit | 0c3223ac72f3bee35c954764c2012f0d495deeb0 (patch) | |
| tree | 5877b977aa4000f0fdb8ba22c9dbdc85062193f8 | |
| parent | 1ffda91e0cc3938af1a9d4f1f1b6a87afa3c210f (diff) | |
| download | niri-0c3223ac72f3bee35c954764c2012f0d495deeb0.tar.gz niri-0c3223ac72f3bee35c954764c2012f0d495deeb0.tar.bz2 niri-0c3223ac72f3bee35c954764c2012f0d495deeb0.zip | |
Add cycle back feature for presets of column/window width/height.
| -rw-r--r-- | niri-config/src/binds.rs | 20 | ||||
| -rw-r--r-- | niri-ipc/src/lib.rs | 18 | ||||
| -rw-r--r-- | resources/default-config.kdl | 2 | ||||
| -rw-r--r-- | src/input/mod.rs | 33 | ||||
| -rw-r--r-- | src/layout/floating.rs | 48 | ||||
| -rw-r--r-- | src/layout/mod.rs | 12 | ||||
| -rw-r--r-- | src/layout/scrolling.rs | 62 | ||||
| -rw-r--r-- | src/layout/tests.rs | 26 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 18 |
9 files changed, 187 insertions, 52 deletions
diff --git a/niri-config/src/binds.rs b/niri-config/src/binds.rs index f476ac2d..c5b4e6ff 100644 --- a/niri-config/src/binds.rs +++ b/niri-config/src/binds.rs @@ -277,12 +277,19 @@ pub enum Action { #[knuffel(skip)] ResetWindowHeightById(u64), SwitchPresetColumnWidth, + SwitchPresetColumnWidthBack, SwitchPresetWindowWidth, + SwitchPresetWindowWidthBack, #[knuffel(skip)] SwitchPresetWindowWidthById(u64), + #[knuffel(skip)] + SwitchPresetWindowWidthBackById(u64), SwitchPresetWindowHeight, + SwitchPresetWindowHeightBack, #[knuffel(skip)] SwitchPresetWindowHeightById(u64), + #[knuffel(skip)] + SwitchPresetWindowHeightBackById(u64), MaximizeColumn, SetColumnWidth(#[knuffel(argument, str)] SizeChange), ExpandColumnToAvailableWidth, @@ -525,16 +532,29 @@ impl From<niri_ipc::Action> for Action { niri_ipc::Action::ResetWindowHeight { id: None } => Self::ResetWindowHeight, niri_ipc::Action::ResetWindowHeight { id: Some(id) } => Self::ResetWindowHeightById(id), niri_ipc::Action::SwitchPresetColumnWidth {} => Self::SwitchPresetColumnWidth, + niri_ipc::Action::SwitchPresetColumnWidthBack {} => Self::SwitchPresetColumnWidthBack, niri_ipc::Action::SwitchPresetWindowWidth { id: None } => Self::SwitchPresetWindowWidth, + niri_ipc::Action::SwitchPresetWindowWidthBack { id: None } => { + Self::SwitchPresetWindowWidthBack + } niri_ipc::Action::SwitchPresetWindowWidth { id: Some(id) } => { Self::SwitchPresetWindowWidthById(id) } + niri_ipc::Action::SwitchPresetWindowWidthBack { id: Some(id) } => { + Self::SwitchPresetWindowWidthBackById(id) + } niri_ipc::Action::SwitchPresetWindowHeight { id: None } => { Self::SwitchPresetWindowHeight } + niri_ipc::Action::SwitchPresetWindowHeightBack { id: None } => { + Self::SwitchPresetWindowHeightBack + } niri_ipc::Action::SwitchPresetWindowHeight { id: Some(id) } => { Self::SwitchPresetWindowHeightById(id) } + niri_ipc::Action::SwitchPresetWindowHeightBack { id: Some(id) } => { + Self::SwitchPresetWindowHeightBackById(id) + } niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn, niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change), niri_ipc::Action::ExpandColumnToAvailableWidth {} => Self::ExpandColumnToAvailableWidth, diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 10d88de7..f01a1923 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -677,6 +677,8 @@ pub enum Action { }, /// Switch between preset column widths. SwitchPresetColumnWidth {}, + /// Switch between preset column widths backwards. + SwitchPresetColumnWidthBack {}, /// Switch between preset window widths. SwitchPresetWindowWidth { /// Id of the window whose width to switch. @@ -685,6 +687,14 @@ pub enum Action { #[cfg_attr(feature = "clap", arg(long))] id: Option<u64>, }, + /// Switch between preset window widths backwards. + SwitchPresetWindowWidthBack { + /// Id of the window whose width to switch. + /// + /// If `None`, uses the focused window. + #[cfg_attr(feature = "clap", arg(long))] + id: Option<u64>, + }, /// Switch between preset window heights. SwitchPresetWindowHeight { /// Id of the window whose height to switch. @@ -693,6 +703,14 @@ pub enum Action { #[cfg_attr(feature = "clap", arg(long))] id: Option<u64>, }, + /// Switch between preset window heights backwards. + SwitchPresetWindowHeightBack { + /// Id of the window whose height to switch. + /// + /// If `None`, uses the focused window. + #[cfg_attr(feature = "clap", arg(long))] + id: Option<u64>, + }, /// Toggle the maximized state of the focused column. MaximizeColumn {}, /// Change the width of the focused column. diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 8b0a93d1..fc140ef7 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -540,6 +540,8 @@ binds { Mod+Period { expel-window-from-column; } Mod+R { switch-preset-column-width; } + // Cycling through the presets in reverse order is also possible. + // Mod+R { switch-preset-column-width-back; } Mod+Shift+R { switch-preset-window-height; } Mod+Ctrl+R { reset-window-height; } Mod+F { maximize-column; } diff --git a/src/input/mod.rs b/src/input/mod.rs index 95d84b87..9fc48c09 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1442,26 +1442,49 @@ impl State { self.niri.queue_redraw_all(); } Action::SwitchPresetColumnWidth => { - self.niri.layout.toggle_width(); + self.niri.layout.toggle_width(true); + } + Action::SwitchPresetColumnWidthBack => { + self.niri.layout.toggle_width(false); } Action::SwitchPresetWindowWidth => { - self.niri.layout.toggle_window_width(None); + self.niri.layout.toggle_window_width(None, true); + } + Action::SwitchPresetWindowWidthBack => { + self.niri.layout.toggle_window_width(None, false); } Action::SwitchPresetWindowWidthById(id) => { let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); let window = window.map(|(_, m)| m.window.clone()); if let Some(window) = window { - self.niri.layout.toggle_window_width(Some(&window)); + self.niri.layout.toggle_window_width(Some(&window), true); + } + } + Action::SwitchPresetWindowWidthBackById(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + self.niri.layout.toggle_window_width(Some(&window), false); } } Action::SwitchPresetWindowHeight => { - self.niri.layout.toggle_window_height(None); + self.niri.layout.toggle_window_height(None, true); + } + Action::SwitchPresetWindowHeightBack => { + self.niri.layout.toggle_window_height(None, false); } Action::SwitchPresetWindowHeightById(id) => { let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); let window = window.map(|(_, m)| m.window.clone()); if let Some(window) = window { - self.niri.layout.toggle_window_height(Some(&window)); + self.niri.layout.toggle_window_height(Some(&window), true); + } + } + Action::SwitchPresetWindowHeightBackById(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + self.niri.layout.toggle_window_height(Some(&window), false); } } Action::CenterColumn => { diff --git a/src/layout/floating.rs b/src/layout/floating.rs index bd976d7d..ad67c5ff 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -624,7 +624,7 @@ impl<W: LayoutElement> FloatingSpace<W> { } } - pub fn toggle_window_width(&mut self, id: Option<&W::Id>) { + pub fn toggle_window_width(&mut self, id: Option<&W::Id>, forwards: bool) { let Some(id) = id.or(self.active_window_id.as_ref()).cloned() else { return; }; @@ -632,18 +632,22 @@ impl<W: LayoutElement> FloatingSpace<W> { let available_size = self.working_area.size.w; + let len = self.options.preset_column_widths.len(); let tile = &mut self.tiles[idx]; let preset_idx = if let Some(idx) = tile.floating_preset_width_idx { - (idx + 1) % self.options.preset_column_widths.len() + (idx + if forwards { 1 } else { len - 1 }) % len } else { let current_window = tile.window_expected_or_current_size().w; let current_tile = tile.tile_expected_or_current_size().w; - self.options + let mut it = self + .options .preset_column_widths .iter() - .position(|preset| { - let resolved = resolve_preset_size(*preset, available_size); + .map(|preset| resolve_preset_size(*preset, available_size)); + + if forwards { + it.position(|resolved| { match resolved { // Some allowance for fractional scaling purposes. ResolvedSize::Tile(resolved) => current_tile + 1. < resolved, @@ -651,6 +655,16 @@ impl<W: LayoutElement> FloatingSpace<W> { } }) .unwrap_or(0) + } else { + it.rposition(|resolved| { + match resolved { + // Some allowance for fractional scaling purposes. + ResolvedSize::Tile(resolved) => resolved + 1. < current_tile, + ResolvedSize::Window(resolved) => resolved + 1. < current_window, + } + }) + .unwrap_or(len - 1) + } }; let preset = self.options.preset_column_widths[preset_idx]; @@ -670,7 +684,7 @@ impl<W: LayoutElement> FloatingSpace<W> { true } - pub fn toggle_window_height(&mut self, id: Option<&W::Id>) { + pub fn toggle_window_height(&mut self, id: Option<&W::Id>, forwards: bool) { let Some(id) = id.or(self.active_window_id.as_ref()).cloned() else { return; }; @@ -678,18 +692,22 @@ impl<W: LayoutElement> FloatingSpace<W> { let available_size = self.working_area.size.h; + let len = self.options.preset_window_heights.len(); let tile = &mut self.tiles[idx]; let preset_idx = if let Some(idx) = tile.floating_preset_height_idx { - (idx + 1) % self.options.preset_window_heights.len() + (idx + if forwards { 1 } else { len - 1 }) % len } else { let current_window = tile.window_expected_or_current_size().h; let current_tile = tile.tile_expected_or_current_size().h; - self.options + let mut it = self + .options .preset_window_heights .iter() - .position(|preset| { - let resolved = resolve_preset_size(*preset, available_size); + .map(|preset| resolve_preset_size(*preset, available_size)); + + if forwards { + it.position(|resolved| { match resolved { // Some allowance for fractional scaling purposes. ResolvedSize::Tile(resolved) => current_tile + 1. < resolved, @@ -697,6 +715,16 @@ impl<W: LayoutElement> FloatingSpace<W> { } }) .unwrap_or(0) + } else { + it.rposition(|resolved| { + match resolved { + // Some allowance for fractional scaling purposes. + ResolvedSize::Tile(resolved) => resolved + 1. < current_tile, + ResolvedSize::Window(resolved) => resolved + 1. < current_window, + } + }) + .unwrap_or(len - 1) + } }; let preset = self.options.preset_window_heights[preset_idx]; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index d68afe6a..243730be 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -3179,14 +3179,14 @@ impl<W: LayoutElement> Layout<W> { self.options = options; } - pub fn toggle_width(&mut self) { + pub fn toggle_width(&mut self, forwards: bool) { let Some(workspace) = self.active_workspace_mut() else { return; }; - workspace.toggle_width(); + workspace.toggle_width(forwards); } - pub fn toggle_window_width(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_width(&mut self, window: Option<&W::Id>, forwards: bool) { if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { if window.is_none() || window == Some(move_.tile.window().id()) { return; @@ -3206,10 +3206,10 @@ impl<W: LayoutElement> Layout<W> { let Some(workspace) = workspace else { return; }; - workspace.toggle_window_width(window); + workspace.toggle_window_width(window, forwards); } - pub fn toggle_window_height(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_height(&mut self, window: Option<&W::Id>, forwards: bool) { if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { if window.is_none() || window == Some(move_.tile.window().id()) { return; @@ -3229,7 +3229,7 @@ impl<W: LayoutElement> Layout<W> { let Some(workspace) = workspace else { return; }; - workspace.toggle_window_height(window); + workspace.toggle_window_height(window, forwards); } pub fn toggle_full_width(&mut self) { diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 17e6673f..39892609 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -2522,13 +2522,13 @@ impl<W: LayoutElement> ScrollingSpace<W> { None } - pub fn toggle_width(&mut self) { + pub fn toggle_width(&mut self, forwards: bool) { if self.columns.is_empty() { return; } let col = &mut self.columns[self.active_column_idx]; - col.toggle_width(None); + col.toggle_width(None, forwards); cancel_resize_for_column(&mut self.interactive_resize, col); } @@ -2616,7 +2616,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { cancel_resize_for_column(&mut self.interactive_resize, col); } - pub fn toggle_window_width(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_width(&mut self, window: Option<&W::Id>, forwards: bool) { if self.columns.is_empty() { return; } @@ -2635,12 +2635,12 @@ impl<W: LayoutElement> ScrollingSpace<W> { (&mut self.columns[self.active_column_idx], None) }; - col.toggle_width(tile_idx); + col.toggle_width(tile_idx, forwards); cancel_resize_for_column(&mut self.interactive_resize, col); } - pub fn toggle_window_height(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_height(&mut self, window: Option<&W::Id>, forwards: bool) { if self.columns.is_empty() { return; } @@ -2659,7 +2659,7 @@ impl<W: LayoutElement> ScrollingSpace<W> { (&mut self.columns[self.active_column_idx], None) }; - col.toggle_window_height(tile_idx, true); + col.toggle_window_height(tile_idx, true, forwards); cancel_resize_for_column(&mut self.interactive_resize, col); } @@ -4557,7 +4557,7 @@ impl<W: LayoutElement> Column<W> { true } - fn toggle_width(&mut self, tile_idx: Option<usize>) { + fn toggle_width(&mut self, tile_idx: Option<usize>, forwards: bool) { let tile_idx = tile_idx.unwrap_or(self.active_tile_idx); let preset_idx = if self.is_full_width { @@ -4566,24 +4566,39 @@ impl<W: LayoutElement> Column<W> { self.preset_width_idx }; + let len = self.options.preset_column_widths.len(); let preset_idx = if let Some(idx) = preset_idx { - (idx + 1) % self.options.preset_column_widths.len() + (idx + if forwards { 1 } else { len - 1 }) % len } else { let tile = &self.tiles[tile_idx]; let current_window = tile.window_expected_or_current_size().w; let current_tile = tile.tile_expected_or_current_size().w; - self.options + let mut it = self + .options .preset_column_widths .iter() - .position(|prop| { - match self.resolve_preset_width(*prop) { + .map(|preset| self.resolve_preset_width(*preset)); + + if forwards { + it.position(|resolved| { + match resolved { // Some allowance for fractional scaling purposes. ResolvedSize::Tile(resolved) => current_tile + 1. < resolved, ResolvedSize::Window(resolved) => current_window + 1. < resolved, } }) .unwrap_or(0) + } else { + it.rposition(|resolved| { + match resolved { + // Some allowance for fractional scaling purposes. + ResolvedSize::Tile(resolved) => resolved + 1. < current_tile, + ResolvedSize::Window(resolved) => resolved + 1. < current_window, + } + }) + .unwrap_or(len - 1) + } }; let preset = self.options.preset_column_widths[preset_idx]; @@ -4744,7 +4759,7 @@ impl<W: LayoutElement> Column<W> { self.update_tile_sizes(animate); } - fn toggle_window_height(&mut self, tile_idx: Option<usize>, animate: bool) { + fn toggle_window_height(&mut self, tile_idx: Option<usize>, animate: bool, forwards: bool) { let tile_idx = tile_idx.unwrap_or(self.active_tile_idx); // Start by converting all heights to automatic, since only one window in the column can be @@ -4756,28 +4771,39 @@ impl<W: LayoutElement> Column<W> { self.convert_heights_to_auto(); } + let len = self.options.preset_window_heights.len(); let preset_idx = match self.data[tile_idx].height { - WindowHeight::Preset(idx) => (idx + 1) % self.options.preset_window_heights.len(), + WindowHeight::Preset(idx) => (idx + if forwards { 1 } else { len - 1 }) % len, _ => { let current = self.data[tile_idx].size.h; let tile = &self.tiles[tile_idx]; - self.options + + let mut it = self + .options .preset_window_heights .iter() .copied() - .position(|preset| { + .map(|preset| { let window_height = match self.resolve_preset_height(preset) { ResolvedSize::Tile(h) => tile.window_height_for_tile_height(h), ResolvedSize::Window(h) => h, }; - let resolved = tile.tile_height_for_window_height( - window_height.round().clamp(1., 100000.), - ); + tile.tile_height_for_window_height(window_height.round().clamp(1., 100000.)) + }); + if forwards { + it.position(|resolved| { // Some allowance for fractional scaling purposes. current + 1. < resolved }) .unwrap_or(0) + } else { + it.rposition(|resolved| { + // Some allowance for fractional scaling purposes. + resolved + 1. < current + }) + .unwrap_or(len - 1) + } } }; self.data[tile_idx].height = WindowHeight::Preset(preset_idx); diff --git a/src/layout/tests.rs b/src/layout/tests.rs index 04381079..27a05b26 100644 --- a/src/layout/tests.rs +++ b/src/layout/tests.rs @@ -548,14 +548,23 @@ enum Op { activate: bool, }, SwitchPresetColumnWidth, + SwitchPresetColumnWidthBack, SwitchPresetWindowWidth { #[proptest(strategy = "proptest::option::of(1..=5usize)")] id: Option<usize>, }, + SwitchPresetWindowWidthBack { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + id: Option<usize>, + }, SwitchPresetWindowHeight { #[proptest(strategy = "proptest::option::of(1..=5usize)")] id: Option<usize>, }, + SwitchPresetWindowHeightBack { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + id: Option<usize>, + }, MaximizeColumn, SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), SetWindowWidth { @@ -1231,14 +1240,23 @@ impl Op { layout.move_workspace_to_output_by_id(old_idx, Some(old_output), output); } - Op::SwitchPresetColumnWidth => layout.toggle_width(), + Op::SwitchPresetColumnWidth => layout.toggle_width(true), + Op::SwitchPresetColumnWidthBack => layout.toggle_width(false), Op::SwitchPresetWindowWidth { id } => { let id = id.filter(|id| layout.has_window(id)); - layout.toggle_window_width(id.as_ref()); + layout.toggle_window_width(id.as_ref(), true); + } + Op::SwitchPresetWindowWidthBack { id } => { + let id = id.filter(|id| layout.has_window(id)); + layout.toggle_window_width(id.as_ref(), false); } Op::SwitchPresetWindowHeight { id } => { let id = id.filter(|id| layout.has_window(id)); - layout.toggle_window_height(id.as_ref()); + layout.toggle_window_height(id.as_ref(), true); + } + Op::SwitchPresetWindowHeightBack { id } => { + let id = id.filter(|id| layout.has_window(id)); + layout.toggle_window_height(id.as_ref(), false); } Op::MaximizeColumn => layout.toggle_full_width(), Op::SetColumnWidth(change) => layout.set_column_width(change), @@ -3250,7 +3268,7 @@ fn preset_column_width_fixed_correct_with_border() { assert_eq!(win.requested_size().unwrap().w, 490); // However, preset fixed width will still work correctly. - layout.toggle_width(); + layout.toggle_width(true); let win = layout.windows().next().unwrap().1; assert_eq!(win.requested_size().unwrap().w, 500); } diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index c01df8fd..efba6ee6 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1107,11 +1107,11 @@ impl<W: LayoutElement> Workspace<W> { self.scrolling.center_visible_columns(); } - pub fn toggle_width(&mut self) { + pub fn toggle_width(&mut self, forwards: bool) { if self.floating_is_active.get() { - self.floating.toggle_window_width(None); + self.floating.toggle_window_width(None, forwards); } else { - self.scrolling.toggle_width(); + self.scrolling.toggle_width(forwards); } } @@ -1161,23 +1161,23 @@ impl<W: LayoutElement> Workspace<W> { self.scrolling.reset_window_height(window); } - pub fn toggle_window_width(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_width(&mut self, window: Option<&W::Id>, forwards: bool) { if window.map_or(self.floating_is_active.get(), |id| { self.floating.has_window(id) }) { - self.floating.toggle_window_width(window); + self.floating.toggle_window_width(window, forwards); } else { - self.scrolling.toggle_window_width(window); + self.scrolling.toggle_window_width(window, forwards); } } - pub fn toggle_window_height(&mut self, window: Option<&W::Id>) { + pub fn toggle_window_height(&mut self, window: Option<&W::Id>, forwards: bool) { if window.map_or(self.floating_is_active.get(), |id| { self.floating.has_window(id) }) { - self.floating.toggle_window_height(window); + self.floating.toggle_window_height(window, forwards); } else { - self.scrolling.toggle_window_height(window); + self.scrolling.toggle_window_height(window, forwards); } } |
