Files
niri/src/layout/mod.rs
T

4674 lines
161 KiB
Rust
Raw Normal View History

//! Window layout logic.
//!
2025-01-31 18:05:03 +03:00
//! Niri implements scrollable tiling with dynamic workspaces. The scrollable tiling is mostly
//! orthogonal to any particular workspace system, though outputs living in separate coordinate
//! spaces suggest per-output workspaces.
//!
2025-01-31 18:05:03 +03:00
//! I chose a dynamic workspace system because I think it works very well. In particular, it works
//! naturally across outputs getting added and removed, since workspaces can move between outputs
//! as necessary.
//!
//! In the layout, one output (the first one to be added) is designated as *primary*. This is where
//! workspaces from disconnected outputs will move. Currently, the primary output has no other
//! distinction from other outputs.
//!
//! Where possible, niri tries to follow these principles with regards to outputs:
//!
//! 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
2025-01-31 18:05:03 +03:00
//! originated on—its *original output*. When an output disconnects, its workspaces 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.
use std::cmp::min;
use std::collections::HashMap;
use std::mem;
use std::rc::Rc;
use std::time::Duration;
2024-12-26 09:37:38 +03:00
use monitor::MonitorAddWindowTarget;
2024-09-05 23:37:10 +02:00
use niri_config::{
2025-02-06 08:42:09 +03:00
CenterFocusedColumn, Config, CornerRadius, FloatOrInt, PresetSize, Struts,
Workspace as WorkspaceConfig, WorkspaceReference,
2024-09-05 23:37:10 +02:00
};
2025-02-06 08:42:09 +03:00
use niri_ipc::{ColumnDisplay, PositionChange, SizeChange};
2024-11-30 09:18:33 +03:00
use scrolling::{Column, ColumnWidth, InsertHint, InsertPosition};
2024-02-04 22:29:09 +04:00
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
2024-05-29 13:32:11 +03:00
use smithay::output::{self, Output};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
2024-07-15 15:51:48 +02:00
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
use tile::{Tile, TileRenderElement};
2024-12-26 09:37:38 +03:00
use workspace::{WorkspaceAddWindowTarget, WorkspaceId};
2023-12-24 15:10:09 +04:00
pub use self::monitor::MonitorRenderElement;
use self::monitor::{Monitor, WorkspaceSwitch};
2024-11-30 09:18:33 +03:00
use self::workspace::{OutputId, Workspace};
use crate::animation::Clock;
2025-01-09 08:29:36 +00:00
use crate::layout::scrolling::ScrollDirection;
use crate::niri_render_elements;
use crate::render_helpers::offscreen::OffscreenData;
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};
use crate::render_helpers::texture::TextureBuffer;
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
2024-07-15 15:51:48 +02:00
use crate::rubber_band::RubberBand;
2024-08-23 15:41:06 +03:00
use crate::utils::transaction::{Transaction, TransactionBlocker};
use crate::utils::{
ensure_min_max_size_maybe_zero, output_matches_name, output_size,
round_logical_in_physical_max1, ResizeEdge,
};
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-11-29 21:11:02 +03:00
pub mod floating;
2024-02-05 17:27:09 +04:00
pub mod focus_ring;
2024-11-02 09:33:44 +03:00
pub mod insert_hint_element;
2024-02-05 17:27:09 +04:00
pub mod monitor;
2024-05-15 19:38:29 +04:00
pub mod opening_window;
2024-11-30 09:18:33 +03:00
pub mod scrolling;
2025-01-15 14:16:05 +03:00
pub mod shadow;
2025-02-02 08:41:42 +03:00
pub mod tab_indicator;
2024-02-05 17:27:09 +04:00
pub mod tile;
pub mod workspace;
2023-08-14 16:19:43 +04:00
2025-01-31 17:55:15 +03:00
#[cfg(test)]
mod tests;
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-07-15 15:51:48 +02:00
/// Pointer needs to move this far to pull a window from the layout.
const INTERACTIVE_MOVE_START_THRESHOLD: f64 = 256. * 256.;
/// Opacity of interactively moved tiles targeting the scrolling layout.
const INTERACTIVE_MOVE_ALPHA: f64 = 0.75;
2024-12-24 10:51:00 +03:00
/// Size-relative units.
pub struct SizeFrac;
niri_render_elements! {
LayoutElementRenderElement<R> => {
Wayland = WaylandSurfaceRenderElement<R>,
SolidColor = SolidColorRenderElement,
}
2024-02-04 22:29:09 +04:00
}
pub type LayoutElementRenderSnapshot =
RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>;
2024-04-09 22:37:10 +04:00
pub trait LayoutElement {
/// Type that can be used as a unique ID of this element.
2024-10-18 08:40:52 +03:00
type Id: PartialEq + std::fmt::Debug + Clone;
/// Unique ID of this element.
fn id(&self) -> &Self::Id;
/// 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.
fn is_in_input_region(&self, point: Point<f64, Logical>) -> bool;
/// 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.
fn render<R: NiriRenderer>(
&self,
renderer: &mut R,
2024-06-17 09:16:28 +03:00
location: Point<f64, Logical>,
scale: Scale<f64>,
2024-03-24 08:30:26 +04:00
alpha: f32,
target: RenderTarget,
) -> 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>,
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>,
scale: Scale<f64>,
alpha: f32,
target: RenderTarget,
) -> Vec<LayoutElementRenderElement<R>> {
self.render(renderer, location, scale, alpha, target).popups
}
2024-12-17 10:03:27 +03:00
/// Requests the element to change its size.
///
/// The size request is stored and will be continuously sent to the element on any further
/// state changes.
2024-08-22 14:44:11 +03:00
fn request_size(
&mut self,
size: Size<i32, Logical>,
is_fullscreen: bool,
2024-08-22 14:44:11 +03:00
animate: bool,
transaction: Option<Transaction>,
);
2024-12-17 10:03:27 +03:00
/// Requests the element to change size once, clearing the request afterwards.
fn request_size_once(&mut self, size: Size<i32, Logical>, animate: bool) {
self.request_size(size, false, animate, None);
2024-12-17 10:03:27 +03:00
}
fn min_size(&self) -> Size<i32, Logical>;
2023-08-16 10:03:24 +04:00
fn max_size(&self) -> Size<i32, Logical>;
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);
fn output_enter(&self, output: &Output);
fn output_leave(&self, output: &Output);
fn set_offscreen_data(&self, data: Option<OffscreenData>);
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-12-27 11:20:03 +03:00
fn set_floating(&mut self, floating: bool);
2024-03-19 13:52:08 +04:00
fn set_bounds(&self, bounds: Size<i32, Logical>);
2025-01-23 10:40:52 +03:00
fn is_ignoring_opacity_window_rule(&self) -> bool;
2024-03-19 13:52:08 +04:00
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);
/// Whether the element is currently fullscreen.
///
/// This will *not* switch immediately after a [`LayoutElement::request_size()`] call.
fn is_fullscreen(&self) -> bool;
/// Whether we're requesting the element to be fullscreen.
///
/// This *will* switch immediately after a [`LayoutElement::request_size()`] call.
fn is_pending_fullscreen(&self) -> bool;
2024-03-19 13:52:08 +04:00
/// Size previously requested through [`LayoutElement::request_size()`].
fn requested_size(&self) -> Option<Size<i32, Logical>>;
/// Non-fullscreen size that we expect this window has or will shortly have.
2024-12-17 10:03:27 +03:00
///
/// This can be different from [`requested_size()`](LayoutElement::requested_size()). For
/// example, for floating windows this will generally return the current window size, rather
/// than the last size that we requested, since we want floating windows to be able to change
/// size freely. But not always: if we just requested a floating window to resize and it hasn't
/// responded to it yet, this will return the newly requested size.
///
/// This function should never return a 0 size component. `None` means there's no known
/// expected size (for example, the window is fullscreen).
///
/// The default impl is for testing only, it will not preserve the window's own size changes.
fn expected_size(&self) -> Option<Size<i32, Logical>> {
if self.is_fullscreen() {
return None;
}
let mut requested = self.requested_size().unwrap_or_default();
let current = self.size();
if requested.w == 0 {
requested.w = current.w;
}
if requested.h == 0 {
requested.h = current.h;
}
Some(requested)
2024-12-17 10:03:27 +03:00
}
2025-03-17 14:56:29 +03:00
fn is_pending_windowed_fullscreen(&self) -> bool {
false
}
fn request_windowed_fullscreen(&mut self, value: bool) {
let _ = value;
}
fn is_child_of(&self, parent: &Self) -> bool;
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 interactive_resize_data(&self) -> Option<InteractiveResizeData>;
fn on_commit(&mut self, serial: Serial);
}
#[derive(Debug)]
pub struct Layout<W: LayoutElement> {
/// Monitors and workspaes in the layout.
monitor_set: MonitorSet<W>,
/// Whether the layout should draw as active.
///
/// This normally indicates that the layout has keyboard focus, but not always. E.g. when the
/// screenshot UI is open, it keeps the layout drawing as active.
is_active: bool,
/// Map from monitor name to id of its last active workspace.
///
/// This data is stored upon monitor removal and is used to restore the active workspace when
/// the monitor is reconnected.
///
/// The workspace id does not necessarily point to a valid workspace. If it doesn't, then it is
/// simply ignored.
last_active_workspace_id: HashMap<String, WorkspaceId>,
2024-07-15 15:51:48 +02:00
/// Ongoing interactive move.
interactive_move: Option<InteractiveMoveState<W>>,
2025-02-15 13:11:34 +03:00
/// Ongoing drag-and-drop operation.
dnd: Option<DndData>,
/// Clock for driving animations.
clock: Clock,
/// Time that we last updated render elements for.
update_render_elements_time: Duration,
/// Configurable properties of the layout.
options: Rc<Options>,
}
#[derive(Debug)]
enum MonitorSet<W: LayoutElement> {
/// At least one output is connected.
Normal {
/// Connected monitors.
monitors: Vec<Monitor<W>>,
/// Index of the primary monitor.
primary_idx: usize,
/// Index of the active monitor.
active_monitor_idx: usize,
},
/// No outputs are connected, and these are the workspaces.
NoOutputs {
/// The workspaces.
workspaces: Vec<Workspace<W>>,
},
}
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,
pub border: niri_config::Border,
2025-01-15 14:16:05 +03:00
pub shadow: niri_config::Shadow,
2025-02-02 08:41:42 +03:00
pub tab_indicator: niri_config::TabIndicator,
2024-07-15 15:51:48 +02:00
pub insert_hint: niri_config::InsertHint,
2024-02-05 17:27:09 +04:00
pub center_focused_column: CenterFocusedColumn,
pub always_center_single_column: bool,
pub empty_workspace_above_first: bool,
2025-02-01 10:46:52 +03:00
pub default_column_display: ColumnDisplay,
/// Column or window widths that `toggle_width()` switches between.
pub preset_column_widths: Vec<PresetSize>,
/// Initial width for new columns.
pub default_column_width: Option<PresetSize>,
2024-09-05 23:37:10 +02:00
/// 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,
2025-02-16 08:46:38 +03:00
pub gestures: niri_config::Gestures,
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,
}
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(),
focus_ring: Default::default(),
border: Default::default(),
2025-01-15 14:16:05 +03:00
shadow: Default::default(),
2025-02-02 08:41:42 +03:00
tab_indicator: Default::default(),
2024-07-15 15:51:48 +02:00
insert_hint: Default::default(),
2024-01-08 17:17:19 +04:00
center_focused_column: Default::default(),
always_center_single_column: false,
empty_workspace_above_first: false,
2025-02-06 08:42:09 +03:00
default_column_display: ColumnDisplay::Normal,
2024-09-05 23:37:10 +02:00
preset_column_widths: vec![
PresetSize::Proportion(1. / 3.),
PresetSize::Proportion(0.5),
PresetSize::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(),
2025-02-16 08:46:38 +03:00
gestures: 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.),
],
}
}
}
2024-11-05 21:08:50 +03:00
#[derive(Debug)]
enum InteractiveMoveState<W: LayoutElement> {
/// Initial rubberbanding; the window remains in the layout.
Starting {
/// The window we're moving.
window_id: W::Id,
/// Current pointer delta from the starting location.
pointer_delta: Point<f64, Logical>,
/// Pointer location within the visual window geometry as ratio from geometry size.
///
/// This helps the pointer remain inside the window as it resizes.
pointer_ratio_within_window: (f64, f64),
},
/// Moving; the window is no longer in the layout.
Moving(InteractiveMoveData<W>),
}
#[derive(Debug)]
struct InteractiveMoveData<W: LayoutElement> {
/// The window being moved.
pub(self) tile: Tile<W>,
/// Output where the window is currently located/rendered.
pub(self) output: Output,
/// Current pointer position within output.
pub(self) pointer_pos_within_output: Point<f64, Logical>,
/// Window column width.
pub(self) width: ColumnWidth,
/// Whether the window column was full-width.
pub(self) is_full_width: bool,
/// Whether the window targets the floating layout.
pub(self) is_floating: bool,
2024-11-05 21:08:50 +03:00
/// Pointer location within the visual window geometry as ratio from geometry size.
///
/// This helps the pointer remain inside the window as it resizes.
pub(self) pointer_ratio_within_window: (f64, f64),
}
2025-02-15 13:11:34 +03:00
#[derive(Debug)]
pub struct DndData {
/// Output where the pointer is currently located.
output: Output,
/// Current pointer position within output.
pointer_pos_within_output: Point<f64, Logical>,
}
2024-11-05 21:08:50 +03:00
#[derive(Debug, Clone, Copy)]
pub struct InteractiveResizeData {
pub(self) edges: ResizeEdge,
}
#[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-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,
2024-11-29 21:11:02 +03:00
/// Whether the tile was floating.
is_floating: bool,
2024-10-14 18:08:44 +03:00
}
/// Whether to activate a newly added window.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ActivateWindow {
/// Activate unconditionally.
Yes,
/// Activate based on heuristics.
#[default]
Smart,
/// Do not activate.
No,
}
2024-12-26 09:37:38 +03:00
/// Where to put a newly added window.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum AddWindowTarget<'a, W: LayoutElement> {
/// No particular preference.
#[default]
Auto,
/// On this output.
Output(&'a Output),
/// On this workspace.
Workspace(WorkspaceId),
/// Next to this existing window.
NextTo(&'a W::Id),
}
2025-02-10 08:03:39 +03:00
/// Type of the window hit from `window_under()`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HitType {
/// The hit is within a window's input region and can be used for sending events to it.
Input {
/// Position of the window's buffer.
win_pos: Point<f64, Logical>,
},
/// The hit can activate a window, but it is not in the input region so cannot send events.
///
/// For example, this could be clicking on a tile border outside the window.
2025-02-10 13:12:53 +03:00
Activate {
/// Whether the hit was on the tab indicator.
is_tab_indicator: bool,
},
2025-02-10 08:03:39 +03:00
}
2024-07-15 15:51:48 +02:00
impl<W: LayoutElement> InteractiveMoveState<W> {
fn moving(&self) -> Option<&InteractiveMoveData<W>> {
match self {
InteractiveMoveState::Moving(move_) => Some(move_),
_ => None,
}
}
2025-02-05 10:08:56 +03:00
fn moving_mut(&mut self) -> Option<&mut InteractiveMoveData<W>> {
match self {
InteractiveMoveState::Moving(move_) => Some(move_),
_ => None,
}
}
2024-07-15 15:51:48 +02:00
}
impl<W: LayoutElement> InteractiveMoveData<W> {
fn tile_render_location(&self) -> Point<f64, Logical> {
let scale = Scale::from(self.output.current_scale().fractional_scale());
let window_size = self.tile.window_size();
let pointer_offset_within_window = Point::from((
window_size.w * self.pointer_ratio_within_window.0,
window_size.h * self.pointer_ratio_within_window.1,
));
let pos =
self.pointer_pos_within_output - pointer_offset_within_window - self.tile.window_loc()
+ self.tile.render_offset();
// Round to physical pixels.
pos.to_physical_precise_round(scale).to_logical(scale)
}
}
impl ActivateWindow {
pub fn map_smart(self, f: impl FnOnce() -> bool) -> bool {
match self {
ActivateWindow::Yes => true,
ActivateWindow::Smart => f(),
ActivateWindow::No => false,
}
}
}
2025-02-10 08:03:39 +03:00
impl HitType {
pub fn offset_win_pos(mut self, offset: Point<f64, Logical>) -> Self {
match &mut self {
HitType::Input { win_pos } => *win_pos += offset,
2025-02-10 13:12:53 +03:00
HitType::Activate { .. } => (),
2025-02-10 08:03:39 +03:00
}
self
}
pub fn hit_tile<W: LayoutElement>(
tile: &Tile<W>,
tile_pos: Point<f64, Logical>,
point: Point<f64, Logical>,
) -> Option<(&W, Self)> {
let pos_within_tile = point - tile_pos;
tile.hit(pos_within_tile)
.map(|hit| (tile.window(), hit.offset_win_pos(tile_pos)))
}
2025-02-10 08:03:39 +03:00
}
impl Options {
fn from_config(config: &Config) -> Self {
let layout = &config.layout;
2024-09-05 23:37:10 +02:00
let preset_column_widths = if layout.preset_column_widths.is_empty() {
Options::default().preset_column_widths
} else {
layout.preset_column_widths.clone()
};
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()
};
// Missing default_column_width maps to Some(PresetSize::Proportion(0.5)),
2023-11-02 21:07:29 +04:00
// 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()
.map(|w| w.0)
.unwrap_or(Some(PresetSize::Proportion(0.5)));
2023-11-02 21:07:29 +04:00
Self {
2024-06-17 09:16:28 +03:00
gaps: layout.gaps.0,
struts: layout.struts,
focus_ring: layout.focus_ring,
border: layout.border,
2025-01-15 14:16:05 +03:00
shadow: layout.shadow,
2025-02-02 08:41:42 +03:00
tab_indicator: layout.tab_indicator,
2024-07-15 15:51:48 +02:00
insert_hint: layout.insert_hint,
2024-01-08 17:17:19 +04:00
center_focused_column: layout.center_focused_column,
always_center_single_column: layout.always_center_single_column,
empty_workspace_above_first: layout.empty_workspace_above_first,
2025-02-01 10:46:52 +03:00
default_column_display: layout.default_column_display,
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(),
2025-02-16 08:46:38 +03:00
gestures: config.gestures,
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,
}
}
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
}
}
impl<W: LayoutElement> Layout<W> {
pub fn new(clock: Clock, config: &Config) -> Self {
Self::with_options_and_workspaces(clock, config, Options::from_config(config))
2024-02-06 19:09:15 +04:00
}
pub fn with_options(clock: Clock, options: Options) -> Self {
Self {
monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
is_active: true,
last_active_workspace_id: HashMap::new(),
2024-07-15 15:51:48 +02:00
interactive_move: None,
2025-02-15 13:11:34 +03:00
dnd: None,
clock,
update_render_elements_time: Duration::ZERO,
2024-02-06 19:09:15 +04:00
options: Rc::new(options),
}
}
fn with_options_and_workspaces(clock: Clock, config: &Config, options: Options) -> Self {
2024-05-11 22:40:30 +02:00
let opts = Rc::new(options);
let workspaces = config
.workspaces
.iter()
.map(|ws| {
Workspace::new_with_config_no_outputs(Some(ws.clone()), clock.clone(), opts.clone())
})
2024-05-11 22:40:30 +02:00
.collect();
Self {
monitor_set: MonitorSet::NoOutputs { workspaces },
is_active: true,
last_active_workspace_id: HashMap::new(),
2024-07-15 15:51:48 +02:00
interactive_move: None,
2025-02-15 13:11:34 +03:00
dnd: None,
clock,
update_render_elements_time: Duration::ZERO,
2024-05-11 22:40:30 +02:00
options: opts,
}
}
pub fn add_output(&mut self, output: Output) {
self.monitor_set = match mem::take(&mut self.monitor_set) {
MonitorSet::Normal {
mut monitors,
primary_idx,
active_monitor_idx,
} => {
let primary = &mut monitors[primary_idx];
let ws_id_to_activate = self.last_active_workspace_id.remove(&output.name());
let mut active_workspace_idx = None;
let mut stopped_primary_ws_switch = false;
let mut workspaces = vec![];
for i in (0..primary.workspaces.len()).rev() {
if primary.workspaces[i].original_output.matches(&output) {
let ws = primary.workspaces.remove(i);
// 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;
}
// The user could've closed a window while remaining on this workspace, on
// another monitor. However, we will add an empty workspace in the end
// instead.
if ws.has_windows_or_name() {
if Some(ws.id()) == ws_id_to_activate {
active_workspace_idx = Some(workspaces.len());
}
workspaces.push(ws);
}
if i <= primary.active_workspace_idx
// Generally when moving the currently active workspace, we want to
// fall back to the workspace above, so as not to end up on the last
// empty workspace. However, with empty workspace above first, when
// moving the workspace at index 1 (first non-empty), we want to stay
// at index 1, so as once again not to end up on an empty workspace.
//
// This comes into play at compositor startup when having named
// workspaces set up across multiple monitors. Without this check, the
// first monitor to connect can end up with the first empty workspace
// focused instead of the first named workspace.
&& !(self.options.empty_workspace_above_first
&& primary.active_workspace_idx == 1)
{
primary.active_workspace_idx =
primary.active_workspace_idx.saturating_sub(1);
}
}
}
// If we stopped a workspace switch, then we might need to clean up workspaces.
// Also if empty_workspace_above_first is set and there are only 2 workspaces left,
// both will be empty and one of them needs to be removed. clean_up_workspaces
// takes care of this.
if stopped_primary_ws_switch
|| (primary.options.empty_workspace_above_first
&& primary.workspaces.len() == 2)
{
primary.clean_up_workspaces();
}
workspaces.reverse();
if let Some(idx) = &mut active_workspace_idx {
*idx = workspaces.len() - *idx - 1;
}
let mut active_workspace_idx = active_workspace_idx.unwrap_or(0);
// Make sure there's always an empty workspace.
workspaces.push(Workspace::new(
output.clone(),
self.clock.clone(),
self.options.clone(),
));
if self.options.empty_workspace_above_first && workspaces.len() > 1 {
workspaces.insert(
0,
Workspace::new(output.clone(), self.clock.clone(), self.options.clone()),
);
active_workspace_idx += 1;
}
for ws in &mut workspaces {
ws.set_output(Some(output.clone()));
}
let mut monitor =
Monitor::new(output, workspaces, self.clock.clone(), self.options.clone());
monitor.active_workspace_idx = active_workspace_idx;
monitors.push(monitor);
MonitorSet::Normal {
monitors,
primary_idx,
active_monitor_idx,
}
}
MonitorSet::NoOutputs { mut workspaces } => {
// We know there are no empty workspaces there, so add one.
workspaces.push(Workspace::new(
output.clone(),
self.clock.clone(),
self.options.clone(),
));
let mut active_workspace_idx = 0;
if self.options.empty_workspace_above_first && workspaces.len() > 1 {
workspaces.insert(
0,
Workspace::new(output.clone(), self.clock.clone(), self.options.clone()),
);
active_workspace_idx += 1;
}
let ws_id_to_activate = self.last_active_workspace_id.remove(&output.name());
for (i, workspace) in workspaces.iter_mut().enumerate() {
workspace.set_output(Some(output.clone()));
if Some(workspace.id()) == ws_id_to_activate {
active_workspace_idx = i;
}
}
let mut monitor =
Monitor::new(output, workspaces, self.clock.clone(), self.options.clone());
monitor.active_workspace_idx = active_workspace_idx;
MonitorSet::Normal {
monitors: vec![monitor],
primary_idx: 0,
active_monitor_idx: 0,
}
}
}
}
pub fn remove_output(&mut self, output: &Output) {
self.monitor_set = match mem::take(&mut self.monitor_set) {
MonitorSet::Normal {
mut monitors,
mut primary_idx,
mut active_monitor_idx,
} => {
let idx = monitors
.iter()
.position(|mon| &mon.output == output)
.expect("trying to remove non-existing output");
let monitor = monitors.remove(idx);
self.last_active_workspace_id.insert(
monitor.output_name().clone(),
monitor.workspaces[monitor.active_workspace_idx].id(),
);
let mut workspaces = monitor.workspaces;
for ws in &mut workspaces {
ws.set_output(None);
}
// Get rid of empty workspaces.
workspaces.retain(|ws| ws.has_windows_or_name());
if monitors.is_empty() {
// Removed the last monitor.
MonitorSet::NoOutputs { workspaces }
} else {
if primary_idx >= idx {
// Update primary_idx to either still point at the same monitor, or at some
// other monitor if the primary has been removed.
primary_idx = primary_idx.saturating_sub(1);
}
if active_monitor_idx >= idx {
// Update active_monitor_idx to either still point at the same monitor, or
// at some other monitor if the active monitor has
// been removed.
active_monitor_idx = active_monitor_idx.saturating_sub(1);
}
let primary = &mut monitors[primary_idx];
for ws in &mut workspaces {
ws.set_output(Some(primary.output.clone()));
}
let mut stopped_primary_ws_switch = false;
if !workspaces.is_empty() && primary.workspace_switch.is_some() {
// FIXME: if we're adding workspaces to currently invisible positions
// (outside the workspace switch), we don't need to cancel it.
primary.workspace_switch = None;
stopped_primary_ws_switch = true;
}
let empty_was_focused =
primary.active_workspace_idx == primary.workspaces.len() - 1;
// Push the workspaces from the removed monitor in the end, right before the
// last, empty, workspace.
let empty = primary.workspaces.remove(primary.workspaces.len() - 1);
primary.workspaces.extend(workspaces);
primary.workspaces.push(empty);
// If empty_workspace_above_first is set and the first workspace is now no
// longer empty, add a new empty workspace on top.
if primary.options.empty_workspace_above_first
&& primary.workspaces[0].has_windows_or_name()
{
primary.add_workspace_top();
}
// 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;
}
if stopped_primary_ws_switch {
primary.clean_up_workspaces();
}
MonitorSet::Normal {
monitors,
primary_idx,
active_monitor_idx,
}
}
}
MonitorSet::NoOutputs { .. } => {
panic!("tried to remove output when there were already 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;
}
}
/// Adds a new window to the layout.
///
/// Returns an output that the window was added to, if there were any outputs.
#[allow(clippy::too_many_arguments)]
2023-11-02 21:07:29 +04:00
pub fn add_window(
&mut self,
window: W,
2024-12-26 09:37:38 +03:00
target: AddWindowTarget<W>,
width: Option<PresetSize>,
height: Option<PresetSize>,
is_full_width: bool,
2024-11-29 21:11:02 +03:00
is_floating: bool,
activate: ActivateWindow,
2023-11-02 21:07:29 +04:00
) -> Option<&Output> {
let scrolling_width = self.resolve_scrolling_width(&window, width);
let scrolling_height = height.map(SizeChange::from);
let id = window.id().clone();
2023-11-02 21:07:29 +04:00
match &mut self.monitor_set {
MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} => {
2024-12-26 09:37:38 +03:00
let (mon_idx, target) = match target {
AddWindowTarget::Auto => (*active_monitor_idx, MonitorAddWindowTarget::Auto),
AddWindowTarget::Output(output) => {
let mon_idx = monitors
.iter()
.position(|mon| mon.output == *output)
.unwrap();
2024-12-26 09:37:38 +03:00
(mon_idx, MonitorAddWindowTarget::Auto)
}
AddWindowTarget::Workspace(ws_id) => {
let mon_idx = monitors
.iter()
.position(|mon| mon.workspaces.iter().any(|ws| ws.id() == ws_id))
.unwrap();
(
mon_idx,
MonitorAddWindowTarget::Workspace {
id: ws_id,
column_idx: None,
},
)
}
AddWindowTarget::NextTo(next_to) => {
if let Some(output) = self
.interactive_move
.as_ref()
.and_then(|move_| {
if let InteractiveMoveState::Moving(move_) = move_ {
Some(move_)
} else {
None
}
})
.filter(|move_| next_to == move_.tile.window().id())
.map(|move_| move_.output.clone())
{
// The next_to window is being interactively moved.
let mon_idx = monitors
.iter()
.position(|mon| mon.output == output)
.unwrap_or(*active_monitor_idx);
(mon_idx, MonitorAddWindowTarget::Auto)
} else {
let mon_idx = monitors
.iter()
.position(|mon| {
mon.workspaces.iter().any(|ws| ws.has_window(next_to))
})
.unwrap();
(mon_idx, MonitorAddWindowTarget::NextTo(next_to))
}
}
};
let mon = &mut monitors[mon_idx];
mon.add_window(
window,
2024-12-26 09:37:38 +03:00
target,
activate,
scrolling_width,
is_full_width,
2024-11-29 21:11:02 +03:00
is_floating,
);
2024-12-26 09:37:38 +03:00
if activate.map_smart(|| false) {
*active_monitor_idx = mon_idx;
2024-07-15 15:51:48 +02:00
}
// Set the default height for scrolling windows.
if !is_floating {
if let Some(change) = scrolling_height {
let ws = mon
.workspaces
.iter_mut()
.find(|ws| ws.has_window(&id))
.unwrap();
ws.set_window_height(Some(&id), change);
}
}
Some(&mon.output)
}
MonitorSet::NoOutputs { workspaces } => {
2024-12-26 09:37:38 +03:00
let (ws_idx, target) = match target {
AddWindowTarget::Auto => {
if workspaces.is_empty() {
workspaces.push(Workspace::new_no_outputs(
self.clock.clone(),
self.options.clone(),
));
}
2024-02-13 17:46:37 +04:00
2024-12-26 09:37:38 +03:00
(0, WorkspaceAddWindowTarget::Auto)
}
AddWindowTarget::Output(_) => panic!(),
AddWindowTarget::Workspace(ws_id) => {
let ws_idx = workspaces.iter().position(|ws| ws.id() == ws_id).unwrap();
(ws_idx, WorkspaceAddWindowTarget::Auto)
}
AddWindowTarget::NextTo(next_to) => {
if self
.interactive_move
.as_ref()
.and_then(|move_| {
if let InteractiveMoveState::Moving(move_) = move_ {
Some(move_)
} else {
None
}
})
.filter(|move_| next_to == move_.tile.window().id())
.is_some()
{
// The next_to window is being interactively moved.
(0, WorkspaceAddWindowTarget::Auto)
} else {
let ws_idx = workspaces
.iter()
.position(|ws| ws.has_window(next_to))
.unwrap();
(ws_idx, WorkspaceAddWindowTarget::NextTo(next_to))
}
}
};
let ws = &mut workspaces[ws_idx];
2024-02-13 17:46:37 +04:00
2024-12-26 09:37:38 +03:00
let tile = ws.make_tile(window);
ws.add_tile(
tile,
target,
activate,
scrolling_width,
2024-12-26 09:37:38 +03:00
is_full_width,
is_floating,
);
2024-02-13 17:46:37 +04:00
// Set the default height for scrolling windows.
if !is_floating {
if let Some(change) = scrolling_height {
ws.set_window_height(Some(&id), change);
}
}
2024-12-26 09:37:38 +03:00
None
}
2024-02-13 17:46:37 +04:00
}
}
2024-10-14 18:08:44 +03:00
pub fn remove_window(
&mut self,
window: &W::Id,
transaction: Transaction,
) -> Option<RemovedTile<W>> {
2024-07-15 15:51:48 +02:00
if let Some(state) = &self.interactive_move {
match state {
InteractiveMoveState::Starting { window_id, .. } => {
if window_id == window {
self.interactive_move_end(window);
}
}
InteractiveMoveState::Moving(move_) => {
if move_.tile.window().id() == window {
let Some(InteractiveMoveState::Moving(move_)) =
self.interactive_move.take()
else {
unreachable!()
};
// Unlock the view on the workspaces.
for ws in self.workspaces_mut() {
ws.dnd_scroll_gesture_end();
}
2024-07-15 15:51:48 +02:00
return Some(RemovedTile {
tile: move_.tile,
width: move_.width,
is_full_width: move_.is_full_width,
2024-11-29 21:11:02 +03:00
is_floating: false,
2024-07-15 15:51:48 +02:00
});
}
}
}
}
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
if ws.has_window(window) {
2024-10-14 18:08:44 +03:00
let removed = ws.remove_tile(window, transaction);
// Clean up empty workspaces that are not active and not last.
if !ws.has_windows_or_name()
&& idx != mon.active_workspace_idx
&& idx != mon.workspaces.len() - 1
&& mon.workspace_switch.is_none()
{
mon.workspaces.remove(idx);
if idx < mon.active_workspace_idx {
mon.active_workspace_idx -= 1;
}
}
// Special case handling when empty_workspace_above_first is set and all
// workspaces are empty.
if mon.options.empty_workspace_above_first
&& mon.workspaces.len() == 2
&& mon.workspace_switch.is_none()
{
assert!(!mon.workspaces[0].has_windows_or_name());
assert!(!mon.workspaces[1].has_windows_or_name());
mon.workspaces.remove(1);
mon.active_workspace_idx = 0;
}
2024-10-14 18:08:44 +03:00
return Some(removed);
}
}
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
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);
// Clean up empty workspaces.
if !ws.has_windows_or_name() {
workspaces.remove(idx);
}
2024-10-14 18:08:44 +03:00
return Some(removed);
}
}
}
}
2024-08-23 15:41:06 +03:00
None
}
pub fn descendants_added(&mut self, id: &W::Id) -> bool {
for ws in self.workspaces_mut() {
if ws.descendants_added(id) {
return true;
}
}
false
}
2024-05-10 16:58:53 +04:00
pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if move_.tile.window().id() == window {
// Do this before calling update_window() so it can get up-to-date info.
if let Some(serial) = serial {
move_.tile.window_mut().on_commit(serial);
}
2024-07-15 15:51:48 +02:00
move_.tile.update_window();
return;
}
}
match &mut self.monitor_set {
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;
}
}
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
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;
}
}
}
}
}
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()
2025-01-10 09:01:23 +03:00
.is_some_and(|name| name.eq_ignore_ascii_case(workspace_name))
2024-05-16 10:54:24 +04:00
})
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()
2025-01-10 09:01:23 +03:00
.is_some_and(|name| name.eq_ignore_ascii_case(workspace_name))
2024-05-16 10:54:24 +04:00
}) {
2024-05-11 22:40:30 +02:00
return Some((index, workspace));
}
}
}
None
}
pub fn find_workspace_by_ref(
&mut self,
reference: WorkspaceReference,
) -> Option<&mut Workspace<W>> {
if let WorkspaceReference::Index(index) = reference {
self.active_monitor().and_then(|m| {
let index = index.saturating_sub(1) as usize;
m.workspaces.get_mut(index)
})
} else {
self.workspaces_mut().find(|ws| match &reference {
WorkspaceReference::Name(ref_name) => ws
.name
.as_ref()
2025-01-10 09:01:23 +03:00
.is_some_and(|name| name.eq_ignore_ascii_case(ref_name)),
WorkspaceReference::Id(id) => ws.id().get() == *id,
WorkspaceReference::Index(_) => unreachable!(),
})
}
}
2024-05-11 22:40:30 +02:00
pub fn unname_workspace(&mut self, workspace_name: &str) {
self.unname_workspace_by_ref(WorkspaceReference::Name(workspace_name.into()));
}
pub fn unname_workspace_by_ref(&mut self, reference: WorkspaceReference) {
let id = self.find_workspace_by_ref(reference).map(|ws| ws.id());
if let Some(id) = id {
self.unname_workspace_by_id(id);
}
}
pub fn unname_workspace_by_id(&mut self, id: WorkspaceId) {
2024-05-11 22:40:30 +02:00
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
if mon.unname_workspace(id) {
2024-05-11 22:40:30 +02:00
if mon.workspace_switch.is_none() {
mon.clean_up_workspaces();
}
return;
}
}
}
MonitorSet::NoOutputs { workspaces } => {
for (idx, ws) in workspaces.iter_mut().enumerate() {
if ws.id() == id {
2024-05-11 22:40:30 +02:00
ws.unname();
// Clean up empty workspaces.
if !ws.has_windows() {
workspaces.remove(idx);
}
return;
}
}
}
}
}
pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(&W, Option<&Output>)> {
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().is_wl_surface(wl_surface) {
return Some((move_.tile.window(), Some(&move_.output)));
}
}
match &self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
for ws in &mon.workspaces {
if let Some(window) = ws.find_wl_surface(wl_surface) {
return Some((window, Some(&mon.output)));
}
}
}
}
MonitorSet::NoOutputs { workspaces } => {
for ws in workspaces {
if let Some(window) = ws.find_wl_surface(wl_surface) {
return Some((window, None));
}
}
}
}
None
}
pub fn find_window_and_output_mut(
&mut self,
wl_surface: &WlSurface,
) -> Option<(&mut W, Option<&Output>)> {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if move_.tile.window().is_wl_surface(wl_surface) {
return Some((move_.tile.window_mut(), Some(&move_.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) {
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) {
return Some((window, None));
}
}
}
}
None
}
2024-11-30 09:18:33 +03:00
/// Computes the window-geometry-relative target rect for popup unconstraining.
///
/// We will try to fit popups inside this rect.
pub fn popup_target_rect(&self, window: &W::Id) -> Rectangle<f64, Logical> {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
2024-11-30 09:18:33 +03:00
// Follow the scrolling layout logic and fit the popup horizontally within the
// window geometry.
let width = move_.tile.window_size().w;
let height = output_size(&move_.output).h;
2025-01-04 10:14:51 +03:00
let mut target = Rectangle::from_size(Size::from((width, height)));
2024-11-30 09:18:33 +03:00
// FIXME: ideally this shouldn't include the tile render offset, but the code
// duplication would be a bit annoying for this edge case.
target.loc.y -= move_.tile_render_location().y;
target.loc.y -= move_.tile.window_loc().y;
return target;
2023-12-19 20:56:00 +04:00
}
}
2024-11-30 09:18:33 +03:00
self.workspaces()
.find_map(|(_, _, ws)| ws.popup_target_rect(window))
.unwrap()
2023-12-19 20:56:00 +04:00
}
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");
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
panic!()
};
for mon in monitors {
if &mon.output == output {
for ws in &mut mon.workspaces {
2024-11-30 09:18:33 +03:00
ws.update_output_size();
}
2023-09-26 20:12:04 +04:00
break;
}
}
}
pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return 0.;
}
}
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.
}
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.
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return true;
}
}
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
}
pub fn activate_window(&mut self, window: &W::Id) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return;
}
}
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
return;
};
for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
2024-12-08 09:15:10 +03:00
if ws.activate_window(window) {
*active_monitor_idx = monitor_idx;
// 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 => {}
_ => mon.switch_workspace(workspace_idx),
}
2024-12-08 09:15:10 +03:00
return;
}
}
}
}
2024-11-29 21:11:02 +03:00
pub fn activate_window_without_raising(&mut self, window: &W::Id) {
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return;
}
}
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
return;
};
for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
if ws.activate_window_without_raising(window) {
*active_monitor_idx = monitor_idx;
// 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 => {}
_ => mon.switch_workspace(workspace_idx),
}
return;
}
}
}
}
pub fn active_output(&self) -> Option<&Output> {
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &self.monitor_set
else {
return None;
};
Some(&monitors[*active_monitor_idx].output)
}
pub fn active_workspace(&self) -> Option<&Workspace<W>> {
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &self.monitor_set
else {
return None;
};
let mon = &monitors[*active_monitor_idx];
Some(&mon.workspaces[mon.active_workspace_idx])
}
pub fn active_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-08-16 10:59:34 +04:00
pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
2023-08-16 10:59:34 +04:00
panic!()
};
2024-07-15 15:51:48 +02:00
let moving_window = self
.interactive_move
.as_ref()
.and_then(|x| x.moving())
.filter(|move_| move_.output == *output)
.map(|move_| move_.tile.window())
.into_iter();
2023-08-16 10:59:34 +04:00
let mon = monitors.iter().find(|mon| &mon.output == output).unwrap();
2024-07-15 15:51:48 +02:00
let mon_windows = mon.workspaces.iter().flat_map(|ws| ws.windows());
moving_window.chain(mon_windows)
2023-08-16 10:59:34 +04:00
}
2025-02-05 10:08:56 +03:00
pub fn windows_for_output_mut(&mut self, output: &Output) -> impl Iterator<Item = &mut W> + '_ {
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
panic!()
};
let moving_window = self
.interactive_move
.as_mut()
.and_then(|x| x.moving_mut())
.filter(|move_| move_.output == *output)
.map(|move_| move_.tile.window_mut())
.into_iter();
let mon = monitors
.iter_mut()
.find(|mon| &mon.output == output)
.unwrap();
let mon_windows = mon.workspaces.iter_mut().flat_map(|ws| ws.windows_mut());
moving_window.chain(mon_windows)
}
2024-07-15 15:51:48 +02:00
pub fn with_windows(&self, mut f: impl FnMut(&W, Option<&Output>, Option<WorkspaceId>)) {
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
f(move_.tile.window(), Some(&move_.output), None);
}
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-07-15 15:51:48 +02:00
f(win, Some(&mon.output), Some(ws.id()));
2024-01-29 19:34:12 +04:00
}
}
}
}
MonitorSet::NoOutputs { workspaces } => {
for ws in workspaces {
for win in ws.windows() {
2024-07-15 15:51:48 +02:00
f(win, None, Some(ws.id()));
2024-01-29 19:34:12 +04:00
}
}
}
}
}
pub fn with_windows_mut(&mut self, mut f: impl FnMut(&mut W, Option<&Output>)) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
f(move_.tile.window_mut(), Some(&move_.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);
}
}
}
}
}
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
return None;
};
Some(&mut monitors[*active_monitor_idx])
}
pub fn 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])
}
pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
return None;
};
2024-07-15 15:51:48 +02:00
monitors.iter().find(|mon| &mon.output == output)
}
pub fn monitor_for_output_mut(&mut self, output: &Output) -> Option<&mut Monitor<W>> {
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
return None;
};
monitors.iter_mut().find(|mon| &mon.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()
2025-01-10 09:01:23 +03:00
.is_some_and(|name| name.eq_ignore_ascii_case(workspace_name))
2024-05-16 10:54:24 +04:00
})
2024-05-11 22:40:30 +02:00
})
}
2023-08-15 12:49:26 +04:00
pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ {
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)
}
pub fn move_left(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.move_left();
}
pub fn move_right(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.move_right();
}
2023-12-29 08:01:02 +04:00
pub fn move_column_to_first(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2023-12-29 08:01:02 +04:00
return;
};
workspace.move_column_to_first();
2023-12-29 08:01:02 +04:00
}
pub fn move_column_to_last(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2023-12-29 08:01:02 +04:00
return;
};
workspace.move_column_to_last();
2023-12-29 08:01:02 +04:00
}
pub fn move_column_left_or_to_output(&mut self, output: &Output) -> bool {
if let Some(workspace) = self.active_workspace_mut() {
if workspace.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(workspace) = self.active_workspace_mut() {
if workspace.move_right() {
return false;
}
}
self.move_column_to_output(output);
true
}
2025-03-13 03:09:06 +01:00
pub fn move_column_to_index(&mut self, index: usize) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.move_column_to_index(index);
}
pub fn move_down(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.move_down();
}
pub fn move_up(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.move_up();
}
pub fn move_down_or_to_workspace_down(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
};
monitor.move_down_or_to_workspace_down();
}
pub fn move_up_or_to_workspace_up(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
};
monitor.move_up_or_to_workspace_up();
}
pub fn consume_or_expel_window_left(&mut self, window: Option<&W::Id>) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
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 {
return;
};
workspace.consume_or_expel_window_left(window);
}
pub fn consume_or_expel_window_right(&mut self, window: Option<&W::Id>) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
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 {
return;
};
workspace.consume_or_expel_window_right(window);
}
pub fn focus_left(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_left();
}
pub fn focus_right(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_right();
}
2023-12-29 07:51:14 +04:00
pub fn focus_column_first(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2023-12-29 07:51:14 +04:00
return;
};
workspace.focus_column_first();
2023-12-29 07:51:14 +04:00
}
pub fn focus_column_last(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2023-12-29 07:51:14 +04:00
return;
};
workspace.focus_column_last();
2023-12-29 07:51:14 +04:00
}
pub fn focus_column_right_or_first(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_column_right_or_first();
}
pub fn focus_column_left_or_last(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_column_left_or_last();
}
2025-03-13 03:05:55 +01:00
pub fn focus_column(&mut self, index: usize) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_column(index);
}
pub fn focus_window_up_or_output(&mut self, output: &Output) -> bool {
if let Some(workspace) = self.active_workspace_mut() {
if workspace.focus_up() {
2024-11-30 09:18:33 +03:00
return false;
}
}
self.focus_output(output);
true
}
pub fn focus_window_down_or_output(&mut self, output: &Output) -> bool {
if let Some(workspace) = self.active_workspace_mut() {
if workspace.focus_down() {
2024-11-30 09:18:33 +03:00
return false;
}
}
self.focus_output(output);
true
}
pub fn focus_column_left_or_output(&mut self, output: &Output) -> bool {
if let Some(workspace) = self.active_workspace_mut() {
if workspace.focus_left() {
return false;
}
}
self.focus_output(output);
true
}
pub fn focus_column_right_or_output(&mut self, output: &Output) -> bool {
if let Some(workspace) = self.active_workspace_mut() {
if workspace.focus_right() {
return false;
}
}
self.focus_output(output);
true
}
pub fn focus_window_in_column(&mut self, index: u8) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_window_in_column(index);
}
pub fn focus_down(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_down();
}
pub fn focus_up(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_up();
}
pub fn focus_down_or_left(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_down_or_left();
}
pub fn focus_down_or_right(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_down_or_right();
}
pub fn focus_up_or_left(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_up_or_left();
}
pub fn focus_up_or_right(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_up_or_right();
}
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();
}
pub fn focus_window_top(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_window_top();
}
pub fn focus_window_bottom(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_window_bottom();
}
pub fn focus_window_down_or_top(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_window_down_or_top();
}
pub fn focus_window_up_or_bottom(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_window_up_or_bottom();
}
pub fn move_to_workspace_up(&mut self) {
2023-08-14 16:19:43 +04:00
let Some(monitor) = self.active_monitor() else {
return;
};
2023-08-14 16:19:43 +04:00
monitor.move_to_workspace_up();
}
pub fn move_to_workspace_down(&mut self) {
2023-08-14 16:19:43 +04:00
let Some(monitor) = self.active_monitor() else {
return;
};
2023-08-14 16:19:43 +04:00
monitor.move_to_workspace_down();
}
pub fn move_to_workspace(
&mut self,
window: Option<&W::Id>,
idx: usize,
activate: ActivateWindow,
) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
let monitor = if let Some(window) = window {
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors
.iter_mut()
.find(|mon| mon.has_window(window))
.unwrap(),
MonitorSet::NoOutputs { .. } => {
return;
}
}
} else {
let Some(monitor) = self.active_monitor() else {
return;
};
monitor
2023-09-16 12:14:02 +04:00
};
monitor.move_to_workspace(window, idx, activate);
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) {
self.move_column_to_output(output);
2024-05-11 22:40:30 +02:00
self.focus_output(output);
self.move_column_to_workspace(idx);
}
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();
}
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();
}
pub fn switch_workspace(&mut self, idx: usize) {
2023-09-16 12:14:02 +04:00
let Some(monitor) = self.active_monitor() else {
return;
};
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();
}
pub fn consume_into_column(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.consume_into_column();
}
pub fn expel_from_column(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.expel_from_column();
}
2025-01-09 08:29:36 +00:00
pub fn swap_window_in_direction(&mut self, direction: ScrollDirection) {
let Some(workspace) = self.active_workspace_mut() else {
2025-01-09 08:29:36 +00:00
return;
};
workspace.swap_window_in_direction(direction);
2025-01-09 08:29:36 +00:00
}
2025-02-01 10:46:52 +03:00
pub fn toggle_column_tabbed_display(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2025-02-01 10:46:52 +03:00
return;
};
workspace.toggle_column_tabbed_display();
2025-02-01 10:46:52 +03:00
}
2025-02-06 08:42:09 +03:00
pub fn set_column_display(&mut self, display: ColumnDisplay) {
let Some(workspace) = self.active_workspace_mut() else {
2025-02-06 08:42:09 +03:00
return;
};
workspace.set_column_display(display);
2025-02-06 08:42:09 +03:00
}
pub fn center_column(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.center_column();
}
2024-12-29 22:44:19 +03:00
pub fn center_window(&mut self, id: Option<&W::Id>) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if id.is_none() || id == Some(move_.tile.window().id()) {
return;
}
}
2024-12-29 22:44:19 +03:00
let workspace = if let Some(id) = id {
Some(self.workspaces_mut().find(|ws| ws.has_window(id)).unwrap())
} else {
self.active_workspace_mut()
};
let Some(workspace) = workspace else {
return;
};
workspace.center_window(id);
}
pub fn focus(&self) -> Option<&W> {
2024-11-30 09:18:33 +03:00
self.focus_with_output().map(|(win, _out)| win)
}
pub fn focus_with_output(&self) -> Option<(&W, &Output)> {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
2024-11-30 09:18:33 +03:00
return Some((move_.tile.window(), &move_.output));
2024-07-15 15:51:48 +02:00
}
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &self.monitor_set
else {
return None;
};
2024-11-30 09:18:33 +03:00
let mon = &monitors[*active_monitor_idx];
mon.active_window().map(|win| (win, &mon.output))
2023-08-14 15:54:11 +04:00
}
2025-02-10 08:03:39 +03:00
/// Returns the window under the cursor and the hit type.
pub fn window_under(
&self,
output: &Output,
pos_within_output: Point<f64, Logical>,
2025-02-10 08:03:39 +03:00
) -> Option<(&W, HitType)> {
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
return None;
};
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
let tile_pos = move_.tile_render_location();
return HitType::hit_tile(&move_.tile, tile_pos, pos_within_output);
2024-07-15 15:51:48 +02:00
};
let mon = monitors.iter().find(|mon| &mon.output == output)?;
mon.window_under(pos_within_output)
}
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)]
fn verify_invariants(&self) {
2024-03-19 14:27:52 +00:00
use std::collections::HashSet;
2024-07-15 15:51:48 +02:00
use approx::assert_abs_diff_eq;
use crate::layout::monitor::WorkspaceSwitch;
2024-07-15 15:51:48 +02:00
let mut move_win_id = None;
if let Some(state) = &self.interactive_move {
match state {
InteractiveMoveState::Starting {
window_id,
pointer_delta: _,
pointer_ratio_within_window: _,
} => {
assert!(
self.has_window(window_id),
"interactive move must be on an existing window"
);
move_win_id = Some(window_id.clone());
}
InteractiveMoveState::Moving(move_) => {
assert_eq!(self.clock, move_.tile.clock);
assert!(!move_.tile.window().is_pending_fullscreen());
2025-03-17 07:45:26 +03:00
move_.tile.verify_invariants();
2024-07-15 15:51:48 +02:00
let scale = move_.output.current_scale().fractional_scale();
let options = Options::clone(&self.options).adjusted_for_scale(scale);
assert_eq!(
&*move_.tile.options, &options,
"interactive moved tile options must be \
base options adjusted for output scale"
);
let tile_pos = move_.tile_render_location();
let rounded_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale);
// Tile position must be rounded to physical pixels.
assert_abs_diff_eq!(tile_pos.x, rounded_pos.x, epsilon = 1e-5);
assert_abs_diff_eq!(tile_pos.y, rounded_pos.y, epsilon = 1e-5);
if let Some(alpha) = &move_.tile.alpha_animation {
if move_.is_floating {
assert_eq!(
alpha.anim.to(),
1.,
"interactively moved floating tile can animate alpha only to 1"
);
assert!(
!alpha.hold_after_done,
"interactively moved floating tile \
cannot have held alpha animation"
);
} else {
assert_ne!(
alpha.anim.to(),
1.,
"interactively moved scrolling tile must animate alpha to not 1"
);
assert!(
alpha.hold_after_done,
"interactively moved scrolling tile \
must have held alpha animation"
);
}
}
2024-07-15 15:51:48 +02:00
}
}
}
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
let (monitors, &primary_idx, &active_monitor_idx) = match &self.monitor_set {
MonitorSet::Normal {
monitors,
primary_idx,
active_monitor_idx,
} => (monitors, primary_idx, active_monitor_idx),
MonitorSet::NoOutputs { workspaces } => {
for workspace in workspaces {
assert!(
workspace.has_windows_or_name(),
2024-05-11 22:40:30 +02:00
"with no outputs there cannot be empty unnamed workspaces"
);
assert_eq!(self.clock, workspace.clock);
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"
);
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
}
2024-07-15 15:51:48 +02:00
workspace.verify_invariants(move_win_id.as_ref());
}
return;
}
};
2023-09-26 20:35:08 +04:00
assert!(primary_idx < monitors.len());
assert!(active_monitor_idx < monitors.len());
let mut saw_view_offset_gesture = false;
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-09-26 20:35:08 +04:00
assert!(monitor.active_workspace_idx < monitor.workspaces.len());
assert_eq!(self.clock, monitor.clock);
assert_eq!(
monitor.options, self.options,
"monitor options must be synchronized with layout"
);
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());
}
if idx == primary_idx {
for ws in &monitor.workspaces {
if ws.original_output.matches(&monitor.output) {
// This is the primary monitor's own workspace.
continue;
}
let own_monitor_exists = monitors
.iter()
.any(|m| ws.original_output.matches(&m.output));
assert!(
!own_monitor_exists,
"primary monitor cannot have workspaces for which their own monitor exists"
);
}
} else {
assert!(
monitor
.workspaces
.iter()
.any(|workspace| workspace.original_output.matches(&monitor.output)),
"secondary monitor must not have any non-own workspaces"
);
}
assert!(
2024-11-30 09:18:33 +03:00
!monitor.workspaces.last().unwrap().has_windows(),
"monitor must have an empty workspace in the end"
);
if monitor.options.empty_workspace_above_first {
assert!(
2024-11-30 09:18:33 +03:00
!monitor.workspaces.first().unwrap().has_windows(),
"first workspace must be empty when empty_workspace_above_first is set"
)
}
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"
);
if monitor.options.empty_workspace_above_first {
assert!(
monitor.workspaces.first().unwrap().name.is_none(),
"first workspace must be unnamed when empty_workspace_above_first is set"
)
}
if monitor.options.empty_workspace_above_first {
assert!(
monitor.workspaces.len() != 2,
"if empty_workspace_above_first is set there must be just 1 or 3+ workspaces"
)
}
2024-05-11 22:40:30 +02:00
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 empty_workspace_above_first is set then the first workspace
// will be empty too.
let pre_skip = if monitor.options.empty_workspace_above_first {
1
} else {
0
};
2023-10-14 20:39:44 +04:00
if monitor.workspace_switch.is_none() {
for (idx, ws) in monitor
.workspaces
.iter()
.enumerate()
.skip(pre_skip)
.rev()
// skip last
.skip(1)
{
2023-10-14 20:39:44 +04:00
if idx != monitor.active_workspace_idx {
assert!(
ws.has_windows_or_name(),
2024-05-11 22:40:30 +02:00
"non-active workspace can't be empty and unnamed except the last one"
2023-10-14 20:39:44 +04:00
);
}
}
}
// FIXME: verify that primary doesn't have any workspaces for which their own monitor
// exists.
for workspace in &monitor.workspaces {
assert_eq!(self.clock, workspace.clock);
assert_eq!(
2024-06-17 09:16:28 +03:00
workspace.base_options, self.options,
"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
}
2024-07-15 15:51:48 +02:00
workspace.verify_invariants(move_win_id.as_ref());
let has_view_offset_gesture = workspace.scrolling().view_offset().is_gesture();
2025-02-15 13:11:34 +03:00
if self.dnd.is_some() || self.interactive_move.is_some() {
// We'd like to check that all workspaces have the gesture here, furthermore we
// want to check that they have the gesture only if the interactive move
// targets the scrolling layout. However, we cannot do that because we start
// and stop the gesture lazily. Otherwise the gesture code would pollute a lot
// of places like adding new workspaces, implicitly moving windows between
// floating and tiling on fullscreen, etc.
//
// assert!(
// has_view_offset_gesture,
// "during an interactive move in the scrolling layout, \
// all workspaces should be in a view offset gesture"
// );
} else if saw_view_offset_gesture {
assert!(
!has_view_offset_gesture,
"only one workspace can have an ongoing view offset gesture"
);
}
saw_view_offset_gesture = has_view_offset_gesture;
}
}
}
2023-08-14 17:40:15 +04:00
pub fn advance_animations(&mut self) {
2023-10-11 14:53:53 +04:00
let _span = tracy_client::span!("Layout::advance_animations");
2025-02-15 13:11:34 +03:00
let mut dnd_scroll = None;
if let Some(dnd) = &self.dnd {
dnd_scroll = Some((dnd.output.clone(), dnd.pointer_pos_within_output));
}
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
move_.tile.advance_animations();
2025-02-15 13:11:34 +03:00
if !move_.is_floating && dnd_scroll.is_none() {
dnd_scroll = Some((move_.output.clone(), move_.pointer_pos_within_output));
}
}
// Scroll the view if needed.
if let Some((output, pos_within_output)) = dnd_scroll {
if let Some(mon) = self.monitor_for_output_mut(&output) {
if let Some((ws, offset)) = mon.workspace_under(pos_within_output) {
let ws_id = ws.id();
let ws = mon
.workspaces
.iter_mut()
.find(|ws| ws.id() == ws_id)
.unwrap();
ws.dnd_scroll_gesture_scroll(pos_within_output - offset);
}
}
2024-07-15 15:51:48 +02:00
}
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
mon.advance_animations();
2023-08-14 17:40:15 +04:00
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
2023-08-14 17:40:15 +04:00
for ws in workspaces {
ws.advance_animations();
2023-08-14 17:40:15 +04:00
}
}
}
}
2023-08-14 18:29:50 +04:00
2024-07-15 15:51:48 +02:00
pub fn are_animations_ongoing(&self, output: Option<&Output>) -> bool {
2025-02-15 13:11:34 +03:00
// Keep advancing animations if we might need to scroll the view.
if let Some(dnd) = &self.dnd {
if output.map_or(true, |output| *output == dnd.output) {
return true;
}
}
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if output.map_or(true, |output| *output == move_.output) {
if move_.tile.are_animations_ongoing() {
return true;
}
// Keep advancing animations if we might need to scroll the view.
if !move_.is_floating {
return true;
}
2024-07-15 15:51:48 +02:00
}
}
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
return false;
};
for mon in monitors {
2025-01-10 09:01:23 +03:00
if output.is_some_and(|output| mon.output != *output) {
2024-07-15 15:51:48 +02:00
continue;
}
if mon.are_animations_ongoing() {
return true;
}
}
false
}
2024-08-23 12:48:05 +03:00
pub fn update_render_elements(&mut self, output: Option<&Output>) {
let _span = tracy_client::span!("Layout::update_render_elements");
self.update_render_elements_time = self.clock.now();
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if output.map_or(true, |output| move_.output == *output) {
let pos_within_output = move_.tile_render_location();
2025-01-04 10:14:51 +03:00
let view_rect =
Rectangle::new(pos_within_output.upscale(-1.), output_size(&move_.output));
move_.tile.update_render_elements(true, view_rect);
2024-07-15 15:51:48 +02:00
}
}
self.update_insert_hint(output);
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-07-15 15:51:48 +02:00
let is_active = self.is_active
&& idx == *active_monitor_idx
&& !matches!(self.interactive_move, Some(InteractiveMoveState::Moving(_)));
mon.update_render_elements(is_active);
}
}
}
pub fn update_shaders(&mut self) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
move_.tile.update_shaders();
}
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
2025-04-17 10:40:48 +03:00
mon.update_shaders();
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces {
ws.update_shaders();
}
}
}
}
2024-07-15 15:51:48 +02:00
fn update_insert_hint(&mut self, output: Option<&Output>) {
let _span = tracy_client::span!("Layout::update_insert_hint");
let _span = tracy_client::span!("Layout::update_insert_hint::clear");
for ws in self.workspaces_mut() {
ws.clear_insert_hint();
}
if !matches!(self.interactive_move, Some(InteractiveMoveState::Moving(_))) {
return;
}
let Some(InteractiveMoveState::Moving(move_)) = self.interactive_move.take() else {
unreachable!()
};
2025-01-10 09:01:23 +03:00
if output.is_some_and(|out| &move_.output != out) {
2024-07-15 15:51:48 +02:00
self.interactive_move = Some(InteractiveMoveState::Moving(move_));
return;
}
// No insert hint when targeting floating.
if move_.is_floating {
self.interactive_move = Some(InteractiveMoveState::Moving(move_));
return;
}
2024-07-15 15:51:48 +02:00
let _span = tracy_client::span!("Layout::update_insert_hint::update");
if let Some(mon) = self.monitor_for_output_mut(&move_.output) {
if let Some((ws, offset)) = mon.workspace_under(move_.pointer_pos_within_output) {
let ws_id = ws.id();
let ws = mon
.workspaces
.iter_mut()
.find(|ws| ws.id() == ws_id)
.unwrap();
let position = ws.get_insert_position(move_.pointer_pos_within_output - offset);
2024-11-02 09:42:46 +03:00
let rules = move_.tile.window().rules();
let border_width = move_.tile.effective_border_width().unwrap_or(0.);
let corner_radius = rules
.geometry_corner_radius
.map_or(CornerRadius::default(), |radius| {
radius.expanded_by(border_width as f32)
});
2024-07-15 15:51:48 +02:00
ws.set_insert_hint(InsertHint {
position,
2024-11-02 09:42:46 +03:00
corner_radius,
2024-07-15 15:51:48 +02:00
});
}
}
self.interactive_move = Some(InteractiveMoveState::Moving(move_));
}
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 clock = self.clock.clone();
2024-05-11 22:40:30 +02:00
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()
.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 mut insert_idx = 0;
if mon.options.empty_workspace_above_first {
// need to insert new empty workspace on top
mon.add_workspace_top();
insert_idx += 1;
}
2024-05-11 22:40:30 +02:00
let ws = Workspace::new_with_config(
mon.output.clone(),
Some(ws_config.clone()),
clock,
2024-05-11 22:40:30 +02:00
options,
);
mon.workspaces.insert(insert_idx, ws);
2024-05-11 22:40:30 +02:00
mon.active_workspace_idx += 1;
2024-05-11 22:40:30 +02:00
mon.workspace_switch = None;
mon.clean_up_workspaces();
}
MonitorSet::NoOutputs { workspaces } => {
let ws =
Workspace::new_with_config_no_outputs(Some(ws_config.clone()), clock, options);
2024-05-11 22:40:30 +02:00
workspaces.insert(0, ws);
}
}
}
2023-09-26 13:09:33 +04:00
pub fn update_config(&mut self, config: &Config) {
2024-11-26 21:23:20 +03:00
self.update_options(Options::from_config(config));
}
fn update_options(&mut self, options: Options) {
let options = Rc::new(options);
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
2024-12-26 08:54:56 +03:00
let view_size = output_size(&move_.output);
2024-07-15 15:51:48 +02:00
let scale = move_.output.current_scale().fractional_scale();
move_.tile.update_config(
2024-12-26 08:54:56 +03:00
view_size,
scale,
Rc::new(Options::clone(&options).adjusted_for_scale(scale)),
);
2024-07-15 15:51:48 +02:00
}
match &mut self.monitor_set {
2023-09-26 13:09:33 +04:00
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
mon.update_config(options.clone());
2023-09-26 13:09:33 +04:00
}
}
MonitorSet::NoOutputs { workspaces } => {
2023-09-26 13:09:33 +04:00
for ws in workspaces {
ws.update_config(options.clone());
2023-09-26 13:09:33 +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(workspace) = self.active_workspace_mut() else {
2023-08-14 18:29:50 +04:00
return;
};
workspace.toggle_width();
2023-08-14 18:29:50 +04:00
}
2023-08-14 18:34:39 +04:00
2024-12-30 09:05:35 +03:00
pub fn toggle_window_width(&mut self, window: Option<&W::Id>) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
return;
}
}
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 {
return;
};
workspace.toggle_window_width(window);
}
2024-09-12 11:53:10 +03:00
pub fn toggle_window_height(&mut self, window: Option<&W::Id>) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
2024-09-12 11:53:10 +03:00
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(workspace) = self.active_workspace_mut() else {
2023-08-14 18:34:39 +04:00
return;
};
workspace.toggle_full_width();
2023-08-14 18:34:39 +04:00
}
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(workspace) = self.active_workspace_mut() else {
2023-10-03 11:38:42 +04:00
return;
};
workspace.set_column_width(change);
2023-10-03 11:38:42 +04:00
}
2024-12-28 10:12:50 +03:00
pub fn set_window_width(&mut self, window: Option<&W::Id>, change: SizeChange) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
return;
}
}
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 {
return;
};
workspace.set_window_width(window, change);
}
pub fn set_window_height(&mut self, window: Option<&W::Id>, change: SizeChange) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
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;
};
workspace.set_window_height(window, change);
2023-11-08 11:17:06 +04:00
}
pub fn reset_window_height(&mut self, window: Option<&W::Id>) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
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;
};
workspace.reset_window_height(window);
2024-05-11 09:33:23 +04:00
}
2025-02-17 21:22:10 +03:00
pub fn expand_column_to_available_width(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
2025-02-17 21:22:10 +03:00
return;
};
workspace.expand_column_to_available_width();
2025-02-17 21:22:10 +03:00
}
2024-11-29 21:11:02 +03:00
pub fn toggle_window_floating(&mut self, window: Option<&W::Id>) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
move_.is_floating = !move_.is_floating;
// When going to floating, restore the floating window size.
if move_.is_floating {
2024-12-25 16:43:15 +03:00
let floating_size = move_.tile.floating_window_size;
let win = move_.tile.window_mut();
let mut size =
floating_size.unwrap_or_else(|| win.expected_size().unwrap_or_default());
// Apply min/max size window rules. If requesting a concrete size, apply
// completely; if requesting (0, 0), apply only when min/max results in a fixed
// size.
let min_size = win.min_size();
let max_size = win.max_size();
size.w = ensure_min_max_size_maybe_zero(size.w, min_size.w, max_size.w);
size.h = ensure_min_max_size_maybe_zero(size.h, min_size.h, max_size.h);
win.request_size_once(size, true);
// Animate the tile back to opaque.
move_.tile.animate_alpha(
INTERACTIVE_MOVE_ALPHA,
1.,
self.options.animations.window_movement.0,
);
} else {
// Animate the tile back to semitransparent.
move_.tile.animate_alpha(
1.,
INTERACTIVE_MOVE_ALPHA,
self.options.animations.window_movement.0,
);
move_.tile.hold_alpha_animation_after_done();
}
2024-11-29 21:11:02 +03:00
return;
}
}
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 {
return;
};
workspace.toggle_window_floating(window);
}
2024-12-28 10:13:30 +03:00
pub fn set_window_floating(&mut self, window: Option<&W::Id>, floating: bool) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
if move_.is_floating != floating {
self.toggle_window_floating(window);
}
return;
}
}
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 {
return;
};
workspace.set_window_floating(window, floating);
}
2024-12-28 10:14:02 +03:00
pub fn focus_floating(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_floating();
}
pub fn focus_tiling(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.focus_tiling();
}
2024-11-29 21:11:02 +03:00
pub fn switch_focus_floating_tiling(&mut self) {
let Some(workspace) = self.active_workspace_mut() else {
return;
};
workspace.switch_focus_floating_tiling();
}
2024-12-28 11:40:16 +03:00
pub fn move_floating_window(
&mut self,
id: Option<&W::Id>,
x: PositionChange,
y: PositionChange,
animate: bool,
2024-12-28 11:40:16 +03:00
) {
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if id.is_none() || id == Some(move_.tile.window().id()) {
return;
}
}
let workspace = if let Some(id) = id {
Some(self.workspaces_mut().find(|ws| ws.has_window(id)).unwrap())
} else {
self.active_workspace_mut()
};
let Some(workspace) = workspace else {
return;
};
workspace.move_floating_window(id, x, y, animate);
2024-12-28 11:40:16 +03: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,
..
} = &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;
}
}
}
}
pub fn move_to_output(
&mut self,
window: Option<&W::Id>,
output: &Output,
target_ws_idx: Option<usize>,
activate: ActivateWindow,
) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if window.is_none() || window == Some(move_.tile.window().id()) {
2024-07-15 15:51:48 +02:00
return;
}
}
2023-08-16 08:03:20 +04:00
if let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
2023-08-16 08:03:20 +04:00
{
let new_idx = monitors
.iter()
.position(|mon| &mon.output == output)
.unwrap();
2024-11-30 09:18:33 +03:00
let (mon_idx, ws_idx) = if let Some(window) = window {
monitors
.iter()
.enumerate()
.find_map(|(mon_idx, mon)| {
2024-11-30 09:18:33 +03:00
mon.workspaces
.iter()
.position(|ws| ws.has_window(window))
.map(|ws_idx| (mon_idx, ws_idx))
})
.unwrap()
} else {
let mon_idx = *active_monitor_idx;
let mon = &monitors[mon_idx];
2024-11-30 09:18:33 +03:00
(mon_idx, mon.active_workspace_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;
}
let mon = &monitors[new_idx];
if mon.workspaces.len() <= workspace_idx {
return;
}
let ws_id = mon.workspaces[workspace_idx].id();
let mon = &mut monitors[mon_idx];
let activate = activate.map_smart(|| {
window.map_or(true, |win| {
mon_idx == *active_monitor_idx
&& mon.active_window().map(|win| win.id()) == Some(win)
})
2024-11-30 09:18:33 +03:00
});
2024-12-26 09:37:38 +03:00
let activate = if activate {
ActivateWindow::Yes
} else {
ActivateWindow::No
};
2024-11-30 09:18:33 +03:00
let ws = &mut mon.workspaces[ws_idx];
let transaction = Transaction::new();
2024-12-24 11:34:20 +03:00
let mut removed = if let Some(window) = window {
2024-11-30 09:18:33 +03:00
ws.remove_tile(window, transaction)
} else if let Some(removed) = ws.remove_active_tile(transaction) {
removed
} else {
return;
};
2023-08-16 08:03:20 +04:00
2024-12-24 11:34:20 +03:00
removed.tile.stop_move_animations();
let mon = &mut monitors[new_idx];
2024-12-26 09:37:38 +03:00
mon.add_tile(
removed.tile,
MonitorAddWindowTarget::Workspace {
id: ws_id,
column_idx: None,
},
activate,
removed.width,
removed.is_full_width,
removed.is_floating,
);
if activate.map_smart(|| false) {
2024-12-24 11:34:20 +03:00
*active_monitor_idx = new_idx;
}
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();
2024-11-29 21:11:02 +03:00
if ws.floating_is_active() {
self.move_to_output(None, output, None, ActivateWindow::Smart);
2024-11-29 21:11:02 +03:00
return;
}
2024-11-30 09:18:33 +03:00
let Some(column) = ws.remove_active_column() else {
2024-01-15 10:36:59 +04:00
return;
2024-11-30 09:18:33 +03:00
};
2024-01-15 10:36:59 +04:00
let workspace_idx = monitors[new_idx].active_workspace_idx;
self.add_column_by_idx(new_idx, workspace_idx, column, true);
}
}
pub fn move_workspace_to_output(&mut self, output: &Output) -> bool {
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
return false;
};
let current = &mut monitors[*active_monitor_idx];
// Do not do anything if the output is already correct
if &current.output == output {
// Just update the original output since this is an explicit movement action.
current.active_workspace().original_output = OutputId::new(output);
return false;
}
if current.active_workspace_idx == current.workspaces.len() - 1 {
// Insert a new empty workspace.
current.add_workspace_bottom();
}
if current.options.empty_workspace_above_first && current.active_workspace_idx == 0 {
current.add_workspace_top();
}
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-03-19 14:27:52 +00:00
target.previous_workspace_id = Some(target.workspaces[target.active_workspace_idx].id());
if target.options.empty_workspace_above_first && target.workspaces.len() == 1 {
2025-01-29 23:43:25 +02:00
// Insert a new empty workspace on top to prepare for insertion of new workspace.
target.add_workspace_top();
}
// 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;
target.workspace_switch = None;
target.clean_up_workspaces();
*active_monitor_idx = target_idx;
true
}
// FIXME: accept workspace by id and deduplicate logic with move_workspace_to_output()
pub fn move_workspace_to_output_by_id(
&mut self,
old_idx: usize,
old_output: Option<Output>,
new_output: Output,
) -> bool {
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
return false;
};
let current_idx = if let Some(old_output) = old_output {
monitors
.iter()
.position(|mon| mon.output == old_output)
.unwrap()
} else {
*active_monitor_idx
};
let target_idx = monitors
.iter()
.position(|mon| mon.output == new_output)
.unwrap();
let current = &mut monitors[current_idx];
if current.workspaces.len() <= old_idx {
return false;
}
// Do not do anything if the output is already correct
if current_idx == target_idx {
// Just update the original output since this is an explicit movement action.
current.workspaces[old_idx].original_output = OutputId::new(&current.output);
return false;
}
let current_active_ws_idx = current.active_workspace_idx;
if old_idx == current.workspaces.len() - 1 {
// Insert a new empty workspace.
current.add_workspace_bottom();
}
let mut ws = current.workspaces.remove(old_idx);
if current.options.empty_workspace_above_first && old_idx == 0 {
current.add_workspace_top();
}
if old_idx < current.active_workspace_idx {
current.active_workspace_idx -= 1;
}
current.workspace_switch = None;
current.clean_up_workspaces();
ws.set_output(Some(new_output.clone()));
ws.original_output = OutputId::new(&new_output);
let target = &mut monitors[target_idx];
target.previous_workspace_id = Some(target.workspaces[target.active_workspace_idx].id());
if target.options.empty_workspace_above_first && target.workspaces.len() == 1 {
2025-01-29 23:43:25 +02:00
// Insert a new empty workspace on top to prepare for insertion of new workspace.
target.add_workspace_top();
}
// 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);
// Only switch active monitor if the workspace moved was the currently focused one on the
// current monitor
let res = if current_idx == *active_monitor_idx && old_idx == current_active_ws_idx {
*active_monitor_idx = target_idx;
target.active_workspace_idx = target_ws_idx;
true
} else {
if target_ws_idx <= target.active_workspace_idx {
target.active_workspace_idx += 1;
}
false
};
target.workspace_switch = None;
target.clean_up_workspaces();
res
}
2025-03-17 14:56:29 +03:00
pub fn set_fullscreen(&mut self, id: &W::Id, is_fullscreen: bool) {
2025-03-17 14:56:29 +03:00
// Check if this is a request to unset the windowed fullscreen state.
if !is_fullscreen {
let mut handled = false;
self.with_windows_mut(|window, _| {
if window.id() == id && window.is_pending_windowed_fullscreen() {
window.request_windowed_fullscreen(false);
handled = true;
}
});
if handled {
return;
}
}
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
2025-03-17 14:56:29 +03:00
if move_.tile.window().id() == id {
2024-07-15 15:51:48 +02:00
return;
}
}
for ws in self.workspaces_mut() {
2025-03-17 14:56:29 +03:00
if ws.has_window(id) {
ws.set_fullscreen(id, is_fullscreen);
return;
2023-08-16 09:08:10 +04:00
}
}
}
2025-03-17 14:56:29 +03:00
pub fn toggle_fullscreen(&mut self, id: &W::Id) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
2025-03-17 14:56:29 +03:00
if move_.tile.window().id() == id {
2024-07-15 15:51:48 +02:00
return;
}
}
for ws in self.workspaces_mut() {
2025-03-17 14:56:29 +03:00
if ws.has_window(id) {
ws.toggle_fullscreen(id);
return;
2023-08-16 09:08:10 +04:00
}
}
}
2025-03-17 14:56:29 +03:00
pub fn toggle_windowed_fullscreen(&mut self, id: &W::Id) {
let (_, window) = self.windows().find(|(_, win)| win.id() == id).unwrap();
if window.is_pending_fullscreen() {
// Remove the real fullscreen.
for ws in self.workspaces_mut() {
if ws.has_window(id) {
ws.set_fullscreen(id, false);
break;
}
}
}
// This will switch is_pending_fullscreen() to false right away.
self.with_windows_mut(|window, _| {
if window.id() == id {
window.request_windowed_fullscreen(!window.is_pending_windowed_fullscreen());
}
});
}
2024-06-19 21:54:46 +03:00
pub fn workspace_switch_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
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 {
2025-04-20 12:36:54 +03:00
monitor.workspace_switch_gesture_end(None);
continue;
}
2024-06-19 21:54:46 +03:00
monitor.workspace_switch_gesture_begin(is_touchpad);
}
}
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>> {
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)
{
if refresh {
return Some(Some(monitor.output.clone()));
} else {
return Some(None);
}
}
}
None
}
2025-04-20 12:36:54 +03:00
pub fn workspace_switch_gesture_end(&mut self, is_touchpad: Option<bool>) -> Option<Output> {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
MonitorSet::NoOutputs { .. } => return None,
};
for monitor in monitors {
2025-04-20 12:36:54 +03:00
if monitor.workspace_switch_gesture_end(is_touchpad) {
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 {
2025-04-20 12:36:54 +03:00
ws.view_offset_gesture_end(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
}
}
}
pub fn view_offset_gesture_update(
&mut self,
delta_x: f64,
timestamp: Duration,
2024-05-11 14:01:48 +04:00
is_touchpad: bool,
) -> 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
}
2025-04-20 12:36:54 +03:00
pub fn view_offset_gesture_end(&mut self, 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 {
2025-04-20 12:36:54 +03:00
if ws.view_offset_gesture_end(is_touchpad) {
2024-02-29 08:56:20 +04:00
return Some(monitor.output.clone());
}
}
}
None
}
2024-07-15 15:51:48 +02:00
pub fn interactive_move_begin(
&mut self,
window_id: W::Id,
output: &Output,
start_pos_within_output: Point<f64, Logical>,
) -> bool {
if self.interactive_move.is_some() {
return false;
}
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
return false;
};
let Some((mon, (ws, ws_offset))) = monitors.iter().find_map(|mon| {
mon.workspaces_with_render_positions()
.find(|(ws, _)| ws.has_window(&window_id))
.map(|rv| (mon, rv))
}) else {
return false;
};
if mon.output() != output {
return false;
}
let is_floating = ws.is_floating(&window_id);
let (tile, tile_offset, _visible) = ws
2024-07-15 15:51:48 +02:00
.tiles_with_render_positions()
.find(|(tile, _, _)| tile.window().id() == &window_id)
2024-07-15 15:51:48 +02:00
.unwrap();
let window_offset = tile.window_loc();
let tile_pos = ws_offset + tile_offset;
let pointer_offset_within_window = start_pos_within_output - tile_pos - window_offset;
let window_size = tile.window_size();
let pointer_ratio_within_window = (
f64::clamp(pointer_offset_within_window.x / window_size.w, 0., 1.),
f64::clamp(pointer_offset_within_window.y / window_size.h, 0., 1.),
);
self.interactive_move = Some(InteractiveMoveState::Starting {
window_id,
pointer_delta: Point::from((0., 0.)),
pointer_ratio_within_window,
});
// Lock the view for scrolling interactive move.
if !is_floating {
for ws in self.workspaces_mut() {
ws.dnd_scroll_gesture_begin();
}
}
2024-07-15 15:51:48 +02:00
true
}
pub fn interactive_move_update(
&mut self,
window: &W::Id,
delta: Point<f64, Logical>,
output: Output,
pointer_pos_within_output: Point<f64, Logical>,
) -> bool {
let Some(state) = self.interactive_move.take() else {
return false;
};
match state {
InteractiveMoveState::Starting {
window_id,
mut pointer_delta,
pointer_ratio_within_window,
} => {
if window_id != *window {
self.interactive_move = Some(InteractiveMoveState::Starting {
window_id,
pointer_delta,
pointer_ratio_within_window,
});
return false;
}
pointer_delta += delta;
let (cx, cy) = (pointer_delta.x, pointer_delta.y);
let sq_dist = cx * cx + cy * cy;
let factor = RubberBand {
stiffness: 1.0,
limit: 0.5,
}
.band(sq_dist / INTERACTIVE_MOVE_START_THRESHOLD);
2024-11-29 21:11:02 +03:00
let (is_floating, tile) = self
2024-07-15 15:51:48 +02:00
.workspaces_mut()
2024-11-29 21:11:02 +03:00
.find(|ws| ws.has_window(&window_id))
.map(|ws| {
(
ws.is_floating(&window_id),
ws.tiles_mut()
.find(|tile| *tile.window().id() == window_id)
.unwrap(),
)
})
2024-07-15 15:51:48 +02:00
.unwrap();
tile.interactive_move_offset = pointer_delta.upscale(factor);
// Put it back to be able to easily return.
self.interactive_move = Some(InteractiveMoveState::Starting {
window_id: window_id.clone(),
pointer_delta,
pointer_ratio_within_window,
});
2024-11-29 21:11:02 +03:00
if !is_floating && sq_dist < INTERACTIVE_MOVE_START_THRESHOLD {
2024-07-15 15:51:48 +02:00
return true;
}
// If the pointer is currently on the window's own output, then we can animate the
// window movement from its current (rubberbanded and possibly moved away) position
// to the pointer. Otherwise, we just teleport it as the layout code is not aware
// of monitor positions.
//
2024-11-29 21:11:02 +03:00
// FIXME: when and if the layout code knows about monitor positions, this will be
// potentially animatable.
2024-07-15 15:51:48 +02:00
let mut tile_pos = None;
if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
if let Some((mon, (ws, ws_offset))) = monitors.iter().find_map(|mon| {
mon.workspaces_with_render_positions()
.find(|(ws, _)| ws.has_window(window))
.map(|rv| (mon, rv))
}) {
if mon.output() == &output {
let (_, tile_offset, _) = ws
2024-07-15 15:51:48 +02:00
.tiles_with_render_positions()
.find(|(tile, _, _)| tile.window().id() == window)
2024-07-15 15:51:48 +02:00
.unwrap();
tile_pos = Some(ws_offset + tile_offset);
}
}
}
// Clear it before calling remove_window() to avoid running interactive_move_end()
// in the middle of interactive_move_update() and the confusion that causes.
self.interactive_move = None;
2024-07-15 15:51:48 +02:00
let RemovedTile {
mut tile,
width,
is_full_width,
mut is_floating,
2024-07-15 15:51:48 +02:00
} = self.remove_window(window, Transaction::new()).unwrap();
tile.stop_move_animations();
tile.interactive_move_offset = Point::from((0., 0.));
tile.window().output_enter(&output);
tile.window().set_preferred_scale_transform(
output.current_scale(),
output.current_transform(),
);
2024-12-26 08:54:56 +03:00
let view_size = output_size(&output);
2024-07-15 15:51:48 +02:00
let scale = output.current_scale().fractional_scale();
tile.update_config(
2024-12-26 08:54:56 +03:00
view_size,
2024-07-15 15:51:48 +02:00
scale,
Rc::new(Options::clone(&self.options).adjusted_for_scale(scale)),
);
2024-12-22 09:28:57 +03:00
// Unfullscreen.
2024-12-25 16:43:15 +03:00
let floating_size = tile.floating_window_size;
let unfullscreen_to_floating = tile.unfullscreen_to_floating;
let win = tile.window_mut();
if win.is_pending_fullscreen() {
2024-12-22 09:28:57 +03:00
// If we're unfullscreening to floating, use the stored floating size,
// otherwise use (0, 0).
let mut size = if unfullscreen_to_floating {
floating_size.unwrap_or_default()
} else {
Size::from((0, 0))
};
// Apply min/max size window rules. If requesting a concrete size, apply
// completely; if requesting (0, 0), apply only when min/max results in a fixed
// size.
let min_size = win.min_size();
let max_size = win.max_size();
size.w = ensure_min_max_size_maybe_zero(size.w, min_size.w, max_size.w);
size.h = ensure_min_max_size_maybe_zero(size.h, min_size.h, max_size.h);
2024-12-17 10:03:27 +03:00
win.request_size_once(size, true);
// If we're unfullscreening to floating, default to the floating layout.
2024-12-22 09:28:57 +03:00
is_floating = unfullscreen_to_floating;
2024-07-15 15:51:48 +02:00
}
// Animate to semitransparent.
if !is_floating {
tile.animate_alpha(
1.,
INTERACTIVE_MOVE_ALPHA,
self.options.animations.window_movement.0,
);
tile.hold_alpha_animation_after_done();
}
2024-07-15 15:51:48 +02:00
let mut data = InteractiveMoveData {
tile,
output,
pointer_pos_within_output,
width,
is_full_width,
is_floating,
2024-07-15 15:51:48 +02:00
pointer_ratio_within_window,
};
if let Some(tile_pos) = tile_pos {
let new_tile_pos = data.tile_render_location();
data.tile.animate_move_from(tile_pos - new_tile_pos);
}
self.interactive_move = Some(InteractiveMoveState::Moving(data));
}
InteractiveMoveState::Moving(mut move_) => {
if window != move_.tile.window().id() {
self.interactive_move = Some(InteractiveMoveState::Moving(move_));
return false;
}
if output != move_.output {
move_.tile.window().output_leave(&move_.output);
move_.tile.window().output_enter(&output);
move_.tile.window().set_preferred_scale_transform(
output.current_scale(),
output.current_transform(),
);
2024-12-26 08:54:56 +03:00
let view_size = output_size(&output);
2024-07-15 15:51:48 +02:00
let scale = output.current_scale().fractional_scale();
move_.tile.update_config(
2024-12-26 08:54:56 +03:00
view_size,
2024-07-15 15:51:48 +02:00
scale,
Rc::new(Options::clone(&self.options).adjusted_for_scale(scale)),
);
move_.output = output.clone();
self.focus_output(&output);
}
move_.pointer_pos_within_output = pointer_pos_within_output;
self.interactive_move = Some(InteractiveMoveState::Moving(move_));
}
}
true
}
pub fn interactive_move_end(&mut self, window: &W::Id) {
let Some(move_) = &self.interactive_move else {
return;
};
let move_ = match move_ {
InteractiveMoveState::Starting { window_id, .. } => {
if window_id != window {
return;
}
let Some(InteractiveMoveState::Starting { window_id, .. }) =
self.interactive_move.take()
else {
unreachable!()
};
for ws in self.workspaces_mut() {
if let Some(tile) = ws.tiles_mut().find(|tile| *tile.window().id() == window_id)
{
let offset = tile.interactive_move_offset;
tile.interactive_move_offset = Point::from((0., 0.));
tile.animate_move_from(offset);
}
// Unlock the view on the workspaces, but if the moved window was active,
// preserve that.
let moved_tile_was_active =
ws.active_window().is_some_and(|win| *win.id() == window_id);
ws.dnd_scroll_gesture_end();
if moved_tile_was_active {
ws.activate_window(&window_id);
}
}
2024-07-15 15:51:48 +02:00
return;
}
InteractiveMoveState::Moving(move_) => move_,
};
if window != move_.tile.window().id() {
return;
}
let Some(InteractiveMoveState::Moving(mut move_)) = self.interactive_move.take() else {
2024-07-15 15:51:48 +02:00
unreachable!()
};
// Unlock the view on the workspaces.
if !move_.is_floating {
for ws in self.workspaces_mut() {
ws.dnd_scroll_gesture_end();
}
// Also animate the tile back to opaque.
move_.tile.animate_alpha(
INTERACTIVE_MOVE_ALPHA,
1.,
self.options.animations.window_movement.0,
);
}
2024-07-15 15:51:48 +02:00
match &mut self.monitor_set {
MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} => {
let (mon, ws_idx, position, offset) = if let Some(mon) =
monitors.iter_mut().find(|mon| mon.output == move_.output)
{
let (ws, offset) = mon
.workspace_under(move_.pointer_pos_within_output)
// If the pointer is somehow outside the move output and a workspace switch
// is in progress, this won't necessarily do the expected thing, but also
// that is not really supposed to happen so eh?
.unwrap_or_else(|| mon.workspaces_with_render_positions().next().unwrap());
let ws_id = ws.id();
let ws_idx = mon
.workspaces
.iter_mut()
.position(|ws| ws.id() == ws_id)
.unwrap();
let position = if move_.is_floating {
InsertPosition::Floating
} else {
let ws = &mut mon.workspaces[ws_idx];
ws.get_insert_position(move_.pointer_pos_within_output - offset)
};
2024-07-15 15:51:48 +02:00
(mon, ws_idx, position, offset)
} else {
let mon = &mut monitors[*active_monitor_idx];
// No point in trying to use the pointer position on the wrong output.
let (ws, offset) = mon.workspaces_with_render_positions().next().unwrap();
let position = if move_.is_floating {
InsertPosition::Floating
} else {
ws.get_insert_position(Point::from((0., 0.)))
};
let ws_id = ws.id();
let ws_idx = mon
.workspaces
.iter_mut()
.position(|ws| ws.id() == ws_id)
.unwrap();
2024-07-15 15:51:48 +02:00
(mon, ws_idx, position, offset)
};
let win_id = move_.tile.window().id().clone();
let window_render_loc = move_.tile_render_location() + move_.tile.window_loc();
match position {
InsertPosition::NewColumn(column_idx) => {
2024-12-26 09:37:38 +03:00
let ws_id = mon.workspaces[ws_idx].id();
2024-07-15 15:51:48 +02:00
mon.add_tile(
move_.tile,
2024-12-26 09:37:38 +03:00
MonitorAddWindowTarget::Workspace {
id: ws_id,
column_idx: Some(column_idx),
},
ActivateWindow::Yes,
2024-07-15 15:51:48 +02:00
move_.width,
move_.is_full_width,
2024-12-26 09:37:38 +03:00
false,
2024-07-15 15:51:48 +02:00
);
}
InsertPosition::InColumn(column_idx, tile_idx) => {
mon.add_tile_to_column(
ws_idx,
column_idx,
Some(tile_idx),
move_.tile,
true,
);
}
2024-11-29 21:11:02 +03:00
InsertPosition::Floating => {
let pos = move_.tile_render_location() - offset;
2024-12-24 10:51:00 +03:00
let mut tile = move_.tile;
let pos = mon.workspaces[ws_idx].floating_logical_to_size_frac(pos);
2024-12-25 16:43:15 +03:00
tile.floating_pos = Some(pos);
2024-12-24 10:51:00 +03:00
// Set the floating size so it takes into account any window resizing that
// took place during the move.
if let Some(size) = tile.window().expected_size() {
2024-12-25 16:43:15 +03:00
tile.floating_window_size = Some(size);
}
2024-12-26 09:37:38 +03:00
let ws_id = mon.workspaces[ws_idx].id();
mon.add_tile(
tile,
MonitorAddWindowTarget::Workspace {
id: ws_id,
column_idx: None,
},
ActivateWindow::Yes,
move_.width,
move_.is_full_width,
true,
);
2024-11-29 21:11:02 +03:00
}
2024-07-15 15:51:48 +02:00
}
// needed because empty_workspace_above_first could have modified the idx
let ws_idx = mon.active_workspace_idx();
2024-07-15 15:51:48 +02:00
let ws = &mut mon.workspaces[ws_idx];
let (tile, tile_render_loc) = ws
.tiles_with_render_positions_mut(false)
.find(|(tile, _)| tile.window().id() == &win_id)
.unwrap();
let new_window_render_loc = offset + tile_render_loc + tile.window_loc();
tile.animate_move_from(window_render_loc - new_window_render_loc);
}
MonitorSet::NoOutputs { workspaces, .. } => {
2024-12-26 09:37:38 +03:00
if workspaces.is_empty() {
workspaces.push(Workspace::new_no_outputs(
self.clock.clone(),
self.options.clone(),
));
2024-12-26 09:37:38 +03:00
}
let ws = &mut workspaces[0];
2024-07-15 15:51:48 +02:00
// No point in trying to use the pointer position without outputs.
2024-12-26 09:37:38 +03:00
ws.add_tile(
move_.tile,
WorkspaceAddWindowTarget::Auto,
ActivateWindow::Yes,
move_.width,
move_.is_full_width,
move_.is_floating,
);
2024-07-15 15:51:48 +02:00
}
}
}
pub fn interactive_move_is_moving_above_output(&self, output: &Output) -> bool {
let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move else {
return false;
};
move_.output == *output
}
2025-02-15 13:11:34 +03:00
pub fn dnd_update(&mut self, output: Output, pointer_pos_within_output: Point<f64, Logical>) {
let begin_gesture = self.dnd.is_none();
self.dnd = Some(DndData {
output,
pointer_pos_within_output,
});
if begin_gesture {
for ws in self.workspaces_mut() {
ws.dnd_scroll_gesture_begin();
}
}
}
pub fn dnd_end(&mut self) {
if self.dnd.is_none() {
return;
}
self.dnd = None;
for ws in self.workspaces_mut() {
ws.dnd_scroll_gesture_end();
2025-02-15 13:11:34 +03:00
}
}
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 {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return false;
}
}
2024-05-10 16:58:53 +04:00
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) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return;
}
}
2024-05-10 16:58:53 +04:00
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
pub fn move_workspace_to_idx(
&mut self,
reference: Option<(Option<Output>, usize)>,
new_idx: usize,
) {
let (monitor, old_idx) = if let Some((output, old_idx)) = reference {
let monitor = if let Some(output) = output {
let Some(monitor) = self.monitor_for_output_mut(&output) else {
return;
};
monitor
} else {
// In case a numbered workspace reference is used, assume the active monitor
let Some(monitor) = self.active_monitor() else {
return;
};
monitor
};
(monitor, old_idx)
} else {
let Some(monitor) = self.active_monitor() else {
return;
};
let index = monitor.active_workspace_idx;
(monitor, index)
};
monitor.move_workspace_to_idx(old_idx, new_idx);
}
pub fn set_workspace_name(&mut self, name: String, reference: Option<WorkspaceReference>) {
// ignore the request if the name is already used by another workspace
if self.find_workspace_by_name(&name).is_some() {
return;
}
let ws = if let Some(reference) = reference {
self.find_workspace_by_ref(reference)
} else {
self.active_workspace_mut()
};
let Some(ws) = ws else {
return;
};
ws.name.replace(name);
let wsid = ws.id();
// if `empty_workspace_above_first` is set and `ws` is the first
// worskpace on a monitor, another empty workspace needs to
// be added before.
// Conversely, if `ws` was the last workspace on a monitor, an
// empty workspace needs to be added after.
if let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
{
let monitor = &mut monitors[*active_monitor_idx];
if self.options.empty_workspace_above_first
&& monitor
.workspaces
.first()
.is_some_and(|first| first.id() == wsid)
{
monitor.add_workspace_top();
}
if monitor
.workspaces
.last()
.is_some_and(|last| last.id() == wsid)
{
monitor.add_workspace_bottom();
}
}
}
pub fn unset_workspace_name(&mut self, reference: Option<WorkspaceReference>) {
let ws = if let Some(reference) = reference {
self.find_workspace_by_ref(reference)
} else {
self.active_workspace_mut()
};
let Some(ws) = ws else {
return;
};
let id = ws.id();
self.unname_workspace_by_id(id);
}
pub fn start_open_animation_for_window(&mut self, window: &W::Id) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == window {
return;
}
}
2024-11-30 09:18:33 +03:00
for ws in self.workspaces_mut() {
2025-02-10 14:12:54 +03:00
if ws.start_open_animation(window) {
return;
2024-02-07 11:32:02 +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");
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if move_.tile.window().id() == window {
move_.tile.store_unmap_snapshot_if_empty(renderer);
2024-07-15 15:51:48 +02:00
return;
}
}
2024-05-01 19:00:11 +04:00
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) {
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if move_.tile.window().id() == window {
let _ = move_.tile.take_unmap_snapshot();
return;
}
}
2024-05-01 19:00:11 +04:00
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");
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
if move_.tile.window().id() == window {
let Some(snapshot) = move_.tile.take_unmap_snapshot() else {
return;
};
let tile_pos = move_.tile_render_location();
let tile_size = move_.tile.tile_size();
let output = move_.output.clone();
let pointer_pos_within_output = move_.pointer_pos_within_output;
let Some(mon) = self.monitor_for_output_mut(&output) else {
return;
};
let Some((ws, offset)) = mon.workspace_under(pointer_pos_within_output) else {
return;
};
let ws_id = ws.id();
let ws = mon
.workspaces
.iter_mut()
.find(|ws| ws.id() == ws_id)
.unwrap();
2024-11-30 09:18:33 +03:00
let tile_pos = tile_pos - offset;
2024-07-15 15:51:48 +02:00
ws.start_close_animation_for_tile(renderer, snapshot, tile_size, tile_pos, blocker);
return;
}
}
2024-04-09 22:37:10 +04:00
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;
}
}
}
}
}
pub fn render_interactive_move_for_output<'a, R: NiriRenderer + 'a>(
2025-01-18 17:42:11 +03:00
&'a self,
2024-07-15 15:51:48 +02:00
renderer: &mut R,
output: &Output,
target: RenderTarget,
2025-01-18 17:42:11 +03:00
) -> impl Iterator<Item = TileRenderElement<R>> + 'a {
if self.update_render_elements_time != self.clock.now() {
error!("clock moved between updating render elements and rendering");
}
2024-07-15 15:51:48 +02:00
let mut rv = None;
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if &move_.output == output {
let location = move_.tile_render_location();
rv = Some(move_.tile.render(renderer, location, true, target));
2024-07-15 15:51:48 +02:00
}
}
rv.into_iter().flatten()
}
pub fn refresh(&mut self, is_active: bool) {
2024-03-19 13:52:08 +04:00
let _span = tracy_client::span!("Layout::refresh");
2023-08-14 14:48:10 +04:00
self.is_active = is_active;
2025-02-15 13:11:34 +03:00
let mut ongoing_scrolling_dnd = self.dnd.is_some().then_some(true);
2024-07-15 15:51:48 +02:00
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
let win = move_.tile.window_mut();
win.set_active_in_column(true);
2024-12-27 11:20:03 +03:00
win.set_floating(move_.is_floating);
2024-07-15 15:51:48 +02:00
win.set_activated(true);
win.set_interactive_resize(None);
win.set_bounds(output_size(&move_.output).to_i32_round());
win.send_pending_configure();
win.refresh();
2025-02-15 13:11:34 +03:00
ongoing_scrolling_dnd.get_or_insert(!move_.is_floating);
} else if let Some(InteractiveMoveState::Starting { window_id, .. }) =
&self.interactive_move
{
2025-02-15 13:11:34 +03:00
ongoing_scrolling_dnd.get_or_insert_with(|| {
let (_, _, ws) = self
.workspaces()
.find(|(_, _, ws)| ws.has_window(window_id))
.unwrap();
!ws.is_floating(window_id)
});
2024-07-15 15:51:48 +02: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() {
let is_active = self.is_active
&& idx == *active_monitor_idx
&& !matches!(self.interactive_move, Some(InteractiveMoveState::Moving(_)));
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
if let Some(is_scrolling) = ongoing_scrolling_dnd {
// Lock or unlock the view for scrolling interactive move.
if is_scrolling {
ws.dnd_scroll_gesture_begin();
} else {
ws.dnd_scroll_gesture_end();
}
} else {
// Cancel the view offset gesture after workspace switches, moves, etc.
if ws_idx != mon.active_workspace_idx {
2025-04-20 12:36:54 +03:00
ws.view_offset_gesture_end(None);
}
2024-02-29 08:56:20 +04:00
}
2023-08-14 14:48:10 +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);
2025-04-20 12:36:54 +03:00
ws.view_offset_gesture_end(None);
2023-08-14 14:48:10 +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
}
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)> {
2024-07-15 15:51:48 +02:00
let moving_window = self
.interactive_move
.as_ref()
.and_then(|x| x.moving())
.map(|move_| (self.monitor_for_output(&move_.output), move_.tile.window()))
.into_iter();
let rest = self
.workspaces()
.flat_map(|(mon, _, ws)| ws.windows().map(move |win| (mon, win)));
moving_window.chain(rest)
}
pub fn has_window(&self, window: &W::Id) -> bool {
self.windows().any(|(_, win)| win.id() == window)
}
fn resolve_scrolling_width(&self, window: &W, width: Option<PresetSize>) -> ColumnWidth {
let width = width.unwrap_or_else(|| PresetSize::Fixed(window.size().w));
match width {
PresetSize::Fixed(fixed) => {
let mut fixed = f64::from(fixed);
2024-12-26 09:37:38 +03:00
// Add border width since ColumnWidth includes borders.
let rules = window.rules();
let border = rules.border.resolve_against(self.options.border);
if !border.off {
fixed += border.width.0 * 2.;
}
ColumnWidth::Fixed(fixed)
}
PresetSize::Proportion(prop) => ColumnWidth::Proportion(prop),
}
}
}
impl<W: LayoutElement> Default for MonitorSet<W> {
fn default() -> Self {
Self::NoOutputs { workspaces: vec![] }
}
}