atagen

monolith

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

Blame: crates/layout/src/tests.rs

9baaafa4b167atagen2026-10-16 10:04 UTC1
use super::*;
9baaafa4b167atagen2026-10-16 10:04 UTC2
use proptest::prelude::*;
9baaafa4b167atagen2026-10-16 10:04 UTC3
9baaafa4b167atagen2026-10-16 10:04 UTC4
const AREA: Rect = Rect::new(0.0, 0.0, 1000.0, 600.0);
9baaafa4b167atagen2026-10-16 10:04 UTC5
9baaafa4b167atagen2026-10-16 10:04 UTC6
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC7
enum Op {
9baaafa4b167atagen2026-10-16 10:04 UTC8
    Add(u8, bool),
9baaafa4b167atagen2026-10-16 10:04 UTC9
    Remove(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC10
    Focus(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC11
    Move(Dir),
21e8e8e5aa3datagen2026-10-20 11:44 UTC12
    Block(Dir, bool),
4444c779eb48atagen2026-10-16 12:07 UTC13
    Merge(Dir),
9baaafa4b167atagen2026-10-16 10:04 UTC14
    View(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC15
    ToggleView(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC16
    Retag(u8, u8),
9baaafa4b167atagen2026-10-16 10:04 UTC17
    Float(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC18
    Expand(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC19
    Flip,
9baaafa4b167atagen2026-10-16 10:04 UTC20
    Resize(bool, i8),
9baaafa4b167atagen2026-10-16 10:04 UTC21
    Scratch(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC22
    Summon,
9baaafa4b167atagen2026-10-16 10:04 UTC23
    Unplug,
9baaafa4b167atagen2026-10-16 10:04 UTC24
    Replug,
9baaafa4b167atagen2026-10-16 10:04 UTC25
}
9baaafa4b167atagen2026-10-16 10:04 UTC26
9baaafa4b167atagen2026-10-16 10:04 UTC27
fn dir() -> impl Strategy<Value = Dir> {
9baaafa4b167atagen2026-10-16 10:04 UTC28
    prop_oneof![
9baaafa4b167atagen2026-10-16 10:04 UTC29
        Just(Dir::Left),
9baaafa4b167atagen2026-10-16 10:04 UTC30
        Just(Dir::Right),
9baaafa4b167atagen2026-10-16 10:04 UTC31
        Just(Dir::Up),
9baaafa4b167atagen2026-10-16 10:04 UTC32
        Just(Dir::Down)
9baaafa4b167atagen2026-10-16 10:04 UTC33
    ]
9baaafa4b167atagen2026-10-16 10:04 UTC34
}
9baaafa4b167atagen2026-10-16 10:04 UTC35
9baaafa4b167atagen2026-10-16 10:04 UTC36
fn op() -> impl Strategy<Value = Op> {
9baaafa4b167atagen2026-10-16 10:04 UTC37
    prop_oneof![
9baaafa4b167atagen2026-10-16 10:04 UTC38
        4 => (0u8..4, prop::bool::weighted(0.15)).prop_map(|(t, f)| Op::Add(t, f)),
9baaafa4b167atagen2026-10-16 10:04 UTC39
        1 => any::<u8>().prop_map(Op::Remove),
9baaafa4b167atagen2026-10-16 10:04 UTC40
        3 => any::<u8>().prop_map(Op::Focus),
9baaafa4b167atagen2026-10-16 10:04 UTC41
        4 => dir().prop_map(Op::Move),
21e8e8e5aa3datagen2026-10-20 11:44 UTC42
        2 => (dir(), any::<bool>()).prop_map(|(d, m)| Op::Block(d, m)),
4444c779eb48atagen2026-10-16 12:07 UTC43
        3 => dir().prop_map(Op::Merge),
9baaafa4b167atagen2026-10-16 10:04 UTC44
        1 => (0u8..4).prop_map(Op::View),
9baaafa4b167atagen2026-10-16 10:04 UTC45
        2 => (0u8..4).prop_map(Op::ToggleView),
9baaafa4b167atagen2026-10-16 10:04 UTC46
        1 => (any::<u8>(), 0u8..4).prop_map(|(w, t)| Op::Retag(w, t)),
9baaafa4b167atagen2026-10-16 10:04 UTC47
        1 => any::<u8>().prop_map(Op::Float),
9baaafa4b167atagen2026-10-16 10:04 UTC48
        1 => (0u8..3).prop_map(Op::Expand),
9baaafa4b167atagen2026-10-16 10:04 UTC49
        1 => Just(Op::Flip),
9baaafa4b167atagen2026-10-16 10:04 UTC50
        1 => (any::<bool>(), any::<i8>()).prop_map(|(a, d)| Op::Resize(a, d)),
9baaafa4b167atagen2026-10-16 10:04 UTC51
        1 => any::<u8>().prop_map(Op::Scratch),
9baaafa4b167atagen2026-10-16 10:04 UTC52
        1 => Just(Op::Summon),
9baaafa4b167atagen2026-10-16 10:04 UTC53
        1 => Just(Op::Unplug),
9baaafa4b167atagen2026-10-16 10:04 UTC54
        1 => Just(Op::Replug),
9baaafa4b167atagen2026-10-16 10:04 UTC55
    ]
9baaafa4b167atagen2026-10-16 10:04 UTC56
}
9baaafa4b167atagen2026-10-16 10:04 UTC57
9baaafa4b167atagen2026-10-16 10:04 UTC58
fn nth(l: &Layout, i: u8) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC59
    let ids: Vec<WindowId> = l.windows().map(|(id, _)| id).collect();
9baaafa4b167atagen2026-10-16 10:04 UTC60
    (!ids.is_empty()).then(|| ids[i as usize % ids.len()])
9baaafa4b167atagen2026-10-16 10:04 UTC61
}
9baaafa4b167atagen2026-10-16 10:04 UTC62
9baaafa4b167atagen2026-10-16 10:04 UTC63
fn apply(l: &mut Layout, tags: &[TagId], op: &Op) {
9baaafa4b167atagen2026-10-16 10:04 UTC64
    let active = l.active_output();
9baaafa4b167atagen2026-10-16 10:04 UTC65
    match *op {
9baaafa4b167atagen2026-10-16 10:04 UTC66
        Op::Add(t, f) => {
9baaafa4b167atagen2026-10-16 10:04 UTC67
            let w = l.add_window(
9baaafa4b167atagen2026-10-16 10:04 UTC68
                None,
9baaafa4b167atagen2026-10-16 10:04 UTC69
                Some(tags[t as usize]),
9baaafa4b167atagen2026-10-16 10:04 UTC70
                f.then(|| Rect::new(10.0, 10.0, 200.0, 100.0)),
9baaafa4b167atagen2026-10-16 10:04 UTC71
            );
9baaafa4b167atagen2026-10-16 10:04 UTC72
            if l.visible(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC73
                l.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC74
            }
9baaafa4b167atagen2026-10-16 10:04 UTC75
        }
9baaafa4b167atagen2026-10-16 10:04 UTC76
        Op::Remove(i) => nth(l, i).into_iter().for_each(|w| l.remove_window(w)),
9baaafa4b167atagen2026-10-16 10:04 UTC77
        Op::Focus(i) => {
9baaafa4b167atagen2026-10-16 10:04 UTC78
            if let Some(w) = nth(l, i).filter(|&w| l.visible(w)) {
9baaafa4b167atagen2026-10-16 10:04 UTC79
                l.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC80
            }
9baaafa4b167atagen2026-10-16 10:04 UTC81
        }
9baaafa4b167atagen2026-10-16 10:04 UTC82
        Op::Move(d) => {
9baaafa4b167atagen2026-10-16 10:04 UTC83
            l.move_window(d);
9baaafa4b167atagen2026-10-16 10:04 UTC84
        }
21e8e8e5aa3datagen2026-10-20 11:44 UTC85
        Op::Block(d, m) => {
21e8e8e5aa3datagen2026-10-20 11:44 UTC86
            l.move_container(d, m);
4444c779eb48atagen2026-10-16 12:07 UTC87
        }
4444c779eb48atagen2026-10-16 12:07 UTC88
        Op::Merge(d) => {
4444c779eb48atagen2026-10-16 12:07 UTC89
            l.merge_window(d);
9baaafa4b167atagen2026-10-16 10:04 UTC90
        }
9baaafa4b167atagen2026-10-16 10:04 UTC91
        Op::View(t) => active.into_iter().for_each(|o| l.view(o, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC92
        Op::ToggleView(t) => active
9baaafa4b167atagen2026-10-16 10:04 UTC93
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC94
            .for_each(|o| l.toggle_view(o, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC95
        Op::Retag(i, t) => nth(l, i)
9baaafa4b167atagen2026-10-16 10:04 UTC96
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC97
            .for_each(|w| l.set_tag(w, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC98
        Op::Float(i) => nth(l, i).into_iter().for_each(|w| l.toggle_floating(w)),
9baaafa4b167atagen2026-10-16 10:04 UTC99
        Op::Expand(s) => {
4444c779eb48atagen2026-10-16 12:07 UTC100
            l.expand(
4444c779eb48atagen2026-10-16 12:07 UTC101
                [
4444c779eb48atagen2026-10-16 12:07 UTC102
                    ExpandScope::Window,
4444c779eb48atagen2026-10-16 12:07 UTC103
                    ExpandScope::Container,
4444c779eb48atagen2026-10-16 12:07 UTC104
                    ExpandScope::All,
4444c779eb48atagen2026-10-16 12:07 UTC105
                ][s as usize],
4444c779eb48atagen2026-10-16 12:07 UTC106
            );
9baaafa4b167atagen2026-10-16 10:04 UTC107
        }
9baaafa4b167atagen2026-10-16 10:04 UTC108
        Op::Flip => active.into_iter().for_each(|o| l.flip(o)),
9baaafa4b167atagen2026-10-16 10:04 UTC109
        Op::Resize(h, d) => {
9baaafa4b167atagen2026-10-16 10:04 UTC110
            let axis = if h { Axis::Horizontal } else { Axis::Vertical };
9baaafa4b167atagen2026-10-16 10:04 UTC111
            l.resize(axis, f64::from(d) / 200.0);
9baaafa4b167atagen2026-10-16 10:04 UTC112
        }
9baaafa4b167atagen2026-10-16 10:04 UTC113
        Op::Scratch(i) => nth(l, i).into_iter().for_each(|w| l.scratchpad_add(w, "s")),
9baaafa4b167atagen2026-10-16 10:04 UTC114
        Op::Summon => {
9baaafa4b167atagen2026-10-16 10:04 UTC115
            l.summon("s");
9baaafa4b167atagen2026-10-16 10:04 UTC116
        }
9baaafa4b167atagen2026-10-16 10:04 UTC117
        Op::Unplug => {
9baaafa4b167atagen2026-10-16 10:04 UTC118
            let b = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o);
9baaafa4b167atagen2026-10-16 10:04 UTC119
            if let Some(o) = b {
9baaafa4b167atagen2026-10-16 10:04 UTC120
                l.remove_output(o);
9baaafa4b167atagen2026-10-16 10:04 UTC121
            }
9baaafa4b167atagen2026-10-16 10:04 UTC122
        }
9baaafa4b167atagen2026-10-16 10:04 UTC123
        Op::Replug => {
9baaafa4b167atagen2026-10-16 10:04 UTC124
            if !l.outputs().any(|(_, out)| out.name == "b") {
9baaafa4b167atagen2026-10-16 10:04 UTC125
                l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
9baaafa4b167atagen2026-10-16 10:04 UTC126
            }
9baaafa4b167atagen2026-10-16 10:04 UTC127
        }
9baaafa4b167atagen2026-10-16 10:04 UTC128
    }
9baaafa4b167atagen2026-10-16 10:04 UTC129
}
9baaafa4b167atagen2026-10-16 10:04 UTC130
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC131
/// The area a tiled window gets, zero for floating or unplaced ones.
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC132
fn tiled_area(l: &Layout, w: WindowId) -> f64 {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC133
    let Some(o) = l.window(w).and_then(|x| x.output) else {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC134
        return 0.0;
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC135
    };
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC136
    l.arrange(o)
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC137
        .tiled
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC138
        .iter()
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC139
        .find(|p| p.window == w && p.visible)
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC140
        .map_or(0.0, |p| p.rect.w * p.rect.h)
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC141
}
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC142
9baaafa4b167atagen2026-10-16 10:04 UTC143
fn check_geometry(l: &Layout) {
9baaafa4b167atagen2026-10-16 10:04 UTC144
    for (o, out) in l.outputs() {
9baaafa4b167atagen2026-10-16 10:04 UTC145
        let arr = l.arrange(o);
38125b3487d7atagen2026-10-17 05:02 UTC146
        let mut drawn: Vec<WindowId> = arr
38125b3487d7atagen2026-10-17 05:02 UTC147
            .tiled
38125b3487d7atagen2026-10-17 05:02 UTC148
            .iter()
38125b3487d7atagen2026-10-17 05:02 UTC149
            .filter(|p| p.visible)
38125b3487d7atagen2026-10-17 05:02 UTC150
            .map(|p| p.window)
38125b3487d7atagen2026-10-17 05:02 UTC151
            .chain(arr.floating.iter().map(|&(w, _)| w))
38125b3487d7atagen2026-10-17 05:02 UTC152
            .chain(arr.fullscreen)
38125b3487d7atagen2026-10-17 05:02 UTC153
            .collect();
38125b3487d7atagen2026-10-17 05:02 UTC154
        drawn.sort();
38125b3487d7atagen2026-10-17 05:02 UTC155
        drawn.dedup();
38125b3487d7atagen2026-10-17 05:02 UTC156
        let mut visible: Vec<WindowId> = l
38125b3487d7atagen2026-10-17 05:02 UTC157
            .windows()
38125b3487d7atagen2026-10-17 05:02 UTC158
            .filter(|&(w, win)| win.output == Some(o) && l.visible(w))
38125b3487d7atagen2026-10-17 05:02 UTC159
            .map(|(w, _)| w)
38125b3487d7atagen2026-10-17 05:02 UTC160
            .collect();
38125b3487d7atagen2026-10-17 05:02 UTC161
        visible.sort();
38125b3487d7atagen2026-10-17 05:02 UTC162
        assert_eq!(drawn, visible, "arrangement disagrees with visible()");
9baaafa4b167atagen2026-10-16 10:04 UTC163
        let shown: Vec<Rect> = arr
9baaafa4b167atagen2026-10-16 10:04 UTC164
            .tiled
9baaafa4b167atagen2026-10-16 10:04 UTC165
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC166
            .filter(|p| p.visible)
9baaafa4b167atagen2026-10-16 10:04 UTC167
            .map(|p| p.rect)
9baaafa4b167atagen2026-10-16 10:04 UTC168
            .collect();
1c8e1b4009f5atagen2026-10-16 23:19 UTC169
        for p in arr.tiled.iter().filter(|p| !p.visible) {
1c8e1b4009f5atagen2026-10-16 23:19 UTC170
            assert!(p.rect.w * p.rect.h <= 1e-6, "hidden {:?} has area", p.rect);
1c8e1b4009f5atagen2026-10-16 23:19 UTC171
        }
9baaafa4b167atagen2026-10-16 10:04 UTC172
        for r in &shown {
9baaafa4b167atagen2026-10-16 10:04 UTC173
            assert!(r.w >= 0.0 && r.h >= 0.0);
9baaafa4b167atagen2026-10-16 10:04 UTC174
            assert!(r.x >= out.area.x - 1e-6 && r.right() <= out.area.right() + 1e-6);
9baaafa4b167atagen2026-10-16 10:04 UTC175
            assert!(r.y >= out.area.y - 1e-6 && r.bottom() <= out.area.bottom() + 1e-6);
9baaafa4b167atagen2026-10-16 10:04 UTC176
        }
9baaafa4b167atagen2026-10-16 10:04 UTC177
        for (i, a) in shown.iter().enumerate() {
9baaafa4b167atagen2026-10-16 10:04 UTC178
            for b in &shown[i + 1..] {
9baaafa4b167atagen2026-10-16 10:04 UTC179
                let ox = a.right().min(b.right()) - a.x.max(b.x);
9baaafa4b167atagen2026-10-16 10:04 UTC180
                let oy = a.bottom().min(b.bottom()) - a.y.max(b.y);
9baaafa4b167atagen2026-10-16 10:04 UTC181
                assert!(ox <= 1e-6 || oy <= 1e-6, "{a:?} overlaps {b:?}");
9baaafa4b167atagen2026-10-16 10:04 UTC182
            }
9baaafa4b167atagen2026-10-16 10:04 UTC183
        }
9baaafa4b167atagen2026-10-16 10:04 UTC184
    }
9baaafa4b167atagen2026-10-16 10:04 UTC185
}
9baaafa4b167atagen2026-10-16 10:04 UTC186
9baaafa4b167atagen2026-10-16 10:04 UTC187
proptest! {
9baaafa4b167atagen2026-10-16 10:04 UTC188
    #![proptest_config(ProptestConfig::with_cases(2000))]
9baaafa4b167atagen2026-10-16 10:04 UTC189
9baaafa4b167atagen2026-10-16 10:04 UTC190
    #[test]
86ef802692f5atagen2026-10-19 07:43 UTC191
    fn random_commands_keep_the_model_sound(
86ef802692f5atagen2026-10-19 07:43 UTC192
        ops in prop::collection::vec(op(), 1..80),
86ef802692f5atagen2026-10-19 07:43 UTC193
        autotile in any::<bool>(),
86ef802692f5atagen2026-10-19 07:43 UTC194
    ) {
86ef802692f5atagen2026-10-19 07:43 UTC195
        let mut l = Layout {
86ef802692f5atagen2026-10-19 07:43 UTC196
            autotile,
86ef802692f5atagen2026-10-19 07:43 UTC197
            ..Layout::default()
86ef802692f5atagen2026-10-19 07:43 UTC198
        };
9baaafa4b167atagen2026-10-16 10:04 UTC199
        l.add_output("a", AREA);
9baaafa4b167atagen2026-10-16 10:04 UTC200
        l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
9baaafa4b167atagen2026-10-16 10:04 UTC201
        let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];
9baaafa4b167atagen2026-10-16 10:04 UTC202
        for op in &ops {
21e8e8e5aa3datagen2026-10-20 11:44 UTC203
            let moving = matches!(op, Op::Move(_) | Op::Merge(_) | Op::Block(..));
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC204
            let shown_before = l.focused().filter(|&w| tiled_area(&l, w) > 1.0);
9baaafa4b167atagen2026-10-16 10:04 UTC205
            apply(&mut l, &tags, op);
9baaafa4b167atagen2026-10-16 10:04 UTC206
            l.check();
9baaafa4b167atagen2026-10-16 10:04 UTC207
            check_geometry(&l);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC208
            if moving && let Some(w) = shown_before {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC209
                prop_assert!(tiled_area(&l, w) > 1.0, "{w:?} vanished after {op:?}");
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC210
            }
9baaafa4b167atagen2026-10-16 10:04 UTC211
        }
9baaafa4b167atagen2026-10-16 10:04 UTC212
    }
38125b3487d7atagen2026-10-17 05:02 UTC213
38125b3487d7atagen2026-10-17 05:02 UTC214
    #[test]
38125b3487d7atagen2026-10-17 05:02 UTC215
    fn replugging_restores_what_unplugging_moved(ops in prop::collection::vec(op(), 1..60)) {
38125b3487d7atagen2026-10-17 05:02 UTC216
        let mut l = Layout::default();
38125b3487d7atagen2026-10-17 05:02 UTC217
        l.add_output("a", AREA);
38125b3487d7atagen2026-10-17 05:02 UTC218
        l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
38125b3487d7atagen2026-10-17 05:02 UTC219
        let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];
38125b3487d7atagen2026-10-17 05:02 UTC220
        for op in &ops {
38125b3487d7atagen2026-10-17 05:02 UTC221
            apply(&mut l, &tags, op);
38125b3487d7atagen2026-10-17 05:02 UTC222
        }
38125b3487d7atagen2026-10-17 05:02 UTC223
        let Some(b) = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o) else {
38125b3487d7atagen2026-10-17 05:02 UTC224
            return Ok(());
38125b3487d7atagen2026-10-17 05:02 UTC225
        };
38125b3487d7atagen2026-10-17 05:02 UTC226
        let before = |l: &Layout, o| {
38125b3487d7atagen2026-10-17 05:02 UTC227
            let out = l.output(o).unwrap();
38125b3487d7atagen2026-10-17 05:02 UTC228
            (out.tree.windows().collect::<Vec<_>>(), out.floating.clone(), out.viewed.clone())
38125b3487d7atagen2026-10-17 05:02 UTC229
        };
38125b3487d7atagen2026-10-17 05:02 UTC230
        let was = before(&l, b);
38125b3487d7atagen2026-10-17 05:02 UTC231
        l.remove_output(b);
38125b3487d7atagen2026-10-17 05:02 UTC232
        let b = l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
38125b3487d7atagen2026-10-17 05:02 UTC233
        l.check();
38125b3487d7atagen2026-10-17 05:02 UTC234
        prop_assert_eq!(before(&l, b), was);
38125b3487d7atagen2026-10-17 05:02 UTC235
    }
9baaafa4b167atagen2026-10-16 10:04 UTC236
}
9baaafa4b167atagen2026-10-16 10:04 UTC237
9baaafa4b167atagen2026-10-16 10:04 UTC238
/// The hidden-slot rules from planner.html, demo 11.
9baaafa4b167atagen2026-10-16 10:04 UTC239
#[test]
9baaafa4b167atagen2026-10-16 10:04 UTC240
fn hidden_windows_keep_their_slots() {
9baaafa4b167atagen2026-10-16 10:04 UTC241
    let (a, b, c, d, e) = (1, 2, 3, 4, 5);
9baaafa4b167atagen2026-10-16 10:04 UTC242
    let mut t: Tree<u32> = Tree::default();
9baaafa4b167atagen2026-10-16 10:04 UTC243
    t.insert_row(a);
9baaafa4b167atagen2026-10-16 10:04 UTC244
    t.insert_after(d, a);
9baaafa4b167atagen2026-10-16 10:04 UTC245
    t.insert_after(b, d);
9baaafa4b167atagen2026-10-16 10:04 UTC246
    t.insert_row(c);
9baaafa4b167atagen2026-10-16 10:04 UTC247
    t.insert_after(e, c);
9baaafa4b167atagen2026-10-16 10:04 UTC248
    t.check();
9baaafa4b167atagen2026-10-16 10:04 UTC249
    let hidden = |w: u32| w != d && w != e;
9baaafa4b167atagen2026-10-16 10:04 UTC250
    let all = |_| true;
9baaafa4b167atagen2026-10-16 10:04 UTC251
    let rect = |t: &Tree<u32>, w| {
9baaafa4b167atagen2026-10-16 10:04 UTC252
        t.arrange(AREA, 0.0, all)
9baaafa4b167atagen2026-10-16 10:04 UTC253
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC254
            .find(|p| p.0 == w)
9baaafa4b167atagen2026-10-16 10:04 UTC255
            .unwrap()
9baaafa4b167atagen2026-10-16 10:04 UTC256
            .1
9baaafa4b167atagen2026-10-16 10:04 UTC257
    };
9baaafa4b167atagen2026-10-16 10:04 UTC258
9baaafa4b167atagen2026-10-16 10:04 UTC259
    assert!(t.move_window(a, Dir::Right, hidden));
9baaafa4b167atagen2026-10-16 10:04 UTC260
    assert!(rect(&t, b).x < rect(&t, d).x && rect(&t, d).x < rect(&t, a).x);
9baaafa4b167atagen2026-10-16 10:04 UTC261
9baaafa4b167atagen2026-10-16 10:04 UTC262
    assert!(t.move_window(c, Dir::Up, hidden));
9baaafa4b167atagen2026-10-16 10:04 UTC263
    t.check();
9baaafa4b167atagen2026-10-16 10:04 UTC264
    assert_eq!(rect(&t, e).w, AREA.w, "E is demoted to a row of its own");
9baaafa4b167atagen2026-10-16 10:04 UTC265
}
60f00dd4f2beatagen2026-10-16 11:50 UTC266
60f00dd4f2beatagen2026-10-16 11:50 UTC267
/// Moving out of a row toward a lane of hidden windows joins that lane, so
60f00dd4f2beatagen2026-10-16 11:50 UTC268
/// they share it when shown again instead of a new lane opening between.
60f00dd4f2beatagen2026-10-16 11:50 UTC269
#[test]
60f00dd4f2beatagen2026-10-16 11:50 UTC270
fn moving_toward_a_hidden_lane_joins_it() {
60f00dd4f2beatagen2026-10-16 11:50 UTC271
    let (a, b, c, d, e) = (1, 2, 3, 4, 5);
60f00dd4f2beatagen2026-10-16 11:50 UTC272
    let mut t: Tree<u32> = Tree::default();
60f00dd4f2beatagen2026-10-16 11:50 UTC273
    t.insert_row(a);
60f00dd4f2beatagen2026-10-16 11:50 UTC274
    t.insert_after(b, a);
60f00dd4f2beatagen2026-10-16 11:50 UTC275
    t.insert_after(c, b);
60f00dd4f2beatagen2026-10-16 11:50 UTC276
    t.insert_row(d);
60f00dd4f2beatagen2026-10-16 11:50 UTC277
    t.insert_after(e, c);
60f00dd4f2beatagen2026-10-16 11:50 UTC278
    let tag_two = |w: u32| w != a && w != d;
60f00dd4f2beatagen2026-10-16 11:50 UTC279
    assert!(t.move_window(e, Dir::Down, tag_two));
60f00dd4f2beatagen2026-10-16 11:50 UTC280
    t.check();
60f00dd4f2beatagen2026-10-16 11:50 UTC281
    let all = t.arrange(AREA, 0.0, |_| true);
60f00dd4f2beatagen2026-10-16 11:50 UTC282
    let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;
60f00dd4f2beatagen2026-10-16 11:50 UTC283
    assert_eq!(rect(e).y, rect(d).y);
60f00dd4f2beatagen2026-10-16 11:50 UTC284
    assert!(rect(d).x < rect(e).x);
60f00dd4f2beatagen2026-10-16 11:50 UTC285
    assert!(rect(a).y < rect(d).y);
60f00dd4f2beatagen2026-10-16 11:50 UTC286
}
91957f0ae29eatagen2026-10-17 09:24 UTC287
91957f0ae29eatagen2026-10-17 09:24 UTC288
#[test]
605c44c8c5fcatagen2026-10-17 09:57 UTC289
fn merging_steps_out_of_a_stack_before_joining_another() {
91957f0ae29eatagen2026-10-17 09:24 UTC290
    let (a, b, c) = (1, 2, 3);
605c44c8c5fcatagen2026-10-17 09:57 UTC291
    let stacked = || {
605c44c8c5fcatagen2026-10-17 09:57 UTC292
        let mut t: Tree<u32> = Tree::default();
605c44c8c5fcatagen2026-10-17 09:57 UTC293
        t.insert_row(a);
605c44c8c5fcatagen2026-10-17 09:57 UTC294
        t.insert_after(b, a);
605c44c8c5fcatagen2026-10-17 09:57 UTC295
        t.insert_after(c, b);
605c44c8c5fcatagen2026-10-17 09:57 UTC296
        assert!(t.merge_window(b, Dir::Left, |_| true));
605c44c8c5fcatagen2026-10-17 09:57 UTC297
        t
605c44c8c5fcatagen2026-10-17 09:57 UTC298
    };
605c44c8c5fcatagen2026-10-17 09:57 UTC299
    let rects = |t: &Tree<u32>| {
605c44c8c5fcatagen2026-10-17 09:57 UTC300
        t.check();
605c44c8c5fcatagen2026-10-17 09:57 UTC301
        let all = t.arrange(AREA, 0.0, |_| true);
605c44c8c5fcatagen2026-10-17 09:57 UTC302
        move |w| all.iter().find(|p: &&(u32, Rect)| p.0 == w).unwrap().1
605c44c8c5fcatagen2026-10-17 09:57 UTC303
    };
605c44c8c5fcatagen2026-10-17 09:57 UTC304
605c44c8c5fcatagen2026-10-17 09:57 UTC305
    let mut t = stacked();
605c44c8c5fcatagen2026-10-17 09:57 UTC306
    assert!(t.merge_window(a, Dir::Right, |_| true));
605c44c8c5fcatagen2026-10-17 09:57 UTC307
    let rect = rects(&t);
605c44c8c5fcatagen2026-10-17 09:57 UTC308
    assert!(rect(b).x < rect(a).x && rect(a).x < rect(c).x);
605c44c8c5fcatagen2026-10-17 09:57 UTC309
    assert_eq!(rect(a).h, AREA.h);
605c44c8c5fcatagen2026-10-17 09:57 UTC310
    assert!(t.merge_window(a, Dir::Right, |_| true));
605c44c8c5fcatagen2026-10-17 09:57 UTC311
    let rect = rects(&t);
605c44c8c5fcatagen2026-10-17 09:57 UTC312
    assert_eq!(rect(a).x, rect(c).x);
605c44c8c5fcatagen2026-10-17 09:57 UTC313
605c44c8c5fcatagen2026-10-17 09:57 UTC314
    let mut t = stacked();
91957f0ae29eatagen2026-10-17 09:24 UTC315
    assert!(t.merge_window(a, Dir::Up, |_| true));
605c44c8c5fcatagen2026-10-17 09:57 UTC316
    let rect = rects(&t);
91957f0ae29eatagen2026-10-17 09:24 UTC317
    assert_eq!(rect(a).w, AREA.w);
91957f0ae29eatagen2026-10-17 09:24 UTC318
    assert!(rect(a).bottom() <= rect(b).y && rect(b).y == rect(c).y);
91957f0ae29eatagen2026-10-17 09:24 UTC319
}
54559daf11c3atagen2026-10-17 13:38 UTC320
21e8e8e5aa3datagen2026-10-20 11:44 UTC321
/// An extended window's group moves whole, even into a container running
21e8e8e5aa3datagen2026-10-20 11:44 UTC322
/// its own way, where it would otherwise dissolve and show what it covers.
21e8e8e5aa3datagen2026-10-20 11:44 UTC323
#[test]
21e8e8e5aa3datagen2026-10-20 11:44 UTC324
fn extended_windows_move_their_group() {
21e8e8e5aa3datagen2026-10-20 11:44 UTC325
    let (a, b, c) = (1, 2, 3);
21e8e8e5aa3datagen2026-10-20 11:44 UTC326
    let all = |_| true;
21e8e8e5aa3datagen2026-10-20 11:44 UTC327
    let mut t: Tree<u32> = Tree::default();
21e8e8e5aa3datagen2026-10-20 11:44 UTC328
    t.insert_row(a);
21e8e8e5aa3datagen2026-10-20 11:44 UTC329
    t.insert_after(b, a);
21e8e8e5aa3datagen2026-10-20 11:44 UTC330
    t.insert_after(c, b);
21e8e8e5aa3datagen2026-10-20 11:44 UTC331
    assert!(t.merge_window(b, Dir::Left, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC332
    assert!(t.toggle_expand(b, 0, 1, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC333
    let rects = |t: &Tree<u32>| {
21e8e8e5aa3datagen2026-10-20 11:44 UTC334
        t.check();
21e8e8e5aa3datagen2026-10-20 11:44 UTC335
        assert!(t.grouped(b, all) && t.eclipsed(a, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC336
        let placed = t.arrange(AREA, 0.0, all);
21e8e8e5aa3datagen2026-10-20 11:44 UTC337
        move |w| placed.iter().find(|p: &&(u32, Rect)| p.0 == w).unwrap().1
21e8e8e5aa3datagen2026-10-20 11:44 UTC338
    };
21e8e8e5aa3datagen2026-10-20 11:44 UTC339
21e8e8e5aa3datagen2026-10-20 11:44 UTC340
    assert!(t.move_window(b, Dir::Right, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC341
    let rect = rects(&t);
21e8e8e5aa3datagen2026-10-20 11:44 UTC342
    assert!(rect(c).x < rect(b).x);
21e8e8e5aa3datagen2026-10-20 11:44 UTC343
21e8e8e5aa3datagen2026-10-20 11:44 UTC344
    assert!(t.move_window(b, Dir::Down, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC345
    let rect = rects(&t);
21e8e8e5aa3datagen2026-10-20 11:44 UTC346
    assert!(rect(c).bottom() <= rect(b).y && rect(b).w == AREA.w);
21e8e8e5aa3datagen2026-10-20 11:44 UTC347
21e8e8e5aa3datagen2026-10-20 11:44 UTC348
    assert!(t.merge_window(b, Dir::Up, all));
21e8e8e5aa3datagen2026-10-20 11:44 UTC349
    let rect = rects(&t);
21e8e8e5aa3datagen2026-10-20 11:44 UTC350
    assert!(rect(c).right() <= rect(b).x && rect(b).y == rect(c).y);
21e8e8e5aa3datagen2026-10-20 11:44 UTC351
}
21e8e8e5aa3datagen2026-10-20 11:44 UTC352
54559daf11c3atagen2026-10-17 13:38 UTC353
/// A window alone on screen in a container of hidden ones expands among the
54559daf11c3atagen2026-10-17 13:38 UTC354
/// windows it is shown beside, not inside the container nobody can see.
54559daf11c3atagen2026-10-17 13:38 UTC355
#[test]
54559daf11c3atagen2026-10-17 13:38 UTC356
fn expand_skips_containers_of_hidden_windows() {
54559daf11c3atagen2026-10-17 13:38 UTC357
    let (a, b, c) = (1, 2, 3);
54559daf11c3atagen2026-10-17 13:38 UTC358
    let mut t: Tree<u32> = Tree::default();
54559daf11c3atagen2026-10-17 13:38 UTC359
    t.insert_row(a);
54559daf11c3atagen2026-10-17 13:38 UTC360
    t.insert_after(b, a);
54559daf11c3atagen2026-10-17 13:38 UTC361
    t.insert_after(c, b);
54559daf11c3atagen2026-10-17 13:38 UTC362
    assert!(t.merge_window(a, Dir::Right, |_| true));
54559daf11c3atagen2026-10-17 13:38 UTC363
    let shown = |w: u32| w != a;
54559daf11c3atagen2026-10-17 13:38 UTC364
    assert!(t.toggle_expand(b, 0, 1, shown));
54559daf11c3atagen2026-10-17 13:38 UTC365
    assert!(t.maximized(b, shown));
54559daf11c3atagen2026-10-17 13:38 UTC366
    let all = t.arrange(AREA, 0.0, shown);
54559daf11c3atagen2026-10-17 13:38 UTC367
    let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;
54559daf11c3atagen2026-10-17 13:38 UTC368
    assert_eq!(rect(b).w, AREA.w);
54559daf11c3atagen2026-10-17 13:38 UTC369
}
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC370
e2443dec4dcdatagen2026-10-18 09:58 UTC371
/// Going in and out of a stack repeatedly leaves every window the size it was.
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC372
#[test]
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC373
fn stacking_in_and_out_does_not_drift() {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC374
    for out in [Dir::Up, Dir::Down, Dir::Right] {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC375
        let (a, b, c) = (1, 2, 3);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC376
        let mut t: Tree<u32> = Tree::default();
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC377
        t.insert_row(a);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC378
        t.insert_after(b, a);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC379
        t.insert_after(c, b);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC380
        assert!(t.merge_window(c, Dir::Left, |_| true));
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC381
        assert!(t.merge_window(c, out, |_| true));
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC382
        let once = t.arrange(AREA, 0.0, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC383
        for _ in 0..8 {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC384
            t.merge_window(c, Dir::Left, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC385
            t.merge_window(c, out, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC386
        }
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC387
        t.check();
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC388
        assert_eq!(t.arrange(AREA, 0.0, |_| true), once, "stepping out {out:?}");
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC389
    }
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC390
}
9c0e9500c151atagen2026-10-18 09:09 UTC391
e2443dec4dcdatagen2026-10-18 09:58 UTC392
/// Windows of other tags in the rows around a window must not change what
e2443dec4dcdatagen2026-10-18 09:58 UTC393
/// the user sees as it moves through them, nor drift over round trips.
9c0e9500c151atagen2026-10-18 09:09 UTC394
#[test]
9c0e9500c151atagen2026-10-18 09:09 UTC395
fn moving_past_hidden_windows_does_not_drift() {
9c0e9500c151atagen2026-10-18 09:09 UTC396
    let (a, b, h, h2) = (1, 2, 3, 4);
9c0e9500c151atagen2026-10-18 09:09 UTC397
    let mut t: Tree<u32> = Tree::default();
9c0e9500c151atagen2026-10-18 09:09 UTC398
    t.insert_row(h);
9c0e9500c151atagen2026-10-18 09:09 UTC399
    t.insert_row(a);
9c0e9500c151atagen2026-10-18 09:09 UTC400
    t.insert_after(h2, a);
9c0e9500c151atagen2026-10-18 09:09 UTC401
    t.insert_after(b, h2);
9c0e9500c151atagen2026-10-18 09:09 UTC402
    let vis = |w: u32| w != h && w != h2;
00c7b3db8a3fatagen2026-10-18 10:19 UTC403
    let mut once = None;
9c0e9500c151atagen2026-10-18 09:09 UTC404
    for _ in 0..4 {
9c0e9500c151atagen2026-10-18 09:09 UTC405
        for d in [Dir::Up, Dir::Up, Dir::Down] {
e2443dec4dcdatagen2026-10-18 09:58 UTC406
            t.move_window(b, d, vis);
e2443dec4dcdatagen2026-10-18 09:58 UTC407
            let shown = t.arrange(AREA, 0.0, vis);
e2443dec4dcdatagen2026-10-18 09:58 UTC408
            let r = shown.iter().find(|p| p.0 == b).unwrap().1;
e2443dec4dcdatagen2026-10-18 09:58 UTC409
            assert!(
e2443dec4dcdatagen2026-10-18 09:58 UTC410
                (r.w * r.h - AREA.w * AREA.h / 2.0).abs() < 1e-6,
e2443dec4dcdatagen2026-10-18 09:58 UTC411
                "{d:?} left {r:?}"
e2443dec4dcdatagen2026-10-18 09:58 UTC412
            );
9c0e9500c151atagen2026-10-18 09:09 UTC413
        }
9c0e9500c151atagen2026-10-18 09:09 UTC414
        t.check();
00c7b3db8a3fatagen2026-10-18 10:19 UTC415
        let all = t.arrange(AREA, 0.0, |_| true);
00c7b3db8a3fatagen2026-10-18 10:19 UTC416
        assert_eq!(*once.get_or_insert_with(|| all.clone()), all);
9c0e9500c151atagen2026-10-18 09:09 UTC417
    }
9c0e9500c151atagen2026-10-18 09:09 UTC418
}
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC419
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC420
/// Monitors sleeping together unplug one after another, so windows already
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC421
/// moved off the first must still go home to it, not to the second.
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC422
#[test]
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC423
fn windows_return_to_where_they_were_first_evicted_from() {
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC424
    let mut l = Layout::default();
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC425
    let a = l.add_output("a", AREA);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC426
    l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC427
    let c = l.add_output("c", Rect::new(1800.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC428
    let w = l.add_window(Some(a), None, None);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC429
    l.remove_output(a);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC430
    let b = l.outputs().find(|(_, out)| out.name == "b").unwrap().0;
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC431
    l.remove_output(b);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC432
    assert_eq!(l.window(w).unwrap().output, Some(c));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC433
    l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC434
    let a = l.add_output("a", AREA);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC435
    l.check();
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC436
    assert_eq!(l.window(w).unwrap().output, Some(a));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC437
    assert!(l.output(a).unwrap().tree.contains(w));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC438
}
00c7b3db8a3fatagen2026-10-18 10:19 UTC439
00c7b3db8a3fatagen2026-10-18 10:19 UTC440
/// A stack showing one window looks like a lone window, so moving toward it
00c7b3db8a3fatagen2026-10-18 10:19 UTC441
/// swaps places instead of joining a stack the user cannot see.
00c7b3db8a3fatagen2026-10-18 10:19 UTC442
#[test]
00c7b3db8a3fatagen2026-10-18 10:19 UTC443
fn moving_toward_a_stack_showing_one_window_swaps() {
00c7b3db8a3fatagen2026-10-18 10:19 UTC444
    let (a, b, h) = (1, 2, 3);
00c7b3db8a3fatagen2026-10-18 10:19 UTC445
    let mut t: Tree<u32> = Tree::default();
00c7b3db8a3fatagen2026-10-18 10:19 UTC446
    t.insert_row(a);
00c7b3db8a3fatagen2026-10-18 10:19 UTC447
    t.insert_after(b, a);
00c7b3db8a3fatagen2026-10-18 10:19 UTC448
    t.insert_after(h, b);
00c7b3db8a3fatagen2026-10-18 10:19 UTC449
    assert!(t.merge_window(h, Dir::Left, |_| true));
00c7b3db8a3fatagen2026-10-18 10:19 UTC450
    let vis = |w: u32| w != h;
00c7b3db8a3fatagen2026-10-18 10:19 UTC451
    assert!(t.move_window(a, Dir::Right, vis));
00c7b3db8a3fatagen2026-10-18 10:19 UTC452
    t.check();
00c7b3db8a3fatagen2026-10-18 10:19 UTC453
    let shown = t.arrange(AREA, 0.0, vis);
00c7b3db8a3fatagen2026-10-18 10:19 UTC454
    let rect = |w| shown.iter().find(|p| p.0 == w).unwrap().1;
00c7b3db8a3fatagen2026-10-18 10:19 UTC455
    assert!(rect(b).x < rect(a).x);
00c7b3db8a3fatagen2026-10-18 10:19 UTC456
    assert_eq!(rect(a).h, AREA.h);
00c7b3db8a3fatagen2026-10-18 10:19 UTC457
}
8f1f743a2f6batagen2026-10-19 03:43 UTC458
ae47743b5745atagen2026-10-19 03:49 UTC459
/// A move either changes what is on screen or changes nothing at all, and a
ae47743b5745atagen2026-10-19 03:49 UTC460
/// window move never rearranges hidden windows among themselves. Steps around
ae47743b5745atagen2026-10-19 03:49 UTC461
/// hidden windows used to succeed invisibly and eat the keypress.
8f1f743a2f6batagen2026-10-19 03:43 UTC462
#[test]
8f1f743a2f6batagen2026-10-19 03:43 UTC463
fn moves_are_seen_or_undone() {
8f1f743a2f6batagen2026-10-19 03:43 UTC464
    let dirs = [Dir::Left, Dir::Right, Dir::Up, Dir::Down];
8f1f743a2f6batagen2026-10-19 03:43 UTC465
    let mut runner = proptest::test_runner::TestRunner::deterministic();
ae47743b5745atagen2026-10-19 03:49 UTC466
    let ops = prop::collection::vec((0u8..4, 0u8..4, 0u8..4, 0u8..32), 1..14);
8f1f743a2f6batagen2026-10-19 03:43 UTC467
    for _ in 0..2000 {
8f1f743a2f6batagen2026-10-19 03:43 UTC468
        let seq = proptest::strategy::ValueTree::current(&ops.new_tree(&mut runner).unwrap());
8f1f743a2f6batagen2026-10-19 03:43 UTC469
        let mut t: Tree<u32> = Tree::default();
8f1f743a2f6batagen2026-10-19 03:43 UTC470
        t.insert_row(0);
8f1f743a2f6batagen2026-10-19 03:43 UTC471
        let mut n = 1;
8f1f743a2f6batagen2026-10-19 03:43 UTC472
        for &(w, d, kind, hide) in &seq {
ae47743b5745atagen2026-10-19 03:49 UTC473
            let vis = |x: u32| hide & (1 << x) == 0;
8f1f743a2f6batagen2026-10-19 03:43 UTC474
            if kind == 0 && n < 5 {
8f1f743a2f6batagen2026-10-19 03:43 UTC475
                t.insert_after(n, u32::from(w) % n);
8f1f743a2f6batagen2026-10-19 03:43 UTC476
                n += 1;
8f1f743a2f6batagen2026-10-19 03:43 UTC477
                continue;
8f1f743a2f6batagen2026-10-19 03:43 UTC478
            }
8f1f743a2f6batagen2026-10-19 03:43 UTC479
            let w = u32::from(w) % n;
8f1f743a2f6batagen2026-10-19 03:43 UTC480
            let shown = |t: &Tree<u32>| {
8f1f743a2f6batagen2026-10-19 03:43 UTC481
                let mut v: Vec<_> = t
8f1f743a2f6batagen2026-10-19 03:43 UTC482
                    .arrange(AREA, 0.0, vis)
8f1f743a2f6batagen2026-10-19 03:43 UTC483
                    .into_iter()
8f1f743a2f6batagen2026-10-19 03:43 UTC484
                    .filter(|p| vis(p.0))
8f1f743a2f6batagen2026-10-19 03:43 UTC485
                    .collect();
8f1f743a2f6batagen2026-10-19 03:43 UTC486
                v.sort_by_key(|p| p.0);
8f1f743a2f6batagen2026-10-19 03:43 UTC487
                v
8f1f743a2f6batagen2026-10-19 03:43 UTC488
            };
8f1f743a2f6batagen2026-10-19 03:43 UTC489
            let before = shown(&t);
ae47743b5745atagen2026-10-19 03:49 UTC490
            let hidden = |t: &Tree<u32>| {
ae47743b5745atagen2026-10-19 03:49 UTC491
                let mut t = t.clone();
ae47743b5745atagen2026-10-19 03:49 UTC492
                t.retain(|x| !vis(x));
ae47743b5745atagen2026-10-19 03:49 UTC493
                t.shape()
ae47743b5745atagen2026-10-19 03:49 UTC494
            };
ae47743b5745atagen2026-10-19 03:49 UTC495
            let rest = hidden(&t);
ae47743b5745atagen2026-10-19 03:49 UTC496
            let d = dirs[usize::from(d)];
ae47743b5745atagen2026-10-19 03:49 UTC497
            let moved = match kind {
ae47743b5745atagen2026-10-19 03:49 UTC498
                1 => t.merge_window(w, d, vis),
ae47743b5745atagen2026-10-19 03:49 UTC499
                2 => t.move_window(w, d, vis),
21e8e8e5aa3datagen2026-10-20 11:44 UTC500
                3 => t.move_container(w, d, false, vis),
ae47743b5745atagen2026-10-19 03:49 UTC501
                _ => continue,
8f1f743a2f6batagen2026-10-19 03:43 UTC502
            };
8f1f743a2f6batagen2026-10-19 03:43 UTC503
            t.check();
8f1f743a2f6batagen2026-10-19 03:43 UTC504
            assert_eq!(moved, shown(&t) != before, "{seq:?}");
ae47743b5745atagen2026-10-19 03:49 UTC505
            if kind != 3 {
ae47743b5745atagen2026-10-19 03:49 UTC506
                assert_eq!(hidden(&t), rest, "{seq:?}");
ae47743b5745atagen2026-10-19 03:49 UTC507
            }
8f1f743a2f6batagen2026-10-19 03:43 UTC508
        }
8f1f743a2f6batagen2026-10-19 03:43 UTC509
    }
8f1f743a2f6batagen2026-10-19 03:43 UTC510
}
b44dde08d7acatagen2026-10-19 07:38 UTC511
b44dde08d7acatagen2026-10-19 07:38 UTC512
/// A fullscreen window hides everything but its own transients, which stay
b44dde08d7acatagen2026-10-19 07:38 UTC513
/// reachable by focus and are not hidden when they map.
b44dde08d7acatagen2026-10-19 07:38 UTC514
#[test]
b44dde08d7acatagen2026-10-19 07:38 UTC515
fn fullscreen_leaves_its_transients_reachable() {
b44dde08d7acatagen2026-10-19 07:38 UTC516
    let mut l = Layout::default();
b44dde08d7acatagen2026-10-19 07:38 UTC517
    let o = l.add_output("a", AREA);
b44dde08d7acatagen2026-10-19 07:38 UTC518
    let beside = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC519
    l.focus(beside);
b44dde08d7acatagen2026-10-19 07:38 UTC520
    let game = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC521
    l.focus(game);
b44dde08d7acatagen2026-10-19 07:38 UTC522
    l.toggle_fullscreen(game);
b44dde08d7acatagen2026-10-19 07:38 UTC523
    let stranger = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC524
    assert!(l.covered(stranger));
b44dde08d7acatagen2026-10-19 07:38 UTC525
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
b44dde08d7acatagen2026-10-19 07:38 UTC526
    l.set_parent(dialog, Some(game));
b44dde08d7acatagen2026-10-19 07:38 UTC527
    assert!(!l.covered(dialog));
b44dde08d7acatagen2026-10-19 07:38 UTC528
    l.focus(dialog);
b44dde08d7acatagen2026-10-19 07:38 UTC529
b44dde08d7acatagen2026-10-19 07:38 UTC530
    let arr = l.arrange(o);
b44dde08d7acatagen2026-10-19 07:38 UTC531
    assert_eq!(arr.fullscreen, Some(game));
b44dde08d7acatagen2026-10-19 07:38 UTC532
    assert_eq!(arr.over, vec![dialog]);
b44dde08d7acatagen2026-10-19 07:38 UTC533
    for dir in [Dir::Left, Dir::Right, Dir::Up, Dir::Down] {
b44dde08d7acatagen2026-10-19 07:38 UTC534
        if let Some(w) = l.focus_dir(dir) {
b44dde08d7acatagen2026-10-19 07:38 UTC535
            assert!(w == game || w == dialog, "focus walked to a covered window");
b44dde08d7acatagen2026-10-19 07:38 UTC536
        }
b44dde08d7acatagen2026-10-19 07:38 UTC537
    }
b44dde08d7acatagen2026-10-19 07:38 UTC538
b44dde08d7acatagen2026-10-19 07:38 UTC539
    l.remove_window(game);
b44dde08d7acatagen2026-10-19 07:38 UTC540
    assert!(!l.covered(stranger));
b44dde08d7acatagen2026-10-19 07:38 UTC541
    assert_eq!(l.window(dialog).and_then(|w| w.parent), None);
b44dde08d7acatagen2026-10-19 07:38 UTC542
}
86ef802692f5atagen2026-10-19 07:43 UTC543
86ef802692f5atagen2026-10-19 07:43 UTC544
fn rect_of(t: &Tree<u32>, w: u32, vis: impl Fn(u32) -> bool) -> Rect {
86ef802692f5atagen2026-10-19 07:43 UTC545
    t.arrange(AREA, 0.0, vis)
86ef802692f5atagen2026-10-19 07:43 UTC546
        .into_iter()
86ef802692f5atagen2026-10-19 07:43 UTC547
        .find(|p| p.0 == w)
86ef802692f5atagen2026-10-19 07:43 UTC548
        .unwrap()
86ef802692f5atagen2026-10-19 07:43 UTC549
        .1
86ef802692f5atagen2026-10-19 07:43 UTC550
}
86ef802692f5atagen2026-10-19 07:43 UTC551
86ef802692f5atagen2026-10-19 07:43 UTC552
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC553
fn splitting_a_lone_window_shares_its_space() {
86ef802692f5atagen2026-10-19 07:43 UTC554
    let (a, b, c) = (1, 2, 3);
86ef802692f5atagen2026-10-19 07:43 UTC555
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC556
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC557
    t.insert_split(b, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC558
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC559
    let (ra, rb) = (rect_of(&t, a, |_| true), rect_of(&t, b, |_| true));
86ef802692f5atagen2026-10-19 07:43 UTC560
    assert_eq!((ra.w, rb.w, ra.h), (AREA.w / 2.0, AREA.w / 2.0, AREA.h));
86ef802692f5atagen2026-10-19 07:43 UTC561
    assert!(ra.x < rb.x);
86ef802692f5atagen2026-10-19 07:43 UTC562
86ef802692f5atagen2026-10-19 07:43 UTC563
    t.insert_split(c, b, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC564
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC565
    let (rb, rc) = (rect_of(&t, b, |_| true), rect_of(&t, c, |_| true));
86ef802692f5atagen2026-10-19 07:43 UTC566
    assert_eq!(
86ef802692f5atagen2026-10-19 07:43 UTC567
        (rb.h, rc.h, rb.w),
86ef802692f5atagen2026-10-19 07:43 UTC568
        (AREA.h / 2.0, AREA.h / 2.0, AREA.w / 2.0)
86ef802692f5atagen2026-10-19 07:43 UTC569
    );
86ef802692f5atagen2026-10-19 07:43 UTC570
    assert!(rb.y < rc.y);
86ef802692f5atagen2026-10-19 07:43 UTC571
    assert_eq!(rect_of(&t, a, |_| true), ra, "the sibling is undisturbed");
86ef802692f5atagen2026-10-19 07:43 UTC572
}
86ef802692f5atagen2026-10-19 07:43 UTC573
86ef802692f5atagen2026-10-19 07:43 UTC574
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC575
fn splitting_along_the_parents_axis_appends_a_sibling() {
86ef802692f5atagen2026-10-19 07:43 UTC576
    let (a, b, c, d) = (1, 2, 3, 4);
86ef802692f5atagen2026-10-19 07:43 UTC577
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC578
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC579
    t.insert_split(b, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC580
    t.insert_split(c, b, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC581
    let before = rect_of(&t, a, |_| true);
86ef802692f5atagen2026-10-19 07:43 UTC582
    t.insert_split(d, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC583
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC584
    let all = |_| true;
86ef802692f5atagen2026-10-19 07:43 UTC585
    let (ra, rd, rb) = (
86ef802692f5atagen2026-10-19 07:43 UTC586
        rect_of(&t, a, all),
86ef802692f5atagen2026-10-19 07:43 UTC587
        rect_of(&t, d, all),
86ef802692f5atagen2026-10-19 07:43 UTC588
        rect_of(&t, b, all),
86ef802692f5atagen2026-10-19 07:43 UTC589
    );
86ef802692f5atagen2026-10-19 07:43 UTC590
    assert_eq!((ra.w, rd.w), (before.w / 2.0, before.w / 2.0));
86ef802692f5atagen2026-10-19 07:43 UTC591
    assert!(ra.x < rd.x && rd.x < rb.x);
86ef802692f5atagen2026-10-19 07:43 UTC592
    assert_eq!(rb.w, AREA.w / 2.0, "the neighbour keeps its share");
86ef802692f5atagen2026-10-19 07:43 UTC593
}
86ef802692f5atagen2026-10-19 07:43 UTC594
86ef802692f5atagen2026-10-19 07:43 UTC595
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC596
fn splitting_beside_hidden_windows_leaves_their_slots_alone() {
86ef802692f5atagen2026-10-19 07:43 UTC597
    let (a, b, c, d) = (1, 2, 3, 4);
86ef802692f5atagen2026-10-19 07:43 UTC598
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC599
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC600
    t.insert_after(b, a);
86ef802692f5atagen2026-10-19 07:43 UTC601
    t.insert_after(c, a);
86ef802692f5atagen2026-10-19 07:43 UTC602
    let tag_one = |w: u32| w == a || w == d;
86ef802692f5atagen2026-10-19 07:43 UTC603
    assert_eq!(rect_of(&t, a, tag_one).h, AREA.h);
86ef802692f5atagen2026-10-19 07:43 UTC604
86ef802692f5atagen2026-10-19 07:43 UTC605
    t.insert_split(d, a, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC606
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC607
    let (ra, rd) = (rect_of(&t, a, tag_one), rect_of(&t, d, tag_one));
86ef802692f5atagen2026-10-19 07:43 UTC608
    assert_eq!((ra.h, rd.h, ra.w), (AREA.h / 2.0, AREA.h / 2.0, AREA.w));
86ef802692f5atagen2026-10-19 07:43 UTC609
    assert!(ra.y < rd.y);
86ef802692f5atagen2026-10-19 07:43 UTC610
86ef802692f5atagen2026-10-19 07:43 UTC611
    t.insert_split(d + 1, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC612
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC613
    let shown = |w: u32| w == a || w == d + 1;
86ef802692f5atagen2026-10-19 07:43 UTC614
    let (ra, re) = (rect_of(&t, a, shown), rect_of(&t, d + 1, shown));
86ef802692f5atagen2026-10-19 07:43 UTC615
    assert_eq!((ra.w, re.w), (AREA.w / 2.0, AREA.w / 2.0));
86ef802692f5atagen2026-10-19 07:43 UTC616
    assert!(ra.x < re.x);
86ef802692f5atagen2026-10-19 07:43 UTC617
    assert_eq!(t.windows().count(), 5, "hidden windows stay in the tree");
86ef802692f5atagen2026-10-19 07:43 UTC618
}
86ef802692f5atagen2026-10-19 07:43 UTC619
86ef802692f5atagen2026-10-19 07:43 UTC620
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC621
fn autotile_dwindles_by_the_anchors_shape() {
86ef802692f5atagen2026-10-19 07:43 UTC622
    let mut l = Layout {
86ef802692f5atagen2026-10-19 07:43 UTC623
        autotile: true,
86ef802692f5atagen2026-10-19 07:43 UTC624
        ..Layout::default()
86ef802692f5atagen2026-10-19 07:43 UTC625
    };
86ef802692f5atagen2026-10-19 07:43 UTC626
    let o = l.add_output("a", AREA);
86ef802692f5atagen2026-10-19 07:43 UTC627
    let tag = l.tag("1");
86ef802692f5atagen2026-10-19 07:43 UTC628
    l.view(o, tag);
86ef802692f5atagen2026-10-19 07:43 UTC629
    let mut ids = Vec::new();
86ef802692f5atagen2026-10-19 07:43 UTC630
    for _ in 0..4 {
86ef802692f5atagen2026-10-19 07:43 UTC631
        let w = l.add_window(Some(o), Some(tag), None);
86ef802692f5atagen2026-10-19 07:43 UTC632
        l.focus(w);
86ef802692f5atagen2026-10-19 07:43 UTC633
        ids.push(w);
86ef802692f5atagen2026-10-19 07:43 UTC634
        l.check();
86ef802692f5atagen2026-10-19 07:43 UTC635
    }
86ef802692f5atagen2026-10-19 07:43 UTC636
    let arr = l.arrange(o);
86ef802692f5atagen2026-10-19 07:43 UTC637
    let r = |w| arr.tiled.iter().find(|p| p.window == w).unwrap().rect;
86ef802692f5atagen2026-10-19 07:43 UTC638
    let [a, b, c, d] = [ids[0], ids[1], ids[2], ids[3]];
86ef802692f5atagen2026-10-19 07:43 UTC639
    assert!(
86ef802692f5atagen2026-10-19 07:43 UTC640
        r(a).x < r(b).x && r(a).h > r(b).h * 1.5,
86ef802692f5atagen2026-10-19 07:43 UTC641
        "{a:?} left, {b:?} right"
86ef802692f5atagen2026-10-19 07:43 UTC642
    );
86ef802692f5atagen2026-10-19 07:43 UTC643
    assert!(r(b).y < r(c).y && r(b).x == r(c).x);
86ef802692f5atagen2026-10-19 07:43 UTC644
    assert!(r(c).x < r(d).x && r(c).y == r(d).y);
86ef802692f5atagen2026-10-19 07:43 UTC645
}
9fdf9584d2bdatagen2026-10-19 07:38 UTC646
9fdf9584d2bdatagen2026-10-19 07:38 UTC647
#[test]
9fdf9584d2bdatagen2026-10-19 07:38 UTC648
fn urgency_skips_the_focused_window_and_ends_with_focus() {
9fdf9584d2bdatagen2026-10-19 07:38 UTC649
    let mut l = Layout::default();
9fdf9584d2bdatagen2026-10-19 07:38 UTC650
    let o = l.add_output("a", AREA);
9fdf9584d2bdatagen2026-10-19 07:38 UTC651
    let tag = l.tag("1");
9fdf9584d2bdatagen2026-10-19 07:38 UTC652
    l.view(o, tag);
9fdf9584d2bdatagen2026-10-19 07:38 UTC653
    let a = l.add_window(Some(o), Some(tag), None);
9fdf9584d2bdatagen2026-10-19 07:38 UTC654
    let b = l.add_window(Some(o), Some(tag), None);
9fdf9584d2bdatagen2026-10-19 07:38 UTC655
    l.focus(a);
9fdf9584d2bdatagen2026-10-19 07:38 UTC656
    l.mark_urgent(a);
9fdf9584d2bdatagen2026-10-19 07:38 UTC657
    l.mark_urgent(b);
9fdf9584d2bdatagen2026-10-19 07:38 UTC658
    assert!(!l.window(a).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC659
    assert!(l.window(b).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC660
    assert!(!l.tags().any(|(_, t)| t.urgent));
9fdf9584d2bdatagen2026-10-19 07:38 UTC661
    l.focus(b);
9fdf9584d2bdatagen2026-10-19 07:38 UTC662
    assert!(!l.window(b).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC663
}
b01f4e7ab7e6atagen2026-10-19 07:44 UTC664
b01f4e7ab7e6atagen2026-10-19 07:44 UTC665
#[test]
b01f4e7ab7e6atagen2026-10-19 07:44 UTC666
fn oversized_floats_keep_their_top_edge_and_a_strip() {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC667
    let bounds = Rect::new(0.0, 0.0, 1000.0, 600.0);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC668
    let huge = Rect::new(-5000.0, -300.0, 3000.0, 2000.0);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC669
    let r = clamp_float(huge, bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC670
    assert_eq!((r.x, r.y), (-2936.0, 0.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC671
    let r = clamp_float(Rect::new(5000.0, 5000.0, 3000.0, 2000.0), bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC672
    assert_eq!((r.x, r.y), (936.0, 536.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC673
    let fits = clamp_float(Rect::new(900.0, 500.0, 300.0, 200.0), bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC674
    assert_eq!((fits.x, fits.y), (700.0, 400.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC675
}
4b73e40af1e3atagen2026-10-19 07:44 UTC676
4b73e40af1e3atagen2026-10-19 07:44 UTC677
#[test]
4b73e40af1e3atagen2026-10-19 07:44 UTC678
fn floats_follow_their_output_area_and_panels() {
4b73e40af1e3atagen2026-10-19 07:44 UTC679
    let mut l = Layout::default();
4b73e40af1e3atagen2026-10-19 07:44 UTC680
    let o = l.add_output("a", AREA);
4b73e40af1e3atagen2026-10-19 07:44 UTC681
    let w = l.add_window(Some(o), None, Some(Rect::new(500.0, 300.0, 400.0, 300.0)));
4b73e40af1e3atagen2026-10-19 07:44 UTC682
    l.set_output_area(o, Rect::new(100.0, 50.0, 500.0, 300.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC683
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC684
    assert_eq!((r.x, r.y, r.w, r.h), (350.0, 200.0, 200.0, 150.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC685
4b73e40af1e3atagen2026-10-19 07:44 UTC686
    l.set_reserved(
4b73e40af1e3atagen2026-10-19 07:44 UTC687
        o,
4b73e40af1e3atagen2026-10-19 07:44 UTC688
        Insets {
4b73e40af1e3atagen2026-10-19 07:44 UTC689
            top: 100.0,
4b73e40af1e3atagen2026-10-19 07:44 UTC690
            ..Insets::default()
4b73e40af1e3atagen2026-10-19 07:44 UTC691
        },
4b73e40af1e3atagen2026-10-19 07:44 UTC692
    );
4b73e40af1e3atagen2026-10-19 07:44 UTC693
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC694
    let usable = l.output(o).unwrap().usable();
4b73e40af1e3atagen2026-10-19 07:44 UTC695
    assert!(r.y >= usable.y && r.bottom() <= usable.bottom());
9c937ba68fa7atagen2026-10-19 08:02 UTC696
    assert_eq!((r.w, r.h), (200.0, 150.0), "a panel never shrinks a float");
4b73e40af1e3atagen2026-10-19 07:44 UTC697
}
4b73e40af1e3atagen2026-10-19 07:44 UTC698
4b73e40af1e3atagen2026-10-19 07:44 UTC699
#[test]
4b73e40af1e3atagen2026-10-19 07:44 UTC700
fn floats_shrink_into_a_smaller_output_they_are_evicted_to() {
4b73e40af1e3atagen2026-10-19 07:44 UTC701
    let mut l = Layout::default();
4b73e40af1e3atagen2026-10-19 07:44 UTC702
    let a = l.add_output("a", AREA);
4b73e40af1e3atagen2026-10-19 07:44 UTC703
    let b = l.add_output("b", Rect::new(1000.0, 0.0, 500.0, 300.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC704
    let w = l.add_window(Some(a), None, Some(Rect::new(800.0, 400.0, 200.0, 200.0)));
4b73e40af1e3atagen2026-10-19 07:44 UTC705
    l.remove_output(a);
4b73e40af1e3atagen2026-10-19 07:44 UTC706
    assert_eq!(l.window(w).unwrap().output, Some(b));
4b73e40af1e3atagen2026-10-19 07:44 UTC707
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC708
    let usable = l.output(b).unwrap().usable();
4b73e40af1e3atagen2026-10-19 07:44 UTC709
    assert_eq!((r.w, r.h), (100.0, 100.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC710
    assert!(r.x >= usable.x && r.right() <= usable.right());
4b73e40af1e3atagen2026-10-19 07:44 UTC711
    assert!(r.y >= usable.y && r.bottom() <= usable.bottom());
4b73e40af1e3atagen2026-10-19 07:44 UTC712
}
abe5e42a95d6atagen2026-10-19 07:52 UTC713
abe5e42a95d6atagen2026-10-19 07:52 UTC714
/// Dragging a split moves only the two tiles that meet at it, whichever of
abe5e42a95d6atagen2026-10-19 07:52 UTC715
/// them the drag started from, and stops where one would vanish.
abe5e42a95d6atagen2026-10-19 07:52 UTC716
#[test]
abe5e42a95d6atagen2026-10-19 07:52 UTC717
fn dragging_a_split_moves_just_that_split() {
abe5e42a95d6atagen2026-10-19 07:52 UTC718
    let mut l = Layout::default();
abe5e42a95d6atagen2026-10-19 07:52 UTC719
    let o = l.add_output("a", AREA);
abe5e42a95d6atagen2026-10-19 07:52 UTC720
    let a = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC721
    let b = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC722
    let c = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC723
    let rect = |l: &Layout, w| {
abe5e42a95d6atagen2026-10-19 07:52 UTC724
        l.arrange(o)
abe5e42a95d6atagen2026-10-19 07:52 UTC725
            .tiled
abe5e42a95d6atagen2026-10-19 07:52 UTC726
            .iter()
abe5e42a95d6atagen2026-10-19 07:52 UTC727
            .find(|p| p.window == w)
abe5e42a95d6atagen2026-10-19 07:52 UTC728
            .unwrap()
abe5e42a95d6atagen2026-10-19 07:52 UTC729
            .rect
abe5e42a95d6atagen2026-10-19 07:52 UTC730
    };
abe5e42a95d6atagen2026-10-19 07:52 UTC731
    let before = [rect(&l, a), rect(&l, b), rect(&l, c)];
abe5e42a95d6atagen2026-10-19 07:52 UTC732
    assert!(!l.has_split(a, Dir::Up));
abe5e42a95d6atagen2026-10-19 07:52 UTC733
    assert!(!l.move_split(a, Dir::Up, 50.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC734
    assert!(l.has_split(a, Dir::Down));
abe5e42a95d6atagen2026-10-19 07:52 UTC735
abe5e42a95d6atagen2026-10-19 07:52 UTC736
    assert!(l.move_split(a, Dir::Down, 40.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC737
    let (ra, rb) = (rect(&l, a), rect(&l, b));
abe5e42a95d6atagen2026-10-19 07:52 UTC738
    assert!((ra.h - before[0].h - 40.0).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC739
    assert!((rb.h - before[1].h + 40.0).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC740
    assert_eq!(rect(&l, c).h, before[2].h);
abe5e42a95d6atagen2026-10-19 07:52 UTC741
abe5e42a95d6atagen2026-10-19 07:52 UTC742
    assert!(l.move_split(b, Dir::Up, -40.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC743
    assert!((rect(&l, a).h - before[0].h).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC744
abe5e42a95d6atagen2026-10-19 07:52 UTC745
    l.move_split(a, Dir::Down, 1e6);
abe5e42a95d6atagen2026-10-19 07:52 UTC746
    assert!(rect(&l, b).h > 1.0);
abe5e42a95d6atagen2026-10-19 07:52 UTC747
    l.check();
abe5e42a95d6atagen2026-10-19 07:52 UTC748
}
b001ce7817edatagen2026-10-19 09:23 UTC749
b001ce7817edatagen2026-10-19 09:23 UTC750
#[test]
b001ce7817edatagen2026-10-19 09:23 UTC751
fn scratchpads_stay_centred_whatever_their_rect() {
b001ce7817edatagen2026-10-19 09:23 UTC752
    let mut l = Layout::default();
b001ce7817edatagen2026-10-19 09:23 UTC753
    let o = l.add_output("a", AREA);
b001ce7817edatagen2026-10-19 09:23 UTC754
    let w = l.add_window(Some(o), None, Some(Rect::new(10.0, 20.0, 300.0, 200.0)));
b001ce7817edatagen2026-10-19 09:23 UTC755
    l.scratchpad_add(w, "s");
b001ce7817edatagen2026-10-19 09:23 UTC756
    l.summon("s");
b001ce7817edatagen2026-10-19 09:23 UTC757
    l.set_float_rect(w, Rect::new(0.0, 0.0, 500.0, 5000.0));
b001ce7817edatagen2026-10-19 09:23 UTC758
    let r = l.arrange(o).floating.iter().find(|p| p.0 == w).unwrap().1;
b001ce7817edatagen2026-10-19 09:23 UTC759
    let area = l.output(o).unwrap().usable();
b001ce7817edatagen2026-10-19 09:23 UTC760
    assert_eq!((r.w, r.h), (500.0, area.h));
b001ce7817edatagen2026-10-19 09:23 UTC761
    assert_eq!(r.x + r.w / 2.0, area.x + area.w / 2.0);
b001ce7817edatagen2026-10-19 09:23 UTC762
    assert_eq!(r.y, area.y);
b001ce7817edatagen2026-10-19 09:23 UTC763
}
ecb0c8c1f31datagen2026-10-19 10:15 UTC764
ecb0c8c1f31datagen2026-10-19 10:15 UTC765
/// Focus aimed at a window with a modal dialog lands on the deepest dialog
ecb0c8c1f31datagen2026-10-19 10:15 UTC766
/// from every path, and falls back to the parent once the dialog is gone.
ecb0c8c1f31datagen2026-10-19 10:15 UTC767
#[test]
ecb0c8c1f31datagen2026-10-19 10:15 UTC768
fn modal_dialogs_keep_focus_over_their_parent() {
ecb0c8c1f31datagen2026-10-19 10:15 UTC769
    let mut l = Layout::default();
ecb0c8c1f31datagen2026-10-19 10:15 UTC770
    l.add_output("a", AREA);
ecb0c8c1f31datagen2026-10-19 10:15 UTC771
    let parent = l.add_window(None, None, None);
ecb0c8c1f31datagen2026-10-19 10:15 UTC772
    let other = l.add_window(None, None, None);
ecb0c8c1f31datagen2026-10-19 10:15 UTC773
    l.focus(parent);
ecb0c8c1f31datagen2026-10-19 10:15 UTC774
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
ecb0c8c1f31datagen2026-10-19 10:15 UTC775
    let nested = l.add_window(None, None, Some(Rect::new(350.0, 250.0, 100.0, 50.0)));
ecb0c8c1f31datagen2026-10-19 10:15 UTC776
    l.set_parent(dialog, Some(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC777
    l.set_parent(nested, Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC778
    l.set_modal(dialog, true);
ecb0c8c1f31datagen2026-10-19 10:15 UTC779
    assert_eq!(l.focused(), Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC780
    l.set_modal(nested, true);
ecb0c8c1f31datagen2026-10-19 10:15 UTC781
    assert_eq!(l.focused(), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC782
ecb0c8c1f31datagen2026-10-19 10:15 UTC783
    l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC784
    assert_eq!(l.focused(), Some(other));
ecb0c8c1f31datagen2026-10-19 10:15 UTC785
    assert_eq!(l.focus(parent), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC786
    assert_eq!(l.focused(), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC787
    assert!(l.is_focused(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC788
ecb0c8c1f31datagen2026-10-19 10:15 UTC789
    l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC790
    for dir in [Dir::Left, Dir::Right, Dir::Up, Dir::Down] {
ecb0c8c1f31datagen2026-10-19 10:15 UTC791
        l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC792
        if let Some(w) = l.focus_dir(dir) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC793
            assert_ne!(w, parent, "directional focus stopped on a blocked parent");
ecb0c8c1f31datagen2026-10-19 10:15 UTC794
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC795
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC796
ecb0c8c1f31datagen2026-10-19 10:15 UTC797
    l.focus(parent);
ecb0c8c1f31datagen2026-10-19 10:15 UTC798
    l.remove_window(nested);
ecb0c8c1f31datagen2026-10-19 10:15 UTC799
    assert_eq!(l.focused(), Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC800
    l.remove_window(dialog);
ecb0c8c1f31datagen2026-10-19 10:15 UTC801
    assert_eq!(l.focused(), Some(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC802
    l.check();
ecb0c8c1f31datagen2026-10-19 10:15 UTC803
}
1c7e3075e57fatagen2026-10-19 10:28 UTC804
1c7e3075e57fatagen2026-10-19 10:28 UTC805
/// A fullscreen window nobody ever focused, as a rule with `focus = false`
1c7e3075e57fatagen2026-10-19 10:28 UTC806
/// makes, still takes the output once its tag is in view.
1c7e3075e57fatagen2026-10-19 10:28 UTC807
#[test]
1c7e3075e57fatagen2026-10-19 10:28 UTC808
fn fullscreen_does_not_need_focus_history() {
1c7e3075e57fatagen2026-10-19 10:28 UTC809
    let mut l = Layout::default();
1c7e3075e57fatagen2026-10-19 10:28 UTC810
    let o = l.add_output("a", AREA);
1c7e3075e57fatagen2026-10-19 10:28 UTC811
    let (main, other) = (l.tag("main"), l.tag("other"));
1c7e3075e57fatagen2026-10-19 10:28 UTC812
    l.view(o, main);
1c7e3075e57fatagen2026-10-19 10:28 UTC813
    let shown = l.add_window(None, Some(main), None);
1c7e3075e57fatagen2026-10-19 10:28 UTC814
    let player = l.add_window(None, Some(main), None);
1c7e3075e57fatagen2026-10-19 10:28 UTC815
    l.toggle_fullscreen(player);
1c7e3075e57fatagen2026-10-19 10:28 UTC816
    assert_eq!(l.arrange(o).fullscreen, Some(player));
1c7e3075e57fatagen2026-10-19 10:28 UTC817
    assert!(l.covered(shown));
1c7e3075e57fatagen2026-10-19 10:28 UTC818
1c7e3075e57fatagen2026-10-19 10:28 UTC819
    let hidden = l.add_window(None, Some(other), None);
1c7e3075e57fatagen2026-10-19 10:28 UTC820
    l.toggle_fullscreen(hidden);
1c7e3075e57fatagen2026-10-19 10:28 UTC821
    assert_eq!(l.arrange(o).fullscreen, Some(player));
1c7e3075e57fatagen2026-10-19 10:28 UTC822
    l.focus(shown);
1c7e3075e57fatagen2026-10-19 10:28 UTC823
    l.view(o, other);
1c7e3075e57fatagen2026-10-19 10:28 UTC824
    assert_eq!(l.arrange(o).fullscreen, Some(hidden));
1c7e3075e57fatagen2026-10-19 10:28 UTC825
}
1c7e3075e57fatagen2026-10-19 10:28 UTC826
1c7e3075e57fatagen2026-10-19 10:28 UTC827
/// A request to focus a covered window flags it instead, but its own
1c7e3075e57fatagen2026-10-19 10:28 UTC828
/// fullscreen window's transients and a summoned scratchpad take the focus.
1c7e3075e57fatagen2026-10-19 10:28 UTC829
#[test]
1c7e3075e57fatagen2026-10-19 10:28 UTC830
fn covered_windows_are_flagged_not_focused() {
1c7e3075e57fatagen2026-10-19 10:28 UTC831
    let mut l = Layout::default();
1c7e3075e57fatagen2026-10-19 10:28 UTC832
    let o = l.add_output("a", AREA);
1c7e3075e57fatagen2026-10-19 10:28 UTC833
    let game = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC834
    l.focus(game);
1c7e3075e57fatagen2026-10-19 10:28 UTC835
    l.toggle_fullscreen(game);
1c7e3075e57fatagen2026-10-19 10:28 UTC836
    let stranger = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC837
    assert!(!l.focus_unless_covered(stranger));
1c7e3075e57fatagen2026-10-19 10:28 UTC838
    assert_eq!(l.focused(), Some(game));
1c7e3075e57fatagen2026-10-19 10:28 UTC839
    assert!(l.window(stranger).unwrap().urgent);
1c7e3075e57fatagen2026-10-19 10:28 UTC840
1c7e3075e57fatagen2026-10-19 10:28 UTC841
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
1c7e3075e57fatagen2026-10-19 10:28 UTC842
    l.set_parent(dialog, Some(game));
1c7e3075e57fatagen2026-10-19 10:28 UTC843
    assert!(l.focus_unless_covered(dialog));
1c7e3075e57fatagen2026-10-19 10:28 UTC844
    assert_eq!(l.focused(), Some(dialog));
1c7e3075e57fatagen2026-10-19 10:28 UTC845
1c7e3075e57fatagen2026-10-19 10:28 UTC846
    let pad = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC847
    l.scratchpad_add(pad, "s");
1c7e3075e57fatagen2026-10-19 10:28 UTC848
    assert_eq!(l.summon("s"), Some(pad));
1c7e3075e57fatagen2026-10-19 10:28 UTC849
    assert_eq!(l.focused(), Some(pad));
1c7e3075e57fatagen2026-10-19 10:28 UTC850
    assert!(l.arrange(o).over.contains(&pad));
1c7e3075e57fatagen2026-10-19 10:28 UTC851
    l.check();
1c7e3075e57fatagen2026-10-19 10:28 UTC852
}
1c7e3075e57fatagen2026-10-19 10:28 UTC853
1c7e3075e57fatagen2026-10-19 10:28 UTC854
/// A window its client hid keeps its place, floating state, parent links and
1c7e3075e57fatagen2026-10-19 10:28 UTC855
/// scratchpad, and comes back as it was when shown again.
1c7e3075e57fatagen2026-10-19 10:28 UTC856
#[test]
1c7e3075e57fatagen2026-10-19 10:28 UTC857
fn hidden_windows_come_back_as_they_were() {
1c7e3075e57fatagen2026-10-19 10:28 UTC858
    let mut l = Layout::default();
1c7e3075e57fatagen2026-10-19 10:28 UTC859
    let o = l.add_output("a", AREA);
1c7e3075e57fatagen2026-10-19 10:28 UTC860
    let tag = l.tag("work");
1c7e3075e57fatagen2026-10-19 10:28 UTC861
    l.view(o, tag);
1c7e3075e57fatagen2026-10-19 10:28 UTC862
    let tray = l.add_window(None, Some(tag), Some(Rect::new(40.0, 50.0, 300.0, 200.0)));
1c7e3075e57fatagen2026-10-19 10:28 UTC863
    let child = l.add_window(None, Some(tag), Some(Rect::new(60.0, 70.0, 100.0, 80.0)));
1c7e3075e57fatagen2026-10-19 10:28 UTC864
    let tiled = l.add_window(None, Some(tag), None);
1c7e3075e57fatagen2026-10-19 10:28 UTC865
    l.set_parent(child, Some(tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC866
    l.toggle_sticky(tray);
1c7e3075e57fatagen2026-10-19 10:28 UTC867
    l.focus(tray);
1c7e3075e57fatagen2026-10-19 10:28 UTC868
    l.focus(tiled);
1c7e3075e57fatagen2026-10-19 10:28 UTC869
1c7e3075e57fatagen2026-10-19 10:28 UTC870
    l.set_hidden(tray, true);
1c7e3075e57fatagen2026-10-19 10:28 UTC871
    assert!(!l.visible(tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC872
    assert!(l.arrange(o).floating.iter().all(|f| f.0 != tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC873
    assert!(!l.focus_unless_covered(tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC874
    assert!(!l.window(tray).unwrap().urgent);
1c7e3075e57fatagen2026-10-19 10:28 UTC875
    assert_eq!(l.focused(), Some(tiled));
1c7e3075e57fatagen2026-10-19 10:28 UTC876
    assert_eq!(l.window(child).and_then(|w| w.parent), Some(tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC877
1c7e3075e57fatagen2026-10-19 10:28 UTC878
    l.set_hidden(tray, false);
1c7e3075e57fatagen2026-10-19 10:28 UTC879
    let w = l.window(tray).unwrap();
1c7e3075e57fatagen2026-10-19 10:28 UTC880
    assert!(l.visible(tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC881
    assert!(w.floating && w.sticky && w.tag == tag);
1c7e3075e57fatagen2026-10-19 10:28 UTC882
    assert_eq!(w.float_rect, Some(Rect::new(40.0, 50.0, 300.0, 200.0)));
1c7e3075e57fatagen2026-10-19 10:28 UTC883
    assert!(l.arrange(o).floating.iter().any(|f| f.0 == tray));
1c7e3075e57fatagen2026-10-19 10:28 UTC884
    l.check();
1c7e3075e57fatagen2026-10-19 10:28 UTC885
}
1c7e3075e57fatagen2026-10-19 10:28 UTC886
1c7e3075e57fatagen2026-10-19 10:28 UTC887
/// Summoning skips a scratchpad window that is hidden to a tray.
1c7e3075e57fatagen2026-10-19 10:28 UTC888
#[test]
1c7e3075e57fatagen2026-10-19 10:28 UTC889
fn summoning_skips_hidden_scratchpad_windows() {
1c7e3075e57fatagen2026-10-19 10:28 UTC890
    let mut l = Layout::default();
1c7e3075e57fatagen2026-10-19 10:28 UTC891
    let o = l.add_output("a", AREA);
1c7e3075e57fatagen2026-10-19 10:28 UTC892
    let a = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC893
    let b = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC894
    l.scratchpad_add(a, "s");
1c7e3075e57fatagen2026-10-19 10:28 UTC895
    l.scratchpad_add(b, "s");
1c7e3075e57fatagen2026-10-19 10:28 UTC896
    l.set_hidden(a, true);
1c7e3075e57fatagen2026-10-19 10:28 UTC897
    assert_eq!(l.summon("s"), Some(b));
1c7e3075e57fatagen2026-10-19 10:28 UTC898
    assert!(l.arrange(o).floating.iter().all(|f| f.0 != a));
1c7e3075e57fatagen2026-10-19 10:28 UTC899
    assert_eq!(l.summon("s"), None);
1c7e3075e57fatagen2026-10-19 10:28 UTC900
}
1c7e3075e57fatagen2026-10-19 10:28 UTC901
1c7e3075e57fatagen2026-10-19 10:28 UTC902
/// A modal dialog that takes focus on mapping puts its parent just beneath
1c7e3075e57fatagen2026-10-19 10:28 UTC903
/// it, so closing it does not land on whatever was focused before the parent.
1c7e3075e57fatagen2026-10-19 10:28 UTC904
#[test]
1c7e3075e57fatagen2026-10-19 10:28 UTC905
fn a_modal_brings_its_parent_up_beneath_it() {
1c7e3075e57fatagen2026-10-19 10:28 UTC906
    let mut l = Layout::default();
1c7e3075e57fatagen2026-10-19 10:28 UTC907
    l.add_output("a", AREA);
1c7e3075e57fatagen2026-10-19 10:28 UTC908
    let parent = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC909
    let other = l.add_window(None, None, None);
1c7e3075e57fatagen2026-10-19 10:28 UTC910
    l.focus(parent);
1c7e3075e57fatagen2026-10-19 10:28 UTC911
    l.focus(other);
1c7e3075e57fatagen2026-10-19 10:28 UTC912
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
1c7e3075e57fatagen2026-10-19 10:28 UTC913
    l.set_parent(dialog, Some(parent));
1c7e3075e57fatagen2026-10-19 10:28 UTC914
    l.set_modal(dialog, true);
1c7e3075e57fatagen2026-10-19 10:28 UTC915
    l.focus(dialog);
1c7e3075e57fatagen2026-10-19 10:28 UTC916
    l.remove_window(dialog);
1c7e3075e57fatagen2026-10-19 10:28 UTC917
    assert_eq!(l.focused(), Some(parent));
1c7e3075e57fatagen2026-10-19 10:28 UTC918
}
243161cd0662atagen2026-10-19 10:41 UTC919
243161cd0662atagen2026-10-19 10:41 UTC920
/// Floats start inside what the bars leave of their output, whether they came
243161cd0662atagen2026-10-19 10:41 UTC921
/// with a rect hanging off its edge or take the default one.
243161cd0662atagen2026-10-19 10:41 UTC922
#[test]
243161cd0662atagen2026-10-19 10:41 UTC923
fn new_floats_land_inside_the_bars() {
243161cd0662atagen2026-10-19 10:41 UTC924
    let mut l = Layout::default();
243161cd0662atagen2026-10-19 10:41 UTC925
    let o = l.add_output("a", AREA);
243161cd0662atagen2026-10-19 10:41 UTC926
    let bar = Insets {
243161cd0662atagen2026-10-19 10:41 UTC927
        top: 40.0,
243161cd0662atagen2026-10-19 10:41 UTC928
        ..Insets::default()
243161cd0662atagen2026-10-19 10:41 UTC929
    };
243161cd0662atagen2026-10-19 10:41 UTC930
    l.set_reserved(o, bar);
243161cd0662atagen2026-10-19 10:41 UTC931
    let usable = l.output(o).unwrap().usable();
243161cd0662atagen2026-10-19 10:41 UTC932
243161cd0662atagen2026-10-19 10:41 UTC933
    let hanging = l.add_window(None, None, Some(Rect::new(900.0, -30.0, 300.0, 200.0)));
243161cd0662atagen2026-10-19 10:41 UTC934
    let r = l.window(hanging).and_then(|w| w.float_rect).unwrap();
243161cd0662atagen2026-10-19 10:41 UTC935
    assert!(r.x >= usable.x && r.right() <= usable.right() && r.y >= usable.y);
243161cd0662atagen2026-10-19 10:41 UTC936
243161cd0662atagen2026-10-19 10:41 UTC937
    let plain = l.add_window(None, None, None);
243161cd0662atagen2026-10-19 10:41 UTC938
    l.toggle_floating(plain);
243161cd0662atagen2026-10-19 10:41 UTC939
    let r = l.window(plain).and_then(|w| w.float_rect).unwrap();
243161cd0662atagen2026-10-19 10:41 UTC940
    assert_eq!(r.y + r.h / 2.0, usable.y + usable.h / 2.0);
243161cd0662atagen2026-10-19 10:41 UTC941
}