diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-27 21:51:42 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-27 21:51:42 +0400 |
| commit | c21805bf705bd36a6eb7f79039b759e9af79dfcb (patch) | |
| tree | e24213b023c8129320daa782c8b0830ea334d519 | |
| parent | bfc24182670a0b3e17f79d66474fd291b7110732 (diff) | |
| download | niri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.tar.gz niri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.tar.bz2 niri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.zip | |
layout: Refactor to support window decorations, add border and fullscreen backdrop
Windows are now wrapped in Tiles, which keep track of window-specific
decorations. Particularly, I implemented a black fullscreen backdrop,
which finally brings fullscreened windows smaller than the screen in
line with how the Wayland protocol says they should lookâcentered in a
black rectangle. I also implemented window borders, which are similar to
the focus ring, but always visible (and hence affect the layout and
sizing).
| -rw-r--r-- | resources/default-config.kdl | 11 | ||||
| -rw-r--r-- | src/config.rs | 35 | ||||
| -rw-r--r-- | src/layout/focus_ring.rs | 8 | ||||
| -rw-r--r-- | src/layout/mod.rs | 22 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 2 | ||||
| -rw-r--r-- | src/layout/tile.rs | 267 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 153 |
7 files changed, 454 insertions, 44 deletions
diff --git a/resources/default-config.kdl b/resources/default-config.kdl index a028488a..f2c567f3 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -89,6 +89,17 @@ focus-ring { inactive-color 80 80 80 255 } +// You can also add a border. It's similar to the focus ring, but always visible. +border { + // The settings are the same as for the focus ring. + // If you enable the border, you probably want to disable the focus ring. + off + + width 4 + active-color 255 200 127 255 + inactive-color 80 80 80 255 +} + cursor { // Change the theme and size of the cursor as well as set the // `XCURSOR_THEME` and `XCURSOR_SIZE` env variables. diff --git a/src/config.rs b/src/config.rs index b5e98adf..ff096017 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,6 +18,8 @@ pub struct Config { pub spawn_at_startup: Vec<SpawnAtStartup>, #[knuffel(child, default)] pub focus_ring: FocusRing, + #[knuffel(child, default = default_border())] + pub border: FocusRing, #[knuffel(child, default)] pub prefer_no_csd: bool, #[knuffel(child, default)] @@ -190,6 +192,15 @@ impl Default for FocusRing { } } +pub const fn default_border() -> FocusRing { + FocusRing { + off: true, + width: 4, + active_color: Color::new(255, 200, 127, 255), + inactive_color: Color::new(80, 80, 80, 255), + } +} + #[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Color { #[knuffel(argument)] @@ -203,7 +214,7 @@ pub struct Color { } impl Color { - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r, g, b, a } } } @@ -590,6 +601,12 @@ mod tests { inactive-color 255 200 100 0 } + border { + width 3 + active-color 0 100 200 255 + inactive-color 255 200 100 0 + } + prefer-no-csd cursor { @@ -680,6 +697,22 @@ mod tests { a: 0, }, }, + border: FocusRing { + off: false, + width: 3, + active_color: Color { + r: 0, + g: 100, + b: 200, + a: 255, + }, + inactive_color: Color { + r: 255, + g: 200, + b: 100, + a: 0, + }, + }, prefer_no_csd: true, cursor: Cursor { xcursor_theme: String::from("breeze_cursors"), diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs index cbcde19c..b5ead3e2 100644 --- a/src/layout/focus_ring.rs +++ b/src/layout/focus_ring.rs @@ -105,4 +105,12 @@ impl FocusRing { rv.into_iter() } + + pub fn width(&self) -> i32 { + self.width + } + + pub fn is_off(&self) -> bool { + self.is_off + } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 61aaf211..8d454728 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -56,6 +56,7 @@ use crate::utils::output_size; mod focus_ring; mod monitor; +mod tile; mod workspace; pub trait LayoutElement: PartialEq { @@ -97,6 +98,11 @@ pub trait LayoutElement: PartialEq { fn set_preferred_scale_transform(&self, scale: i32, transform: Transform); fn output_enter(&self, output: &Output); fn output_leave(&self, output: &Output); + + /// Whether the element is currently fullscreen. + /// + /// This will *not* switch immediately after a [`LayoutElement::request_fullscreen()`] call. + fn is_fullscreen(&self) -> bool; } #[derive(Debug)] @@ -132,6 +138,7 @@ pub struct Options { /// Extra padding around the working area in logical pixels. struts: Struts, focus_ring: config::FocusRing, + border: config::FocusRing, /// Column widths that `toggle_width()` switches between. preset_widths: Vec<ColumnWidth>, /// Initial width for new windows. @@ -144,6 +151,7 @@ impl Default for Options { gaps: 16, struts: Default::default(), focus_ring: Default::default(), + border: config::default_border(), preset_widths: vec![ ColumnWidth::Proportion(1. / 3.), ColumnWidth::Proportion(0.5), @@ -180,6 +188,7 @@ impl Options { gaps: config.gaps.into(), struts: config.struts, focus_ring: config.focus_ring, + border: config.border, preset_widths, default_width, } @@ -269,6 +278,13 @@ impl LayoutElement for Window { fn output_leave(&self, output: &Output) { SpaceElement::output_leave(self, output) } + + fn is_fullscreen(&self) -> bool { + self.toplevel() + .current_state() + .states + .contains(xdg_toplevel::State::Fullscreen) + } } impl<W: LayoutElement> Layout<W> { @@ -701,7 +717,7 @@ impl<W: LayoutElement> Layout<W> { } let col = &ws.columns[ws.active_column_idx]; - Some((&col.windows[col.active_window_idx], &mon.output)) + Some((&col.windows[col.active_window_idx].window(), &mon.output)) } pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ { @@ -1458,6 +1474,10 @@ mod tests { fn output_enter(&self, _output: &Output) {} fn output_leave(&self, _output: &Output) {} + + fn is_fullscreen(&self) -> bool { + false + } } fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> { diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 8654af0e..77569a0c 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -345,7 +345,7 @@ impl<W: LayoutElement> Monitor<W> { } let column = &workspace.columns[workspace.active_column_idx]; - Some(&column.windows[column.active_window_idx]) + Some(column.windows[column.active_window_idx].window()) } pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) { diff --git a/src/layout/tile.rs b/src/layout/tile.rs new file mode 100644 index 00000000..2e8dcd90 --- /dev/null +++ b/src/layout/tile.rs @@ -0,0 +1,267 @@ +use std::cmp::max; +use std::rc::Rc; +use std::time::Duration; + +use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; +use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; +use smithay::backend::renderer::element::Kind; +use smithay::backend::renderer::{ImportAll, Renderer}; +use smithay::utils::{Logical, Point, Scale, Size}; + +use super::focus_ring::FocusRing; +use super::workspace::WorkspaceRenderElement; +use super::{LayoutElement, Options}; + +/// Toplevel window with decorations. +#[derive(Debug)] +pub struct Tile<W: LayoutElement> { + /// The toplevel window itself. + window: W, + + /// The border around the window. + border: FocusRing, + + /// Whether this tile is fullscreen. + /// + /// This will update only when the `window` actually goes fullscreen, rather than right away, + /// to avoid black backdrop flicker before the window has had a chance to resize. + is_fullscreen: bool, + + /// The black backdrop for fullscreen windows. + fullscreen_backdrop: SolidColorBuffer, + + /// The size we were requested to fullscreen into. + fullscreen_size: Size<i32, Logical>, + + /// Configurable properties of the layout. + options: Rc<Options>, +} + +impl<W: LayoutElement> Tile<W> { + pub fn new(window: W, options: Rc<Options>) -> Self { + Self { + window, + border: FocusRing::new(options.border), + is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size. + fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]), + fullscreen_size: Default::default(), + options, + } + } + + pub fn update_config(&mut self, options: Rc<Options>) { + self.border.update_config(options.border); + self.options = options; + } + + pub fn advance_animations(&mut self, _current_time: Duration, is_active: bool) { + let width = self.border.width(); + self.border.update( + (width, width).into(), + self.window.size(), + self.window.has_ssd(), + ); + self.border.set_active(is_active); + + // FIXME: remove when we can get a fullscreen size right away. + if self.fullscreen_size != Size::from((0, 0)) { + self.is_fullscreen = self.window.is_fullscreen(); + } + } + + pub fn window(&self) -> &W { + &self.window + } + + pub fn into_window(self) -> W { + self.window + } + + /// Returns `None` if the border is hidden and `Some(width)` if it should be shown. + fn effective_border_width(&self) -> Option<i32> { + if self.is_fullscreen { + return None; + } + + if self.border.is_off() { + return None; + } + + Some(self.border.width()) + } + + /// Returns the location of the window's visual geometry within this Tile. + fn window_loc(&self) -> Point<i32, Logical> { + let mut loc = Point::from((0, 0)); + + // In fullscreen, center the window in the given size. + if self.is_fullscreen { + let window_size = self.window.size(); + let target_size = self.fullscreen_size; + + // Windows aren't supposed to be larger than the fullscreen size, but in case we get + // one, leave it at the top-left as usual. + if window_size.w < target_size.w { + loc.x += (target_size.w - window_size.w) / 2; + } + if window_size.h < target_size.h { + loc.y += (target_size.h - window_size.h) / 2; + } + } + + if let Some(width) = self.effective_border_width() { + loc += (width, width).into(); + } + + loc + } + + pub fn tile_size(&self) -> Size<i32, Logical> { + let mut size = self.window.size(); + + if self.is_fullscreen { + // Normally we'd just return the fullscreen size here, but this makes things a bit + // nicer if a fullscreen window is bigger than the fullscreen size for some reason. + size.w = max(size.w, self.fullscreen_size.w); + size.h = max(size.h, self.fullscreen_size.h); + return size; + } + + if let Some(width) = self.effective_border_width() { + size += (width * 2, width * 2).into(); + } + + size + } + + pub fn window_size(&self) -> Size<i32, Logical> { + self.window.size() + } + + pub fn buf_loc(&self) -> Point<i32, Logical> { + let mut loc = Point::from((0, 0)); + loc += self.window_loc(); + loc += self.window.buf_loc(); + loc + } + + pub fn is_in_input_region(&self, mut point: Point<f64, Logical>) -> bool { + point -= self.window_loc().to_f64(); + self.window.is_in_input_region(point) + } + + pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>) { + // Can't go through effective_border_width() because we might be fullscreen. + if !self.border.is_off() { + let width = self.border.width(); + size.w = max(1, size.w - width * 2); + size.h = max(1, size.h - width * 2); + } + + self.window.request_size(size); + } + + pub fn tile_width_for_window_width(&self, size: i32) -> i32 { + if self.border.is_off() { + size + } else { + size + self.border.width() * 2 + } + } + + pub fn tile_height_for_window_height(&self, size: i32) -> i32 { + if self.border.is_off() { + size + } else { + size + self.border.width() * 2 + } + } + + pub fn window_height_for_tile_height(&self, size: i32) -> i32 { + if self.border.is_off() { + size + } else { + size - self.border.width() * 2 + } + } + + pub fn request_fullscreen(&mut self, size: Size<i32, Logical>) { + self.fullscreen_backdrop.resize(size); + self.fullscreen_size = size; + self.window.request_fullscreen(size); + } + + pub fn min_size(&self) -> Size<i32, Logical> { + let mut size = self.window.min_size(); + + if let Some(width) = self.effective_border_width() { + size.w = max(1, size.w); + size.h = max(1, size.h); + size += (width * 2, width * 2).into(); + } + + size + } + + pub fn max_size(&self) -> Size<i32, Logical> { + let mut size = self.window.max_size(); + + if let Some(width) = self.effective_border_width() { + if size.w > 0 { + size.w += width * 2; + } + if size.h > 0 { + size.h += width * 2; + } + } + + size + } + + pub fn has_ssd(&self) -> bool { + self.effective_border_width().is_some() || self.window.has_ssd() + } + + pub fn render<R: Renderer + ImportAll>( + &self, + renderer: &mut R, + location: Point<i32, Logical>, + scale: Scale<f64>, + ) -> Vec<WorkspaceRenderElement<R>> + where + <R as Renderer>::TextureId: 'static, + { + let mut rv = Vec::new(); + + let window_pos = location + self.window_loc(); + rv.extend(self.window.render(renderer, window_pos, scale)); + + if self.effective_border_width().is_some() { + rv.extend( + self.border + .render(scale) + .map(|elem| { + RelocateRenderElement::from_element( + elem, + location.to_physical_precise_round(scale), + Relocate::Relative, + ) + }) + .map(Into::into), + ); + } + + if self.is_fullscreen { + let elem = SolidColorRenderElement::from_buffer( + &self.fullscreen_backdrop, + location.to_physical_precise_round(scale), + scale, + 1., + Kind::Unspecified, + ); + rv.push(elem.into()); + } + + rv + } +} diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 2187fee0..954e6ff5 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use std::time::Duration; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; +use smithay::backend::renderer::element::utils::RelocateRenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::ImportAll; use smithay::desktop::space::SpaceElement; @@ -14,6 +15,7 @@ use smithay::render_elements; use smithay::utils::{Logical, Point, Rectangle, Scale, Size}; use super::focus_ring::{FocusRing, FocusRingRenderElement}; +use super::tile::Tile; use super::{LayoutElement, Options}; use crate::animation::Animation; use crate::config::{PresetWidth, SizeChange, Struts}; @@ -83,6 +85,7 @@ render_elements! { pub WorkspaceRenderElement<R> where R: ImportAll; Wayland = WaylandSurfaceRenderElement<R>, FocusRing = FocusRingRenderElement, + Border = RelocateRenderElement<FocusRingRenderElement>, } /// Width of a column. @@ -121,17 +124,19 @@ pub enum WindowHeight { #[derive(Debug)] pub struct Column<W: LayoutElement> { - /// Windows in this column. + /// Tiles in this column. /// /// Must be non-empty. - pub windows: Vec<W>, + pub windows: Vec<Tile<W>>, /// Heights of the windows. /// /// Must have the same number of elements as `windows`. + /// + /// These heights are window heights, so they exclude tile decorations, if any. heights: Vec<WindowHeight>, - /// Index of the currently active window. + /// Index of the currently active tile. pub active_window_idx: usize, /// Desired width of this column. @@ -231,11 +236,20 @@ impl<W: LayoutElement> Workspace<W> { let view_pos = self.view_pos(); + for (col_idx, col) in self.columns.iter_mut().enumerate() { + for (win_idx, tile) in col.windows.iter_mut().enumerate() { + let is_active = is_active + && col_idx == self.active_column_idx + && win_idx == col.active_window_idx; + tile.advance_animations(current_time, is_active); + } + } + // This shall one day become a proper animation. if !self.columns.is_empty() { let col = &self.columns[self.active_column_idx]; let active_win = &col.windows[col.active_window_idx]; - let size = active_win.size(); + let size = active_win.tile_size(); let has_ssd = active_win.has_ssd(); let win_pos = Point::from(( @@ -264,7 +278,10 @@ impl<W: LayoutElement> Workspace<W> { } pub fn windows(&self) -> impl Iterator<Item = &W> + '_ { - self.columns.iter().flat_map(|col| col.windows.iter()) + self.columns + .iter() + .flat_map(|col| col.windows.iter()) + .map(Tile::window) } pub fn set_output(&mut self, output: Option<Output>) { @@ -315,20 +332,33 @@ impl<W: LayoutElement> Workspace<W> { } fn toplevel_bounds(&self) -> Size<i32, Logical> { + let mut border = 0; + if !self.options.border.off { + border = self.options.border.width as i32 * 2; + } + Size::from(( - max(self.working_area.size.w - self.options.gaps * 2, 1), - max(self.working_area.size.h - self.options.gaps * 2, 1), + max(self.working_area.size.w - self.options.gaps * 2 - border, 1), + max(self.working_area.size.h - self.options.gaps * 2 - border, 1), )) } pub fn configure_new_window(&self, window: &Window) { let width = if let Some(width) = self.options.default_width { - max(1, width.resolve(&self.options, self.working_area.size.w)) + let mut width = width.resolve(&self.options, self.working_area.size.w); + if !self.options.border.off { + width -= self.options.border.width as i32 * 2; + } + max(1, width) } else { 0 }; - let height = self.working_area.size.h - self.options.gaps * 2; + let mut height = self.working_area.size.h - self.options.gaps * 2; + if !self.options.border.off { + height -= self.options.border.width as i32 * 2; + } + let size = Size::from((width, max(height, 1))); let bounds = self.toplevel_bounds(); @@ -478,7 +508,7 @@ impl<W: LayoutElement> Workspace<W> { pub fn remove_window_by_idx(&mut self, column_idx: usize, window_idx: usize) -> W { let column = &mut self.columns[column_idx]; - let window = column.windows.remove(window_idx); + let window = column.windows.remove(window_idx).into_window(); column.heights.remove(window_idx); if let Some(output) = &self.output { @@ -746,20 +776,20 @@ impl<W: LayoutElement> Workspace<W> { col.window_y(col.active_window_idx), )); if active_win.is_in_input_region(pos - win_pos.to_f64()) { - return Some((active_win, win_pos + active_win.buf_loc())); + return Some((active_win.window(), win_pos + active_win.buf_loc())); } let mut x = -view_pos; for col in &self.columns { for (win, y) in zip(&col.windows, col.window_ys()) { - if win == active_win { + if win.window() == active_win.window() { // Already handled it above. continue; } let win_pos = Point::from((x, y)); if win.is_in_input_region(pos - win_pos.to_f64()) { - return Some((win, win_pos + win.buf_loc())); + return Some((win.window(), win_pos + win.buf_loc())); } } @@ -815,7 +845,7 @@ impl<W: LayoutElement> Workspace<W> { // This wasn't the only window in its column; extract it into a separate column. let target_window_was_focused = self.active_column_idx == col_idx && col.active_window_idx == win_idx; - let window = col.windows.remove(win_idx); + let window = col.windows.remove(win_idx).into_window(); col.heights.remove(win_idx); col.active_window_idx = min(col.active_window_idx, col.windows.len() - 1); col.update_window_sizes(); @@ -872,7 +902,8 @@ impl Workspace<Window> { let bounds = self.toplevel_bounds(); for (col_idx, col) in self.columns.iter().enumerate() { - for (win_idx, win) in col.windows.iter().enumerate() { + for (win_idx, tile) in col.windows.iter().enumerate() { + let win = tile.window(); let active = self.active_column_idx == col_idx && col.active_window_idx == win_idx; win.set_activated(active); @@ -922,7 +953,7 @@ impl Workspace<Window> { let mut x = -view_pos; for col in &self.columns { for (win, y) in zip(&col.windows, col.window_ys()) { - if win == active_win { + if win.window() == active_win.window() { // Already handled it above. continue; } @@ -989,6 +1020,16 @@ impl<W: LayoutElement> Column<W> { update_sizes = true; } + if self.options.border.off != options.border.off + || self.options.border.width != options.border.width + { + update_sizes = true; + } + + for tile in &mut self.windows { + tile.update_config(options.clone()); + } + self.options = options; if update_sizes { @@ -1003,11 +1044,17 @@ impl<W: LayoutElement> Column<W> { } pub fn contains(&self, window: &W) -> bool { - self.windows.iter().any(|win| win == window) + self.windows + .iter() + .map(Tile::window) + .any(|win| win == window) } pub fn position(&self, window: &W) -> Option<usize> { - self.windows.iter().position(|win| win == window) + self.windows + .iter() + .map(Tile::window) + .position(|win| win == window) } fn activate_window(&mut self, window: &W) { @@ -1016,8 +1063,9 @@ impl<W: LayoutElement> Column<W> { } fn add_window(&mut self, window: W) { + let tile = Tile::new(window, self.options.clone()); self.is_fullscreen = false; - self.windows.push(window); + self.windows.push(tile); self.heights.push(WindowHeight::Auto); self.update_window_sizes(); } @@ -1028,8 +1076,8 @@ impl<W: LayoutElement> Column<W> { return; } - let min_size: Vec<_> = self.windows.iter().map(LayoutElement::min_size).collect(); - let max_size: Vec<_> = self.windows.iter().map(LayoutElement::max_size).collect(); + let min_size: Vec<_> = self.windows.iter().map(Tile::min_size).collect(); + let max_size: Vec<_> = self.windows.iter().map(Tile::max_size).collect(); // Compute the column width. let min_width = min_size @@ -1067,8 +1115,15 @@ impl<W: LayoutElement> Column<W> { let width = width.resolve(&self.options, self.working_area.size.w); let width = max(min(width, max_width), min_width); - // Compute the window heights. - let mut heights = self.heights.clone(); + // Compute the window heights. Start by converting window heights to tile heights. + let mut heights = zip(&self.windows, &self.heights) + .map(|(tile, height)| match *height { + WindowHeight::Auto => WindowHeight::Auto, + WindowHeight::Fixed(height) => { + WindowHeight::Fixed(tile.tile_height_for_window_height(height)) + } + }) + .collect::<Vec<_>>(); let mut height_left = self.working_area.size.h - self.options.gaps; let mut auto_windows_left = self.windows.len(); @@ -1162,18 +1217,22 @@ impl<W: LayoutElement> Column<W> { assert_eq!(auto_windows_left, 0); } - for (win, h) in zip(&self.windows, heights) { + for (tile, h) in zip(&mut self.windows, heights) { let WindowHeight::Fixed(height) = h else { unreachable!() }; let size = Size::from((width, height)); - win.request_size(size); + tile.request_tile_size(size); } } fn width(&self) -> i32 { - self.windows.iter().map(|win| win.size().w).max().unwrap() + self.windows + .iter() + .map(|win| win.tile_size().w) + .max() + .unwrap() } fn focus_up(&mut self) { @@ -1265,7 +1324,13 @@ impl<W: LayoutElement> Column<W> { const MAX_F: f64 = 10000.; let width = match (current, change) { - (_, SizeChange::SetFixed(fixed)) => ColumnWidth::Fixed(fixed.clamp(1, MAX_PX)), + (_, SizeChange::SetFixed(fixed)) => { + // As a special case, setting a fixed column width will compute it in such a way + // that the active window gets that width. This is the intention behind the ability + // to set a fixed size. + let tile = &self.windows[self.active_window_idx]; + ColumnWidth::Fixed(tile.tile_width_for_window_width(fixed).clamp(1, MAX_PX)) + } (_, SizeChange::SetProportion(proportion)) => { ColumnWidth::Proportion((proportion / 100.).clamp(0., MAX_F)) } @@ -1291,45 +1356,51 @@ impl<W: LayoutElement> Column<W> { fn set_window_height(&mut self, change: SizeChange) { let current = self.heights[self.active_window_idx]; - let current_px = match current { - WindowHeight::Auto => self.windows[self.active_window_idx].size().h, + let tile = &self.windows[self.active_window_idx]; + let current_window_px = match current { + WindowHeight::Auto => tile.window_size().h, WindowHeight::Fixed(height) => height, }; - let current_prop = (current_px + self.options.gaps) as f64 + let current_tile_px = tile.tile_height_for_window_height(current_window_px); + let current_prop = (current_tile_px + self.options.gaps) as f64 / (self.working_area.size.h - self.options.gaps) as f64; // FIXME: fix overflows then remove limits. const MAX_PX: i32 = 100000; - let mut height = match change { + let mut window_height = match change { SizeChange::SetFixed(fixed) => fixed, SizeChange::SetProportion(proportion) => { - ((self.working_area.size.h - self.options.gaps) as f64 * proportion + let tile_height = ((self.working_area.size.h - self.options.gaps) as f64 + * proportion - self.options.gaps as f64) - .round() as i32 + .round() as i32; + tile.window_height_for_tile_height(tile_height) } - SizeChange::AdjustFixed(delta) => current_px.saturating_add(delta), + SizeChange::AdjustFixed(delta) => current_window_px.saturating_add(delta), SizeChange::AdjustProportion(delta) => { let proportion = current_prop + delta / 100.; - ((self.working_area.size.h - self.options.gaps) as f64 * proportion + let tile_height = ((self.working_area.size.h - self.options.gaps) as f64 + * proportion - self.options.gaps as f64) - .round() as i32 + .round() as i32; + tile.window_height_for_tile_height(tile_height) } }; // Clamp it against the window height constraints. - let win = &self.windows[self.active_window_idx]; + let win = &self.windows[self.active_window_idx].window(); let min_h = win.min_size().h; let max_h = win.max_size().h; if max_h > 0 { - height = height.min(max_h); + window_height = window_height.min(max_h); } if min_h > 0 { - height = height.max(min_h); + window_height = window_height.max(min_h); } - self.heights[self.active_window_idx] = WindowHeight::Fixed(height.clamp(1, MAX_PX)); + self.heights[self.active_window_idx] = WindowHeight::Fixed(window_height.clamp(1, MAX_PX)); self.update_window_sizes(); } @@ -1352,7 +1423,7 @@ impl<W: LayoutElement> Column<W> { self.windows.iter().map(move |win| { let pos = y; - y += win.size().h + self.options.gaps; + y += win.tile_size().h + self.options.gaps; pos }) } |
