diff options
| -rw-r--r-- | niri-config/src/lib.rs | 2 | ||||
| -rw-r--r-- | niri-ipc/src/lib.rs | 2 | ||||
| -rw-r--r-- | resources/default-config.kdl | 5 | ||||
| -rw-r--r-- | src/input/mod.rs | 3 | ||||
| -rw-r--r-- | src/layout/mod.rs | 7 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 4 | ||||
| -rw-r--r-- | src/layout/scrolling.rs | 83 | ||||
| -rw-r--r-- | src/layout/tests.rs | 2 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 7 |
9 files changed, 115 insertions, 0 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 769f5eba..268d8dca 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1593,6 +1593,7 @@ pub enum Action { SwitchPresetWindowHeightById(u64), MaximizeColumn, SetColumnWidth(#[knuffel(argument, str)] SizeChange), + ExpandColumnToAvailableWidth, SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget), ShowHotkeyOverlay, MoveWorkspaceToMonitorLeft, @@ -1794,6 +1795,7 @@ impl From<niri_ipc::Action> for Action { } niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn, niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change), + niri_ipc::Action::ExpandColumnToAvailableWidth {} => Self::ExpandColumnToAvailableWidth, niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout), niri_ipc::Action::ShowHotkeyOverlay {} => Self::ShowHotkeyOverlay, niri_ipc::Action::MoveWorkspaceToMonitorLeft {} => Self::MoveWorkspaceToMonitorLeft, diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 3a585eb3..69a355f1 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -539,6 +539,8 @@ pub enum Action { #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))] change: SizeChange, }, + /// Expand the focused column to space not taken up by other fully visible columns. + ExpandColumnToAvailableWidth {}, /// Switch between keyboard layouts. SwitchLayout { /// Layout to switch to. diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 133a542f..d0f4398b 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -504,6 +504,11 @@ binds { Mod+Ctrl+R { reset-window-height; } Mod+F { maximize-column; } Mod+Shift+F { fullscreen-window; } + + // Expand the focused column to space not taken up by other fully visible columns. + // Makes the column "fill the rest of the space". + Mod+Ctrl+F { expand-column-to-available-width; } + Mod+C { center-column; } // Finer width adjustments. diff --git a/src/input/mod.rs b/src/input/mod.rs index b38bc0e9..d7c9cea4 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1511,6 +1511,9 @@ impl State { self.niri.layout.reset_window_height(Some(&window)); } } + Action::ExpandColumnToAvailableWidth => { + self.niri.layout.expand_column_to_available_width(); + } Action::ShowHotkeyOverlay => { if self.niri.hotkey_overlay.show() { self.niri.queue_redraw_all(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 1f99f9d1..b46f5e3c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -2977,6 +2977,13 @@ impl<W: LayoutElement> Layout<W> { workspace.reset_window_height(window); } + pub fn expand_column_to_available_width(&mut self) { + let Some(monitor) = self.active_monitor() else { + return; + }; + monitor.expand_column_to_available_width(); + } + pub fn toggle_window_floating(&mut self, window: Option<&W::Id>) { if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { if window.is_none() || window == Some(move_.tile.window().id()) { diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 8abaf5f6..1aae32e0 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -840,6 +840,10 @@ impl<W: LayoutElement> Monitor<W> { self.active_workspace().set_column_width(change); } + pub fn expand_column_to_available_width(&mut self) { + self.active_workspace().expand_column_to_available_width(); + } + pub fn move_workspace_down(&mut self) { let mut new_idx = min(self.active_workspace_idx + 1, self.workspaces.len() - 1); if new_idx == self.active_workspace_idx { diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 926cfd76..417def6a 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -2524,6 +2524,89 @@ impl<W: LayoutElement> ScrollingSpace<W> { cancel_resize_for_column(&mut self.interactive_resize, col); } + pub fn expand_column_to_available_width(&mut self) { + if self.columns.is_empty() { + return; + } + + let col = &mut self.columns[self.active_column_idx]; + if col.is_fullscreen || col.is_full_width { + return; + } + + if self.is_centering_focused_column() { + // Always-centered mode is different since the active window position cannot be + // controlled (it's always at the center). I guess you could come up with different + // logic here that computes the width in such a way so as to leave nearby columns fully + // on screen while taking into account that the active column will remain centered + // after resizing. But I'm not sure it's that useful? So let's do the simple thing. + let col = &mut self.columns[self.active_column_idx]; + col.set_column_width(SizeChange::SetProportion(1.), None, true); + cancel_resize_for_column(&mut self.interactive_resize, col); + return; + } + + // Consider the end of an ongoing animation because that's what compute to fit does too. + let view_x = self.target_view_pos(); + let working_x = self.working_area.loc.x; + let working_w = self.working_area.size.w; + + // Count all columns that are fully visible inside the working area. + let mut width_taken = 0.; + let mut leftmost_col_x = None; + let mut active_col_x = None; + + let gap = self.options.gaps; + let col_xs = self.column_xs(self.data.iter().copied()); + for (idx, col_x) in col_xs.take(self.columns.len()).enumerate() { + if col_x < view_x + working_x + gap { + // Column goes off-screen to the left. + continue; + } + + leftmost_col_x.get_or_insert(col_x); + + let width = self.data[idx].width; + if view_x + working_x + working_w < col_x + width + gap { + // Column goes off-screen to the right. We can stop here. + break; + } + + if idx == self.active_column_idx { + active_col_x = Some(col_x); + } + + width_taken += width + gap; + } + + if active_col_x.is_none() { + // The active column wasn't fully on screen, so we can't meaningfully do anything. + return; + } + + let available_width = working_w - gap - width_taken; + if available_width <= 0. { + // Nowhere to expand. + return; + } + + let active_width = self.data[self.active_column_idx].width; + + let col = &mut self.columns[self.active_column_idx]; + col.width = ColumnWidth::Fixed(active_width + available_width); + col.preset_width_idx = None; + col.is_full_width = false; + col.update_tile_sizes(true); + + cancel_resize_for_column(&mut self.interactive_resize, col); + + // Put the leftmost window into the view. + let new_view_x = leftmost_col_x.unwrap() - gap - working_x; + self.animate_view_offset(self.active_column_idx, new_view_x - active_col_x.unwrap()); + // Just in case. + self.animate_view_offset_to_column(None, self.active_column_idx, None); + } + pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) -> bool { let (mut col_idx, tile_idx) = self .columns diff --git a/src/layout/tests.rs b/src/layout/tests.rs index 813f4765..9d3e8634 100644 --- a/src/layout/tests.rs +++ b/src/layout/tests.rs @@ -496,6 +496,7 @@ enum Op { #[proptest(strategy = "proptest::option::of(1..=5usize)")] id: Option<usize>, }, + ExpandColumnToAvailableWidth, ToggleWindowFloating { #[proptest(strategy = "proptest::option::of(1..=5usize)")] id: Option<usize>, @@ -1133,6 +1134,7 @@ impl Op { let id = id.filter(|id| layout.has_window(id)); layout.reset_window_height(id.as_ref()); } + Op::ExpandColumnToAvailableWidth => layout.expand_column_to_available_width(), Op::ToggleWindowFloating { id } => { let id = id.filter(|id| layout.has_window(id)); layout.toggle_window_floating(id.as_ref()); diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 54926a1f..15f6b9b0 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1128,6 +1128,13 @@ impl<W: LayoutElement> Workspace<W> { } } + pub fn expand_column_to_available_width(&mut self) { + if self.floating_is_active.get() { + return; + } + self.scrolling.expand_column_to_available_width(); + } + pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) { let mut unfullscreen_to_floating = false; if self.floating.has_window(window) { |
