From ed3080d908001bf468789b8f47f893e00306135d Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sun, 24 Dec 2023 15:10:09 +0400 Subject: Split layout mod into files No functional change intended. --- src/layout/focus_ring.rs | 108 +++ src/layout/mod.rs | 2079 +--------------------------------------------- src/layout/monitor.rs | 565 +++++++++++++ src/layout/workspace.rs | 1441 ++++++++++++++++++++++++++++++++ 4 files changed, 2125 insertions(+), 2068 deletions(-) create mode 100644 src/layout/focus_ring.rs create mode 100644 src/layout/monitor.rs create mode 100644 src/layout/workspace.rs diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs new file mode 100644 index 00000000..9385fc6b --- /dev/null +++ b/src/layout/focus_ring.rs @@ -0,0 +1,108 @@ +use std::iter::zip; + +use arrayvec::ArrayVec; +use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; +use smithay::backend::renderer::element::Kind; +use smithay::utils::{Logical, Point, Scale, Size}; + +use crate::config::{self, Color}; + +#[derive(Debug)] +pub struct FocusRing { + buffers: [SolidColorBuffer; 4], + locations: [Point; 4], + is_off: bool, + is_border: bool, + width: i32, + active_color: Color, + inactive_color: Color, +} + +pub type FocusRingRenderElement = SolidColorRenderElement; + +impl FocusRing { + pub fn new(config: config::FocusRing) -> Self { + Self { + buffers: Default::default(), + locations: Default::default(), + is_off: config.off, + is_border: false, + width: config.width.into(), + active_color: config.active_color, + inactive_color: config.inactive_color, + } + } + + pub fn update_config(&mut self, config: config::FocusRing) { + self.is_off = config.off; + self.width = config.width.into(); + self.active_color = config.active_color; + self.inactive_color = config.inactive_color; + } + + pub fn update( + &mut self, + win_pos: Point, + win_size: Size, + is_border: bool, + ) { + if is_border { + self.buffers[0].resize((win_size.w + self.width * 2, self.width)); + self.buffers[1].resize((win_size.w + self.width * 2, self.width)); + self.buffers[2].resize((self.width, win_size.h)); + self.buffers[3].resize((self.width, win_size.h)); + + self.locations[0] = win_pos + Point::from((-self.width, -self.width)); + self.locations[1] = win_pos + Point::from((-self.width, win_size.h)); + self.locations[2] = win_pos + Point::from((-self.width, 0)); + self.locations[3] = win_pos + Point::from((win_size.w, 0)); + } else { + let size = win_size + Size::from((self.width * 2, self.width * 2)); + self.buffers[0].resize(size); + self.locations[0] = win_pos - Point::from((self.width, self.width)); + } + + self.is_border = is_border; + } + + pub fn set_active(&mut self, is_active: bool) { + let color = if is_active { + self.active_color.into() + } else { + self.inactive_color.into() + }; + + for buf in &mut self.buffers { + buf.set_color(color); + } + } + + pub fn render(&self, scale: Scale) -> impl Iterator { + let mut rv = ArrayVec::<_, 4>::new(); + + if self.is_off { + return rv.into_iter(); + } + + let mut push = |buffer, location: Point| { + let elem = SolidColorRenderElement::from_buffer( + buffer, + location.to_physical_precise_round(scale), + scale, + 1., + Kind::Unspecified, + ); + rv.push(elem); + }; + + if self.is_border { + for (buf, loc) in zip(&self.buffers, self.locations) { + push(buf, loc); + } + } else { + push(&self.buffers[0], self.locations[0]); + } + + rv.into_iter() + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8946ffd0..5d747cfe 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -29,47 +29,30 @@ //! compromise we only keep the first workspace there, and move the rest to the primary output, //! making the primary output their original output. -use std::cmp::{max, min}; -use std::iter::zip; use std::mem; use std::rc::Rc; use std::time::Duration; -use arrayvec::ArrayVec; -use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; -use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; -use smithay::backend::renderer::element::utils::{ - CropRenderElement, Relocate, RelocateRenderElement, -}; -use smithay::backend::renderer::element::{AsRenderElements, Kind}; -use smithay::backend::renderer::gles::GlesRenderer; -use smithay::backend::renderer::ImportAll; use smithay::desktop::space::SpaceElement; -use smithay::desktop::{layer_map_for_output, Window}; +use smithay::desktop::Window; use smithay::output::Output; use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; -use smithay::render_elements; -use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform}; +use smithay::utils::{Logical, Point, Size, Transform}; use smithay::wayland::compositor::{send_surface_state, with_states}; use smithay::wayland::shell::xdg::SurfaceCachedState; +pub use self::monitor::MonitorRenderElement; +use self::monitor::{Monitor, WorkspaceSwitch, WorkspaceSwitchGesture}; +use self::workspace::{compute_working_area, ColumnWidth, OutputId, Workspace}; use crate::animation::Animation; -use crate::config::{self, Color, Config, PresetWidth, SizeChange, Struts}; +use crate::config::{self, Config, SizeChange, Struts}; use crate::utils::output_size; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OutputId(String); - -render_elements! { - #[derive(Debug)] - pub WorkspaceRenderElement where R: ImportAll; - Wayland = WaylandSurfaceRenderElement, - FocusRing = SolidColorRenderElement, -} -pub type MonitorRenderElement = - RelocateRenderElement>>; +mod focus_ring; +mod monitor; +mod workspace; pub trait LayoutElement: SpaceElement + PartialEq + Clone { fn request_size(&self, size: Size); @@ -107,103 +90,8 @@ enum MonitorSet { }, } -#[derive(Debug)] -pub struct Monitor { - /// Output for this monitor. - output: Output, - // Must always contain at least one. - workspaces: Vec>, - /// Index of the currently active workspace. - active_workspace_idx: usize, - /// In-progress switch between workspaces. - workspace_switch: Option, - /// Configurable properties of the layout. - options: Rc, -} - -#[derive(Debug)] -enum WorkspaceSwitch { - Animation(Animation), - Gesture(WorkspaceSwitchGesture), -} - -#[derive(Debug)] -struct WorkspaceSwitchGesture { - /// Index of the workspace where the gesture was started. - center_idx: usize, - /// Current, fractional workspace index. - current_idx: f64, -} - -#[derive(Debug)] -pub struct Workspace { - /// The original output of this workspace. - /// - /// Most of the time this will be the workspace's current output, however, after an output - /// disconnection, it may remain pointing to the disconnected output. - original_output: OutputId, - - /// Current output of this workspace. - output: Option, - - /// Latest known view size for this workspace. - /// - /// This should be computed from the current workspace output size, or, if all outputs have - /// been disconnected, preserved until a new output is connected. - view_size: Size, - - /// Latest known working area for this workspace. - /// - /// This is similar to view size, but takes into account things like layer shell exclusive - /// zones. - working_area: Rectangle, - - /// Columns of windows on this workspace. - columns: Vec>, - - /// Index of the currently active column, if any. - active_column_idx: usize, - - /// Focus ring buffer and parameters. - focus_ring: FocusRing, - - /// Offset of the view computed from the active column. - /// - /// Any gaps, including left padding from work area left exclusive zone, is handled - /// with this view offset (rather than added as a constant elsewhere in the code). This allows - /// for natural handling of fullscreen windows, which must ignore work area padding. - view_offset: i32, - - /// Animation of the view offset, if one is currently ongoing. - view_offset_anim: Option, - - /// Whether to activate the previous, rather than the next, column upon column removal. - /// - /// When a new column is created and removed with no focus changes in-between, it is more - /// natural to activate the previously-focused column. This variable tracks that. - /// - /// Since we only create-and-activate columns immediately to the right of the active column (in - /// contrast to tabs in Firefox, for example), we can track this as a bool, rather than an - /// index of the previous column to activate. - activate_prev_column_on_removal: bool, - - /// Configurable properties of the layout. - options: Rc, -} - -#[derive(Debug)] -struct FocusRing { - buffers: [SolidColorBuffer; 4], - locations: [Point; 4], - is_off: bool, - is_border: bool, - width: i32, - active_color: Color, - inactive_color: Color, -} - #[derive(Debug, PartialEq)] -struct Options { +pub struct Options { /// Padding around windows in logical pixels. gaps: i32, /// Extra padding around the working area in logical pixels. @@ -263,92 +151,6 @@ impl Options { } } -/// Width of a column. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ColumnWidth { - /// Proportion of the current view width. - Proportion(f64), - /// One of the proportion presets. - /// - /// This is separate from Proportion in order to be able to reliably cycle between preset - /// proportions. - Preset(usize), - /// Fixed width in logical pixels. - Fixed(i32), -} - -impl From for ColumnWidth { - fn from(value: PresetWidth) -> Self { - match value { - PresetWidth::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)), - PresetWidth::Fixed(f) => Self::Fixed(f.clamp(1, 100000)), - } - } -} - -/// 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. - Auto, - /// Fixed height in logical pixels. - Fixed(i32), -} - -#[derive(Debug)] -struct Column { - /// Windows in this column. - /// - /// Must be non-empty. - windows: Vec, - - /// Heights of the windows. - /// - /// Must have the same number of elements as `windows`. - heights: Vec, - - /// Index of the currently active window. - active_window_idx: usize, - - /// Desired width of this column. - /// - /// If the column is full-width or full-screened, this is the width that should be restored - /// upon unfullscreening and untoggling full-width. - width: ColumnWidth, - - /// Whether this column is full-width. - is_full_width: bool, - - /// Whether this column contains a single full-screened window. - is_fullscreen: bool, - - /// Latest known view size for this column's workspace. - view_size: Size, - - /// Latest known working area for this column's workspace. - working_area: Rectangle, - - /// Configurable properties of the layout. - options: Rc, -} - -impl OutputId { - pub fn new(output: &Output) -> Self { - Self(output.name()) - } -} - impl LayoutElement for Window { fn request_size(&self, size: Size) { self.toplevel().with_pending_state(|state| { @@ -394,117 +196,6 @@ impl LayoutElement for Window { } } -impl FocusRing { - fn update( - &mut self, - win_pos: Point, - win_size: Size, - is_border: bool, - ) { - if is_border { - self.buffers[0].resize((win_size.w + self.width * 2, self.width)); - self.buffers[1].resize((win_size.w + self.width * 2, self.width)); - self.buffers[2].resize((self.width, win_size.h)); - self.buffers[3].resize((self.width, win_size.h)); - - self.locations[0] = win_pos + Point::from((-self.width, -self.width)); - self.locations[1] = win_pos + Point::from((-self.width, win_size.h)); - self.locations[2] = win_pos + Point::from((-self.width, 0)); - self.locations[3] = win_pos + Point::from((win_size.w, 0)); - } else { - let size = win_size + Size::from((self.width * 2, self.width * 2)); - self.buffers[0].resize(size); - self.locations[0] = win_pos - Point::from((self.width, self.width)); - } - - self.is_border = is_border; - } - - fn set_active(&mut self, is_active: bool) { - let color = if is_active { - self.active_color.into() - } else { - self.inactive_color.into() - }; - - for buf in &mut self.buffers { - buf.set_color(color); - } - } - - fn render(&self, scale: Scale) -> impl Iterator { - let mut rv = ArrayVec::<_, 4>::new(); - - if self.is_off { - return rv.into_iter(); - } - - let mut push = |buffer, location: Point| { - let elem = SolidColorRenderElement::from_buffer( - buffer, - location.to_physical_precise_round(scale), - scale, - 1., - Kind::Unspecified, - ); - rv.push(elem); - }; - - if self.is_border { - for (buf, loc) in zip(&self.buffers, self.locations) { - push(buf, loc); - } - } else { - push(&self.buffers[0], self.locations[0]); - } - - rv.into_iter() - } -} - -impl FocusRing { - fn new(config: config::FocusRing) -> Self { - Self { - buffers: Default::default(), - locations: Default::default(), - is_off: config.off, - is_border: false, - width: config.width.into(), - active_color: config.active_color, - inactive_color: config.inactive_color, - } - } -} - -impl WorkspaceSwitch { - fn current_idx(&self) -> f64 { - match self { - WorkspaceSwitch::Animation(anim) => anim.value(), - WorkspaceSwitch::Gesture(gesture) => gesture.current_idx, - } - } - - /// Returns `true` if the workspace switch is [`Animation`]. - /// - /// [`Animation`]: WorkspaceSwitch::Animation - #[must_use] - fn is_animation(&self) -> bool { - matches!(self, Self::Animation(..)) - } -} - -impl ColumnWidth { - fn resolve(self, options: &Options, view_width: i32) -> i32 { - match self { - ColumnWidth::Proportion(proportion) => { - ((view_width - options.gaps) as f64 * proportion).floor() as i32 - options.gaps - } - ColumnWidth::Preset(idx) => options.preset_widths[idx].resolve(options, view_width), - ColumnWidth::Fixed(width) => width, - } - } -} - impl Layout { pub fn new(config: &Config) -> Self { Self { @@ -1578,1754 +1269,6 @@ impl Default for MonitorSet { } } -impl Monitor { - fn new(output: Output, workspaces: Vec>, options: Rc) -> Self { - Self { - output, - workspaces, - active_workspace_idx: 0, - workspace_switch: None, - options, - } - } - - fn active_workspace(&mut self) -> &mut Workspace { - &mut self.workspaces[self.active_workspace_idx] - } - - fn activate_workspace(&mut self, idx: usize) { - if self.active_workspace_idx == idx { - return; - } - - let current_idx = self - .workspace_switch - .as_ref() - .map(|s| s.current_idx()) - .unwrap_or(self.active_workspace_idx as f64); - - self.active_workspace_idx = idx; - - self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( - current_idx, - idx as f64, - Duration::from_millis(250), - ))); - } - - pub fn add_window( - &mut self, - workspace_idx: usize, - window: W, - activate: bool, - width: ColumnWidth, - is_full_width: bool, - ) { - let workspace = &mut self.workspaces[workspace_idx]; - - workspace.add_window(window.clone(), activate, width, is_full_width); - - // After adding a new window, workspace becomes this output's own. - workspace.original_output = OutputId::new(&self.output); - - if workspace_idx == self.workspaces.len() - 1 { - // Insert a new empty workspace. - let ws = Workspace::new(self.output.clone(), self.options.clone()); - self.workspaces.push(ws); - } - - if activate { - self.activate_workspace(workspace_idx); - } - } - - fn clean_up_workspaces(&mut self) { - assert!(self.workspace_switch.is_none()); - - for idx in (0..self.workspaces.len() - 1).rev() { - if self.active_workspace_idx == idx { - continue; - } - - if !self.workspaces[idx].has_windows() { - self.workspaces.remove(idx); - if self.active_workspace_idx > idx { - self.active_workspace_idx -= 1; - } - } - } - } - - pub fn move_left(&mut self) { - self.active_workspace().move_left(); - } - - pub fn move_right(&mut self) { - self.active_workspace().move_right(); - } - - pub fn move_down(&mut self) { - self.active_workspace().move_down(); - } - - pub fn move_up(&mut self) { - self.active_workspace().move_up(); - } - - pub fn move_down_or_to_workspace_down(&mut self) { - let workspace = self.active_workspace(); - if workspace.columns.is_empty() { - return; - } - let column = &mut workspace.columns[workspace.active_column_idx]; - let curr_idx = column.active_window_idx; - let new_idx = min(column.active_window_idx + 1, column.windows.len() - 1); - if curr_idx == new_idx { - self.move_to_workspace_down(); - } else { - workspace.move_down(); - } - } - - pub fn move_up_or_to_workspace_up(&mut self) { - let workspace = self.active_workspace(); - if workspace.columns.is_empty() { - return; - } - let curr_idx = workspace.columns[workspace.active_column_idx].active_window_idx; - let new_idx = curr_idx.saturating_sub(1); - if curr_idx == new_idx { - self.move_to_workspace_up(); - } else { - workspace.move_up(); - } - } - - pub fn focus_left(&mut self) { - self.active_workspace().focus_left(); - } - - pub fn focus_right(&mut self) { - self.active_workspace().focus_right(); - } - - pub fn focus_down(&mut self) { - self.active_workspace().focus_down(); - } - - pub fn focus_up(&mut self) { - self.active_workspace().focus_up(); - } - - pub fn focus_window_or_workspace_down(&mut self) { - let workspace = self.active_workspace(); - if workspace.columns.is_empty() { - self.switch_workspace_down(); - } else { - let column = &workspace.columns[workspace.active_column_idx]; - let curr_idx = column.active_window_idx; - let new_idx = min(column.active_window_idx + 1, column.windows.len() - 1); - if curr_idx == new_idx { - self.switch_workspace_down(); - } else { - workspace.focus_down(); - } - } - } - - pub fn focus_window_or_workspace_up(&mut self) { - let workspace = self.active_workspace(); - if workspace.columns.is_empty() { - self.switch_workspace_up(); - } else { - let curr_idx = workspace.columns[workspace.active_column_idx].active_window_idx; - let new_idx = curr_idx.saturating_sub(1); - if curr_idx == new_idx { - self.switch_workspace_up(); - } else { - workspace.focus_up(); - } - } - } - - pub fn move_to_workspace_up(&mut self) { - let source_workspace_idx = self.active_workspace_idx; - - let new_idx = source_workspace_idx.saturating_sub(1); - if new_idx == source_workspace_idx { - return; - } - - let workspace = &mut self.workspaces[source_workspace_idx]; - if workspace.columns.is_empty() { - return; - } - - let column = &mut workspace.columns[workspace.active_column_idx]; - let width = column.width; - let is_full_width = column.is_full_width; - let window = column.windows[column.active_window_idx].clone(); - workspace.remove_window(&window); - - self.add_window(new_idx, window, true, width, is_full_width); - } - - pub fn move_to_workspace_down(&mut self) { - let source_workspace_idx = self.active_workspace_idx; - - let new_idx = min(source_workspace_idx + 1, self.workspaces.len() - 1); - if new_idx == source_workspace_idx { - return; - } - - let workspace = &mut self.workspaces[source_workspace_idx]; - if workspace.columns.is_empty() { - return; - } - - let column = &mut workspace.columns[workspace.active_column_idx]; - let width = column.width; - let is_full_width = column.is_full_width; - let window = column.windows[column.active_window_idx].clone(); - workspace.remove_window(&window); - - self.add_window(new_idx, window, true, width, is_full_width); - } - - pub fn move_to_workspace(&mut self, idx: usize) { - let source_workspace_idx = self.active_workspace_idx; - - let new_idx = min(idx, self.workspaces.len() - 1); - if new_idx == source_workspace_idx { - return; - } - - let workspace = &mut self.workspaces[source_workspace_idx]; - if workspace.columns.is_empty() { - return; - } - - let column = &mut workspace.columns[workspace.active_column_idx]; - let width = column.width; - let is_full_width = column.is_full_width; - let window = column.windows[column.active_window_idx].clone(); - workspace.remove_window(&window); - - self.add_window(new_idx, window, true, width, is_full_width); - - // Don't animate this action. - self.workspace_switch = None; - - self.clean_up_workspaces(); - } - - pub fn switch_workspace_up(&mut self) { - self.activate_workspace(self.active_workspace_idx.saturating_sub(1)); - } - - pub fn switch_workspace_down(&mut self) { - self.activate_workspace(min( - self.active_workspace_idx + 1, - self.workspaces.len() - 1, - )); - } - - pub fn switch_workspace(&mut self, idx: usize) { - self.activate_workspace(min(idx, self.workspaces.len() - 1)); - // Don't animate this action. - self.workspace_switch = None; - - self.clean_up_workspaces(); - } - - pub fn consume_into_column(&mut self) { - self.active_workspace().consume_into_column(); - } - - pub fn expel_from_column(&mut self) { - self.active_workspace().expel_from_column(); - } - - pub fn center_column(&mut self) { - self.active_workspace().center_column(); - } - - pub fn focus(&self) -> Option<&W> { - let workspace = &self.workspaces[self.active_workspace_idx]; - if !workspace.has_windows() { - return None; - } - - let column = &workspace.columns[workspace.active_column_idx]; - Some(&column.windows[column.active_window_idx]) - } - - pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) { - if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch { - anim.set_current_time(current_time); - if anim.is_done() { - self.workspace_switch = None; - self.clean_up_workspaces(); - } - } - - for ws in &mut self.workspaces { - ws.advance_animations(current_time, is_active); - } - } - - pub fn are_animations_ongoing(&self) -> bool { - self.workspace_switch - .as_ref() - .is_some_and(|s| s.is_animation()) - || self.workspaces.iter().any(|ws| ws.are_animations_ongoing()) - } - - pub fn are_transitions_ongoing(&self) -> bool { - self.workspace_switch.is_some() - || self.workspaces.iter().any(|ws| ws.are_animations_ongoing()) - } - - fn update_config(&mut self, options: Rc) { - for ws in &mut self.workspaces { - ws.update_config(options.clone()); - } - - if self.options.struts != options.struts { - let view_size = output_size(&self.output); - let working_area = compute_working_area(&self.output, options.struts); - - for ws in &mut self.workspaces { - ws.set_view_size(view_size, working_area); - } - } - - self.options = options; - } - - fn toggle_width(&mut self) { - self.active_workspace().toggle_width(); - } - - fn toggle_full_width(&mut self) { - self.active_workspace().toggle_full_width(); - } - - fn set_column_width(&mut self, change: SizeChange) { - self.active_workspace().set_column_width(change); - } - - fn set_window_height(&mut self, change: SizeChange) { - self.active_workspace().set_window_height(change); - } - - fn move_workspace_down(&mut self) { - let new_idx = min(self.active_workspace_idx + 1, self.workspaces.len() - 1); - if new_idx == self.active_workspace_idx { - return; - } - - self.workspaces.swap(self.active_workspace_idx, new_idx); - - if new_idx == self.workspaces.len() - 1 { - // Insert a new empty workspace. - let ws = Workspace::new(self.output.clone(), self.options.clone()); - self.workspaces.push(ws); - } - - self.activate_workspace(new_idx); - self.workspace_switch = None; - - self.clean_up_workspaces(); - } - - fn move_workspace_up(&mut self) { - let new_idx = self.active_workspace_idx.saturating_sub(1); - if new_idx == self.active_workspace_idx { - return; - } - - self.workspaces.swap(self.active_workspace_idx, new_idx); - - if self.active_workspace_idx == self.workspaces.len() - 1 { - // Insert a new empty workspace. - let ws = Workspace::new(self.output.clone(), self.options.clone()); - self.workspaces.push(ws); - } - - self.activate_workspace(new_idx); - self.workspace_switch = None; - - self.clean_up_workspaces(); - } - - pub fn window_under( - &self, - pos_within_output: Point, - ) -> Option<(&W, Point)> { - match &self.workspace_switch { - Some(switch) => { - let size = output_size(&self.output); - - let render_idx = switch.current_idx(); - let before_idx = render_idx.floor() as usize; - let after_idx = render_idx.ceil() as usize; - - let offset = ((render_idx - before_idx as f64) * size.h as f64).round() as i32; - - let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 { - (before_idx, Point::from((0, offset))) - } else { - (after_idx, Point::from((0, -size.h + offset))) - }; - - let ws = &self.workspaces[idx]; - let (win, win_pos) = ws.window_under(pos_within_output + ws_offset.to_f64())?; - Some((win, win_pos - ws_offset)) - } - None => { - let ws = &self.workspaces[self.active_workspace_idx]; - ws.window_under(pos_within_output) - } - } - } - - pub fn render_above_top_layer(&self) -> bool { - // Render above the top layer only if the view is stationary. - if self.workspace_switch.is_some() { - return false; - } - - let ws = &self.workspaces[self.active_workspace_idx]; - ws.render_above_top_layer() - } -} - -impl Monitor { - pub fn render_elements( - &self, - renderer: &mut GlesRenderer, - ) -> Vec> { - let _span = tracy_client::span!("Monitor::render_elements"); - - let output_scale = Scale::from(self.output.current_scale().fractional_scale()); - let output_transform = self.output.current_transform(); - let output_mode = self.output.current_mode().unwrap(); - let size = output_transform.transform_size(output_mode.size); - - match &self.workspace_switch { - Some(switch) => { - let render_idx = switch.current_idx(); - let before_idx = render_idx.floor() as usize; - let after_idx = render_idx.ceil() as usize; - - let offset = ((render_idx - before_idx as f64) * size.h as f64).round() as i32; - - let before = self.workspaces[before_idx].render_elements(renderer); - let after = self.workspaces[after_idx].render_elements(renderer); - - let before = before.into_iter().filter_map(|elem| { - Some(RelocateRenderElement::from_element( - CropRenderElement::from_element( - elem, - output_scale, - Rectangle::from_extemities((0, offset), (size.w, size.h)), - )?, - (0, -offset), - Relocate::Relative, - )) - }); - let after = after.into_iter().filter_map(|elem| { - Some(RelocateRenderElement::from_element( - CropRenderElement::from_element( - elem, - output_scale, - Rectangle::from_extemities((0, 0), (size.w, offset)), - )?, - (0, -offset + size.h), - Relocate::Relative, - )) - }); - before.chain(after).collect() - } - None => { - let elements = self.workspaces[self.active_workspace_idx].render_elements(renderer); - elements - .into_iter() - .filter_map(|elem| { - Some(RelocateRenderElement::from_element( - CropRenderElement::from_element( - elem, - output_scale, - // HACK: set infinite crop bounds due to a damage tracking bug - // which causes glitched rendering for maximized GTK windows. - // FIXME: use proper bounds after fixing the Crop element. - Rectangle::from_loc_and_size( - (-i32::MAX / 2, -i32::MAX / 2), - (i32::MAX, i32::MAX), - ), - // Rectangle::from_loc_and_size((0, 0), size), - )?, - (0, 0), - Relocate::Relative, - )) - }) - .collect() - } - } - } -} - -impl Workspace { - fn new(output: Output, options: Rc) -> Self { - let working_area = compute_working_area(&output, options.struts); - Self { - original_output: OutputId::new(&output), - view_size: output_size(&output), - working_area, - output: Some(output), - columns: vec![], - active_column_idx: 0, - focus_ring: FocusRing::new(options.focus_ring), - view_offset: 0, - view_offset_anim: None, - activate_prev_column_on_removal: false, - options, - } - } - - fn new_no_outputs(options: Rc) -> Self { - Self { - output: None, - original_output: OutputId(String::new()), - view_size: Size::from((1280, 720)), - working_area: Rectangle::from_loc_and_size((0, 0), (1280, 720)), - columns: vec![], - active_column_idx: 0, - focus_ring: FocusRing::new(options.focus_ring), - view_offset: 0, - view_offset_anim: None, - activate_prev_column_on_removal: false, - options, - } - } - - pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) { - match &mut self.view_offset_anim { - Some(anim) => { - anim.set_current_time(current_time); - self.view_offset = anim.value().round() as i32; - if anim.is_done() { - self.view_offset_anim = None; - } - } - None => (), - } - - let view_pos = self.view_pos(); - - // 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 geom = active_win.geometry(); - let has_ssd = active_win.has_ssd(); - - let win_pos = Point::from(( - self.column_x(self.active_column_idx) - view_pos, - col.window_y(col.active_window_idx), - )); - - self.focus_ring.update(win_pos, geom.size, has_ssd); - self.focus_ring.set_active(is_active); - } - } - - pub fn are_animations_ongoing(&self) -> bool { - self.view_offset_anim.is_some() - } - - fn update_config(&mut self, options: Rc) { - let c = &options.focus_ring; - self.focus_ring.is_off = c.off; - self.focus_ring.width = c.width.into(); - self.focus_ring.active_color = c.active_color; - self.focus_ring.inactive_color = c.inactive_color; - // The focus ring buffer will be updated in a subsequent update_animations call. - - for column in &mut self.columns { - column.update_config(options.clone()); - } - - self.options = options; - } - - fn windows(&self) -> impl Iterator + '_ { - self.columns.iter().flat_map(|col| col.windows.iter()) - } - - fn set_output(&mut self, output: Option) { - if self.output == output { - return; - } - - if let Some(output) = self.output.take() { - for win in self.windows() { - win.output_leave(&output); - } - } - - self.output = output; - - if let Some(output) = &self.output { - let working_area = compute_working_area(output, self.options.struts); - self.set_view_size(output_size(output), working_area); - - for win in self.windows() { - self.enter_output_for_window(win); - } - } - } - - fn enter_output_for_window(&self, window: &W) { - if let Some(output) = &self.output { - prepare_for_output(window, output); - - // FIXME: proper overlap. - window.output_enter( - output, - Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX)), - ); - } - } - - fn set_view_size(&mut self, size: Size, working_area: Rectangle) { - if self.view_size == size && self.working_area == working_area { - return; - } - - self.view_size = size; - self.working_area = working_area; - - for col in &mut self.columns { - col.set_view_size(self.view_size, self.working_area); - } - } - - fn toplevel_bounds(&self) -> Size { - Size::from(( - max(self.working_area.size.w - self.options.gaps * 2, 1), - max(self.working_area.size.h - self.options.gaps * 2, 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)) - } else { - 0 - }; - - let height = self.working_area.size.h - self.options.gaps * 2; - let size = Size::from((width, max(height, 1))); - - let bounds = self.toplevel_bounds(); - - if let Some(output) = self.output.as_ref() { - prepare_for_output(window, output); - } - - window.toplevel().with_pending_state(|state| { - state.size = Some(size); - state.bounds = Some(bounds); - }); - } - - fn compute_new_view_offset_for_column(&self, current_x: i32, idx: usize) -> i32 { - if self.columns[idx].is_fullscreen { - return 0; - } - - let new_col_x = self.column_x(idx); - - let final_x = if let Some(anim) = &self.view_offset_anim { - current_x - self.view_offset + anim.to().round() as i32 - } else { - current_x - }; - - let new_offset = compute_new_view_offset( - final_x + self.working_area.loc.x, - self.working_area.size.w, - new_col_x, - self.columns[idx].width(), - self.options.gaps, - ); - - // Non-fullscreen windows are always offset at least by the working area position. - new_offset - self.working_area.loc.x - } - - fn animate_view_offset_to_column(&mut self, current_x: i32, idx: usize) { - let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx); - - let new_col_x = self.column_x(idx); - let from_view_offset = current_x - new_col_x; - self.view_offset = from_view_offset; - - // If we're already animating towards that, don't restart it. - if let Some(anim) = &self.view_offset_anim { - if anim.value().round() as i32 == self.view_offset - && anim.to().round() as i32 == new_view_offset - { - return; - } - } - - // If our view offset is already this, we don't need to do anything. - if self.view_offset == new_view_offset { - self.view_offset_anim = None; - return; - } - - self.view_offset_anim = Some(Animation::new( - self.view_offset as f64, - new_view_offset as f64, - Duration::from_millis(250), - )); - } - - fn activate_column(&mut self, idx: usize) { - if self.active_column_idx == idx { - return; - } - - let current_x = self.view_pos(); - self.animate_view_offset_to_column(current_x, idx); - - self.active_column_idx = idx; - - // A different column was activated; reset the flag. - self.activate_prev_column_on_removal = false; - } - - fn has_windows(&self) -> bool { - self.windows().next().is_some() - } - - fn has_window(&self, window: &W) -> bool { - self.windows().any(|win| win == window) - } - - fn find_wl_surface(&self, wl_surface: &WlSurface) -> Option<&W> { - self.windows().find(|win| win.is_wl_surface(wl_surface)) - } - - /// Computes the X position of the windows in the given column, in logical coordinates. - fn column_x(&self, column_idx: usize) -> i32 { - let mut x = 0; - - for column in self.columns.iter().take(column_idx) { - x += column.width() + self.options.gaps; - } - - x - } - - fn add_window(&mut self, window: W, activate: bool, width: ColumnWidth, is_full_width: bool) { - self.enter_output_for_window(&window); - - let was_empty = self.columns.is_empty(); - - let idx = if self.columns.is_empty() { - 0 - } else { - self.active_column_idx + 1 - }; - - let column = Column::new( - window, - self.view_size, - self.working_area, - self.options.clone(), - width, - is_full_width, - ); - self.columns.insert(idx, column); - - if activate { - // If this is the first window on an empty workspace, skip the animation from whatever - // view_offset was left over. - if was_empty { - // Try to make the code produce a left-aligned offset, even in presence of left - // exclusive zones. - self.view_offset = self.compute_new_view_offset_for_column(self.column_x(0), 0); - self.view_offset_anim = None; - } - - self.activate_column(idx); - self.activate_prev_column_on_removal = true; - } - } - - fn remove_window(&mut self, window: &W) { - if let Some(output) = &self.output { - window.output_leave(output); - } - - let column_idx = self - .columns - .iter() - .position(|col| col.contains(window)) - .unwrap(); - let column = &mut self.columns[column_idx]; - - let window_idx = column.windows.iter().position(|win| win == window).unwrap(); - column.windows.remove(window_idx); - column.heights.remove(window_idx); - if column.windows.is_empty() { - if column_idx + 1 == self.active_column_idx { - // The previous column, that we were going to activate upon removal of the active - // column, has just been itself removed. - self.activate_prev_column_on_removal = false; - } - - // FIXME: activate_column below computes current view position to compute the new view - // position, which can include the column we're removing here. This leads to unwanted - // view jumps. - self.columns.remove(column_idx); - if self.columns.is_empty() { - return; - } - - if self.active_column_idx > column_idx - || (self.active_column_idx == column_idx && self.activate_prev_column_on_removal) - { - // A column to the left was removed; preserve the current position. - // FIXME: preserve activate_prev_column_on_removal. - // Or, the active column was removed, and we needed to activate the previous column. - self.activate_column(self.active_column_idx.saturating_sub(1)); - } else { - self.activate_column(min(self.active_column_idx, self.columns.len() - 1)); - } - - return; - } - - column.active_window_idx = min(column.active_window_idx, column.windows.len() - 1); - column.update_window_sizes(); - } - - fn update_window(&mut self, window: &W) { - let (idx, column) = self - .columns - .iter_mut() - .enumerate() - .find(|(_, col)| col.contains(window)) - .unwrap(); - column.update_window_sizes(); - - if idx == self.active_column_idx { - // We might need to move the view to ensure the resized window is still visible. - let current_x = self.view_pos(); - self.animate_view_offset_to_column(current_x, idx); - } - } - - fn activate_window(&mut self, window: &W) { - let column_idx = self - .columns - .iter() - .position(|col| col.contains(window)) - .unwrap(); - let column = &mut self.columns[column_idx]; - - column.activate_window(window); - self.activate_column(column_idx); - } - - #[cfg(test)] - fn verify_invariants(&self) { - assert!(self.view_size.w > 0); - assert!(self.view_size.h > 0); - - if !self.columns.is_empty() { - assert!(self.active_column_idx < self.columns.len()); - - for column in &self.columns { - column.verify_invariants(); - } - } - } - - fn focus_left(&mut self) { - self.activate_column(self.active_column_idx.saturating_sub(1)); - } - - fn focus_right(&mut self) { - if self.columns.is_empty() { - return; - } - - self.activate_column(min(self.active_column_idx + 1, self.columns.len() - 1)); - } - - fn focus_down(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].focus_down(); - } - - fn focus_up(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].focus_up(); - } - - fn move_left(&mut self) { - let new_idx = self.active_column_idx.saturating_sub(1); - if self.active_column_idx == new_idx { - return; - } - - let current_x = self.view_pos(); - - self.columns.swap(self.active_column_idx, new_idx); - - self.view_offset = - self.compute_new_view_offset_for_column(current_x, self.active_column_idx); - - self.activate_column(new_idx); - } - - fn move_right(&mut self) { - if self.columns.is_empty() { - return; - } - - let new_idx = min(self.active_column_idx + 1, self.columns.len() - 1); - if self.active_column_idx == new_idx { - return; - } - - let current_x = self.view_pos(); - - self.columns.swap(self.active_column_idx, new_idx); - - self.view_offset = - self.compute_new_view_offset_for_column(current_x, self.active_column_idx); - - self.activate_column(new_idx); - } - - fn move_down(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].move_down(); - } - - fn move_up(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].move_up(); - } - - fn consume_into_column(&mut self) { - if self.columns.len() < 2 { - return; - } - - if self.active_column_idx == self.columns.len() - 1 { - return; - } - - let source_column_idx = self.active_column_idx + 1; - - let source_column = &mut self.columns[source_column_idx]; - let window = source_column.windows[0].clone(); - self.remove_window(&window); - - let target_column = &mut self.columns[self.active_column_idx]; - target_column.add_window(window); - } - - fn expel_from_column(&mut self) { - if self.columns.is_empty() { - return; - } - - let source_column = &mut self.columns[self.active_column_idx]; - if source_column.windows.len() == 1 { - return; - } - - let width = source_column.width; - let is_full_width = source_column.is_full_width; - let window = source_column.windows[source_column.active_window_idx].clone(); - self.remove_window(&window); - - self.add_window(window, true, width, is_full_width); - } - - fn center_column(&mut self) { - if self.columns.is_empty() { - return; - } - - let col = &self.columns[self.active_column_idx]; - if col.is_fullscreen { - return; - } - - let width = col.width(); - - // If the column is wider than the working area, then on commit it will be shifted to left - // edge alignment by the usual positioning code, so there's no use in doing anything here. - if self.working_area.size.w <= width { - return; - } - - let new_view_offset = -(self.working_area.size.w - width) / 2 - self.working_area.loc.x; - - // If we're already animating towards that, don't restart it. - if let Some(anim) = &self.view_offset_anim { - if anim.to().round() as i32 == new_view_offset { - return; - } - } - - // If our view offset is already this, we don't need to do anything. - if self.view_offset == new_view_offset { - return; - } - - self.view_offset_anim = Some(Animation::new( - self.view_offset as f64, - new_view_offset as f64, - Duration::from_millis(250), - )); - } - - fn view_pos(&self) -> i32 { - self.column_x(self.active_column_idx) + self.view_offset - } - - fn window_under(&self, pos: Point) -> Option<(&W, Point)> { - if self.columns.is_empty() { - return None; - } - - let view_pos = self.view_pos(); - - // Prefer the active window since it's drawn on top. - let col = &self.columns[self.active_column_idx]; - let active_win = &col.windows[col.active_window_idx]; - let geom = active_win.geometry(); - let buf_pos = Point::from(( - self.column_x(self.active_column_idx) - view_pos, - col.window_y(col.active_window_idx), - )) - geom.loc; - if active_win.is_in_input_region(&(pos - buf_pos.to_f64())) { - return Some((active_win, buf_pos)); - } - - let mut x = -view_pos; - for col in &self.columns { - for (win, y) in zip(&col.windows, col.window_ys()) { - if win == active_win { - // Already handled it above. - continue; - } - - let geom = win.geometry(); - let buf_pos = Point::from((x, y)) - geom.loc; - if win.is_in_input_region(&(pos - buf_pos.to_f64())) { - return Some((win, buf_pos)); - } - } - - x += col.width() + self.options.gaps; - } - - None - } - - fn toggle_width(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].toggle_width(); - } - - fn toggle_full_width(&mut self) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].toggle_full_width(); - } - - fn set_column_width(&mut self, change: SizeChange) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].set_column_width(change); - } - - fn set_window_height(&mut self, change: SizeChange) { - if self.columns.is_empty() { - return; - } - - self.columns[self.active_column_idx].set_window_height(change); - } - - pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) { - let (mut col_idx, win_idx) = self - .columns - .iter() - .enumerate() - .find_map(|(col_idx, col)| { - col.windows - .iter() - .position(|w| w == window) - .map(|win_idx| (col_idx, win_idx)) - }) - .unwrap(); - - let mut col = &mut self.columns[col_idx]; - - if is_fullscreen && col.windows.len() > 1 { - // 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); - col.heights.remove(win_idx); - col.active_window_idx = min(col.active_window_idx, col.windows.len() - 1); - col.update_window_sizes(); - let width = col.width; - let is_full_width = col.is_full_width; - - col_idx += 1; - self.columns.insert( - col_idx, - Column::new( - window, - self.view_size, - self.working_area, - self.options.clone(), - width, - is_full_width, - ), - ); - if self.active_column_idx >= col_idx || target_window_was_focused { - self.active_column_idx += 1; - } - col = &mut self.columns[col_idx]; - } - - col.set_fullscreen(is_fullscreen); - } - - pub fn toggle_fullscreen(&mut self, window: &W) { - let col = self - .columns - .iter_mut() - .find(|col| col.windows.contains(window)) - .unwrap(); - let value = !col.is_fullscreen; - self.set_fullscreen(window, value); - } - - pub fn render_above_top_layer(&self) -> bool { - // Render above the top layer if we're on a fullscreen window and the view is stationary. - if self.columns.is_empty() { - return false; - } - - if self.view_offset_anim.is_some() { - return false; - } - - self.columns[self.active_column_idx].is_fullscreen - } -} - -impl Workspace { - fn refresh(&self) { - let bounds = self.toplevel_bounds(); - - for (col_idx, col) in self.columns.iter().enumerate() { - for (win_idx, win) in col.windows.iter().enumerate() { - let active = self.active_column_idx == col_idx && col.active_window_idx == win_idx; - win.set_activated(active); - - win.toplevel().with_pending_state(|state| { - state.bounds = Some(bounds); - }); - - win.toplevel().send_pending_configure(); - win.refresh(); - } - } - } - - pub fn render_elements( - &self, - renderer: &mut GlesRenderer, - ) -> Vec> { - if self.columns.is_empty() { - return vec![]; - } - - // FIXME: workspaces should probably cache their last used scale so they can be correctly - // rendered even with no outputs connected. - let output_scale = self - .output - .as_ref() - .map(|o| Scale::from(o.current_scale().fractional_scale())) - .unwrap_or(Scale::from(1.)); - - let mut rv = vec![]; - let view_pos = self.view_pos(); - - // Draw the active window on top. - let col = &self.columns[self.active_column_idx]; - let active_win = &col.windows[col.active_window_idx]; - let win_pos = Point::from(( - self.column_x(self.active_column_idx) - view_pos, - col.window_y(col.active_window_idx), - )); - - // Draw the window itself. - let geom = active_win.geometry(); - let buf_pos = win_pos - geom.loc; - rv.extend(active_win.render_elements( - renderer, - buf_pos.to_physical_precise_round(output_scale), - output_scale, - 1., - )); - - // Draw the focus ring. - rv.extend(self.focus_ring.render(output_scale).map(Into::into)); - - let mut x = -view_pos; - for col in &self.columns { - for (win, y) in zip(&col.windows, col.window_ys()) { - if win == active_win { - // Already handled it above. - continue; - } - - let geom = win.geometry(); - let buf_pos = Point::from((x, y)) - geom.loc; - rv.extend(win.render_elements( - renderer, - buf_pos.to_physical_precise_round(output_scale), - output_scale, - 1., - )); - } - - x += col.width() + self.options.gaps; - } - - rv - } -} - -impl Column { - fn new( - window: W, - view_size: Size, - working_area: Rectangle, - options: Rc, - width: ColumnWidth, - is_full_width: bool, - ) -> Self { - let mut rv = Self { - windows: vec![], - heights: vec![], - active_window_idx: 0, - width, - is_full_width, - is_fullscreen: false, - view_size, - working_area, - options, - }; - - rv.add_window(window); - - rv - } - - fn set_view_size(&mut self, size: Size, working_area: Rectangle) { - if self.view_size == size && self.working_area == working_area { - return; - } - - self.view_size = size; - self.working_area = working_area; - - self.update_window_sizes(); - } - - fn update_config(&mut self, options: Rc) { - let mut update_sizes = false; - - // If preset widths changed, make our width non-preset. - if self.options.preset_widths != options.preset_widths { - if let ColumnWidth::Preset(idx) = self.width { - self.width = self.options.preset_widths[idx]; - } - } - - if self.options.gaps != options.gaps { - update_sizes = true; - } - - self.options = options; - - if update_sizes { - self.update_window_sizes(); - } - } - - fn set_width(&mut self, width: ColumnWidth) { - self.width = width; - self.is_full_width = false; - self.update_window_sizes(); - } - - fn contains(&self, window: &W) -> bool { - self.windows.iter().any(|win| win == window) - } - - fn activate_window(&mut self, window: &W) { - let idx = self.windows.iter().position(|win| win == window).unwrap(); - self.active_window_idx = idx; - } - - fn add_window(&mut self, window: W) { - self.is_fullscreen = false; - self.windows.push(window); - self.heights.push(WindowHeight::Auto); - self.update_window_sizes(); - } - - fn update_window_sizes(&mut self) { - if self.is_fullscreen { - self.windows[0].request_fullscreen(self.view_size); - 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(); - - // Compute the column width. - let min_width = min_size - .iter() - .filter_map(|size| { - let w = size.w; - if w == 0 { - None - } else { - Some(w) - } - }) - .max() - .unwrap_or(1); - let max_width = max_size - .iter() - .filter_map(|size| { - let w = size.w; - if w == 0 { - None - } else { - Some(w) - } - }) - .min() - .unwrap_or(i32::MAX); - let max_width = max(max_width, min_width); - - let width = if self.is_full_width { - ColumnWidth::Proportion(1.) - } else { - self.width - }; - - 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(); - 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. -