diff --git a/niri-config/src/layout.rs b/niri-config/src/layout.rs index f84528bd33..a8d07797bc 100644 --- a/niri-config/src/layout.rs +++ b/niri-config/src/layout.rs @@ -24,6 +24,7 @@ pub struct Layout { pub gaps: f64, pub struts: Struts, pub background_color: Color, + pub auto_tile: Option, } impl Default for Layout { @@ -52,6 +53,7 @@ impl Default for Layout { PresetSize::Proportion(2. / 3.), ], background_color: DEFAULT_BACKGROUND_COLOR, + auto_tile: None, } } } @@ -91,6 +93,10 @@ impl MergeWith for Layout { if self.preset_window_heights.is_empty() { self.preset_window_heights = Layout::default().preset_window_heights; } + + if let Some(x) = &part.auto_tile { + self.auto_tile = Some(x.clone()); + } } } @@ -126,6 +132,16 @@ pub struct LayoutPart { pub struts: Option, #[knuffel(child)] pub background_color: Option, + #[knuffel(child)] + pub auto_tile: Option, +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub struct AutoTile { + /// Maximum number of columns to auto-tile before scrolling kicks in. + /// Defaults to 2 (full screen, then 50/50, then scroll). + #[knuffel(child, unwrap(argument), default = 2)] + pub max_columns: u16, } #[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index b61fe1c1a0..7213eca592 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1451,6 +1451,7 @@ mod tests { b: 0.25, a: 1.0, }, + auto_tile: None, }, prefer_no_csd: true, cursor: Cursor { diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 69d0ed62a7..9ee4753254 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -91,6 +91,10 @@ pub struct ScrollingSpace { /// Configurable properties of the layout. options: Rc, + + /// Number of columns that are currently auto-tiled (filling the screen with dwindle layout). + /// These are always the first N columns. Columns beyond this count are in scrolling mode. + auto_tile_count: usize, } niri_render_elements! { @@ -306,6 +310,7 @@ impl ScrollingSpace { scale, clock, options, + auto_tile_count: 0, } } @@ -959,6 +964,49 @@ impl ScrollingSpace { self.add_tile(Some(col_idx), tile, activate, width, is_full_width, None); } + fn is_auto_tile_enabled(&self) -> bool { + self.options.layout.auto_tile.is_some() + } + + fn auto_tile_max(&self) -> usize { + match &self.options.layout.auto_tile { + Some(at) => at.max_columns as usize, + None => 0, + } + } + + /// Returns whether auto-tiling should apply to a new column at `idx`. + fn should_auto_tile(&self, idx: usize) -> bool { + if !self.is_auto_tile_enabled() { + return false; + } + + let max = self.auto_tile_max(); + if self.auto_tile_count >= max { + return false; + } + + idx <= self.auto_tile_count + } + + /// Redistribute widths of all auto-tiled columns to fill the screen equally. + fn redistribute_auto_tile_widths(&mut self) { + if self.auto_tile_count == 0 { + return; + } + + let prop = 1.0 / self.auto_tile_count as f64; + for i in 0..self.auto_tile_count { + if i >= self.columns.len() { + break; + } + let col = &mut self.columns[i]; + col.set_width_proportion(prop); + col.update_tile_sizes(true); + self.data[i].update(col); + } + } + pub fn add_column( &mut self, idx: Option, @@ -976,6 +1024,9 @@ impl ScrollingSpace { } }); + // Determine if this new column should be auto-tiled. + let do_auto_tile = self.should_auto_tile(idx); + column.update_config( self.view_size, self.working_area, @@ -990,6 +1041,11 @@ impl ScrollingSpace { self.active_column_idx += 1; } + if do_auto_tile { + self.auto_tile_count += 1; + self.redistribute_auto_tile_widths(); + } + // Animate movement of other columns. let offset = self.column_x(idx + 1) - self.column_x(idx); let config = anim_config.unwrap_or(self.options.animations.window_movement.0); @@ -1179,6 +1235,12 @@ impl ScrollingSpace { let column = self.columns.remove(column_idx); self.data.remove(column_idx); + // If the removed column was in the auto-tile zone, redistribute. + if column_idx < self.auto_tile_count { + self.auto_tile_count -= 1; + self.redistribute_auto_tile_widths(); + } + // Stop interactive resize. if let Some(resize) = &self.interactive_resize { if column @@ -4854,6 +4916,13 @@ impl Column { self.update_tile_sizes(true); } + fn set_width_proportion(&mut self, proportion: f64) { + self.width = ColumnWidth::Proportion(proportion); + self.is_full_width = false; + self.is_pending_maximized = false; + self.preset_width_idx = None; + } + fn set_column_width(&mut self, change: SizeChange, tile_idx: Option, animate: bool) { let current = if self.is_full_width || self.is_pending_maximized { ColumnWidth::Proportion(1.)