atagen

monolith

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