diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-11-08 10:26:07 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-11-08 11:27:25 +0400 |
| commit | dc9cee4589495204f56c4cc53da1d18ea1f8764c (patch) | |
| tree | d23970abcda2a618d8216ab9194fcc827c5f06ee | |
| parent | 8056dd0c7ecc3abd0afea988a74eb8664ebe7c8a (diff) | |
| download | niri-dc9cee4589495204f56c4cc53da1d18ea1f8764c.tar.gz niri-dc9cee4589495204f56c4cc53da1d18ea1f8764c.tar.bz2 niri-dc9cee4589495204f56c4cc53da1d18ea1f8764c.zip | |
layout: Implement auto height distribution
Takes into account min and, partially, max window heights, and adds 1 px
when necessary to account for uneven division.
| -rw-r--r-- | src/layout.rs | 139 |
1 files changed, 123 insertions, 16 deletions
diff --git a/src/layout.rs b/src/layout.rs index 00d2903e..aec29dd0 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -282,6 +282,17 @@ impl From<PresetWidth> for ColumnWidth { } /// Height of a window in a column. +/// +/// Proportional height is intentionally omitted. With column widths you frequently want e.g. two +/// columns side-by-side with 50% width each, and you want them to remain this way when moving to a +/// differently sized monitor. Windows in a column, however, already auto-size to fill the available +/// height, giving you this behavior. The only reason to set a different window height, then, is +/// when you want something in the window to fit exactly, e.g. to fit 30 lines in a terminal, which +/// corresponds to the `Fixed` variant. +/// +/// This does not preclude the usual set of binds to set or resize a window proportionally. Just, +/// they are converted to, and stored as fixed height right away, so that once you resize a window +/// to fit the desired content, it can never become smaller than that when moving between monitors. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WindowHeight { /// Automatically computed height, evenly distributed across the column. @@ -2651,10 +2662,6 @@ impl<W: LayoutElement> Column<W> { } } - fn window_count(&self) -> usize { - self.windows.len() - } - fn set_width(&mut self, width: ColumnWidth) { self.width = width; self.update_window_sizes(); @@ -2682,11 +2689,14 @@ impl<W: LayoutElement> Column<W> { return; } - let min_width = self - .windows + let min_size: Vec<_> = self.windows.iter().map(LayoutElement::min_size).collect(); + let max_size: Vec<_> = self.windows.iter().map(LayoutElement::max_size).collect(); + + // Compute the column width. + let min_width = min_size .iter() - .filter_map(|win| { - let w = win.min_size().w; + .filter_map(|size| { + let w = size.w; if w == 0 { None } else { @@ -2695,11 +2705,10 @@ impl<W: LayoutElement> Column<W> { }) .max() .unwrap_or(1); - let max_width = self - .windows + let max_width = max_size .iter() - .filter_map(|win| { - let w = win.max_size().w; + .filter_map(|size| { + let w = size.w; if w == 0 { None } else { @@ -2711,11 +2720,109 @@ impl<W: LayoutElement> Column<W> { let max_width = max(max_width, min_width); let width = self.width.resolve(&self.options, self.working_area.size.w); - let height = (self.working_area.size.h - self.options.gaps) / self.window_count() as i32 - - self.options.gaps; - let size = Size::from((max(min(width, max_width), min_width), max(height, 1))); + let width = max(min(width, max_width), min_width); + + // Compute the window heights. + let mut heights = self.heights.clone(); + let mut height_left = self.working_area.size.h - self.options.gaps; + let mut auto_windows_left = self.windows.len(); + + // Subtract all fixed-height windows. + for (h, (min_size, max_size)) in zip(&mut heights, zip(&min_size, &max_size)) { + // Check if the window has an exact height constraint. + if min_size.h > 0 && min_size.h == max_size.h { + *h = WindowHeight::Fixed(min_size.h); + } + + if let WindowHeight::Fixed(h) = h { + if max_size.h > 0 { + *h = min(*h, max_size.h); + } + if min_size.h > 0 { + *h = max(*h, min_size.h); + } + *h = max(*h, 1); + + height_left -= *h + self.options.gaps; + auto_windows_left -= 1; + } + } + + // Iteratively try to distribute the remaining height, checking against window min heights. + // Pick an auto height according to the current sizes, then check if it satisfies all + // remaining min heights. If not, allocate fixed height to those windows and repeat the + // loop. On each iteration the auto height will get smaller. + // + // NOTE: we do not respect max height here. Doing so would complicate things: if the current + // auto height is above some window's max height, then the auto height can become larger. + // Combining this with the min height loop is where the complexity appears. + // + // However, most max height uses are for fixed-size dialogs, where min height == max_height. + // This case is separately handled above. + while auto_windows_left > 0 { + // Compute the current auto height. + let auto_height = height_left / auto_windows_left as i32 - self.options.gaps; + let auto_height = max(auto_height, 1); + + // Integer division above can result in imperfect height distribution. We will make some + // windows 1 px taller to account for this. + let mut ones_left = height_left + .saturating_sub((auto_height + self.options.gaps) * auto_windows_left as i32); + + let mut unsatisfied_min = false; + let mut ones_left_2 = ones_left; + for (h, min_size) in zip(&mut heights, &min_size) { + if matches!(h, WindowHeight::Fixed(_)) { + continue; + } + + let mut auto = auto_height; + if ones_left_2 > 0 { + auto += 1; + ones_left_2 -= 1; + } + + // Check if the auto height satisfies the min height. + if min_size.h > 0 && min_size.h > auto { + *h = WindowHeight::Fixed(min_size.h); + height_left -= min_size.h + self.options.gaps; + auto_windows_left -= 1; + unsatisfied_min = true; + } + } + + // If some min height was unsatisfied, then we allocated the window more than the auto + // height, which means that the remaining auto windows now have less height to work + // with, and the loop must run again. + if unsatisfied_min { + continue; + } + + // All min heights were satisfied, fill them in. + for h in &mut heights { + if matches!(h, WindowHeight::Fixed(_)) { + continue; + } + + let mut auto = auto_height; + if ones_left > 0 { + auto += 1; + ones_left -= 1; + } + + *h = WindowHeight::Fixed(auto); + auto_windows_left -= 1; + } + + assert_eq!(auto_windows_left, 0); + } + + for (win, h) in zip(&self.windows, heights) { + let WindowHeight::Fixed(height) = h else { + unreachable!() + }; - for win in &self.windows { + let size = Size::from((width, height)); win.request_size(size); } } |
