2023-08-13 12:46:53 +04:00
|
|
|
//! 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.
|
|
|
|
|
|
2024-02-03 08:42:56 +04:00
|
|
|
use std::cmp::min;
|
2023-08-13 12:46:53 +04:00
|
|
|
use std::mem;
|
2023-10-05 09:25:07 +04:00
|
|
|
use std::rc::Rc;
|
2023-08-13 19:55:37 +04:00
|
|
|
use std::time::Duration;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-09-05 23:37:10 +02:00
|
|
|
use niri_config::{
|
|
|
|
|
CenterFocusedColumn, Config, FloatOrInt, PresetSize, Struts, Workspace as WorkspaceConfig,
|
|
|
|
|
};
|
2024-02-10 09:33:32 +04:00
|
|
|
use niri_ipc::SizeChange;
|
2024-02-04 22:29:09 +04:00
|
|
|
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
2024-03-19 14:41:17 +04:00
|
|
|
use smithay::backend::renderer::element::Id;
|
2024-04-10 08:53:35 +04:00
|
|
|
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
2024-05-29 13:32:11 +03:00
|
|
|
use smithay::output::{self, Output};
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
2024-05-10 16:58:53 +04:00
|
|
|
use smithay::utils::{Logical, Point, Scale, Serial, Size, Transform};
|
2024-10-14 18:08:44 +03:00
|
|
|
use tile::Tile;
|
2024-06-20 12:04:10 +03:00
|
|
|
use workspace::WorkspaceId;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-12-24 15:10:09 +04:00
|
|
|
pub use self::monitor::MonitorRenderElement;
|
2024-06-17 20:20:26 +03:00
|
|
|
use self::monitor::{Monitor, WorkspaceSwitch};
|
2024-02-04 22:29:09 +04:00
|
|
|
use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace};
|
2024-02-05 14:05:08 +04:00
|
|
|
use crate::niri_render_elements;
|
2024-02-06 11:24:50 +04:00
|
|
|
use crate::render_helpers::renderer::NiriRenderer;
|
2024-04-13 14:16:07 +04:00
|
|
|
use crate::render_helpers::snapshot::RenderSnapshot;
|
2024-06-17 09:16:28 +03:00
|
|
|
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
2024-06-01 12:27:30 +03:00
|
|
|
use crate::render_helpers::texture::TextureBuffer;
|
2024-05-01 19:02:22 +04:00
|
|
|
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
2024-08-23 15:41:06 +03:00
|
|
|
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
2024-09-03 12:13:04 +03:00
|
|
|
use crate::utils::{output_matches_name, output_size, round_logical_in_physical_max1, ResizeEdge};
|
2024-03-19 18:22:25 +04:00
|
|
|
use crate::window::ResolvedWindowRules;
|
2023-08-14 15:54:11 +04:00
|
|
|
|
2024-04-09 22:37:10 +04:00
|
|
|
pub mod closing_window;
|
2024-02-05 17:27:09 +04:00
|
|
|
pub mod focus_ring;
|
|
|
|
|
pub mod monitor;
|
2024-05-15 19:38:29 +04:00
|
|
|
pub mod opening_window;
|
2024-02-05 17:27:09 +04:00
|
|
|
pub mod tile;
|
|
|
|
|
pub mod workspace;
|
2023-08-14 16:19:43 +04:00
|
|
|
|
2024-04-13 11:07:23 +04:00
|
|
|
/// Size changes up to this many pixels don't animate.
|
2024-06-17 09:16:28 +03:00
|
|
|
pub const RESIZE_ANIMATION_THRESHOLD: f64 = 10.;
|
2024-04-13 11:07:23 +04:00
|
|
|
|
2024-02-05 14:05:08 +04:00
|
|
|
niri_render_elements! {
|
2024-02-21 09:05:41 +04:00
|
|
|
LayoutElementRenderElement<R> => {
|
2024-02-05 14:05:08 +04:00
|
|
|
Wayland = WaylandSurfaceRenderElement<R>,
|
|
|
|
|
SolidColor = SolidColorRenderElement,
|
|
|
|
|
}
|
2024-02-04 22:29:09 +04:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 08:53:35 +04:00
|
|
|
pub type LayoutElementRenderSnapshot =
|
|
|
|
|
RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>;
|
2024-04-09 22:37:10 +04:00
|
|
|
|
2024-05-10 16:58:53 +04:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub struct InteractiveResizeData {
|
2024-10-10 09:24:20 +03:00
|
|
|
pub(self) edges: ResizeEdge,
|
2024-05-10 16:58:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-22 14:36:47 +03:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum ConfigureIntent {
|
|
|
|
|
/// A configure is not needed (no changes to server pending state).
|
|
|
|
|
NotNeeded,
|
|
|
|
|
/// A configure is throttled (due to resizing too fast for example).
|
|
|
|
|
Throttled,
|
|
|
|
|
/// Can send the configure if it isn't throttled externally (only size changed).
|
|
|
|
|
CanSend,
|
|
|
|
|
/// Should send the configure regardless of external throttling (something other than size
|
|
|
|
|
/// changed).
|
|
|
|
|
ShouldSend,
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
pub trait LayoutElement {
|
|
|
|
|
/// Type that can be used as a unique ID of this element.
|
2024-05-10 16:58:53 +04:00
|
|
|
type Id: PartialEq + std::fmt::Debug;
|
2024-03-19 13:42:04 +04:00
|
|
|
|
|
|
|
|
/// Unique ID of this element.
|
|
|
|
|
fn id(&self) -> &Self::Id;
|
|
|
|
|
|
2023-12-26 17:38:40 +04:00
|
|
|
/// Visual size of the element.
|
|
|
|
|
///
|
|
|
|
|
/// This is what the user would consider the size, i.e. excluding CSD shadows and whatnot.
|
|
|
|
|
/// Corresponds to the Wayland window geometry size.
|
|
|
|
|
fn size(&self) -> Size<i32, Logical>;
|
|
|
|
|
|
|
|
|
|
/// Returns the location of the element's buffer relative to the element's visual geometry.
|
|
|
|
|
///
|
|
|
|
|
/// I.e. if the element has CSD shadows, its buffer location will have negative coordinates.
|
|
|
|
|
fn buf_loc(&self) -> Point<i32, Logical>;
|
|
|
|
|
|
|
|
|
|
/// Checks whether a point is in the element's input region.
|
|
|
|
|
///
|
|
|
|
|
/// The point is relative to the element's visual geometry.
|
2023-12-24 18:14:42 +04:00
|
|
|
fn is_in_input_region(&self, point: Point<f64, Logical>) -> bool;
|
2023-12-26 17:38:40 +04:00
|
|
|
|
|
|
|
|
/// Renders the element at the given visual location.
|
|
|
|
|
///
|
|
|
|
|
/// The element should be rendered in such a way that its visual geometry ends up at the given
|
|
|
|
|
/// location.
|
2024-02-05 14:05:08 +04:00
|
|
|
fn render<R: NiriRenderer>(
|
2023-12-26 17:38:40 +04:00
|
|
|
&self,
|
|
|
|
|
renderer: &mut R,
|
2024-06-17 09:16:28 +03:00
|
|
|
location: Point<f64, Logical>,
|
2023-12-26 17:38:40 +04:00
|
|
|
scale: Scale<f64>,
|
2024-03-24 08:30:26 +04:00
|
|
|
alpha: f32,
|
2024-03-24 09:03:59 +04:00
|
|
|
target: RenderTarget,
|
2024-05-01 19:02:22 +04:00
|
|
|
) -> SplitElements<LayoutElementRenderElement<R>>;
|
|
|
|
|
|
|
|
|
|
/// Renders the non-popup parts of the element.
|
|
|
|
|
fn render_normal<R: NiriRenderer>(
|
|
|
|
|
&self,
|
|
|
|
|
renderer: &mut R,
|
2024-06-17 09:16:28 +03:00
|
|
|
location: Point<f64, Logical>,
|
2024-05-01 19:02:22 +04:00
|
|
|
scale: Scale<f64>,
|
|
|
|
|
alpha: f32,
|
|
|
|
|
target: RenderTarget,
|
|
|
|
|
) -> Vec<LayoutElementRenderElement<R>> {
|
|
|
|
|
self.render(renderer, location, scale, alpha, target).normal
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Renders the popups of the element.
|
|
|
|
|
fn render_popups<R: NiriRenderer>(
|
|
|
|
|
&self,
|
|
|
|
|
renderer: &mut R,
|
2024-06-17 09:16:28 +03:00
|
|
|
location: Point<f64, Logical>,
|
2024-05-01 19:02:22 +04:00
|
|
|
scale: Scale<f64>,
|
|
|
|
|
alpha: f32,
|
|
|
|
|
target: RenderTarget,
|
|
|
|
|
) -> Vec<LayoutElementRenderElement<R>> {
|
|
|
|
|
self.render(renderer, location, scale, alpha, target).popups
|
|
|
|
|
}
|
2023-12-26 17:38:40 +04:00
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
fn request_size(
|
|
|
|
|
&mut self,
|
|
|
|
|
size: Size<i32, Logical>,
|
|
|
|
|
animate: bool,
|
|
|
|
|
transaction: Option<Transaction>,
|
|
|
|
|
);
|
2023-08-16 09:08:10 +04:00
|
|
|
fn request_fullscreen(&self, size: Size<i32, Logical>);
|
2023-08-13 19:55:37 +04:00
|
|
|
fn min_size(&self) -> Size<i32, Logical>;
|
2023-08-16 10:03:24 +04:00
|
|
|
fn max_size(&self) -> Size<i32, Logical>;
|
2023-08-13 19:55:37 +04:00
|
|
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool;
|
2023-10-11 14:32:29 +04:00
|
|
|
fn has_ssd(&self) -> bool;
|
2024-05-29 13:32:11 +03:00
|
|
|
fn set_preferred_scale_transform(&self, scale: output::Scale, transform: Transform);
|
2023-12-24 18:14:42 +04:00
|
|
|
fn output_enter(&self, output: &Output);
|
|
|
|
|
fn output_leave(&self, output: &Output);
|
2024-02-07 11:32:02 +04:00
|
|
|
fn set_offscreen_element_id(&self, id: Option<Id>);
|
2024-03-23 14:38:07 +04:00
|
|
|
fn set_activated(&mut self, active: bool);
|
2024-04-22 22:51:52 +02:00
|
|
|
fn set_active_in_column(&mut self, active: bool);
|
2024-03-19 13:52:08 +04:00
|
|
|
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
|
|
|
|
|
2024-08-22 14:36:47 +03:00
|
|
|
fn configure_intent(&self) -> ConfigureIntent;
|
2024-04-13 11:07:23 +04:00
|
|
|
fn send_pending_configure(&mut self);
|
2023-12-27 21:51:42 +04:00
|
|
|
|
|
|
|
|
/// Whether the element is currently fullscreen.
|
|
|
|
|
///
|
|
|
|
|
/// This will *not* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
|
|
|
|
fn is_fullscreen(&self) -> bool;
|
2024-02-03 09:44:34 +04:00
|
|
|
|
|
|
|
|
/// Whether we're requesting the element to be fullscreen.
|
|
|
|
|
///
|
|
|
|
|
/// This *will* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
|
|
|
|
fn is_pending_fullscreen(&self) -> bool;
|
2024-03-19 13:52:08 +04:00
|
|
|
|
2024-09-12 19:31:47 +03:00
|
|
|
/// Size previously requested through [`LayoutElement::request_size()`].
|
|
|
|
|
fn requested_size(&self) -> Option<Size<i32, Logical>>;
|
|
|
|
|
|
2024-03-19 18:22:25 +04:00
|
|
|
fn rules(&self) -> &ResolvedWindowRules;
|
|
|
|
|
|
2024-03-19 13:52:08 +04:00
|
|
|
/// Runs periodic clean-up tasks.
|
|
|
|
|
fn refresh(&self);
|
2024-04-13 11:07:23 +04:00
|
|
|
|
2024-04-13 14:16:07 +04:00
|
|
|
fn animation_snapshot(&self) -> Option<&LayoutElementRenderSnapshot>;
|
|
|
|
|
fn take_animation_snapshot(&mut self) -> Option<LayoutElementRenderSnapshot>;
|
2024-05-10 16:58:53 +04:00
|
|
|
|
|
|
|
|
fn set_interactive_resize(&mut self, data: Option<InteractiveResizeData>);
|
|
|
|
|
fn cancel_interactive_resize(&mut self);
|
2024-05-11 08:26:49 +04:00
|
|
|
fn update_interactive_resize(&mut self, serial: Serial);
|
|
|
|
|
fn interactive_resize_data(&self) -> Option<InteractiveResizeData>;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug)]
|
2023-10-05 09:25:07 +04:00
|
|
|
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> {
|
2023-08-13 12:46:53 +04:00
|
|
|
/// At least one output is connected.
|
|
|
|
|
Normal {
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Connected monitors.
|
|
|
|
|
monitors: Vec<Monitor<W>>,
|
2023-08-13 12:46:53 +04:00
|
|
|
/// 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.
|
2023-10-05 09:25:07 +04:00
|
|
|
NoOutputs {
|
|
|
|
|
/// The workspaces.
|
|
|
|
|
workspaces: Vec<Workspace<W>>,
|
|
|
|
|
},
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2023-12-24 15:10:09 +04:00
|
|
|
pub struct Options {
|
2023-10-07 17:45:55 +04:00
|
|
|
/// Padding around windows in logical pixels.
|
2024-06-17 09:16:28 +03:00
|
|
|
pub gaps: f64,
|
2023-12-21 08:37:30 +04:00
|
|
|
/// Extra padding around the working area in logical pixels.
|
2024-02-05 17:27:09 +04:00
|
|
|
pub struts: Struts,
|
|
|
|
|
pub focus_ring: niri_config::FocusRing,
|
2024-02-12 09:34:54 +04:00
|
|
|
pub border: niri_config::Border,
|
2024-02-05 17:27:09 +04:00
|
|
|
pub center_focused_column: CenterFocusedColumn,
|
2024-08-29 16:41:59 +02:00
|
|
|
pub always_center_single_column: bool,
|
2023-10-05 09:25:07 +04:00
|
|
|
/// Column widths that `toggle_width()` switches between.
|
2024-09-05 23:37:10 +02:00
|
|
|
pub preset_column_widths: Vec<ColumnWidth>,
|
2023-12-28 08:14:35 +04:00
|
|
|
/// Initial width for new columns.
|
2024-09-05 23:37:10 +02:00
|
|
|
pub default_column_width: Option<ColumnWidth>,
|
|
|
|
|
/// Window height that `toggle_window_height()` switches between.
|
|
|
|
|
pub preset_window_heights: Vec<PresetSize>,
|
2024-02-07 17:05:15 +04:00
|
|
|
pub animations: niri_config::Animations,
|
2024-08-22 14:36:47 +03:00
|
|
|
// Debug flags.
|
|
|
|
|
pub disable_resize_throttling: bool,
|
2024-08-22 14:44:11 +03:00
|
|
|
pub disable_transactions: bool,
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Options {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2024-06-17 09:16:28 +03:00
|
|
|
gaps: 16.,
|
2023-12-21 08:37:30 +04:00
|
|
|
struts: Default::default(),
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: Default::default(),
|
2024-02-12 09:34:54 +04:00
|
|
|
border: Default::default(),
|
2024-01-08 17:17:19 +04:00
|
|
|
center_focused_column: Default::default(),
|
2024-08-29 16:41:59 +02:00
|
|
|
always_center_single_column: false,
|
2024-09-05 23:37:10 +02:00
|
|
|
preset_column_widths: vec![
|
2023-10-05 09:25:07 +04:00
|
|
|
ColumnWidth::Proportion(1. / 3.),
|
|
|
|
|
ColumnWidth::Proportion(0.5),
|
|
|
|
|
ColumnWidth::Proportion(2. / 3.),
|
|
|
|
|
],
|
2024-09-05 23:37:10 +02:00
|
|
|
default_column_width: None,
|
2024-02-07 17:05:15 +04:00
|
|
|
animations: Default::default(),
|
2024-08-22 14:36:47 +03:00
|
|
|
disable_resize_throttling: false,
|
2024-08-22 14:44:11 +03:00
|
|
|
disable_transactions: false,
|
2024-09-05 23:37:10 +02:00
|
|
|
preset_window_heights: vec![
|
|
|
|
|
PresetSize::Proportion(1. / 3.),
|
|
|
|
|
PresetSize::Proportion(0.5),
|
|
|
|
|
PresetSize::Proportion(2. / 3.),
|
|
|
|
|
],
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 18:08:44 +03:00
|
|
|
/// Tile that was just removed from the layout.
|
|
|
|
|
pub struct RemovedTile<W: LayoutElement> {
|
|
|
|
|
tile: Tile<W>,
|
|
|
|
|
/// Width of the column the tile was in.
|
|
|
|
|
width: ColumnWidth,
|
|
|
|
|
/// Whether the column the tile was in was full-width.
|
|
|
|
|
is_full_width: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl Options {
|
|
|
|
|
fn from_config(config: &Config) -> Self {
|
2024-01-06 13:04:21 +04:00
|
|
|
let layout = &config.layout;
|
2023-10-05 09:25:07 +04:00
|
|
|
|
2024-09-05 23:37:10 +02:00
|
|
|
let preset_column_widths = if layout.preset_column_widths.is_empty() {
|
|
|
|
|
Options::default().preset_column_widths
|
2023-10-05 09:25:07 +04:00
|
|
|
} else {
|
2024-09-05 23:37:10 +02:00
|
|
|
layout
|
|
|
|
|
.preset_column_widths
|
2023-10-05 09:25:07 +04:00
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.map(ColumnWidth::from)
|
|
|
|
|
.collect()
|
|
|
|
|
};
|
2024-09-05 23:37:10 +02:00
|
|
|
let preset_window_heights = if layout.preset_window_heights.is_empty() {
|
|
|
|
|
Options::default().preset_window_heights
|
|
|
|
|
} else {
|
|
|
|
|
layout.preset_window_heights.clone()
|
|
|
|
|
};
|
2023-10-05 09:25:07 +04:00
|
|
|
|
2023-11-02 21:07:29 +04:00
|
|
|
// Missing default_column_width maps to Some(ColumnWidth::Proportion(0.5)),
|
|
|
|
|
// while present, but empty, maps to None.
|
2024-09-05 23:37:10 +02:00
|
|
|
let default_column_width = layout
|
2023-11-02 21:07:29 +04:00
|
|
|
.default_column_width
|
|
|
|
|
.as_ref()
|
2024-03-01 12:50:49 +01:00
|
|
|
.map(|w| w.0.map(ColumnWidth::from))
|
2023-11-02 21:07:29 +04:00
|
|
|
.unwrap_or(Some(ColumnWidth::Proportion(0.5)));
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
Self {
|
2024-06-17 09:16:28 +03:00
|
|
|
gaps: layout.gaps.0,
|
2024-01-06 13:04:21 +04:00
|
|
|
struts: layout.struts,
|
|
|
|
|
focus_ring: layout.focus_ring,
|
|
|
|
|
border: layout.border,
|
2024-01-08 17:17:19 +04:00
|
|
|
center_focused_column: layout.center_focused_column,
|
2024-08-29 16:41:59 +02:00
|
|
|
always_center_single_column: layout.always_center_single_column,
|
2024-09-05 23:37:10 +02:00
|
|
|
preset_column_widths,
|
|
|
|
|
default_column_width,
|
2024-04-21 20:10:35 +04:00
|
|
|
animations: config.animations.clone(),
|
2024-08-22 14:36:47 +03:00
|
|
|
disable_resize_throttling: config.debug.disable_resize_throttling,
|
2024-08-22 14:44:11 +03:00
|
|
|
disable_transactions: config.debug.disable_transactions,
|
2024-09-05 23:37:10 +02:00
|
|
|
preset_window_heights,
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-17 09:16:28 +03:00
|
|
|
|
|
|
|
|
fn adjusted_for_scale(mut self, scale: f64) -> Self {
|
|
|
|
|
let round = |logical: f64| round_logical_in_physical_max1(scale, logical);
|
|
|
|
|
|
|
|
|
|
self.gaps = round(self.gaps);
|
|
|
|
|
self.focus_ring.width = FloatOrInt(round(self.focus_ring.width.0));
|
|
|
|
|
self.border.width = FloatOrInt(round(self.border.width.0));
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<W: LayoutElement> Layout<W> {
|
|
|
|
|
pub fn new(config: &Config) -> Self {
|
2024-05-11 22:40:30 +02:00
|
|
|
Self::with_options_and_workspaces(config, Options::from_config(config))
|
2024-02-06 19:09:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_options(options: Options) -> Self {
|
2023-10-05 09:25:07 +04:00
|
|
|
Self {
|
|
|
|
|
monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
|
2024-02-06 19:09:15 +04:00
|
|
|
options: Rc::new(options),
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
fn with_options_and_workspaces(config: &Config, options: Options) -> Self {
|
|
|
|
|
let opts = Rc::new(options);
|
|
|
|
|
|
|
|
|
|
let workspaces = config
|
|
|
|
|
.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|ws| Workspace::new_with_config_no_outputs(Some(ws.clone()), opts.clone()))
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
monitor_set: MonitorSet::NoOutputs { workspaces },
|
|
|
|
|
options: opts,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn add_output(&mut self, output: Output) {
|
2023-10-05 09:25:07 +04:00
|
|
|
self.monitor_set = match mem::take(&mut self.monitor_set) {
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
mut monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => {
|
|
|
|
|
let primary = &mut monitors[primary_idx];
|
|
|
|
|
|
2024-01-19 20:24:59 +04:00
|
|
|
let mut stopped_primary_ws_switch = false;
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let mut workspaces = vec![];
|
|
|
|
|
for i in (0..primary.workspaces.len()).rev() {
|
2024-09-03 12:13:04 +03:00
|
|
|
if primary.workspaces[i].original_output.matches(&output) {
|
2023-08-13 19:55:37 +04:00
|
|
|
let ws = primary.workspaces.remove(i);
|
2023-10-14 20:32:53 +04:00
|
|
|
|
2024-01-19 20:24:59 +04:00
|
|
|
// FIXME: this can be coded in a way that the workspace switch won't be
|
|
|
|
|
// affected if the removed workspace is invisible. But this is good enough
|
|
|
|
|
// for now.
|
|
|
|
|
if primary.workspace_switch.is_some() {
|
|
|
|
|
primary.workspace_switch = None;
|
|
|
|
|
stopped_primary_ws_switch = true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:32:53 +04:00
|
|
|
// 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.
|
2024-05-11 22:40:30 +02:00
|
|
|
if ws.has_windows() || ws.name.is_some() {
|
2023-10-14 20:32:53 +04:00
|
|
|
workspaces.push(ws);
|
|
|
|
|
}
|
2023-09-26 20:36:48 +04:00
|
|
|
|
|
|
|
|
if i <= primary.active_workspace_idx {
|
|
|
|
|
primary.active_workspace_idx =
|
|
|
|
|
primary.active_workspace_idx.saturating_sub(1);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-19 20:24:59 +04:00
|
|
|
|
|
|
|
|
// If we stopped a workspace switch, then we might need to clean up workspaces.
|
|
|
|
|
if stopped_primary_ws_switch {
|
|
|
|
|
primary.clean_up_workspaces();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
workspaces.reverse();
|
2023-10-14 20:32:53 +04:00
|
|
|
|
|
|
|
|
// Make sure there's always an empty workspace.
|
|
|
|
|
workspaces.push(Workspace::new(output.clone(), self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for ws in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(Some(output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
monitors.push(Monitor::new(output, workspaces, self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { mut workspaces } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
// We know there are no empty workspaces there, so add one.
|
2023-10-05 09:25:07 +04:00
|
|
|
workspaces.push(Workspace::new(output.clone(), self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for workspace in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.set_output(Some(output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let monitor = Monitor::new(output, workspaces, self.options.clone());
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors: vec![monitor],
|
|
|
|
|
primary_idx: 0,
|
|
|
|
|
active_monitor_idx: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
2023-10-05 09:25:07 +04:00
|
|
|
self.monitor_set = match mem::take(&mut self.monitor_set) {
|
2023-08-13 12:46:53 +04:00
|
|
|
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 {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(None);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get rid of empty workspaces.
|
2024-06-02 08:17:26 +03:00
|
|
|
workspaces.retain(|ws| ws.has_windows() || ws.name.is_some());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
if monitors.is_empty() {
|
|
|
|
|
// Removed the last monitor.
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces }
|
2023-08-13 12:46:53 +04:00
|
|
|
} 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 {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(Some(primary.output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
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.
|
2023-08-13 19:55:37 +04:00
|
|
|
let empty = primary.workspaces.remove(primary.workspaces.len() - 1);
|
2023-08-13 12:46:53 +04:00
|
|
|
primary.workspaces.extend(workspaces);
|
2023-08-13 19:55:37 +04:00
|
|
|
primary.workspaces.push(empty);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { .. } => {
|
2023-08-13 12:46:53 +04:00
|
|
|
panic!("tried to remove output when there were already none")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:41:50 +04:00
|
|
|
pub fn add_window_by_idx(
|
2023-08-13 12:46:53 +04:00
|
|
|
&mut self,
|
|
|
|
|
monitor_idx: usize,
|
|
|
|
|
workspace_idx: usize,
|
2023-08-13 19:55:37 +04:00
|
|
|
window: W,
|
2023-08-13 12:46:53 +04:00
|
|
|
activate: bool,
|
2023-11-01 17:41:02 +04:00
|
|
|
width: ColumnWidth,
|
2023-11-13 15:03:09 +04:00
|
|
|
is_full_width: bool,
|
2023-08-13 12:46:53 +04:00
|
|
|
) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-13 15:03:09 +04:00
|
|
|
monitors[monitor_idx].add_window(workspace_idx, window, activate, width, is_full_width);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
*active_monitor_idx = monitor_idx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
/// Adds a new window to the layout on a specific workspace.
|
|
|
|
|
pub fn add_window_to_named_workspace(
|
|
|
|
|
&mut self,
|
|
|
|
|
workspace_name: &str,
|
|
|
|
|
window: W,
|
|
|
|
|
width: Option<ColumnWidth>,
|
|
|
|
|
is_full_width: bool,
|
|
|
|
|
) -> Option<&Output> {
|
2024-10-10 10:40:59 +03:00
|
|
|
let width = self.resolve_default_width(&window, width);
|
2024-05-11 22:40:30 +02:00
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
let (mon_idx, mon, ws_idx) = monitors
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find_map(|(mon_idx, mon)| {
|
|
|
|
|
mon.find_named_workspace_index(workspace_name)
|
|
|
|
|
.map(move |ws_idx| (mon_idx, mon, ws_idx))
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// Don't steal focus from an active fullscreen window.
|
|
|
|
|
let mut activate = true;
|
|
|
|
|
let ws = &mon.workspaces[ws_idx];
|
|
|
|
|
if mon_idx == *active_monitor_idx
|
|
|
|
|
&& !ws.columns.is_empty()
|
|
|
|
|
&& ws.columns[ws.active_column_idx].is_fullscreen
|
|
|
|
|
{
|
|
|
|
|
activate = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't activate if on a different workspace.
|
|
|
|
|
if mon.active_workspace_idx != ws_idx {
|
|
|
|
|
activate = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mon.add_window(ws_idx, window, activate, width, is_full_width);
|
|
|
|
|
Some(&mon.output)
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
let ws = workspaces
|
|
|
|
|
.iter_mut()
|
2024-05-16 10:54:24 +04:00
|
|
|
.find(|ws| {
|
|
|
|
|
ws.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(workspace_name))
|
|
|
|
|
})
|
2024-05-11 22:40:30 +02:00
|
|
|
.unwrap();
|
2024-10-10 10:17:16 +03:00
|
|
|
ws.add_window(None, window, true, width, is_full_width);
|
2024-05-11 22:40:30 +02:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-15 10:36:59 +04:00
|
|
|
pub fn add_column_by_idx(
|
|
|
|
|
&mut self,
|
|
|
|
|
monitor_idx: usize,
|
|
|
|
|
workspace_idx: usize,
|
|
|
|
|
column: Column<W>,
|
|
|
|
|
activate: bool,
|
|
|
|
|
) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors[monitor_idx].add_column(workspace_idx, column, activate);
|
|
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
*active_monitor_idx = monitor_idx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:41:50 +04:00
|
|
|
/// Adds a new window to the layout.
|
|
|
|
|
///
|
|
|
|
|
/// Returns an output that the window was added to, if there were any outputs.
|
2023-11-02 21:07:29 +04:00
|
|
|
pub fn add_window(
|
|
|
|
|
&mut self,
|
|
|
|
|
window: W,
|
2024-02-23 13:57:56 +04:00
|
|
|
width: Option<ColumnWidth>,
|
2023-11-13 15:03:09 +04:00
|
|
|
is_full_width: bool,
|
2023-11-02 21:07:29 +04:00
|
|
|
) -> Option<&Output> {
|
2024-10-10 10:40:59 +03:00
|
|
|
let width = self.resolve_default_width(&window, width);
|
2023-11-02 21:07:29 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-09-12 19:41:50 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
let mon = &mut monitors[*active_monitor_idx];
|
2023-12-05 15:28:31 +04:00
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 15:03:09 +04:00
|
|
|
mon.add_window(
|
|
|
|
|
mon.active_workspace_idx,
|
|
|
|
|
window,
|
|
|
|
|
activate,
|
|
|
|
|
width,
|
|
|
|
|
is_full_width,
|
|
|
|
|
);
|
2023-09-12 19:41:50 +04:00
|
|
|
Some(&mon.output)
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-09-12 19:41:50 +04:00
|
|
|
let ws = if let Some(ws) = workspaces.get_mut(0) {
|
|
|
|
|
ws
|
|
|
|
|
} else {
|
2023-10-05 09:25:07 +04:00
|
|
|
workspaces.push(Workspace::new_no_outputs(self.options.clone()));
|
2023-09-12 19:41:50 +04:00
|
|
|
&mut workspaces[0]
|
|
|
|
|
};
|
2024-10-10 10:17:16 +03:00
|
|
|
ws.add_window(None, window, true, width, is_full_width);
|
2023-09-12 19:41:50 +04:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-02-07 10:49:01 +04:00
|
|
|
/// Adds a new window to the layout immediately to the right of another window.
|
|
|
|
|
///
|
|
|
|
|
/// If that another window was active, activates the new window.
|
|
|
|
|
///
|
|
|
|
|
/// Returns an output that the window was added to, if there were any outputs.
|
|
|
|
|
pub fn add_window_right_of(
|
|
|
|
|
&mut self,
|
2024-03-19 13:42:04 +04:00
|
|
|
right_of: &W::Id,
|
2024-02-07 10:49:01 +04:00
|
|
|
window: W,
|
2024-02-23 13:57:56 +04:00
|
|
|
width: Option<ColumnWidth>,
|
2024-02-07 10:49:01 +04:00
|
|
|
is_full_width: bool,
|
|
|
|
|
) -> Option<&Output> {
|
2024-10-10 10:40:59 +03:00
|
|
|
let width = self.resolve_default_width(&window, width);
|
2024-02-07 10:49:01 +04:00
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
2024-02-07 13:15:20 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
let mon = monitors
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|mon| mon.workspaces.iter().any(|ws| ws.has_window(right_of)))
|
2024-02-07 10:49:01 +04:00
|
|
|
.unwrap();
|
|
|
|
|
|
2024-02-07 13:15:20 +04:00
|
|
|
mon.add_window_right_of(right_of, window, width, is_full_width);
|
2024-02-07 10:49:01 +04:00
|
|
|
Some(&mon.output)
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
let ws = workspaces
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|ws| ws.has_window(right_of))
|
|
|
|
|
.unwrap();
|
2024-02-07 13:15:20 +04:00
|
|
|
ws.add_window_right_of(right_of, window, width, is_full_width);
|
2024-02-07 10:49:01 +04:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 17:46:37 +04:00
|
|
|
/// Adds a new window to the layout on a specific output.
|
|
|
|
|
pub fn add_window_on_output(
|
|
|
|
|
&mut self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
window: W,
|
2024-02-23 13:57:56 +04:00
|
|
|
width: Option<ColumnWidth>,
|
2024-02-13 17:46:37 +04:00
|
|
|
is_full_width: bool,
|
|
|
|
|
) {
|
2024-10-10 10:40:59 +03:00
|
|
|
let width = self.resolve_default_width(&window, width);
|
2024-02-13 17:46:37 +04:00
|
|
|
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (mon_idx, mon) = monitors
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find(|(_, mon)| mon.output == *output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// Don't steal focus from an active fullscreen window.
|
|
|
|
|
let mut activate = true;
|
|
|
|
|
let ws = &mon.workspaces[mon.active_workspace_idx];
|
|
|
|
|
if mon_idx == *active_monitor_idx
|
|
|
|
|
&& !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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 18:08:44 +03:00
|
|
|
pub fn remove_window(
|
|
|
|
|
&mut self,
|
|
|
|
|
window: &W::Id,
|
|
|
|
|
transaction: Transaction,
|
|
|
|
|
) -> Option<RemovedTile<W>> {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-13 19:55:37 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
|
|
|
|
if ws.has_window(window) {
|
2024-10-14 18:08:44 +03:00
|
|
|
let removed = ws.remove_tile(window, transaction);
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
// Clean up empty workspaces that are not active and not last.
|
|
|
|
|
if !ws.has_windows()
|
2024-10-13 09:30:44 +03:00
|
|
|
&& ws.name.is_none()
|
2023-08-13 19:55:37 +04:00
|
|
|
&& idx != mon.active_workspace_idx
|
|
|
|
|
&& idx != mon.workspaces.len() - 1
|
2024-01-19 20:24:18 +04:00
|
|
|
&& mon.workspace_switch.is_none()
|
2023-08-13 19:55:37 +04:00
|
|
|
{
|
|
|
|
|
mon.workspaces.remove(idx);
|
2023-09-26 20:42:16 +04:00
|
|
|
|
|
|
|
|
if idx < mon.active_workspace_idx {
|
|
|
|
|
mon.active_workspace_idx -= 1;
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2024-10-14 18:08:44 +03:00
|
|
|
return Some(removed);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
for (idx, ws) in workspaces.iter_mut().enumerate() {
|
|
|
|
|
if ws.has_window(window) {
|
2024-10-14 18:08:44 +03:00
|
|
|
let removed = ws.remove_tile(window, transaction);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
// Clean up empty workspaces.
|
2024-10-13 09:30:44 +03:00
|
|
|
if !ws.has_windows() && ws.name.is_none() {
|
2023-08-13 19:55:37 +04:00
|
|
|
workspaces.remove(idx);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-10-14 18:08:44 +03:00
|
|
|
return Some(removed);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-19 13:42:04 +04:00
|
|
|
|
2024-08-23 15:41:06 +03:00
|
|
|
None
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-05-10 16:58:53 +04:00
|
|
|
pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-13 19:55:37 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
2024-05-10 16:58:53 +04:00
|
|
|
ws.update_window(window, serial);
|
2023-08-16 09:08:10 +04:00
|
|
|
return;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
2024-05-10 16:58:53 +04:00
|
|
|
ws.update_window(window, serial);
|
2023-08-16 09:08:10 +04:00
|
|
|
return;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:20:03 +04:00
|
|
|
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, &mon.output));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 09:20:23 +03:00
|
|
|
pub fn find_workspace_by_id(&self, id: WorkspaceId) -> Option<(usize, &Workspace<W>)> {
|
|
|
|
|
match &self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { ref monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if let Some((index, workspace)) = mon
|
|
|
|
|
.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find(|(_, w)| w.id() == id)
|
|
|
|
|
{
|
|
|
|
|
return Some((index, workspace));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
if let Some((index, workspace)) =
|
|
|
|
|
workspaces.iter().enumerate().find(|(_, w)| w.id() == id)
|
|
|
|
|
{
|
|
|
|
|
return Some((index, workspace));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
pub fn find_workspace_by_name(&self, workspace_name: &str) -> Option<(usize, &Workspace<W>)> {
|
|
|
|
|
match &self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { ref monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
2024-05-16 10:54:24 +04:00
|
|
|
if let Some((index, workspace)) =
|
|
|
|
|
mon.workspaces.iter().enumerate().find(|(_, w)| {
|
|
|
|
|
w.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(workspace_name))
|
|
|
|
|
})
|
2024-05-11 22:40:30 +02:00
|
|
|
{
|
|
|
|
|
return Some((index, workspace));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2024-05-16 10:54:24 +04:00
|
|
|
if let Some((index, workspace)) = workspaces.iter().enumerate().find(|(_, w)| {
|
|
|
|
|
w.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(workspace_name))
|
|
|
|
|
}) {
|
2024-05-11 22:40:30 +02:00
|
|
|
return Some((index, workspace));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn unname_workspace(&mut self, workspace_name: &str) {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if mon.unname_workspace(workspace_name) {
|
|
|
|
|
if mon.workspace_switch.is_none() {
|
|
|
|
|
mon.clean_up_workspaces();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
for (idx, ws) in workspaces.iter_mut().enumerate() {
|
2024-05-16 10:54:24 +04:00
|
|
|
if ws
|
|
|
|
|
.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(workspace_name))
|
|
|
|
|
{
|
2024-05-11 22:40:30 +02:00
|
|
|
ws.unname();
|
|
|
|
|
|
|
|
|
|
// Clean up empty workspaces.
|
|
|
|
|
if !ws.has_windows() {
|
|
|
|
|
workspaces.remove(idx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:20:03 +04:00
|
|
|
pub fn find_window_and_output_mut(
|
|
|
|
|
&mut self,
|
|
|
|
|
wl_surface: &WlSurface,
|
|
|
|
|
) -> Option<(&mut W, Option<&Output>)> {
|
2024-03-19 14:41:17 +04:00
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
2024-03-19 15:20:03 +04:00
|
|
|
return Some((window, Some(&mon.output)));
|
2024-03-19 14:41:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if let Some(window) = ws.find_wl_surface_mut(wl_surface) {
|
2024-03-19 15:20:03 +04:00
|
|
|
return Some((window, None));
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
pub fn window_loc(&self, window: &W::Id) -> Option<Point<f64, Logical>> {
|
2023-12-19 20:56:00 +04:00
|
|
|
match &self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mon.workspaces {
|
|
|
|
|
for col in &ws.columns {
|
2023-12-26 10:29:00 +04:00
|
|
|
if let Some(idx) = col.position(window) {
|
2024-05-14 15:00:11 +04:00
|
|
|
return Some(col.window_loc(idx));
|
2023-12-19 20:56:00 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for col in &ws.columns {
|
2023-12-26 10:29:00 +04:00
|
|
|
if let Some(idx) = col.position(window) {
|
2024-05-14 15:00:11 +04:00
|
|
|
return Some(col.window_loc(idx));
|
2023-12-19 20:56:00 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:12:04 +04:00
|
|
|
pub fn update_output_size(&mut self, output: &Output) {
|
2024-01-16 09:45:47 +04:00
|
|
|
let _span = tracy_client::span!("Layout::update_output_size");
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
|
2023-08-13 19:55:37 +04:00
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if &mon.output == output {
|
2024-06-11 09:55:58 +03:00
|
|
|
let scale = output.current_scale();
|
|
|
|
|
let transform = output.current_transform();
|
2023-09-26 20:12:04 +04:00
|
|
|
let view_size = output_size(output);
|
2023-12-21 08:37:30 +04:00
|
|
|
let working_area = compute_working_area(output, self.options.struts);
|
2023-09-26 20:12:04 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
for ws in &mut mon.workspaces {
|
2024-06-11 09:55:58 +03:00
|
|
|
ws.set_view_size(scale, transform, view_size, working_area);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-09-26 20:12:04 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 20:12:56 +04:00
|
|
|
pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return 0.;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
return ws.scroll_amount_to_activate(window);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
0.
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 16:48:29 +03:00
|
|
|
pub fn should_trigger_focus_follows_mouse_on(&self, window: &W::Id) -> bool {
|
|
|
|
|
// During an animation, it's easy to trigger focus-follows-mouse on the previous workspace,
|
|
|
|
|
// especially when clicking to switch workspace on a bar of some kind. This cancels the
|
|
|
|
|
// workspace switch, which is annoying and not intended.
|
|
|
|
|
//
|
|
|
|
|
// This function allows focus-follows-mouse to trigger only on the animation target
|
|
|
|
|
// workspace.
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (mon, ws_idx) = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.find_map(|mon| {
|
|
|
|
|
mon.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|ws| ws.has_window(window))
|
|
|
|
|
.map(|ws_idx| (mon, ws_idx))
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// During a gesture, focus-follows-mouse does not cause any unintended workspace switches.
|
|
|
|
|
if let Some(WorkspaceSwitch::Gesture(_)) = mon.workspace_switch {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws_idx == mon.active_workspace_idx
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
pub fn activate_window(&mut self, window: &W::Id) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
2024-07-05 11:56:45 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
|
2023-11-24 09:49:39 +04:00
|
|
|
for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
2023-08-13 19:55:37 +04:00
|
|
|
if ws.has_window(window) {
|
2023-08-13 12:46:53 +04:00
|
|
|
*active_monitor_idx = monitor_idx;
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.activate_window(window);
|
2023-11-24 09:49:39 +04:00
|
|
|
|
2024-06-17 20:20:26 +03:00
|
|
|
// If currently in the middle of a vertical swipe between the target workspace
|
|
|
|
|
// and some other, don't switch the workspace.
|
|
|
|
|
match &mon.workspace_switch {
|
|
|
|
|
Some(WorkspaceSwitch::Gesture(gesture))
|
|
|
|
|
if gesture.current_idx.floor() == workspace_idx as f64
|
|
|
|
|
|| gesture.current_idx.ceil() == workspace_idx as f64 => {}
|
2024-08-29 15:04:20 +03:00
|
|
|
_ => mon.switch_workspace(workspace_idx),
|
2023-11-24 09:49:39 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn activate_output(&mut self, output: &Output) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
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,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&monitors[*active_monitor_idx].output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
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])
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
pub fn active_workspace_mut(&mut self) -> Option<&mut Workspace<W>> {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = &mut monitors[*active_monitor_idx];
|
|
|
|
|
Some(&mut mon.workspaces[mon.active_workspace_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-24 17:40:30 +04:00
|
|
|
pub fn active_window(&self) -> Option<(&W, &Output)> {
|
2023-10-10 12:42:24 +04:00
|
|
|
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];
|
2024-02-22 14:04:18 +04:00
|
|
|
Some((col.tiles[col.active_tile_idx].window(), &mon.output))
|
2023-10-10 12:42:24 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
2023-08-16 10:59:34 +04:00
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.iter().find(|mon| &mon.output == output).unwrap();
|
|
|
|
|
mon.workspaces.iter().flat_map(|ws| ws.windows())
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-20 12:04:10 +03:00
|
|
|
pub fn with_windows(&self, mut f: impl FnMut(&W, Option<&Output>, WorkspaceId)) {
|
2024-01-29 19:34:12 +04:00
|
|
|
match &self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
2024-06-20 12:04:10 +03:00
|
|
|
f(win, Some(&mon.output), ws.id());
|
2024-01-29 19:34:12 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
2024-06-20 12:04:10 +03:00
|
|
|
f(win, None, ws.id());
|
2024-01-29 19:34:12 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:20:03 +04:00
|
|
|
pub fn with_windows_mut(&mut self, mut f: impl FnMut(&mut W, Option<&Output>)) {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows_mut() {
|
|
|
|
|
f(win, Some(&mon.output));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for win in ws.windows_mut() {
|
|
|
|
|
f(win, None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&mut monitors[*active_monitor_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 13:57:56 +04:00
|
|
|
pub fn active_monitor_ref(&self) -> Option<&Monitor<W>> {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&monitors[*active_monitor_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
2023-08-27 17:44:22 +04:00
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().find(|monitor| &monitor.output == output)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
pub fn monitor_for_workspace(&self, workspace_name: &str) -> Option<&Monitor<W>> {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().find(|monitor| {
|
2024-05-16 10:54:24 +04:00
|
|
|
monitor.workspaces.iter().any(|ws| {
|
|
|
|
|
ws.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(workspace_name))
|
|
|
|
|
})
|
2024-05-11 22:40:30 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ {
|
2023-10-05 09:25:07 +04:00
|
|
|
let monitors = if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
|
2023-08-15 12:49:26 +04:00
|
|
|
&monitors[..]
|
|
|
|
|
} else {
|
|
|
|
|
&[][..]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().map(|mon| &mon.output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn move_left(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_left();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_right(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_right();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 08:01:02 +04:00
|
|
|
pub fn move_column_to_first(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_column_to_first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_column_to_last(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_column_to_last();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-09 21:52:48 -07:00
|
|
|
pub fn move_column_left_or_to_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
let curr_idx = workspace.active_column_idx;
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() && curr_idx != 0 {
|
|
|
|
|
monitor.move_left();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.move_column_to_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_column_right_or_to_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
let curr_idx = workspace.active_column_idx;
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() && curr_idx != workspace.columns.len() - 1 {
|
|
|
|
|
monitor.move_right();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.move_column_to_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn move_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-19 19:25:05 +11:00
|
|
|
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_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_up_or_to_workspace_up();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 19:52:03 +03:00
|
|
|
pub fn consume_or_expel_window_left(&mut self, window: Option<&W::Id>) {
|
|
|
|
|
let workspace = if let Some(window) = window {
|
|
|
|
|
Some(
|
|
|
|
|
self.workspaces_mut()
|
|
|
|
|
.find(|ws| ws.has_window(window))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
self.active_workspace_mut()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(workspace) = workspace else {
|
2024-02-03 20:25:08 +01:00
|
|
|
return;
|
|
|
|
|
};
|
2024-10-11 19:52:03 +03:00
|
|
|
workspace.consume_or_expel_window_left(window);
|
2024-02-03 20:25:08 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-11 19:52:03 +03:00
|
|
|
pub fn consume_or_expel_window_right(&mut self, window: Option<&W::Id>) {
|
|
|
|
|
let workspace = if let Some(window) = window {
|
|
|
|
|
Some(
|
|
|
|
|
self.workspaces_mut()
|
|
|
|
|
.find(|ws| ws.has_window(window))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
self.active_workspace_mut()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(workspace) = workspace else {
|
2024-02-03 20:25:08 +01:00
|
|
|
return;
|
|
|
|
|
};
|
2024-10-11 19:52:03 +03:00
|
|
|
workspace.consume_or_expel_window_right(window);
|
2024-02-03 20:25:08 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn focus_left(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_left();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_right(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_right();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 07:51:14 +04:00
|
|
|
pub fn focus_column_first(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_column_first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_column_last(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_column_last();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-09 21:14:51 +10:00
|
|
|
pub fn focus_column_right_or_first(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_column_right_or_first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_column_left_or_last(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_column_left_or_last();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 06:55:04 +02:00
|
|
|
pub fn focus_window_up_or_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() {
|
|
|
|
|
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
|
|
|
|
let new_idx = curr_idx.saturating_sub(1);
|
|
|
|
|
if curr_idx != new_idx {
|
|
|
|
|
workspace.focus_up();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.focus_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_window_down_or_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() {
|
|
|
|
|
let column = &workspace.columns[workspace.active_column_idx];
|
|
|
|
|
let curr_idx = column.active_tile_idx;
|
|
|
|
|
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
|
|
|
|
if curr_idx != new_idx {
|
|
|
|
|
workspace.focus_down();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.focus_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 11:44:24 -03:00
|
|
|
pub fn focus_column_left_or_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
let curr_idx = workspace.active_column_idx;
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() && curr_idx != 0 {
|
|
|
|
|
monitor.focus_left();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.focus_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_column_right_or_output(&mut self, output: &Output) -> bool {
|
|
|
|
|
if let Some(monitor) = self.active_monitor() {
|
|
|
|
|
let workspace = monitor.active_workspace();
|
|
|
|
|
let curr_idx = workspace.active_column_idx;
|
|
|
|
|
let columns = &workspace.columns;
|
|
|
|
|
|
|
|
|
|
if !workspace.columns.is_empty() && curr_idx != columns.len() - 1 {
|
|
|
|
|
monitor.focus_right();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.focus_output(output);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn focus_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-05-22 13:53:44 -06:00
|
|
|
pub fn focus_down_or_left(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_down_or_left();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_down_or_right(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_down_or_right();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_up_or_left(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_up_or_left();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_up_or_right(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_up_or_right();
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-19 19:25:05 +11:00
|
|
|
pub fn focus_window_or_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_window_or_workspace_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_window_or_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.focus_window_or_workspace_up();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn move_to_workspace_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_to_workspace_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_workspace_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_to_workspace_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
|
2023-09-16 12:14:02 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2024-09-06 15:10:01 +03:00
|
|
|
monitor.move_to_workspace(window, idx);
|
2024-05-11 22:40:30 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-15 10:31:44 +04:00
|
|
|
pub fn move_column_to_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_column_to_workspace_up();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_column_to_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_column_to_workspace_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_column_to_workspace(&mut self, idx: usize) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_column_to_workspace(idx);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
pub fn move_column_to_workspace_on_output(&mut self, output: &Output, idx: usize) {
|
2024-09-06 15:10:01 +03:00
|
|
|
self.move_column_to_output(output);
|
2024-05-11 22:40:30 +02:00
|
|
|
self.focus_output(output);
|
|
|
|
|
self.move_column_to_workspace(idx);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn switch_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.switch_workspace_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.switch_workspace_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 09:48:37 +04:00
|
|
|
pub fn switch_workspace(&mut self, idx: usize) {
|
2023-09-16 12:14:02 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2024-08-29 15:04:20 +03:00
|
|
|
monitor.switch_workspace(idx);
|
2023-09-16 12:14:02 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 14:27:52 +00:00
|
|
|
pub fn switch_workspace_auto_back_and_forth(&mut self, idx: usize) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.switch_workspace_auto_back_and_forth(idx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_previous(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.switch_workspace_previous();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn consume_into_column(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.consume_into_column();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn expel_from_column(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.expel_from_column();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-11-13 19:08:29 +04:00
|
|
|
pub fn center_column(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.center_column();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn focus(&self) -> Option<&W> {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
monitors[*active_monitor_idx].focus()
|
2023-08-14 15:54:11 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-28 08:45:12 +04:00
|
|
|
/// Returns the window under the cursor and the position of its toplevel surface within the
|
|
|
|
|
/// output.
|
|
|
|
|
///
|
|
|
|
|
/// `Some((w, Some(p)))` means that the cursor is within the window's input region and can be
|
|
|
|
|
/// used for delivering events to the window. `Some((w, None))` means that the cursor is within
|
|
|
|
|
/// the window's activation region, but not within the window's input region. For example, the
|
|
|
|
|
/// cursor may be on the window's server-side border.
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn window_under(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
2024-06-17 09:16:28 +03:00
|
|
|
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
2023-10-21 20:49:29 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.iter().find(|mon| &mon.output == output)?;
|
|
|
|
|
mon.window_under(pos_within_output)
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-05-10 16:58:53 +04:00
|
|
|
pub fn resize_edges_under(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> Option<ResizeEdge> {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.iter().find(|mon| &mon.output == output)?;
|
|
|
|
|
mon.resize_edges_under(pos_within_output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
2023-08-13 12:46:53 +04:00
|
|
|
fn verify_invariants(&self) {
|
2024-03-19 14:27:52 +00:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
2024-02-29 09:14:11 +04:00
|
|
|
use crate::layout::monitor::WorkspaceSwitch;
|
|
|
|
|
|
2024-03-19 14:27:52 +00:00
|
|
|
let mut seen_workspace_id = HashSet::new();
|
2024-05-16 10:54:24 +04:00
|
|
|
let mut seen_workspace_name = Vec::<String>::new();
|
2024-03-19 14:27:52 +00:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let (monitors, &primary_idx, &active_monitor_idx) = match &self.monitor_set {
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => (monitors, primary_idx, active_monitor_idx),
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-08-13 12:46:53 +04:00
|
|
|
for workspace in workspaces {
|
|
|
|
|
assert!(
|
2024-05-11 22:40:30 +02:00
|
|
|
workspace.has_windows() || workspace.name.is_some(),
|
|
|
|
|
"with no outputs there cannot be empty unnamed workspaces"
|
2023-08-13 12:46:53 +04:00
|
|
|
);
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
2024-06-17 09:16:28 +03:00
|
|
|
workspace.base_options, self.options,
|
|
|
|
|
"workspace base options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let options = Options::clone(&workspace.base_options)
|
|
|
|
|
.adjusted_for_scale(workspace.scale().fractional_scale());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
&*workspace.options, &options,
|
|
|
|
|
"workspace options must be base options adjusted for workspace scale"
|
2023-10-05 09:25:07 +04:00
|
|
|
);
|
|
|
|
|
|
2024-03-19 14:27:52 +00:00
|
|
|
assert!(
|
|
|
|
|
seen_workspace_id.insert(workspace.id()),
|
|
|
|
|
"workspace id must be unique"
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
if let Some(name) = &workspace.name {
|
|
|
|
|
assert!(
|
2024-05-16 10:54:24 +04:00
|
|
|
!seen_workspace_name
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|n| n.eq_ignore_ascii_case(name)),
|
2024-05-11 22:40:30 +02:00
|
|
|
"workspace name must be unique"
|
|
|
|
|
);
|
2024-05-16 10:54:24 +04:00
|
|
|
seen_workspace_name.push(name.clone());
|
2024-05-11 22:40:30 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-26 20:35:08 +04:00
|
|
|
assert!(primary_idx < monitors.len());
|
|
|
|
|
assert!(active_monitor_idx < monitors.len());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for (idx, monitor) in monitors.iter().enumerate() {
|
|
|
|
|
assert!(
|
|
|
|
|
!monitor.workspaces.is_empty(),
|
2023-10-14 19:24:12 +04:00
|
|
|
"monitor must have at least one workspace"
|
2023-08-13 12:46:53 +04:00
|
|
|
);
|
2023-09-26 20:35:08 +04:00
|
|
|
assert!(monitor.active_workspace_idx < monitor.workspaces.len());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
monitor.options, self.options,
|
|
|
|
|
"monitor options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
2024-01-19 20:24:59 +04:00
|
|
|
if let Some(WorkspaceSwitch::Animation(anim)) = &monitor.workspace_switch {
|
|
|
|
|
let before_idx = anim.from() as usize;
|
|
|
|
|
let after_idx = anim.to() as usize;
|
|
|
|
|
|
|
|
|
|
assert!(before_idx < monitor.workspaces.len());
|
|
|
|
|
assert!(after_idx < monitor.workspaces.len());
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
if idx == primary_idx {
|
2023-11-02 09:39:37 +04:00
|
|
|
for ws in &monitor.workspaces {
|
2024-09-03 12:13:04 +03:00
|
|
|
if ws.original_output.matches(&monitor.output) {
|
2023-11-02 09:39:37 +04:00
|
|
|
// This is the primary monitor's own workspace.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let own_monitor_exists = monitors
|
|
|
|
|
.iter()
|
2024-09-03 12:13:04 +03:00
|
|
|
.any(|m| ws.original_output.matches(&m.output));
|
2023-11-02 09:39:37 +04:00
|
|
|
assert!(
|
|
|
|
|
!own_monitor_exists,
|
|
|
|
|
"primary monitor cannot have workspaces for which their own monitor exists"
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
} else {
|
|
|
|
|
assert!(
|
|
|
|
|
monitor
|
|
|
|
|
.workspaces
|
|
|
|
|
.iter()
|
2024-09-03 12:13:04 +03:00
|
|
|
.any(|workspace| workspace.original_output.matches(&monitor.output)),
|
2023-11-02 09:39:37 +04:00
|
|
|
"secondary monitor must not have any non-own workspaces"
|
2023-08-13 12:46:53 +04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:32:53 +04:00
|
|
|
assert!(
|
|
|
|
|
monitor.workspaces.last().unwrap().columns.is_empty(),
|
|
|
|
|
"monitor must have an empty workspace in the end"
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
assert!(
|
|
|
|
|
monitor.workspaces.last().unwrap().name.is_none(),
|
|
|
|
|
"monitor must have an unnamed workspace in the end"
|
|
|
|
|
);
|
|
|
|
|
|
2023-10-14 20:39:44 +04:00
|
|
|
// If there's no workspace switch in progress, there can't be any non-last non-active
|
|
|
|
|
// empty workspaces.
|
|
|
|
|
if monitor.workspace_switch.is_none() {
|
|
|
|
|
for (idx, ws) in monitor.workspaces.iter().enumerate().rev().skip(1) {
|
|
|
|
|
if idx != monitor.active_workspace_idx {
|
|
|
|
|
assert!(
|
2024-05-11 22:40:30 +02:00
|
|
|
!ws.columns.is_empty() || ws.name.is_some(),
|
|
|
|
|
"non-active workspace can't be empty and unnamed except the last one"
|
2023-10-14 20:39:44 +04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// FIXME: verify that primary doesn't have any workspaces for which their own monitor
|
|
|
|
|
// exists.
|
|
|
|
|
|
|
|
|
|
for workspace in &monitor.workspaces {
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
2024-06-17 09:16:28 +03:00
|
|
|
workspace.base_options, self.options,
|
2023-10-05 09:25:07 +04:00
|
|
|
"workspace options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
let options = Options::clone(&workspace.base_options)
|
|
|
|
|
.adjusted_for_scale(workspace.scale().fractional_scale());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
&*workspace.options, &options,
|
|
|
|
|
"workspace options must be base options adjusted for workspace scale"
|
|
|
|
|
);
|
|
|
|
|
|
2024-03-19 14:27:52 +00:00
|
|
|
assert!(
|
|
|
|
|
seen_workspace_id.insert(workspace.id()),
|
|
|
|
|
"workspace id must be unique"
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
if let Some(name) = &workspace.name {
|
|
|
|
|
assert!(
|
2024-05-16 10:54:24 +04:00
|
|
|
!seen_workspace_name
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|n| n.eq_ignore_ascii_case(name)),
|
2024-05-11 22:40:30 +02:00
|
|
|
"workspace name must be unique"
|
|
|
|
|
);
|
2024-05-16 10:54:24 +04:00
|
|
|
seen_workspace_name.push(name.clone());
|
2024-05-11 22:40:30 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 17:40:15 +04:00
|
|
|
|
|
|
|
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
2023-10-11 14:53:53 +04:00
|
|
|
let _span = tracy_client::span!("Layout::advance_animations");
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2024-05-04 11:10:02 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
mon.advance_animations(current_time);
|
2023-08-14 17:40:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-14 17:40:15 +04:00
|
|
|
for ws in workspaces {
|
2024-05-04 11:10:02 +04:00
|
|
|
ws.advance_animations(current_time);
|
2023-08-14 17:40:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 18:29:50 +04:00
|
|
|
|
2024-08-23 12:48:05 +03:00
|
|
|
pub fn update_render_elements(&mut self, output: Option<&Output>) {
|
2024-05-04 11:10:02 +04:00
|
|
|
let _span = tracy_client::span!("Layout::update_render_elements");
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
error!("update_render_elements called with no monitors");
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (idx, mon) in monitors.iter_mut().enumerate() {
|
2024-08-23 12:48:05 +03:00
|
|
|
if output.map_or(true, |output| mon.output == *output) {
|
2024-05-04 11:10:02 +04:00
|
|
|
mon.update_render_elements(idx == *active_monitor_idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 21:21:58 +04:00
|
|
|
pub fn update_shaders(&mut self) {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
ws.update_shaders();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
ws.update_shaders();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 22:40:30 +02:00
|
|
|
pub fn ensure_named_workspace(&mut self, ws_config: &WorkspaceConfig) {
|
|
|
|
|
if self.find_workspace_by_name(&ws_config.name.0).is_some() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let options = self.options.clone();
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => {
|
|
|
|
|
let mon_idx = ws_config
|
|
|
|
|
.open_on_output
|
|
|
|
|
.as_deref()
|
|
|
|
|
.map(|name| {
|
|
|
|
|
monitors
|
|
|
|
|
.iter_mut()
|
2024-09-03 12:13:04 +03:00
|
|
|
.position(|monitor| output_matches_name(&monitor.output, name))
|
2024-05-11 22:40:30 +02:00
|
|
|
.unwrap_or(*primary_idx)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(*active_monitor_idx);
|
|
|
|
|
let mon = &mut monitors[mon_idx];
|
|
|
|
|
|
|
|
|
|
let ws = Workspace::new_with_config(
|
|
|
|
|
mon.output.clone(),
|
|
|
|
|
Some(ws_config.clone()),
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
mon.workspaces.insert(0, ws);
|
|
|
|
|
mon.active_workspace_idx += 1;
|
|
|
|
|
mon.workspace_switch = None;
|
|
|
|
|
mon.clean_up_workspaces();
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
let ws = Workspace::new_with_config_no_outputs(Some(ws_config.clone()), options);
|
|
|
|
|
workspaces.insert(0, ws);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
pub fn update_config(&mut self, config: &Config) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let options = Rc::new(Options::from_config(config));
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
2023-09-26 13:09:33 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
2023-10-05 09:25:07 +04:00
|
|
|
mon.update_config(options.clone());
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-09-26 13:09:33 +04:00
|
|
|
for ws in workspaces {
|
2023-10-05 09:25:07 +04:00
|
|
|
ws.update_config(options.clone());
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
self.options = options;
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 18:29:50 +04:00
|
|
|
pub fn toggle_width(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.toggle_width();
|
|
|
|
|
}
|
2023-08-14 18:34:39 +04:00
|
|
|
|
2024-09-12 11:53:10 +03:00
|
|
|
pub fn toggle_window_height(&mut self, window: Option<&W::Id>) {
|
|
|
|
|
let workspace = if let Some(window) = window {
|
|
|
|
|
Some(
|
|
|
|
|
self.workspaces_mut()
|
|
|
|
|
.find(|ws| ws.has_window(window))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
self.active_workspace_mut()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(workspace) = workspace else {
|
2024-09-05 23:37:10 +02:00
|
|
|
return;
|
|
|
|
|
};
|
2024-09-12 11:53:10 +03:00
|
|
|
workspace.toggle_window_height(window);
|
2024-09-05 23:37:10 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 18:34:39 +04:00
|
|
|
pub fn toggle_full_width(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.toggle_full_width();
|
|
|
|
|
}
|
2023-08-16 08:03:20 +04:00
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
pub fn set_column_width(&mut self, change: SizeChange) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.set_column_width(change);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
pub fn set_window_height(&mut self, window: Option<&W::Id>, change: SizeChange) {
|
|
|
|
|
let workspace = if let Some(window) = window {
|
|
|
|
|
Some(
|
|
|
|
|
self.workspaces_mut()
|
|
|
|
|
.find(|ws| ws.has_window(window))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
self.active_workspace_mut()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(workspace) = workspace else {
|
2023-11-08 11:17:06 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2024-09-06 15:10:01 +03:00
|
|
|
workspace.set_window_height(window, change);
|
2023-11-08 11:17:06 +04:00
|
|
|
}
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
pub fn reset_window_height(&mut self, window: Option<&W::Id>) {
|
|
|
|
|
let workspace = if let Some(window) = window {
|
|
|
|
|
Some(
|
|
|
|
|
self.workspaces_mut()
|
|
|
|
|
.find(|ws| ws.has_window(window))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
self.active_workspace_mut()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(workspace) = workspace else {
|
2024-05-11 09:33:23 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2024-09-06 15:10:01 +03:00
|
|
|
workspace.reset_window_height(window);
|
2024-05-11 09:33:23 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-16 08:03:20 +04:00
|
|
|
pub fn focus_output(&mut self, output: &Output) {
|
|
|
|
|
if let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-16 08:03:20 +04:00
|
|
|
{
|
|
|
|
|
for (idx, mon) in monitors.iter().enumerate() {
|
|
|
|
|
if &mon.output == output {
|
|
|
|
|
*active_monitor_idx = idx;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
pub fn move_to_output(
|
|
|
|
|
&mut self,
|
|
|
|
|
window: Option<&W::Id>,
|
|
|
|
|
output: &Output,
|
|
|
|
|
target_ws_idx: Option<usize>,
|
|
|
|
|
) {
|
2023-08-16 08:03:20 +04:00
|
|
|
if let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-16 08:03:20 +04:00
|
|
|
{
|
|
|
|
|
let new_idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
let (mon_idx, ws_idx, col_idx, tile_idx) = if let Some(window) = window {
|
|
|
|
|
monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find_map(|(mon_idx, mon)| {
|
|
|
|
|
mon.workspaces.iter().enumerate().find_map(|(ws_idx, ws)| {
|
|
|
|
|
ws.columns.iter().enumerate().find_map(|(col_idx, col)| {
|
|
|
|
|
col.tiles
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|tile| tile.window().id() == window)
|
|
|
|
|
.map(|tile_idx| (mon_idx, ws_idx, col_idx, tile_idx))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
let mon_idx = *active_monitor_idx;
|
|
|
|
|
let mon = &monitors[mon_idx];
|
|
|
|
|
let ws_idx = mon.active_workspace_idx;
|
|
|
|
|
let ws = &mon.workspaces[ws_idx];
|
|
|
|
|
|
|
|
|
|
if ws.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let col_idx = ws.active_column_idx;
|
|
|
|
|
let tile_idx = ws.columns[col_idx].active_tile_idx;
|
|
|
|
|
(mon_idx, ws_idx, col_idx, tile_idx)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let workspace_idx = target_ws_idx.unwrap_or(monitors[new_idx].active_workspace_idx);
|
|
|
|
|
if mon_idx == new_idx && ws_idx == workspace_idx {
|
2023-08-16 08:03:20 +04:00
|
|
|
return;
|
|
|
|
|
}
|
2024-09-06 15:10:01 +03:00
|
|
|
|
|
|
|
|
let mon = &mut monitors[mon_idx];
|
|
|
|
|
let ws = &mut mon.workspaces[ws_idx];
|
|
|
|
|
let column = &ws.columns[col_idx];
|
|
|
|
|
let activate = mon_idx == *active_monitor_idx
|
|
|
|
|
&& ws_idx == mon.active_workspace_idx
|
|
|
|
|
&& col_idx == ws.active_column_idx
|
|
|
|
|
&& tile_idx == column.active_tile_idx;
|
|
|
|
|
|
2024-10-14 18:08:44 +03:00
|
|
|
let removed = ws.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None);
|
2023-08-16 08:03:20 +04:00
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
self.add_window_by_idx(
|
|
|
|
|
new_idx,
|
|
|
|
|
workspace_idx,
|
2024-10-14 18:08:44 +03:00
|
|
|
removed.tile.into_window(),
|
2024-09-06 15:10:01 +03:00
|
|
|
activate,
|
2024-10-14 18:08:44 +03:00
|
|
|
removed.width,
|
|
|
|
|
removed.is_full_width,
|
2024-09-06 15:10:01 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
let mon = &mut monitors[mon_idx];
|
|
|
|
|
if mon.workspace_switch.is_none() {
|
|
|
|
|
monitors[mon_idx].clean_up_workspaces();
|
|
|
|
|
}
|
2023-08-16 08:03:20 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-16 09:08:10 +04:00
|
|
|
|
2024-01-15 10:36:59 +04:00
|
|
|
pub fn move_column_to_output(&mut self, output: &Output) {
|
|
|
|
|
if let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
{
|
|
|
|
|
let new_idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let current = &mut monitors[*active_monitor_idx];
|
|
|
|
|
let ws = current.active_workspace();
|
|
|
|
|
if !ws.has_windows() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let column = ws.remove_column_by_idx(ws.active_column_idx);
|
|
|
|
|
|
|
|
|
|
let workspace_idx = monitors[new_idx].active_workspace_idx;
|
|
|
|
|
self.add_column_by_idx(new_idx, workspace_idx, column, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 23:57:01 +01:00
|
|
|
pub fn move_workspace_to_output(&mut self, output: &Output) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &mut self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let current = &mut monitors[*active_monitor_idx];
|
|
|
|
|
if current.active_workspace_idx == current.workspaces.len() - 1 {
|
|
|
|
|
// Insert a new empty workspace.
|
|
|
|
|
let ws = Workspace::new(current.output.clone(), current.options.clone());
|
|
|
|
|
current.workspaces.push(ws);
|
|
|
|
|
}
|
|
|
|
|
let mut ws = current.workspaces.remove(current.active_workspace_idx);
|
|
|
|
|
current.active_workspace_idx = current.active_workspace_idx.saturating_sub(1);
|
|
|
|
|
current.workspace_switch = None;
|
|
|
|
|
current.clean_up_workspaces();
|
|
|
|
|
|
|
|
|
|
ws.set_output(Some(output.clone()));
|
|
|
|
|
ws.original_output = OutputId::new(output);
|
|
|
|
|
|
|
|
|
|
let target_idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let target = &mut monitors[target_idx];
|
2024-02-03 08:42:56 +04:00
|
|
|
|
2024-03-19 14:27:52 +00:00
|
|
|
target.previous_workspace_id = Some(target.workspaces[target.active_workspace_idx].id());
|
|
|
|
|
|
2024-02-03 08:42:56 +04:00
|
|
|
// Insert the workspace after the currently active one. Unless the currently active one is
|
|
|
|
|
// the last empty workspace, then insert before.
|
|
|
|
|
let target_ws_idx = min(target.active_workspace_idx + 1, target.workspaces.len() - 1);
|
|
|
|
|
target.workspaces.insert(target_ws_idx, ws);
|
|
|
|
|
target.active_workspace_idx = target_ws_idx;
|
2023-12-02 23:57:01 +01:00
|
|
|
target.workspace_switch = None;
|
|
|
|
|
target.clean_up_workspaces();
|
|
|
|
|
|
|
|
|
|
*active_monitor_idx = target_idx;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-16 09:08:10 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.set_fullscreen(window, is_fullscreen);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-16 09:08:10 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.set_fullscreen(window, is_fullscreen);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
pub fn toggle_fullscreen(&mut self, window: &W::Id) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-16 09:08:10 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.toggle_fullscreen(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-16 09:08:10 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.toggle_fullscreen(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-08 09:57:59 +04:00
|
|
|
|
2024-06-19 21:54:46 +03:00
|
|
|
pub fn workspace_switch_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
|
2023-10-08 09:57:59 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
// Cancel the gesture on other outputs.
|
|
|
|
|
if &monitor.output != output {
|
2024-06-19 21:54:46 +03:00
|
|
|
monitor.workspace_switch_gesture_end(true, None);
|
2023-10-08 09:57:59 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 21:54:46 +03:00
|
|
|
monitor.workspace_switch_gesture_begin(is_touchpad);
|
2023-10-08 09:57:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-02 14:33:22 +04:00
|
|
|
pub fn workspace_switch_gesture_update(
|
|
|
|
|
&mut self,
|
|
|
|
|
delta_y: f64,
|
|
|
|
|
timestamp: Duration,
|
2024-06-19 21:54:46 +03:00
|
|
|
is_touchpad: bool,
|
2024-03-02 14:33:22 +04:00
|
|
|
) -> Option<Option<Output>> {
|
2023-10-08 09:57:59 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
2024-06-19 21:54:46 +03:00
|
|
|
if let Some(refresh) =
|
|
|
|
|
monitor.workspace_switch_gesture_update(delta_y, timestamp, is_touchpad)
|
|
|
|
|
{
|
2024-02-29 09:14:11 +04:00
|
|
|
if refresh {
|
|
|
|
|
return Some(Some(monitor.output.clone()));
|
|
|
|
|
} else {
|
2023-10-08 09:57:59 +04:00
|
|
|
return Some(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 21:54:46 +03:00
|
|
|
pub fn workspace_switch_gesture_end(
|
|
|
|
|
&mut self,
|
|
|
|
|
cancelled: bool,
|
|
|
|
|
is_touchpad: Option<bool>,
|
|
|
|
|
) -> Option<Output> {
|
2023-10-08 09:57:59 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
2024-06-19 21:54:46 +03:00
|
|
|
if monitor.workspace_switch_gesture_end(cancelled, is_touchpad) {
|
2023-10-08 09:57:59 +04:00
|
|
|
return Some(monitor.output.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-10-14 20:42:10 +04:00
|
|
|
|
2024-05-11 14:01:48 +04:00
|
|
|
pub fn view_offset_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
|
2024-02-29 08:56:20 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
for (idx, ws) in monitor.workspaces.iter_mut().enumerate() {
|
|
|
|
|
// Cancel the gesture on other workspaces.
|
|
|
|
|
if &monitor.output != output || idx != monitor.active_workspace_idx {
|
2024-05-11 14:01:48 +04:00
|
|
|
ws.view_offset_gesture_end(true, None);
|
2024-02-29 08:56:20 +04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 14:01:48 +04:00
|
|
|
ws.view_offset_gesture_begin(is_touchpad);
|
2024-02-29 08:56:20 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-02 15:23:40 +04:00
|
|
|
pub fn view_offset_gesture_update(
|
|
|
|
|
&mut self,
|
|
|
|
|
delta_x: f64,
|
|
|
|
|
timestamp: Duration,
|
2024-05-11 14:01:48 +04:00
|
|
|
is_touchpad: bool,
|
2024-03-02 15:23:40 +04:00
|
|
|
) -> Option<Option<Output>> {
|
2024-02-29 08:56:20 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
for ws in &mut monitor.workspaces {
|
2024-05-11 14:01:48 +04:00
|
|
|
if let Some(refresh) =
|
|
|
|
|
ws.view_offset_gesture_update(delta_x, timestamp, is_touchpad)
|
|
|
|
|
{
|
2024-02-29 08:56:20 +04:00
|
|
|
if refresh {
|
|
|
|
|
return Some(Some(monitor.output.clone()));
|
|
|
|
|
} else {
|
|
|
|
|
return Some(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 14:01:48 +04:00
|
|
|
pub fn view_offset_gesture_end(
|
|
|
|
|
&mut self,
|
|
|
|
|
cancelled: bool,
|
|
|
|
|
is_touchpad: Option<bool>,
|
|
|
|
|
) -> Option<Output> {
|
2024-02-29 08:56:20 +04:00
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
for ws in &mut monitor.workspaces {
|
2024-05-11 14:01:48 +04:00
|
|
|
if ws.view_offset_gesture_end(cancelled, is_touchpad) {
|
2024-02-29 08:56:20 +04:00
|
|
|
return Some(monitor.output.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-10 16:58:53 +04:00
|
|
|
pub fn interactive_resize_begin(&mut self, window: W::Id, edges: ResizeEdge) -> bool {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(&window) {
|
|
|
|
|
return ws.interactive_resize_begin(window, edges);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(&window) {
|
|
|
|
|
return ws.interactive_resize_begin(window, edges);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn interactive_resize_update(
|
|
|
|
|
&mut self,
|
|
|
|
|
window: &W::Id,
|
|
|
|
|
delta: Point<f64, Logical>,
|
|
|
|
|
) -> bool {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
return ws.interactive_resize_update(window, delta);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
return ws.interactive_resize_update(window, delta);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn interactive_resize_end(&mut self, window: &W::Id) {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.interactive_resize_end(Some(window));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.interactive_resize_end(Some(window));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:42:10 +04:00
|
|
|
pub fn move_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_workspace_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_workspace_up();
|
|
|
|
|
}
|
2024-02-07 11:32:02 +04:00
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
pub fn start_open_animation_for_window(&mut self, window: &W::Id) {
|
2024-02-07 11:32:02 +04:00
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
2024-04-16 08:55:56 +04:00
|
|
|
for col in &mut ws.columns {
|
2024-02-07 11:32:02 +04:00
|
|
|
for tile in &mut col.tiles {
|
2024-03-19 13:42:04 +04:00
|
|
|
if tile.window().id() == window {
|
2024-02-07 11:32:02 +04:00
|
|
|
tile.start_open_animation();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
2024-04-16 08:55:56 +04:00
|
|
|
for col in &mut ws.columns {
|
2024-03-19 13:42:04 +04:00
|
|
|
for tile in &mut col.tiles {
|
|
|
|
|
if tile.window().id() == window {
|
|
|
|
|
tile.start_open_animation();
|
|
|
|
|
return;
|
2024-02-07 11:32:02 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2024-05-01 19:00:11 +04:00
|
|
|
pub fn store_unmap_snapshot(&mut self, renderer: &mut GlesRenderer, window: &W::Id) {
|
|
|
|
|
let _span = tracy_client::span!("Layout::store_unmap_snapshot");
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.store_unmap_snapshot_if_empty(renderer, window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.store_unmap_snapshot_if_empty(renderer, window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn clear_unmap_snapshot(&mut self, window: &W::Id) {
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.clear_unmap_snapshot(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.clear_unmap_snapshot(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-09 22:37:10 +04:00
|
|
|
pub fn start_close_animation_for_window(
|
|
|
|
|
&mut self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
window: &W::Id,
|
2024-08-23 15:41:06 +03:00
|
|
|
blocker: TransactionBlocker,
|
2024-04-09 22:37:10 +04:00
|
|
|
) {
|
|
|
|
|
let _span = tracy_client::span!("Layout::start_close_animation_for_window");
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
2024-08-23 15:41:06 +03:00
|
|
|
ws.start_close_animation_for_window(renderer, window, blocker);
|
2024-04-09 22:37:10 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
2024-08-23 15:41:06 +03:00
|
|
|
ws.start_close_animation_for_window(renderer, window, blocker);
|
2024-04-09 22:37:10 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 08:56:20 +04:00
|
|
|
pub fn refresh(&mut self) {
|
2024-03-19 13:52:08 +04:00
|
|
|
let _span = tracy_client::span!("Layout::refresh");
|
2023-08-14 14:48:10 +04:00
|
|
|
|
2024-02-29 08:56:20 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-12-28 09:09:28 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-02-29 08:56:20 +04:00
|
|
|
for (idx, mon) in monitors.iter_mut().enumerate() {
|
2023-12-28 09:09:28 +04:00
|
|
|
let is_active = idx == *active_monitor_idx;
|
2024-02-29 08:56:20 +04:00
|
|
|
for (ws_idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
2023-12-28 09:09:28 +04:00
|
|
|
ws.refresh(is_active);
|
2024-02-29 08:56:20 +04:00
|
|
|
|
|
|
|
|
// Cancel the view offset gesture after workspace switches, moves, etc.
|
|
|
|
|
if ws_idx != mon.active_workspace_idx {
|
2024-05-11 14:01:48 +04:00
|
|
|
ws.view_offset_gesture_end(false, None);
|
2024-02-29 08:56:20 +04:00
|
|
|
}
|
2023-08-14 14:48:10 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-14 14:48:10 +04:00
|
|
|
for ws in workspaces {
|
2023-12-28 09:09:28 +04:00
|
|
|
ws.refresh(false);
|
2024-05-11 14:01:48 +04:00
|
|
|
ws.view_offset_gesture_end(false, None);
|
2023-08-14 14:48:10 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2024-05-16 14:30:52 -07:00
|
|
|
|
2024-06-20 12:04:10 +03:00
|
|
|
pub fn workspaces(
|
|
|
|
|
&self,
|
|
|
|
|
) -> impl Iterator<Item = (Option<&Monitor<W>>, usize, &Workspace<W>)> + '_ {
|
|
|
|
|
let iter_normal;
|
|
|
|
|
let iter_no_outputs;
|
|
|
|
|
|
2024-05-16 14:30:52 -07:00
|
|
|
match &self.monitor_set {
|
2024-06-20 12:04:10 +03:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
let it = monitors.iter().flat_map(|mon| {
|
|
|
|
|
mon.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(move |(idx, ws)| (Some(mon), idx, ws))
|
|
|
|
|
});
|
2024-05-16 14:30:52 -07:00
|
|
|
|
2024-06-20 12:04:10 +03:00
|
|
|
iter_normal = Some(it);
|
|
|
|
|
iter_no_outputs = None;
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
let it = workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(idx, ws)| (None, idx, ws));
|
|
|
|
|
|
|
|
|
|
iter_normal = None;
|
|
|
|
|
iter_no_outputs = Some(it);
|
2024-05-16 14:30:52 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-20 12:04:10 +03:00
|
|
|
|
|
|
|
|
let iter_normal = iter_normal.into_iter().flatten();
|
|
|
|
|
let iter_no_outputs = iter_no_outputs.into_iter().flatten();
|
|
|
|
|
iter_normal.chain(iter_no_outputs)
|
2024-05-16 14:30:52 -07:00
|
|
|
}
|
2024-09-06 15:10:01 +03:00
|
|
|
|
|
|
|
|
pub fn workspaces_mut(&mut self) -> impl Iterator<Item = &mut Workspace<W>> + '_ {
|
|
|
|
|
let iter_normal;
|
|
|
|
|
let iter_no_outputs;
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
let it = monitors
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.flat_map(|mon| mon.workspaces.iter_mut());
|
|
|
|
|
|
|
|
|
|
iter_normal = Some(it);
|
|
|
|
|
iter_no_outputs = None;
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
|
|
|
|
let it = workspaces.iter_mut();
|
|
|
|
|
|
|
|
|
|
iter_normal = None;
|
|
|
|
|
iter_no_outputs = Some(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let iter_normal = iter_normal.into_iter().flatten();
|
|
|
|
|
let iter_no_outputs = iter_no_outputs.into_iter().flatten();
|
|
|
|
|
iter_normal.chain(iter_no_outputs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn windows(&self) -> impl Iterator<Item = (Option<&Monitor<W>>, &W)> {
|
|
|
|
|
self.workspaces()
|
|
|
|
|
.flat_map(|(mon, _, ws)| ws.windows().map(move |win| (mon, win)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn has_window(&self, window: &W::Id) -> bool {
|
|
|
|
|
self.windows().any(|(_, win)| win.id() == window)
|
|
|
|
|
}
|
2024-10-10 10:40:59 +03:00
|
|
|
|
|
|
|
|
fn resolve_default_width(&self, window: &W, width: Option<ColumnWidth>) -> ColumnWidth {
|
|
|
|
|
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w)));
|
|
|
|
|
if let ColumnWidth::Fixed(w) = &mut width {
|
|
|
|
|
let rules = window.rules();
|
|
|
|
|
let border_config = rules.border.resolve_against(self.options.border);
|
|
|
|
|
if !border_config.off {
|
|
|
|
|
*w += border_config.width.0 * 2.;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
width
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Default for MonitorSet<W> {
|
2023-08-13 12:46:53 +04:00
|
|
|
fn default() -> Self {
|
2023-10-05 09:25:07 +04:00
|
|
|
Self::NoOutputs { workspaces: vec![] }
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use std::cell::Cell;
|
|
|
|
|
|
2024-09-03 12:13:04 +03:00
|
|
|
use niri_config::{FloatOrInt, OutputName, WorkspaceName};
|
2023-09-27 13:35:02 +04:00
|
|
|
use proptest::prelude::*;
|
|
|
|
|
use proptest_derive::Arbitrary;
|
2023-09-12 19:44:17 +04:00
|
|
|
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
2024-03-19 14:41:17 +04:00
|
|
|
use smithay::utils::Rectangle;
|
2023-09-12 19:44:17 +04:00
|
|
|
|
|
|
|
|
use super::*;
|
2024-06-17 09:16:28 +03:00
|
|
|
use crate::utils::round_logical_in_physical;
|
2023-09-12 19:44:17 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl<W: LayoutElement> Default for Layout<W> {
|
|
|
|
|
fn default() -> Self {
|
2024-02-06 19:09:15 +04:00
|
|
|
Self::with_options(Default::default())
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct TestWindowInner {
|
|
|
|
|
id: usize,
|
|
|
|
|
bbox: Cell<Rectangle<i32, Logical>>,
|
2023-09-28 08:59:45 +04:00
|
|
|
initial_bbox: Rectangle<i32, Logical>,
|
|
|
|
|
requested_size: Cell<Option<Size<i32, Logical>>>,
|
2023-12-29 11:42:54 +04:00
|
|
|
min_size: Size<i32, Logical>,
|
|
|
|
|
max_size: Size<i32, Logical>,
|
2024-02-03 09:44:34 +04:00
|
|
|
pending_fullscreen: Cell<bool>,
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct TestWindow(Rc<TestWindowInner>);
|
|
|
|
|
|
|
|
|
|
impl TestWindow {
|
2023-12-29 11:42:54 +04:00
|
|
|
fn new(
|
|
|
|
|
id: usize,
|
|
|
|
|
bbox: Rectangle<i32, Logical>,
|
|
|
|
|
min_size: Size<i32, Logical>,
|
|
|
|
|
max_size: Size<i32, Logical>,
|
|
|
|
|
) -> Self {
|
2023-09-12 19:44:17 +04:00
|
|
|
Self(Rc::new(TestWindowInner {
|
|
|
|
|
id,
|
|
|
|
|
bbox: Cell::new(bbox),
|
2023-09-28 08:59:45 +04:00
|
|
|
initial_bbox: bbox,
|
|
|
|
|
requested_size: Cell::new(None),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_size,
|
|
|
|
|
max_size,
|
2024-02-03 09:44:34 +04:00
|
|
|
pending_fullscreen: Cell::new(false),
|
2023-09-12 19:44:17 +04:00
|
|
|
}))
|
|
|
|
|
}
|
2023-09-28 08:59:45 +04:00
|
|
|
|
|
|
|
|
fn communicate(&self) -> bool {
|
2024-09-12 19:31:47 +03:00
|
|
|
if let Some(size) = self.0.requested_size.get() {
|
2023-09-28 08:59:45 +04:00
|
|
|
assert!(size.w >= 0);
|
|
|
|
|
assert!(size.h >= 0);
|
|
|
|
|
|
|
|
|
|
let mut new_bbox = self.0.initial_bbox;
|
|
|
|
|
if size.w != 0 {
|
|
|
|
|
new_bbox.size.w = size.w;
|
|
|
|
|
}
|
|
|
|
|
if size.h != 0 {
|
|
|
|
|
new_bbox.size.h = size.h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.0.bbox.get() != new_bbox {
|
|
|
|
|
self.0.bbox.set(new_bbox);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
impl LayoutElement for TestWindow {
|
|
|
|
|
type Id = usize;
|
|
|
|
|
|
|
|
|
|
fn id(&self) -> &Self::Id {
|
|
|
|
|
&self.0.id
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-26 17:38:40 +04:00
|
|
|
fn size(&self) -> Size<i32, Logical> {
|
|
|
|
|
self.0.bbox.get().size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn buf_loc(&self) -> Point<i32, Logical> {
|
|
|
|
|
(0, 0).into()
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-24 18:14:42 +04:00
|
|
|
fn is_in_input_region(&self, _point: Point<f64, Logical>) -> bool {
|
2023-09-12 19:44:17 +04:00
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-05 14:05:08 +04:00
|
|
|
fn render<R: NiriRenderer>(
|
2023-12-26 17:38:40 +04:00
|
|
|
&self,
|
|
|
|
|
_renderer: &mut R,
|
2024-06-17 09:16:28 +03:00
|
|
|
_location: Point<f64, Logical>,
|
2023-12-26 17:38:40 +04:00
|
|
|
_scale: Scale<f64>,
|
2024-03-24 08:30:26 +04:00
|
|
|
_alpha: f32,
|
2024-03-24 09:03:59 +04:00
|
|
|
_target: RenderTarget,
|
2024-05-01 19:02:22 +04:00
|
|
|
) -> SplitElements<LayoutElementRenderElement<R>> {
|
|
|
|
|
SplitElements::default()
|
2023-12-26 17:38:40 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-22 14:44:11 +03:00
|
|
|
fn request_size(
|
|
|
|
|
&mut self,
|
|
|
|
|
size: Size<i32, Logical>,
|
|
|
|
|
_animate: bool,
|
|
|
|
|
_transaction: Option<Transaction>,
|
|
|
|
|
) {
|
2023-09-28 08:59:45 +04:00
|
|
|
self.0.requested_size.set(Some(size));
|
2024-02-03 09:44:34 +04:00
|
|
|
self.0.pending_fullscreen.set(false);
|
2023-09-28 08:59:45 +04:00
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
|
2024-02-03 09:44:34 +04:00
|
|
|
fn request_fullscreen(&self, _size: Size<i32, Logical>) {
|
|
|
|
|
self.0.pending_fullscreen.set(true);
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
|
|
|
|
|
fn min_size(&self) -> Size<i32, Logical> {
|
2023-12-29 11:42:54 +04:00
|
|
|
self.0.min_size
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn max_size(&self) -> Size<i32, Logical> {
|
2023-12-29 11:42:54 +04:00
|
|
|
self.0.max_size
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-10-11 14:32:29 +04:00
|
|
|
|
2024-05-29 13:32:11 +03:00
|
|
|
fn set_preferred_scale_transform(&self, _scale: output::Scale, _transform: Transform) {}
|
2023-10-26 00:15:46 +04:00
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
fn has_ssd(&self) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-12-24 18:14:42 +04:00
|
|
|
|
|
|
|
|
fn output_enter(&self, _output: &Output) {}
|
|
|
|
|
|
|
|
|
|
fn output_leave(&self, _output: &Output) {}
|
2023-12-27 21:51:42 +04:00
|
|
|
|
2024-02-07 11:32:02 +04:00
|
|
|
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
|
|
|
|
|
2024-03-23 14:38:07 +04:00
|
|
|
fn set_activated(&mut self, _active: bool) {}
|
2024-03-19 13:52:08 +04:00
|
|
|
|
|
|
|
|
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
|
|
|
|
|
2024-08-22 14:36:47 +03:00
|
|
|
fn configure_intent(&self) -> ConfigureIntent {
|
|
|
|
|
ConfigureIntent::CanSend
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 11:07:23 +04:00
|
|
|
fn send_pending_configure(&mut self) {}
|
2024-03-19 13:52:08 +04:00
|
|
|
|
2024-04-22 22:51:52 +02:00
|
|
|
fn set_active_in_column(&mut self, _active: bool) {}
|
|
|
|
|
|
2023-12-27 21:51:42 +04:00
|
|
|
fn is_fullscreen(&self) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
2024-02-03 09:44:34 +04:00
|
|
|
|
|
|
|
|
fn is_pending_fullscreen(&self) -> bool {
|
|
|
|
|
self.0.pending_fullscreen.get()
|
|
|
|
|
}
|
2024-03-19 13:52:08 +04:00
|
|
|
|
2024-09-12 19:31:47 +03:00
|
|
|
fn requested_size(&self) -> Option<Size<i32, Logical>> {
|
|
|
|
|
self.0.requested_size.get()
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:52:08 +04:00
|
|
|
fn refresh(&self) {}
|
2024-03-19 18:22:25 +04:00
|
|
|
|
|
|
|
|
fn rules(&self) -> &ResolvedWindowRules {
|
|
|
|
|
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
|
|
|
|
&EMPTY
|
|
|
|
|
}
|
2024-04-13 11:07:23 +04:00
|
|
|
|
2024-04-13 14:16:07 +04:00
|
|
|
fn animation_snapshot(&self) -> Option<&LayoutElementRenderSnapshot> {
|
2024-04-13 11:07:23 +04:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 14:16:07 +04:00
|
|
|
fn take_animation_snapshot(&mut self) -> Option<LayoutElementRenderSnapshot> {
|
2024-04-13 11:07:23 +04:00
|
|
|
None
|
|
|
|
|
}
|
2024-05-10 16:58:53 +04:00
|
|
|
|
|
|
|
|
fn set_interactive_resize(&mut self, _data: Option<InteractiveResizeData>) {}
|
|
|
|
|
|
|
|
|
|
fn cancel_interactive_resize(&mut self) {}
|
|
|
|
|
|
2024-05-11 08:26:49 +04:00
|
|
|
fn update_interactive_resize(&mut self, _serial: Serial) {}
|
|
|
|
|
|
|
|
|
|
fn interactive_resize_data(&self) -> Option<InteractiveResizeData> {
|
2024-05-10 16:58:53 +04:00
|
|
|
None
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
|
|
|
|
any::<(i16, i16, u16, u16)>().prop_map(|(x, y, w, h)| {
|
|
|
|
|
let loc: Point<i32, _> = Point::from((x.into(), y.into()));
|
|
|
|
|
let size: Size<i32, _> = Size::from((w.into(), h.into()));
|
|
|
|
|
Rectangle::from_loc_and_size(loc, size)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
fn arbitrary_size_change() -> impl Strategy<Value = SizeChange> {
|
|
|
|
|
prop_oneof![
|
|
|
|
|
(0..).prop_map(SizeChange::SetFixed),
|
|
|
|
|
(0f64..).prop_map(SizeChange::SetProportion),
|
|
|
|
|
any::<i32>().prop_map(SizeChange::AdjustFixed),
|
|
|
|
|
any::<f64>().prop_map(SizeChange::AdjustProportion),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:42:54 +04:00
|
|
|
fn arbitrary_min_max() -> impl Strategy<Value = (i32, i32)> {
|
|
|
|
|
prop_oneof![
|
|
|
|
|
Just((0, 0)),
|
|
|
|
|
(1..65536).prop_map(|n| (n, n)),
|
|
|
|
|
(1..65536).prop_map(|min| (min, 0)),
|
|
|
|
|
(1..).prop_map(|max| (0, max)),
|
|
|
|
|
(1..65536, 1..).prop_map(|(min, max): (i32, i32)| (min, max.max(min))),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn arbitrary_min_max_size() -> impl Strategy<Value = (Size<i32, Logical>, Size<i32, Logical>)> {
|
|
|
|
|
(arbitrary_min_max(), arbitrary_min_max()).prop_map(|((min_w, max_w), (min_h, max_h))| {
|
|
|
|
|
let min_size = Size::from((min_w, min_h));
|
|
|
|
|
let max_size = Size::from((max_w, max_h));
|
|
|
|
|
(min_size, max_size)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 08:56:20 +04:00
|
|
|
fn arbitrary_view_offset_gesture_delta() -> impl Strategy<Value = f64> {
|
|
|
|
|
prop_oneof![(-10f64..10f64), (-50000f64..50000f64),]
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-10 16:58:53 +04:00
|
|
|
fn arbitrary_resize_edge() -> impl Strategy<Value = ResizeEdge> {
|
|
|
|
|
prop_oneof![
|
|
|
|
|
Just(ResizeEdge::RIGHT),
|
|
|
|
|
Just(ResizeEdge::BOTTOM),
|
|
|
|
|
Just(ResizeEdge::LEFT),
|
|
|
|
|
Just(ResizeEdge::TOP),
|
|
|
|
|
Just(ResizeEdge::BOTTOM_RIGHT),
|
|
|
|
|
Just(ResizeEdge::BOTTOM_LEFT),
|
|
|
|
|
Just(ResizeEdge::TOP_RIGHT),
|
|
|
|
|
Just(ResizeEdge::TOP_LEFT),
|
|
|
|
|
Just(ResizeEdge::empty()),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
fn arbitrary_scale() -> impl Strategy<Value = f64> {
|
|
|
|
|
prop_oneof![Just(1.), Just(1.5), Just(2.),]
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
#[derive(Debug, Clone, Copy, Arbitrary)]
|
2023-09-12 19:44:17 +04:00
|
|
|
enum Op {
|
2023-09-27 13:35:02 +04:00
|
|
|
AddOutput(#[proptest(strategy = "1..=5usize")] usize),
|
2024-06-17 09:16:28 +03:00
|
|
|
AddScaledOutput {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
id: usize,
|
|
|
|
|
#[proptest(strategy = "arbitrary_scale()")]
|
|
|
|
|
scale: f64,
|
|
|
|
|
},
|
2023-09-27 13:35:02 +04:00
|
|
|
RemoveOutput(#[proptest(strategy = "1..=5usize")] usize),
|
|
|
|
|
FocusOutput(#[proptest(strategy = "1..=5usize")] usize),
|
2024-05-11 22:40:30 +02:00
|
|
|
AddNamedWorkspace {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
ws_name: usize,
|
|
|
|
|
#[proptest(strategy = "prop::option::of(1..=5usize)")]
|
|
|
|
|
output_name: Option<usize>,
|
|
|
|
|
},
|
|
|
|
|
UnnameWorkspace {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
ws_name: usize,
|
|
|
|
|
},
|
2023-09-12 19:44:17 +04:00
|
|
|
AddWindow {
|
2023-09-27 13:35:02 +04:00
|
|
|
#[proptest(strategy = "1..=5usize")]
|
2023-09-12 19:44:17 +04:00
|
|
|
id: usize,
|
2023-09-27 13:35:02 +04:00
|
|
|
#[proptest(strategy = "arbitrary_bbox()")]
|
2023-09-12 19:44:17 +04:00
|
|
|
bbox: Rectangle<i32, Logical>,
|
2023-12-29 11:42:54 +04:00
|
|
|
#[proptest(strategy = "arbitrary_min_max_size()")]
|
|
|
|
|
min_max_size: (Size<i32, Logical>, Size<i32, Logical>),
|
2023-09-12 19:44:17 +04:00
|
|
|
},
|
2024-02-07 10:49:01 +04:00
|
|
|
AddWindowRightOf {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
id: usize,
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
right_of_id: usize,
|
|
|
|
|
#[proptest(strategy = "arbitrary_bbox()")]
|
|
|
|
|
bbox: Rectangle<i32, Logical>,
|
|
|
|
|
#[proptest(strategy = "arbitrary_min_max_size()")]
|
|
|
|
|
min_max_size: (Size<i32, Logical>, Size<i32, Logical>),
|
|
|
|
|
},
|
2024-05-11 22:40:30 +02:00
|
|
|
AddWindowToNamedWorkspace {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
id: usize,
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
ws_name: usize,
|
|
|
|
|
#[proptest(strategy = "arbitrary_bbox()")]
|
|
|
|
|
bbox: Rectangle<i32, Logical>,
|
|
|
|
|
#[proptest(strategy = "arbitrary_min_max_size()")]
|
|
|
|
|
min_max_size: (Size<i32, Logical>, Size<i32, Logical>),
|
|
|
|
|
},
|
2023-09-27 13:35:02 +04:00
|
|
|
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
|
2023-09-27 13:45:49 +04:00
|
|
|
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
|
2024-06-04 19:44:33 +03:00
|
|
|
SetFullscreenWindow {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
window: usize,
|
|
|
|
|
is_fullscreen: bool,
|
|
|
|
|
},
|
2023-09-12 19:44:17 +04:00
|
|
|
FocusColumnLeft,
|
|
|
|
|
FocusColumnRight,
|
2023-12-29 07:51:14 +04:00
|
|
|
FocusColumnFirst,
|
|
|
|
|
FocusColumnLast,
|
2024-06-09 21:14:51 +10:00
|
|
|
FocusColumnRightOrFirst,
|
|
|
|
|
FocusColumnLeftOrLast,
|
2024-07-05 06:55:04 +02:00
|
|
|
FocusWindowOrMonitorUp(#[proptest(strategy = "1..=2u8")] u8),
|
|
|
|
|
FocusWindowOrMonitorDown(#[proptest(strategy = "1..=2u8")] u8),
|
2024-06-28 11:44:24 -03:00
|
|
|
FocusColumnOrMonitorLeft(#[proptest(strategy = "1..=2u8")] u8),
|
|
|
|
|
FocusColumnOrMonitorRight(#[proptest(strategy = "1..=2u8")] u8),
|
2023-09-27 13:45:49 +04:00
|
|
|
FocusWindowDown,
|
|
|
|
|
FocusWindowUp,
|
2024-05-22 13:53:44 -06:00
|
|
|
FocusWindowDownOrColumnLeft,
|
|
|
|
|
FocusWindowDownOrColumnRight,
|
|
|
|
|
FocusWindowUpOrColumnLeft,
|
|
|
|
|
FocusWindowUpOrColumnRight,
|
2023-12-19 19:25:05 +11:00
|
|
|
FocusWindowOrWorkspaceDown,
|
|
|
|
|
FocusWindowOrWorkspaceUp,
|
2023-09-12 19:44:17 +04:00
|
|
|
MoveColumnLeft,
|
|
|
|
|
MoveColumnRight,
|
2023-12-29 08:01:02 +04:00
|
|
|
MoveColumnToFirst,
|
|
|
|
|
MoveColumnToLast,
|
2024-07-09 21:52:48 -07:00
|
|
|
MoveColumnLeftOrToMonitorLeft(#[proptest(strategy = "1..=2u8")] u8),
|
|
|
|
|
MoveColumnRightOrToMonitorRight(#[proptest(strategy = "1..=2u8")] u8),
|
2023-09-27 13:45:49 +04:00
|
|
|
MoveWindowDown,
|
|
|
|
|
MoveWindowUp,
|
2023-12-19 19:25:05 +11:00
|
|
|
MoveWindowDownOrToWorkspaceDown,
|
|
|
|
|
MoveWindowUpOrToWorkspaceUp,
|
2024-10-11 19:52:03 +03:00
|
|
|
ConsumeOrExpelWindowLeft {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
id: Option<usize>,
|
|
|
|
|
},
|
|
|
|
|
ConsumeOrExpelWindowRight {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
id: Option<usize>,
|
|
|
|
|
},
|
2023-09-12 19:44:17 +04:00
|
|
|
ConsumeWindowIntoColumn,
|
|
|
|
|
ExpelWindowFromColumn,
|
2023-11-13 19:08:29 +04:00
|
|
|
CenterColumn,
|
2023-09-12 19:44:17 +04:00
|
|
|
FocusWorkspaceDown,
|
|
|
|
|
FocusWorkspaceUp,
|
2023-11-24 09:48:37 +04:00
|
|
|
FocusWorkspace(#[proptest(strategy = "0..=4usize")] usize),
|
2024-03-19 14:27:52 +00:00
|
|
|
FocusWorkspaceAutoBackAndForth(#[proptest(strategy = "0..=4usize")] usize),
|
|
|
|
|
FocusWorkspacePrevious,
|
2023-09-12 19:44:17 +04:00
|
|
|
MoveWindowToWorkspaceDown,
|
|
|
|
|
MoveWindowToWorkspaceUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
MoveWindowToWorkspace {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
window_id: Option<usize>,
|
|
|
|
|
#[proptest(strategy = "0..=4usize")]
|
|
|
|
|
workspace_idx: usize,
|
|
|
|
|
},
|
2024-01-15 10:31:44 +04:00
|
|
|
MoveColumnToWorkspaceDown,
|
|
|
|
|
MoveColumnToWorkspaceUp,
|
|
|
|
|
MoveColumnToWorkspace(#[proptest(strategy = "0..=4usize")] usize),
|
2023-10-14 20:42:10 +04:00
|
|
|
MoveWorkspaceDown,
|
|
|
|
|
MoveWorkspaceUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
MoveWindowToOutput {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
window_id: Option<usize>,
|
|
|
|
|
#[proptest(strategy = "1..=5u8")]
|
|
|
|
|
output_id: u8,
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(0..=4usize)")]
|
|
|
|
|
target_ws_idx: Option<usize>,
|
|
|
|
|
},
|
2024-01-15 10:36:59 +04:00
|
|
|
MoveColumnToOutput(#[proptest(strategy = "1..=5u8")] u8),
|
2023-09-27 13:45:49 +04:00
|
|
|
SwitchPresetColumnWidth,
|
2024-09-12 11:53:10 +03:00
|
|
|
SwitchPresetWindowHeight {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
id: Option<usize>,
|
|
|
|
|
},
|
2023-09-27 13:45:49 +04:00
|
|
|
MaximizeColumn,
|
2023-10-03 11:38:42 +04:00
|
|
|
SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange),
|
2024-09-06 15:10:01 +03:00
|
|
|
SetWindowHeight {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
id: Option<usize>,
|
|
|
|
|
#[proptest(strategy = "arbitrary_size_change()")]
|
|
|
|
|
change: SizeChange,
|
|
|
|
|
},
|
|
|
|
|
ResetWindowHeight {
|
|
|
|
|
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
|
|
|
|
id: Option<usize>,
|
|
|
|
|
},
|
2023-09-28 08:59:45 +04:00
|
|
|
Communicate(#[proptest(strategy = "1..=5usize")] usize),
|
2023-12-02 23:57:01 +01:00
|
|
|
MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8),
|
2024-02-29 08:56:20 +04:00
|
|
|
ViewOffsetGestureBegin {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
output_idx: usize,
|
2024-05-11 14:01:48 +04:00
|
|
|
is_touchpad: bool,
|
2024-02-29 08:56:20 +04:00
|
|
|
},
|
|
|
|
|
ViewOffsetGestureUpdate {
|
|
|
|
|
#[proptest(strategy = "arbitrary_view_offset_gesture_delta()")]
|
|
|
|
|
delta: f64,
|
2024-03-02 15:23:40 +04:00
|
|
|
timestamp: Duration,
|
2024-05-11 14:01:48 +04:00
|
|
|
is_touchpad: bool,
|
|
|
|
|
},
|
|
|
|
|
ViewOffsetGestureEnd {
|
|
|
|
|
is_touchpad: Option<bool>,
|
2024-02-29 08:56:20 +04:00
|
|
|
},
|
2024-02-29 09:14:11 +04:00
|
|
|
WorkspaceSwitchGestureBegin {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
output_idx: usize,
|
2024-06-19 21:54:46 +03:00
|
|
|
is_touchpad: bool,
|
2024-02-29 09:14:11 +04:00
|
|
|
},
|
|
|
|
|
WorkspaceSwitchGestureUpdate {
|
|
|
|
|
#[proptest(strategy = "-400f64..400f64")]
|
|
|
|
|
delta: f64,
|
2024-03-02 14:33:22 +04:00
|
|
|
timestamp: Duration,
|
2024-06-19 21:54:46 +03:00
|
|
|
is_touchpad: bool,
|
2024-02-29 09:14:11 +04:00
|
|
|
},
|
|
|
|
|
WorkspaceSwitchGestureEnd {
|
|
|
|
|
cancelled: bool,
|
2024-06-19 21:54:46 +03:00
|
|
|
is_touchpad: Option<bool>,
|
2024-02-29 09:14:11 +04:00
|
|
|
},
|
2024-05-10 16:58:53 +04:00
|
|
|
InteractiveResizeBegin {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
window: usize,
|
|
|
|
|
#[proptest(strategy = "arbitrary_resize_edge()")]
|
|
|
|
|
edges: ResizeEdge,
|
|
|
|
|
},
|
|
|
|
|
InteractiveResizeUpdate {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
window: usize,
|
|
|
|
|
#[proptest(strategy = "-20000f64..20000f64")]
|
|
|
|
|
dx: f64,
|
|
|
|
|
#[proptest(strategy = "-20000f64..20000f64")]
|
|
|
|
|
dy: f64,
|
|
|
|
|
},
|
|
|
|
|
InteractiveResizeEnd {
|
|
|
|
|
#[proptest(strategy = "1..=5usize")]
|
|
|
|
|
window: usize,
|
|
|
|
|
},
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Op {
|
2023-10-05 09:25:07 +04:00
|
|
|
fn apply(self, layout: &mut Layout<TestWindow>) {
|
2023-09-12 19:44:17 +04:00
|
|
|
match self {
|
|
|
|
|
Op::AddOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
if layout.outputs().any(|o| o.name() == name) {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
2024-09-03 12:13:04 +03:00
|
|
|
name.clone(),
|
2023-09-12 19:44:17 +04:00
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
Some(Mode {
|
|
|
|
|
size: Size::from((1280, 720)),
|
2024-02-06 19:09:27 +04:00
|
|
|
refresh: 60000,
|
2023-09-12 19:44:17 +04:00
|
|
|
}),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2024-09-03 12:13:04 +03:00
|
|
|
output.user_data().insert_if_missing(|| OutputName {
|
|
|
|
|
connector: name,
|
|
|
|
|
make: None,
|
|
|
|
|
model: None,
|
|
|
|
|
serial: None,
|
|
|
|
|
});
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.add_output(output.clone());
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
2024-06-17 09:16:28 +03:00
|
|
|
Op::AddScaledOutput { id, scale } => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
if layout.outputs().any(|o| o.name() == name) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
2024-09-03 12:13:04 +03:00
|
|
|
name.clone(),
|
2024-06-17 09:16:28 +03:00
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
Some(Mode {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
refresh: 60000,
|
|
|
|
|
}),
|
|
|
|
|
None,
|
|
|
|
|
Some(smithay::output::Scale::Fractional(scale)),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2024-09-03 12:13:04 +03:00
|
|
|
output.user_data().insert_if_missing(|| OutputName {
|
|
|
|
|
connector: name,
|
|
|
|
|
make: None,
|
|
|
|
|
model: None,
|
|
|
|
|
serial: None,
|
|
|
|
|
});
|
2024-06-17 09:16:28 +03:00
|
|
|
layout.add_output(output.clone());
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::RemoveOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.remove_output(&output);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
Op::FocusOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.focus_output(&output);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddNamedWorkspace {
|
|
|
|
|
ws_name,
|
|
|
|
|
output_name,
|
|
|
|
|
} => {
|
|
|
|
|
layout.ensure_named_workspace(&WorkspaceConfig {
|
|
|
|
|
name: WorkspaceName(format!("ws{ws_name}")),
|
|
|
|
|
open_on_output: output_name.map(|name| format!("output{name}")),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Op::UnnameWorkspace { ws_name } => {
|
|
|
|
|
layout.unname_workspace(&format!("ws{ws_name}"));
|
|
|
|
|
}
|
2023-12-29 11:42:54 +04:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id,
|
|
|
|
|
bbox,
|
|
|
|
|
min_max_size,
|
|
|
|
|
} => {
|
2024-10-14 17:36:56 +03:00
|
|
|
if layout.has_window(&id) {
|
|
|
|
|
return;
|
2023-10-14 20:42:10 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:42:54 +04:00
|
|
|
let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
|
2023-12-05 15:28:31 +04:00
|
|
|
layout.add_window(win, None, false);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
2024-02-07 10:49:01 +04:00
|
|
|
Op::AddWindowRightOf {
|
|
|
|
|
id,
|
|
|
|
|
right_of_id,
|
|
|
|
|
bbox,
|
|
|
|
|
min_max_size,
|
|
|
|
|
} => {
|
|
|
|
|
let mut found_right_of = false;
|
|
|
|
|
|
|
|
|
|
match &mut layout.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if win.0.id == right_of_id {
|
|
|
|
|
found_right_of = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if win.0.id == right_of_id {
|
|
|
|
|
found_right_of = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !found_right_of {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
|
2024-03-19 13:42:04 +04:00
|
|
|
layout.add_window_right_of(&right_of_id, win, None, false);
|
2024-02-07 10:49:01 +04:00
|
|
|
}
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddWindowToNamedWorkspace {
|
|
|
|
|
id,
|
|
|
|
|
ws_name,
|
|
|
|
|
bbox,
|
|
|
|
|
min_max_size,
|
|
|
|
|
} => {
|
|
|
|
|
let ws_name = format!("ws{ws_name}");
|
|
|
|
|
let mut found_workspace = false;
|
|
|
|
|
|
|
|
|
|
match &mut layout.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 10:54:24 +04:00
|
|
|
if ws
|
|
|
|
|
.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(&ws_name))
|
|
|
|
|
{
|
2024-05-11 22:40:30 +02:00
|
|
|
found_workspace = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 10:54:24 +04:00
|
|
|
if ws
|
|
|
|
|
.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |name| name.eq_ignore_ascii_case(&ws_name))
|
|
|
|
|
{
|
2024-05-11 22:40:30 +02:00
|
|
|
found_workspace = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !found_workspace {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1);
|
|
|
|
|
layout.add_window_to_named_workspace(&ws_name, win, None, false);
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::CloseWindow(id) => {
|
2024-08-23 15:41:06 +03:00
|
|
|
layout.remove_window(&id, Transaction::new());
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
2023-09-27 13:45:49 +04:00
|
|
|
Op::FullscreenWindow(id) => {
|
2024-03-19 13:42:04 +04:00
|
|
|
layout.toggle_fullscreen(&id);
|
2023-09-27 13:45:49 +04:00
|
|
|
}
|
2024-06-04 19:44:33 +03:00
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window,
|
|
|
|
|
is_fullscreen,
|
|
|
|
|
} => {
|
|
|
|
|
layout.set_fullscreen(&window, is_fullscreen);
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::FocusColumnLeft => layout.focus_left(),
|
|
|
|
|
Op::FocusColumnRight => layout.focus_right(),
|
2023-12-29 07:51:14 +04:00
|
|
|
Op::FocusColumnFirst => layout.focus_column_first(),
|
|
|
|
|
Op::FocusColumnLast => layout.focus_column_last(),
|
2024-06-09 21:14:51 +10:00
|
|
|
Op::FocusColumnRightOrFirst => layout.focus_column_right_or_first(),
|
|
|
|
|
Op::FocusColumnLeftOrLast => layout.focus_column_left_or_last(),
|
2024-07-05 06:55:04 +02:00
|
|
|
Op::FocusWindowOrMonitorUp(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.focus_window_up_or_output(&output);
|
|
|
|
|
}
|
|
|
|
|
Op::FocusWindowOrMonitorDown(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.focus_window_down_or_output(&output);
|
|
|
|
|
}
|
2024-06-28 11:44:24 -03:00
|
|
|
Op::FocusColumnOrMonitorLeft(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.focus_column_left_or_output(&output);
|
|
|
|
|
}
|
|
|
|
|
Op::FocusColumnOrMonitorRight(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.focus_column_right_or_output(&output);
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::FocusWindowDown => layout.focus_down(),
|
|
|
|
|
Op::FocusWindowUp => layout.focus_up(),
|
2024-05-22 13:53:44 -06:00
|
|
|
Op::FocusWindowDownOrColumnLeft => layout.focus_down_or_left(),
|
|
|
|
|
Op::FocusWindowDownOrColumnRight => layout.focus_down_or_right(),
|
|
|
|
|
Op::FocusWindowUpOrColumnLeft => layout.focus_up_or_left(),
|
|
|
|
|
Op::FocusWindowUpOrColumnRight => layout.focus_up_or_right(),
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowOrWorkspaceDown => layout.focus_window_or_workspace_down(),
|
|
|
|
|
Op::FocusWindowOrWorkspaceUp => layout.focus_window_or_workspace_up(),
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::MoveColumnLeft => layout.move_left(),
|
|
|
|
|
Op::MoveColumnRight => layout.move_right(),
|
2023-12-29 08:01:02 +04:00
|
|
|
Op::MoveColumnToFirst => layout.move_column_to_first(),
|
|
|
|
|
Op::MoveColumnToLast => layout.move_column_to_last(),
|
2024-07-09 21:52:48 -07:00
|
|
|
Op::MoveColumnLeftOrToMonitorLeft(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.move_column_left_or_to_output(&output);
|
|
|
|
|
}
|
|
|
|
|
Op::MoveColumnRightOrToMonitorRight(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.move_column_right_or_to_output(&output);
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::MoveWindowDown => layout.move_down(),
|
|
|
|
|
Op::MoveWindowUp => layout.move_up(),
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::MoveWindowDownOrToWorkspaceDown => layout.move_down_or_to_workspace_down(),
|
|
|
|
|
Op::MoveWindowUpOrToWorkspaceUp => layout.move_up_or_to_workspace_up(),
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id } => {
|
|
|
|
|
let id = id.filter(|id| layout.has_window(id));
|
|
|
|
|
layout.consume_or_expel_window_left(id.as_ref());
|
|
|
|
|
}
|
|
|
|
|
Op::ConsumeOrExpelWindowRight { id } => {
|
|
|
|
|
let id = id.filter(|id| layout.has_window(id));
|
|
|
|
|
layout.consume_or_expel_window_right(id.as_ref());
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::ConsumeWindowIntoColumn => layout.consume_into_column(),
|
|
|
|
|
Op::ExpelWindowFromColumn => layout.expel_from_column(),
|
2023-11-13 19:08:29 +04:00
|
|
|
Op::CenterColumn => layout.center_column(),
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::FocusWorkspaceDown => layout.switch_workspace_down(),
|
|
|
|
|
Op::FocusWorkspaceUp => layout.switch_workspace_up(),
|
|
|
|
|
Op::FocusWorkspace(idx) => layout.switch_workspace(idx),
|
2024-03-19 14:27:52 +00:00
|
|
|
Op::FocusWorkspaceAutoBackAndForth(idx) => {
|
|
|
|
|
layout.switch_workspace_auto_back_and_forth(idx)
|
|
|
|
|
}
|
|
|
|
|
Op::FocusWorkspacePrevious => layout.switch_workspace_previous(),
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::MoveWindowToWorkspaceDown => layout.move_to_workspace_down(),
|
|
|
|
|
Op::MoveWindowToWorkspaceUp => layout.move_to_workspace_up(),
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id,
|
|
|
|
|
workspace_idx,
|
|
|
|
|
} => {
|
|
|
|
|
let window_id = window_id.filter(|id| {
|
|
|
|
|
layout
|
|
|
|
|
.active_monitor()
|
|
|
|
|
.map_or(false, |mon| mon.has_window(id))
|
|
|
|
|
});
|
|
|
|
|
layout.move_to_workspace(window_id.as_ref(), workspace_idx);
|
|
|
|
|
}
|
2024-01-15 10:31:44 +04:00
|
|
|
Op::MoveColumnToWorkspaceDown => layout.move_column_to_workspace_down(),
|
|
|
|
|
Op::MoveColumnToWorkspaceUp => layout.move_column_to_workspace_up(),
|
|
|
|
|
Op::MoveColumnToWorkspace(idx) => layout.move_column_to_workspace(idx),
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::MoveWindowToOutput {
|
|
|
|
|
window_id,
|
|
|
|
|
output_id: id,
|
|
|
|
|
target_ws_idx,
|
|
|
|
|
} => {
|
2023-09-27 13:45:49 +04:00
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-27 13:45:49 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2024-09-06 15:10:01 +03:00
|
|
|
let mon = layout.monitor_for_output(&output).unwrap();
|
2023-09-27 13:45:49 +04:00
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
let window_id = window_id.filter(|id| layout.has_window(id));
|
|
|
|
|
let target_ws_idx = target_ws_idx.filter(|idx| mon.workspaces.len() > *idx);
|
|
|
|
|
layout.move_to_output(window_id.as_ref(), &output, target_ws_idx);
|
2023-09-27 13:45:49 +04:00
|
|
|
}
|
2024-01-15 10:36:59 +04:00
|
|
|
Op::MoveColumnToOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.move_column_to_output(&output);
|
|
|
|
|
}
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWorkspaceDown => layout.move_workspace_down(),
|
|
|
|
|
Op::MoveWorkspaceUp => layout.move_workspace_up(),
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::SwitchPresetColumnWidth => layout.toggle_width(),
|
2024-09-12 11:53:10 +03:00
|
|
|
Op::SwitchPresetWindowHeight { id } => {
|
|
|
|
|
let id = id.filter(|id| layout.has_window(id));
|
|
|
|
|
layout.toggle_window_height(id.as_ref());
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::MaximizeColumn => layout.toggle_full_width(),
|
|
|
|
|
Op::SetColumnWidth(change) => layout.set_column_width(change),
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight { id, change } => {
|
|
|
|
|
let id = id.filter(|id| layout.has_window(id));
|
|
|
|
|
layout.set_window_height(id.as_ref(), change);
|
|
|
|
|
}
|
|
|
|
|
Op::ResetWindowHeight { id } => {
|
|
|
|
|
let id = id.filter(|id| layout.has_window(id));
|
|
|
|
|
layout.reset_window_height(id.as_ref());
|
|
|
|
|
}
|
2023-09-28 08:59:45 +04:00
|
|
|
Op::Communicate(id) => {
|
2024-03-19 13:42:04 +04:00
|
|
|
let mut update = false;
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut layout.monitor_set {
|
2023-09-28 08:59:45 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
'outer: for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
if win.communicate() {
|
2024-03-19 13:42:04 +04:00
|
|
|
update = true;
|
2023-09-28 08:59:45 +04:00
|
|
|
}
|
|
|
|
|
break 'outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-09-28 08:59:45 +04:00
|
|
|
'outer: for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
if win.communicate() {
|
2024-03-19 13:42:04 +04:00
|
|
|
update = true;
|
2023-09-28 08:59:45 +04:00
|
|
|
}
|
|
|
|
|
break 'outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 13:42:04 +04:00
|
|
|
if update {
|
2024-05-10 16:58:53 +04:00
|
|
|
// FIXME: serial.
|
|
|
|
|
layout.update_window(&id, None);
|
2023-09-28 08:59:45 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-12-02 23:57:01 +01:00
|
|
|
Op::MoveWorkspaceToOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout.move_workspace_to_output(&output);
|
|
|
|
|
}
|
2024-05-11 14:01:48 +04:00
|
|
|
Op::ViewOffsetGestureBegin {
|
|
|
|
|
output_idx: id,
|
|
|
|
|
is_touchpad: normalize,
|
|
|
|
|
} => {
|
2024-02-29 08:56:20 +04:00
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-11 14:01:48 +04:00
|
|
|
layout.view_offset_gesture_begin(&output, normalize);
|
2024-02-29 08:56:20 +04:00
|
|
|
}
|
2024-05-11 14:01:48 +04:00
|
|
|
Op::ViewOffsetGestureUpdate {
|
|
|
|
|
delta,
|
|
|
|
|
timestamp,
|
|
|
|
|
is_touchpad,
|
|
|
|
|
} => {
|
|
|
|
|
layout.view_offset_gesture_update(delta, timestamp, is_touchpad);
|
2024-02-29 08:56:20 +04:00
|
|
|
}
|
2024-05-11 14:01:48 +04:00
|
|
|
Op::ViewOffsetGestureEnd { is_touchpad } => {
|
2024-02-29 08:56:20 +04:00
|
|
|
// We don't handle cancels in this gesture.
|
2024-05-11 14:01:48 +04:00
|
|
|
layout.view_offset_gesture_end(false, is_touchpad);
|
2024-02-29 08:56:20 +04:00
|
|
|
}
|
2024-06-19 21:54:46 +03:00
|
|
|
Op::WorkspaceSwitchGestureBegin {
|
|
|
|
|
output_idx: id,
|
|
|
|
|
is_touchpad,
|
|
|
|
|
} => {
|
2024-02-29 09:14:11 +04:00
|
|
|
let name = format!("output{id}");
|
|
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-19 21:54:46 +03:00
|
|
|
layout.workspace_switch_gesture_begin(&output, is_touchpad);
|
2024-02-29 09:14:11 +04:00
|
|
|
}
|
2024-06-19 21:54:46 +03:00
|
|
|
Op::WorkspaceSwitchGestureUpdate {
|
|
|
|
|
delta,
|
|
|
|
|
timestamp,
|
|
|
|
|
is_touchpad,
|
|
|
|
|
} => {
|
|
|
|
|
layout.workspace_switch_gesture_update(delta, timestamp, is_touchpad);
|
2024-02-29 09:14:11 +04:00
|
|
|
}
|
2024-06-19 21:54:46 +03:00
|
|
|
Op::WorkspaceSwitchGestureEnd {
|
|
|
|
|
cancelled,
|
|
|
|
|
is_touchpad,
|
|
|
|
|
} => {
|
|
|
|
|
layout.workspace_switch_gesture_end(cancelled, is_touchpad);
|
2024-02-29 09:14:11 +04:00
|
|
|
}
|
2024-05-10 16:58:53 +04:00
|
|
|
Op::InteractiveResizeBegin { window, edges } => {
|
|
|
|
|
layout.interactive_resize_begin(window, edges);
|
|
|
|
|
}
|
|
|
|
|
Op::InteractiveResizeUpdate { window, dx, dy } => {
|
|
|
|
|
layout.interactive_resize_update(&window, Point::from((dx, dy)));
|
|
|
|
|
}
|
|
|
|
|
Op::InteractiveResizeEnd { window } => {
|
|
|
|
|
layout.interactive_resize_end(&window);
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:41:55 +04:00
|
|
|
#[track_caller]
|
|
|
|
|
fn check_ops(ops: &[Op]) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let mut layout = Layout::default();
|
2023-09-26 20:41:55 +04:00
|
|
|
for op in ops {
|
2023-10-05 09:25:07 +04:00
|
|
|
op.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
2023-09-26 20:41:55 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:38:01 +04:00
|
|
|
#[track_caller]
|
|
|
|
|
fn check_ops_with_options(options: Options, ops: &[Op]) {
|
2024-02-06 19:09:15 +04:00
|
|
|
let mut layout = Layout::with_options(options);
|
2023-12-29 11:38:01 +04:00
|
|
|
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[test]
|
2023-10-05 09:59:28 +04:00
|
|
|
fn operations_dont_panic() {
|
2023-09-12 19:44:17 +04:00
|
|
|
let every_op = [
|
|
|
|
|
Op::AddOutput(0),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(0),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusOutput(0),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::FocusOutput(2),
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddNamedWorkspace {
|
|
|
|
|
ws_name: 1,
|
|
|
|
|
output_name: Some(1),
|
|
|
|
|
},
|
|
|
|
|
Op::UnnameWorkspace { ws_name: 1 },
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-12 19:44:17 +04:00
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-12 19:44:17 +04:00
|
|
|
},
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddWindowRightOf {
|
2023-09-12 19:44:17 +04:00
|
|
|
id: 2,
|
2024-05-11 22:40:30 +02:00
|
|
|
right_of_id: 1,
|
2023-09-12 19:44:17 +04:00
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-12 19:44:17 +04:00
|
|
|
},
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddWindowToNamedWorkspace {
|
2024-02-07 10:49:01 +04:00
|
|
|
id: 3,
|
2024-05-11 22:40:30 +02:00
|
|
|
ws_name: 1,
|
2024-02-07 10:49:01 +04:00
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::CloseWindow(2),
|
2024-02-03 09:44:34 +04:00
|
|
|
Op::FullscreenWindow(1),
|
|
|
|
|
Op::FullscreenWindow(2),
|
|
|
|
|
Op::FullscreenWindow(3),
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::FocusColumnRight,
|
2024-06-09 21:14:51 +10:00
|
|
|
Op::FocusColumnRightOrFirst,
|
|
|
|
|
Op::FocusColumnLeftOrLast,
|
2024-07-05 06:55:04 +02:00
|
|
|
Op::FocusWindowOrMonitorUp(0),
|
|
|
|
|
Op::FocusWindowOrMonitorDown(1),
|
2024-06-28 11:44:24 -03:00
|
|
|
Op::FocusColumnOrMonitorLeft(0),
|
|
|
|
|
Op::FocusColumnOrMonitorRight(1),
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowUp,
|
2024-05-22 13:53:44 -06:00
|
|
|
Op::FocusWindowUpOrColumnLeft,
|
|
|
|
|
Op::FocusWindowUpOrColumnRight,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowOrWorkspaceUp,
|
|
|
|
|
Op::FocusWindowDown,
|
2024-05-22 13:53:44 -06:00
|
|
|
Op::FocusWindowDownOrColumnLeft,
|
|
|
|
|
Op::FocusWindowDownOrColumnRight,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowOrWorkspaceDown,
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::MoveColumnLeft,
|
|
|
|
|
Op::MoveColumnRight,
|
2024-07-09 21:52:48 -07:00
|
|
|
Op::MoveColumnLeftOrToMonitorLeft(0),
|
|
|
|
|
Op::MoveColumnRightOrToMonitorRight(1),
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::ExpelWindowFromColumn,
|
2023-11-13 19:08:29 +04:00
|
|
|
Op::CenterColumn,
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::FocusWorkspaceUp,
|
2023-09-16 12:14:02 +04:00
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::FocusWorkspace(2),
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::MoveWindowToWorkspaceUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: None,
|
|
|
|
|
workspace_idx: 1,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: None,
|
|
|
|
|
workspace_idx: 2,
|
|
|
|
|
},
|
2024-01-15 10:31:44 +04:00
|
|
|
Op::MoveColumnToWorkspaceDown,
|
|
|
|
|
Op::MoveColumnToWorkspaceUp,
|
|
|
|
|
Op::MoveColumnToWorkspace(1),
|
|
|
|
|
Op::MoveColumnToWorkspace(2),
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowDown,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::MoveWindowDownOrToWorkspaceDown,
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowUp,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::MoveWindowUpOrToWorkspaceUp,
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
|
|
|
|
Op::ConsumeOrExpelWindowRight { id: None },
|
2023-12-02 23:57:01 +01:00
|
|
|
Op::MoveWorkspaceToOutput(1),
|
2023-09-12 19:44:17 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for third in every_op {
|
|
|
|
|
for second in every_op {
|
|
|
|
|
for first in every_op {
|
2023-10-05 09:59:28 +04:00
|
|
|
// eprintln!("{first:?}, {second:?}, {third:?}");
|
2023-09-12 19:44:17 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
first.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
second.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
third.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 20:36:48 +04:00
|
|
|
|
2023-10-05 09:59:28 +04:00
|
|
|
#[test]
|
|
|
|
|
fn operations_from_starting_state_dont_panic() {
|
|
|
|
|
if std::env::var_os("RUN_SLOW_TESTS").is_none() {
|
|
|
|
|
eprintln!("ignoring slow test");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Running every op from an empty state doesn't get us to all the interesting states. So,
|
|
|
|
|
// also run it from a manually-created starting state with more things going on to exercise
|
|
|
|
|
// more code paths.
|
|
|
|
|
let setup_ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 3,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 4,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 5,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::MoveWindowToOutput {
|
|
|
|
|
window_id: None,
|
|
|
|
|
output_id: 2,
|
|
|
|
|
target_ws_idx: None,
|
|
|
|
|
},
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::Communicate(1),
|
|
|
|
|
Op::Communicate(2),
|
|
|
|
|
Op::Communicate(3),
|
|
|
|
|
Op::Communicate(4),
|
|
|
|
|
Op::Communicate(5),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let every_op = [
|
|
|
|
|
Op::AddOutput(0),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(0),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusOutput(0),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::FocusOutput(2),
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddNamedWorkspace {
|
|
|
|
|
ws_name: 1,
|
|
|
|
|
output_name: Some(1),
|
|
|
|
|
},
|
|
|
|
|
Op::UnnameWorkspace { ws_name: 1 },
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-05 09:59:28 +04:00
|
|
|
},
|
2024-02-07 10:49:01 +04:00
|
|
|
Op::AddWindowRightOf {
|
|
|
|
|
id: 6,
|
|
|
|
|
right_of_id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindowRightOf {
|
|
|
|
|
id: 7,
|
|
|
|
|
right_of_id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-05-11 22:40:30 +02:00
|
|
|
Op::AddWindowToNamedWorkspace {
|
|
|
|
|
id: 5,
|
|
|
|
|
ws_name: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::CloseWindow(2),
|
2024-02-03 09:44:34 +04:00
|
|
|
Op::FullscreenWindow(1),
|
|
|
|
|
Op::FullscreenWindow(2),
|
|
|
|
|
Op::FullscreenWindow(3),
|
2024-06-04 19:44:33 +03:00
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window: 1,
|
|
|
|
|
is_fullscreen: false,
|
|
|
|
|
},
|
|
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window: 1,
|
|
|
|
|
is_fullscreen: true,
|
|
|
|
|
},
|
|
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window: 2,
|
|
|
|
|
is_fullscreen: false,
|
|
|
|
|
},
|
|
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window: 2,
|
|
|
|
|
is_fullscreen: true,
|
|
|
|
|
},
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::FocusColumnRight,
|
2024-06-09 21:14:51 +10:00
|
|
|
Op::FocusColumnRightOrFirst,
|
|
|
|
|
Op::FocusColumnLeftOrLast,
|
2024-07-05 06:55:04 +02:00
|
|
|
Op::FocusWindowOrMonitorUp(0),
|
|
|
|
|
Op::FocusWindowOrMonitorDown(1),
|
2024-06-28 11:44:24 -03:00
|
|
|
Op::FocusColumnOrMonitorLeft(0),
|
|
|
|
|
Op::FocusColumnOrMonitorRight(1),
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowUp,
|
2024-05-22 13:53:44 -06:00
|
|
|
Op::FocusWindowUpOrColumnLeft,
|
|
|
|
|
Op::FocusWindowUpOrColumnRight,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowOrWorkspaceUp,
|
|
|
|
|
Op::FocusWindowDown,
|
2024-05-22 13:53:44 -06:00
|
|
|
Op::FocusWindowDownOrColumnLeft,
|
|
|
|
|
Op::FocusWindowDownOrColumnRight,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::FocusWindowOrWorkspaceDown,
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::MoveColumnLeft,
|
|
|
|
|
Op::MoveColumnRight,
|
2024-07-09 21:52:48 -07:00
|
|
|
Op::MoveColumnLeftOrToMonitorLeft(0),
|
|
|
|
|
Op::MoveColumnRightOrToMonitorRight(1),
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::ExpelWindowFromColumn,
|
2023-11-13 19:08:29 +04:00
|
|
|
Op::CenterColumn,
|
2023-10-05 09:59:28 +04:00
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::FocusWorkspaceUp,
|
|
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::FocusWorkspace(2),
|
|
|
|
|
Op::FocusWorkspace(3),
|
|
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::MoveWindowToWorkspaceUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: None,
|
|
|
|
|
workspace_idx: 1,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: None,
|
|
|
|
|
workspace_idx: 2,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: None,
|
|
|
|
|
workspace_idx: 3,
|
|
|
|
|
},
|
2024-01-15 10:31:44 +04:00
|
|
|
Op::MoveColumnToWorkspaceDown,
|
|
|
|
|
Op::MoveColumnToWorkspaceUp,
|
|
|
|
|
Op::MoveColumnToWorkspace(1),
|
|
|
|
|
Op::MoveColumnToWorkspace(2),
|
|
|
|
|
Op::MoveColumnToWorkspace(3),
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowDown,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::MoveWindowDownOrToWorkspaceDown,
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowUp,
|
2023-12-19 19:25:05 +11:00
|
|
|
Op::MoveWindowUpOrToWorkspaceUp,
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
|
|
|
|
Op::ConsumeOrExpelWindowRight { id: None },
|
2023-10-05 09:59:28 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for third in every_op {
|
|
|
|
|
for second in every_op {
|
|
|
|
|
for first in every_op {
|
|
|
|
|
// eprintln!("{first:?}, {second:?}, {third:?}");
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in setup_ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
first.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
second.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
third.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:36:48 +04:00
|
|
|
#[test]
|
|
|
|
|
fn primary_active_workspace_idx_not_updated_on_output_add() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-26 20:36:48 +04:00
|
|
|
},
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-26 20:36:48 +04:00
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusWorkspace(3),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
];
|
|
|
|
|
|
2023-09-26 20:41:55 +04:00
|
|
|
check_ops(&ops);
|
2023-09-26 20:36:48 +04:00
|
|
|
}
|
2023-09-26 20:42:16 +04:00
|
|
|
|
|
|
|
|
#[test]
|
2023-09-27 13:09:39 +04:00
|
|
|
fn window_closed_on_previous_workspace() {
|
2023-09-26 20:42:16 +04:00
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-09-26 20:42:16 +04:00
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
2023-09-27 13:35:02 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
#[test]
|
|
|
|
|
fn removing_output_must_keep_empty_focus_on_primary() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-14 19:24:52 +04:00
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// The workspace from the removed output was inserted at position 0, so the active workspace
|
|
|
|
|
// must change to 1 to keep the focus on the empty workspace.
|
|
|
|
|
assert_eq!(monitors[0].active_workspace_idx, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:18:09 +04:00
|
|
|
#[test]
|
|
|
|
|
fn move_to_workspace_by_idx_does_not_leave_empty_workspaces() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-14 20:18:09 +04:00
|
|
|
},
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::MoveWindowToWorkspace {
|
|
|
|
|
window_id: Some(0),
|
|
|
|
|
workspace_idx: 2,
|
|
|
|
|
},
|
2023-10-14 20:18:09 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-06 15:10:01 +03:00
|
|
|
assert!(monitors[0].workspaces[1].has_windows());
|
2023-10-14 20:18:09 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:32:53 +04:00
|
|
|
#[test]
|
|
|
|
|
fn empty_workspaces_dont_move_back_to_original_output() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-14 20:32:53 +04:00
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-10-14 20:32:53 +04:00
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:38:01 +04:00
|
|
|
#[test]
|
|
|
|
|
fn large_negative_height_change() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
2023-12-29 11:42:54 +04:00
|
|
|
min_max_size: Default::default(),
|
2023-12-29 11:38:01 +04:00
|
|
|
},
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::AdjustProportion(-1e129),
|
|
|
|
|
},
|
2023-12-29 11:38:01 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut options = Options::default();
|
|
|
|
|
options.border.off = false;
|
2024-06-17 09:16:28 +03:00
|
|
|
options.border.width = FloatOrInt(1.);
|
2023-12-29 11:38:01 +04:00
|
|
|
|
|
|
|
|
check_ops_with_options(options, &ops);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:42:54 +04:00
|
|
|
#[test]
|
|
|
|
|
fn large_max_size() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut options = Options::default();
|
|
|
|
|
options.border.off = false;
|
2024-06-17 09:16:28 +03:00
|
|
|
options.border.width = FloatOrInt(1.);
|
2023-12-29 11:42:54 +04:00
|
|
|
|
|
|
|
|
check_ops_with_options(options, &ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 20:24:18 +04:00
|
|
|
#[test]
|
|
|
|
|
fn workspace_cleanup_during_switch() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 20:24:59 +04:00
|
|
|
#[test]
|
|
|
|
|
fn workspace_transfer_during_switch() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn workspace_transfer_during_switch_from_last() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::FocusWorkspaceUp,
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn workspace_transfer_during_switch_gets_cleaned_up() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::MoveColumnToWorkspaceDown,
|
|
|
|
|
Op::MoveColumnToWorkspaceDown,
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 23:57:01 +01:00
|
|
|
#[test]
|
|
|
|
|
fn move_workspace_to_output() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWorkspaceToOutput(2),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = layout.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(active_monitor_idx, 1);
|
|
|
|
|
assert_eq!(monitors[0].workspaces.len(), 1);
|
|
|
|
|
assert!(!monitors[0].workspaces[0].has_windows());
|
|
|
|
|
assert_eq!(monitors[1].active_workspace_idx, 0);
|
|
|
|
|
assert_eq!(monitors[1].workspaces.len(), 2);
|
|
|
|
|
assert!(monitors[1].workspaces[0].has_windows());
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-03 09:44:34 +04:00
|
|
|
#[test]
|
|
|
|
|
fn fullscreen() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::FullscreenWindow(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 19:44:33 +03:00
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_window_in_column() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-06-04 19:44:33 +03:00
|
|
|
Op::SetFullscreenWindow {
|
|
|
|
|
window: 2,
|
|
|
|
|
is_fullscreen: false,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-07 13:15:20 +04:00
|
|
|
#[test]
|
|
|
|
|
fn open_right_of_on_different_workspace() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindowRightOf {
|
|
|
|
|
id: 3,
|
|
|
|
|
right_of_id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.into_iter().next().unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
mon.active_workspace_idx, 1,
|
|
|
|
|
"the second workspace must remain active"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
mon.workspaces[0].active_column_idx, 1,
|
|
|
|
|
"the new window must become active"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 18:17:49 +04:00
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_view_offset_not_reset_on_removal() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::FullscreenWindow(0),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowRight { id: None },
|
2024-04-13 18:17:49 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_view_offset_not_reset_on_consume() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::FullscreenWindow(0),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_view_offset_not_reset_on_quick_double_toggle() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::FullscreenWindow(0),
|
|
|
|
|
Op::FullscreenWindow(0),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_view_offset_set_on_fullscreening_inactive_tile_in_column() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-04-13 18:17:49 +04:00
|
|
|
Op::FullscreenWindow(0),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unfullscreen_view_offset_not_reset_on_gesture() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (200, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::FullscreenWindow(1),
|
2024-05-11 14:01:48 +04:00
|
|
|
Op::ViewOffsetGestureBegin {
|
|
|
|
|
output_idx: 1,
|
|
|
|
|
is_touchpad: true,
|
|
|
|
|
},
|
|
|
|
|
Op::ViewOffsetGestureEnd {
|
|
|
|
|
is_touchpad: Some(true),
|
|
|
|
|
},
|
2024-04-13 18:17:49 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 08:17:26 +03:00
|
|
|
#[test]
|
|
|
|
|
fn removing_all_outputs_preserves_empty_named_workspaces() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddNamedWorkspace {
|
|
|
|
|
ws_name: 1,
|
|
|
|
|
output_name: None,
|
|
|
|
|
},
|
|
|
|
|
Op::AddNamedWorkspace {
|
|
|
|
|
ws_name: 2,
|
|
|
|
|
output_name: None,
|
|
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::NoOutputs { workspaces } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(workspaces.len(), 2);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-13 09:50:11 +03:00
|
|
|
#[test]
|
|
|
|
|
fn config_change_updates_cached_sizes() {
|
|
|
|
|
let mut config = Config::default();
|
|
|
|
|
config.layout.border.off = false;
|
2024-06-17 09:16:28 +03:00
|
|
|
config.layout.border.width = FloatOrInt(2.);
|
2024-06-13 09:50:11 +03:00
|
|
|
|
|
|
|
|
let mut layout = Layout::new(&config);
|
|
|
|
|
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
.apply(&mut layout);
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
config.layout.border.width = FloatOrInt(4.);
|
2024-06-13 09:50:11 +03:00
|
|
|
layout.update_config(&config);
|
|
|
|
|
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-05 23:37:10 +02:00
|
|
|
#[test]
|
|
|
|
|
fn preset_height_change_removes_preset() {
|
|
|
|
|
let mut config = Config::default();
|
|
|
|
|
config.layout.preset_window_heights = vec![PresetSize::Fixed(1), PresetSize::Fixed(2)];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::new(&config);
|
|
|
|
|
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-09-12 11:53:10 +03:00
|
|
|
Op::SwitchPresetWindowHeight { id: None },
|
|
|
|
|
Op::SwitchPresetWindowHeight { id: None },
|
2024-09-05 23:37:10 +02:00
|
|
|
];
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Leave only one.
|
|
|
|
|
config.layout.preset_window_heights = vec![PresetSize::Fixed(1)];
|
|
|
|
|
|
|
|
|
|
layout.update_config(&config);
|
|
|
|
|
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
#[test]
|
|
|
|
|
fn working_area_starts_at_physical_pixel() {
|
|
|
|
|
let struts = Struts {
|
|
|
|
|
left: FloatOrInt(0.5),
|
|
|
|
|
right: FloatOrInt(1.),
|
|
|
|
|
top: FloatOrInt(0.75),
|
|
|
|
|
bottom: FloatOrInt(1.),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
|
|
|
|
String::from("output"),
|
|
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
Some(Mode {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
refresh: 60000,
|
|
|
|
|
}),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let area = compute_working_area(&output, struts);
|
|
|
|
|
|
|
|
|
|
assert_eq!(round_logical_in_physical(1., area.loc.x), area.loc.x);
|
|
|
|
|
assert_eq!(round_logical_in_physical(1., area.loc.y), area.loc.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn large_fractional_strut() {
|
|
|
|
|
let struts = Struts {
|
|
|
|
|
left: FloatOrInt(0.),
|
|
|
|
|
right: FloatOrInt(0.),
|
|
|
|
|
top: FloatOrInt(50000.5),
|
|
|
|
|
bottom: FloatOrInt(0.),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
|
|
|
|
String::from("output"),
|
|
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
Some(Mode {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
refresh: 60000,
|
|
|
|
|
}),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
compute_working_area(&output, struts);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 10:47:27 +03:00
|
|
|
#[test]
|
|
|
|
|
fn set_window_height_recomputes_to_auto() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(100),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::FocusWindowUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(200),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn one_window_in_column_becomes_weight_1() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(100),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::Communicate(2),
|
|
|
|
|
Op::FocusWindowUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(200),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::Communicate(1),
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn one_window_in_column_becomes_weight_1_after_fullscreen() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(100),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::Communicate(2),
|
|
|
|
|
Op::FocusWindowUp,
|
2024-09-06 15:10:01 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: None,
|
|
|
|
|
change: SizeChange::SetFixed(200),
|
|
|
|
|
},
|
2024-08-15 10:47:27 +03:00
|
|
|
Op::Communicate(1),
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::FullscreenWindow(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 19:31:47 +03:00
|
|
|
#[test]
|
|
|
|
|
fn fixed_height_takes_max_non_auto_into_account() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-09-12 20:54:44 +03:00
|
|
|
Op::SetWindowHeight {
|
|
|
|
|
id: Some(0),
|
|
|
|
|
change: SizeChange::SetFixed(704),
|
|
|
|
|
},
|
2024-09-12 19:31:47 +03:00
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
min_max_size: Default::default(),
|
|
|
|
|
},
|
2024-10-11 19:52:03 +03:00
|
|
|
Op::ConsumeOrExpelWindowLeft { id: None },
|
2024-09-12 19:31:47 +03:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let options = Options {
|
|
|
|
|
border: niri_config::Border {
|
|
|
|
|
off: false,
|
|
|
|
|
width: niri_config::FloatOrInt(4.),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
gaps: 0.,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
check_ops_with_options(options, &ops);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 09:16:28 +03:00
|
|
|
fn arbitrary_spacing() -> impl Strategy<Value = f64> {
|
2024-01-08 20:57:53 +04:00
|
|
|
// Give equal weight to:
|
|
|
|
|
// - 0: the element is disabled
|
|
|
|
|
// - 4: some reasonable value
|
|
|
|
|
// - random value, likely unreasonably big
|
2024-06-17 09:16:28 +03:00
|
|
|
prop_oneof![Just(0.), Just(4.), ((1.)..=65535.)]
|
2024-01-08 20:57:53 +04:00
|
|
|
}
|
|
|
|
|
|
2024-07-22 12:35:57 +03:00
|
|
|
fn arbitrary_spacing_neg() -> impl Strategy<Value = f64> {
|
|
|
|
|
// Give equal weight to:
|
|
|
|
|
// - 0: the element is disabled
|
|
|
|
|
// - 4: some reasonable value
|
|
|
|
|
// - -4: some reasonable negative value
|
|
|
|
|
// - random value, likely unreasonably big
|
|
|
|
|
prop_oneof![Just(0.), Just(4.), Just(-4.), ((1.)..=65535.)]
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 20:57:53 +04:00
|
|
|
fn arbitrary_struts() -> impl Strategy<Value = Struts> {
|
|
|
|
|
(
|
2024-07-22 12:35:57 +03:00
|
|
|
arbitrary_spacing_neg(),
|
|
|
|
|
arbitrary_spacing_neg(),
|
|
|
|
|
arbitrary_spacing_neg(),
|
|
|
|
|
arbitrary_spacing_neg(),
|
2024-01-08 20:57:53 +04:00
|
|
|
)
|
|
|
|
|
.prop_map(|(left, right, top, bottom)| Struts {
|
2024-06-17 09:16:28 +03:00
|
|
|
left: FloatOrInt(left),
|
|
|
|
|
right: FloatOrInt(right),
|
|
|
|
|
top: FloatOrInt(top),
|
|
|
|
|
bottom: FloatOrInt(bottom),
|
2024-01-08 20:57:53 +04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn arbitrary_center_focused_column() -> impl Strategy<Value = CenterFocusedColumn> {
|
|
|
|
|
prop_oneof![
|
|
|
|
|
Just(CenterFocusedColumn::Never),
|
|
|
|
|
Just(CenterFocusedColumn::OnOverflow),
|
|
|
|
|
Just(CenterFocusedColumn::Always),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
|
fn arbitrary_focus_ring()(
|
|
|
|
|
off in any::<bool>(),
|
|
|
|
|
width in arbitrary_spacing(),
|
|
|
|
|
) -> niri_config::FocusRing {
|
|
|
|
|
niri_config::FocusRing {
|
|
|
|
|
off,
|
2024-06-17 09:16:28 +03:00
|
|
|
width: FloatOrInt(width),
|
2024-01-08 20:57:53 +04:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 09:34:54 +04:00
|
|
|
prop_compose! {
|
|
|
|
|
fn arbitrary_border()(
|
|
|
|
|
off in any::<bool>(),
|
|
|
|
|
width in arbitrary_spacing(),
|
|
|
|
|
) -> niri_config::Border {
|
|
|
|
|
niri_config::Border {
|
|
|
|
|
off,
|
2024-06-17 09:16:28 +03:00
|
|
|
width: FloatOrInt(width),
|
2024-02-12 09:34:54 +04:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 20:57:53 +04:00
|
|
|
prop_compose! {
|
|
|
|
|
fn arbitrary_options()(
|
|
|
|
|
gaps in arbitrary_spacing(),
|
|
|
|
|
struts in arbitrary_struts(),
|
|
|
|
|
focus_ring in arbitrary_focus_ring(),
|
2024-02-12 09:34:54 +04:00
|
|
|
border in arbitrary_border(),
|
2024-01-08 20:57:53 +04:00
|
|
|
center_focused_column in arbitrary_center_focused_column(),
|
2024-08-29 16:41:59 +02:00
|
|
|
always_center_single_column in any::<bool>(),
|
2024-01-08 20:57:53 +04:00
|
|
|
) -> Options {
|
|
|
|
|
Options {
|
2024-06-17 09:16:28 +03:00
|
|
|
gaps,
|
2024-01-08 20:57:53 +04:00
|
|
|
struts,
|
|
|
|
|
center_focused_column,
|
2024-08-29 16:41:59 +02:00
|
|
|
always_center_single_column,
|
2024-01-08 20:57:53 +04:00
|
|
|
focus_ring,
|
|
|
|
|
border,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-29 11:43:16 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
proptest! {
|
|
|
|
|
#![proptest_config(ProptestConfig {
|
|
|
|
|
cases: if std::env::var_os("RUN_SLOW_TESTS").is_none() {
|
|
|
|
|
eprintln!("ignoring slow test");
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
ProptestConfig::default().cases
|
|
|
|
|
},
|
|
|
|
|
..ProptestConfig::default()
|
|
|
|
|
})]
|
|
|
|
|
|
|
|
|
|
#[test]
|
2024-01-08 20:57:53 +04:00
|
|
|
fn random_operations_dont_panic(ops: Vec<Op>, options in arbitrary_options()) {
|
2023-09-27 13:35:02 +04:00
|
|
|
// eprintln!("{ops:?}");
|
2023-12-29 11:43:16 +04:00
|
|
|
check_ops_with_options(options, &ops);
|
2023-09-27 13:35:02 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|