atagen

monolith

public
Star0
Watch0
890bf5ded7c308eae73d7974435d40ca8b6f491b
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),
9baaafa4b167atagen2026-10-16 10:04 UTC12
    Block(Dir),
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),
9baaafa4b167atagen2026-10-16 10:04 UTC42
        2 => dir().prop_map(Op::Block),
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
        }
9baaafa4b167atagen2026-10-16 10:04 UTC85
        Op::Block(d) => {
4444c779eb48atagen2026-10-16 12:07 UTC86
            l.move_container(d);
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 {
3b1cf5ee2ea2atagen2026-10-17 13:53 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
54559daf11c3atagen2026-10-17 13:38 UTC321
/// A window alone on screen in a container of hidden ones expands among the
54559daf11c3atagen2026-10-17 13:38 UTC322
/// windows it is shown beside, not inside the container nobody can see.
54559daf11c3atagen2026-10-17 13:38 UTC323
#[test]
54559daf11c3atagen2026-10-17 13:38 UTC324
fn expand_skips_containers_of_hidden_windows() {
54559daf11c3atagen2026-10-17 13:38 UTC325
    let (a, b, c) = (1, 2, 3);
54559daf11c3atagen2026-10-17 13:38 UTC326
    let mut t: Tree<u32> = Tree::default();
54559daf11c3atagen2026-10-17 13:38 UTC327
    t.insert_row(a);
54559daf11c3atagen2026-10-17 13:38 UTC328
    t.insert_after(b, a);
54559daf11c3atagen2026-10-17 13:38 UTC329
    t.insert_after(c, b);
54559daf11c3atagen2026-10-17 13:38 UTC330
    assert!(t.merge_window(a, Dir::Right, |_| true));
54559daf11c3atagen2026-10-17 13:38 UTC331
    let shown = |w: u32| w != a;
54559daf11c3atagen2026-10-17 13:38 UTC332
    assert!(t.toggle_expand(b, 0, 1, shown));
54559daf11c3atagen2026-10-17 13:38 UTC333
    assert!(t.maximized(b, shown));
54559daf11c3atagen2026-10-17 13:38 UTC334
    let all = t.arrange(AREA, 0.0, shown);
54559daf11c3atagen2026-10-17 13:38 UTC335
    let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;
54559daf11c3atagen2026-10-17 13:38 UTC336
    assert_eq!(rect(b).w, AREA.w);
54559daf11c3atagen2026-10-17 13:38 UTC337
}
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC338
e2443dec4dcdatagen2026-10-18 09:58 UTC339
/// Going in and out of a stack repeatedly leaves every window the size it was.
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC340
#[test]
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC341
fn stacking_in_and_out_does_not_drift() {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC342
    for out in [Dir::Up, Dir::Down, Dir::Right] {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC343
        let (a, b, c) = (1, 2, 3);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC344
        let mut t: Tree<u32> = Tree::default();
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC345
        t.insert_row(a);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC346
        t.insert_after(b, a);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC347
        t.insert_after(c, b);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC348
        assert!(t.merge_window(c, Dir::Left, |_| true));
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC349
        assert!(t.merge_window(c, out, |_| true));
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC350
        let once = t.arrange(AREA, 0.0, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC351
        for _ in 0..8 {
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC352
            t.merge_window(c, Dir::Left, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC353
            t.merge_window(c, out, |_| true);
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC354
        }
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC355
        t.check();
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC356
        assert_eq!(t.arrange(AREA, 0.0, |_| true), once, "stepping out {out:?}");
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC357
    }
3b1cf5ee2ea2atagen2026-10-17 13:53 UTC358
}
9c0e9500c151atagen2026-10-18 09:09 UTC359
e2443dec4dcdatagen2026-10-18 09:58 UTC360
/// Windows of other tags in the rows around a window must not change what
e2443dec4dcdatagen2026-10-18 09:58 UTC361
/// the user sees as it moves through them, nor drift over round trips.
9c0e9500c151atagen2026-10-18 09:09 UTC362
#[test]
9c0e9500c151atagen2026-10-18 09:09 UTC363
fn moving_past_hidden_windows_does_not_drift() {
9c0e9500c151atagen2026-10-18 09:09 UTC364
    let (a, b, h, h2) = (1, 2, 3, 4);
9c0e9500c151atagen2026-10-18 09:09 UTC365
    let mut t: Tree<u32> = Tree::default();
9c0e9500c151atagen2026-10-18 09:09 UTC366
    t.insert_row(h);
9c0e9500c151atagen2026-10-18 09:09 UTC367
    t.insert_row(a);
9c0e9500c151atagen2026-10-18 09:09 UTC368
    t.insert_after(h2, a);
9c0e9500c151atagen2026-10-18 09:09 UTC369
    t.insert_after(b, h2);
9c0e9500c151atagen2026-10-18 09:09 UTC370
    let vis = |w: u32| w != h && w != h2;
00c7b3db8a3fatagen2026-10-18 10:19 UTC371
    let mut once = None;
9c0e9500c151atagen2026-10-18 09:09 UTC372
    for _ in 0..4 {
9c0e9500c151atagen2026-10-18 09:09 UTC373
        for d in [Dir::Up, Dir::Up, Dir::Down] {
e2443dec4dcdatagen2026-10-18 09:58 UTC374
            t.move_window(b, d, vis);
e2443dec4dcdatagen2026-10-18 09:58 UTC375
            let shown = t.arrange(AREA, 0.0, vis);
e2443dec4dcdatagen2026-10-18 09:58 UTC376
            let r = shown.iter().find(|p| p.0 == b).unwrap().1;
e2443dec4dcdatagen2026-10-18 09:58 UTC377
            assert!(
e2443dec4dcdatagen2026-10-18 09:58 UTC378
                (r.w * r.h - AREA.w * AREA.h / 2.0).abs() < 1e-6,
e2443dec4dcdatagen2026-10-18 09:58 UTC379
                "{d:?} left {r:?}"
e2443dec4dcdatagen2026-10-18 09:58 UTC380
            );
9c0e9500c151atagen2026-10-18 09:09 UTC381
        }
9c0e9500c151atagen2026-10-18 09:09 UTC382
        t.check();
00c7b3db8a3fatagen2026-10-18 10:19 UTC383
        let all = t.arrange(AREA, 0.0, |_| true);
00c7b3db8a3fatagen2026-10-18 10:19 UTC384
        assert_eq!(*once.get_or_insert_with(|| all.clone()), all);
9c0e9500c151atagen2026-10-18 09:09 UTC385
    }
9c0e9500c151atagen2026-10-18 09:09 UTC386
}
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC387
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC388
/// Monitors sleeping together unplug one after another, so windows already
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC389
/// moved off the first must still go home to it, not to the second.
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC390
#[test]
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC391
fn windows_return_to_where_they_were_first_evicted_from() {
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC392
    let mut l = Layout::default();
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC393
    let a = l.add_output("a", AREA);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC394
    l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC395
    let c = l.add_output("c", Rect::new(1800.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC396
    let w = l.add_window(Some(a), None, None);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC397
    l.remove_output(a);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC398
    let b = l.outputs().find(|(_, out)| out.name == "b").unwrap().0;
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC399
    l.remove_output(b);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC400
    assert_eq!(l.window(w).unwrap().output, Some(c));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC401
    l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC402
    let a = l.add_output("a", AREA);
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC403
    l.check();
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC404
    assert_eq!(l.window(w).unwrap().output, Some(a));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC405
    assert!(l.output(a).unwrap().tree.contains(w));
8ffa3fa4d4a9atagen2026-10-18 09:38 UTC406
}
00c7b3db8a3fatagen2026-10-18 10:19 UTC407
00c7b3db8a3fatagen2026-10-18 10:19 UTC408
/// A stack showing one window looks like a lone window, so moving toward it
00c7b3db8a3fatagen2026-10-18 10:19 UTC409
/// swaps places instead of joining a stack the user cannot see.
00c7b3db8a3fatagen2026-10-18 10:19 UTC410
#[test]
00c7b3db8a3fatagen2026-10-18 10:19 UTC411
fn moving_toward_a_stack_showing_one_window_swaps() {
00c7b3db8a3fatagen2026-10-18 10:19 UTC412
    let (a, b, h) = (1, 2, 3);
00c7b3db8a3fatagen2026-10-18 10:19 UTC413
    let mut t: Tree<u32> = Tree::default();
00c7b3db8a3fatagen2026-10-18 10:19 UTC414
    t.insert_row(a);
00c7b3db8a3fatagen2026-10-18 10:19 UTC415
    t.insert_after(b, a);
00c7b3db8a3fatagen2026-10-18 10:19 UTC416
    t.insert_after(h, b);
00c7b3db8a3fatagen2026-10-18 10:19 UTC417
    assert!(t.merge_window(h, Dir::Left, |_| true));
00c7b3db8a3fatagen2026-10-18 10:19 UTC418
    let vis = |w: u32| w != h;
00c7b3db8a3fatagen2026-10-18 10:19 UTC419
    assert!(t.move_window(a, Dir::Right, vis));
00c7b3db8a3fatagen2026-10-18 10:19 UTC420
    t.check();
00c7b3db8a3fatagen2026-10-18 10:19 UTC421
    let shown = t.arrange(AREA, 0.0, vis);
00c7b3db8a3fatagen2026-10-18 10:19 UTC422
    let rect = |w| shown.iter().find(|p| p.0 == w).unwrap().1;
00c7b3db8a3fatagen2026-10-18 10:19 UTC423
    assert!(rect(b).x < rect(a).x);
00c7b3db8a3fatagen2026-10-18 10:19 UTC424
    assert_eq!(rect(a).h, AREA.h);
00c7b3db8a3fatagen2026-10-18 10:19 UTC425
}
8f1f743a2f6batagen2026-10-19 03:43 UTC426
ae47743b5745atagen2026-10-19 03:49 UTC427
/// A move either changes what is on screen or changes nothing at all, and a
ae47743b5745atagen2026-10-19 03:49 UTC428
/// window move never rearranges hidden windows among themselves. Steps around
ae47743b5745atagen2026-10-19 03:49 UTC429
/// hidden windows used to succeed invisibly and eat the keypress.
8f1f743a2f6batagen2026-10-19 03:43 UTC430
#[test]
8f1f743a2f6batagen2026-10-19 03:43 UTC431
fn moves_are_seen_or_undone() {
8f1f743a2f6batagen2026-10-19 03:43 UTC432
    let dirs = [Dir::Left, Dir::Right, Dir::Up, Dir::Down];
8f1f743a2f6batagen2026-10-19 03:43 UTC433
    let mut runner = proptest::test_runner::TestRunner::deterministic();
ae47743b5745atagen2026-10-19 03:49 UTC434
    let ops = prop::collection::vec((0u8..4, 0u8..4, 0u8..4, 0u8..32), 1..14);
8f1f743a2f6batagen2026-10-19 03:43 UTC435
    for _ in 0..2000 {
8f1f743a2f6batagen2026-10-19 03:43 UTC436
        let seq = proptest::strategy::ValueTree::current(&ops.new_tree(&mut runner).unwrap());
8f1f743a2f6batagen2026-10-19 03:43 UTC437
        let mut t: Tree<u32> = Tree::default();
8f1f743a2f6batagen2026-10-19 03:43 UTC438
        t.insert_row(0);
8f1f743a2f6batagen2026-10-19 03:43 UTC439
        let mut n = 1;
8f1f743a2f6batagen2026-10-19 03:43 UTC440
        for &(w, d, kind, hide) in &seq {
ae47743b5745atagen2026-10-19 03:49 UTC441
            let vis = |x: u32| hide & (1 << x) == 0;
8f1f743a2f6batagen2026-10-19 03:43 UTC442
            if kind == 0 && n < 5 {
8f1f743a2f6batagen2026-10-19 03:43 UTC443
                t.insert_after(n, u32::from(w) % n);
8f1f743a2f6batagen2026-10-19 03:43 UTC444
                n += 1;
8f1f743a2f6batagen2026-10-19 03:43 UTC445
                continue;
8f1f743a2f6batagen2026-10-19 03:43 UTC446
            }
8f1f743a2f6batagen2026-10-19 03:43 UTC447
            let w = u32::from(w) % n;
8f1f743a2f6batagen2026-10-19 03:43 UTC448
            let shown = |t: &Tree<u32>| {
8f1f743a2f6batagen2026-10-19 03:43 UTC449
                let mut v: Vec<_> = t
8f1f743a2f6batagen2026-10-19 03:43 UTC450
                    .arrange(AREA, 0.0, vis)
8f1f743a2f6batagen2026-10-19 03:43 UTC451
                    .into_iter()
8f1f743a2f6batagen2026-10-19 03:43 UTC452
                    .filter(|p| vis(p.0))
8f1f743a2f6batagen2026-10-19 03:43 UTC453
                    .collect();
8f1f743a2f6batagen2026-10-19 03:43 UTC454
                v.sort_by_key(|p| p.0);
8f1f743a2f6batagen2026-10-19 03:43 UTC455
                v
8f1f743a2f6batagen2026-10-19 03:43 UTC456
            };
8f1f743a2f6batagen2026-10-19 03:43 UTC457
            let before = shown(&t);
ae47743b5745atagen2026-10-19 03:49 UTC458
            let hidden = |t: &Tree<u32>| {
ae47743b5745atagen2026-10-19 03:49 UTC459
                let mut t = t.clone();
ae47743b5745atagen2026-10-19 03:49 UTC460
                t.retain(|x| !vis(x));
ae47743b5745atagen2026-10-19 03:49 UTC461
                t.shape()
ae47743b5745atagen2026-10-19 03:49 UTC462
            };
ae47743b5745atagen2026-10-19 03:49 UTC463
            let rest = hidden(&t);
ae47743b5745atagen2026-10-19 03:49 UTC464
            let d = dirs[usize::from(d)];
ae47743b5745atagen2026-10-19 03:49 UTC465
            let moved = match kind {
ae47743b5745atagen2026-10-19 03:49 UTC466
                1 => t.merge_window(w, d, vis),
ae47743b5745atagen2026-10-19 03:49 UTC467
                2 => t.move_window(w, d, vis),
ae47743b5745atagen2026-10-19 03:49 UTC468
                3 => t.move_container(w, d, vis),
ae47743b5745atagen2026-10-19 03:49 UTC469
                _ => continue,
8f1f743a2f6batagen2026-10-19 03:43 UTC470
            };
8f1f743a2f6batagen2026-10-19 03:43 UTC471
            t.check();
8f1f743a2f6batagen2026-10-19 03:43 UTC472
            assert_eq!(moved, shown(&t) != before, "{seq:?}");
ae47743b5745atagen2026-10-19 03:49 UTC473
            if kind != 3 {
ae47743b5745atagen2026-10-19 03:49 UTC474
                assert_eq!(hidden(&t), rest, "{seq:?}");
ae47743b5745atagen2026-10-19 03:49 UTC475
            }
8f1f743a2f6batagen2026-10-19 03:43 UTC476
        }
8f1f743a2f6batagen2026-10-19 03:43 UTC477
    }
8f1f743a2f6batagen2026-10-19 03:43 UTC478
}
b44dde08d7acatagen2026-10-19 07:38 UTC479
b44dde08d7acatagen2026-10-19 07:38 UTC480
/// A fullscreen window hides everything but its own transients, which stay
b44dde08d7acatagen2026-10-19 07:38 UTC481
/// reachable by focus and are not hidden when they map.
b44dde08d7acatagen2026-10-19 07:38 UTC482
#[test]
b44dde08d7acatagen2026-10-19 07:38 UTC483
fn fullscreen_leaves_its_transients_reachable() {
b44dde08d7acatagen2026-10-19 07:38 UTC484
    let mut l = Layout::default();
b44dde08d7acatagen2026-10-19 07:38 UTC485
    let o = l.add_output("a", AREA);
b44dde08d7acatagen2026-10-19 07:38 UTC486
    let beside = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC487
    l.focus(beside);
b44dde08d7acatagen2026-10-19 07:38 UTC488
    let game = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC489
    l.focus(game);
b44dde08d7acatagen2026-10-19 07:38 UTC490
    l.toggle_fullscreen(game);
b44dde08d7acatagen2026-10-19 07:38 UTC491
    let stranger = l.add_window(None, None, None);
b44dde08d7acatagen2026-10-19 07:38 UTC492
    assert!(l.covered(stranger));
b44dde08d7acatagen2026-10-19 07:38 UTC493
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
b44dde08d7acatagen2026-10-19 07:38 UTC494
    l.set_parent(dialog, Some(game));
b44dde08d7acatagen2026-10-19 07:38 UTC495
    assert!(!l.covered(dialog));
b44dde08d7acatagen2026-10-19 07:38 UTC496
    l.focus(dialog);
b44dde08d7acatagen2026-10-19 07:38 UTC497
b44dde08d7acatagen2026-10-19 07:38 UTC498
    let arr = l.arrange(o);
b44dde08d7acatagen2026-10-19 07:38 UTC499
    assert_eq!(arr.fullscreen, Some(game));
b44dde08d7acatagen2026-10-19 07:38 UTC500
    assert_eq!(arr.over, vec![dialog]);
b44dde08d7acatagen2026-10-19 07:38 UTC501
    for dir in [Dir::Left, Dir::Right, Dir::Up, Dir::Down] {
b44dde08d7acatagen2026-10-19 07:38 UTC502
        if let Some(w) = l.focus_dir(dir) {
b44dde08d7acatagen2026-10-19 07:38 UTC503
            assert!(w == game || w == dialog, "focus walked to a covered window");
b44dde08d7acatagen2026-10-19 07:38 UTC504
        }
b44dde08d7acatagen2026-10-19 07:38 UTC505
    }
b44dde08d7acatagen2026-10-19 07:38 UTC506
b44dde08d7acatagen2026-10-19 07:38 UTC507
    l.remove_window(game);
b44dde08d7acatagen2026-10-19 07:38 UTC508
    assert!(!l.covered(stranger));
b44dde08d7acatagen2026-10-19 07:38 UTC509
    assert_eq!(l.window(dialog).and_then(|w| w.parent), None);
b44dde08d7acatagen2026-10-19 07:38 UTC510
}
86ef802692f5atagen2026-10-19 07:43 UTC511
86ef802692f5atagen2026-10-19 07:43 UTC512
fn rect_of(t: &Tree<u32>, w: u32, vis: impl Fn(u32) -> bool) -> Rect {
86ef802692f5atagen2026-10-19 07:43 UTC513
    t.arrange(AREA, 0.0, vis)
86ef802692f5atagen2026-10-19 07:43 UTC514
        .into_iter()
86ef802692f5atagen2026-10-19 07:43 UTC515
        .find(|p| p.0 == w)
86ef802692f5atagen2026-10-19 07:43 UTC516
        .unwrap()
86ef802692f5atagen2026-10-19 07:43 UTC517
        .1
86ef802692f5atagen2026-10-19 07:43 UTC518
}
86ef802692f5atagen2026-10-19 07:43 UTC519
86ef802692f5atagen2026-10-19 07:43 UTC520
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC521
fn splitting_a_lone_window_shares_its_space() {
86ef802692f5atagen2026-10-19 07:43 UTC522
    let (a, b, c) = (1, 2, 3);
86ef802692f5atagen2026-10-19 07:43 UTC523
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC524
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC525
    t.insert_split(b, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC526
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC527
    let (ra, rb) = (rect_of(&t, a, |_| true), rect_of(&t, b, |_| true));
86ef802692f5atagen2026-10-19 07:43 UTC528
    assert_eq!((ra.w, rb.w, ra.h), (AREA.w / 2.0, AREA.w / 2.0, AREA.h));
86ef802692f5atagen2026-10-19 07:43 UTC529
    assert!(ra.x < rb.x);
86ef802692f5atagen2026-10-19 07:43 UTC530
86ef802692f5atagen2026-10-19 07:43 UTC531
    t.insert_split(c, b, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC532
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC533
    let (rb, rc) = (rect_of(&t, b, |_| true), rect_of(&t, c, |_| true));
86ef802692f5atagen2026-10-19 07:43 UTC534
    assert_eq!(
86ef802692f5atagen2026-10-19 07:43 UTC535
        (rb.h, rc.h, rb.w),
86ef802692f5atagen2026-10-19 07:43 UTC536
        (AREA.h / 2.0, AREA.h / 2.0, AREA.w / 2.0)
86ef802692f5atagen2026-10-19 07:43 UTC537
    );
86ef802692f5atagen2026-10-19 07:43 UTC538
    assert!(rb.y < rc.y);
86ef802692f5atagen2026-10-19 07:43 UTC539
    assert_eq!(rect_of(&t, a, |_| true), ra, "the sibling is undisturbed");
86ef802692f5atagen2026-10-19 07:43 UTC540
}
86ef802692f5atagen2026-10-19 07:43 UTC541
86ef802692f5atagen2026-10-19 07:43 UTC542
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC543
fn splitting_along_the_parents_axis_appends_a_sibling() {
86ef802692f5atagen2026-10-19 07:43 UTC544
    let (a, b, c, d) = (1, 2, 3, 4);
86ef802692f5atagen2026-10-19 07:43 UTC545
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC546
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC547
    t.insert_split(b, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC548
    t.insert_split(c, b, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC549
    let before = rect_of(&t, a, |_| true);
86ef802692f5atagen2026-10-19 07:43 UTC550
    t.insert_split(d, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC551
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC552
    let all = |_| true;
86ef802692f5atagen2026-10-19 07:43 UTC553
    let (ra, rd, rb) = (
86ef802692f5atagen2026-10-19 07:43 UTC554
        rect_of(&t, a, all),
86ef802692f5atagen2026-10-19 07:43 UTC555
        rect_of(&t, d, all),
86ef802692f5atagen2026-10-19 07:43 UTC556
        rect_of(&t, b, all),
86ef802692f5atagen2026-10-19 07:43 UTC557
    );
86ef802692f5atagen2026-10-19 07:43 UTC558
    assert_eq!((ra.w, rd.w), (before.w / 2.0, before.w / 2.0));
86ef802692f5atagen2026-10-19 07:43 UTC559
    assert!(ra.x < rd.x && rd.x < rb.x);
86ef802692f5atagen2026-10-19 07:43 UTC560
    assert_eq!(rb.w, AREA.w / 2.0, "the neighbour keeps its share");
86ef802692f5atagen2026-10-19 07:43 UTC561
}
86ef802692f5atagen2026-10-19 07:43 UTC562
86ef802692f5atagen2026-10-19 07:43 UTC563
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC564
fn splitting_beside_hidden_windows_leaves_their_slots_alone() {
86ef802692f5atagen2026-10-19 07:43 UTC565
    let (a, b, c, d) = (1, 2, 3, 4);
86ef802692f5atagen2026-10-19 07:43 UTC566
    let mut t: Tree<u32> = Tree::default();
86ef802692f5atagen2026-10-19 07:43 UTC567
    t.insert_row(a);
86ef802692f5atagen2026-10-19 07:43 UTC568
    t.insert_after(b, a);
86ef802692f5atagen2026-10-19 07:43 UTC569
    t.insert_after(c, a);
86ef802692f5atagen2026-10-19 07:43 UTC570
    let tag_one = |w: u32| w == a || w == d;
86ef802692f5atagen2026-10-19 07:43 UTC571
    assert_eq!(rect_of(&t, a, tag_one).h, AREA.h);
86ef802692f5atagen2026-10-19 07:43 UTC572
86ef802692f5atagen2026-10-19 07:43 UTC573
    t.insert_split(d, a, Axis::Vertical);
86ef802692f5atagen2026-10-19 07:43 UTC574
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC575
    let (ra, rd) = (rect_of(&t, a, tag_one), rect_of(&t, d, tag_one));
86ef802692f5atagen2026-10-19 07:43 UTC576
    assert_eq!((ra.h, rd.h, ra.w), (AREA.h / 2.0, AREA.h / 2.0, AREA.w));
86ef802692f5atagen2026-10-19 07:43 UTC577
    assert!(ra.y < rd.y);
86ef802692f5atagen2026-10-19 07:43 UTC578
86ef802692f5atagen2026-10-19 07:43 UTC579
    t.insert_split(d + 1, a, Axis::Horizontal);
86ef802692f5atagen2026-10-19 07:43 UTC580
    t.check();
86ef802692f5atagen2026-10-19 07:43 UTC581
    let shown = |w: u32| w == a || w == d + 1;
86ef802692f5atagen2026-10-19 07:43 UTC582
    let (ra, re) = (rect_of(&t, a, shown), rect_of(&t, d + 1, shown));
86ef802692f5atagen2026-10-19 07:43 UTC583
    assert_eq!((ra.w, re.w), (AREA.w / 2.0, AREA.w / 2.0));
86ef802692f5atagen2026-10-19 07:43 UTC584
    assert!(ra.x < re.x);
86ef802692f5atagen2026-10-19 07:43 UTC585
    assert_eq!(t.windows().count(), 5, "hidden windows stay in the tree");
86ef802692f5atagen2026-10-19 07:43 UTC586
}
86ef802692f5atagen2026-10-19 07:43 UTC587
86ef802692f5atagen2026-10-19 07:43 UTC588
#[test]
86ef802692f5atagen2026-10-19 07:43 UTC589
fn autotile_dwindles_by_the_anchors_shape() {
86ef802692f5atagen2026-10-19 07:43 UTC590
    let mut l = Layout {
86ef802692f5atagen2026-10-19 07:43 UTC591
        autotile: true,
86ef802692f5atagen2026-10-19 07:43 UTC592
        ..Layout::default()
86ef802692f5atagen2026-10-19 07:43 UTC593
    };
86ef802692f5atagen2026-10-19 07:43 UTC594
    let o = l.add_output("a", AREA);
86ef802692f5atagen2026-10-19 07:43 UTC595
    let tag = l.tag("1");
86ef802692f5atagen2026-10-19 07:43 UTC596
    l.view(o, tag);
86ef802692f5atagen2026-10-19 07:43 UTC597
    let mut ids = Vec::new();
86ef802692f5atagen2026-10-19 07:43 UTC598
    for _ in 0..4 {
86ef802692f5atagen2026-10-19 07:43 UTC599
        let w = l.add_window(Some(o), Some(tag), None);
86ef802692f5atagen2026-10-19 07:43 UTC600
        l.focus(w);
86ef802692f5atagen2026-10-19 07:43 UTC601
        ids.push(w);
86ef802692f5atagen2026-10-19 07:43 UTC602
        l.check();
86ef802692f5atagen2026-10-19 07:43 UTC603
    }
86ef802692f5atagen2026-10-19 07:43 UTC604
    let arr = l.arrange(o);
86ef802692f5atagen2026-10-19 07:43 UTC605
    let r = |w| arr.tiled.iter().find(|p| p.window == w).unwrap().rect;
86ef802692f5atagen2026-10-19 07:43 UTC606
    let [a, b, c, d] = [ids[0], ids[1], ids[2], ids[3]];
86ef802692f5atagen2026-10-19 07:43 UTC607
    assert!(
86ef802692f5atagen2026-10-19 07:43 UTC608
        r(a).x < r(b).x && r(a).h > r(b).h * 1.5,
86ef802692f5atagen2026-10-19 07:43 UTC609
        "{a:?} left, {b:?} right"
86ef802692f5atagen2026-10-19 07:43 UTC610
    );
86ef802692f5atagen2026-10-19 07:43 UTC611
    assert!(r(b).y < r(c).y && r(b).x == r(c).x);
86ef802692f5atagen2026-10-19 07:43 UTC612
    assert!(r(c).x < r(d).x && r(c).y == r(d).y);
86ef802692f5atagen2026-10-19 07:43 UTC613
}
9fdf9584d2bdatagen2026-10-19 07:38 UTC614
9fdf9584d2bdatagen2026-10-19 07:38 UTC615
#[test]
9fdf9584d2bdatagen2026-10-19 07:38 UTC616
fn urgency_skips_the_focused_window_and_ends_with_focus() {
9fdf9584d2bdatagen2026-10-19 07:38 UTC617
    let mut l = Layout::default();
9fdf9584d2bdatagen2026-10-19 07:38 UTC618
    let o = l.add_output("a", AREA);
9fdf9584d2bdatagen2026-10-19 07:38 UTC619
    let tag = l.tag("1");
9fdf9584d2bdatagen2026-10-19 07:38 UTC620
    l.view(o, tag);
9fdf9584d2bdatagen2026-10-19 07:38 UTC621
    let a = l.add_window(Some(o), Some(tag), None);
9fdf9584d2bdatagen2026-10-19 07:38 UTC622
    let b = l.add_window(Some(o), Some(tag), None);
9fdf9584d2bdatagen2026-10-19 07:38 UTC623
    l.focus(a);
9fdf9584d2bdatagen2026-10-19 07:38 UTC624
    l.mark_urgent(a);
9fdf9584d2bdatagen2026-10-19 07:38 UTC625
    l.mark_urgent(b);
9fdf9584d2bdatagen2026-10-19 07:38 UTC626
    assert!(!l.window(a).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC627
    assert!(l.window(b).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC628
    assert!(!l.tags().any(|(_, t)| t.urgent));
9fdf9584d2bdatagen2026-10-19 07:38 UTC629
    l.focus(b);
9fdf9584d2bdatagen2026-10-19 07:38 UTC630
    assert!(!l.window(b).unwrap().urgent);
9fdf9584d2bdatagen2026-10-19 07:38 UTC631
}
b01f4e7ab7e6atagen2026-10-19 07:44 UTC632
b01f4e7ab7e6atagen2026-10-19 07:44 UTC633
#[test]
b01f4e7ab7e6atagen2026-10-19 07:44 UTC634
fn oversized_floats_keep_their_top_edge_and_a_strip() {
b01f4e7ab7e6atagen2026-10-19 07:44 UTC635
    let bounds = Rect::new(0.0, 0.0, 1000.0, 600.0);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC636
    let huge = Rect::new(-5000.0, -300.0, 3000.0, 2000.0);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC637
    let r = clamp_float(huge, bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC638
    assert_eq!((r.x, r.y), (-2936.0, 0.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC639
    let r = clamp_float(Rect::new(5000.0, 5000.0, 3000.0, 2000.0), bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC640
    assert_eq!((r.x, r.y), (936.0, 536.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC641
    let fits = clamp_float(Rect::new(900.0, 500.0, 300.0, 200.0), bounds);
b01f4e7ab7e6atagen2026-10-19 07:44 UTC642
    assert_eq!((fits.x, fits.y), (700.0, 400.0));
b01f4e7ab7e6atagen2026-10-19 07:44 UTC643
}
4b73e40af1e3atagen2026-10-19 07:44 UTC644
4b73e40af1e3atagen2026-10-19 07:44 UTC645
#[test]
4b73e40af1e3atagen2026-10-19 07:44 UTC646
fn floats_follow_their_output_area_and_panels() {
4b73e40af1e3atagen2026-10-19 07:44 UTC647
    let mut l = Layout::default();
4b73e40af1e3atagen2026-10-19 07:44 UTC648
    let o = l.add_output("a", AREA);
4b73e40af1e3atagen2026-10-19 07:44 UTC649
    let w = l.add_window(Some(o), None, Some(Rect::new(500.0, 300.0, 400.0, 300.0)));
4b73e40af1e3atagen2026-10-19 07:44 UTC650
    l.set_output_area(o, Rect::new(100.0, 50.0, 500.0, 300.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC651
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC652
    assert_eq!((r.x, r.y, r.w, r.h), (350.0, 200.0, 200.0, 150.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC653
4b73e40af1e3atagen2026-10-19 07:44 UTC654
    l.set_reserved(
4b73e40af1e3atagen2026-10-19 07:44 UTC655
        o,
4b73e40af1e3atagen2026-10-19 07:44 UTC656
        Insets {
4b73e40af1e3atagen2026-10-19 07:44 UTC657
            top: 100.0,
4b73e40af1e3atagen2026-10-19 07:44 UTC658
            ..Insets::default()
4b73e40af1e3atagen2026-10-19 07:44 UTC659
        },
4b73e40af1e3atagen2026-10-19 07:44 UTC660
    );
4b73e40af1e3atagen2026-10-19 07:44 UTC661
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC662
    let usable = l.output(o).unwrap().usable();
4b73e40af1e3atagen2026-10-19 07:44 UTC663
    assert!(r.y >= usable.y && r.bottom() <= usable.bottom());
9c937ba68fa7atagen2026-10-19 08:02 UTC664
    assert_eq!((r.w, r.h), (200.0, 150.0), "a panel never shrinks a float");
4b73e40af1e3atagen2026-10-19 07:44 UTC665
}
4b73e40af1e3atagen2026-10-19 07:44 UTC666
4b73e40af1e3atagen2026-10-19 07:44 UTC667
#[test]
4b73e40af1e3atagen2026-10-19 07:44 UTC668
fn floats_shrink_into_a_smaller_output_they_are_evicted_to() {
4b73e40af1e3atagen2026-10-19 07:44 UTC669
    let mut l = Layout::default();
4b73e40af1e3atagen2026-10-19 07:44 UTC670
    let a = l.add_output("a", AREA);
4b73e40af1e3atagen2026-10-19 07:44 UTC671
    let b = l.add_output("b", Rect::new(1000.0, 0.0, 500.0, 300.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC672
    let w = l.add_window(Some(a), None, Some(Rect::new(800.0, 400.0, 200.0, 200.0)));
4b73e40af1e3atagen2026-10-19 07:44 UTC673
    l.remove_output(a);
4b73e40af1e3atagen2026-10-19 07:44 UTC674
    assert_eq!(l.window(w).unwrap().output, Some(b));
4b73e40af1e3atagen2026-10-19 07:44 UTC675
    let r = l.window(w).unwrap().float_rect.unwrap();
4b73e40af1e3atagen2026-10-19 07:44 UTC676
    let usable = l.output(b).unwrap().usable();
4b73e40af1e3atagen2026-10-19 07:44 UTC677
    assert_eq!((r.w, r.h), (100.0, 100.0));
4b73e40af1e3atagen2026-10-19 07:44 UTC678
    assert!(r.x >= usable.x && r.right() <= usable.right());
4b73e40af1e3atagen2026-10-19 07:44 UTC679
    assert!(r.y >= usable.y && r.bottom() <= usable.bottom());
4b73e40af1e3atagen2026-10-19 07:44 UTC680
}
abe5e42a95d6atagen2026-10-19 07:52 UTC681
abe5e42a95d6atagen2026-10-19 07:52 UTC682
/// Dragging a split moves only the two tiles that meet at it, whichever of
abe5e42a95d6atagen2026-10-19 07:52 UTC683
/// them the drag started from, and stops where one would vanish.
abe5e42a95d6atagen2026-10-19 07:52 UTC684
#[test]
abe5e42a95d6atagen2026-10-19 07:52 UTC685
fn dragging_a_split_moves_just_that_split() {
abe5e42a95d6atagen2026-10-19 07:52 UTC686
    let mut l = Layout::default();
abe5e42a95d6atagen2026-10-19 07:52 UTC687
    let o = l.add_output("a", AREA);
abe5e42a95d6atagen2026-10-19 07:52 UTC688
    let a = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC689
    let b = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC690
    let c = l.add_window(Some(o), None, None);
abe5e42a95d6atagen2026-10-19 07:52 UTC691
    let rect = |l: &Layout, w| {
abe5e42a95d6atagen2026-10-19 07:52 UTC692
        l.arrange(o)
abe5e42a95d6atagen2026-10-19 07:52 UTC693
            .tiled
abe5e42a95d6atagen2026-10-19 07:52 UTC694
            .iter()
abe5e42a95d6atagen2026-10-19 07:52 UTC695
            .find(|p| p.window == w)
abe5e42a95d6atagen2026-10-19 07:52 UTC696
            .unwrap()
abe5e42a95d6atagen2026-10-19 07:52 UTC697
            .rect
abe5e42a95d6atagen2026-10-19 07:52 UTC698
    };
abe5e42a95d6atagen2026-10-19 07:52 UTC699
    let before = [rect(&l, a), rect(&l, b), rect(&l, c)];
abe5e42a95d6atagen2026-10-19 07:52 UTC700
    assert!(!l.has_split(a, Dir::Up));
abe5e42a95d6atagen2026-10-19 07:52 UTC701
    assert!(!l.move_split(a, Dir::Up, 50.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC702
    assert!(l.has_split(a, Dir::Down));
abe5e42a95d6atagen2026-10-19 07:52 UTC703
abe5e42a95d6atagen2026-10-19 07:52 UTC704
    assert!(l.move_split(a, Dir::Down, 40.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC705
    let (ra, rb) = (rect(&l, a), rect(&l, b));
abe5e42a95d6atagen2026-10-19 07:52 UTC706
    assert!((ra.h - before[0].h - 40.0).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC707
    assert!((rb.h - before[1].h + 40.0).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC708
    assert_eq!(rect(&l, c).h, before[2].h);
abe5e42a95d6atagen2026-10-19 07:52 UTC709
abe5e42a95d6atagen2026-10-19 07:52 UTC710
    assert!(l.move_split(b, Dir::Up, -40.0));
abe5e42a95d6atagen2026-10-19 07:52 UTC711
    assert!((rect(&l, a).h - before[0].h).abs() < 1e-6);
abe5e42a95d6atagen2026-10-19 07:52 UTC712
abe5e42a95d6atagen2026-10-19 07:52 UTC713
    l.move_split(a, Dir::Down, 1e6);
abe5e42a95d6atagen2026-10-19 07:52 UTC714
    assert!(rect(&l, b).h > 1.0);
abe5e42a95d6atagen2026-10-19 07:52 UTC715
    l.check();
abe5e42a95d6atagen2026-10-19 07:52 UTC716
}
b001ce7817edatagen2026-10-19 09:23 UTC717
b001ce7817edatagen2026-10-19 09:23 UTC718
#[test]
b001ce7817edatagen2026-10-19 09:23 UTC719
fn scratchpads_stay_centred_whatever_their_rect() {
b001ce7817edatagen2026-10-19 09:23 UTC720
    let mut l = Layout::default();
b001ce7817edatagen2026-10-19 09:23 UTC721
    let o = l.add_output("a", AREA);
b001ce7817edatagen2026-10-19 09:23 UTC722
    let w = l.add_window(Some(o), None, Some(Rect::new(10.0, 20.0, 300.0, 200.0)));
b001ce7817edatagen2026-10-19 09:23 UTC723
    l.scratchpad_add(w, "s");
b001ce7817edatagen2026-10-19 09:23 UTC724
    l.summon("s");
b001ce7817edatagen2026-10-19 09:23 UTC725
    l.set_float_rect(w, Rect::new(0.0, 0.0, 500.0, 5000.0));
b001ce7817edatagen2026-10-19 09:23 UTC726
    let r = l.arrange(o).floating.iter().find(|p| p.0 == w).unwrap().1;
b001ce7817edatagen2026-10-19 09:23 UTC727
    let area = l.output(o).unwrap().usable();
b001ce7817edatagen2026-10-19 09:23 UTC728
    assert_eq!((r.w, r.h), (500.0, area.h));
b001ce7817edatagen2026-10-19 09:23 UTC729
    assert_eq!(r.x + r.w / 2.0, area.x + area.w / 2.0);
b001ce7817edatagen2026-10-19 09:23 UTC730
    assert_eq!(r.y, area.y);
b001ce7817edatagen2026-10-19 09:23 UTC731
}
ecb0c8c1f31datagen2026-10-19 10:15 UTC732
ecb0c8c1f31datagen2026-10-19 10:15 UTC733
/// Focus aimed at a window with a modal dialog lands on the deepest dialog
ecb0c8c1f31datagen2026-10-19 10:15 UTC734
/// from every path, and falls back to the parent once the dialog is gone.
ecb0c8c1f31datagen2026-10-19 10:15 UTC735
#[test]
ecb0c8c1f31datagen2026-10-19 10:15 UTC736
fn modal_dialogs_keep_focus_over_their_parent() {
ecb0c8c1f31datagen2026-10-19 10:15 UTC737
    let mut l = Layout::default();
ecb0c8c1f31datagen2026-10-19 10:15 UTC738
    l.add_output("a", AREA);
ecb0c8c1f31datagen2026-10-19 10:15 UTC739
    let parent = l.add_window(None, None, None);
ecb0c8c1f31datagen2026-10-19 10:15 UTC740
    let other = l.add_window(None, None, None);
ecb0c8c1f31datagen2026-10-19 10:15 UTC741
    l.focus(parent);
ecb0c8c1f31datagen2026-10-19 10:15 UTC742
    let dialog = l.add_window(None, None, Some(Rect::new(300.0, 200.0, 200.0, 100.0)));
ecb0c8c1f31datagen2026-10-19 10:15 UTC743
    let nested = l.add_window(None, None, Some(Rect::new(350.0, 250.0, 100.0, 50.0)));
ecb0c8c1f31datagen2026-10-19 10:15 UTC744
    l.set_parent(dialog, Some(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC745
    l.set_parent(nested, Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC746
    l.set_modal(dialog, true);
ecb0c8c1f31datagen2026-10-19 10:15 UTC747
    assert_eq!(l.focused(), Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC748
    l.set_modal(nested, true);
ecb0c8c1f31datagen2026-10-19 10:15 UTC749
    assert_eq!(l.focused(), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC750
ecb0c8c1f31datagen2026-10-19 10:15 UTC751
    l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC752
    assert_eq!(l.focused(), Some(other));
ecb0c8c1f31datagen2026-10-19 10:15 UTC753
    assert_eq!(l.focus(parent), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC754
    assert_eq!(l.focused(), Some(nested));
ecb0c8c1f31datagen2026-10-19 10:15 UTC755
    assert!(l.is_focused(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC756
ecb0c8c1f31datagen2026-10-19 10:15 UTC757
    l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC758
    for dir in [Dir::Left, Dir::Right, Dir::Up, Dir::Down] {
ecb0c8c1f31datagen2026-10-19 10:15 UTC759
        l.focus(other);
ecb0c8c1f31datagen2026-10-19 10:15 UTC760
        if let Some(w) = l.focus_dir(dir) {
ecb0c8c1f31datagen2026-10-19 10:15 UTC761
            assert_ne!(w, parent, "directional focus stopped on a blocked parent");
ecb0c8c1f31datagen2026-10-19 10:15 UTC762
        }
ecb0c8c1f31datagen2026-10-19 10:15 UTC763
    }
ecb0c8c1f31datagen2026-10-19 10:15 UTC764
ecb0c8c1f31datagen2026-10-19 10:15 UTC765
    l.focus(parent);
ecb0c8c1f31datagen2026-10-19 10:15 UTC766
    l.remove_window(nested);
ecb0c8c1f31datagen2026-10-19 10:15 UTC767
    assert_eq!(l.focused(), Some(dialog));
ecb0c8c1f31datagen2026-10-19 10:15 UTC768
    l.remove_window(dialog);
ecb0c8c1f31datagen2026-10-19 10:15 UTC769
    assert_eq!(l.focused(), Some(parent));
ecb0c8c1f31datagen2026-10-19 10:15 UTC770
    l.check();
ecb0c8c1f31datagen2026-10-19 10:15 UTC771
}