atagen

monolith

public
Star0
Watch0
509842c1b6dc0e9d48985cb5aa8d691a1b2301ee
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/layout/src/lib.rs

9baaafa4b167atagen2026-10-16 10:04 UTC1
//! Window organisation without smithay. Tags with per-output views, one
9baaafa4b167atagen2026-10-16 10:04 UTC2
//! tiling tree per output filtered by those views, floating windows,
9baaafa4b167atagen2026-10-16 10:04 UTC3
//! scratchpads, focus, and restoring windows when an output comes back.
57f44a63d99fatagen2026-10-16 05:34 UTC4
4cbf881c67bdatagen2026-10-17 04:56 UTC5
mod outputs;
4cbf881c67bdatagen2026-10-17 04:56 UTC6
mod scratchpad;
9baaafa4b167atagen2026-10-16 10:04 UTC7
mod tree;
9baaafa4b167atagen2026-10-16 10:04 UTC8
9baaafa4b167atagen2026-10-16 10:04 UTC9
#[cfg(test)]
9baaafa4b167atagen2026-10-16 10:04 UTC10
mod tests;
9baaafa4b167atagen2026-10-16 10:04 UTC11
9baaafa4b167atagen2026-10-16 10:04 UTC12
pub use monolith_planner::Rect;
9baaafa4b167atagen2026-10-16 10:04 UTC13
pub use tree::{Axis, Dir, Tree};
9baaafa4b167atagen2026-10-16 10:04 UTC14
4cbf881c67bdatagen2026-10-17 04:56 UTC15
use outputs::Parked;
9baaafa4b167atagen2026-10-16 10:04 UTC16
use slotmap::{SlotMap, new_key_type};
9baaafa4b167atagen2026-10-16 10:04 UTC17
use std::collections::HashMap;
9baaafa4b167atagen2026-10-16 10:04 UTC18
9baaafa4b167atagen2026-10-16 10:04 UTC19
new_key_type! {
9baaafa4b167atagen2026-10-16 10:04 UTC20
    pub struct WindowId;
9baaafa4b167atagen2026-10-16 10:04 UTC21
    pub struct TagId;
9baaafa4b167atagen2026-10-16 10:04 UTC22
    pub struct OutputId;
9baaafa4b167atagen2026-10-16 10:04 UTC23
}
9baaafa4b167atagen2026-10-16 10:04 UTC24
9baaafa4b167atagen2026-10-16 10:04 UTC25
pub const HOMELESS: &str = "homeless";
9baaafa4b167atagen2026-10-16 10:04 UTC26
9baaafa4b167atagen2026-10-16 10:04 UTC27
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9baaafa4b167atagen2026-10-16 10:04 UTC28
pub enum ExpandScope {
9baaafa4b167atagen2026-10-16 10:04 UTC29
    Window,
4444c779eb48atagen2026-10-16 12:07 UTC30
    Container,
9baaafa4b167atagen2026-10-16 10:04 UTC31
    All,
9baaafa4b167atagen2026-10-16 10:04 UTC32
}
9baaafa4b167atagen2026-10-16 10:04 UTC33
bc51c38a6fd9atagen2026-10-16 22:17 UTC34
/// Space taken from an output's edges by bars and other panels.
bc51c38a6fd9atagen2026-10-16 22:17 UTC35
#[derive(Clone, Copy, Debug, PartialEq, Default)]
bc51c38a6fd9atagen2026-10-16 22:17 UTC36
pub struct Insets {
bc51c38a6fd9atagen2026-10-16 22:17 UTC37
    pub top: f64,
bc51c38a6fd9atagen2026-10-16 22:17 UTC38
    pub right: f64,
bc51c38a6fd9atagen2026-10-16 22:17 UTC39
    pub bottom: f64,
bc51c38a6fd9atagen2026-10-16 22:17 UTC40
    pub left: f64,
bc51c38a6fd9atagen2026-10-16 22:17 UTC41
}
bc51c38a6fd9atagen2026-10-16 22:17 UTC42
9baaafa4b167atagen2026-10-16 10:04 UTC43
#[derive(Clone, Copy, Debug, PartialEq)]
9baaafa4b167atagen2026-10-16 10:04 UTC44
pub struct Gaps {
9baaafa4b167atagen2026-10-16 10:04 UTC45
    pub inner: f64,
9baaafa4b167atagen2026-10-16 10:04 UTC46
    pub outer: f64,
9baaafa4b167atagen2026-10-16 10:04 UTC47
}
9baaafa4b167atagen2026-10-16 10:04 UTC48
9baaafa4b167atagen2026-10-16 10:04 UTC49
impl Default for Gaps {
9baaafa4b167atagen2026-10-16 10:04 UTC50
    fn default() -> Self {
9baaafa4b167atagen2026-10-16 10:04 UTC51
        Self {
9baaafa4b167atagen2026-10-16 10:04 UTC52
            inner: 8.0,
9baaafa4b167atagen2026-10-16 10:04 UTC53
            outer: 8.0,
9baaafa4b167atagen2026-10-16 10:04 UTC54
        }
9baaafa4b167atagen2026-10-16 10:04 UTC55
    }
9baaafa4b167atagen2026-10-16 10:04 UTC56
}
9baaafa4b167atagen2026-10-16 10:04 UTC57
9baaafa4b167atagen2026-10-16 10:04 UTC58
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC59
pub struct Window {
9baaafa4b167atagen2026-10-16 10:04 UTC60
    pub output: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC61
    pub tag: TagId,
9baaafa4b167atagen2026-10-16 10:04 UTC62
    pub floating: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC63
    pub float_rect: Option<Rect>,
754ca2132943atagen2026-10-19 07:29 UTC64
    /// `float_rect` is the layout's default, not a size the window or the
754ca2132943atagen2026-10-19 07:29 UTC65
    /// user chose.
754ca2132943atagen2026-10-19 07:29 UTC66
    pub float_default: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC67
    pub fullscreen: bool,
b44dde08d7acatagen2026-10-19 07:38 UTC68
    /// The window this one is a dialog or other transient of.
b44dde08d7acatagen2026-10-19 07:38 UTC69
    pub parent: Option<WindowId>,
ecb0c8c1f31datagen2026-10-19 10:15 UTC70
    /// Focus aimed at the parent goes to this window while it is mapped.
ecb0c8c1f31datagen2026-10-19 10:15 UTC71
    pub modal: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC72
    pub sticky: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC73
    pub scratchpad: Option<usize>,
9fdf9584d2bdatagen2026-10-19 07:38 UTC74
    pub urgent: bool,
1c7e3075e57fatagen2026-10-19 10:28 UTC75
    /// Its client took the buffer away, as when hiding to a tray. The entry
1c7e3075e57fatagen2026-10-19 10:28 UTC76
    /// stays for when the client maps it again, but nothing shows or focuses it.
1c7e3075e57fatagen2026-10-19 10:28 UTC77
    pub hidden: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC78
    evicted_from: Option<String>,
9baaafa4b167atagen2026-10-16 10:04 UTC79
}
9baaafa4b167atagen2026-10-16 10:04 UTC80
9baaafa4b167atagen2026-10-16 10:04 UTC81
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC82
pub struct Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC83
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC84
    pub urgent: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC85
}
9baaafa4b167atagen2026-10-16 10:04 UTC86
9baaafa4b167atagen2026-10-16 10:04 UTC87
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC88
pub struct Output {
9baaafa4b167atagen2026-10-16 10:04 UTC89
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC90
    pub area: Rect,
bc51c38a6fd9atagen2026-10-16 22:17 UTC91
    pub reserved: Insets,
9baaafa4b167atagen2026-10-16 10:04 UTC92
    pub tree: Tree<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC93
    /// Viewed tags besides the homeless tag, which is always viewed.
9baaafa4b167atagen2026-10-16 10:04 UTC94
    pub viewed: Vec<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC95
    /// Floating windows, bottom to top.
9baaafa4b167atagen2026-10-16 10:04 UTC96
    pub floating: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC97
}
9baaafa4b167atagen2026-10-16 10:04 UTC98
b01f4e7ab7e6atagen2026-10-19 07:44 UTC99
impl Output {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC100
    /// The area left once bars and panels have taken their edges.
b01f4e7ab7e6atagen2026-10-19 07:44 UTC101
    pub fn usable(&self) -> Rect {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC102
        let (a, r) = (self.area, self.reserved);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC103
        Rect::new(
b01f4e7ab7e6atagen2026-10-19 07:44 UTC104
            a.x + r.left,
b01f4e7ab7e6atagen2026-10-19 07:44 UTC105
            a.y + r.top,
b01f4e7ab7e6atagen2026-10-19 07:44 UTC106
            (a.w - r.left - r.right).max(0.0),
b01f4e7ab7e6atagen2026-10-19 07:44 UTC107
            (a.h - r.top - r.bottom).max(0.0),
b01f4e7ab7e6atagen2026-10-19 07:44 UTC108
        )
b01f4e7ab7e6atagen2026-10-19 07:44 UTC109
    }
b01f4e7ab7e6atagen2026-10-19 07:44 UTC110
}
b01f4e7ab7e6atagen2026-10-19 07:44 UTC111
9baaafa4b167atagen2026-10-16 10:04 UTC112
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC113
pub struct Scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC114
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC115
    pub windows: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC116
    pub shown: Option<usize>,
9baaafa4b167atagen2026-10-16 10:04 UTC117
    pub output: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC118
}
9baaafa4b167atagen2026-10-16 10:04 UTC119
9baaafa4b167atagen2026-10-16 10:04 UTC120
#[derive(Clone, Debug, Default)]
9baaafa4b167atagen2026-10-16 10:04 UTC121
pub struct Arrangement {
9baaafa4b167atagen2026-10-16 10:04 UTC122
    /// Every tiled window on the output. Hidden ones are zero-size seeds.
9baaafa4b167atagen2026-10-16 10:04 UTC123
    pub tiled: Vec<Placed>,
9baaafa4b167atagen2026-10-16 10:04 UTC124
    /// Visible floating windows and shown scratchpads, bottom to top.
9baaafa4b167atagen2026-10-16 10:04 UTC125
    pub floating: Vec<(WindowId, Rect)>,
9baaafa4b167atagen2026-10-16 10:04 UTC126
    pub fullscreen: Option<WindowId>,
1c7e3075e57fatagen2026-10-19 10:28 UTC127
    /// Floating transients of the fullscreen window and shown scratchpads,
1c7e3075e57fatagen2026-10-19 10:28 UTC128
    /// which stay above it.
b44dde08d7acatagen2026-10-19 07:38 UTC129
    pub over: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC130
}
9baaafa4b167atagen2026-10-16 10:04 UTC131
9baaafa4b167atagen2026-10-16 10:04 UTC132
#[derive(Clone, Copy, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC133
pub struct Placed {
9baaafa4b167atagen2026-10-16 10:04 UTC134
    pub window: WindowId,
9baaafa4b167atagen2026-10-16 10:04 UTC135
    pub rect: Rect,
9baaafa4b167atagen2026-10-16 10:04 UTC136
    pub visible: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC137
}
9baaafa4b167atagen2026-10-16 10:04 UTC138
9baaafa4b167atagen2026-10-16 10:04 UTC139
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC140
pub struct Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC141
    windows: SlotMap<WindowId, Window>,
9baaafa4b167atagen2026-10-16 10:04 UTC142
    tags: SlotMap<TagId, Tag>,
9baaafa4b167atagen2026-10-16 10:04 UTC143
    tag_order: Vec<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC144
    homeless: TagId,
9baaafa4b167atagen2026-10-16 10:04 UTC145
    outputs: SlotMap<OutputId, Output>,
9baaafa4b167atagen2026-10-16 10:04 UTC146
    active: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC147
    /// Focus history, most recent last.
9baaafa4b167atagen2026-10-16 10:04 UTC148
    history: Vec<WindowId>,
ecb0c8c1f31datagen2026-10-19 10:15 UTC149
    /// How many windows are modal, so focus lookups skip the scan for one.
ecb0c8c1f31datagen2026-10-19 10:15 UTC150
    modals: usize,
9baaafa4b167atagen2026-10-16 10:04 UTC151
    scratchpads: Vec<Scratchpad>,
9baaafa4b167atagen2026-10-16 10:04 UTC152
    last_scratchpad: Option<usize>,
9baaafa4b167atagen2026-10-16 10:04 UTC153
    parked: HashMap<String, Parked>,
9baaafa4b167atagen2026-10-16 10:04 UTC154
    pub gaps: Gaps,
86ef802692f5atagen2026-10-19 07:43 UTC155
    pub autotile: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC156
}
9baaafa4b167atagen2026-10-16 10:04 UTC157
9baaafa4b167atagen2026-10-16 10:04 UTC158
impl Default for Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC159
    fn default() -> Self {
9baaafa4b167atagen2026-10-16 10:04 UTC160
        let mut tags = SlotMap::with_key();
9baaafa4b167atagen2026-10-16 10:04 UTC161
        let homeless = tags.insert(Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC162
            name: HOMELESS.into(),
9baaafa4b167atagen2026-10-16 10:04 UTC163
            urgent: false,
9baaafa4b167atagen2026-10-16 10:04 UTC164
        });
9baaafa4b167atagen2026-10-16 10:04 UTC165
        Self {
9baaafa4b167atagen2026-10-16 10:04 UTC166
            windows: SlotMap::with_key(),
9baaafa4b167atagen2026-10-16 10:04 UTC167
            tags,
9baaafa4b167atagen2026-10-16 10:04 UTC168
            tag_order: vec![homeless],
9baaafa4b167atagen2026-10-16 10:04 UTC169
            homeless,
9baaafa4b167atagen2026-10-16 10:04 UTC170
            outputs: SlotMap::with_key(),
9baaafa4b167atagen2026-10-16 10:04 UTC171
            active: None,
9baaafa4b167atagen2026-10-16 10:04 UTC172
            history: Vec::new(),
ecb0c8c1f31datagen2026-10-19 10:15 UTC173
            modals: 0,
9baaafa4b167atagen2026-10-16 10:04 UTC174
            scratchpads: Vec::new(),
9baaafa4b167atagen2026-10-16 10:04 UTC175
            last_scratchpad: None,
9baaafa4b167atagen2026-10-16 10:04 UTC176
            parked: HashMap::new(),
9baaafa4b167atagen2026-10-16 10:04 UTC177
            gaps: Gaps::default(),
86ef802692f5atagen2026-10-19 07:43 UTC178
            autotile: false,
9baaafa4b167atagen2026-10-16 10:04 UTC179
        }
9baaafa4b167atagen2026-10-16 10:04 UTC180
    }
9baaafa4b167atagen2026-10-16 10:04 UTC181
}
9baaafa4b167atagen2026-10-16 10:04 UTC182
9baaafa4b167atagen2026-10-16 10:04 UTC183
impl Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC184
    pub fn homeless(&self) -> TagId {
9baaafa4b167atagen2026-10-16 10:04 UTC185
        self.homeless
9baaafa4b167atagen2026-10-16 10:04 UTC186
    }
9baaafa4b167atagen2026-10-16 10:04 UTC187
9baaafa4b167atagen2026-10-16 10:04 UTC188
    /// Looks a tag up by name, creating it at the end of the order.
9baaafa4b167atagen2026-10-16 10:04 UTC189
    pub fn tag(&mut self, name: &str) -> TagId {
9baaafa4b167atagen2026-10-16 10:04 UTC190
        if let Some(id) = self.tag_id(name) {
9baaafa4b167atagen2026-10-16 10:04 UTC191
            return id;
9baaafa4b167atagen2026-10-16 10:04 UTC192
        }
9baaafa4b167atagen2026-10-16 10:04 UTC193
        let id = self.tags.insert(Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC194
            name: name.into(),
9baaafa4b167atagen2026-10-16 10:04 UTC195
            urgent: false,
9baaafa4b167atagen2026-10-16 10:04 UTC196
        });
9baaafa4b167atagen2026-10-16 10:04 UTC197
        self.tag_order.push(id);
9baaafa4b167atagen2026-10-16 10:04 UTC198
        id
9baaafa4b167atagen2026-10-16 10:04 UTC199
    }
9baaafa4b167atagen2026-10-16 10:04 UTC200
9baaafa4b167atagen2026-10-16 10:04 UTC201
    pub fn tag_id(&self, name: &str) -> Option<TagId> {
9baaafa4b167atagen2026-10-16 10:04 UTC202
        self.tag_order
9baaafa4b167atagen2026-10-16 10:04 UTC203
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC204
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC205
            .find(|&t| self.tags[t].name == name)
9baaafa4b167atagen2026-10-16 10:04 UTC206
    }
9baaafa4b167atagen2026-10-16 10:04 UTC207
9baaafa4b167atagen2026-10-16 10:04 UTC208
    pub fn tags(&self) -> impl Iterator<Item = (TagId, &Tag)> {
9baaafa4b167atagen2026-10-16 10:04 UTC209
        self.tag_order.iter().map(|&t| (t, &self.tags[t]))
9baaafa4b167atagen2026-10-16 10:04 UTC210
    }
9baaafa4b167atagen2026-10-16 10:04 UTC211
9baaafa4b167atagen2026-10-16 10:04 UTC212
    pub fn reorder_tag(&mut self, tag: TagId, index: usize) {
9baaafa4b167atagen2026-10-16 10:04 UTC213
        if let Some(i) = self.tag_order.iter().position(|&t| t == tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC214
            self.tag_order.remove(i);
9baaafa4b167atagen2026-10-16 10:04 UTC215
            let index = index.min(self.tag_order.len());
9baaafa4b167atagen2026-10-16 10:04 UTC216
            self.tag_order.insert(index, tag);
9baaafa4b167atagen2026-10-16 10:04 UTC217
        }
9baaafa4b167atagen2026-10-16 10:04 UTC218
    }
9baaafa4b167atagen2026-10-16 10:04 UTC219
9baaafa4b167atagen2026-10-16 10:04 UTC220
    pub fn window(&self, w: WindowId) -> Option<&Window> {
9baaafa4b167atagen2026-10-16 10:04 UTC221
        self.windows.get(w)
9baaafa4b167atagen2026-10-16 10:04 UTC222
    }
9baaafa4b167atagen2026-10-16 10:04 UTC223
9baaafa4b167atagen2026-10-16 10:04 UTC224
    pub fn windows(&self) -> impl Iterator<Item = (WindowId, &Window)> {
9baaafa4b167atagen2026-10-16 10:04 UTC225
        self.windows.iter()
9baaafa4b167atagen2026-10-16 10:04 UTC226
    }
9baaafa4b167atagen2026-10-16 10:04 UTC227
9baaafa4b167atagen2026-10-16 10:04 UTC228
    pub fn output(&self, o: OutputId) -> Option<&Output> {
9baaafa4b167atagen2026-10-16 10:04 UTC229
        self.outputs.get(o)
9baaafa4b167atagen2026-10-16 10:04 UTC230
    }
9baaafa4b167atagen2026-10-16 10:04 UTC231
9baaafa4b167atagen2026-10-16 10:04 UTC232
    pub fn outputs(&self) -> impl Iterator<Item = (OutputId, &Output)> {
9baaafa4b167atagen2026-10-16 10:04 UTC233
        self.outputs.iter()
9baaafa4b167atagen2026-10-16 10:04 UTC234
    }
9baaafa4b167atagen2026-10-16 10:04 UTC235
9baaafa4b167atagen2026-10-16 10:04 UTC236
    pub fn scratchpads(&self) -> &[Scratchpad] {
9baaafa4b167atagen2026-10-16 10:04 UTC237
        &self.scratchpads
9baaafa4b167atagen2026-10-16 10:04 UTC238
    }
9baaafa4b167atagen2026-10-16 10:04 UTC239
9baaafa4b167atagen2026-10-16 10:04 UTC240
    pub fn active_output(&self) -> Option<OutputId> {
9baaafa4b167atagen2026-10-16 10:04 UTC241
        self.active
9baaafa4b167atagen2026-10-16 10:04 UTC242
    }
9baaafa4b167atagen2026-10-16 10:04 UTC243
9baaafa4b167atagen2026-10-16 10:04 UTC244
    pub fn set_active_output(&mut self, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC245
        if self.outputs.contains_key(o) {
9baaafa4b167atagen2026-10-16 10:04 UTC246
            self.active = Some(o);
9baaafa4b167atagen2026-10-16 10:04 UTC247
        }
9baaafa4b167atagen2026-10-16 10:04 UTC248
    }
9baaafa4b167atagen2026-10-16 10:04 UTC249
9baaafa4b167atagen2026-10-16 10:04 UTC250
    pub fn is_viewed(&self, o: OutputId, tag: TagId) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC251
        tag == self.homeless
9baaafa4b167atagen2026-10-16 10:04 UTC252
            || self
9baaafa4b167atagen2026-10-16 10:04 UTC253
                .outputs
9baaafa4b167atagen2026-10-16 10:04 UTC254
                .get(o)
9baaafa4b167atagen2026-10-16 10:04 UTC255
                .is_some_and(|out| out.viewed.contains(&tag))
9baaafa4b167atagen2026-10-16 10:04 UTC256
    }
9baaafa4b167atagen2026-10-16 10:04 UTC257
9baaafa4b167atagen2026-10-16 10:04 UTC258
    /// Views `tag` alone, besides the homeless tag.
9baaafa4b167atagen2026-10-16 10:04 UTC259
    pub fn view(&mut self, o: OutputId, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC260
        let Some(out) = self.outputs.get_mut(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC261
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC262
        };
9baaafa4b167atagen2026-10-16 10:04 UTC263
        out.viewed.clear();
9baaafa4b167atagen2026-10-16 10:04 UTC264
        if tag != self.homeless {
9baaafa4b167atagen2026-10-16 10:04 UTC265
            out.viewed.push(tag);
9baaafa4b167atagen2026-10-16 10:04 UTC266
        }
9baaafa4b167atagen2026-10-16 10:04 UTC267
        self.tags[tag].urgent = false;
9baaafa4b167atagen2026-10-16 10:04 UTC268
    }
9baaafa4b167atagen2026-10-16 10:04 UTC269
9baaafa4b167atagen2026-10-16 10:04 UTC270
    pub fn toggle_view(&mut self, o: OutputId, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC271
        if tag == self.homeless {
9baaafa4b167atagen2026-10-16 10:04 UTC272
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC273
        }
9baaafa4b167atagen2026-10-16 10:04 UTC274
        let Some(out) = self.outputs.get_mut(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC275
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC276
        };
9baaafa4b167atagen2026-10-16 10:04 UTC277
        if let Some(i) = out.viewed.iter().position(|&t| t == tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC278
            out.viewed.remove(i);
9baaafa4b167atagen2026-10-16 10:04 UTC279
        } else {
9baaafa4b167atagen2026-10-16 10:04 UTC280
            out.viewed.push(tag);
9baaafa4b167atagen2026-10-16 10:04 UTC281
            self.tags[tag].urgent = false;
9baaafa4b167atagen2026-10-16 10:04 UTC282
        }
9baaafa4b167atagen2026-10-16 10:04 UTC283
    }
9baaafa4b167atagen2026-10-16 10:04 UTC284
5ab6f980c72fatagen2026-10-17 10:20 UTC285
    /// Brings `w`'s tag into view beside the others when `w` is the only
5ab6f980c72fatagen2026-10-17 10:20 UTC286
    /// window on it there.
5ab6f980c72fatagen2026-10-17 10:20 UTC287
    pub fn view_if_alone(&mut self, w: WindowId) {
5ab6f980c72fatagen2026-10-17 10:20 UTC288
        let Some(win) = self.windows.get(w) else {
5ab6f980c72fatagen2026-10-17 10:20 UTC289
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC290
        };
5ab6f980c72fatagen2026-10-17 10:20 UTC291
        let (Some(o), tag) = (win.output, win.tag) else {
5ab6f980c72fatagen2026-10-17 10:20 UTC292
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC293
        };
5ab6f980c72fatagen2026-10-17 10:20 UTC294
        if win.scratchpad.is_some() || self.is_viewed(o, tag) {
5ab6f980c72fatagen2026-10-17 10:20 UTC295
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC296
        }
cadf3a07a5dcatagen2026-10-19 10:29 UTC297
        let alone = !self.windows.iter().any(|(x, v)| {
cadf3a07a5dcatagen2026-10-19 10:29 UTC298
            x != w && v.output == Some(o) && v.tag == tag && v.scratchpad.is_none() && !v.hidden
cadf3a07a5dcatagen2026-10-19 10:29 UTC299
        });
5ab6f980c72fatagen2026-10-17 10:20 UTC300
        if alone {
5ab6f980c72fatagen2026-10-17 10:20 UTC301
            self.toggle_view(o, tag);
5ab6f980c72fatagen2026-10-17 10:20 UTC302
        }
5ab6f980c72fatagen2026-10-17 10:20 UTC303
    }
5ab6f980c72fatagen2026-10-17 10:20 UTC304
bae1138a3e88atagen2026-10-19 11:47 UTC305
    /// The tag a launch from `o` belongs to, the focused window's tag or else
bae1138a3e88atagen2026-10-19 11:47 UTC306
    /// homeless.
4e5afcada08batagen2026-10-16 10:14 UTC307
    pub fn launch_tag(&self, o: OutputId) -> TagId {
4e5afcada08batagen2026-10-16 10:14 UTC308
        self.focused_on(o)
4e5afcada08batagen2026-10-16 10:14 UTC309
            .map(|w| &self.windows[w])
bae1138a3e88atagen2026-10-19 11:47 UTC310
            .filter(|w| w.scratchpad.is_none() && !w.sticky)
bae1138a3e88atagen2026-10-19 11:47 UTC311
            .map_or(self.homeless, |w| w.tag)
4e5afcada08batagen2026-10-16 10:14 UTC312
    }
4e5afcada08batagen2026-10-16 10:14 UTC313
9baaafa4b167atagen2026-10-16 10:04 UTC314
    pub fn view_everywhere(&mut self, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC315
        for o in self.outputs.keys().collect::<Vec<_>>() {
9baaafa4b167atagen2026-10-16 10:04 UTC316
            self.view(o, tag);
9baaafa4b167atagen2026-10-16 10:04 UTC317
        }
9baaafa4b167atagen2026-10-16 10:04 UTC318
    }
9baaafa4b167atagen2026-10-16 10:04 UTC319
9baaafa4b167atagen2026-10-16 10:04 UTC320
    pub fn visible(&self, w: WindowId) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC321
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC322
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC323
        };
1c7e3075e57fatagen2026-10-19 10:28 UTC324
        let Some(o) = win.output.filter(|_| !win.hidden) else {
9baaafa4b167atagen2026-10-16 10:04 UTC325
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC326
        };
9baaafa4b167atagen2026-10-16 10:04 UTC327
        match win.scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC328
            Some(s) => {
9baaafa4b167atagen2026-10-16 10:04 UTC329
                let sp = &self.scratchpads[s];
9baaafa4b167atagen2026-10-16 10:04 UTC330
                sp.output == Some(o) && sp.shown.map(|i| sp.windows[i]) == Some(w)
9baaafa4b167atagen2026-10-16 10:04 UTC331
            }
9baaafa4b167atagen2026-10-16 10:04 UTC332
            None => win.sticky || self.is_viewed(o, win.tag),
9baaafa4b167atagen2026-10-16 10:04 UTC333
        }
9baaafa4b167atagen2026-10-16 10:04 UTC334
    }
9baaafa4b167atagen2026-10-16 10:04 UTC335
9baaafa4b167atagen2026-10-16 10:04 UTC336
    /// Adds a window on `output` (default the active one) and `tag` (default
9baaafa4b167atagen2026-10-16 10:04 UTC337
    /// homeless). A window landing on a tag its output is not viewing marks
9baaafa4b167atagen2026-10-16 10:04 UTC338
    /// the tag urgent.
9baaafa4b167atagen2026-10-16 10:04 UTC339
    pub fn add_window(
9baaafa4b167atagen2026-10-16 10:04 UTC340
        &mut self,
9baaafa4b167atagen2026-10-16 10:04 UTC341
        output: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC342
        tag: Option<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC343
        floating: Option<Rect>,
9baaafa4b167atagen2026-10-16 10:04 UTC344
    ) -> WindowId {
9baaafa4b167atagen2026-10-16 10:04 UTC345
        let output = output
9baaafa4b167atagen2026-10-16 10:04 UTC346
            .filter(|&o| self.outputs.contains_key(o))
9baaafa4b167atagen2026-10-16 10:04 UTC347
            .or(self.active);
9baaafa4b167atagen2026-10-16 10:04 UTC348
        let tag = tag
9baaafa4b167atagen2026-10-16 10:04 UTC349
            .filter(|&t| self.tags.contains_key(t))
9baaafa4b167atagen2026-10-16 10:04 UTC350
            .unwrap_or(self.homeless);
9baaafa4b167atagen2026-10-16 10:04 UTC351
        let w = self.windows.insert(Window {
9baaafa4b167atagen2026-10-16 10:04 UTC352
            output,
9baaafa4b167atagen2026-10-16 10:04 UTC353
            tag,
9baaafa4b167atagen2026-10-16 10:04 UTC354
            floating: floating.is_some(),
9baaafa4b167atagen2026-10-16 10:04 UTC355
            float_rect: floating,
754ca2132943atagen2026-10-19 07:29 UTC356
            float_default: false,
9baaafa4b167atagen2026-10-16 10:04 UTC357
            fullscreen: false,
b44dde08d7acatagen2026-10-19 07:38 UTC358
            parent: None,
ecb0c8c1f31datagen2026-10-19 10:15 UTC359
            modal: false,
9baaafa4b167atagen2026-10-16 10:04 UTC360
            sticky: false,
9baaafa4b167atagen2026-10-16 10:04 UTC361
            scratchpad: None,
9fdf9584d2bdatagen2026-10-19 07:38 UTC362
            urgent: false,
1c7e3075e57fatagen2026-10-19 10:28 UTC363
            hidden: false,
9baaafa4b167atagen2026-10-16 10:04 UTC364
            evicted_from: None,
9baaafa4b167atagen2026-10-16 10:04 UTC365
        });
9baaafa4b167atagen2026-10-16 10:04 UTC366
        if let Some(o) = output {
9baaafa4b167atagen2026-10-16 10:04 UTC367
            if !self.is_viewed(o, tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC368
                self.tags[tag].urgent = true;
9baaafa4b167atagen2026-10-16 10:04 UTC369
            }
9baaafa4b167atagen2026-10-16 10:04 UTC370
            self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC371
        }
9baaafa4b167atagen2026-10-16 10:04 UTC372
        w
9baaafa4b167atagen2026-10-16 10:04 UTC373
    }
9baaafa4b167atagen2026-10-16 10:04 UTC374
9baaafa4b167atagen2026-10-16 10:04 UTC375
    pub fn remove_window(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC376
        if !self.windows.contains_key(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC377
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC378
        }
9baaafa4b167atagen2026-10-16 10:04 UTC379
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC380
        self.history.retain(|&h| h != w);
ecb0c8c1f31datagen2026-10-19 10:15 UTC381
        self.modals -= usize::from(self.windows[w].modal);
9baaafa4b167atagen2026-10-16 10:04 UTC382
        if let Some(s) = self.windows[w].scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC383
            self.leave_scratchpad(s, w);
9baaafa4b167atagen2026-10-16 10:04 UTC384
        }
b44dde08d7acatagen2026-10-19 07:38 UTC385
        for win in self.windows.values_mut() {
b44dde08d7acatagen2026-10-19 07:38 UTC386
            if win.parent == Some(w) {
b44dde08d7acatagen2026-10-19 07:38 UTC387
                win.parent = None;
b44dde08d7acatagen2026-10-19 07:38 UTC388
            }
b44dde08d7acatagen2026-10-19 07:38 UTC389
        }
9baaafa4b167atagen2026-10-16 10:04 UTC390
        self.windows.remove(w);
9baaafa4b167atagen2026-10-16 10:04 UTC391
    }
9baaafa4b167atagen2026-10-16 10:04 UTC392
9baaafa4b167atagen2026-10-16 10:04 UTC393
    /// Puts a window into its output's tree or floating stack. Tiled windows
9baaafa4b167atagen2026-10-16 10:04 UTC394
    /// go next to the focused window if both are visible, else next to the
9baaafa4b167atagen2026-10-16 10:04 UTC395
    /// latest window of their tag, else into a new row.
9baaafa4b167atagen2026-10-16 10:04 UTC396
    fn place(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC397
        let win = &self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC398
        let Some(o) = win.output else { return };
9baaafa4b167atagen2026-10-16 10:04 UTC399
        if win.scratchpad.is_some() {
9baaafa4b167atagen2026-10-16 10:04 UTC400
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC401
        }
9baaafa4b167atagen2026-10-16 10:04 UTC402
        if win.floating {
243161cd0662atagen2026-10-19 10:41 UTC403
            let usable = self.outputs[o].usable();
243161cd0662atagen2026-10-19 10:41 UTC404
            let win = &mut self.windows[w];
243161cd0662atagen2026-10-19 10:41 UTC405
            match win.float_rect {
243161cd0662atagen2026-10-19 10:41 UTC406
                Some(rect) => win.float_rect = Some(clamp_float(rect, usable)),
243161cd0662atagen2026-10-19 10:41 UTC407
                None => {
243161cd0662atagen2026-10-19 10:41 UTC408
                    win.float_rect = Some(centred(usable, 0.6));
243161cd0662atagen2026-10-19 10:41 UTC409
                    win.float_default = true;
243161cd0662atagen2026-10-19 10:41 UTC410
                }
9baaafa4b167atagen2026-10-16 10:04 UTC411
            }
9baaafa4b167atagen2026-10-16 10:04 UTC412
            self.outputs[o].floating.push(w);
9baaafa4b167atagen2026-10-16 10:04 UTC413
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC414
        }
9baaafa4b167atagen2026-10-16 10:04 UTC415
        let tag = win.tag;
9baaafa4b167atagen2026-10-16 10:04 UTC416
        let tree = &self.outputs[o].tree;
9baaafa4b167atagen2026-10-16 10:04 UTC417
        let focused = self
9baaafa4b167atagen2026-10-16 10:04 UTC418
            .focused()
9baaafa4b167atagen2026-10-16 10:04 UTC419
            .filter(|&f| tree.contains(f) && self.is_viewed(o, tag));
9baaafa4b167atagen2026-10-16 10:04 UTC420
        let anchor = focused.or_else(|| {
9baaafa4b167atagen2026-10-16 10:04 UTC421
            self.history
9baaafa4b167atagen2026-10-16 10:04 UTC422
                .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC423
                .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC424
                .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC425
                .find(|&h| tree.contains(h) && self.windows[h].tag == tag)
9baaafa4b167atagen2026-10-16 10:04 UTC426
        });
86ef802692f5atagen2026-10-19 07:43 UTC427
        let split = anchor
86ef802692f5atagen2026-10-19 07:43 UTC428
            .filter(|_| self.autotile)
86ef802692f5atagen2026-10-19 07:43 UTC429
            .and_then(|a| self.split_axis(o, a));
9d1128e3c227atagen2026-10-24 06:53 UTC430
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9d1128e3c227atagen2026-10-24 06:53 UTC431
        let vis = self.vis_on(o);
86ef802692f5atagen2026-10-19 07:43 UTC432
        match (anchor, split) {
9d1128e3c227atagen2026-10-24 06:53 UTC433
            (Some(a), Some(axis)) => tree.insert_split(w, a, axis, vis),
9d1128e3c227atagen2026-10-24 06:53 UTC434
            (Some(a), None) => tree.insert_after(w, a, vis),
9d1128e3c227atagen2026-10-24 06:53 UTC435
            (None, _) => tree.insert_row(w, vis),
9baaafa4b167atagen2026-10-16 10:04 UTC436
        }
9d1128e3c227atagen2026-10-24 06:53 UTC437
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC438
    }
9baaafa4b167atagen2026-10-16 10:04 UTC439
86ef802692f5atagen2026-10-19 07:43 UTC440
    /// The axis a window opening beside `anchor` should split it along, the
86ef802692f5atagen2026-10-19 07:43 UTC441
    /// way the anchor is laid out right now. Hidden anchors have no shape.
86ef802692f5atagen2026-10-19 07:43 UTC442
    fn split_axis(&self, o: OutputId, anchor: WindowId) -> Option<Axis> {
86ef802692f5atagen2026-10-19 07:43 UTC443
        let rect = self
86ef802692f5atagen2026-10-19 07:43 UTC444
            .arrange(o)
86ef802692f5atagen2026-10-19 07:43 UTC445
            .tiled
86ef802692f5atagen2026-10-19 07:43 UTC446
            .iter()
86ef802692f5atagen2026-10-19 07:43 UTC447
            .find(|p| p.window == anchor && p.visible)?
86ef802692f5atagen2026-10-19 07:43 UTC448
            .rect;
86ef802692f5atagen2026-10-19 07:43 UTC449
        let axis = if rect.w > rect.h {
86ef802692f5atagen2026-10-19 07:43 UTC450
            Axis::Horizontal
86ef802692f5atagen2026-10-19 07:43 UTC451
        } else {
86ef802692f5atagen2026-10-19 07:43 UTC452
            Axis::Vertical
86ef802692f5atagen2026-10-19 07:43 UTC453
        };
86ef802692f5atagen2026-10-19 07:43 UTC454
        (rect.w > 0.0 && rect.h > 0.0).then_some(axis)
86ef802692f5atagen2026-10-19 07:43 UTC455
    }
86ef802692f5atagen2026-10-19 07:43 UTC456
9baaafa4b167atagen2026-10-16 10:04 UTC457
    fn unplace(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC458
        let Some(o) = self.windows[w].output else {
9baaafa4b167atagen2026-10-16 10:04 UTC459
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC460
        };
9d1128e3c227atagen2026-10-24 06:53 UTC461
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9d1128e3c227atagen2026-10-24 06:53 UTC462
        tree.remove(w, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC463
        let out = &mut self.outputs[o];
9d1128e3c227atagen2026-10-24 06:53 UTC464
        out.tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC465
        out.floating.retain(|&f| f != w);
9baaafa4b167atagen2026-10-16 10:04 UTC466
    }
9baaafa4b167atagen2026-10-16 10:04 UTC467
ecb0c8c1f31datagen2026-10-19 10:15 UTC468
    /// Focuses `w`, or the deepest modal dialog over it, which is returned.
1c7e3075e57fatagen2026-10-19 10:28 UTC469
    /// The windows between stay below it in the history, and a modal dialog
1c7e3075e57fatagen2026-10-19 10:28 UTC470
    /// focused on its own brings its parent up beneath it, so closing the
ecb0c8c1f31datagen2026-10-19 10:15 UTC471
    /// dialog hands focus back to its parent.
ecb0c8c1f31datagen2026-10-19 10:15 UTC472
    pub fn focus(&mut self, mut w: WindowId) -> Option<WindowId> {
692a9dda8779atagen2026-10-20 11:44 UTC473
        if self.eclipsed(w) {
692a9dda8779atagen2026-10-20 11:44 UTC474
            return None;
692a9dda8779atagen2026-10-20 11:44 UTC475
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC476
        loop {
ecb0c8c1f31datagen2026-10-19 10:15 UTC477
            let win = self.windows.get(w)?;
ecb0c8c1f31datagen2026-10-19 10:15 UTC478
            self.active = win.output.or(self.active);
ecb0c8c1f31datagen2026-10-19 10:15 UTC479
            if let Some(o) = win.output {
ecb0c8c1f31datagen2026-10-19 10:15 UTC480
                let stack = &mut self.outputs[o].floating;
ecb0c8c1f31datagen2026-10-19 10:15 UTC481
                if let Some(i) = stack.iter().position(|&f| f == w) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC482
                    stack.remove(i);
ecb0c8c1f31datagen2026-10-19 10:15 UTC483
                    stack.push(w);
ecb0c8c1f31datagen2026-10-19 10:15 UTC484
                }
ecb0c8c1f31datagen2026-10-19 10:15 UTC485
            }
1c7e3075e57fatagen2026-10-19 10:28 UTC486
            let parent = win.parent.filter(|_| win.modal);
ecb0c8c1f31datagen2026-10-19 10:15 UTC487
            self.history.retain(|&h| h != w);
ecb0c8c1f31datagen2026-10-19 10:15 UTC488
            self.history.push(w);
1c7e3075e57fatagen2026-10-19 10:28 UTC489
            if let Some(p) = parent {
1c7e3075e57fatagen2026-10-19 10:28 UTC490
                self.history.retain(|&h| h != p);
1c7e3075e57fatagen2026-10-19 10:28 UTC491
                self.history.insert(self.history.len() - 1, p);
1c7e3075e57fatagen2026-10-19 10:28 UTC492
            }
ecb0c8c1f31datagen2026-10-19 10:15 UTC493
            self.windows[w].urgent = false;
ecb0c8c1f31datagen2026-10-19 10:15 UTC494
            match self.modal_child(w) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC495
                Some(child) => w = child,
ecb0c8c1f31datagen2026-10-19 10:15 UTC496
                None => return Some(w),
9baaafa4b167atagen2026-10-16 10:04 UTC497
            }
9baaafa4b167atagen2026-10-16 10:04 UTC498
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC499
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC500
ecb0c8c1f31datagen2026-10-19 10:15 UTC501
    /// Marks `w` as a modal dialog of its parent, or not.
ecb0c8c1f31datagen2026-10-19 10:15 UTC502
    pub fn set_modal(&mut self, w: WindowId, modal: bool) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC503
        if let Some(win) = self.windows.get_mut(w)
ecb0c8c1f31datagen2026-10-19 10:15 UTC504
            && std::mem::replace(&mut win.modal, modal) != modal
ecb0c8c1f31datagen2026-10-19 10:15 UTC505
        {
ecb0c8c1f31datagen2026-10-19 10:15 UTC506
            self.modals = if modal {
ecb0c8c1f31datagen2026-10-19 10:15 UTC507
                self.modals + 1
ecb0c8c1f31datagen2026-10-19 10:15 UTC508
            } else {
ecb0c8c1f31datagen2026-10-19 10:15 UTC509
                self.modals - 1
ecb0c8c1f31datagen2026-10-19 10:15 UTC510
            };
ecb0c8c1f31datagen2026-10-19 10:15 UTC511
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC512
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC513
ecb0c8c1f31datagen2026-10-19 10:15 UTC514
    /// The visible modal dialog of `w` on its output, the most recently
ecb0c8c1f31datagen2026-10-19 10:15 UTC515
    /// focused if it has several.
ecb0c8c1f31datagen2026-10-19 10:15 UTC516
    pub fn modal_child(&self, w: WindowId) -> Option<WindowId> {
ecb0c8c1f31datagen2026-10-19 10:15 UTC517
        if self.modals == 0 {
ecb0c8c1f31datagen2026-10-19 10:15 UTC518
            return None;
ecb0c8c1f31datagen2026-10-19 10:15 UTC519
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC520
        let output = self.windows.get(w)?.output;
ecb0c8c1f31datagen2026-10-19 10:15 UTC521
        self.windows
ecb0c8c1f31datagen2026-10-19 10:15 UTC522
            .iter()
ecb0c8c1f31datagen2026-10-19 10:15 UTC523
            .filter(|&(c, win)| {
ecb0c8c1f31datagen2026-10-19 10:15 UTC524
                win.modal && win.parent == Some(w) && win.output == output && self.visible(c)
ecb0c8c1f31datagen2026-10-19 10:15 UTC525
            })
ecb0c8c1f31datagen2026-10-19 10:15 UTC526
            .max_by_key(|&(c, _)| self.history.iter().rposition(|&h| h == c))
ecb0c8c1f31datagen2026-10-19 10:15 UTC527
            .map(|(c, _)| c)
ecb0c8c1f31datagen2026-10-19 10:15 UTC528
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC529
ecb0c8c1f31datagen2026-10-19 10:15 UTC530
    /// The window that takes focus aimed at `w`, the deepest modal dialog
ecb0c8c1f31datagen2026-10-19 10:15 UTC531
    /// over it or `w` itself.
ecb0c8c1f31datagen2026-10-19 10:15 UTC532
    pub fn modal_leaf(&self, mut w: WindowId) -> WindowId {
ecb0c8c1f31datagen2026-10-19 10:15 UTC533
        while let Some(child) = self.modal_child(w) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC534
            w = child;
ecb0c8c1f31datagen2026-10-19 10:15 UTC535
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC536
        w
ecb0c8c1f31datagen2026-10-19 10:15 UTC537
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC538
ecb0c8c1f31datagen2026-10-19 10:15 UTC539
    /// Whether focus aimed at `w` would change nothing.
ecb0c8c1f31datagen2026-10-19 10:15 UTC540
    pub fn is_focused(&self, w: WindowId) -> bool {
ecb0c8c1f31datagen2026-10-19 10:15 UTC541
        self.focused() == Some(self.modal_leaf(w))
9baaafa4b167atagen2026-10-16 10:04 UTC542
    }
9baaafa4b167atagen2026-10-16 10:04 UTC543
adcf397e4f74atagen2026-10-18 09:56 UTC544
    /// The most recently focused visible window on the active output. Focus
adcf397e4f74atagen2026-10-18 09:56 UTC545
    /// falling back to another output would leave keybinds acting on one
adcf397e4f74atagen2026-10-18 09:56 UTC546
    /// output while the keyboard and pointer sit on another.
9baaafa4b167atagen2026-10-16 10:04 UTC547
    pub fn focused(&self) -> Option<WindowId> {
adcf397e4f74atagen2026-10-18 09:56 UTC548
        self.focused_on(self.active?)
9baaafa4b167atagen2026-10-16 10:04 UTC549
    }
9baaafa4b167atagen2026-10-16 10:04 UTC550
9baaafa4b167atagen2026-10-16 10:04 UTC551
    /// The most recently focused visible window on `o`.
9baaafa4b167atagen2026-10-16 10:04 UTC552
    pub fn focused_on(&self, o: OutputId) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC553
        self.history
9baaafa4b167atagen2026-10-16 10:04 UTC554
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC555
            .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC556
            .copied()
692a9dda8779atagen2026-10-20 11:44 UTC557
            .find(|&w| self.windows[w].output == Some(o) && self.visible(w) && !self.eclipsed(w))
ecb0c8c1f31datagen2026-10-19 10:15 UTC558
            .map(|w| self.modal_leaf(w))
9baaafa4b167atagen2026-10-16 10:04 UTC559
    }
9baaafa4b167atagen2026-10-16 10:04 UTC560
af095d8ce0fcatagen2026-10-28 04:02 UTC561
    /// Focuses the nearest visible window in `dir` on the focused window's
af095d8ce0fcatagen2026-10-28 04:02 UTC562
    /// layer, tiled or floating, crossing to the next output when there is
af095d8ce0fcatagen2026-10-28 04:02 UTC563
    /// none on this one.
9baaafa4b167atagen2026-10-16 10:04 UTC564
    pub fn focus_dir(&mut self, dir: Dir) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC565
        let o = self.active?;
ecb0c8c1f31datagen2026-10-19 10:15 UTC566
        let current = self.focused_on(o);
ecb0c8c1f31datagen2026-10-19 10:15 UTC567
        let from = current.and_then(|f| self.rect_of(f));
9baaafa4b167atagen2026-10-16 10:04 UTC568
        let target = from.and_then(|from| {
9baaafa4b167atagen2026-10-16 10:04 UTC569
            let arr = self.arrange(o);
af095d8ce0fcatagen2026-10-28 04:02 UTC570
            let floating = current.is_some_and(|f| arr.floating.iter().any(|p| p.0 == f));
af095d8ce0fcatagen2026-10-28 04:02 UTC571
            let candidates = self
af095d8ce0fcatagen2026-10-28 04:02 UTC572
                .layer(&arr, floating)
af095d8ce0fcatagen2026-10-28 04:02 UTC573
                .filter(|&(w, _)| Some(self.modal_leaf(w)) != current);
af095d8ce0fcatagen2026-10-28 04:02 UTC574
            if floating {
af095d8ce0fcatagen2026-10-28 04:02 UTC575
                nearest_centre(from, dir, candidates)
af095d8ce0fcatagen2026-10-28 04:02 UTC576
            } else {
af095d8ce0fcatagen2026-10-28 04:02 UTC577
                nearest(from, dir, candidates)
af095d8ce0fcatagen2026-10-28 04:02 UTC578
            }
9baaafa4b167atagen2026-10-16 10:04 UTC579
        });
9baaafa4b167atagen2026-10-16 10:04 UTC580
        if let Some(w) = target {
ecb0c8c1f31datagen2026-10-19 10:15 UTC581
            return self.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC582
        }
4e5afcada08batagen2026-10-16 10:14 UTC583
        let next = self.output_towards(o, dir)?;
9baaafa4b167atagen2026-10-16 10:04 UTC584
        self.active = Some(next);
9baaafa4b167atagen2026-10-16 10:04 UTC585
        let w = self.focused_on(next);
9baaafa4b167atagen2026-10-16 10:04 UTC586
        if let Some(w) = w {
9baaafa4b167atagen2026-10-16 10:04 UTC587
            self.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC588
        }
9baaafa4b167atagen2026-10-16 10:04 UTC589
        w
9baaafa4b167atagen2026-10-16 10:04 UTC590
    }
9baaafa4b167atagen2026-10-16 10:04 UTC591
af095d8ce0fcatagen2026-10-28 04:02 UTC592
    /// Focuses the most recently focused window on the other layer, floating
af095d8ce0fcatagen2026-10-28 04:02 UTC593
    /// when a tiled window has focus and tiled otherwise.
af095d8ce0fcatagen2026-10-28 04:02 UTC594
    pub fn focus_layer(&mut self) -> Option<WindowId> {
af095d8ce0fcatagen2026-10-28 04:02 UTC595
        let o = self.active?;
af095d8ce0fcatagen2026-10-28 04:02 UTC596
        let arr = self.arrange(o);
af095d8ce0fcatagen2026-10-28 04:02 UTC597
        let floating = self
af095d8ce0fcatagen2026-10-28 04:02 UTC598
            .focused_on(o)
af095d8ce0fcatagen2026-10-28 04:02 UTC599
            .is_some_and(|f| arr.floating.iter().any(|p| p.0 == f));
af095d8ce0fcatagen2026-10-28 04:02 UTC600
        let w = self
af095d8ce0fcatagen2026-10-28 04:02 UTC601
            .layer(&arr, !floating)
af095d8ce0fcatagen2026-10-28 04:02 UTC602
            .map(|(w, _)| w)
af095d8ce0fcatagen2026-10-28 04:02 UTC603
            .max_by_key(|&w| self.history.iter().position(|&h| h == w))?;
af095d8ce0fcatagen2026-10-28 04:02 UTC604
        self.focus(w)
af095d8ce0fcatagen2026-10-28 04:02 UTC605
    }
af095d8ce0fcatagen2026-10-28 04:02 UTC606
af095d8ce0fcatagen2026-10-28 04:02 UTC607
    /// The windows focus can reach on one layer of an arrangement.
af095d8ce0fcatagen2026-10-28 04:02 UTC608
    fn layer<'a>(
af095d8ce0fcatagen2026-10-28 04:02 UTC609
        &'a self,
af095d8ce0fcatagen2026-10-28 04:02 UTC610
        arr: &'a Arrangement,
af095d8ce0fcatagen2026-10-28 04:02 UTC611
        floating: bool,
af095d8ce0fcatagen2026-10-28 04:02 UTC612
    ) -> impl Iterator<Item = (WindowId, Rect)> + 'a {
af095d8ce0fcatagen2026-10-28 04:02 UTC613
        let tiled = arr
af095d8ce0fcatagen2026-10-28 04:02 UTC614
            .tiled
af095d8ce0fcatagen2026-10-28 04:02 UTC615
            .iter()
af095d8ce0fcatagen2026-10-28 04:02 UTC616
            .filter(move |p| !floating && p.visible && !self.eclipsed(p.window))
af095d8ce0fcatagen2026-10-28 04:02 UTC617
            .map(|p| (p.window, p.rect));
af095d8ce0fcatagen2026-10-28 04:02 UTC618
        let floats = arr.floating.iter().copied().filter(move |_| floating);
af095d8ce0fcatagen2026-10-28 04:02 UTC619
        tiled.chain(floats).filter(|&(w, _)| {
af095d8ce0fcatagen2026-10-28 04:02 UTC620
            arr.fullscreen
af095d8ce0fcatagen2026-10-28 04:02 UTC621
                .is_none_or(|f| w == f || arr.over.contains(&w))
af095d8ce0fcatagen2026-10-28 04:02 UTC622
        })
af095d8ce0fcatagen2026-10-28 04:02 UTC623
    }
af095d8ce0fcatagen2026-10-28 04:02 UTC624
4e5afcada08batagen2026-10-16 10:14 UTC625
    pub fn output_towards(&self, from: OutputId, dir: Dir) -> Option<OutputId> {
4e5afcada08batagen2026-10-16 10:14 UTC626
        let here = self.outputs.get(from)?.area;
4e5afcada08batagen2026-10-16 10:14 UTC627
        let others = self
4e5afcada08batagen2026-10-16 10:14 UTC628
            .outputs
4e5afcada08batagen2026-10-16 10:14 UTC629
            .iter()
4e5afcada08batagen2026-10-16 10:14 UTC630
            .filter(|&(id, _)| id != from)
4e5afcada08batagen2026-10-16 10:14 UTC631
            .map(|(id, out)| (id, out.area));
4e5afcada08batagen2026-10-16 10:14 UTC632
        nearest(here, dir, others)
4e5afcada08batagen2026-10-16 10:14 UTC633
    }
4e5afcada08batagen2026-10-16 10:14 UTC634
9baaafa4b167atagen2026-10-16 10:04 UTC635
    fn rect_of(&self, w: WindowId) -> Option<Rect> {
9baaafa4b167atagen2026-10-16 10:04 UTC636
        let o = self.windows.get(w)?.output?;
9baaafa4b167atagen2026-10-16 10:04 UTC637
        let arr = self.arrange(o);
9baaafa4b167atagen2026-10-16 10:04 UTC638
        arr.tiled
9baaafa4b167atagen2026-10-16 10:04 UTC639
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC640
            .find(|p| p.window == w)
9baaafa4b167atagen2026-10-16 10:04 UTC641
            .map(|p| p.rect)
9baaafa4b167atagen2026-10-16 10:04 UTC642
            .or_else(|| arr.floating.iter().find(|f| f.0 == w).map(|f| f.1))
9baaafa4b167atagen2026-10-16 10:04 UTC643
    }
9baaafa4b167atagen2026-10-16 10:04 UTC644
9baaafa4b167atagen2026-10-16 10:04 UTC645
    fn vis_on(&self, o: OutputId) -> impl Fn(WindowId) -> bool + '_ {
9baaafa4b167atagen2026-10-16 10:04 UTC646
        move |w| self.windows[w].output == Some(o) && self.visible(w)
9baaafa4b167atagen2026-10-16 10:04 UTC647
    }
9baaafa4b167atagen2026-10-16 10:04 UTC648
9baaafa4b167atagen2026-10-16 10:04 UTC649
    fn focused_tiled(&self) -> Option<(WindowId, OutputId)> {
9baaafa4b167atagen2026-10-16 10:04 UTC650
        let w = self.focused()?;
9baaafa4b167atagen2026-10-16 10:04 UTC651
        let o = self.windows[w].output?;
9baaafa4b167atagen2026-10-16 10:04 UTC652
        self.outputs[o].tree.contains(w).then_some((w, o))
9baaafa4b167atagen2026-10-16 10:04 UTC653
    }
9baaafa4b167atagen2026-10-16 10:04 UTC654
9baaafa4b167atagen2026-10-16 10:04 UTC655
    pub fn move_window(&mut self, dir: Dir) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC656
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC657
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC658
        };
9baaafa4b167atagen2026-10-16 10:04 UTC659
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9baaafa4b167atagen2026-10-16 10:04 UTC660
        let moved = tree.move_window(w, dir, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC661
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC662
        moved
9baaafa4b167atagen2026-10-16 10:04 UTC663
    }
9baaafa4b167atagen2026-10-16 10:04 UTC664
21e8e8e5aa3datagen2026-10-20 11:44 UTC665
    pub fn move_container(&mut self, dir: Dir, merge: bool) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC666
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC667
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC668
        };
9baaafa4b167atagen2026-10-16 10:04 UTC669
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
21e8e8e5aa3datagen2026-10-20 11:44 UTC670
        let moved = tree.move_container(w, dir, merge, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC671
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC672
        moved
9baaafa4b167atagen2026-10-16 10:04 UTC673
    }
9baaafa4b167atagen2026-10-16 10:04 UTC674
4444c779eb48atagen2026-10-16 12:07 UTC675
    pub fn merge_window(&mut self, dir: Dir) -> bool {
4444c779eb48atagen2026-10-16 12:07 UTC676
        let Some((w, o)) = self.focused_tiled() else {
4444c779eb48atagen2026-10-16 12:07 UTC677
            return false;
4444c779eb48atagen2026-10-16 12:07 UTC678
        };
4444c779eb48atagen2026-10-16 12:07 UTC679
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
4444c779eb48atagen2026-10-16 12:07 UTC680
        let merged = tree.merge_window(w, dir, self.vis_on(o));
4444c779eb48atagen2026-10-16 12:07 UTC681
        self.outputs[o].tree = tree;
4444c779eb48atagen2026-10-16 12:07 UTC682
        merged
4444c779eb48atagen2026-10-16 12:07 UTC683
    }
4444c779eb48atagen2026-10-16 12:07 UTC684
e70a8ec6d58datagen2026-10-28 04:01 UTC685
    /// Whether a tiled window, or a container holding it, is extended over
e70a8ec6d58datagen2026-10-28 04:01 UTC686
    /// others.
e70a8ec6d58datagen2026-10-28 04:01 UTC687
    pub fn extended(&self, w: WindowId) -> bool {
406db966344catagen2026-10-20 11:44 UTC688
        let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
406db966344catagen2026-10-20 11:44 UTC689
            return false;
406db966344catagen2026-10-20 11:44 UTC690
        };
e70a8ec6d58datagen2026-10-28 04:01 UTC691
        self.outputs[o].tree.extended(w, self.vis_on(o))
406db966344catagen2026-10-20 11:44 UTC692
    }
406db966344catagen2026-10-20 11:44 UTC693
692a9dda8779atagen2026-10-20 11:44 UTC694
    /// Whether another window is extended over the tiled window `w`, which
692a9dda8779atagen2026-10-20 11:44 UTC695
    /// leaves it no room and takes it out of reach of focus.
692a9dda8779atagen2026-10-20 11:44 UTC696
    pub fn eclipsed(&self, w: WindowId) -> bool {
692a9dda8779atagen2026-10-20 11:44 UTC697
        let Some(o) = self
692a9dda8779atagen2026-10-20 11:44 UTC698
            .windows
692a9dda8779atagen2026-10-20 11:44 UTC699
            .get(w)
692a9dda8779atagen2026-10-20 11:44 UTC700
            .filter(|w| !w.fullscreen)
692a9dda8779atagen2026-10-20 11:44 UTC701
            .and_then(|w| w.output)
692a9dda8779atagen2026-10-20 11:44 UTC702
        else {
692a9dda8779atagen2026-10-20 11:44 UTC703
            return false;
692a9dda8779atagen2026-10-20 11:44 UTC704
        };
692a9dda8779atagen2026-10-20 11:44 UTC705
        self.outputs[o].tree.eclipsed(w, self.vis_on(o))
692a9dda8779atagen2026-10-20 11:44 UTC706
    }
692a9dda8779atagen2026-10-20 11:44 UTC707
4444c779eb48atagen2026-10-16 12:07 UTC708
    /// Whether a tiled window fills its output through expansion.
4444c779eb48atagen2026-10-16 12:07 UTC709
    pub fn maximized(&self, w: WindowId) -> bool {
4444c779eb48atagen2026-10-16 12:07 UTC710
        let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
4444c779eb48atagen2026-10-16 12:07 UTC711
            return false;
4444c779eb48atagen2026-10-16 12:07 UTC712
        };
54559daf11c3atagen2026-10-17 13:38 UTC713
        self.outputs[o].tree.maximized(w, self.vis_on(o))
4444c779eb48atagen2026-10-16 12:07 UTC714
    }
4444c779eb48atagen2026-10-16 12:07 UTC715
9baaafa4b167atagen2026-10-16 10:04 UTC716
    pub fn resize(&mut self, axis: Axis, delta: f64) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC717
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC718
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC719
        };
9baaafa4b167atagen2026-10-16 10:04 UTC720
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9baaafa4b167atagen2026-10-16 10:04 UTC721
        let done = tree.resize(w, axis, delta, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC722
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC723
        done
9baaafa4b167atagen2026-10-16 10:04 UTC724
    }
9baaafa4b167atagen2026-10-16 10:04 UTC725
b2274115d1c6atagen2026-10-22 10:05 UTC726
    pub fn balance(&mut self) -> bool {
b2274115d1c6atagen2026-10-22 10:05 UTC727
        let Some((w, o)) = self.focused_tiled() else {
b2274115d1c6atagen2026-10-22 10:05 UTC728
            return false;
b2274115d1c6atagen2026-10-22 10:05 UTC729
        };
b2274115d1c6atagen2026-10-22 10:05 UTC730
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
b2274115d1c6atagen2026-10-22 10:05 UTC731
        let done = tree.balance(w, self.vis_on(o));
b2274115d1c6atagen2026-10-22 10:05 UTC732
        self.outputs[o].tree = tree;
b2274115d1c6atagen2026-10-22 10:05 UTC733
        done
b2274115d1c6atagen2026-10-22 10:05 UTC734
    }
b2274115d1c6atagen2026-10-22 10:05 UTC735
abe5e42a95d6atagen2026-10-19 07:52 UTC736
    /// Whether `w` is tiled with a visible window across the split on side `dir`.
abe5e42a95d6atagen2026-10-19 07:52 UTC737
    pub fn has_split(&self, w: WindowId, dir: Dir) -> bool {
abe5e42a95d6atagen2026-10-19 07:52 UTC738
        let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
abe5e42a95d6atagen2026-10-19 07:52 UTC739
            return false;
abe5e42a95d6atagen2026-10-19 07:52 UTC740
        };
abe5e42a95d6atagen2026-10-19 07:52 UTC741
        self.outputs[o].tree.has_split(w, dir, self.vis_on(o))
abe5e42a95d6atagen2026-10-19 07:52 UTC742
    }
abe5e42a95d6atagen2026-10-19 07:52 UTC743
abe5e42a95d6atagen2026-10-19 07:52 UTC744
    /// Moves the split on side `dir` of the tiled window `w` by `px`, positive
abe5e42a95d6atagen2026-10-19 07:52 UTC745
    /// toward the right or bottom.
abe5e42a95d6atagen2026-10-19 07:52 UTC746
    pub fn move_split(&mut self, w: WindowId, dir: Dir, px: f64) -> bool {
abe5e42a95d6atagen2026-10-19 07:52 UTC747
        let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
abe5e42a95d6atagen2026-10-19 07:52 UTC748
            return false;
abe5e42a95d6atagen2026-10-19 07:52 UTC749
        };
abe5e42a95d6atagen2026-10-19 07:52 UTC750
        let area = inset(self.outputs[o].usable(), self.gaps.outer);
abe5e42a95d6atagen2026-10-19 07:52 UTC751
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
abe5e42a95d6atagen2026-10-19 07:52 UTC752
        let moved = tree.move_split(w, dir, px, area, self.gaps.inner, self.vis_on(o));
abe5e42a95d6atagen2026-10-19 07:52 UTC753
        self.outputs[o].tree = tree;
abe5e42a95d6atagen2026-10-19 07:52 UTC754
        moved
abe5e42a95d6atagen2026-10-19 07:52 UTC755
    }
abe5e42a95d6atagen2026-10-19 07:52 UTC756
9baaafa4b167atagen2026-10-16 10:04 UTC757
    pub fn expand(&mut self, scope: ExpandScope) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC758
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC759
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC760
        };
9baaafa4b167atagen2026-10-16 10:04 UTC761
        let (skip, count) = match scope {
9baaafa4b167atagen2026-10-16 10:04 UTC762
            ExpandScope::Window => (0, 1),
4444c779eb48atagen2026-10-16 12:07 UTC763
            ExpandScope::Container => (1, 1),
9baaafa4b167atagen2026-10-16 10:04 UTC764
            ExpandScope::All => (0, usize::MAX),
9baaafa4b167atagen2026-10-16 10:04 UTC765
        };
54559daf11c3atagen2026-10-17 13:38 UTC766
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
54559daf11c3atagen2026-10-17 13:38 UTC767
        let expanded = tree.toggle_expand(w, skip, count, self.vis_on(o));
54559daf11c3atagen2026-10-17 13:38 UTC768
        self.outputs[o].tree = tree;
54559daf11c3atagen2026-10-17 13:38 UTC769
        expanded
9baaafa4b167atagen2026-10-16 10:04 UTC770
    }
9baaafa4b167atagen2026-10-16 10:04 UTC771
9baaafa4b167atagen2026-10-16 10:04 UTC772
    pub fn flip(&mut self, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC773
        if let Some(out) = self.outputs.get_mut(o) {
9baaafa4b167atagen2026-10-16 10:04 UTC774
            out.tree.flip();
9baaafa4b167atagen2026-10-16 10:04 UTC775
        }
9baaafa4b167atagen2026-10-16 10:04 UTC776
    }
9baaafa4b167atagen2026-10-16 10:04 UTC777
1c1f70798e98atagen2026-10-22 05:39 UTC778
    /// Retags a window along with the windows it is extended over. They keep
1c1f70798e98atagen2026-10-22 05:39 UTC779
    /// their slots and disappear if the new tag is not viewed.
9baaafa4b167atagen2026-10-16 10:04 UTC780
    pub fn set_tag(&mut self, w: WindowId, tag: TagId) {
1c1f70798e98atagen2026-10-22 05:39 UTC781
        let Some(win) = self.windows.get(w) else {
1c1f70798e98atagen2026-10-22 05:39 UTC782
            return;
1c1f70798e98atagen2026-10-22 05:39 UTC783
        };
1c1f70798e98atagen2026-10-22 05:39 UTC784
        if !self.tags.contains_key(tag) {
1c1f70798e98atagen2026-10-22 05:39 UTC785
            return;
1c1f70798e98atagen2026-10-22 05:39 UTC786
        }
1c1f70798e98atagen2026-10-22 05:39 UTC787
        let group = match win.output {
1c1f70798e98atagen2026-10-22 05:39 UTC788
            Some(o) => self.outputs[o].tree.group_windows(w, self.vis_on(o)),
1c1f70798e98atagen2026-10-22 05:39 UTC789
            None => vec![w],
1c1f70798e98atagen2026-10-22 05:39 UTC790
        };
1c1f70798e98atagen2026-10-22 05:39 UTC791
        for g in group {
1c1f70798e98atagen2026-10-22 05:39 UTC792
            self.windows[g].tag = tag;
9baaafa4b167atagen2026-10-16 10:04 UTC793
        }
9baaafa4b167atagen2026-10-16 10:04 UTC794
    }
9baaafa4b167atagen2026-10-16 10:04 UTC795
02d5a5d5b17eatagen2026-10-16 22:24 UTC796
    /// Moves a window to `tag` on `o`, marking the tag urgent when the window
02d5a5d5b17eatagen2026-10-16 22:24 UTC797
    /// ends up out of sight.
02d5a5d5b17eatagen2026-10-16 22:24 UTC798
    pub fn relocate(&mut self, w: WindowId, o: Option<OutputId>, tag: TagId) {
02d5a5d5b17eatagen2026-10-16 22:24 UTC799
        if let Some(o) = o {
02d5a5d5b17eatagen2026-10-16 22:24 UTC800
            self.send_to_output(w, o);
02d5a5d5b17eatagen2026-10-16 22:24 UTC801
        }
02d5a5d5b17eatagen2026-10-16 22:24 UTC802
        self.set_tag(w, tag);
02d5a5d5b17eatagen2026-10-16 22:24 UTC803
        self.mark_urgent(w);
02d5a5d5b17eatagen2026-10-16 22:24 UTC804
    }
02d5a5d5b17eatagen2026-10-16 22:24 UTC805
9fdf9584d2bdatagen2026-10-19 07:38 UTC806
    /// Flags a window that wants attention unless it already has the focus,
9fdf9584d2bdatagen2026-10-19 07:38 UTC807
    /// and its tag too when the window is out of sight.
02d5a5d5b17eatagen2026-10-16 22:24 UTC808
    pub fn mark_urgent(&mut self, w: WindowId) {
1c7e3075e57fatagen2026-10-19 10:28 UTC809
        if self.windows.get(w).is_none_or(|win| win.hidden) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC810
            return;
9fdf9584d2bdatagen2026-10-19 07:38 UTC811
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC812
        if self.focused() != Some(w) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC813
            self.windows[w].urgent = true;
9fdf9584d2bdatagen2026-10-19 07:38 UTC814
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC815
        if !self.visible(w) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC816
            let tag = self.windows[w].tag;
02d5a5d5b17eatagen2026-10-16 22:24 UTC817
            self.tags[tag].urgent = true;
02d5a5d5b17eatagen2026-10-16 22:24 UTC818
        }
02d5a5d5b17eatagen2026-10-16 22:24 UTC819
    }
02d5a5d5b17eatagen2026-10-16 22:24 UTC820
9fdf9584d2bdatagen2026-10-19 07:38 UTC821
    /// Drops the flag of the window holding the focus, which can reach it
9fdf9584d2bdatagen2026-10-19 07:38 UTC822
    /// without `focus`, when the window before it closes or its tag is viewed.
9fdf9584d2bdatagen2026-10-19 07:38 UTC823
    pub fn clear_focused_urgent(&mut self) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC824
        if let Some(w) = self.focused() {
9fdf9584d2bdatagen2026-10-19 07:38 UTC825
            self.windows[w].urgent = false;
9fdf9584d2bdatagen2026-10-19 07:38 UTC826
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC827
    }
9fdf9584d2bdatagen2026-10-19 07:38 UTC828
9baaafa4b167atagen2026-10-16 10:04 UTC829
    pub fn send_to_output(&mut self, w: WindowId, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC830
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC831
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC832
        };
9baaafa4b167atagen2026-10-16 10:04 UTC833
        if !self.outputs.contains_key(o) || win.output == Some(o) || win.scratchpad.is_some() {
9baaafa4b167atagen2026-10-16 10:04 UTC834
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC835
        }
4b73e40af1e3atagen2026-10-19 07:44 UTC836
        let from = win.output.map(|f| self.outputs[f].usable());
9baaafa4b167atagen2026-10-16 10:04 UTC837
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC838
        let win = &mut self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC839
        if let (Some(from), Some(rect)) = (from, win.float_rect) {
4b73e40af1e3atagen2026-10-19 07:44 UTC840
            win.float_rect = Some(translate(rect, from, self.outputs[o].usable()));
9baaafa4b167atagen2026-10-16 10:04 UTC841
        }
9baaafa4b167atagen2026-10-16 10:04 UTC842
        win.output = Some(o);
9baaafa4b167atagen2026-10-16 10:04 UTC843
        win.evicted_from = None;
9baaafa4b167atagen2026-10-16 10:04 UTC844
        self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC845
    }
9baaafa4b167atagen2026-10-16 10:04 UTC846
48af562d968batagen2026-10-16 12:39 UTC847
    /// Switches a window between tiled and floating. A scratchpad window
48af562d968batagen2026-10-16 12:39 UTC848
    /// leaves its scratchpad and tiles on the tag being viewed, since its own
48af562d968batagen2026-10-16 12:39 UTC849
    /// tag may well be hidden.
9baaafa4b167atagen2026-10-16 10:04 UTC850
    pub fn toggle_floating(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC851
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC852
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC853
        };
48af562d968batagen2026-10-16 12:39 UTC854
        if let Some(s) = win.scratchpad {
48af562d968batagen2026-10-16 12:39 UTC855
            let o = win.output.or(self.active);
bae1138a3e88atagen2026-10-19 11:47 UTC856
            let tag = o
bae1138a3e88atagen2026-10-19 11:47 UTC857
                .and_then(|o| self.outputs.get(o)?.viewed.first().copied())
bae1138a3e88atagen2026-10-19 11:47 UTC858
                .unwrap_or(self.homeless);
48af562d968batagen2026-10-16 12:39 UTC859
            self.leave_scratchpad(s, w);
48af562d968batagen2026-10-16 12:39 UTC860
            let win = &mut self.windows[w];
48af562d968batagen2026-10-16 12:39 UTC861
            win.output = o;
48af562d968batagen2026-10-16 12:39 UTC862
            win.tag = tag;
48af562d968batagen2026-10-16 12:39 UTC863
            win.floating = false;
48af562d968batagen2026-10-16 12:39 UTC864
            self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC865
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC866
        }
9baaafa4b167atagen2026-10-16 10:04 UTC867
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC868
        let win = &mut self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC869
        win.floating = !win.floating;
9baaafa4b167atagen2026-10-16 10:04 UTC870
        self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC871
    }
9baaafa4b167atagen2026-10-16 10:04 UTC872
9baaafa4b167atagen2026-10-16 10:04 UTC873
    pub fn set_float_rect(&mut self, w: WindowId, rect: Rect) {
9baaafa4b167atagen2026-10-16 10:04 UTC874
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC875
            win.float_rect = Some(rect);
754ca2132943atagen2026-10-19 07:29 UTC876
            win.float_default = false;
9baaafa4b167atagen2026-10-16 10:04 UTC877
        }
9baaafa4b167atagen2026-10-16 10:04 UTC878
    }
9baaafa4b167atagen2026-10-16 10:04 UTC879
fd834695f723atagen2026-10-19 07:49 UTC880
    /// Places a float without settling its size, so a window still deciding
fd834695f723atagen2026-10-19 07:49 UTC881
    /// on one is asked again after being tiled.
fd834695f723atagen2026-10-19 07:49 UTC882
    pub fn hold_float_rect(&mut self, w: WindowId, rect: Rect) {
fd834695f723atagen2026-10-19 07:49 UTC883
        if let Some(win) = self.windows.get_mut(w) {
fd834695f723atagen2026-10-19 07:49 UTC884
            win.float_rect = Some(rect);
fd834695f723atagen2026-10-19 07:49 UTC885
        }
fd834695f723atagen2026-10-19 07:49 UTC886
    }
fd834695f723atagen2026-10-19 07:49 UTC887
9baaafa4b167atagen2026-10-16 10:04 UTC888
    pub fn toggle_fullscreen(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC889
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC890
            win.fullscreen = !win.fullscreen;
9baaafa4b167atagen2026-10-16 10:04 UTC891
        }
9baaafa4b167atagen2026-10-16 10:04 UTC892
    }
9baaafa4b167atagen2026-10-16 10:04 UTC893
9baaafa4b167atagen2026-10-16 10:04 UTC894
    pub fn toggle_sticky(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC895
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC896
            win.sticky = !win.sticky;
9baaafa4b167atagen2026-10-16 10:04 UTC897
        }
9baaafa4b167atagen2026-10-16 10:04 UTC898
    }
9baaafa4b167atagen2026-10-16 10:04 UTC899
1c7e3075e57fatagen2026-10-19 10:28 UTC900
    /// Hides `w` or shows it again, keeping everything else about it.
1c7e3075e57fatagen2026-10-19 10:28 UTC901
    pub fn set_hidden(&mut self, w: WindowId, hidden: bool) {
1c7e3075e57fatagen2026-10-19 10:28 UTC902
        if let Some(win) = self.windows.get_mut(w) {
1c7e3075e57fatagen2026-10-19 10:28 UTC903
            win.hidden = hidden;
1c7e3075e57fatagen2026-10-19 10:28 UTC904
            win.urgent &= !hidden;
1c7e3075e57fatagen2026-10-19 10:28 UTC905
        }
1c7e3075e57fatagen2026-10-19 10:28 UTC906
    }
1c7e3075e57fatagen2026-10-19 10:28 UTC907
9baaafa4b167atagen2026-10-16 10:04 UTC908
    /// Target geometry for everything on `o`, in output-local logical
9baaafa4b167atagen2026-10-16 10:04 UTC909
    /// coordinates offset by the output's area.
9baaafa4b167atagen2026-10-16 10:04 UTC910
    pub fn arrange(&self, o: OutputId) -> Arrangement {
2af1d8c55876atagen2026-10-16 11:05 UTC911
        self.arrange_hiding(o, &[])
2af1d8c55876atagen2026-10-16 11:05 UTC912
    }
2af1d8c55876atagen2026-10-16 11:05 UTC913
2af1d8c55876atagen2026-10-16 11:05 UTC914
    /// Like `arrange` with `hide` treated as hidden, which places them on the
2af1d8c55876atagen2026-10-16 11:05 UTC915
    /// seeds they would grow from.
2af1d8c55876atagen2026-10-16 11:05 UTC916
    pub fn arrange_hiding(&self, o: OutputId, hide: &[WindowId]) -> Arrangement {
9baaafa4b167atagen2026-10-16 10:04 UTC917
        let Some(out) = self.outputs.get(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC918
            return Arrangement::default();
9baaafa4b167atagen2026-10-16 10:04 UTC919
        };
b01f4e7ab7e6atagen2026-10-19 07:44 UTC920
        let area = inset(out.usable(), self.gaps.outer);
2af1d8c55876atagen2026-10-16 11:05 UTC921
        let shown = self.vis_on(o);
2af1d8c55876atagen2026-10-16 11:05 UTC922
        let vis = &|w: WindowId| shown(w) && !hide.contains(&w);
9baaafa4b167atagen2026-10-16 10:04 UTC923
        let tiled = out
9baaafa4b167atagen2026-10-16 10:04 UTC924
            .tree
2af1d8c55876atagen2026-10-16 11:05 UTC925
            .arrange(area, self.gaps.inner, vis)
9baaafa4b167atagen2026-10-16 10:04 UTC926
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC927
            .map(|(window, rect)| Placed {
9baaafa4b167atagen2026-10-16 10:04 UTC928
                window,
9baaafa4b167atagen2026-10-16 10:04 UTC929
                rect,
9baaafa4b167atagen2026-10-16 10:04 UTC930
                visible: vis(window),
9baaafa4b167atagen2026-10-16 10:04 UTC931
            })
9baaafa4b167atagen2026-10-16 10:04 UTC932
            .collect();
5c459f657de6atagen2026-10-19 10:37 UTC933
        let floating = self.floating_on(o, out, vis);
5c459f657de6atagen2026-10-19 10:37 UTC934
        let fullscreen = self.fullscreen_in(vis);
5c459f657de6atagen2026-10-19 10:37 UTC935
        let over = fullscreen.map_or_else(Vec::new, |f| self.above_fullscreen(f, &floating));
5c459f657de6atagen2026-10-19 10:37 UTC936
        Arrangement {
5c459f657de6atagen2026-10-19 10:37 UTC937
            tiled,
5c459f657de6atagen2026-10-19 10:37 UTC938
            floating,
5c459f657de6atagen2026-10-19 10:37 UTC939
            fullscreen,
5c459f657de6atagen2026-10-19 10:37 UTC940
            over,
5c459f657de6atagen2026-10-19 10:37 UTC941
        }
5c459f657de6atagen2026-10-19 10:37 UTC942
    }
5c459f657de6atagen2026-10-19 10:37 UTC943
5c459f657de6atagen2026-10-19 10:37 UTC944
    fn floating_on(
5c459f657de6atagen2026-10-19 10:37 UTC945
        &self,
5c459f657de6atagen2026-10-19 10:37 UTC946
        o: OutputId,
5c459f657de6atagen2026-10-19 10:37 UTC947
        out: &Output,
5c459f657de6atagen2026-10-19 10:37 UTC948
        vis: &dyn Fn(WindowId) -> bool,
5c459f657de6atagen2026-10-19 10:37 UTC949
    ) -> Vec<(WindowId, Rect)> {
9baaafa4b167atagen2026-10-16 10:04 UTC950
        let mut floating: Vec<(WindowId, Rect)> = out
9baaafa4b167atagen2026-10-16 10:04 UTC951
            .floating
9baaafa4b167atagen2026-10-16 10:04 UTC952
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC953
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC954
            .filter(|&w| vis(w))
9baaafa4b167atagen2026-10-16 10:04 UTC955
            .filter_map(|w| Some((w, self.windows[w].float_rect?)))
9baaafa4b167atagen2026-10-16 10:04 UTC956
            .collect();
9baaafa4b167atagen2026-10-16 10:04 UTC957
        for sp in &self.scratchpads {
9baaafa4b167atagen2026-10-16 10:04 UTC958
            if let Some(i) = sp.shown
9baaafa4b167atagen2026-10-16 10:04 UTC959
                && sp.output == Some(o)
1c7e3075e57fatagen2026-10-19 10:28 UTC960
                && !self.windows[sp.windows[i]].hidden
9baaafa4b167atagen2026-10-16 10:04 UTC961
                && let Some(r) = self.windows[sp.windows[i]].float_rect
9baaafa4b167atagen2026-10-16 10:04 UTC962
            {
b001ce7817edatagen2026-10-19 09:23 UTC963
                floating.push((sp.windows[i], centre_in(out.usable(), r.w, r.h)));
9baaafa4b167atagen2026-10-16 10:04 UTC964
            }
9baaafa4b167atagen2026-10-16 10:04 UTC965
        }
5c459f657de6atagen2026-10-19 10:37 UTC966
        floating
5c459f657de6atagen2026-10-19 10:37 UTC967
    }
5c459f657de6atagen2026-10-19 10:37 UTC968
5c459f657de6atagen2026-10-19 10:37 UTC969
    fn fullscreen_in(&self, vis: &dyn Fn(WindowId) -> bool) -> Option<WindowId> {
1c7e3075e57fatagen2026-10-19 10:28 UTC970
        let is_fullscreen = |w: WindowId| self.windows[w].fullscreen && vis(w);
5c459f657de6atagen2026-10-19 10:37 UTC971
        self.history
9baaafa4b167atagen2026-10-16 10:04 UTC972
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC973
            .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC974
            .copied()
1c7e3075e57fatagen2026-10-19 10:28 UTC975
            .find(|&w| is_fullscreen(w))
5c459f657de6atagen2026-10-19 10:37 UTC976
            .or_else(|| self.windows.keys().find(|&w| is_fullscreen(w)))
5c459f657de6atagen2026-10-19 10:37 UTC977
    }
5c459f657de6atagen2026-10-19 10:37 UTC978
5c459f657de6atagen2026-10-19 10:37 UTC979
    fn above_fullscreen(&self, f: WindowId, floating: &[(WindowId, Rect)]) -> Vec<WindowId> {
5c459f657de6atagen2026-10-19 10:37 UTC980
        floating
5c459f657de6atagen2026-10-19 10:37 UTC981
            .iter()
5c459f657de6atagen2026-10-19 10:37 UTC982
            .map(|&(w, _)| w)
5c459f657de6atagen2026-10-19 10:37 UTC983
            .filter(|&w| w != f && (self.descends(w, f) || self.windows[w].scratchpad.is_some()))
5c459f657de6atagen2026-10-19 10:37 UTC984
            .collect()
5c459f657de6atagen2026-10-19 10:37 UTC985
    }
5c459f657de6atagen2026-10-19 10:37 UTC986
5c459f657de6atagen2026-10-19 10:37 UTC987
    /// The fullscreen window of the output under (x, y), with the floating
5c459f657de6atagen2026-10-19 10:37 UTC988
    /// transients that stay above it. Cheaper than arranging the output.
5c459f657de6atagen2026-10-19 10:37 UTC989
    pub fn fullscreen_at(&self, x: f64, y: f64) -> Option<(WindowId, Vec<WindowId>)> {
5c459f657de6atagen2026-10-19 10:37 UTC990
        let (o, out) = self
5c459f657de6atagen2026-10-19 10:37 UTC991
            .outputs
5c459f657de6atagen2026-10-19 10:37 UTC992
            .iter()
5c459f657de6atagen2026-10-19 10:37 UTC993
            .find(|(_, out)| out.area.contains(x, y))?;
5c459f657de6atagen2026-10-19 10:37 UTC994
        let vis = self.vis_on(o);
5c459f657de6atagen2026-10-19 10:37 UTC995
        let f = self.fullscreen_in(&vis)?;
5c459f657de6atagen2026-10-19 10:37 UTC996
        Some((f, self.above_fullscreen(f, &self.floating_on(o, out, &vis))))
b44dde08d7acatagen2026-10-19 07:38 UTC997
    }
b44dde08d7acatagen2026-10-19 07:38 UTC998
b44dde08d7acatagen2026-10-19 07:38 UTC999
    /// Records that `w` is a transient of `parent`.
b44dde08d7acatagen2026-10-19 07:38 UTC1000
    pub fn set_parent(&mut self, w: WindowId, parent: Option<WindowId>) {
b44dde08d7acatagen2026-10-19 07:38 UTC1001
        if parent.is_some_and(|p| p == w || self.descends(p, w)) {
b44dde08d7acatagen2026-10-19 07:38 UTC1002
            return;
b44dde08d7acatagen2026-10-19 07:38 UTC1003
        }
b44dde08d7acatagen2026-10-19 07:38 UTC1004
        if let Some(win) = self.windows.get_mut(w) {
b44dde08d7acatagen2026-10-19 07:38 UTC1005
            win.parent = parent;
b44dde08d7acatagen2026-10-19 07:38 UTC1006
        }
b44dde08d7acatagen2026-10-19 07:38 UTC1007
    }
b44dde08d7acatagen2026-10-19 07:38 UTC1008
b44dde08d7acatagen2026-10-19 07:38 UTC1009
    fn descends(&self, mut w: WindowId, of: WindowId) -> bool {
b44dde08d7acatagen2026-10-19 07:38 UTC1010
        for _ in 0..self.windows.len() {
b44dde08d7acatagen2026-10-19 07:38 UTC1011
            match self.windows.get(w).and_then(|x| x.parent) {
b44dde08d7acatagen2026-10-19 07:38 UTC1012
                Some(p) if p == of => return true,
b44dde08d7acatagen2026-10-19 07:38 UTC1013
                Some(p) => w = p,
b44dde08d7acatagen2026-10-19 07:38 UTC1014
                None => return false,
b44dde08d7acatagen2026-10-19 07:38 UTC1015
            }
9baaafa4b167atagen2026-10-16 10:04 UTC1016
        }
b44dde08d7acatagen2026-10-19 07:38 UTC1017
        false
b44dde08d7acatagen2026-10-19 07:38 UTC1018
    }
b44dde08d7acatagen2026-10-19 07:38 UTC1019
f50ebc6999a6atagen2026-10-19 07:51 UTC1020
    /// Moves focus to a fullscreen `w` when it covers the focused window,
f50ebc6999a6atagen2026-10-19 07:51 UTC1021
    /// unless that is one of its own transients.
f50ebc6999a6atagen2026-10-19 07:51 UTC1022
    pub fn focus_if_covering(&mut self, w: WindowId) {
f50ebc6999a6atagen2026-10-19 07:51 UTC1023
        let Some(win) = self.windows.get(w).filter(|win| win.fullscreen) else {
f50ebc6999a6atagen2026-10-19 07:51 UTC1024
            return;
f50ebc6999a6atagen2026-10-19 07:51 UTC1025
        };
f50ebc6999a6atagen2026-10-19 07:51 UTC1026
        let covers = self.focused().is_some_and(|f| {
f50ebc6999a6atagen2026-10-19 07:51 UTC1027
            f != w && self.windows[f].output == win.output && !self.descends(f, w)
f50ebc6999a6atagen2026-10-19 07:51 UTC1028
        });
f50ebc6999a6atagen2026-10-19 07:51 UTC1029
        if covers && self.visible(w) {
f50ebc6999a6atagen2026-10-19 07:51 UTC1030
            self.focus(w);
f50ebc6999a6atagen2026-10-19 07:51 UTC1031
        }
f50ebc6999a6atagen2026-10-19 07:51 UTC1032
    }
f50ebc6999a6atagen2026-10-19 07:51 UTC1033
1c7e3075e57fatagen2026-10-19 10:28 UTC1034
    /// Focuses `w` unless it is out of sight or a fullscreen window hides it,
1c7e3075e57fatagen2026-10-19 10:28 UTC1035
    /// flagging it urgent then. Returns whether it took the focus.
1c7e3075e57fatagen2026-10-19 10:28 UTC1036
    pub fn focus_unless_covered(&mut self, w: WindowId) -> bool {
1c7e3075e57fatagen2026-10-19 10:28 UTC1037
        if self.visible(w) && !self.covered(w) {
1c7e3075e57fatagen2026-10-19 10:28 UTC1038
            self.focus(w);
1c7e3075e57fatagen2026-10-19 10:28 UTC1039
            return true;
1c7e3075e57fatagen2026-10-19 10:28 UTC1040
        }
1c7e3075e57fatagen2026-10-19 10:28 UTC1041
        self.mark_urgent(w);
1c7e3075e57fatagen2026-10-19 10:28 UTC1042
        false
1c7e3075e57fatagen2026-10-19 10:28 UTC1043
    }
1c7e3075e57fatagen2026-10-19 10:28 UTC1044
692a9dda8779atagen2026-10-20 11:44 UTC1045
    /// Whether a fullscreen or extended window on `w`'s output hides it. The
692a9dda8779atagen2026-10-20 11:44 UTC1046
    /// fullscreen window's transients and shown scratchpads are not hidden,
692a9dda8779atagen2026-10-20 11:44 UTC1047
    /// and a window that is itself fullscreen takes over.
b44dde08d7acatagen2026-10-19 07:38 UTC1048
    pub fn covered(&self, w: WindowId) -> bool {
692a9dda8779atagen2026-10-20 11:44 UTC1049
        if self.eclipsed(w) {
692a9dda8779atagen2026-10-20 11:44 UTC1050
            return true;
692a9dda8779atagen2026-10-20 11:44 UTC1051
        }
b44dde08d7acatagen2026-10-19 07:38 UTC1052
        let Some(win) = self.windows.get(w) else {
b44dde08d7acatagen2026-10-19 07:38 UTC1053
            return false;
b44dde08d7acatagen2026-10-19 07:38 UTC1054
        };
b44dde08d7acatagen2026-10-19 07:38 UTC1055
        let Some(o) = win.output else {
b44dde08d7acatagen2026-10-19 07:38 UTC1056
            return false;
b44dde08d7acatagen2026-10-19 07:38 UTC1057
        };
b44dde08d7acatagen2026-10-19 07:38 UTC1058
        let arr = self.arrange(o);
b44dde08d7acatagen2026-10-19 07:38 UTC1059
        !win.fullscreen && arr.fullscreen.is_some_and(|f| f != w) && !arr.over.contains(&w)
9baaafa4b167atagen2026-10-16 10:04 UTC1060
    }
9baaafa4b167atagen2026-10-16 10:04 UTC1061
9baaafa4b167atagen2026-10-16 10:04 UTC1062
    #[cfg(test)]
9baaafa4b167atagen2026-10-16 10:04 UTC1063
    pub(crate) fn check(&self) {
9baaafa4b167atagen2026-10-16 10:04 UTC1064
        for (o, out) in &self.outputs {
9baaafa4b167atagen2026-10-16 10:04 UTC1065
            out.tree.check();
9baaafa4b167atagen2026-10-16 10:04 UTC1066
            for w in out.tree.windows() {
9baaafa4b167atagen2026-10-16 10:04 UTC1067
                let win = &self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC1068
                assert_eq!(win.output, Some(o));
9baaafa4b167atagen2026-10-16 10:04 UTC1069
                assert!(!win.floating && win.scratchpad.is_none());
9baaafa4b167atagen2026-10-16 10:04 UTC1070
            }
9baaafa4b167atagen2026-10-16 10:04 UTC1071
            for &w in &out.floating {
9baaafa4b167atagen2026-10-16 10:04 UTC1072
                assert_eq!(self.windows[w].output, Some(o));
9baaafa4b167atagen2026-10-16 10:04 UTC1073
                assert!(self.windows[w].floating);
9baaafa4b167atagen2026-10-16 10:04 UTC1074
            }
9baaafa4b167atagen2026-10-16 10:04 UTC1075
        }
9baaafa4b167atagen2026-10-16 10:04 UTC1076
        for (w, win) in &self.windows {
9baaafa4b167atagen2026-10-16 10:04 UTC1077
            let homes = self
9baaafa4b167atagen2026-10-16 10:04 UTC1078
                .outputs
9baaafa4b167atagen2026-10-16 10:04 UTC1079
                .values()
9baaafa4b167atagen2026-10-16 10:04 UTC1080
                .filter(|out| out.tree.contains(w) || out.floating.contains(&w))
9baaafa4b167atagen2026-10-16 10:04 UTC1081
                .count();
9baaafa4b167atagen2026-10-16 10:04 UTC1082
            let expected = usize::from(win.output.is_some() && win.scratchpad.is_none());
9baaafa4b167atagen2026-10-16 10:04 UTC1083
            assert_eq!(homes, expected, "window placed {homes} times");
9baaafa4b167atagen2026-10-16 10:04 UTC1084
        }
9baaafa4b167atagen2026-10-16 10:04 UTC1085
    }
9baaafa4b167atagen2026-10-16 10:04 UTC1086
}
9baaafa4b167atagen2026-10-16 10:04 UTC1087
9baaafa4b167atagen2026-10-16 10:04 UTC1088
fn nearest<K: Copy>(
9baaafa4b167atagen2026-10-16 10:04 UTC1089
    from: Rect,
9baaafa4b167atagen2026-10-16 10:04 UTC1090
    dir: Dir,
9baaafa4b167atagen2026-10-16 10:04 UTC1091
    candidates: impl Iterator<Item = (K, Rect)>,
9baaafa4b167atagen2026-10-16 10:04 UTC1092
) -> Option<K> {
9baaafa4b167atagen2026-10-16 10:04 UTC1093
    const TOL: f64 = 0.5;
9baaafa4b167atagen2026-10-16 10:04 UTC1094
    let score = |r: Rect| -> Option<(bool, f64, f64)> {
9baaafa4b167atagen2026-10-16 10:04 UTC1095
        let (gap, overlap, offset) = match dir {
9baaafa4b167atagen2026-10-16 10:04 UTC1096
            Dir::Right => (
9baaafa4b167atagen2026-10-16 10:04 UTC1097
                r.x - from.right(),
9baaafa4b167atagen2026-10-16 10:04 UTC1098
                span_overlap(from.y, from.h, r.y, r.h),
9baaafa4b167atagen2026-10-16 10:04 UTC1099
                r.center_y() - from.center_y(),
9baaafa4b167atagen2026-10-16 10:04 UTC1100
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC1101
            Dir::Left => (
9baaafa4b167atagen2026-10-16 10:04 UTC1102
                from.x - r.right(),
9baaafa4b167atagen2026-10-16 10:04 UTC1103
                span_overlap(from.y, from.h, r.y, r.h),
9baaafa4b167atagen2026-10-16 10:04 UTC1104
                r.center_y() - from.center_y(),
9baaafa4b167atagen2026-10-16 10:04 UTC1105
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC1106
            Dir::Down => (
9baaafa4b167atagen2026-10-16 10:04 UTC1107
                r.y - from.bottom(),
9baaafa4b167atagen2026-10-16 10:04 UTC1108
                span_overlap(from.x, from.w, r.x, r.w),
9baaafa4b167atagen2026-10-16 10:04 UTC1109
                r.center_x() - from.center_x(),
9baaafa4b167atagen2026-10-16 10:04 UTC1110
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC1111
            Dir::Up => (
9baaafa4b167atagen2026-10-16 10:04 UTC1112
                from.y - r.bottom(),
9baaafa4b167atagen2026-10-16 10:04 UTC1113
                span_overlap(from.x, from.w, r.x, r.w),
9baaafa4b167atagen2026-10-16 10:04 UTC1114
                r.center_x() - from.center_x(),
9baaafa4b167atagen2026-10-16 10:04 UTC1115
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC1116
        };
9baaafa4b167atagen2026-10-16 10:04 UTC1117
        (gap > -TOL).then_some((overlap <= 0.0, gap.max(0.0), offset.abs()))
9baaafa4b167atagen2026-10-16 10:04 UTC1118
    };
9baaafa4b167atagen2026-10-16 10:04 UTC1119
    candidates
9baaafa4b167atagen2026-10-16 10:04 UTC1120
        .filter_map(|(k, r)| score(r).map(|s| (k, s)))
9baaafa4b167atagen2026-10-16 10:04 UTC1121
        .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
9baaafa4b167atagen2026-10-16 10:04 UTC1122
        .map(|(k, _)| k)
9baaafa4b167atagen2026-10-16 10:04 UTC1123
}
af095d8ce0fcatagen2026-10-28 04:02 UTC1124
af095d8ce0fcatagen2026-10-28 04:02 UTC1125
/// The candidate whose centre is nearest `from`'s within 45 degrees of
af095d8ce0fcatagen2026-10-28 04:02 UTC1126
/// `dir`. Floating windows overlap freely, so their edges say little about
af095d8ce0fcatagen2026-10-28 04:02 UTC1127
/// which way one lies from another.
af095d8ce0fcatagen2026-10-28 04:02 UTC1128
fn nearest_centre<K: Copy>(
af095d8ce0fcatagen2026-10-28 04:02 UTC1129
    from: Rect,
af095d8ce0fcatagen2026-10-28 04:02 UTC1130
    dir: Dir,
af095d8ce0fcatagen2026-10-28 04:02 UTC1131
    candidates: impl Iterator<Item = (K, Rect)>,
af095d8ce0fcatagen2026-10-28 04:02 UTC1132
) -> Option<K> {
af095d8ce0fcatagen2026-10-28 04:02 UTC1133
    candidates
af095d8ce0fcatagen2026-10-28 04:02 UTC1134
        .filter_map(|(k, r)| {
af095d8ce0fcatagen2026-10-28 04:02 UTC1135
            let (dx, dy) = (
af095d8ce0fcatagen2026-10-28 04:02 UTC1136
                r.center_x() - from.center_x(),
af095d8ce0fcatagen2026-10-28 04:02 UTC1137
                r.center_y() - from.center_y(),
af095d8ce0fcatagen2026-10-28 04:02 UTC1138
            );
af095d8ce0fcatagen2026-10-28 04:02 UTC1139
            let (along, across) = match dir {
af095d8ce0fcatagen2026-10-28 04:02 UTC1140
                Dir::Right => (dx, dy),
af095d8ce0fcatagen2026-10-28 04:02 UTC1141
                Dir::Left => (-dx, dy),
af095d8ce0fcatagen2026-10-28 04:02 UTC1142
                Dir::Down => (dy, dx),
af095d8ce0fcatagen2026-10-28 04:02 UTC1143
                Dir::Up => (-dy, dx),
af095d8ce0fcatagen2026-10-28 04:02 UTC1144
            };
af095d8ce0fcatagen2026-10-28 04:02 UTC1145
            (along > 0.0 && along >= across.abs()).then_some((k, along.hypot(across)))
af095d8ce0fcatagen2026-10-28 04:02 UTC1146
        })
af095d8ce0fcatagen2026-10-28 04:02 UTC1147
        .min_by(|a, b| a.1.total_cmp(&b.1))
af095d8ce0fcatagen2026-10-28 04:02 UTC1148
        .map(|(k, _)| k)
af095d8ce0fcatagen2026-10-28 04:02 UTC1149
}
9baaafa4b167atagen2026-10-16 10:04 UTC1150
9baaafa4b167atagen2026-10-16 10:04 UTC1151
fn span_overlap(a: f64, al: f64, b: f64, bl: f64) -> f64 {
9baaafa4b167atagen2026-10-16 10:04 UTC1152
    (a + al).min(b + bl) - a.max(b)
9baaafa4b167atagen2026-10-16 10:04 UTC1153
}
9baaafa4b167atagen2026-10-16 10:04 UTC1154
9baaafa4b167atagen2026-10-16 10:04 UTC1155
fn inset(r: Rect, by: f64) -> Rect {
9baaafa4b167atagen2026-10-16 10:04 UTC1156
    Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC1157
        r.x + by,
9baaafa4b167atagen2026-10-16 10:04 UTC1158
        r.y + by,
9baaafa4b167atagen2026-10-16 10:04 UTC1159
        (r.w - 2.0 * by).max(0.0),
9baaafa4b167atagen2026-10-16 10:04 UTC1160
        (r.h - 2.0 * by).max(0.0),
9baaafa4b167atagen2026-10-16 10:04 UTC1161
    )
9baaafa4b167atagen2026-10-16 10:04 UTC1162
}
9baaafa4b167atagen2026-10-16 10:04 UTC1163
9baaafa4b167atagen2026-10-16 10:04 UTC1164
fn centred(area: Rect, frac: f64) -> Rect {
b001ce7817edatagen2026-10-19 09:23 UTC1165
    centre_in(area, area.w * frac, area.h * frac)
b001ce7817edatagen2026-10-19 09:23 UTC1166
}
b001ce7817edatagen2026-10-19 09:23 UTC1167
b001ce7817edatagen2026-10-19 09:23 UTC1168
/// A w by h rect centred in area, shrunk to fit it.
b001ce7817edatagen2026-10-19 09:23 UTC1169
pub(crate) fn centre_in(area: Rect, w: f64, h: f64) -> Rect {
b001ce7817edatagen2026-10-19 09:23 UTC1170
    let (w, h) = (w.min(area.w), h.min(area.h));
9baaafa4b167atagen2026-10-16 10:04 UTC1171
    Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC1172
        area.x + (area.w - w) / 2.0,
9baaafa4b167atagen2026-10-16 10:04 UTC1173
        area.y + (area.h - h) / 2.0,
9baaafa4b167atagen2026-10-16 10:04 UTC1174
        w,
9baaafa4b167atagen2026-10-16 10:04 UTC1175
        h,
9baaafa4b167atagen2026-10-16 10:04 UTC1176
    )
9baaafa4b167atagen2026-10-16 10:04 UTC1177
}
9baaafa4b167atagen2026-10-16 10:04 UTC1178
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1179
/// The smallest a floating window gets when dragged or rescaled.
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1180
pub const MIN_FLOAT_SIZE: f64 = 32.0;
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1181
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1182
/// How much of a floating window stays reachable when it is larger than
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1183
/// the area it lives in.
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1184
const STRIP: f64 = 64.0;
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1185
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1186
/// Moves a floating rect to keep it reachable inside bounds. A window that
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1187
/// fits stays whole inside, a larger one keeps its top edge and a strip.
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1188
pub fn clamp_float(r: Rect, bounds: Rect) -> Rect {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1189
    let x = if r.w <= bounds.w {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1190
        r.x.clamp(bounds.x, bounds.right() - r.w)
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1191
    } else {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1192
        let strip = STRIP.min(bounds.w);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1193
        r.x.clamp(bounds.x + strip - r.w, bounds.right() - strip)
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1194
    };
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1195
    let y = if r.h <= bounds.h {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1196
        r.y.clamp(bounds.y, bounds.bottom() - r.h)
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1197
    } else {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1198
        r.y.clamp(bounds.y, bounds.bottom() - STRIP.min(bounds.h))
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1199
    };
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1200
    Rect::new(x, y, r.w, r.h)
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1201
}
b01f4e7ab7e6atagen2026-10-19 07:44 UTC1202
4b73e40af1e3atagen2026-10-19 07:44 UTC1203
/// Keeps a floating rect at the same relative spot and size when the area
4b73e40af1e3atagen2026-10-19 07:44 UTC1204
/// it lives in changes.
9baaafa4b167atagen2026-10-16 10:04 UTC1205
fn translate(r: Rect, from: Rect, to: Rect) -> Rect {
4b73e40af1e3atagen2026-10-19 07:44 UTC1206
    if to.w <= 0.0 || to.h <= 0.0 {
4b73e40af1e3atagen2026-10-19 07:44 UTC1207
        return r;
4b73e40af1e3atagen2026-10-19 07:44 UTC1208
    }
9baaafa4b167atagen2026-10-16 10:04 UTC1209
    let sx = if from.w > 0.0 { to.w / from.w } else { 1.0 };
9baaafa4b167atagen2026-10-16 10:04 UTC1210
    let sy = if from.h > 0.0 { to.h / from.h } else { 1.0 };
4b73e40af1e3atagen2026-10-19 07:44 UTC1211
    let scaled = Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC1212
        to.x + (r.x - from.x) * sx,
9baaafa4b167atagen2026-10-16 10:04 UTC1213
        to.y + (r.y - from.y) * sy,
4b73e40af1e3atagen2026-10-19 07:44 UTC1214
        (r.w * sx).max(MIN_FLOAT_SIZE),
4b73e40af1e3atagen2026-10-19 07:44 UTC1215
        (r.h * sy).max(MIN_FLOAT_SIZE),
4b73e40af1e3atagen2026-10-19 07:44 UTC1216
    );
4b73e40af1e3atagen2026-10-19 07:44 UTC1217
    clamp_float(scaled, to)
9baaafa4b167atagen2026-10-16 10:04 UTC1218
}