atagen

monolith

public
Star0
Watch0
92060e8f176e213609bb06f877261635527213cb
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>,
9baaafa4b167atagen2026-10-16 10:04 UTC70
    pub sticky: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC71
    pub scratchpad: Option<usize>,
9fdf9584d2bdatagen2026-10-19 07:38 UTC72
    pub urgent: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC73
    evicted_from: Option<String>,
9baaafa4b167atagen2026-10-16 10:04 UTC74
}
9baaafa4b167atagen2026-10-16 10:04 UTC75
9baaafa4b167atagen2026-10-16 10:04 UTC76
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC77
pub struct Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC78
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC79
    pub urgent: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC80
}
9baaafa4b167atagen2026-10-16 10:04 UTC81
9baaafa4b167atagen2026-10-16 10:04 UTC82
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC83
pub struct Output {
9baaafa4b167atagen2026-10-16 10:04 UTC84
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC85
    pub area: Rect,
bc51c38a6fd9atagen2026-10-16 22:17 UTC86
    pub reserved: Insets,
9baaafa4b167atagen2026-10-16 10:04 UTC87
    pub tree: Tree<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC88
    /// Viewed tags besides the homeless tag, which is always viewed.
9baaafa4b167atagen2026-10-16 10:04 UTC89
    pub viewed: Vec<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC90
    /// Floating windows, bottom to top.
9baaafa4b167atagen2026-10-16 10:04 UTC91
    pub floating: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC92
}
9baaafa4b167atagen2026-10-16 10:04 UTC93
9baaafa4b167atagen2026-10-16 10:04 UTC94
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC95
pub struct Scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC96
    pub name: String,
9baaafa4b167atagen2026-10-16 10:04 UTC97
    pub windows: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC98
    pub shown: Option<usize>,
9baaafa4b167atagen2026-10-16 10:04 UTC99
    pub output: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC100
}
9baaafa4b167atagen2026-10-16 10:04 UTC101
9baaafa4b167atagen2026-10-16 10:04 UTC102
#[derive(Clone, Debug, Default)]
9baaafa4b167atagen2026-10-16 10:04 UTC103
pub struct Arrangement {
9baaafa4b167atagen2026-10-16 10:04 UTC104
    /// Every tiled window on the output. Hidden ones are zero-size seeds.
9baaafa4b167atagen2026-10-16 10:04 UTC105
    pub tiled: Vec<Placed>,
9baaafa4b167atagen2026-10-16 10:04 UTC106
    /// Visible floating windows and shown scratchpads, bottom to top.
9baaafa4b167atagen2026-10-16 10:04 UTC107
    pub floating: Vec<(WindowId, Rect)>,
9baaafa4b167atagen2026-10-16 10:04 UTC108
    pub fullscreen: Option<WindowId>,
b44dde08d7acatagen2026-10-19 07:38 UTC109
    /// Floating transients of the fullscreen window, which stay above it.
b44dde08d7acatagen2026-10-19 07:38 UTC110
    pub over: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC111
}
9baaafa4b167atagen2026-10-16 10:04 UTC112
9baaafa4b167atagen2026-10-16 10:04 UTC113
#[derive(Clone, Copy, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC114
pub struct Placed {
9baaafa4b167atagen2026-10-16 10:04 UTC115
    pub window: WindowId,
9baaafa4b167atagen2026-10-16 10:04 UTC116
    pub rect: Rect,
9baaafa4b167atagen2026-10-16 10:04 UTC117
    pub visible: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC118
}
9baaafa4b167atagen2026-10-16 10:04 UTC119
9baaafa4b167atagen2026-10-16 10:04 UTC120
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC121
pub struct Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC122
    windows: SlotMap<WindowId, Window>,
9baaafa4b167atagen2026-10-16 10:04 UTC123
    tags: SlotMap<TagId, Tag>,
9baaafa4b167atagen2026-10-16 10:04 UTC124
    tag_order: Vec<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC125
    homeless: TagId,
9baaafa4b167atagen2026-10-16 10:04 UTC126
    outputs: SlotMap<OutputId, Output>,
9baaafa4b167atagen2026-10-16 10:04 UTC127
    active: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC128
    /// Focus history, most recent last.
9baaafa4b167atagen2026-10-16 10:04 UTC129
    history: Vec<WindowId>,
9baaafa4b167atagen2026-10-16 10:04 UTC130
    scratchpads: Vec<Scratchpad>,
9baaafa4b167atagen2026-10-16 10:04 UTC131
    last_scratchpad: Option<usize>,
9baaafa4b167atagen2026-10-16 10:04 UTC132
    parked: HashMap<String, Parked>,
9baaafa4b167atagen2026-10-16 10:04 UTC133
    pub gaps: Gaps,
86ef802692f5atagen2026-10-19 07:43 UTC134
    pub autotile: bool,
9baaafa4b167atagen2026-10-16 10:04 UTC135
}
9baaafa4b167atagen2026-10-16 10:04 UTC136
9baaafa4b167atagen2026-10-16 10:04 UTC137
impl Default for Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC138
    fn default() -> Self {
9baaafa4b167atagen2026-10-16 10:04 UTC139
        let mut tags = SlotMap::with_key();
9baaafa4b167atagen2026-10-16 10:04 UTC140
        let homeless = tags.insert(Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC141
            name: HOMELESS.into(),
9baaafa4b167atagen2026-10-16 10:04 UTC142
            urgent: false,
9baaafa4b167atagen2026-10-16 10:04 UTC143
        });
9baaafa4b167atagen2026-10-16 10:04 UTC144
        Self {
9baaafa4b167atagen2026-10-16 10:04 UTC145
            windows: SlotMap::with_key(),
9baaafa4b167atagen2026-10-16 10:04 UTC146
            tags,
9baaafa4b167atagen2026-10-16 10:04 UTC147
            tag_order: vec![homeless],
9baaafa4b167atagen2026-10-16 10:04 UTC148
            homeless,
9baaafa4b167atagen2026-10-16 10:04 UTC149
            outputs: SlotMap::with_key(),
9baaafa4b167atagen2026-10-16 10:04 UTC150
            active: None,
9baaafa4b167atagen2026-10-16 10:04 UTC151
            history: Vec::new(),
9baaafa4b167atagen2026-10-16 10:04 UTC152
            scratchpads: Vec::new(),
9baaafa4b167atagen2026-10-16 10:04 UTC153
            last_scratchpad: None,
9baaafa4b167atagen2026-10-16 10:04 UTC154
            parked: HashMap::new(),
9baaafa4b167atagen2026-10-16 10:04 UTC155
            gaps: Gaps::default(),
86ef802692f5atagen2026-10-19 07:43 UTC156
            autotile: false,
9baaafa4b167atagen2026-10-16 10:04 UTC157
        }
9baaafa4b167atagen2026-10-16 10:04 UTC158
    }
9baaafa4b167atagen2026-10-16 10:04 UTC159
}
9baaafa4b167atagen2026-10-16 10:04 UTC160
9baaafa4b167atagen2026-10-16 10:04 UTC161
impl Layout {
9baaafa4b167atagen2026-10-16 10:04 UTC162
    pub fn homeless(&self) -> TagId {
9baaafa4b167atagen2026-10-16 10:04 UTC163
        self.homeless
9baaafa4b167atagen2026-10-16 10:04 UTC164
    }
9baaafa4b167atagen2026-10-16 10:04 UTC165
9baaafa4b167atagen2026-10-16 10:04 UTC166
    /// Looks a tag up by name, creating it at the end of the order.
9baaafa4b167atagen2026-10-16 10:04 UTC167
    pub fn tag(&mut self, name: &str) -> TagId {
9baaafa4b167atagen2026-10-16 10:04 UTC168
        if let Some(id) = self.tag_id(name) {
9baaafa4b167atagen2026-10-16 10:04 UTC169
            return id;
9baaafa4b167atagen2026-10-16 10:04 UTC170
        }
9baaafa4b167atagen2026-10-16 10:04 UTC171
        let id = self.tags.insert(Tag {
9baaafa4b167atagen2026-10-16 10:04 UTC172
            name: name.into(),
9baaafa4b167atagen2026-10-16 10:04 UTC173
            urgent: false,
9baaafa4b167atagen2026-10-16 10:04 UTC174
        });
9baaafa4b167atagen2026-10-16 10:04 UTC175
        self.tag_order.push(id);
9baaafa4b167atagen2026-10-16 10:04 UTC176
        id
9baaafa4b167atagen2026-10-16 10:04 UTC177
    }
9baaafa4b167atagen2026-10-16 10:04 UTC178
9baaafa4b167atagen2026-10-16 10:04 UTC179
    pub fn tag_id(&self, name: &str) -> Option<TagId> {
9baaafa4b167atagen2026-10-16 10:04 UTC180
        self.tag_order
9baaafa4b167atagen2026-10-16 10:04 UTC181
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC182
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC183
            .find(|&t| self.tags[t].name == name)
9baaafa4b167atagen2026-10-16 10:04 UTC184
    }
9baaafa4b167atagen2026-10-16 10:04 UTC185
9baaafa4b167atagen2026-10-16 10:04 UTC186
    pub fn tags(&self) -> impl Iterator<Item = (TagId, &Tag)> {
9baaafa4b167atagen2026-10-16 10:04 UTC187
        self.tag_order.iter().map(|&t| (t, &self.tags[t]))
9baaafa4b167atagen2026-10-16 10:04 UTC188
    }
9baaafa4b167atagen2026-10-16 10:04 UTC189
9baaafa4b167atagen2026-10-16 10:04 UTC190
    pub fn reorder_tag(&mut self, tag: TagId, index: usize) {
9baaafa4b167atagen2026-10-16 10:04 UTC191
        if let Some(i) = self.tag_order.iter().position(|&t| t == tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC192
            self.tag_order.remove(i);
9baaafa4b167atagen2026-10-16 10:04 UTC193
            let index = index.min(self.tag_order.len());
9baaafa4b167atagen2026-10-16 10:04 UTC194
            self.tag_order.insert(index, tag);
9baaafa4b167atagen2026-10-16 10:04 UTC195
        }
9baaafa4b167atagen2026-10-16 10:04 UTC196
    }
9baaafa4b167atagen2026-10-16 10:04 UTC197
9baaafa4b167atagen2026-10-16 10:04 UTC198
    pub fn window(&self, w: WindowId) -> Option<&Window> {
9baaafa4b167atagen2026-10-16 10:04 UTC199
        self.windows.get(w)
9baaafa4b167atagen2026-10-16 10:04 UTC200
    }
9baaafa4b167atagen2026-10-16 10:04 UTC201
9baaafa4b167atagen2026-10-16 10:04 UTC202
    pub fn windows(&self) -> impl Iterator<Item = (WindowId, &Window)> {
9baaafa4b167atagen2026-10-16 10:04 UTC203
        self.windows.iter()
9baaafa4b167atagen2026-10-16 10:04 UTC204
    }
9baaafa4b167atagen2026-10-16 10:04 UTC205
9baaafa4b167atagen2026-10-16 10:04 UTC206
    pub fn output(&self, o: OutputId) -> Option<&Output> {
9baaafa4b167atagen2026-10-16 10:04 UTC207
        self.outputs.get(o)
9baaafa4b167atagen2026-10-16 10:04 UTC208
    }
9baaafa4b167atagen2026-10-16 10:04 UTC209
9baaafa4b167atagen2026-10-16 10:04 UTC210
    pub fn outputs(&self) -> impl Iterator<Item = (OutputId, &Output)> {
9baaafa4b167atagen2026-10-16 10:04 UTC211
        self.outputs.iter()
9baaafa4b167atagen2026-10-16 10:04 UTC212
    }
9baaafa4b167atagen2026-10-16 10:04 UTC213
9baaafa4b167atagen2026-10-16 10:04 UTC214
    pub fn scratchpads(&self) -> &[Scratchpad] {
9baaafa4b167atagen2026-10-16 10:04 UTC215
        &self.scratchpads
9baaafa4b167atagen2026-10-16 10:04 UTC216
    }
9baaafa4b167atagen2026-10-16 10:04 UTC217
9baaafa4b167atagen2026-10-16 10:04 UTC218
    pub fn active_output(&self) -> Option<OutputId> {
9baaafa4b167atagen2026-10-16 10:04 UTC219
        self.active
9baaafa4b167atagen2026-10-16 10:04 UTC220
    }
9baaafa4b167atagen2026-10-16 10:04 UTC221
9baaafa4b167atagen2026-10-16 10:04 UTC222
    pub fn set_active_output(&mut self, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC223
        if self.outputs.contains_key(o) {
9baaafa4b167atagen2026-10-16 10:04 UTC224
            self.active = Some(o);
9baaafa4b167atagen2026-10-16 10:04 UTC225
        }
9baaafa4b167atagen2026-10-16 10:04 UTC226
    }
9baaafa4b167atagen2026-10-16 10:04 UTC227
9baaafa4b167atagen2026-10-16 10:04 UTC228
    pub fn is_viewed(&self, o: OutputId, tag: TagId) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC229
        tag == self.homeless
9baaafa4b167atagen2026-10-16 10:04 UTC230
            || self
9baaafa4b167atagen2026-10-16 10:04 UTC231
                .outputs
9baaafa4b167atagen2026-10-16 10:04 UTC232
                .get(o)
9baaafa4b167atagen2026-10-16 10:04 UTC233
                .is_some_and(|out| out.viewed.contains(&tag))
9baaafa4b167atagen2026-10-16 10:04 UTC234
    }
9baaafa4b167atagen2026-10-16 10:04 UTC235
9baaafa4b167atagen2026-10-16 10:04 UTC236
    /// Views `tag` alone, besides the homeless tag.
9baaafa4b167atagen2026-10-16 10:04 UTC237
    pub fn view(&mut self, o: OutputId, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC238
        let Some(out) = self.outputs.get_mut(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC239
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC240
        };
9baaafa4b167atagen2026-10-16 10:04 UTC241
        out.viewed.clear();
9baaafa4b167atagen2026-10-16 10:04 UTC242
        if tag != self.homeless {
9baaafa4b167atagen2026-10-16 10:04 UTC243
            out.viewed.push(tag);
9baaafa4b167atagen2026-10-16 10:04 UTC244
        }
9baaafa4b167atagen2026-10-16 10:04 UTC245
        self.tags[tag].urgent = false;
9baaafa4b167atagen2026-10-16 10:04 UTC246
    }
9baaafa4b167atagen2026-10-16 10:04 UTC247
9baaafa4b167atagen2026-10-16 10:04 UTC248
    pub fn toggle_view(&mut self, o: OutputId, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC249
        if tag == self.homeless {
9baaafa4b167atagen2026-10-16 10:04 UTC250
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC251
        }
9baaafa4b167atagen2026-10-16 10:04 UTC252
        let Some(out) = self.outputs.get_mut(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC253
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC254
        };
9baaafa4b167atagen2026-10-16 10:04 UTC255
        if let Some(i) = out.viewed.iter().position(|&t| t == tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC256
            out.viewed.remove(i);
9baaafa4b167atagen2026-10-16 10:04 UTC257
        } else {
9baaafa4b167atagen2026-10-16 10:04 UTC258
            out.viewed.push(tag);
9baaafa4b167atagen2026-10-16 10:04 UTC259
            self.tags[tag].urgent = false;
9baaafa4b167atagen2026-10-16 10:04 UTC260
        }
9baaafa4b167atagen2026-10-16 10:04 UTC261
    }
9baaafa4b167atagen2026-10-16 10:04 UTC262
5ab6f980c72fatagen2026-10-17 10:20 UTC263
    /// Brings `w`'s tag into view beside the others when `w` is the only
5ab6f980c72fatagen2026-10-17 10:20 UTC264
    /// window on it there.
5ab6f980c72fatagen2026-10-17 10:20 UTC265
    pub fn view_if_alone(&mut self, w: WindowId) {
5ab6f980c72fatagen2026-10-17 10:20 UTC266
        let Some(win) = self.windows.get(w) else {
5ab6f980c72fatagen2026-10-17 10:20 UTC267
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC268
        };
5ab6f980c72fatagen2026-10-17 10:20 UTC269
        let (Some(o), tag) = (win.output, win.tag) else {
5ab6f980c72fatagen2026-10-17 10:20 UTC270
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC271
        };
5ab6f980c72fatagen2026-10-17 10:20 UTC272
        if win.scratchpad.is_some() || self.is_viewed(o, tag) {
5ab6f980c72fatagen2026-10-17 10:20 UTC273
            return;
5ab6f980c72fatagen2026-10-17 10:20 UTC274
        }
5ab6f980c72fatagen2026-10-17 10:20 UTC275
        let alone = !self
5ab6f980c72fatagen2026-10-17 10:20 UTC276
            .windows
5ab6f980c72fatagen2026-10-17 10:20 UTC277
            .iter()
5ab6f980c72fatagen2026-10-17 10:20 UTC278
            .any(|(x, v)| x != w && v.output == Some(o) && v.tag == tag && v.scratchpad.is_none());
5ab6f980c72fatagen2026-10-17 10:20 UTC279
        if alone {
5ab6f980c72fatagen2026-10-17 10:20 UTC280
            self.toggle_view(o, tag);
5ab6f980c72fatagen2026-10-17 10:20 UTC281
        }
5ab6f980c72fatagen2026-10-17 10:20 UTC282
    }
5ab6f980c72fatagen2026-10-17 10:20 UTC283
4e5afcada08batagen2026-10-16 10:14 UTC284
    /// The tag a launch from `o` belongs to. The focused window's tag, else
4e5afcada08batagen2026-10-16 10:14 UTC285
    /// the first viewed tag, else homeless. Homeless is always on screen, so
4e5afcada08batagen2026-10-16 10:14 UTC286
    /// focusing one of its windows does not mean the user left their tag.
4e5afcada08batagen2026-10-16 10:14 UTC287
    pub fn launch_tag(&self, o: OutputId) -> TagId {
4e5afcada08batagen2026-10-16 10:14 UTC288
        self.focused_on(o)
4e5afcada08batagen2026-10-16 10:14 UTC289
            .map(|w| &self.windows[w])
4e5afcada08batagen2026-10-16 10:14 UTC290
            .filter(|w| w.scratchpad.is_none() && !w.sticky && w.tag != self.homeless)
4e5afcada08batagen2026-10-16 10:14 UTC291
            .map(|w| w.tag)
4e5afcada08batagen2026-10-16 10:14 UTC292
            .or_else(|| self.outputs.get(o)?.viewed.first().copied())
4e5afcada08batagen2026-10-16 10:14 UTC293
            .unwrap_or(self.homeless)
4e5afcada08batagen2026-10-16 10:14 UTC294
    }
4e5afcada08batagen2026-10-16 10:14 UTC295
9baaafa4b167atagen2026-10-16 10:04 UTC296
    pub fn view_everywhere(&mut self, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC297
        for o in self.outputs.keys().collect::<Vec<_>>() {
9baaafa4b167atagen2026-10-16 10:04 UTC298
            self.view(o, tag);
9baaafa4b167atagen2026-10-16 10:04 UTC299
        }
9baaafa4b167atagen2026-10-16 10:04 UTC300
    }
9baaafa4b167atagen2026-10-16 10:04 UTC301
9baaafa4b167atagen2026-10-16 10:04 UTC302
    pub fn visible(&self, w: WindowId) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC303
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC304
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC305
        };
9baaafa4b167atagen2026-10-16 10:04 UTC306
        let Some(o) = win.output else {
9baaafa4b167atagen2026-10-16 10:04 UTC307
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC308
        };
9baaafa4b167atagen2026-10-16 10:04 UTC309
        match win.scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC310
            Some(s) => {
9baaafa4b167atagen2026-10-16 10:04 UTC311
                let sp = &self.scratchpads[s];
9baaafa4b167atagen2026-10-16 10:04 UTC312
                sp.output == Some(o) && sp.shown.map(|i| sp.windows[i]) == Some(w)
9baaafa4b167atagen2026-10-16 10:04 UTC313
            }
9baaafa4b167atagen2026-10-16 10:04 UTC314
            None => win.sticky || self.is_viewed(o, win.tag),
9baaafa4b167atagen2026-10-16 10:04 UTC315
        }
9baaafa4b167atagen2026-10-16 10:04 UTC316
    }
9baaafa4b167atagen2026-10-16 10:04 UTC317
9baaafa4b167atagen2026-10-16 10:04 UTC318
    /// Adds a window on `output` (default the active one) and `tag` (default
9baaafa4b167atagen2026-10-16 10:04 UTC319
    /// homeless). A window landing on a tag its output is not viewing marks
9baaafa4b167atagen2026-10-16 10:04 UTC320
    /// the tag urgent.
9baaafa4b167atagen2026-10-16 10:04 UTC321
    pub fn add_window(
9baaafa4b167atagen2026-10-16 10:04 UTC322
        &mut self,
9baaafa4b167atagen2026-10-16 10:04 UTC323
        output: Option<OutputId>,
9baaafa4b167atagen2026-10-16 10:04 UTC324
        tag: Option<TagId>,
9baaafa4b167atagen2026-10-16 10:04 UTC325
        floating: Option<Rect>,
9baaafa4b167atagen2026-10-16 10:04 UTC326
    ) -> WindowId {
9baaafa4b167atagen2026-10-16 10:04 UTC327
        let output = output
9baaafa4b167atagen2026-10-16 10:04 UTC328
            .filter(|&o| self.outputs.contains_key(o))
9baaafa4b167atagen2026-10-16 10:04 UTC329
            .or(self.active);
9baaafa4b167atagen2026-10-16 10:04 UTC330
        let tag = tag
9baaafa4b167atagen2026-10-16 10:04 UTC331
            .filter(|&t| self.tags.contains_key(t))
9baaafa4b167atagen2026-10-16 10:04 UTC332
            .unwrap_or(self.homeless);
9baaafa4b167atagen2026-10-16 10:04 UTC333
        let w = self.windows.insert(Window {
9baaafa4b167atagen2026-10-16 10:04 UTC334
            output,
9baaafa4b167atagen2026-10-16 10:04 UTC335
            tag,
9baaafa4b167atagen2026-10-16 10:04 UTC336
            floating: floating.is_some(),
9baaafa4b167atagen2026-10-16 10:04 UTC337
            float_rect: floating,
754ca2132943atagen2026-10-19 07:29 UTC338
            float_default: false,
9baaafa4b167atagen2026-10-16 10:04 UTC339
            fullscreen: false,
b44dde08d7acatagen2026-10-19 07:38 UTC340
            parent: None,
9baaafa4b167atagen2026-10-16 10:04 UTC341
            sticky: false,
9baaafa4b167atagen2026-10-16 10:04 UTC342
            scratchpad: None,
9fdf9584d2bdatagen2026-10-19 07:38 UTC343
            urgent: false,
9baaafa4b167atagen2026-10-16 10:04 UTC344
            evicted_from: None,
9baaafa4b167atagen2026-10-16 10:04 UTC345
        });
9baaafa4b167atagen2026-10-16 10:04 UTC346
        if let Some(o) = output {
9baaafa4b167atagen2026-10-16 10:04 UTC347
            if !self.is_viewed(o, tag) {
9baaafa4b167atagen2026-10-16 10:04 UTC348
                self.tags[tag].urgent = true;
9baaafa4b167atagen2026-10-16 10:04 UTC349
            }
9baaafa4b167atagen2026-10-16 10:04 UTC350
            self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC351
        }
9baaafa4b167atagen2026-10-16 10:04 UTC352
        w
9baaafa4b167atagen2026-10-16 10:04 UTC353
    }
9baaafa4b167atagen2026-10-16 10:04 UTC354
9baaafa4b167atagen2026-10-16 10:04 UTC355
    pub fn remove_window(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC356
        if !self.windows.contains_key(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC357
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC358
        }
9baaafa4b167atagen2026-10-16 10:04 UTC359
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC360
        self.history.retain(|&h| h != w);
9baaafa4b167atagen2026-10-16 10:04 UTC361
        if let Some(s) = self.windows[w].scratchpad {
9baaafa4b167atagen2026-10-16 10:04 UTC362
            self.leave_scratchpad(s, w);
9baaafa4b167atagen2026-10-16 10:04 UTC363
        }
b44dde08d7acatagen2026-10-19 07:38 UTC364
        for win in self.windows.values_mut() {
b44dde08d7acatagen2026-10-19 07:38 UTC365
            if win.parent == Some(w) {
b44dde08d7acatagen2026-10-19 07:38 UTC366
                win.parent = None;
b44dde08d7acatagen2026-10-19 07:38 UTC367
            }
b44dde08d7acatagen2026-10-19 07:38 UTC368
        }
9baaafa4b167atagen2026-10-16 10:04 UTC369
        self.windows.remove(w);
9baaafa4b167atagen2026-10-16 10:04 UTC370
    }
9baaafa4b167atagen2026-10-16 10:04 UTC371
9baaafa4b167atagen2026-10-16 10:04 UTC372
    /// Puts a window into its output's tree or floating stack. Tiled windows
9baaafa4b167atagen2026-10-16 10:04 UTC373
    /// go next to the focused window if both are visible, else next to the
9baaafa4b167atagen2026-10-16 10:04 UTC374
    /// latest window of their tag, else into a new row.
9baaafa4b167atagen2026-10-16 10:04 UTC375
    fn place(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC376
        let win = &self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC377
        let Some(o) = win.output else { return };
9baaafa4b167atagen2026-10-16 10:04 UTC378
        if win.scratchpad.is_some() {
9baaafa4b167atagen2026-10-16 10:04 UTC379
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC380
        }
9baaafa4b167atagen2026-10-16 10:04 UTC381
        if win.floating {
9baaafa4b167atagen2026-10-16 10:04 UTC382
            if win.float_rect.is_none() {
9baaafa4b167atagen2026-10-16 10:04 UTC383
                let rect = centred(self.outputs[o].area, 0.6);
9baaafa4b167atagen2026-10-16 10:04 UTC384
                self.windows[w].float_rect = Some(rect);
754ca2132943atagen2026-10-19 07:29 UTC385
                self.windows[w].float_default = true;
9baaafa4b167atagen2026-10-16 10:04 UTC386
            }
9baaafa4b167atagen2026-10-16 10:04 UTC387
            self.outputs[o].floating.push(w);
9baaafa4b167atagen2026-10-16 10:04 UTC388
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC389
        }
9baaafa4b167atagen2026-10-16 10:04 UTC390
        let tag = win.tag;
9baaafa4b167atagen2026-10-16 10:04 UTC391
        let tree = &self.outputs[o].tree;
9baaafa4b167atagen2026-10-16 10:04 UTC392
        let focused = self
9baaafa4b167atagen2026-10-16 10:04 UTC393
            .focused()
9baaafa4b167atagen2026-10-16 10:04 UTC394
            .filter(|&f| tree.contains(f) && self.is_viewed(o, tag));
9baaafa4b167atagen2026-10-16 10:04 UTC395
        let anchor = focused.or_else(|| {
9baaafa4b167atagen2026-10-16 10:04 UTC396
            self.history
9baaafa4b167atagen2026-10-16 10:04 UTC397
                .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC398
                .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC399
                .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC400
                .find(|&h| tree.contains(h) && self.windows[h].tag == tag)
9baaafa4b167atagen2026-10-16 10:04 UTC401
        });
86ef802692f5atagen2026-10-19 07:43 UTC402
        let split = anchor
86ef802692f5atagen2026-10-19 07:43 UTC403
            .filter(|_| self.autotile)
86ef802692f5atagen2026-10-19 07:43 UTC404
            .and_then(|a| self.split_axis(o, a));
9baaafa4b167atagen2026-10-16 10:04 UTC405
        let tree = &mut self.outputs[o].tree;
86ef802692f5atagen2026-10-19 07:43 UTC406
        match (anchor, split) {
86ef802692f5atagen2026-10-19 07:43 UTC407
            (Some(a), Some(axis)) => tree.insert_split(w, a, axis),
86ef802692f5atagen2026-10-19 07:43 UTC408
            (Some(a), None) => tree.insert_after(w, a),
86ef802692f5atagen2026-10-19 07:43 UTC409
            (None, _) => tree.insert_row(w),
9baaafa4b167atagen2026-10-16 10:04 UTC410
        }
9baaafa4b167atagen2026-10-16 10:04 UTC411
    }
9baaafa4b167atagen2026-10-16 10:04 UTC412
86ef802692f5atagen2026-10-19 07:43 UTC413
    /// The axis a window opening beside `anchor` should split it along, the
86ef802692f5atagen2026-10-19 07:43 UTC414
    /// way the anchor is laid out right now. Hidden anchors have no shape.
86ef802692f5atagen2026-10-19 07:43 UTC415
    fn split_axis(&self, o: OutputId, anchor: WindowId) -> Option<Axis> {
86ef802692f5atagen2026-10-19 07:43 UTC416
        let rect = self
86ef802692f5atagen2026-10-19 07:43 UTC417
            .arrange(o)
86ef802692f5atagen2026-10-19 07:43 UTC418
            .tiled
86ef802692f5atagen2026-10-19 07:43 UTC419
            .iter()
86ef802692f5atagen2026-10-19 07:43 UTC420
            .find(|p| p.window == anchor && p.visible)?
86ef802692f5atagen2026-10-19 07:43 UTC421
            .rect;
86ef802692f5atagen2026-10-19 07:43 UTC422
        let axis = if rect.w > rect.h {
86ef802692f5atagen2026-10-19 07:43 UTC423
            Axis::Horizontal
86ef802692f5atagen2026-10-19 07:43 UTC424
        } else {
86ef802692f5atagen2026-10-19 07:43 UTC425
            Axis::Vertical
86ef802692f5atagen2026-10-19 07:43 UTC426
        };
86ef802692f5atagen2026-10-19 07:43 UTC427
        (rect.w > 0.0 && rect.h > 0.0).then_some(axis)
86ef802692f5atagen2026-10-19 07:43 UTC428
    }
86ef802692f5atagen2026-10-19 07:43 UTC429
9baaafa4b167atagen2026-10-16 10:04 UTC430
    fn unplace(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC431
        let Some(o) = self.windows[w].output else {
9baaafa4b167atagen2026-10-16 10:04 UTC432
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC433
        };
9baaafa4b167atagen2026-10-16 10:04 UTC434
        let out = &mut self.outputs[o];
9baaafa4b167atagen2026-10-16 10:04 UTC435
        out.tree.remove(w);
9baaafa4b167atagen2026-10-16 10:04 UTC436
        out.floating.retain(|&f| f != w);
9baaafa4b167atagen2026-10-16 10:04 UTC437
    }
9baaafa4b167atagen2026-10-16 10:04 UTC438
9baaafa4b167atagen2026-10-16 10:04 UTC439
    pub fn focus(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC440
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC441
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC442
        };
9baaafa4b167atagen2026-10-16 10:04 UTC443
        self.active = win.output.or(self.active);
9baaafa4b167atagen2026-10-16 10:04 UTC444
        if let Some(o) = win.output {
9baaafa4b167atagen2026-10-16 10:04 UTC445
            let stack = &mut self.outputs[o].floating;
9baaafa4b167atagen2026-10-16 10:04 UTC446
            if let Some(i) = stack.iter().position(|&f| f == w) {
9baaafa4b167atagen2026-10-16 10:04 UTC447
                stack.remove(i);
9baaafa4b167atagen2026-10-16 10:04 UTC448
                stack.push(w);
9baaafa4b167atagen2026-10-16 10:04 UTC449
            }
9baaafa4b167atagen2026-10-16 10:04 UTC450
        }
9baaafa4b167atagen2026-10-16 10:04 UTC451
        self.history.retain(|&h| h != w);
9baaafa4b167atagen2026-10-16 10:04 UTC452
        self.history.push(w);
9fdf9584d2bdatagen2026-10-19 07:38 UTC453
        self.windows[w].urgent = false;
9baaafa4b167atagen2026-10-16 10:04 UTC454
    }
9baaafa4b167atagen2026-10-16 10:04 UTC455
adcf397e4f74atagen2026-10-18 09:56 UTC456
    /// The most recently focused visible window on the active output. Focus
adcf397e4f74atagen2026-10-18 09:56 UTC457
    /// falling back to another output would leave keybinds acting on one
adcf397e4f74atagen2026-10-18 09:56 UTC458
    /// output while the keyboard and pointer sit on another.
9baaafa4b167atagen2026-10-16 10:04 UTC459
    pub fn focused(&self) -> Option<WindowId> {
adcf397e4f74atagen2026-10-18 09:56 UTC460
        self.focused_on(self.active?)
9baaafa4b167atagen2026-10-16 10:04 UTC461
    }
9baaafa4b167atagen2026-10-16 10:04 UTC462
9baaafa4b167atagen2026-10-16 10:04 UTC463
    /// The most recently focused visible window on `o`.
9baaafa4b167atagen2026-10-16 10:04 UTC464
    pub fn focused_on(&self, o: OutputId) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC465
        self.history
9baaafa4b167atagen2026-10-16 10:04 UTC466
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC467
            .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC468
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC469
            .find(|&w| self.windows[w].output == Some(o) && self.visible(w))
9baaafa4b167atagen2026-10-16 10:04 UTC470
    }
9baaafa4b167atagen2026-10-16 10:04 UTC471
9baaafa4b167atagen2026-10-16 10:04 UTC472
    /// Focuses the nearest visible window in `dir`, crossing to the next
9baaafa4b167atagen2026-10-16 10:04 UTC473
    /// output when there is none on this one.
9baaafa4b167atagen2026-10-16 10:04 UTC474
    pub fn focus_dir(&mut self, dir: Dir) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC475
        let o = self.active?;
9baaafa4b167atagen2026-10-16 10:04 UTC476
        let from = self.focused_on(o).and_then(|f| self.rect_of(f));
9baaafa4b167atagen2026-10-16 10:04 UTC477
        let target = from.and_then(|from| {
9baaafa4b167atagen2026-10-16 10:04 UTC478
            let arr = self.arrange(o);
b44dde08d7acatagen2026-10-19 07:38 UTC479
            let reachable = |w| {
b44dde08d7acatagen2026-10-19 07:38 UTC480
                arr.fullscreen
b44dde08d7acatagen2026-10-19 07:38 UTC481
                    .is_none_or(|f| w == f || arr.over.contains(&w))
b44dde08d7acatagen2026-10-19 07:38 UTC482
            };
9baaafa4b167atagen2026-10-16 10:04 UTC483
            let candidates = arr
9baaafa4b167atagen2026-10-16 10:04 UTC484
                .tiled
9baaafa4b167atagen2026-10-16 10:04 UTC485
                .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC486
                .filter(|p| p.visible)
9baaafa4b167atagen2026-10-16 10:04 UTC487
                .map(|p| (p.window, p.rect))
9baaafa4b167atagen2026-10-16 10:04 UTC488
                .chain(arr.floating.iter().copied())
b44dde08d7acatagen2026-10-19 07:38 UTC489
                .filter(|&(w, _)| Some(w) != self.focused_on(o) && reachable(w));
9baaafa4b167atagen2026-10-16 10:04 UTC490
            nearest(from, dir, candidates)
9baaafa4b167atagen2026-10-16 10:04 UTC491
        });
9baaafa4b167atagen2026-10-16 10:04 UTC492
        if let Some(w) = target {
9baaafa4b167atagen2026-10-16 10:04 UTC493
            self.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC494
            return Some(w);
9baaafa4b167atagen2026-10-16 10:04 UTC495
        }
4e5afcada08batagen2026-10-16 10:14 UTC496
        let next = self.output_towards(o, dir)?;
9baaafa4b167atagen2026-10-16 10:04 UTC497
        self.active = Some(next);
9baaafa4b167atagen2026-10-16 10:04 UTC498
        let w = self.focused_on(next);
9baaafa4b167atagen2026-10-16 10:04 UTC499
        if let Some(w) = w {
9baaafa4b167atagen2026-10-16 10:04 UTC500
            self.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC501
        }
9baaafa4b167atagen2026-10-16 10:04 UTC502
        w
9baaafa4b167atagen2026-10-16 10:04 UTC503
    }
9baaafa4b167atagen2026-10-16 10:04 UTC504
4e5afcada08batagen2026-10-16 10:14 UTC505
    pub fn output_towards(&self, from: OutputId, dir: Dir) -> Option<OutputId> {
4e5afcada08batagen2026-10-16 10:14 UTC506
        let here = self.outputs.get(from)?.area;
4e5afcada08batagen2026-10-16 10:14 UTC507
        let others = self
4e5afcada08batagen2026-10-16 10:14 UTC508
            .outputs
4e5afcada08batagen2026-10-16 10:14 UTC509
            .iter()
4e5afcada08batagen2026-10-16 10:14 UTC510
            .filter(|&(id, _)| id != from)
4e5afcada08batagen2026-10-16 10:14 UTC511
            .map(|(id, out)| (id, out.area));
4e5afcada08batagen2026-10-16 10:14 UTC512
        nearest(here, dir, others)
4e5afcada08batagen2026-10-16 10:14 UTC513
    }
4e5afcada08batagen2026-10-16 10:14 UTC514
9baaafa4b167atagen2026-10-16 10:04 UTC515
    fn rect_of(&self, w: WindowId) -> Option<Rect> {
9baaafa4b167atagen2026-10-16 10:04 UTC516
        let o = self.windows.get(w)?.output?;
9baaafa4b167atagen2026-10-16 10:04 UTC517
        let arr = self.arrange(o);
9baaafa4b167atagen2026-10-16 10:04 UTC518
        arr.tiled
9baaafa4b167atagen2026-10-16 10:04 UTC519
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC520
            .find(|p| p.window == w)
9baaafa4b167atagen2026-10-16 10:04 UTC521
            .map(|p| p.rect)
9baaafa4b167atagen2026-10-16 10:04 UTC522
            .or_else(|| arr.floating.iter().find(|f| f.0 == w).map(|f| f.1))
9baaafa4b167atagen2026-10-16 10:04 UTC523
    }
9baaafa4b167atagen2026-10-16 10:04 UTC524
9baaafa4b167atagen2026-10-16 10:04 UTC525
    fn vis_on(&self, o: OutputId) -> impl Fn(WindowId) -> bool + '_ {
9baaafa4b167atagen2026-10-16 10:04 UTC526
        move |w| self.windows[w].output == Some(o) && self.visible(w)
9baaafa4b167atagen2026-10-16 10:04 UTC527
    }
9baaafa4b167atagen2026-10-16 10:04 UTC528
9baaafa4b167atagen2026-10-16 10:04 UTC529
    fn focused_tiled(&self) -> Option<(WindowId, OutputId)> {
9baaafa4b167atagen2026-10-16 10:04 UTC530
        let w = self.focused()?;
9baaafa4b167atagen2026-10-16 10:04 UTC531
        let o = self.windows[w].output?;
9baaafa4b167atagen2026-10-16 10:04 UTC532
        self.outputs[o].tree.contains(w).then_some((w, o))
9baaafa4b167atagen2026-10-16 10:04 UTC533
    }
9baaafa4b167atagen2026-10-16 10:04 UTC534
9baaafa4b167atagen2026-10-16 10:04 UTC535
    pub fn move_window(&mut self, dir: Dir) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC536
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC537
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC538
        };
9baaafa4b167atagen2026-10-16 10:04 UTC539
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9baaafa4b167atagen2026-10-16 10:04 UTC540
        let moved = tree.move_window(w, dir, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC541
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC542
        moved
9baaafa4b167atagen2026-10-16 10:04 UTC543
    }
9baaafa4b167atagen2026-10-16 10:04 UTC544
4444c779eb48atagen2026-10-16 12:07 UTC545
    pub fn move_container(&mut self, dir: Dir) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC546
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC547
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC548
        };
9baaafa4b167atagen2026-10-16 10:04 UTC549
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
4444c779eb48atagen2026-10-16 12:07 UTC550
        let moved = tree.move_container(w, dir, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC551
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC552
        moved
9baaafa4b167atagen2026-10-16 10:04 UTC553
    }
9baaafa4b167atagen2026-10-16 10:04 UTC554
4444c779eb48atagen2026-10-16 12:07 UTC555
    pub fn merge_window(&mut self, dir: Dir) -> bool {
4444c779eb48atagen2026-10-16 12:07 UTC556
        let Some((w, o)) = self.focused_tiled() else {
4444c779eb48atagen2026-10-16 12:07 UTC557
            return false;
4444c779eb48atagen2026-10-16 12:07 UTC558
        };
4444c779eb48atagen2026-10-16 12:07 UTC559
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
4444c779eb48atagen2026-10-16 12:07 UTC560
        let merged = tree.merge_window(w, dir, self.vis_on(o));
4444c779eb48atagen2026-10-16 12:07 UTC561
        self.outputs[o].tree = tree;
4444c779eb48atagen2026-10-16 12:07 UTC562
        merged
4444c779eb48atagen2026-10-16 12:07 UTC563
    }
4444c779eb48atagen2026-10-16 12:07 UTC564
4444c779eb48atagen2026-10-16 12:07 UTC565
    /// Whether a tiled window fills its output through expansion.
4444c779eb48atagen2026-10-16 12:07 UTC566
    pub fn maximized(&self, w: WindowId) -> bool {
4444c779eb48atagen2026-10-16 12:07 UTC567
        let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
4444c779eb48atagen2026-10-16 12:07 UTC568
            return false;
4444c779eb48atagen2026-10-16 12:07 UTC569
        };
54559daf11c3atagen2026-10-17 13:38 UTC570
        self.outputs[o].tree.maximized(w, self.vis_on(o))
4444c779eb48atagen2026-10-16 12:07 UTC571
    }
4444c779eb48atagen2026-10-16 12:07 UTC572
9baaafa4b167atagen2026-10-16 10:04 UTC573
    pub fn resize(&mut self, axis: Axis, delta: f64) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC574
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC575
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC576
        };
9baaafa4b167atagen2026-10-16 10:04 UTC577
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
9baaafa4b167atagen2026-10-16 10:04 UTC578
        let done = tree.resize(w, axis, delta, self.vis_on(o));
9baaafa4b167atagen2026-10-16 10:04 UTC579
        self.outputs[o].tree = tree;
9baaafa4b167atagen2026-10-16 10:04 UTC580
        done
9baaafa4b167atagen2026-10-16 10:04 UTC581
    }
9baaafa4b167atagen2026-10-16 10:04 UTC582
9baaafa4b167atagen2026-10-16 10:04 UTC583
    pub fn expand(&mut self, scope: ExpandScope) -> bool {
9baaafa4b167atagen2026-10-16 10:04 UTC584
        let Some((w, o)) = self.focused_tiled() else {
9baaafa4b167atagen2026-10-16 10:04 UTC585
            return false;
9baaafa4b167atagen2026-10-16 10:04 UTC586
        };
9baaafa4b167atagen2026-10-16 10:04 UTC587
        let (skip, count) = match scope {
9baaafa4b167atagen2026-10-16 10:04 UTC588
            ExpandScope::Window => (0, 1),
4444c779eb48atagen2026-10-16 12:07 UTC589
            ExpandScope::Container => (1, 1),
9baaafa4b167atagen2026-10-16 10:04 UTC590
            ExpandScope::All => (0, usize::MAX),
9baaafa4b167atagen2026-10-16 10:04 UTC591
        };
54559daf11c3atagen2026-10-17 13:38 UTC592
        let mut tree = std::mem::take(&mut self.outputs[o].tree);
54559daf11c3atagen2026-10-17 13:38 UTC593
        let expanded = tree.toggle_expand(w, skip, count, self.vis_on(o));
54559daf11c3atagen2026-10-17 13:38 UTC594
        self.outputs[o].tree = tree;
54559daf11c3atagen2026-10-17 13:38 UTC595
        expanded
9baaafa4b167atagen2026-10-16 10:04 UTC596
    }
9baaafa4b167atagen2026-10-16 10:04 UTC597
9baaafa4b167atagen2026-10-16 10:04 UTC598
    pub fn flip(&mut self, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC599
        if let Some(out) = self.outputs.get_mut(o) {
9baaafa4b167atagen2026-10-16 10:04 UTC600
            out.tree.flip();
9baaafa4b167atagen2026-10-16 10:04 UTC601
        }
9baaafa4b167atagen2026-10-16 10:04 UTC602
    }
9baaafa4b167atagen2026-10-16 10:04 UTC603
9baaafa4b167atagen2026-10-16 10:04 UTC604
    /// Retags a window. It keeps its slot and disappears if the new tag is
9baaafa4b167atagen2026-10-16 10:04 UTC605
    /// not viewed.
9baaafa4b167atagen2026-10-16 10:04 UTC606
    pub fn set_tag(&mut self, w: WindowId, tag: TagId) {
9baaafa4b167atagen2026-10-16 10:04 UTC607
        if self.tags.contains_key(tag)
9baaafa4b167atagen2026-10-16 10:04 UTC608
            && let Some(win) = self.windows.get_mut(w)
9baaafa4b167atagen2026-10-16 10:04 UTC609
        {
9baaafa4b167atagen2026-10-16 10:04 UTC610
            win.tag = tag;
9baaafa4b167atagen2026-10-16 10:04 UTC611
        }
9baaafa4b167atagen2026-10-16 10:04 UTC612
    }
9baaafa4b167atagen2026-10-16 10:04 UTC613
02d5a5d5b17eatagen2026-10-16 22:24 UTC614
    /// Moves a window to `tag` on `o`, marking the tag urgent when the window
02d5a5d5b17eatagen2026-10-16 22:24 UTC615
    /// ends up out of sight.
02d5a5d5b17eatagen2026-10-16 22:24 UTC616
    pub fn relocate(&mut self, w: WindowId, o: Option<OutputId>, tag: TagId) {
02d5a5d5b17eatagen2026-10-16 22:24 UTC617
        if let Some(o) = o {
02d5a5d5b17eatagen2026-10-16 22:24 UTC618
            self.send_to_output(w, o);
02d5a5d5b17eatagen2026-10-16 22:24 UTC619
        }
02d5a5d5b17eatagen2026-10-16 22:24 UTC620
        self.set_tag(w, tag);
02d5a5d5b17eatagen2026-10-16 22:24 UTC621
        self.mark_urgent(w);
02d5a5d5b17eatagen2026-10-16 22:24 UTC622
    }
02d5a5d5b17eatagen2026-10-16 22:24 UTC623
9fdf9584d2bdatagen2026-10-19 07:38 UTC624
    /// Flags a window that wants attention unless it already has the focus,
9fdf9584d2bdatagen2026-10-19 07:38 UTC625
    /// and its tag too when the window is out of sight.
02d5a5d5b17eatagen2026-10-16 22:24 UTC626
    pub fn mark_urgent(&mut self, w: WindowId) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC627
        if !self.windows.contains_key(w) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC628
            return;
9fdf9584d2bdatagen2026-10-19 07:38 UTC629
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC630
        if self.focused() != Some(w) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC631
            self.windows[w].urgent = true;
9fdf9584d2bdatagen2026-10-19 07:38 UTC632
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC633
        if !self.visible(w) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC634
            let tag = self.windows[w].tag;
02d5a5d5b17eatagen2026-10-16 22:24 UTC635
            self.tags[tag].urgent = true;
02d5a5d5b17eatagen2026-10-16 22:24 UTC636
        }
02d5a5d5b17eatagen2026-10-16 22:24 UTC637
    }
02d5a5d5b17eatagen2026-10-16 22:24 UTC638
9fdf9584d2bdatagen2026-10-19 07:38 UTC639
    /// Drops the flag of the window holding the focus, which can reach it
9fdf9584d2bdatagen2026-10-19 07:38 UTC640
    /// without `focus`, when the window before it closes or its tag is viewed.
9fdf9584d2bdatagen2026-10-19 07:38 UTC641
    pub fn clear_focused_urgent(&mut self) {
9fdf9584d2bdatagen2026-10-19 07:38 UTC642
        if let Some(w) = self.focused() {
9fdf9584d2bdatagen2026-10-19 07:38 UTC643
            self.windows[w].urgent = false;
9fdf9584d2bdatagen2026-10-19 07:38 UTC644
        }
9fdf9584d2bdatagen2026-10-19 07:38 UTC645
    }
9fdf9584d2bdatagen2026-10-19 07:38 UTC646
9baaafa4b167atagen2026-10-16 10:04 UTC647
    pub fn send_to_output(&mut self, w: WindowId, o: OutputId) {
9baaafa4b167atagen2026-10-16 10:04 UTC648
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC649
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC650
        };
9baaafa4b167atagen2026-10-16 10:04 UTC651
        if !self.outputs.contains_key(o) || win.output == Some(o) || win.scratchpad.is_some() {
9baaafa4b167atagen2026-10-16 10:04 UTC652
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC653
        }
9baaafa4b167atagen2026-10-16 10:04 UTC654
        let from = win.output.map(|f| self.outputs[f].area);
9baaafa4b167atagen2026-10-16 10:04 UTC655
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC656
        let win = &mut self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC657
        if let (Some(from), Some(rect)) = (from, win.float_rect) {
9baaafa4b167atagen2026-10-16 10:04 UTC658
            win.float_rect = Some(translate(rect, from, self.outputs[o].area));
9baaafa4b167atagen2026-10-16 10:04 UTC659
        }
9baaafa4b167atagen2026-10-16 10:04 UTC660
        win.output = Some(o);
9baaafa4b167atagen2026-10-16 10:04 UTC661
        win.evicted_from = None;
9baaafa4b167atagen2026-10-16 10:04 UTC662
        self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC663
    }
9baaafa4b167atagen2026-10-16 10:04 UTC664
48af562d968batagen2026-10-16 12:39 UTC665
    /// Switches a window between tiled and floating. A scratchpad window
48af562d968batagen2026-10-16 12:39 UTC666
    /// leaves its scratchpad and tiles on the tag being viewed, since its own
48af562d968batagen2026-10-16 12:39 UTC667
    /// tag may well be hidden.
9baaafa4b167atagen2026-10-16 10:04 UTC668
    pub fn toggle_floating(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC669
        let Some(win) = self.windows.get(w) else {
9baaafa4b167atagen2026-10-16 10:04 UTC670
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC671
        };
48af562d968batagen2026-10-16 12:39 UTC672
        if let Some(s) = win.scratchpad {
48af562d968batagen2026-10-16 12:39 UTC673
            let o = win.output.or(self.active);
48af562d968batagen2026-10-16 12:39 UTC674
            let tag = o.map_or(self.homeless, |o| self.launch_tag(o));
48af562d968batagen2026-10-16 12:39 UTC675
            self.leave_scratchpad(s, w);
48af562d968batagen2026-10-16 12:39 UTC676
            let win = &mut self.windows[w];
48af562d968batagen2026-10-16 12:39 UTC677
            win.output = o;
48af562d968batagen2026-10-16 12:39 UTC678
            win.tag = tag;
48af562d968batagen2026-10-16 12:39 UTC679
            win.floating = false;
48af562d968batagen2026-10-16 12:39 UTC680
            self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC681
            return;
9baaafa4b167atagen2026-10-16 10:04 UTC682
        }
9baaafa4b167atagen2026-10-16 10:04 UTC683
        self.unplace(w);
9baaafa4b167atagen2026-10-16 10:04 UTC684
        let win = &mut self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC685
        win.floating = !win.floating;
9baaafa4b167atagen2026-10-16 10:04 UTC686
        self.place(w);
9baaafa4b167atagen2026-10-16 10:04 UTC687
    }
9baaafa4b167atagen2026-10-16 10:04 UTC688
9baaafa4b167atagen2026-10-16 10:04 UTC689
    pub fn set_float_rect(&mut self, w: WindowId, rect: Rect) {
9baaafa4b167atagen2026-10-16 10:04 UTC690
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC691
            win.float_rect = Some(rect);
754ca2132943atagen2026-10-19 07:29 UTC692
            win.float_default = false;
9baaafa4b167atagen2026-10-16 10:04 UTC693
        }
9baaafa4b167atagen2026-10-16 10:04 UTC694
    }
9baaafa4b167atagen2026-10-16 10:04 UTC695
fd834695f723atagen2026-10-19 07:49 UTC696
    /// Places a float without settling its size, so a window still deciding
fd834695f723atagen2026-10-19 07:49 UTC697
    /// on one is asked again after being tiled.
fd834695f723atagen2026-10-19 07:49 UTC698
    pub fn hold_float_rect(&mut self, w: WindowId, rect: Rect) {
fd834695f723atagen2026-10-19 07:49 UTC699
        if let Some(win) = self.windows.get_mut(w) {
fd834695f723atagen2026-10-19 07:49 UTC700
            win.float_rect = Some(rect);
fd834695f723atagen2026-10-19 07:49 UTC701
        }
fd834695f723atagen2026-10-19 07:49 UTC702
    }
fd834695f723atagen2026-10-19 07:49 UTC703
9baaafa4b167atagen2026-10-16 10:04 UTC704
    pub fn toggle_fullscreen(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC705
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC706
            win.fullscreen = !win.fullscreen;
9baaafa4b167atagen2026-10-16 10:04 UTC707
        }
9baaafa4b167atagen2026-10-16 10:04 UTC708
    }
9baaafa4b167atagen2026-10-16 10:04 UTC709
9baaafa4b167atagen2026-10-16 10:04 UTC710
    pub fn toggle_sticky(&mut self, w: WindowId) {
9baaafa4b167atagen2026-10-16 10:04 UTC711
        if let Some(win) = self.windows.get_mut(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC712
            win.sticky = !win.sticky;
9baaafa4b167atagen2026-10-16 10:04 UTC713
        }
9baaafa4b167atagen2026-10-16 10:04 UTC714
    }
9baaafa4b167atagen2026-10-16 10:04 UTC715
9baaafa4b167atagen2026-10-16 10:04 UTC716
    /// Target geometry for everything on `o`, in output-local logical
9baaafa4b167atagen2026-10-16 10:04 UTC717
    /// coordinates offset by the output's area.
9baaafa4b167atagen2026-10-16 10:04 UTC718
    pub fn arrange(&self, o: OutputId) -> Arrangement {
2af1d8c55876atagen2026-10-16 11:05 UTC719
        self.arrange_hiding(o, &[])
2af1d8c55876atagen2026-10-16 11:05 UTC720
    }
2af1d8c55876atagen2026-10-16 11:05 UTC721
2af1d8c55876atagen2026-10-16 11:05 UTC722
    /// Like `arrange` with `hide` treated as hidden, which places them on the
2af1d8c55876atagen2026-10-16 11:05 UTC723
    /// seeds they would grow from.
2af1d8c55876atagen2026-10-16 11:05 UTC724
    pub fn arrange_hiding(&self, o: OutputId, hide: &[WindowId]) -> Arrangement {
9baaafa4b167atagen2026-10-16 10:04 UTC725
        let Some(out) = self.outputs.get(o) else {
9baaafa4b167atagen2026-10-16 10:04 UTC726
            return Arrangement::default();
9baaafa4b167atagen2026-10-16 10:04 UTC727
        };
bc51c38a6fd9atagen2026-10-16 22:17 UTC728
        let r = out.reserved;
bc51c38a6fd9atagen2026-10-16 22:17 UTC729
        let usable = Rect::new(
bc51c38a6fd9atagen2026-10-16 22:17 UTC730
            out.area.x + r.left,
bc51c38a6fd9atagen2026-10-16 22:17 UTC731
            out.area.y + r.top,
bc51c38a6fd9atagen2026-10-16 22:17 UTC732
            (out.area.w - r.left - r.right).max(0.0),
bc51c38a6fd9atagen2026-10-16 22:17 UTC733
            (out.area.h - r.top - r.bottom).max(0.0),
bc51c38a6fd9atagen2026-10-16 22:17 UTC734
        );
bc51c38a6fd9atagen2026-10-16 22:17 UTC735
        let area = inset(usable, self.gaps.outer);
2af1d8c55876atagen2026-10-16 11:05 UTC736
        let shown = self.vis_on(o);
2af1d8c55876atagen2026-10-16 11:05 UTC737
        let vis = &|w: WindowId| shown(w) && !hide.contains(&w);
9baaafa4b167atagen2026-10-16 10:04 UTC738
        let tiled = out
9baaafa4b167atagen2026-10-16 10:04 UTC739
            .tree
2af1d8c55876atagen2026-10-16 11:05 UTC740
            .arrange(area, self.gaps.inner, vis)
9baaafa4b167atagen2026-10-16 10:04 UTC741
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC742
            .map(|(window, rect)| Placed {
9baaafa4b167atagen2026-10-16 10:04 UTC743
                window,
9baaafa4b167atagen2026-10-16 10:04 UTC744
                rect,
9baaafa4b167atagen2026-10-16 10:04 UTC745
                visible: vis(window),
9baaafa4b167atagen2026-10-16 10:04 UTC746
            })
9baaafa4b167atagen2026-10-16 10:04 UTC747
            .collect();
9baaafa4b167atagen2026-10-16 10:04 UTC748
        let mut floating: Vec<(WindowId, Rect)> = out
9baaafa4b167atagen2026-10-16 10:04 UTC749
            .floating
9baaafa4b167atagen2026-10-16 10:04 UTC750
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC751
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC752
            .filter(|&w| vis(w))
9baaafa4b167atagen2026-10-16 10:04 UTC753
            .filter_map(|w| Some((w, self.windows[w].float_rect?)))
9baaafa4b167atagen2026-10-16 10:04 UTC754
            .collect();
9baaafa4b167atagen2026-10-16 10:04 UTC755
        for sp in &self.scratchpads {
9baaafa4b167atagen2026-10-16 10:04 UTC756
            if let Some(i) = sp.shown
9baaafa4b167atagen2026-10-16 10:04 UTC757
                && sp.output == Some(o)
9baaafa4b167atagen2026-10-16 10:04 UTC758
                && let Some(r) = self.windows[sp.windows[i]].float_rect
9baaafa4b167atagen2026-10-16 10:04 UTC759
            {
9baaafa4b167atagen2026-10-16 10:04 UTC760
                floating.push((sp.windows[i], r));
9baaafa4b167atagen2026-10-16 10:04 UTC761
            }
9baaafa4b167atagen2026-10-16 10:04 UTC762
        }
9baaafa4b167atagen2026-10-16 10:04 UTC763
        let fullscreen = self
9baaafa4b167atagen2026-10-16 10:04 UTC764
            .history
9baaafa4b167atagen2026-10-16 10:04 UTC765
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC766
            .rev()
9baaafa4b167atagen2026-10-16 10:04 UTC767
            .copied()
9baaafa4b167atagen2026-10-16 10:04 UTC768
            .find(|&w| self.windows[w].fullscreen && vis(w));
b44dde08d7acatagen2026-10-19 07:38 UTC769
        let over = fullscreen
b44dde08d7acatagen2026-10-19 07:38 UTC770
            .map(|f| {
b44dde08d7acatagen2026-10-19 07:38 UTC771
                floating
b44dde08d7acatagen2026-10-19 07:38 UTC772
                    .iter()
b44dde08d7acatagen2026-10-19 07:38 UTC773
                    .map(|&(w, _)| w)
b44dde08d7acatagen2026-10-19 07:38 UTC774
                    .filter(|&w| w != f && self.descends(w, f))
b44dde08d7acatagen2026-10-19 07:38 UTC775
                    .collect()
b44dde08d7acatagen2026-10-19 07:38 UTC776
            })
b44dde08d7acatagen2026-10-19 07:38 UTC777
            .unwrap_or_default();
9baaafa4b167atagen2026-10-16 10:04 UTC778
        Arrangement {
9baaafa4b167atagen2026-10-16 10:04 UTC779
            tiled,
9baaafa4b167atagen2026-10-16 10:04 UTC780
            floating,
9baaafa4b167atagen2026-10-16 10:04 UTC781
            fullscreen,
b44dde08d7acatagen2026-10-19 07:38 UTC782
            over,
b44dde08d7acatagen2026-10-19 07:38 UTC783
        }
b44dde08d7acatagen2026-10-19 07:38 UTC784
    }
b44dde08d7acatagen2026-10-19 07:38 UTC785
b44dde08d7acatagen2026-10-19 07:38 UTC786
    /// Records that `w` is a transient of `parent`.
b44dde08d7acatagen2026-10-19 07:38 UTC787
    pub fn set_parent(&mut self, w: WindowId, parent: Option<WindowId>) {
b44dde08d7acatagen2026-10-19 07:38 UTC788
        if parent.is_some_and(|p| p == w || self.descends(p, w)) {
b44dde08d7acatagen2026-10-19 07:38 UTC789
            return;
b44dde08d7acatagen2026-10-19 07:38 UTC790
        }
b44dde08d7acatagen2026-10-19 07:38 UTC791
        if let Some(win) = self.windows.get_mut(w) {
b44dde08d7acatagen2026-10-19 07:38 UTC792
            win.parent = parent;
b44dde08d7acatagen2026-10-19 07:38 UTC793
        }
b44dde08d7acatagen2026-10-19 07:38 UTC794
    }
b44dde08d7acatagen2026-10-19 07:38 UTC795
b44dde08d7acatagen2026-10-19 07:38 UTC796
    fn descends(&self, mut w: WindowId, of: WindowId) -> bool {
b44dde08d7acatagen2026-10-19 07:38 UTC797
        for _ in 0..self.windows.len() {
b44dde08d7acatagen2026-10-19 07:38 UTC798
            match self.windows.get(w).and_then(|x| x.parent) {
b44dde08d7acatagen2026-10-19 07:38 UTC799
                Some(p) if p == of => return true,
b44dde08d7acatagen2026-10-19 07:38 UTC800
                Some(p) => w = p,
b44dde08d7acatagen2026-10-19 07:38 UTC801
                None => return false,
b44dde08d7acatagen2026-10-19 07:38 UTC802
            }
9baaafa4b167atagen2026-10-16 10:04 UTC803
        }
b44dde08d7acatagen2026-10-19 07:38 UTC804
        false
b44dde08d7acatagen2026-10-19 07:38 UTC805
    }
b44dde08d7acatagen2026-10-19 07:38 UTC806
f50ebc6999a6atagen2026-10-19 07:51 UTC807
    /// Moves focus to a fullscreen `w` when it covers the focused window,
f50ebc6999a6atagen2026-10-19 07:51 UTC808
    /// unless that is one of its own transients.
f50ebc6999a6atagen2026-10-19 07:51 UTC809
    pub fn focus_if_covering(&mut self, w: WindowId) {
f50ebc6999a6atagen2026-10-19 07:51 UTC810
        let Some(win) = self.windows.get(w).filter(|win| win.fullscreen) else {
f50ebc6999a6atagen2026-10-19 07:51 UTC811
            return;
f50ebc6999a6atagen2026-10-19 07:51 UTC812
        };
f50ebc6999a6atagen2026-10-19 07:51 UTC813
        let covers = self.focused().is_some_and(|f| {
f50ebc6999a6atagen2026-10-19 07:51 UTC814
            f != w && self.windows[f].output == win.output && !self.descends(f, w)
f50ebc6999a6atagen2026-10-19 07:51 UTC815
        });
f50ebc6999a6atagen2026-10-19 07:51 UTC816
        if covers && self.visible(w) {
f50ebc6999a6atagen2026-10-19 07:51 UTC817
            self.focus(w);
f50ebc6999a6atagen2026-10-19 07:51 UTC818
        }
f50ebc6999a6atagen2026-10-19 07:51 UTC819
    }
f50ebc6999a6atagen2026-10-19 07:51 UTC820
b44dde08d7acatagen2026-10-19 07:38 UTC821
    /// Whether a fullscreen window on `w`'s output hides it. The fullscreen
b44dde08d7acatagen2026-10-19 07:38 UTC822
    /// window's transients are not hidden, and a window that is itself
b44dde08d7acatagen2026-10-19 07:38 UTC823
    /// fullscreen takes over.
b44dde08d7acatagen2026-10-19 07:38 UTC824
    pub fn covered(&self, w: WindowId) -> bool {
b44dde08d7acatagen2026-10-19 07:38 UTC825
        let Some(win) = self.windows.get(w) else {
b44dde08d7acatagen2026-10-19 07:38 UTC826
            return false;
b44dde08d7acatagen2026-10-19 07:38 UTC827
        };
b44dde08d7acatagen2026-10-19 07:38 UTC828
        let Some(o) = win.output else {
b44dde08d7acatagen2026-10-19 07:38 UTC829
            return false;
b44dde08d7acatagen2026-10-19 07:38 UTC830
        };
b44dde08d7acatagen2026-10-19 07:38 UTC831
        let arr = self.arrange(o);
b44dde08d7acatagen2026-10-19 07:38 UTC832
        !win.fullscreen && arr.fullscreen.is_some_and(|f| f != w) && !arr.over.contains(&w)
9baaafa4b167atagen2026-10-16 10:04 UTC833
    }
9baaafa4b167atagen2026-10-16 10:04 UTC834
9baaafa4b167atagen2026-10-16 10:04 UTC835
    #[cfg(test)]
9baaafa4b167atagen2026-10-16 10:04 UTC836
    pub(crate) fn check(&self) {
9baaafa4b167atagen2026-10-16 10:04 UTC837
        for (o, out) in &self.outputs {
9baaafa4b167atagen2026-10-16 10:04 UTC838
            out.tree.check();
9baaafa4b167atagen2026-10-16 10:04 UTC839
            for w in out.tree.windows() {
9baaafa4b167atagen2026-10-16 10:04 UTC840
                let win = &self.windows[w];
9baaafa4b167atagen2026-10-16 10:04 UTC841
                assert_eq!(win.output, Some(o));
9baaafa4b167atagen2026-10-16 10:04 UTC842
                assert!(!win.floating && win.scratchpad.is_none());
9baaafa4b167atagen2026-10-16 10:04 UTC843
            }
9baaafa4b167atagen2026-10-16 10:04 UTC844
            for &w in &out.floating {
9baaafa4b167atagen2026-10-16 10:04 UTC845
                assert_eq!(self.windows[w].output, Some(o));
9baaafa4b167atagen2026-10-16 10:04 UTC846
                assert!(self.windows[w].floating);
9baaafa4b167atagen2026-10-16 10:04 UTC847
            }
9baaafa4b167atagen2026-10-16 10:04 UTC848
        }
9baaafa4b167atagen2026-10-16 10:04 UTC849
        for (w, win) in &self.windows {
9baaafa4b167atagen2026-10-16 10:04 UTC850
            let homes = self
9baaafa4b167atagen2026-10-16 10:04 UTC851
                .outputs
9baaafa4b167atagen2026-10-16 10:04 UTC852
                .values()
9baaafa4b167atagen2026-10-16 10:04 UTC853
                .filter(|out| out.tree.contains(w) || out.floating.contains(&w))
9baaafa4b167atagen2026-10-16 10:04 UTC854
                .count();
9baaafa4b167atagen2026-10-16 10:04 UTC855
            let expected = usize::from(win.output.is_some() && win.scratchpad.is_none());
9baaafa4b167atagen2026-10-16 10:04 UTC856
            assert_eq!(homes, expected, "window placed {homes} times");
9baaafa4b167atagen2026-10-16 10:04 UTC857
        }
9baaafa4b167atagen2026-10-16 10:04 UTC858
    }
9baaafa4b167atagen2026-10-16 10:04 UTC859
}
9baaafa4b167atagen2026-10-16 10:04 UTC860
9baaafa4b167atagen2026-10-16 10:04 UTC861
fn nearest<K: Copy>(
9baaafa4b167atagen2026-10-16 10:04 UTC862
    from: Rect,
9baaafa4b167atagen2026-10-16 10:04 UTC863
    dir: Dir,
9baaafa4b167atagen2026-10-16 10:04 UTC864
    candidates: impl Iterator<Item = (K, Rect)>,
9baaafa4b167atagen2026-10-16 10:04 UTC865
) -> Option<K> {
9baaafa4b167atagen2026-10-16 10:04 UTC866
    const TOL: f64 = 0.5;
9baaafa4b167atagen2026-10-16 10:04 UTC867
    let score = |r: Rect| -> Option<(bool, f64, f64)> {
9baaafa4b167atagen2026-10-16 10:04 UTC868
        let (gap, overlap, offset) = match dir {
9baaafa4b167atagen2026-10-16 10:04 UTC869
            Dir::Right => (
9baaafa4b167atagen2026-10-16 10:04 UTC870
                r.x - from.right(),
9baaafa4b167atagen2026-10-16 10:04 UTC871
                span_overlap(from.y, from.h, r.y, r.h),
9baaafa4b167atagen2026-10-16 10:04 UTC872
                r.center_y() - from.center_y(),
9baaafa4b167atagen2026-10-16 10:04 UTC873
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC874
            Dir::Left => (
9baaafa4b167atagen2026-10-16 10:04 UTC875
                from.x - r.right(),
9baaafa4b167atagen2026-10-16 10:04 UTC876
                span_overlap(from.y, from.h, r.y, r.h),
9baaafa4b167atagen2026-10-16 10:04 UTC877
                r.center_y() - from.center_y(),
9baaafa4b167atagen2026-10-16 10:04 UTC878
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC879
            Dir::Down => (
9baaafa4b167atagen2026-10-16 10:04 UTC880
                r.y - from.bottom(),
9baaafa4b167atagen2026-10-16 10:04 UTC881
                span_overlap(from.x, from.w, r.x, r.w),
9baaafa4b167atagen2026-10-16 10:04 UTC882
                r.center_x() - from.center_x(),
9baaafa4b167atagen2026-10-16 10:04 UTC883
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC884
            Dir::Up => (
9baaafa4b167atagen2026-10-16 10:04 UTC885
                from.y - r.bottom(),
9baaafa4b167atagen2026-10-16 10:04 UTC886
                span_overlap(from.x, from.w, r.x, r.w),
9baaafa4b167atagen2026-10-16 10:04 UTC887
                r.center_x() - from.center_x(),
9baaafa4b167atagen2026-10-16 10:04 UTC888
            ),
9baaafa4b167atagen2026-10-16 10:04 UTC889
        };
9baaafa4b167atagen2026-10-16 10:04 UTC890
        (gap > -TOL).then_some((overlap <= 0.0, gap.max(0.0), offset.abs()))
9baaafa4b167atagen2026-10-16 10:04 UTC891
    };
9baaafa4b167atagen2026-10-16 10:04 UTC892
    candidates
9baaafa4b167atagen2026-10-16 10:04 UTC893
        .filter_map(|(k, r)| score(r).map(|s| (k, s)))
9baaafa4b167atagen2026-10-16 10:04 UTC894
        .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
9baaafa4b167atagen2026-10-16 10:04 UTC895
        .map(|(k, _)| k)
9baaafa4b167atagen2026-10-16 10:04 UTC896
}
9baaafa4b167atagen2026-10-16 10:04 UTC897
9baaafa4b167atagen2026-10-16 10:04 UTC898
fn span_overlap(a: f64, al: f64, b: f64, bl: f64) -> f64 {
9baaafa4b167atagen2026-10-16 10:04 UTC899
    (a + al).min(b + bl) - a.max(b)
9baaafa4b167atagen2026-10-16 10:04 UTC900
}
9baaafa4b167atagen2026-10-16 10:04 UTC901
9baaafa4b167atagen2026-10-16 10:04 UTC902
fn inset(r: Rect, by: f64) -> Rect {
9baaafa4b167atagen2026-10-16 10:04 UTC903
    Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC904
        r.x + by,
9baaafa4b167atagen2026-10-16 10:04 UTC905
        r.y + by,
9baaafa4b167atagen2026-10-16 10:04 UTC906
        (r.w - 2.0 * by).max(0.0),
9baaafa4b167atagen2026-10-16 10:04 UTC907
        (r.h - 2.0 * by).max(0.0),
9baaafa4b167atagen2026-10-16 10:04 UTC908
    )
9baaafa4b167atagen2026-10-16 10:04 UTC909
}
9baaafa4b167atagen2026-10-16 10:04 UTC910
9baaafa4b167atagen2026-10-16 10:04 UTC911
fn centred(area: Rect, frac: f64) -> Rect {
9baaafa4b167atagen2026-10-16 10:04 UTC912
    let (w, h) = (area.w * frac, area.h * frac);
9baaafa4b167atagen2026-10-16 10:04 UTC913
    Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC914
        area.x + (area.w - w) / 2.0,
9baaafa4b167atagen2026-10-16 10:04 UTC915
        area.y + (area.h - h) / 2.0,
9baaafa4b167atagen2026-10-16 10:04 UTC916
        w,
9baaafa4b167atagen2026-10-16 10:04 UTC917
        h,
9baaafa4b167atagen2026-10-16 10:04 UTC918
    )
9baaafa4b167atagen2026-10-16 10:04 UTC919
}
9baaafa4b167atagen2026-10-16 10:04 UTC920
9baaafa4b167atagen2026-10-16 10:04 UTC921
/// Keeps a rect at the same relative spot when it changes output.
9baaafa4b167atagen2026-10-16 10:04 UTC922
fn translate(r: Rect, from: Rect, to: Rect) -> Rect {
9baaafa4b167atagen2026-10-16 10:04 UTC923
    let sx = if from.w > 0.0 { to.w / from.w } else { 1.0 };
9baaafa4b167atagen2026-10-16 10:04 UTC924
    let sy = if from.h > 0.0 { to.h / from.h } else { 1.0 };
9baaafa4b167atagen2026-10-16 10:04 UTC925
    Rect::new(
9baaafa4b167atagen2026-10-16 10:04 UTC926
        to.x + (r.x - from.x) * sx,
9baaafa4b167atagen2026-10-16 10:04 UTC927
        to.y + (r.y - from.y) * sy,
9baaafa4b167atagen2026-10-16 10:04 UTC928
        r.w.min(to.w),
9baaafa4b167atagen2026-10-16 10:04 UTC929
        r.h.min(to.h),
9baaafa4b167atagen2026-10-16 10:04 UTC930
    )
9baaafa4b167atagen2026-10-16 10:04 UTC931
}