diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-24 14:20:50 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-24 14:20:50 +0400 |
| commit | 624c799ebf4470ca6dfc80592029132071730c57 (patch) | |
| tree | c6afaf00c947e0eb8115c52a8051881425c09590 /src/layout.rs | |
| parent | 57f267454fa0c746707c430baa1fbd12124f636c (diff) | |
| download | niri-624c799ebf4470ca6dfc80592029132071730c57.tar.gz niri-624c799ebf4470ca6dfc80592029132071730c57.tar.bz2 niri-624c799ebf4470ca6dfc80592029132071730c57.zip | |
Move layout.rs into its own module
Diffstat (limited to 'src/layout.rs')
| -rw-r--r-- | src/layout.rs | 4025 |
1 files changed, 0 insertions, 4025 deletions
diff --git a/src/layout.rs b/src/layout.rs deleted file mode 100644 index bf012311..00000000 --- a/src/layout.rs +++ /dev/null @@ -1,4025 +0,0 @@ -//! Window layout logic. -//! -//! Niri implements scrollable tiling with workspaces. There's one primary output, and potentially -//! multiple other outputs. -//! -//! Our layout has the following invariants: -//! -//! 1. Disconnecting and reconnecting the same output must not change the layout. -//! * This includes both secondary outputs and the primary output. -//! 2. Connecting an output must not change the layout for any workspaces that were never on that -//! output. -//! -//! Therefore, we implement the following logic: every workspace keeps track of which output it -//! originated on. When an output disconnects, its workspace (or workspaces, in case of the primary -//! output disconnecting) are appended to the (potentially new) primary output, but remember their -//! original output. Then, if the original output connects again, all workspaces originally from -//! there move back to that output. -//! -//! In order to avoid surprising behavior, if the user creates or moves any new windows onto a -//! workspace, it forgets its original output, and its current output becomes its original output. -//! Imagine a scenario: the user works with a laptop and a monitor at home, then takes their laptop -//! with them, disconnecting the monitor, and keeps working as normal, using the second monitor's -//! workspace just like any other. Then they come back, reconnect the second monitor, and now we -//! don't want an unassuming workspace to end up on it. -//! -//! ## Workspaces-only-on-primary considerations -//! -//! If this logic results in more than one workspace present on a secondary output, then as a -//! 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::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::wayland::compositor::{send_surface_state, with_states}; -use smithay::wayland::shell::xdg::SurfaceCachedState; - -use crate::animation::Animation; -use crate::config::{self, Color, Config, PresetWidth, SizeChange, Struts}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OutputId(String); - -render_elements! { - #[derive(Debug)] - pub WorkspaceRenderElement<R> where R: ImportAll; - Wayland = WaylandSurfaceRenderElement<R>, - FocusRing = SolidColorRenderElement, -} -pub type MonitorRenderElement<R> = - RelocateRenderElement<CropRenderElement<WorkspaceRenderElement<R>>>; - -pub trait LayoutElement: SpaceElement + PartialEq + Clone { - fn request_size(&self, size: Size<i32, Logical>); - fn request_fullscreen(&self, size: Size<i32, Logical>); - fn min_size(&self) -> Size<i32, Logical>; - fn max_size(&self) -> Size<i32, Logical>; - fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool; - fn has_ssd(&self) -> bool; - fn set_preferred_scale_transform(&self, scale: i32, transform: Transform); -} - -#[derive(Debug)] -pub struct Layout<W: LayoutElement> { - /// Monitors and workspaes in the layout. - monitor_set: MonitorSet<W>, - /// Configurable properties of the layout. - options: Rc<Options>, -} - -#[derive(Debug)] -enum MonitorSet<W: LayoutElement> { - /// At least one output is connected. - Normal { - /// Connected monitors. - monitors: Vec<Monitor<W>>, - /// Index of the primary monitor. - primary_idx: usize, - /// Index of the active monitor. - active_monitor_idx: usize, - }, - /// No outputs are connected, and these are the workspaces. - NoOutputs { - /// The workspaces. - workspaces: Vec<Workspace<W>>, - }, -} - -#[derive(Debug)] -pub struct Monitor<W: LayoutElement> { - /// Output for this monitor. - output: Output, - // Must always contain at least one. - workspaces: Vec<Workspace<W>>, - /// Index of the currently active workspace. - active_workspace_idx: usize, - /// In-progress switch between workspaces. - workspace_switch: Option<WorkspaceSwitch>, - /// Configurable properties of the layout. - options: Rc<Options>, -} - -#[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<W: LayoutElement> { - /// 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<Output>, - - /// 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<i32, Logical>, - - /// 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<i32, Logical>, - - /// Columns of windows on this workspace. - columns: Vec<Column<W>>, - - /// 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<Animation>, - - /// 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<Options>, -} - -#[derive(Debug)] -struct FocusRing { - buffers: [SolidColorBuffer; 4], - locations: [Point<i32, Logical>; 4], - is_off: bool, - is_border: bool, - width: i32, - active_color: Color, - inactive_color: Color, -} - -#[derive(Debug, PartialEq)] -struct Options { - /// Padding around windows in logical pixels. - gaps: i32, - /// Extra padding around the working area in logical pixels. - struts: Struts, - focus_ring: config::FocusRing, - /// Column widths that `toggle_width()` switches between. - preset_widths: Vec<ColumnWidth>, - /// Initial width for new windows. - default_width: Option<ColumnWidth>, -} - -impl Default for Options { - fn default() -> Self { - Self { - gaps: 16, - struts: Default::default(), - focus_ring: Default::default(), - preset_widths: vec![ - ColumnWidth::Proportion(1. / 3.), - ColumnWidth::Proportion(0.5), - ColumnWidth::Proportion(2. / 3.), - ], - default_width: None, - } - } -} - -impl Options { - fn from_config(config: &Config) -> Self { - let preset_column_widths = &config.preset_column_widths; - - let preset_widths = if preset_column_widths.is_empty() { - Options::default().preset_widths - } else { - preset_column_widths - .iter() - .copied() - .map(ColumnWidth::from) - .collect() - }; - - // Missing default_column_width maps to Some(ColumnWidth::Proportion(0.5)), - // while present, but empty, maps to None. - let default_width = config - .default_column_width - .as_ref() - .map(|w| w.0.first().copied().map(ColumnWidth::from)) - .unwrap_or(Some(ColumnWidth::Proportion(0.5))); - - Self { - gaps: config.gaps.into(), - struts: config.struts, - focus_ring: config.focus_ring, - preset_widths, - default_width, - } - } -} - -/// 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<PresetWidth> 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<W: LayoutElement> { - /// Windows in this column. - /// - /// Must be non-empty. - windows: Vec<W>, - - /// Heights of the windows. - /// - /// Must have the same number of elements as `windows`. - heights: Vec<WindowHeight>, - - /// 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<i32, Logical>, - - /// Latest known working area for this column's workspace. - working_area: Rectangle<i32, Logical>, - - /// Configurable properties of the layout. - options: Rc<Options>, -} - -impl OutputId { - pub fn new(output: &Output) -> Self { - Self(output.name()) - } -} - -impl LayoutElement for Window { - fn request_size(&self, size: Size<i32, Logical>) { - self.toplevel().with_pending_state(|state| { - state.size = Some(size); - state.states.unset(xdg_toplevel::State::Fullscreen); - }); - } - - fn request_fullscreen(&self, size: Size<i32, Logical>) { - self.toplevel().with_pending_state(|state| { - state.size = Some(size); - state.states.set(xdg_toplevel::State::Fullscreen); - }); - } - - fn min_size(&self) -> Size<i32, Logical> { - with_states(self.toplevel().wl_surface(), |state| { - let curr = state.cached_state.current::<SurfaceCachedState>(); - curr.min_size - }) - } - - fn max_size(&self) -> Size<i32, Logical> { - with_states(self.toplevel().wl_surface(), |state| { - let curr = state.cached_state.current::<SurfaceCachedState>(); - curr.max_size - }) - } - - fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool { - self.toplevel().wl_surface() == wl_surface - } - - fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) { - self.with_surfaces(|surface, data| { - send_surface_state(surface, data, scale, transform); - }); - } - - fn has_ssd(&self) -> bool { - self.toplevel().current_state().decoration_mode - == Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) - } -} - -impl FocusRing { - fn update( - &mut self, - win_pos: Point<i32, Logical>, - win_size: Size<i32, Logical>, - 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<f64>) -> impl Iterator<Item = SolidColorRenderElement> { - let mut rv = ArrayVec::<_, 4>::new(); - - if self.is_off { - return rv.into_iter(); - } - - let mut push = |buffer, location: Point<i32, Logical>| { - 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<W: LayoutElement> Layout<W> { - pub fn new(config: &Config) -> Self { - Self { - monitor_set: MonitorSet::NoOutputs { workspaces: vec![] }, - options: Rc::new(Options::from_config(config)), - } - } - - pub fn add_output(&mut self, output: Output) { - let id = OutputId::new(&output); - - self.monitor_set = match mem::take(&mut self.monitor_set) { - MonitorSet::Normal { - mut monitors, - primary_idx, - active_monitor_idx, - } => { - let primary = &mut monitors[primary_idx]; - - let mut workspaces = vec![]; - for i in (0..primary.workspaces.len()).rev() { - if primary.workspaces[i].original_output == id { - let ws = primary.workspaces.remove(i); - - // The user could've closed a window while remaining on this workspace, on - // another monitor. However, we will add an empty workspace in the end - // instead. - if ws.has_windows() { - workspaces.push(ws); - } - - if i <= primary.active_workspace_idx { - primary.active_workspace_idx = - primary.active_workspace_idx.saturating_sub(1); - } - } - } - workspaces.reverse(); - - // Make sure there's always an empty workspace. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); - - for ws in &mut workspaces { - ws.set_output(Some(output.clone())); - } - - monitors.push(Monitor::new(output, workspaces, self.options.clone())); - MonitorSet::Normal { - monitors, - primary_idx, - active_monitor_idx, - } - } - MonitorSet::NoOutputs { mut workspaces } => { - // We know there are no empty workspaces there, so add one. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); - - for workspace in &mut workspaces { - workspace.set_output(Some(output.clone())); - } - - let monitor = Monitor::new(output, workspaces, self.options.clone()); - - MonitorSet::Normal { - monitors: vec![monitor], - primary_idx: 0, - active_monitor_idx: 0, - } - } - } - } - - pub fn remove_output(&mut self, output: &Output) { - self.monitor_set = match mem::take(&mut self.monitor_set) { - MonitorSet::Normal { - mut monitors, - mut primary_idx, - mut active_monitor_idx, - } => { - let idx = monitors - .iter() - .position(|mon| &mon.output == output) - .expect("trying to remove non-existing output"); - let monitor = monitors.remove(idx); - let mut workspaces = monitor.workspaces; - - for ws in &mut workspaces { - ws.set_output(None); - } - - // Get rid of empty workspaces. - workspaces.retain(|ws| ws.has_windows()); - - if monitors.is_empty() { - // Removed the last monitor. - MonitorSet::NoOutputs { workspaces } - } else { - if primary_idx >= idx { - // Update primary_idx to either still point at the same monitor, or at some - // other monitor if the primary has been removed. - primary_idx = primary_idx.saturating_sub(1); - } - if active_monitor_idx >= idx { - // Update active_monitor_idx to either still point at the same monitor, or - // at some other monitor if the active monitor has - // been removed. - active_monitor_idx = active_monitor_idx.saturating_sub(1); - } - - let primary = &mut monitors[primary_idx]; - for ws in &mut workspaces { - ws.set_output(Some(primary.output.clone())); - } - - let empty_was_focused = - primary.active_workspace_idx == primary.workspaces.len() - 1; - - // Push the workspaces from the removed monitor in the end, right before the - // last, empty, workspace. - let empty = primary.workspaces.remove(primary.workspaces.len() - 1); - primary.workspaces.extend(workspaces); - primary.workspaces.push(empty); - - // If the empty workspace was focused on the primary monitor, keep it focused. - if empty_was_focused { - primary.active_workspace_idx = primary.workspaces.len() - 1; - } - - MonitorSet::Normal { - monitors, - primary_idx, - active_monitor_idx, - } - } - } - MonitorSet::NoOutputs { .. } => { - panic!("tried to remove output when there were already none") - } - } - } - - pub fn add_window_by_idx( - &mut self, - monitor_idx: usize, - workspace_idx: usize, - window: W, - activate: bool, - width: ColumnWidth, - is_full_width: bool, - ) { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &mut self.monitor_set - else { - panic!() - }; - - monitors[monitor_idx].add_window(workspace_idx, window, activate, width, is_full_width); - - if activate { - *active_monitor_idx = monitor_idx; - } - } - - /// Adds a new window to the layout. - /// - /// Returns an output that the window was added to, if there were any outputs. - pub fn add_window( - &mut self, - window: W, - width: Option<ColumnWidth>, - is_full_width: bool, - ) -> Option<&Output> { - let width = width - .or(self.options.default_width) - .unwrap_or_else(|| ColumnWidth::Fixed(window.geometry().size.w)); - - match &mut self.monitor_set { - MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } => { - let mon = &mut monitors[*active_monitor_idx]; - - // Don't steal focus from an active fullscreen window. - let mut activate = true; - let ws = &mon.workspaces[mon.active_workspace_idx]; - if !ws.columns.is_empty() && ws.columns[ws.active_column_idx].is_fullscreen { - activate = false; - } - - mon.add_window( - mon.active_workspace_idx, - window, - activate, - width, - is_full_width, - ); - Some(&mon.output) - } - MonitorSet::NoOutputs { workspaces } => { - let ws = if let Some(ws) = workspaces.get_mut(0) { - ws - } else { - workspaces.push(Workspace::new_no_outputs(self.options.clone())); - &mut workspaces[0] - }; - ws.add_window(window, true, width, is_full_width); - None - } - } - } - - pub fn remove_window(&mut self, window: &W) { - match &mut self.monitor_set { - MonitorSet::Normal { monitors, .. } => { - for mon in monitors { - for (idx, ws) in mon.workspaces.iter_mut().enumerate() { - if ws.has_window(window) { - ws.remove_window(window); - - // Clean up empty workspaces that are not active and not last. - if !ws.has_windows() - && idx != mon.active_workspace_idx - && idx != mon.workspaces.len() - 1 - { - mon.workspaces.remove(idx); - - if idx < mon.active_workspace_idx { - mon.active_workspace_idx -= 1; - } - } - - break; - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - for (idx, ws) in workspaces.iter_mut().enumerate() { - if ws.has_window(window) { - ws.remove_window(window); - - // Clean up empty workspaces. - if !ws.has_windows() { - workspaces.remove(idx); - } - - break; - } - } - } - } - } - - pub fn update_window(&mut self, window: &W) { - match &mut self.monitor_set { - MonitorSet::Normal { monitors, .. } => { - for mon in monitors { - for ws in &mut mon.workspaces { - if ws.has_window(window) { - ws.update_window(window); - return; - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - for ws in workspaces { - if ws.has_window(window) { - ws.update_window(window); - return; - } - } - } - } - } - - pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(W, Output)> { - if let MonitorSet::Normal { monitors, .. } = &self.monitor_set { - for mon in monitors { - for ws in &mon.workspaces { - if let Some(window) = ws.find_wl_surface(wl_surface) { - return Some((window.clone(), mon.output.clone())); - } - } - } - } - - None - } - - pub fn window_y(&self, window: &W) -> Option<i32> { - match &self.monitor_set { - MonitorSet::Normal { monitors, .. } => { - for mon in monitors { - for ws in &mon.workspaces { - for col in &ws.columns { - if let Some(idx) = col.windows.iter().position(|w| w == window) { - return Some(col.window_y(idx)); - } - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - for ws in workspaces { - for col in &ws.columns { - if let Some(idx) = col.windows.iter().position(|w| w == window) { - return Some(col.window_y(idx)); - } - } - } - } - } - - None - } - - pub fn update_output_size(&mut self, output: &Output) { - let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else { - panic!() - }; - - for mon in monitors { - if &mon.output == output { - let view_size = output_size(output); - let working_area = compute_working_area(output, self.options.struts); - - for ws in &mut mon.workspaces { - ws.set_view_size(view_size, working_area); - } - - break; - } - } - } - - pub fn activate_window(&mut self, window: &W) { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &mut self.monitor_set - else { - todo!() - }; - - for (monitor_idx, mon) in monitors.iter_mut().enumerate() { - for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() { - if ws.has_window(window) { - *active_monitor_idx = monitor_idx; - ws.activate_window(window); - - // Switch to that workspace if not already during a transition. - if mon.workspace_switch.is_none() { - mon.switch_workspace(workspace_idx); - } - - break; - } - } - } - } - - pub fn activate_output(&mut self, output: &Output) { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &mut self.monitor_set - else { - return; - }; - - let idx = monitors - .iter() - .position(|mon| &mon.output == output) - .unwrap(); - *active_monitor_idx = idx; - } - - pub fn active_output(&self) -> Option<&Output> { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &self.monitor_set - else { - return None; - }; - - Some(&monitors[*active_monitor_idx].output) - } - - pub fn active_workspace(&self) -> Option<&Workspace<W>> { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &self.monitor_set - else { - return None; - }; - - let mon = &monitors[*active_monitor_idx]; - Some(&mon.workspaces[mon.active_workspace_idx]) - } - - pub fn active_window(&self) -> Option<(W, Output)> { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &self.monitor_set - else { - return None; - }; - - let mon = &monitors[*active_monitor_idx]; - let ws = &mon.workspaces[mon.active_workspace_idx]; - - if ws.columns.is_empty() { - return None; - } - - let col = &ws.columns[ws.active_column_idx]; - Some(( - col.windows[col.active_window_idx].clone(), - mon.output.clone(), - )) - } - - pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ { - let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { - panic!() - }; - - let mon = monitors.iter().find(|mon| &mon.output == output).unwrap(); - mon.workspaces.iter().flat_map(|ws| ws.windows()) - } - - fn active_monitor(&mut self) -> Option<&mut Monitor<W>> { - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &mut self.monitor_set - else { - return None; - }; - - Some(&mut monitors[*active_monitor_idx]) - } - - pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> { - let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { - return None; - }; - - monitors.iter().find(|monitor| &monitor.output == output) - } - - pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ { - let monitors = if let MonitorSet::Normal { monitors, .. } = &self.monitor_set { - &monitors[..] - } else { - &[][..] - }; - - monitors.iter().map(|mon| &mon.output) - } - - pub fn move_left(&mut self) { - let Some(monitor) = self.active_monitor() else { - return; - }; - monitor.move_left(); - } - - pub fn move_right(&mut self) { - let Some(monitor) = self.active_monitor() else { - return; - }; - monitor.move_right(); - } - - pub fn move_down(&mut self) { - let Some(monitor) = self.active_monitor() else { - return; - }; - monitor.move_down(); - } - - pub fn move_up(&mut self) { - let Some(monitor) = self.active_monitor() else { - return; - }; - monitor.move_up(); - } - - pub fn move_down_or_to_workspace_down(&mut self) { - let Some(monitor) = self.active_monitor() else { - return; - }; - monitor.move_down_or_to_workspace_down(); - } - - pub fn move_up_or_to_works |
