Skip to main content

solium_layout/
tree.rs

1//! Dwindle, as Hyprland actually does it: a tree, not a formula.
2//!
3//! The arrangement people mean by "Hyprland tiling" cannot be written as a
4//! function of how many windows there are. Read `addTarget` in Hyprland's
5//! `DwindleAlgorithm.cpp` and the reason is immediate: a new window splits **a
6//! specific existing window** — the one under the mouse, or the focused one —
7//! and the split runs across whichever axis *that window's own box* is longer
8//! on. Two windows opened in a different order, or with the cursor somewhere
9//! else, give different layouts. There is no count to compute from.
10//!
11//! So this is a binary tree. Leaves are windows; every branch remembers which
12//! way it was cut and where. Inserting replaces a leaf with a branch holding
13//! the old window and the new one. Removing replaces a branch with whichever
14//! child is left, which is what makes closing a window hand its space back to
15//! its neighbour rather than reshuffling the screen.
16//!
17//! Per-branch ratios are the other half of it: resizing a window moves one
18//! seam, and everything on the far side of the tree stays exactly where it is.
19//!
20//! [`Tiling::insert`] splits the tile it is pointed at however small that tile
21//! already is, so tiles go on halving for as long as windows keep opening.
22//! [`Settings::minimum`] is the floor (#134). `insert` still ignores it, because
23//! it is what a layout falls back to when nothing else will do; the two
24//! `insert_*` methods beside it refuse a split that would go under it, so a
25//! layout can try them first, and the seam movers will not move a seam to take
26//! a tile under it either.
27//!
28//! A window's own floor is the other bound (#115): what its application says
29//! it cannot go under, frame included, handed in with [`Tiling::set_floors`].
30//! It is the one thing that moves a seam nobody dragged. [`Tiling::layout`]
31//! lays a split out at its own ratio, then moves it just far enough for a side
32//! under a window's floor to reach it, taking the room from the other side down
33//! to *its* floors and [`Settings::minimum`], and no further. The ratio the
34//! branch holds is left as it was, so a window whose floor goes away hands the
35//! room back and the arrangement is the one the user made. Where the room is
36//! not there, the window is laid out short and [`Tiling::cramped`] says so.
37//! The splits and the seams are held to the same floors: `insert_fitting` and
38//! `insert_largest` refuse a split that would put a window under its own, and
39//! a seam stops at the floors on either side of it. Each of those sentences is
40//! a test in `floor_tests`.
41
42use crate::{Floors, Minimum, Rect, Settings};
43
44/// How far under the minimum a tile may come out and still count as at it.
45///
46/// A split's halves are `f64` arithmetic on a box that has already been
47/// through a division or two, so a tile laid out at exactly the minimum can
48/// land a rounding error short of it. A millionth of a pixel is far below
49/// anything drawn and far above that error.
50const SLACK: f64 = 1e-6;
51
52/// Which way a branch was cut.
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum Axis {
55    /// Children sit side by side.
56    Vertical,
57    /// Children sit one above the other.
58    Horizontal,
59}
60
61impl Axis {
62    /// The cut a box would get from [`Tiling::insert`]: across whichever side
63    /// is longer, so the split follows the shape of the space being divided.
64    fn longer(rect: Rect) -> Self {
65        if rect.w >= rect.h {
66            Self::Vertical
67        } else {
68            Self::Horizontal
69        }
70    }
71
72    /// The other one.
73    const fn across(self) -> Self {
74        match self {
75            Self::Vertical => Self::Horizontal,
76            Self::Horizontal => Self::Vertical,
77        }
78    }
79
80    /// The side of a size this cut divides: the width for children side by
81    /// side, the height for children one above the other.
82    const fn of(self, size: Minimum) -> f64 {
83        match self {
84            Self::Vertical => size.w,
85            Self::Horizontal => size.h,
86        }
87    }
88
89    /// The same, of a rectangle.
90    const fn extent(self, rect: Rect) -> f64 {
91        match self {
92            Self::Vertical => rect.w,
93            Self::Horizontal => rect.h,
94        }
95    }
96}
97
98/// Which side of a window was grabbed.
99///
100/// Not an axis, and the difference is the whole of #120. A window that is the
101/// right-hand child of a vertical split has that split's seam on its **left**.
102/// Dragging its left edge moves that seam; dragging its right edge must find a
103/// different seam, or none at all. An axis cannot tell those two apart, so a
104/// layout handed only "horizontal" moves whichever seam the walk to the root
105/// meets first — which for a right-edge drag is the seam on the far side, and
106/// the window's left edge then jumps while the edge under the pointer sits
107/// still. Measured: the slot's x moved 209px in one frame while its width did
108/// not change at all.
109///
110/// The compositor has known the side all along — `ResizeEdge` in `state.rs`,
111/// which the floating path already uses — and reduced it to a pair of booleans
112/// on the way to the layout.
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub enum Edge {
115    /// The low-x side, which a vertical split's seam bounds from the left.
116    Left,
117    /// The high-x side.
118    Right,
119    /// The low-y side, which a horizontal split's seam bounds from above.
120    Top,
121    /// The high-y side.
122    Bottom,
123}
124
125impl Edge {
126    /// The axis a seam along this side is cut on.
127    #[must_use]
128    pub const fn axis(self) -> Axis {
129        match self {
130            Self::Left | Self::Right => Axis::Vertical,
131            Self::Top | Self::Bottom => Axis::Horizontal,
132        }
133    }
134
135    /// Which child of a split has that split's seam on this side.
136    ///
137    /// A split's seam lies *between* its two children, so the child that has
138    /// the seam on its left — or above it — is the second one, and the child
139    /// that has it on its right or below is the first. This single index is
140    /// what turns "a branch cut the right way" into "the branch whose seam is
141    /// actually under the pointer".
142    const fn child(self) -> usize {
143        match self {
144            Self::Left | Self::Top => 1,
145            Self::Right | Self::Bottom => 0,
146        }
147    }
148}
149
150#[derive(Clone, Copy, Debug)]
151enum Node {
152    Window {
153        id: u64,
154    },
155    Split {
156        axis: Axis,
157        ratio: f64,
158        children: [usize; 2],
159    },
160}
161
162/// A dwindle tree.
163#[derive(Clone, Debug, Default)]
164pub struct Tiling {
165    nodes: Vec<Option<Node>>,
166    root: Option<usize>,
167    /// Each window's own floor, as the caller last handed them in. See
168    /// [`Self::set_floors`].
169    floors: Floors,
170}
171
172/// What a subtree asks of the room along one axis: the least it must have for
173/// every window in it that has a floor of its own to be at that floor, and the
174/// least it will give up.
175///
176/// `pull` is zero for a subtree with no window floor in it: [`Settings::minimum`]
177/// bounds splits and seams and moves nothing by itself, as it did not before
178/// (#134). `hold` is what the subtree can be taken down to when a sibling
179/// pulls -- each window's own floor, or the minimum where that is larger.
180#[derive(Clone, Copy, Debug, Default)]
181struct Need {
182    pull: f64,
183    hold: f64,
184}
185
186/// A size read from a caller as a size: a finite length of more than nothing,
187/// and anything else as no size at all.
188fn length(value: f64) -> f64 {
189    if value.is_finite() && value > 0.0 {
190        value
191    } else {
192        0.0
193    }
194}
195
196impl Tiling {
197    #[must_use]
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    /// Each window's own floor, by id, for every call after this one.
203    ///
204    /// Replaced whole, not merged: the caller hands in what every window says
205    /// now, and a window missing from it has no floor
206    /// (`floor_tests::a_window_whose_floor_goes_away_hands_the_room_back`). A
207    /// window that is not in the tree yet is read when it is inserted
208    /// (`floor_tests::a_split_that_would_put_a_window_under_its_floor_is_refused`).
209    pub fn set_floors(&mut self, floors: Floors) {
210        self.floors = floors;
211    }
212
213    /// Whether `rect`, a tile this tree laid out for window `id`, is smaller
214    /// than that window's own floor on either side.
215    ///
216    /// Its own floor and nothing else: a tile under [`Settings::minimum`] is
217    /// one the layout chose or allowed (#134), and a client inside it is not
218    /// refusing anything (`floor_tests::a_sibling_gives_no_more_than_it_holds`
219    /// has one of each). This is the window whose client will commit more than
220    /// it was given and be cut to its tile (#133).
221    #[must_use]
222    pub fn cramped(&self, id: u64, rect: Rect) -> bool {
223        let floor = self.floor_of(id);
224        rect.w + SLACK < floor.w || rect.h + SLACK < floor.h
225    }
226
227    /// Window `id`'s own floor, each side read as a [`length`].
228    fn floor_of(&self, id: u64) -> Minimum {
229        self.floors
230            .get(&id)
231            .map_or_else(Minimum::default, |floor| Minimum {
232                w: length(floor.w),
233                h: length(floor.h),
234            })
235    }
236
237    /// What window `id` asks of the room along `axis`. See [`need_of`].
238    fn leaf_need(&self, id: u64, axis: Axis, settings: Settings) -> Need {
239        need_of(self.floor_of(id), axis, settings)
240    }
241
242    /// What the subtree at `index` asks of the room along `axis`. See [`Need`].
243    ///
244    /// Cut along `axis`, its children share that room, so it needs both of
245    /// theirs and the gap -- and once anything in it pulls, it needs every
246    /// window in it at what that window will give down to, since those are
247    /// what the pulling one takes its room from. Cut the other way, each child
248    /// has the whole extent, so it needs only the larger. Both are
249    /// `floor_tests::a_floor_deep_in_a_subtree_takes_room_across_the_root`.
250    fn need(&self, index: usize, axis: Axis, settings: Settings) -> Need {
251        match self.nodes.get(index).copied().flatten() {
252            Some(Node::Window { id }) => self.leaf_need(id, axis, settings),
253            Some(Node::Split {
254                axis: cut_on,
255                children,
256                ..
257            }) => {
258                let first = self.need(children[0], axis, settings);
259                let second = self.need(children[1], axis, settings);
260                if cut_on == axis {
261                    let hold = if first.hold > 0.0 || second.hold > 0.0 {
262                        first.hold + settings.gap + second.hold
263                    } else {
264                        0.0
265                    };
266                    let pull = if first.pull > 0.0 || second.pull > 0.0 {
267                        hold
268                    } else {
269                        0.0
270                    };
271                    Need { pull, hold }
272                } else {
273                    Need {
274                        pull: first.pull.max(second.pull),
275                        hold: first.hold.max(second.hold),
276                    }
277                }
278            }
279            None => Need::default(),
280        }
281    }
282
283    /// The ratio a split is laid out at, in `rect`: its own, or the one that
284    /// gives a side under its floor its floor. See [`shared`].
285    ///
286    /// No floors at all is the ratio as it is, without any arithmetic on it:
287    /// a tree nobody has handed floors lays out what it did before #115, and
288    /// every test above `floor_tests` is one that has none.
289    fn share(
290        &self,
291        axis: Axis,
292        ratio: f64,
293        children: [usize; 2],
294        rect: Rect,
295        settings: Settings,
296    ) -> f64 {
297        if self.floors.is_empty() {
298            return ratio;
299        }
300        shared(
301            axis,
302            ratio,
303            self.need(children[0], axis, settings),
304            self.need(children[1], axis, settings),
305            rect,
306            settings,
307        )
308    }
309
310    #[must_use]
311    pub fn is_empty(&self) -> bool {
312        self.root.is_none()
313    }
314
315    #[must_use]
316    pub fn contains(&self, id: u64) -> bool {
317        self.leaf(id).is_some()
318    }
319
320    /// Every window in the tree, in insertion-independent tree order.
321    #[must_use]
322    pub fn windows(&self) -> Vec<u64> {
323        let mut out = Vec::new();
324        if let Some(root) = self.root {
325            self.walk(root, &mut out);
326        }
327        out
328    }
329
330    fn walk(&self, index: usize, out: &mut Vec<u64>) {
331        match self.nodes.get(index).copied().flatten() {
332            Some(Node::Window { id }) => out.push(id),
333            Some(Node::Split { children, .. }) => {
334                self.walk(children[0], out);
335                self.walk(children[1], out);
336            }
337            None => {}
338        }
339    }
340
341    /// Add a window by splitting `target`, or the whole area if there is none.
342    ///
343    /// `at` is where the pointer was. It decides which side of the split the
344    /// new window lands on — dropping a window on the right half of another
345    /// puts it on the right — which is the part that makes this feel like
346    /// placing a window rather than appending to a list.
347    ///
348    /// Splits whatever it is pointed at, across that box's longer side,
349    /// however small the halves come out: [`Settings::minimum`] is not read
350    /// here. This is the last resort, `"allow"` in `config.tiling.overflow`;
351    /// [`Self::insert_fitting`] is the same placement with the floor on it.
352    pub fn insert(
353        &mut self,
354        id: u64,
355        target: Option<u64>,
356        at: Option<(f64, f64)>,
357        area: Rect,
358        settings: Settings,
359    ) {
360        if self.contains(id) {
361            return;
362        }
363        let Some(root) = self.root else {
364            self.root = Some(self.push(Node::Window { id }));
365            return;
366        };
367        let boxes = self.layout(area, settings);
368        let target = self.split_target(id, target, at, &boxes).unwrap_or(root);
369        let rect = self.box_of(target, &boxes, area);
370        self.split(id, target, rect, Axis::longer(rect), at, settings);
371    }
372
373    /// [`Self::insert`], unless that would make a tile smaller than
374    /// [`Settings::minimum`], or leave the window being split or the one being
375    /// added under its own floor ([`Self::set_floors`]) — in which case the
376    /// other axis is tried, and failing that nothing is done at all.
377    ///
378    /// The same target as `insert` and the same side of it: this is "open
379    /// where the pointer is" with a floor under it, not a different rule. The
380    /// other axis first because a box that cannot be divided one way often can
381    /// be divided the other — a wide, short tile that has no room side by side
382    /// may still have room one above the other — and that is still the tile
383    /// the user was pointing at.
384    ///
385    /// Returns whether the window is in the tree now. `false` leaves the tree
386    /// exactly as it was, so the caller can try somewhere else; nothing is
387    /// pushed until a split has been chosen, because a node pushed and then
388    /// abandoned would be found by [`Self::contains`] for ever after. An empty
389    /// tree always has room: its one window takes the whole area whatever
390    /// size that is, since there is nothing to divide.
391    pub fn insert_fitting(
392        &mut self,
393        id: u64,
394        target: Option<u64>,
395        at: Option<(f64, f64)>,
396        area: Rect,
397        settings: Settings,
398    ) -> bool {
399        if self.contains(id) {
400            return true;
401        }
402        let Some(root) = self.root else {
403            self.root = Some(self.push(Node::Window { id }));
404            return true;
405        };
406        let boxes = self.layout(area, settings);
407        let target = self.split_target(id, target, at, &boxes).unwrap_or(root);
408        let rect = self.box_of(target, &boxes, area);
409        let Some(axis) = self.room_in(rect, target, id, at, settings) else {
410            return false;
411        };
412        self.split(id, target, rect, axis, at, settings);
413        true
414    }
415
416    /// Split the largest tile that has room for another window without going
417    /// under [`Settings::minimum`] or either window's own floor: `"largest"` in
418    /// `config.tiling.overflow`.
419    ///
420    /// Largest by area, and the largest *with room* rather than the largest:
421    /// a big square tile can fail both ways where a smaller, longer one has
422    /// room along its length, and skipping it is the difference between
423    /// placing the window and sending it off this workspace. Ties go to the
424    /// earlier tile in tree order, so the choice does not depend on anything
425    /// the tree does not hold.
426    ///
427    /// Each tile is tried across its longer side and then the other, as
428    /// [`Self::insert_fitting`] tries the one under the pointer. The new
429    /// window takes the far side, as it does from `insert` with no pointer:
430    /// the pointer is in some other tile by now, so it says nothing about this
431    /// one.
432    ///
433    /// Only windows the tree holds have tiles here. A window a layout took out
434    /// at `closing` (#128) has none, and the neighbour that grew into its
435    /// space is measured at the size it is now.
436    ///
437    /// Returns whether the window is in the tree now; `false` leaves the tree
438    /// as it was. An empty tree always has room, as for `insert_fitting`.
439    pub fn insert_largest(&mut self, id: u64, area: Rect, settings: Settings) -> bool {
440        if self.contains(id) {
441            return true;
442        }
443        if self.root.is_none() {
444            self.root = Some(self.push(Node::Window { id }));
445            return true;
446        }
447        let mut boxes = self.layout(area, settings);
448        // Stable, so equal areas keep tree order.
449        boxes.sort_by(|(_, a), (_, b)| (b.w * b.h).total_cmp(&(a.w * a.h)));
450        for (other, rect) in boxes {
451            if let Some(target) = self.leaf(other)
452                && let Some(axis) = self.room_in(rect, target, id, None, settings)
453            {
454                self.split(id, target, rect, axis, None, settings);
455                return true;
456            }
457        }
458        false
459    }
460
461    /// The leaf a new window would split: the one named, else the one under
462    /// the pointer, else the one nearest it. `None` only for an empty tree.
463    ///
464    /// The last step matters more than it looks: falling back to the root
465    /// instead means splitting the whole screen, and every new window then
466    /// lands as another full-height column — which is not dwindle at all, and
467    /// is exactly what this did before. Hyprland calls this `getClosestNode`.
468    fn split_target(
469        &self,
470        id: u64,
471        target: Option<u64>,
472        at: Option<(f64, f64)>,
473        boxes: &[(u64, Rect)],
474    ) -> Option<usize> {
475        // A window cannot be split by itself, and the caller can easily name
476        // it: a new window is mapped and under the pointer before this runs,
477        // so hit-testing the cursor answers with the very window being
478        // inserted. Back when its node was pushed before this lookup, `leaf`
479        // found that node, the branch took it as both children and hung from
480        // nothing, and the window vanished without any error at all. The node
481        // is pushed after the lookup now, in [`Self::split`], so `leaf` would
482        // find nothing; the filter says so outright instead of leaving it to
483        // the order of two calls. `a_window_named_as_its_own_target_is_still_added`
484        // pins the outcome.
485        //
486        // Hyprland guards the same case in `addTarget`, calling it a fail-safe
487        // and picking a different node. This is that guard.
488        target
489            .filter(|named| *named != id)
490            .and_then(|named| self.leaf(named))
491            .or_else(|| self.leaf_at(at, boxes))
492            .or_else(|| self.closest_leaf(at, boxes))
493    }
494
495    /// The box a split target has now, which is what decides the axis: the
496    /// split follows the shape of the space being divided rather than the
497    /// shape of the screen.
498    fn box_of(&self, target: usize, boxes: &[(u64, Rect)], area: Rect) -> Rect {
499        self.id_of(target)
500            .and_then(|id| boxes.iter().find(|(other, _)| *other == id))
501            .map_or(area, |(_, rect)| *rect)
502    }
503
504    /// Hang window `id` beside `target`, which occupies `rect`, across `axis`.
505    ///
506    /// Everything that decides *whether* and *where* is done by the callers;
507    /// this is the one place a node is pushed for a window that is not the
508    /// root, so a caller that has decided against a split has pushed nothing.
509    fn split(
510        &mut self,
511        id: u64,
512        target: usize,
513        rect: Rect,
514        axis: Axis,
515        at: Option<(f64, f64)>,
516        settings: Settings,
517    ) {
518        let fresh = self.push(Node::Window { id });
519
520        let children = if goes_second(at, rect, axis) {
521            [target, fresh]
522        } else {
523            [fresh, target]
524        };
525
526        // Where the target hung, found *before* the branch exists. Looking it
527        // up afterwards finds the new branch instead — the branch holds the
528        // target as a child — and hanging the branch inside itself makes a
529        // cycle. That only shows up from the third window on, because until
530        // then the target is the root and the root is handled separately.
531        let hung_from = self.parent(target);
532        let branch = self.push(Node::Split {
533            axis,
534            ratio: settings.split.clamp(0.05, 0.95),
535            children,
536        });
537        self.attach(hung_from, target, branch);
538    }
539
540    /// Trade the tiles of windows `a` and `b`, and change nothing else: every
541    /// split keeps its axis and its ratio, so the arrangement is the one it
542    /// was with two windows in each other's places, and a second swap puts it
543    /// back. What a keyboard move does in tiling (#150). Returns whether both
544    /// were in the tree; `false` leaves it as it was.
545    /// `swap_trades_two_tiles_and_nothing_else`.
546    pub fn swap(&mut self, a: u64, b: u64) -> bool {
547        let (Some(first), Some(second)) = (self.leaf(a), self.leaf(b)) else {
548            return false;
549        };
550        self.nodes[first] = Some(Node::Window { id: b });
551        self.nodes[second] = Some(Node::Window { id: a });
552        true
553    }
554
555    /// Take a window out. Its sibling inherits the space, which is what makes
556    /// closing a window a local change rather than a re-tile of the screen.
557    pub fn remove(&mut self, id: u64) {
558        let Some(leaf) = self.leaf(id) else {
559            return;
560        };
561        let Some(parent) = self.parent(leaf) else {
562            self.nodes[leaf] = None;
563            self.root = None;
564            return;
565        };
566        let Some(Node::Split { children, .. }) = self.nodes[parent] else {
567            return;
568        };
569        let sibling = if children[0] == leaf {
570            children[1]
571        } else {
572            children[0]
573        };
574        self.nodes[leaf] = None;
575        self.replace(parent, sibling);
576        self.nodes[parent] = None;
577    }
578
579    /// Put the seam beside a window's `edge` where `edge_at` says that edge
580    /// belongs.
581    ///
582    /// `edge_at` is a position on each axis and only the one `edge` names is
583    /// read — `.0` for a left or right side, `.1` for a top or bottom — which
584    /// is what lets a corner drag call this twice with one pair and have each
585    /// axis take its own edge. The coordinates are the ones [`Self::layout`]
586    /// hands back, so "where this window's right edge should be" is directly
587    /// comparable with the `x + w` of the slot this same tree produced for it.
588    ///
589    /// It is **not** where the pointer is, and calling it that was #124. The
590    /// two coincide only for a drag that began exactly on the edge, and no
591    /// gesture does: a border grab is a band sixteen pixels wide, and the
592    /// modifier drag starts from wherever in the window the button went down.
593    /// The compositor derives this from the rectangle the drag has produced —
594    /// `solium::input::resize::dragged_edge` — so what arrives here has already
595    /// moved exactly as far as the pointer has, from exactly where the edge
596    /// already was. The arithmetic below did not change for #124 and did not
597    /// need to.
598    ///
599    /// This is the **tiled** resize path: a tiled window has no size of its
600    /// own, so an edge drag moves the division it shares with its neighbour
601    /// and the compositor never resizes the window directly. The floating path
602    /// is the other one — see `Solium::settle_resize`, which only reaches
603    /// `hold_resize` when no layout claimed the drag — and the two are
604    /// deliberately independent.
605    ///
606    /// Three things this does that adding a delta to the nearest parent
607    /// cannot.
608    ///
609    /// It finds the seam on the side that was grabbed. [`Self::seam_beside`]
610    /// carries the argument; the short version is that "the nearest ancestor
611    /// cut on this axis" is not the same branch as "the branch whose seam is
612    /// under the pointer", and taking the first for the second is #120.
613    ///
614    /// It reaches past the immediate parent. A window's parent may have been
615    /// cut the other way — in a two-by-two, the seam beside a side edge is two
616    /// levels up — which is why width could be dragged in some arrangements
617    /// and not others.
618    ///
619    /// And it is idempotent. The ratio comes from a *position*, not from how
620    /// far anything moved, so handling the same drag twice gives the same
621    /// layout. Accumulating deltas fed the layout's own response back in as the
622    /// next input, and the windows shook themselves apart for as long as the
623    /// button was held. A relative gesture and a delta are not the same thing:
624    /// #124 made the gesture relative by choosing a better position to send,
625    /// and left this idempotence exactly where it was.
626    pub fn drag_seam(
627        &mut self,
628        id: u64,
629        edge: Edge,
630        edge_at: (f64, f64),
631        area: Rect,
632        settings: Settings,
633    ) {
634        let Some(leaf) = self.leaf(id) else {
635            return;
636        };
637        // No seam on that side: the window is flush against its container
638        // there, and the thing beyond its edge is the screen, which does not
639        // move. Doing nothing is the answer — moving some other seam instead
640        // is the whole of the bug this replaced.
641        let Some(seam) = self.seam_beside(leaf, edge) else {
642            return;
643        };
644
645        let Some(rect) = self.node_box(seam, area, settings) else {
646            return;
647        };
648        // Inverted from [`cut`], which is the only thing that decides where an
649        // edge actually lands. Along the cut axis it lays out a first child of
650        // `(rect.w - gap) * ratio`, then a `gap`-wide band, then the second
651        // child — so a seam is not a line but a band, and the two windows
652        // beside it have their edges on its two sides:
653        //
654        //     first child's trailing edge: rect.x + (rect.w - gap) * ratio
655        //     second child's leading edge: rect.x + (rect.w - gap) * ratio + gap
656        //
657        // Solving each for `ratio` gives the two spellings below. The share is
658        // of `rect.w - gap` and not of `rect.w` because the band is not part
659        // of what the ratio divides, and the leading side subtracts a further
660        // `gap` because the second child begins on the far side of it.
661        //
662        // Dividing by `rect.w` and ignoring the band — what this did — leaves
663        // the grabbed edge near the asked-for place rather than on it. With the
664        // shipped `gap` of 12, asking for x=1200 on a 1920 screen put a right
665        // edge at 1192.5 and a left edge at 1204.5: one seam, grabbed from its
666        // two sides, coming to rest a full gap apart. "The edge goes where it
667        // was asked to" is the whole of #120, so this arithmetic has to be
668        // right and not merely close. It is also why `edge_at` and `rect` have
669        // to be measured in the same space, which they are: `node_box` and
670        // [`Self::layout`] both start from `area.inset(gap)` and both descend
671        // through [`cut`], so a slot's edge and a seam's box are two readings
672        // off one arrangement. `dragged_edge_tests` pins that at a real gap,
673        // where half a gap of skew would be the whole of #120 again.
674        //
675        // [`Edge::child`] already answers which of the two sides the hand is
676        // on — it is the index [`Self::seam_beside`] matched the window
677        // against — so reading it as a distance rather than as an index is
678        // what keeps the two in step.
679        let gap = settings.gap;
680        let leading = if edge.child() == 1 { gap } else { 0.0 };
681        let ratio = match edge.axis() {
682            Axis::Vertical => (edge_at.0 - rect.x - leading) / (rect.w - gap).max(1.0),
683            Axis::Horizontal => (edge_at.1 - rect.y - leading) / (rect.h - gap).max(1.0),
684        };
685        // **The only clamps on a tiled drag, which is why they have to be
686        // here.** `edge_at` arrives unbounded: the compositor sends this pane's
687        // own laid-out edge displaced by the pointer, and it deliberately does
688        // not floor it. It cannot usefully. `resize::MINIMUM` is a floor on a
689        // *window's* size, and applying it to a seam's position put a 120-pixel
690        // window's floor on a tile that was narrower than that to begin with --
691        // which reported an edge 120px from where it was on the first frame of
692        // the drag, before the pointer had moved at all. A ratio of the seam's
693        // own box is the quantity this layout actually owns, and the box is not
694        // the window.
695        //
696        // Three bounds on that ratio, and [`Self::room`] works out all of
697        // them. One is 0.05..0.95, because a seam driven to either end of its
698        // box leaves a child of no width, and a window of no width cannot be
699        // grabbed again to undo it. Another, since #134, is
700        // `settings.minimum`: no tile on either side of the seam is taken
701        // under it, so a seam cannot be dragged to make a tile smaller than a
702        // new window is allowed to open in. A tile, not a window, so it is
703        // this layout's to hold -- and it bends for a side that is already
704        // under it rather than snapping that side up to it, which is what
705        // keeps the first frame of such a drag where it was. See `room`.
706        //
707        // The third, since #115, is each window's own floor: a seam stops
708        // where a window beyond it reaches the size its application says it
709        // cannot go under, measured on the tiles as the layout rebalances
710        // them. See `room`, and
711        // `floor_tests::a_seam_stops_at_a_neighbours_own_floor`.
712        //
713        // This is also the tighter of two clamps on a *floating* drag, where
714        // `resize::MINIMUM` does apply -- to the window, in `resized`, for the
715        // floating path's own rectangle. The two never meet on one number.
716        let (low, high) = self.room(seam, rect, settings);
717        if let Some(Node::Split { ratio: current, .. }) = self.nodes[seam].as_mut() {
718            *current = within(ratio, low, high, *current);
719        }
720    }
721
722    /// The ratios a seam may take: 0.05..0.95, narrowed so that no tile on
723    /// either side of it is made smaller than [`Settings::minimum`].
724    ///
725    /// `rect` is the seam's own box, from [`Self::node_box`]. Each side needs
726    /// [`Self::floor`] of it along the seam's axis, and a side's share of the
727    /// box less the gap is what the ratio divides, so the floors turn into
728    /// ratios by the same arithmetic [`cut`] runs forwards.
729    ///
730    /// **A side already under its floor is held where it is rather than moved
731    /// up to it.** Tiles under the minimum are ordinary: `"allow"` makes them,
732    /// a monitor change or a smaller work area can, and so can raising the
733    /// minimum in a running session. Clamping such a seam into its range on
734    /// the first frame of a drag would throw it across the screen before the
735    /// pointer had moved -- the #124 class of bug, reached through a floor
736    /// instead of a skew. So the range is widened to take in the ratio the
737    /// seam has now: that side can be grown, and cannot be made smaller still.
738    /// With both sides under, the seam cannot move at all, because any move
739    /// shrinks one of them.
740    ///
741    /// No minimum on the seam's axis leaves exactly 0.05..0.95, which is what
742    /// this clamp was before #134.
743    ///
744    /// **Each window's own floor narrows it further (#115)**, by what each
745    /// side needs for every window in it with a floor to reach it -- [`Need`],
746    /// the same measure [`Self::layout`] rebalances by. So the seam itself
747    /// stops there, and not only the picture of it: a floor that goes away
748    /// afterwards leaves the seam where the drag was stopped
749    /// (`floor_tests::a_seam_stops_at_a_neighbours_own_floor`). "The ratio it
750    /// has" is the one it is drawn at, [`Self::share`], which is the one it
751    /// holds unless a floor has moved it.
752    fn room(&self, seam: usize, rect: Rect, settings: Settings) -> (f64, f64) {
753        let Some(Node::Split {
754            axis,
755            ratio,
756            children,
757        }) = self.nodes.get(seam).copied().flatten()
758        else {
759            return (0.05, 0.95);
760        };
761        let current = self.share(axis, ratio, children, rect, settings);
762        let span = (axis.extent(rect) - settings.gap).max(1.0);
763        let (mut low, mut high) = (0.05_f64, 0.95_f64);
764        let least = axis.of(settings.minimum);
765        // NaN is spelled out because it compares false both ways, and a NaN
766        // floor would make every ratio below NaN too.
767        if !(least.is_nan() || least <= 0.0) {
768            low = low.max(self.floor(children[0], axis, least, settings.gap) / span);
769            high = high.min(1.0 - self.floor(children[1], axis, least, settings.gap) / span);
770        }
771        if !self.floors.is_empty() {
772            let first = self.need(children[0], axis, settings);
773            let second = self.need(children[1], axis, settings);
774            if first.pull > 0.0 {
775                low = low.max(first.pull / span);
776            }
777            if second.pull > 0.0 {
778                high = high.min(1.0 - second.pull / span);
779            }
780        }
781        (low.min(current).max(0.05), high.max(current).min(0.95))
782    }
783
784    /// The least room along `axis` the subtree at `index` needs for every tile
785    /// in it to be at least `least` long on that axis, with its own seams
786    /// where they are now.
787    ///
788    /// At the ratios it has rather than at the best ratios it could have: a
789    /// drag moves one seam, so a subtree's inner seams stay where they are and
790    /// its tiles grow and shrink in proportion. A branch cut along `axis`
791    /// needs each child's floor divided by that child's share, plus the gap
792    /// between them. A branch cut the other way gives each child the whole of
793    /// its extent along `axis`, so it needs only the larger of the two floors
794    /// -- a column of stacked windows is as narrow as its widest floor, not
795    /// the sum of them.
796    fn floor(&self, index: usize, axis: Axis, least: f64, gap: f64) -> f64 {
797        match self.nodes.get(index).copied().flatten() {
798            Some(Node::Window { .. }) => least,
799            Some(Node::Split {
800                axis: cut_on,
801                ratio,
802                children,
803            }) => {
804                let first = self.floor(children[0], axis, least, gap);
805                let second = self.floor(children[1], axis, least, gap);
806                if cut_on == axis {
807                    // Clamped as `cut` clamps it, since that is the share the
808                    // children are actually laid out at.
809                    let ratio = ratio.clamp(0.05, 0.95);
810                    gap + (first / ratio).max(second / (1.0 - ratio))
811                } else {
812                    first.max(second)
813                }
814            }
815            None => 0.0,
816        }
817    }
818
819    /// The branch whose seam runs along `edge` of the window at `leaf`.
820    ///
821    /// Two conditions, and the old walk checked only the first. The branch has
822    /// to be cut along the dragged axis, *and* this subtree has to be on the
823    /// side of it that puts the seam under the grabbed edge — which is
824    /// [`Edge::child`]. A window that is the second child of a vertical split
825    /// has that seam on its left and nowhere else; matching it for a
826    /// right-edge drag moves the window's far side while the pointer's side
827    /// stands still.
828    ///
829    /// The walk continues towards the root rather than stopping at the first
830    /// branch on the axis, because a branch on the wrong side is not a
831    /// near-miss: the seam beside a right edge may be any number of levels up,
832    /// past ancestors cut the other way and past ancestors cut the same way
833    /// that this subtree happens to sit on the far side of.
834    ///
835    /// `None` when the walk reaches the root having found no such branch,
836    /// which means exactly one thing: the window is flush against its
837    /// container on that side, and there is no seam there to move.
838    fn seam_beside(&self, leaf: usize, edge: Edge) -> Option<usize> {
839        let axis = edge.axis();
840        let side = edge.child();
841        let mut node = leaf;
842        while let Some(parent) = self.parent(node) {
843            if matches!(
844                self.nodes[parent],
845                Some(Node::Split { axis: cut, children, .. })
846                    if cut == axis && children[side] == node
847            ) {
848                return Some(parent);
849            }
850            node = parent;
851        }
852        None
853    }
854
855    /// The rectangle a node occupies, branches included.
856    fn node_box(&self, wanted: usize, area: Rect, settings: Settings) -> Option<Rect> {
857        fn find(
858            tree: &Tiling,
859            index: usize,
860            rect: Rect,
861            wanted: usize,
862            settings: Settings,
863        ) -> Option<Rect> {
864            if index == wanted {
865                return Some(rect);
866            }
867            match tree.nodes.get(index).copied().flatten() {
868                Some(Node::Split {
869                    axis,
870                    ratio,
871                    children,
872                }) => {
873                    let ratio = tree.share(axis, ratio, children, rect, settings);
874                    let (first, second) = cut(rect, axis, ratio, settings.gap);
875                    find(tree, children[0], first, wanted, settings)
876                        .or_else(|| find(tree, children[1], second, wanted, settings))
877                }
878                _ => None,
879            }
880        }
881        let root = self.root?;
882        find(self, root, area.inset(settings.gap), wanted, settings)
883    }
884
885    /// Grow a window along `axis` by a fraction, moving one seam. Everything
886    /// on the far side stays put.
887    ///
888    /// The keyboard path. It takes an axis and not an [`Edge`] because a
889    /// keypress names neither side: `super+equal` means "make this wider", and
890    /// which of the two seams beside it gives up the space is not something
891    /// the user expressed. A drag is the opposite — the hand is on one
892    /// specific edge — which is why the two take different arguments.
893    ///
894    /// So it prefers the trailing seam (right, or below) and falls back to the
895    /// leading one, with the sign flipped so that positive `by` still grows
896    /// the window either way. Only a window with no seam on either side —
897    /// which on that axis means the only window on the screen — does nothing.
898    ///
899    /// This used to adjust the immediate parent and nothing else, which is the
900    /// same shortcut [`Self::drag_seam`] made: in an ordinary four-window
901    /// dwindle every leaf's parent is a horizontal split, so the centre
902    /// vertical seam could not be reached from the keyboard at all and
903    /// `super+equal` silently changed the height instead.
904    ///
905    /// Bounded as a drag is, by [`Self::room`]: a press does not make a tile
906    /// smaller than [`Settings::minimum`], nor one already under it any
907    /// smaller. That is why this takes the area and the settings, which it did
908    /// not need before #134 -- the floor is in pixels, and the seam's box is
909    /// what turns pixels into a ratio.
910    pub fn resize(&mut self, id: u64, axis: Axis, by: f64, area: Rect, settings: Settings) {
911        let Some(leaf) = self.leaf(id) else {
912            return;
913        };
914        let (trailing, leading) = match axis {
915            Axis::Vertical => (Edge::Right, Edge::Left),
916            Axis::Horizontal => (Edge::Bottom, Edge::Top),
917        };
918        // A seam on the trailing side is the first child's, so raising its
919        // ratio grows this window; a seam on the leading side is the second
920        // child's, where the same move shrinks it. Hence the negation, which
921        // is the one thing about this that reads backwards.
922        let (seam, towards) = match self.seam_beside(leaf, trailing) {
923            Some(seam) => (seam, by),
924            None => match self.seam_beside(leaf, leading) {
925                Some(seam) => (seam, -by),
926                None => return,
927            },
928        };
929        // `seam_beside` only returns a branch it reached by walking up from a
930        // leaf of this tree, so `node_box` finds it walking down; the whole
931        // area is a fallback that keeps some bound rather than none.
932        let rect = self.node_box(seam, area, settings).unwrap_or(area);
933        let (low, high) = self.room(seam, rect, settings);
934        // From the ratio the seam is drawn at, which a window's floor may have
935        // moved off the one it holds (#115): a press moves what the user can
936        // see by `by`. From the one it holds, a press that did not reach past
937        // the floor changed nothing on screen at all.
938        // `floor_tests::a_press_beside_a_floor_moves_the_seam_from_where_it_is_drawn`.
939        let Some(Node::Split {
940            axis: cut,
941            ratio: held,
942            children,
943        }) = self.nodes[seam]
944        else {
945            return;
946        };
947        let shown = self.share(cut, held, children, rect, settings);
948        if let Some(Node::Split { ratio, .. }) = self.nodes[seam].as_mut() {
949            *ratio = within(shown + towards, low, high, *ratio);
950        }
951    }
952
953    /// Where every window goes.
954    ///
955    /// Each split at the ratio it holds, unless a window beside it is under
956    /// its own floor -- see the module note and [`shared`].
957    #[must_use]
958    pub fn layout(&self, area: Rect, settings: Settings) -> Vec<(u64, Rect)> {
959        let mut out = Vec::new();
960        if let Some(root) = self.root {
961            self.place(root, area.inset(settings.gap), settings, &mut out);
962        }
963        out
964    }
965
966    fn place(&self, index: usize, rect: Rect, settings: Settings, out: &mut Vec<(u64, Rect)>) {
967        match self.nodes.get(index).copied().flatten() {
968            Some(Node::Window { id }) => out.push((id, rect)),
969            Some(Node::Split {
970                axis,
971                ratio,
972                children,
973            }) => {
974                let ratio = self.share(axis, ratio, children, rect, settings);
975                let (first, second) = cut(rect, axis, ratio, settings.gap);
976                self.place(children[0], first, settings, out);
977                self.place(children[1], second, settings, out);
978            }
979            None => {}
980        }
981    }
982
983    fn push(&mut self, node: Node) -> usize {
984        self.nodes.push(Some(node));
985        self.nodes.len() - 1
986    }
987
988    fn leaf(&self, id: u64) -> Option<usize> {
989        self.nodes
990            .iter()
991            .position(|node| matches!(node, Some(Node::Window { id: other }) if *other == id))
992    }
993
994    fn id_of(&self, index: usize) -> Option<u64> {
995        match self.nodes.get(index).copied().flatten() {
996            Some(Node::Window { id }) => Some(id),
997            _ => None,
998        }
999    }
1000
1001    /// The window a point lands in.
1002    fn leaf_at(&self, at: Option<(f64, f64)>, boxes: &[(u64, Rect)]) -> Option<usize> {
1003        let (x, y) = at?;
1004        boxes
1005            .iter()
1006            .find(|(_, rect)| rect.contains(x, y))
1007            .and_then(|(id, _)| self.leaf(*id))
1008    }
1009
1010    /// The window nearest a point, by distance from its centre.
1011    ///
1012    /// Used when the pointer is over no window at all — off the edge, or on a
1013    /// gap. There is always a window to split; the question is only which.
1014    fn closest_leaf(&self, at: Option<(f64, f64)>, boxes: &[(u64, Rect)]) -> Option<usize> {
1015        let (x, y) = at.unwrap_or_else(|| {
1016            boxes
1017                .first()
1018                .map_or((0.0, 0.0), |(_, rect)| (rect.x, rect.y))
1019        });
1020        boxes
1021            .iter()
1022            .min_by(|(_, a), (_, b)| {
1023                let distance = |rect: &Rect| {
1024                    let dx = rect.x + rect.w / 2.0 - x;
1025                    let dy = rect.y + rect.h / 2.0 - y;
1026                    dx.mul_add(dx, dy * dy)
1027                };
1028                distance(a).total_cmp(&distance(b))
1029            })
1030            .and_then(|(id, _)| self.leaf(*id))
1031    }
1032
1033    fn parent(&self, index: usize) -> Option<usize> {
1034        self.nodes.iter().position(
1035            |node| matches!(node, Some(Node::Split { children, .. }) if children.contains(&index)),
1036        )
1037    }
1038
1039    /// Put `with` wherever `what` was.
1040    fn replace(&mut self, what: usize, with: usize) {
1041        let parent = self.parent(what);
1042        self.attach(parent, what, with);
1043    }
1044
1045    /// Put `with` where `what` hung, given the parent it hung from.
1046    ///
1047    /// Taking the parent as an argument rather than looking it up is what
1048    /// makes this safe to call once the replacement already references the
1049    /// thing being replaced.
1050    fn attach(&mut self, parent: Option<usize>, what: usize, with: usize) {
1051        let Some(parent) = parent else {
1052            if self.root == Some(what) {
1053                self.root = Some(with);
1054            }
1055            return;
1056        };
1057        if let Some(Node::Split { children, .. }) = self.nodes[parent].as_mut() {
1058            for child in children.iter_mut() {
1059                if *child == what {
1060                    *child = with;
1061                }
1062            }
1063        }
1064    }
1065}
1066
1067impl Tiling {
1068    /// Which way `rect`, window `old`'s tile, can be split to add window `new`
1069    /// with neither half under [`Settings::minimum`] or under its own window's
1070    /// floor: across its longer side if that has room, else across the other,
1071    /// else neither.
1072    ///
1073    /// Measured with [`cut`] itself, at the configured split and gap, so "has
1074    /// room" means exactly what the layout will then draw -- and since #115 at
1075    /// the ratio [`shared`] moves that split to, which is what the layout will
1076    /// draw when one of the two has a floor of its own. `at` puts the new
1077    /// window on the side [`Self::split`] will, since a split that is not
1078    /// at one half is not the same split mirrored. Both halves on both sides:
1079    /// a split across the width leaves the height alone, and a tile that is
1080    /// already too short does not become tall enough by being divided.
1081    ///
1082    /// Neither window with a floor of its own is the check #134 made, to the
1083    /// bit: the ratio is the split, and each half is held to the minimum.
1084    fn room_in(
1085        &self,
1086        rect: Rect,
1087        old: usize,
1088        new: u64,
1089        at: Option<(f64, f64)>,
1090        settings: Settings,
1091    ) -> Option<Axis> {
1092        let old = self
1093            .id_of(old)
1094            .map_or_else(Minimum::default, |id| self.floor_of(id));
1095        let new = self.floor_of(new);
1096        let longer = Axis::longer(rect);
1097        [longer, longer.across()].into_iter().find(|axis| {
1098            let (first, second) = if goes_second(at, rect, *axis) {
1099                (old, new)
1100            } else {
1101                (new, old)
1102            };
1103            let ratio = shared(
1104                *axis,
1105                settings.split,
1106                need_of(first, *axis, settings),
1107                need_of(second, *axis, settings),
1108                rect,
1109                settings,
1110            );
1111            let (one, two) = cut(rect, *axis, ratio, settings.gap);
1112            holds(one, first, settings) && holds(two, second, settings)
1113        })
1114    }
1115}
1116
1117/// What a window with `floor` asks of the room along `axis`: its own floor to
1118/// pull towards, and the larger of that and the minimum to give down to.
1119fn need_of(floor: Minimum, axis: Axis, settings: Settings) -> Need {
1120    let pull = axis.of(floor);
1121    Need {
1122        pull,
1123        hold: pull.max(length(axis.of(settings.minimum))),
1124    }
1125}
1126
1127/// Whether `half` is at least a window's `floor` and the minimum, both ways.
1128///
1129/// The larger of the two per side, spelled so that a minimum of NaN still
1130/// refuses every split as it did before #115: `f64::max` would drop it.
1131fn holds(half: Rect, floor: Minimum, settings: Settings) -> bool {
1132    let least = |own: f64, minimum: f64| if own > minimum { own } else { minimum };
1133    half.w + SLACK >= least(floor.w, settings.minimum.w)
1134        && half.h + SLACK >= least(floor.h, settings.minimum.h)
1135}
1136
1137/// Whether a window added to `rect` across `axis` takes the second child: the
1138/// half the pointer is in, and the far side by default.
1139///
1140/// One answer for [`Tiling::split`], which does it, and [`Tiling::room_in`],
1141/// which has to measure the split it will be.
1142fn goes_second(at: Option<(f64, f64)>, rect: Rect, axis: Axis) -> bool {
1143    at.is_none_or(|(x, y)| match axis {
1144        Axis::Vertical => x >= rect.x + rect.w / 2.0,
1145        Axis::Horizontal => y >= rect.y + rect.h / 2.0,
1146    })
1147}
1148
1149/// The ratio a split of `rect` across `axis` is drawn at, when it holds
1150/// `ratio` and its two children ask `first` and `second` of the room (#115).
1151///
1152/// `ratio` itself unless exactly one side is under what it pulls towards.
1153/// Then the seam moves towards the other side by the shortfall, or by as much
1154/// as the other side has above what it holds, whichever is less: a window
1155/// under its floor is given the room its siblings can spare and none they
1156/// cannot (`floor_tests::a_window_under_its_floor_takes_room_from_its_sibling`
1157/// and `floor_tests::a_sibling_gives_no_more_than_it_holds`). With both sides
1158/// short the seam stays where the user put it, since moving it would take from
1159/// one window short of room to give to another
1160/// (`floor_tests::two_windows_that_cannot_both_fit_keep_the_users_seam`).
1161/// The span is what [`cut`] divides, and `cut` clamps the result as it clamps
1162/// every ratio.
1163fn shared(
1164    axis: Axis,
1165    ratio: f64,
1166    first: Need,
1167    second: Need,
1168    rect: Rect,
1169    settings: Settings,
1170) -> f64 {
1171    if first.pull <= 0.0 && second.pull <= 0.0 {
1172        return ratio;
1173    }
1174    let span = (axis.extent(rect) - settings.gap).max(1.0);
1175    let one = span * ratio.clamp(0.05, 0.95);
1176    let two = span - one;
1177    let (short_one, short_two) = (first.pull - one, second.pull - two);
1178    let one = if short_one > SLACK && short_two <= SLACK {
1179        one + short_one.min((two - second.hold).max(0.0))
1180    } else if short_two > SLACK && short_one <= SLACK {
1181        one - short_two.min((one - first.hold).max(0.0))
1182    } else {
1183        return ratio;
1184    };
1185    one / span
1186}
1187
1188/// `value` held to `low..=high`, or `otherwise` when that range is empty.
1189///
1190/// Not `f64::clamp` alone, which panics when `low > high` or either is NaN --
1191/// and a compositor that panics takes the session with it. [`Tiling::room`]
1192/// always includes the current ratio, so its range is empty only when that
1193/// ratio is itself NaN; `otherwise` is then the seam left as it was.
1194fn within(value: f64, low: f64, high: f64, otherwise: f64) -> f64 {
1195    if low <= high {
1196        value.clamp(low, high)
1197    } else {
1198        otherwise
1199    }
1200}
1201
1202fn cut(rect: Rect, axis: Axis, ratio: f64, gap: f64) -> (Rect, Rect) {
1203    let ratio = ratio.clamp(0.05, 0.95);
1204    match axis {
1205        Axis::Vertical => {
1206            let first = ((rect.w - gap) * ratio).max(1.0);
1207            let second = (rect.w - gap - first).max(1.0);
1208            (
1209                Rect::new(rect.x, rect.y, first, rect.h),
1210                Rect::new(rect.x + first + gap, rect.y, second, rect.h),
1211            )
1212        }
1213        Axis::Horizontal => {
1214            let first = ((rect.h - gap) * ratio).max(1.0);
1215            let second = (rect.h - gap - first).max(1.0);
1216            (
1217                Rect::new(rect.x, rect.y, rect.w, first),
1218                Rect::new(rect.x, rect.y + first + gap, rect.w, second),
1219            )
1220        }
1221    }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226    use super::{Axis, Tiling};
1227    use crate::{Rect, Settings};
1228
1229    fn area() -> Rect {
1230        Rect::new(0.0, 0.0, 1000.0, 600.0)
1231    }
1232
1233    fn settings() -> Settings {
1234        Settings {
1235            gap: 0.0,
1236            split: 0.5,
1237            ..Settings::default()
1238        }
1239    }
1240
1241    fn rect_of(tiling: &Tiling, id: u64) -> Rect {
1242        tiling
1243            .layout(area(), settings())
1244            .into_iter()
1245            .find(|(other, _)| *other == id)
1246            .expect("window is in the tree")
1247            .1
1248    }
1249
1250    #[test]
1251    fn the_first_window_takes_everything() {
1252        let mut tiling = Tiling::new();
1253        tiling.insert(1, None, None, area(), settings());
1254        assert!((rect_of(&tiling, 1).w - 1000.0).abs() < 1.0);
1255    }
1256
1257    /// The split runs across the longer axis of *the window being split*, not
1258    /// of the screen. This is the property a count-based spiral cannot have,
1259    /// because it never knows which window is being divided.
1260    #[test]
1261    fn a_wide_window_splits_side_by_side_and_a_tall_one_does_not() {
1262        let mut tiling = Tiling::new();
1263        tiling.insert(1, None, None, area(), settings());
1264        tiling.insert(2, Some(1), None, area(), settings());
1265        assert!(
1266            (rect_of(&tiling, 1).w - 500.0).abs() < 1.0,
1267            "cut down the middle"
1268        );
1269
1270        // Window 2 is now 500x600 — taller than wide — so splitting *it* cuts
1271        // across, not down.
1272        tiling.insert(3, Some(2), None, area(), settings());
1273        let two = rect_of(&tiling, 2);
1274        let three = rect_of(&tiling, 3);
1275        assert!((two.w - 500.0).abs() < 1.0, "width untouched");
1276        assert!((two.h - 300.0).abs() < 1.0, "split horizontally: {two:?}");
1277        assert!(three.y > two.y, "the new window went below");
1278    }
1279
1280    /// Where the pointer is decides which side the window lands on. This is
1281    /// what "the window opens where the cursor is" means, and it is the whole
1282    /// difference between placing a window and appending to a list.
1283    #[test]
1284    fn the_pointer_decides_which_side_the_new_window_takes() {
1285        let mut left = Tiling::new();
1286        left.insert(1, None, None, area(), settings());
1287        left.insert(2, Some(1), Some((100.0, 300.0)), area(), settings());
1288        assert!(
1289            rect_of(&left, 2).x < rect_of(&left, 1).x,
1290            "dropped left, opens left"
1291        );
1292
1293        let mut right = Tiling::new();
1294        right.insert(1, None, None, area(), settings());
1295        right.insert(2, Some(1), Some((900.0, 300.0)), area(), settings());
1296        assert!(
1297            rect_of(&right, 2).x > rect_of(&right, 1).x,
1298            "dropped right, opens right"
1299        );
1300    }
1301
1302    /// With no target named, the window under the pointer is the one that
1303    /// gets split — which is how Hyprland picks `OPENINGON`.
1304    #[test]
1305    fn with_no_target_the_window_under_the_pointer_is_split() {
1306        let mut tiling = Tiling::new();
1307        tiling.insert(1, None, None, area(), settings());
1308        tiling.insert(2, Some(1), Some((900.0, 300.0)), area(), settings());
1309        // Point into window 1's half and open a third with no explicit target.
1310        tiling.insert(3, None, Some((100.0, 300.0)), area(), settings());
1311        assert!(
1312            (rect_of(&tiling, 2).w - 500.0).abs() < 1.0,
1313            "the untouched half kept its width"
1314        );
1315        // Window 1 was 500x600 — taller than wide — so dividing it cuts
1316        // across: its width is untouched and its height halves.
1317        let one = rect_of(&tiling, 1);
1318        assert!((one.w - 500.0).abs() < 1.0, "width unchanged: {one:?}");
1319        assert!(
1320            (one.h - 300.0).abs() < 1.0,
1321            "the pointed-at window was divided: {one:?}"
1322        );
1323    }
1324
1325    /// Closing a window hands its space to its neighbour instead of re-tiling
1326    /// the screen. Everything else must stay exactly where it was.
1327    #[test]
1328    fn removing_a_window_gives_its_space_to_its_sibling() {
1329        let mut tiling = Tiling::new();
1330        tiling.insert(1, None, None, area(), settings());
1331        tiling.insert(2, Some(1), None, area(), settings());
1332        tiling.insert(3, Some(2), None, area(), settings());
1333        let before = rect_of(&tiling, 1);
1334
1335        tiling.remove(3);
1336        assert_eq!(tiling.windows().len(), 2);
1337        let after = rect_of(&tiling, 1);
1338        assert!(
1339            (before.w - after.w).abs() < 1.0,
1340            "the far side did not move"
1341        );
1342        assert!(
1343            (rect_of(&tiling, 2).h - 600.0).abs() < 1.0,
1344            "the sibling took the space"
1345        );
1346    }
1347
1348    #[test]
1349    fn removing_the_last_window_empties_the_tree() {
1350        let mut tiling = Tiling::new();
1351        tiling.insert(1, None, None, area(), settings());
1352        tiling.remove(1);
1353        assert!(tiling.is_empty());
1354        assert!(tiling.layout(area(), settings()).is_empty());
1355    }
1356
1357    /// Resizing moves one seam. A tree that re-derived every box from a count
1358    /// could not do this at all.
1359    #[test]
1360    fn resizing_moves_one_seam_and_leaves_the_rest() {
1361        let mut tiling = Tiling::new();
1362        tiling.insert(1, None, None, area(), settings());
1363        tiling.insert(2, Some(1), None, area(), settings());
1364        tiling.insert(3, Some(2), None, area(), settings());
1365        let untouched = rect_of(&tiling, 1);
1366
1367        tiling.resize(2, Axis::Horizontal, 0.2, area(), settings());
1368        assert!(rect_of(&tiling, 2).h > 300.0, "the seam moved");
1369        assert!(
1370            (rect_of(&tiling, 1).w - untouched.w).abs() < 1.0,
1371            "the other branch is untouched"
1372        );
1373    }
1374
1375    #[test]
1376    fn every_window_gets_a_box_and_none_collapses() {
1377        let mut tiling = Tiling::new();
1378        for id in 1..=10 {
1379            tiling.insert(id, Some(id.saturating_sub(1)), None, area(), settings());
1380        }
1381        let boxes = tiling.layout(area(), settings());
1382        assert_eq!(boxes.len(), 10);
1383        for (id, rect) in boxes {
1384            assert!(
1385                rect.w >= 1.0 && rect.h >= 1.0,
1386                "window {id} collapsed: {rect:?}"
1387            );
1388        }
1389    }
1390
1391    #[test]
1392    fn a_window_is_never_added_twice() {
1393        let mut tiling = Tiling::new();
1394        tiling.insert(1, None, None, area(), settings());
1395        tiling.insert(1, None, None, area(), settings());
1396        assert_eq!(tiling.windows(), vec![1]);
1397    }
1398
1399    #[test]
1400    fn axes_are_named_for_how_the_children_sit() {
1401        assert_ne!(Axis::Vertical, Axis::Horizontal);
1402    }
1403
1404    /// **A swap trades two tiles and nothing else** (#150). Three windows at
1405    /// an uneven seam: after 1 and 3 swap, each has the other's rectangle,
1406    /// 2 has not moved, and swapping again is the tree it started as. A
1407    /// window the tree does not hold swaps with nothing.
1408    #[test]
1409    fn swap_trades_two_tiles_and_nothing_else() {
1410        let mut tiling = Tiling::new();
1411        tiling.insert(1, None, None, area(), settings());
1412        tiling.insert(2, Some(1), Some((900.0, 300.0)), area(), settings());
1413        tiling.insert(3, Some(2), Some((900.0, 500.0)), area(), settings());
1414        tiling.resize(1, Axis::Vertical, 0.1, area(), settings());
1415        let before: Vec<_> = [1, 2, 3].map(|id| rect_of(&tiling, id)).into();
1416
1417        assert!(tiling.swap(1, 3), "both windows are in the tree");
1418        assert_eq!(rect_of(&tiling, 1), before[2], "1 took 3's tile");
1419        assert_eq!(rect_of(&tiling, 3), before[0], "3 took 1's tile");
1420        assert_eq!(rect_of(&tiling, 2), before[1], "2 was not part of it");
1421
1422        assert!(tiling.swap(3, 1));
1423        let after: Vec<_> = [1, 2, 3].map(|id| rect_of(&tiling, id)).into();
1424        assert_eq!(after, before, "a second swap is the tree it started as");
1425
1426        assert!(!tiling.swap(1, 9), "9 is not in the tree");
1427        let untouched: Vec<_> = [1, 2, 3].map(|id| rect_of(&tiling, id)).into();
1428        assert_eq!(untouched, before, "a refused swap changed the tree");
1429    }
1430}
1431
1432#[cfg(test)]
1433mod fallback_tests {
1434    use super::Tiling;
1435    use crate::{Rect, Settings};
1436
1437    fn area() -> Rect {
1438        Rect::new(0.0, 0.0, 1000.0, 600.0)
1439    }
1440    fn settings() -> Settings {
1441        Settings {
1442            gap: 0.0,
1443            split: 0.5,
1444            ..Settings::default()
1445        }
1446    }
1447
1448    /// With the pointer nowhere near a window, the split must still land on a
1449    /// *window*. Falling back to the root splits the whole screen instead, and
1450    /// every window after the first then becomes another full-height column —
1451    /// three terminals in a row rather than a dwindle.
1452    #[test]
1453    fn a_pointer_off_the_edge_still_divides_a_window_not_the_screen() {
1454        let mut tiling = Tiling::new();
1455        tiling.insert(1, None, None, area(), settings());
1456        tiling.insert(2, None, Some((-500.0, -500.0)), area(), settings());
1457        tiling.insert(3, None, Some((-500.0, -500.0)), area(), settings());
1458
1459        let boxes = tiling.layout(area(), settings());
1460        let full_height = boxes.iter().filter(|(_, r)| r.h > 599.0).count();
1461        assert!(
1462            full_height < 3,
1463            "all three are full-height columns, so the screen was split each time: {boxes:?}"
1464        );
1465    }
1466}
1467
1468#[cfg(test)]
1469mod insert_sequence {
1470    use super::Tiling;
1471    use crate::{Rect, Settings};
1472
1473    /// The exact sequence the tiling script performs: a work area with a gap,
1474    /// a pointer that is outside every window, and no named target — so the
1475    /// closest-leaf fallback chooses. Three windows opened this way must all
1476    /// be in the tree.
1477    #[test]
1478    fn opening_three_windows_at_the_origin_keeps_all_three() {
1479        let area = Rect::new(0.0, 0.0, 1600.0, 900.0);
1480        let settings = Settings {
1481            gap: 12.0,
1482            split: 0.5,
1483            ..Settings::default()
1484        };
1485        let mut tiling = Tiling::new();
1486        for id in 1..=3 {
1487            tiling.insert(id, None, Some((0.0, 0.0)), area, settings);
1488            println!("after {id}: {:?}", tiling.windows());
1489        }
1490        assert_eq!(tiling.windows().len(), 3, "got {:?}", tiling.windows());
1491    }
1492}
1493
1494#[cfg(test)]
1495mod self_target {
1496    use super::Tiling;
1497    use crate::{Rect, Settings};
1498
1499    fn area() -> Rect {
1500        Rect::new(0.0, 0.0, 1600.0, 900.0)
1501    }
1502    fn settings() -> Settings {
1503        Settings {
1504            gap: 12.0,
1505            split: 0.5,
1506            ..Settings::default()
1507        }
1508    }
1509
1510    /// Naming the window being inserted as its own split target must not lose
1511    /// it. The tiling script does exactly this by accident: a new window is
1512    /// mapped and under the pointer before the layout runs, so hit-testing the
1513    /// cursor answers with the window being opened. Every window after the
1514    /// first was silently dropped — the tree kept one, and three terminals
1515    /// showed one.
1516    #[test]
1517    fn a_window_named_as_its_own_target_is_still_added() {
1518        let mut tiling = Tiling::new();
1519        for id in 1..=4 {
1520            tiling.insert(id, Some(id), Some((0.0, 0.0)), area(), settings());
1521        }
1522        assert_eq!(
1523            tiling.windows().len(),
1524            4,
1525            "windows were dropped: {:?}",
1526            tiling.windows()
1527        );
1528        for (id, rect) in tiling.layout(area(), settings()) {
1529            assert!(rect.w >= 1.0 && rect.h >= 1.0, "window {id} collapsed");
1530        }
1531    }
1532}
1533
1534#[cfg(test)]
1535mod seam_tests {
1536    use super::{Edge, Tiling};
1537    use crate::{Rect, Settings};
1538
1539    fn area() -> Rect {
1540        Rect::new(0.0, 0.0, 1000.0, 600.0)
1541    }
1542    fn settings() -> Settings {
1543        Settings {
1544            gap: 0.0,
1545            split: 0.5,
1546            ..Settings::default()
1547        }
1548    }
1549    fn rect_of(tiling: &Tiling, id: u64) -> Rect {
1550        tiling
1551            .layout(area(), settings())
1552            .into_iter()
1553            .find(|(other, _)| *other == id)
1554            .expect("in the tree")
1555            .1
1556    }
1557
1558    /// A two-by-two: every window's immediate parent is a horizontal split, so
1559    /// dragging a side edge has to reach a seam two levels up. Adjusting only
1560    /// the parent does nothing at all, which is why width could be dragged in
1561    /// some arrangements and not this one.
1562    fn quad() -> Tiling {
1563        let mut tiling = Tiling::new();
1564        tiling.insert(1, None, None, area(), settings());
1565        tiling.insert(2, Some(1), None, area(), settings());
1566        tiling.insert(3, Some(1), None, area(), settings());
1567        tiling.insert(4, Some(2), None, area(), settings());
1568        tiling
1569    }
1570
1571    #[test]
1572    fn a_two_by_two_can_have_its_width_dragged() {
1573        let mut tiling = quad();
1574        let before = rect_of(&tiling, 1).w;
1575        assert!((before - 500.0).abs() < 1.0, "starts halved: {before}");
1576
1577        tiling.drag_seam(1, Edge::Right, (700.0, 300.0), area(), settings());
1578        let after = rect_of(&tiling, 1).w;
1579        assert!(
1580            (after - 700.0).abs() < 2.0,
1581            "the seam went where it was asked: {after}"
1582        );
1583    }
1584
1585    /// Dragging to the same place twice gives the same layout. Deltas fed the
1586    /// layout's own response back in and the windows shook themselves apart.
1587    #[test]
1588    fn dragging_to_the_same_place_twice_is_the_same_layout() {
1589        let mut tiling = quad();
1590        tiling.drag_seam(1, Edge::Right, (650.0, 300.0), area(), settings());
1591        let once = rect_of(&tiling, 1).w;
1592        for _ in 0..20 {
1593            tiling.drag_seam(1, Edge::Right, (650.0, 300.0), area(), settings());
1594        }
1595        let many = rect_of(&tiling, 1).w;
1596        assert!((once - many).abs() < f64::EPSILON, "{once} then {many}");
1597    }
1598
1599    #[test]
1600    fn height_drags_find_the_horizontal_seam() {
1601        let mut tiling = quad();
1602        tiling.drag_seam(1, Edge::Bottom, (250.0, 400.0), area(), settings());
1603        let one = rect_of(&tiling, 1);
1604        assert!(
1605            (one.h - 400.0).abs() < 2.0,
1606            "went where it was asked: {one:?}"
1607        );
1608    }
1609
1610    #[test]
1611    fn a_lone_window_has_no_seam_to_drag() {
1612        let mut tiling = Tiling::new();
1613        tiling.insert(1, None, None, area(), settings());
1614        tiling.drag_seam(1, Edge::Right, (700.0, 300.0), area(), settings());
1615        assert!((rect_of(&tiling, 1).w - 1000.0).abs() < 1.0, "unchanged");
1616    }
1617}
1618
1619/// #120: the seam that moves is the one beside the edge that was grabbed.
1620///
1621/// The measurement this pins came off a real drag. Dragging one edge of a
1622/// tiled window slowly, the drag's own rectangle moved smoothly — 808, 809,
1623/// 817, 835 — while the slot the layout produced went 806, 1015, 1016, 1024:
1624/// a 209px jump in one frame, three frames of unchanged width, then another
1625/// leap. That is the signature of moving the seam on the *far* side, because
1626/// the far side moving is the near side standing still.
1627///
1628/// Every test below is written against [`quad`], where each window's immediate
1629/// parent is cut the opposite way to the split its side edges touch, so
1630/// "nearest ancestor on the axis" and "seam beside the edge" are different
1631/// branches and the difference is visible in the rectangles.
1632#[cfg(test)]
1633mod dragged_edge_tests {
1634    use super::{Axis, Edge, Tiling};
1635    use crate::{Rect, Settings};
1636
1637    fn area() -> Rect {
1638        Rect::new(0.0, 0.0, 1000.0, 600.0)
1639    }
1640    fn settings() -> Settings {
1641        Settings {
1642            gap: 0.0,
1643            split: 0.5,
1644            ..Settings::default()
1645        }
1646    }
1647    fn rect_of(tiling: &Tiling, id: u64) -> Rect {
1648        tiling
1649            .layout(area(), settings())
1650            .into_iter()
1651            .find(|(other, _)| *other == id)
1652            .expect("in the tree")
1653            .1
1654    }
1655    fn every_rect(tiling: &Tiling) -> Vec<(u64, Rect)> {
1656        let mut out = tiling.layout(area(), settings());
1657        out.sort_by_key(|(id, _)| *id);
1658        out
1659    }
1660
1661    /// Two columns of two. Window 1 is top-left, 2 top-right, 3 bottom-left,
1662    /// 4 bottom-right; the columns are divided by one vertical seam at the
1663    /// root, and each column by a horizontal seam of its own.
1664    ///
1665    /// The same shape as `seam_tests::quad`, restated here so this module
1666    /// reads on its own — the arrangement *is* the argument, and a fixture
1667    /// imported from elsewhere is one the reader has to go and look up.
1668    fn quad() -> Tiling {
1669        let mut tiling = Tiling::new();
1670        tiling.insert(1, None, None, area(), settings());
1671        tiling.insert(2, Some(1), None, area(), settings());
1672        tiling.insert(3, Some(1), None, area(), settings());
1673        tiling.insert(4, Some(2), None, area(), settings());
1674        tiling
1675    }
1676
1677    /// Window 1 is the first child of the root's vertical split, so that seam
1678    /// is on its right. Dragging the right edge to 700 has to leave the left
1679    /// edge at 0 — the failure being pinned is precisely a left edge that
1680    /// moves instead — and has to carry window 3 with it, because they share
1681    /// the column the root seam bounds.
1682    #[test]
1683    fn a_right_edge_drag_moves_the_seam_on_the_right() {
1684        let mut tiling = quad();
1685        tiling.drag_seam(1, Edge::Right, (700.0, 150.0), area(), settings());
1686
1687        let one = rect_of(&tiling, 1);
1688        assert!(
1689            (one.x - 0.0).abs() < 1.0,
1690            "the left edge stood still: {one:?}"
1691        );
1692        assert!(
1693            (one.w - 700.0).abs() < 2.0,
1694            "the right edge followed: {one:?}"
1695        );
1696        assert!(
1697            (rect_of(&tiling, 3).w - 700.0).abs() < 2.0,
1698            "the root seam moved, so the whole column did"
1699        );
1700        assert!((one.h - 300.0).abs() < 1.0, "nothing horizontal moved");
1701    }
1702
1703    /// The mirror. Window 2 is the *second* child of the root's vertical
1704    /// split, so that same seam is on its left, and a left-edge drag is what
1705    /// moves it. Its right edge must stay against the container at 1000.
1706    #[test]
1707    fn a_left_edge_drag_moves_the_seam_on_the_left() {
1708        let mut tiling = quad();
1709        tiling.drag_seam(2, Edge::Left, (700.0, 150.0), area(), settings());
1710
1711        let two = rect_of(&tiling, 2);
1712        assert!(
1713            (two.x - 700.0).abs() < 2.0,
1714            "the left edge followed: {two:?}"
1715        );
1716        assert!(
1717            (two.x + two.w - 1000.0).abs() < 2.0,
1718            "the right edge stood still: {two:?}"
1719        );
1720    }
1721
1722    /// Window 1 is the first child of its column's horizontal split, so that
1723    /// seam is below it. Its top edge is the container's, and its width is
1724    /// bounded by a seam that a vertical drag must not touch.
1725    #[test]
1726    fn a_bottom_edge_drag_moves_the_seam_below() {
1727        let mut tiling = quad();
1728        tiling.drag_seam(1, Edge::Bottom, (250.0, 400.0), area(), settings());
1729
1730        let one = rect_of(&tiling, 1);
1731        assert!(
1732            (one.y - 0.0).abs() < 1.0,
1733            "the top edge stood still: {one:?}"
1734        );
1735        assert!(
1736            (one.h - 400.0).abs() < 2.0,
1737            "the bottom edge followed: {one:?}"
1738        );
1739        assert!((one.w - 500.0).abs() < 1.0, "nothing vertical moved");
1740        assert!(
1741            (rect_of(&tiling, 2).h - 300.0).abs() < 1.0,
1742            "the other column's seam is a different seam and did not move"
1743        );
1744    }
1745
1746    /// And the mirror of that: window 3 is the second child of the left
1747    /// column's split, so the seam is above it and its bottom edge is the
1748    /// container's.
1749    #[test]
1750    fn a_top_edge_drag_moves_the_seam_above() {
1751        let mut tiling = quad();
1752        tiling.drag_seam(3, Edge::Top, (250.0, 200.0), area(), settings());
1753
1754        let three = rect_of(&tiling, 3);
1755        assert!(
1756            (three.y - 200.0).abs() < 2.0,
1757            "the top edge followed: {three:?}"
1758        );
1759        assert!(
1760            (three.y + three.h - 600.0).abs() < 2.0,
1761            "the bottom edge stood still: {three:?}"
1762        );
1763        assert!(
1764            (rect_of(&tiling, 1).h - 200.0).abs() < 2.0,
1765            "the neighbour above gave up the space"
1766        );
1767    }
1768
1769    /// The four cases where the grabbed edge is the container's own, which is
1770    /// where the old walk did its damage: it found the nearest ancestor cut on
1771    /// the axis regardless of which side of it the window sat, so every one of
1772    /// these dragged the seam on the *opposite* side and the window jumped.
1773    ///
1774    /// Nothing at all is the right answer. The thing beyond that edge is the
1775    /// screen, and the screen does not move.
1776    #[test]
1777    fn a_window_against_the_container_has_no_seam_on_that_side() {
1778        // Window 1 is top-left: its left edge and its top edge are the
1779        // container's. Window 2 is top-right, so its right edge is; window 3
1780        // is bottom-left, so its bottom edge is.
1781        let cases = [
1782            (1_u64, Edge::Left, (200.0, 150.0)),
1783            (1, Edge::Top, (250.0, 200.0)),
1784            (2, Edge::Right, (800.0, 150.0)),
1785            (3, Edge::Bottom, (250.0, 450.0)),
1786        ];
1787        for (id, edge, at) in cases {
1788            let mut tiling = quad();
1789            let before = every_rect(&tiling);
1790            tiling.drag_seam(id, edge, at, area(), settings());
1791            assert_eq!(
1792                every_rect(&tiling),
1793                before,
1794                "dragging window {id}'s {edge:?} edge moved something"
1795            );
1796        }
1797    }
1798
1799    /// A three-level tree, where the seam beside an edge is provably *not* the
1800    /// nearest ancestor cut on that axis — the case the two-by-two cannot show
1801    /// because there the nearest ancestor is the only one.
1802    ///
1803    /// Splitting window 4 again gives the bottom-right corner its own vertical
1804    /// seam. Window 4's right edge touches that inner seam; its *left* edge
1805    /// touches the root's, two branches further up, past a horizontal split
1806    /// and past a vertical one it is on the wrong side of. Walking to the
1807    /// nearest vertical ancestor finds the inner seam for both.
1808    fn nested() -> Tiling {
1809        let mut tiling = quad();
1810        tiling.insert(5, Some(4), None, area(), settings());
1811        tiling
1812    }
1813
1814    #[test]
1815    fn the_seam_beside_an_edge_is_not_always_the_nearest_ancestor() {
1816        let mut tiling = nested();
1817        assert!(
1818            (rect_of(&tiling, 4).w - 250.0).abs() < 1.0,
1819            "the corner was split again: {:?}",
1820            rect_of(&tiling, 4)
1821        );
1822
1823        // The left edge of window 4 is the root's seam. Moving it to 700 has
1824        // to widen the left column to 700 and leave window 5 — on the far side
1825        // of the inner seam — with its right edge still against the container.
1826        tiling.drag_seam(4, Edge::Left, (700.0, 450.0), area(), settings());
1827        assert!(
1828            (rect_of(&tiling, 1).w - 700.0).abs() < 2.0,
1829            "the root seam moved: {:?}",
1830            rect_of(&tiling, 1)
1831        );
1832        let five = rect_of(&tiling, 5);
1833        assert!(
1834            (five.x + five.w - 1000.0).abs() < 2.0,
1835            "the far side of the inner seam is still against the edge: {five:?}"
1836        );
1837    }
1838
1839    /// Window 5 sits in the bottom-right corner, so its right edge is the
1840    /// container's even though it has a vertical seam on its other side. The
1841    /// nearest-ancestor walk finds that inner seam and drags it, which moves
1842    /// the window's left edge for a drag on its right.
1843    #[test]
1844    fn a_nested_window_at_the_far_edge_still_moves_nothing() {
1845        let mut tiling = nested();
1846        let before = every_rect(&tiling);
1847        tiling.drag_seam(5, Edge::Right, (900.0, 450.0), area(), settings());
1848        assert_eq!(every_rect(&tiling), before, "the inner seam was dragged");
1849    }
1850
1851    /// The keyboard path, which took the same shortcut from the other end: it
1852    /// adjusted the immediate parent, and in this arrangement every leaf's
1853    /// parent is a horizontal split. `super+equal` meaning "wider" could not
1854    /// reach the centre vertical seam at all — it changed the height instead.
1855    #[test]
1856    fn the_keyboard_can_reach_the_centre_vertical_seam() {
1857        let mut tiling = quad();
1858        tiling.resize(1, Axis::Vertical, 0.1, area(), settings());
1859
1860        let one = rect_of(&tiling, 1);
1861        assert!((one.w - 600.0).abs() < 2.0, "it got wider: {one:?}");
1862        assert!((one.h - 300.0).abs() < 1.0, "and not taller: {one:?}");
1863    }
1864
1865    /// Growing is growing from either side. Window 2 has no seam on its right
1866    /// — it is against the container — so the seam on its left gives up the
1867    /// space, and the sign has to flip for the window to get bigger rather
1868    /// than smaller.
1869    #[test]
1870    fn a_window_against_the_container_grows_from_the_other_side() {
1871        let mut tiling = quad();
1872        tiling.resize(2, Axis::Vertical, 0.1, area(), settings());
1873
1874        let two = rect_of(&tiling, 2);
1875        assert!((two.w - 600.0).abs() < 2.0, "it got wider: {two:?}");
1876        assert!(
1877            (two.x + two.w - 1000.0).abs() < 2.0,
1878            "from the left, because the right is the container's: {two:?}"
1879        );
1880    }
1881
1882    /// The gap the compositor actually ships with.
1883    ///
1884    /// Every test above this point runs at `gap: 0.0`, and zero is the single
1885    /// value at which the seam band has no width — so "a share of the box" and
1886    /// "a share of the box less the gap" give the same answer, and the two
1887    /// sides of a seam are the same line. A suite written only at zero cannot
1888    /// tell whether a grabbed edge lands under the pointer or several pixels
1889    /// off it, which is the only thing #120 is about. Hence the cases below.
1890    fn gapped() -> Settings {
1891        Settings {
1892            gap: 12.0,
1893            split: 0.5,
1894            ..Settings::default()
1895        }
1896    }
1897
1898    /// [`quad`] built at a given gap.
1899    ///
1900    /// The same arrangement, not a different one: `insert` picks each split's
1901    /// axis from the target box's aspect ratio, and insetting a 1000x600
1902    /// screen by 12 changes neither which side of the root box is longer nor
1903    /// which side of a column's is. The tree is identical; only the pixels
1904    /// move.
1905    fn quad_at(settings: Settings) -> Tiling {
1906        let mut tiling = Tiling::new();
1907        tiling.insert(1, None, None, area(), settings);
1908        tiling.insert(2, Some(1), None, area(), settings);
1909        tiling.insert(3, Some(1), None, area(), settings);
1910        tiling.insert(4, Some(2), None, area(), settings);
1911        tiling
1912    }
1913
1914    fn rect_at(tiling: &Tiling, id: u64, settings: Settings) -> Rect {
1915        tiling
1916            .layout(area(), settings)
1917            .into_iter()
1918            .find(|(other, _)| *other == id)
1919            .expect("in the tree")
1920            .1
1921    }
1922
1923    /// The whole of #120 as a coordinate: the edge the hand is on comes to rest
1924    /// exactly where it was asked to.
1925    ///
1926    /// Named for the ask and not for the pointer, because since #124 they are
1927    /// not the same place -- the compositor sends where the *edge* belongs.
1928    /// What #120 was about is unaffected: whatever the caller asks for, the
1929    /// grabbed edge has to land on it and not a gap away from it.
1930    ///
1931    /// Both halves drag the *same* seam — the root's vertical one — to the
1932    /// same x, one of them by window 1's right edge and one by window 2's
1933    /// left. Each has to put its own grabbed edge at exactly 700, and so the
1934    /// two drags have to agree with each other about where 700 is.
1935    ///
1936    /// Against the formula this replaced — a share of `rect.w`, with no term
1937    /// for the gap — the right edge came to rest at 691.5 and the left at
1938    /// 703.5. Each wrong, wrong in opposite directions, and a full gap apart
1939    /// from each other; all three assertions below fail on it. At `gap: 0.0`
1940    /// not one of them does, which is how the defect passed a green suite.
1941    #[test]
1942    fn a_dragged_vertical_edge_lands_where_it_was_asked_across_the_gap() {
1943        let settings = gapped();
1944
1945        let mut trailing = quad_at(settings);
1946        trailing.drag_seam(1, Edge::Right, (700.0, 150.0), area(), settings);
1947        let one = rect_at(&trailing, 1, settings);
1948        assert!(
1949            (one.x + one.w - 700.0).abs() < 0.5,
1950            "the grabbed right edge is where it was asked: {one:?}"
1951        );
1952
1953        let mut leading = quad_at(settings);
1954        leading.drag_seam(2, Edge::Left, (700.0, 150.0), area(), settings);
1955        let two = rect_at(&leading, 2, settings);
1956        assert!(
1957            (two.x - 700.0).abs() < 0.5,
1958            "the grabbed left edge is where it was asked: {two:?}"
1959        );
1960
1961        // One seam, grabbed from either side, put in one place. This is the
1962        // assertion the old arithmetic missed by exactly `gap`.
1963        assert!(
1964            ((one.x + one.w) - two.x).abs() < 0.5,
1965            "the two sides of one seam disagree: {one:?} against {two:?}"
1966        );
1967    }
1968
1969    /// The same, on the other axis, because the gap term is spelled once per
1970    /// axis and a fix to one of them is not a fix to the other. Window 1's
1971    /// bottom edge and window 3's top edge are the two sides of the left
1972    /// column's horizontal seam.
1973    #[test]
1974    fn a_dragged_horizontal_edge_lands_where_it_was_asked_across_the_gap() {
1975        let settings = gapped();
1976
1977        let mut trailing = quad_at(settings);
1978        trailing.drag_seam(1, Edge::Bottom, (250.0, 400.0), area(), settings);
1979        let one = rect_at(&trailing, 1, settings);
1980        assert!(
1981            (one.y + one.h - 400.0).abs() < 0.5,
1982            "the grabbed bottom edge is where it was asked: {one:?}"
1983        );
1984
1985        let mut leading = quad_at(settings);
1986        leading.drag_seam(3, Edge::Top, (250.0, 400.0), area(), settings);
1987        let three = rect_at(&leading, 3, settings);
1988        assert!(
1989            (three.y - 400.0).abs() < 0.5,
1990            "the grabbed top edge is where it was asked: {three:?}"
1991        );
1992
1993        assert!(
1994            ((one.y + one.h) - three.y).abs() < 0.5,
1995            "the two sides of one seam disagree: {one:?} against {three:?}"
1996        );
1997    }
1998
1999    /// #124: a window handed its own edge does not move.
2000    ///
2001    /// **Internal to this tree, and the #124 review is why that is now written
2002    /// down.** This was billed as the space check — the thing that pins the
2003    /// compositor's rectangle for a pane against the one [`Tiling::node_box`]
2004    /// measures a seam in. It is not, and structurally cannot be: the edge it
2005    /// hands `drag_seam` comes from `rect_at`, which is [`Tiling::layout`], so
2006    /// both ends of the round trip are this same tree. A skew introduced
2007    /// anywhere on the compositor's side of the call leaves it green, which is
2008    /// exactly what it did.
2009    ///
2010    /// What it *does* pin is worth keeping and is a narrower claim: within one
2011    /// arrangement, `node_box` and `layout` agree. Both begin at
2012    /// `area.inset(gap)` and both descend through [`cut`], so a slot's edge and
2013    /// a seam's box are two readings off one thing, and the inversion in
2014    /// [`Tiling::drag_seam`] undoes `cut` exactly rather than approximately. A
2015    /// half-gap error in either — #120's symptom by another route — shows here.
2016    ///
2017    /// The check this one was mistaken for lives in the compositor, because
2018    /// only the compositor has both rectangles:
2019    /// `solium::state::tests::real_client::a_client_that_rounds_its_size_does_not_move_the_seam`
2020    /// lays a real tree out through `Solium::place`, lets a real client commit a
2021    /// cell less than it was asked for, and feeds the edge the compositor would
2022    /// send on the first frame back into the tree. That is the boundary, and it
2023    /// cannot be crossed from this crate at all.
2024    ///
2025    /// At [`gapped`] rather than at zero, which is the first reason this can
2026    /// say anything: at `gap: 0` the two sides of a seam are one line and every
2027    /// skew it could catch is zero pixels wide.
2028    ///
2029    /// **And off-centre, which is the second, discovered by checking that the
2030    /// test can fail.** Skewing [`Tiling::node_box`]'s own inset by half a gap
2031    /// and re-running it — the exact defect it is here to catch — left it
2032    /// green. At ratio 0.5 that skew is self-cancelling: moving the box's
2033    /// origin out by 6 and its width out by 12 leaves its midpoint exactly
2034    /// where it was, so a seam sitting at the midpoint does not move and a
2035    /// fixture built at `split: 0.5` cannot tell. Each case therefore drags its
2036    /// seam somewhere deliberately lopsided first, and asserts that it went. On
2037    /// the same skew the strengthened version fails on its first case, by
2038    /// 1.87px — which is why the tolerance is 0.5px and not "a few pixels". A
2039    /// skew does not have to be as wide as the gap to be a skew.
2040    ///
2041    /// Unlike the compositor-side tests in
2042    /// `solium::input::resize::dragged_edge_tests`, this one passes against
2043    /// `bf80265` and against `ec1da24` too: `drag_seam` did not change for #124
2044    /// and has not changed since. It is a guard on this tree's own internal
2045    /// consistency, not a regression test for the value the compositor sends.
2046    #[test]
2047    fn a_window_handed_its_own_edge_does_not_move() {
2048        let settings = gapped();
2049        // One case per side, each on a window that actually has a seam there:
2050        // 1 has the root's vertical seam on its right and its column's
2051        // horizontal seam below, 2 has the root's on its left, 3 has its
2052        // column's above. A window flush against the container on the side
2053        // tested would pass this vacuously, `drag_seam` having returned early.
2054        //
2055        // The third number is where that seam goes first. Every one of them is
2056        // well inside the 0.05..0.95 clamp -- the extremes of these two seams
2057        // are 60..928 and 40..548 -- because a clamped ratio would hold still
2058        // for the second drag whatever space it was measured in, which is the
2059        // other way this test could pass without meaning anything.
2060        for (id, edge, lopsided) in [
2061            (1_u64, Edge::Right, (340.0, 150.0)),
2062            (2, Edge::Left, (640.0, 150.0)),
2063            (1, Edge::Bottom, (250.0, 200.0)),
2064            (3, Edge::Top, (250.0, 420.0)),
2065        ] {
2066            let mut tiling = quad_at(settings);
2067            let centred = rect_at(&tiling, id, settings);
2068            tiling.drag_seam(id, edge, lopsided, area(), settings);
2069            let before = rect_at(&tiling, id, settings);
2070            assert!(
2071                (before.w - centred.w).abs() > 50.0 || (before.h - centred.h).abs() > 50.0,
2072                "the fixture has to be off-centre before it proves anything: \
2073                 window {id} is still {before:?}"
2074            );
2075
2076            let own_edge = match edge {
2077                Edge::Left => (before.x, before.y + before.h / 2.0),
2078                Edge::Right => (before.x + before.w, before.y + before.h / 2.0),
2079                Edge::Top => (before.x + before.w / 2.0, before.y),
2080                Edge::Bottom => (before.x + before.w / 2.0, before.y + before.h),
2081            };
2082
2083            tiling.drag_seam(id, edge, own_edge, area(), settings);
2084            let after = rect_at(&tiling, id, settings);
2085
2086            assert!(
2087                (after.x - before.x).abs() < 0.5
2088                    && (after.y - before.y).abs() < 0.5
2089                    && (after.w - before.w).abs() < 0.5
2090                    && (after.h - before.h).abs() < 0.5,
2091                "window {id} handed its own {edge:?} edge at {own_edge:?} moved: \
2092                 {before:?} became {after:?}"
2093            );
2094        }
2095    }
2096
2097    /// Idempotence at a real gap.
2098    ///
2099    /// The doc on [`Tiling::drag_seam`] promises that dragging to the same
2100    /// place twice gives the same layout, and that promise is what stops the
2101    /// windows shaking while the button is held. It is worth pinning at a
2102    /// non-zero gap specifically.
2103    ///
2104    /// Unlike the two above, this one passed against the old formula too —
2105    /// being wrong by a constant offset is still being wrong in the same place
2106    /// every time. It is a guard and not a witness: what it catches is a
2107    /// future inversion that reads its own output back, the shape the
2108    /// delta-accumulating version had, and only a non-zero gap would make that
2109    /// term visible.
2110    #[test]
2111    fn dragging_to_the_same_place_twice_changes_nothing_at_a_real_gap() {
2112        let settings = gapped();
2113        let mut tiling = quad_at(settings);
2114
2115        tiling.drag_seam(1, Edge::Right, (700.0, 150.0), area(), settings);
2116        let once = rect_at(&tiling, 1, settings);
2117        tiling.drag_seam(1, Edge::Right, (700.0, 150.0), area(), settings);
2118        let twice = rect_at(&tiling, 1, settings);
2119
2120        assert!(
2121            (once.w - twice.w).abs() < 0.001,
2122            "the second drag moved it: {once:?} then {twice:?}"
2123        );
2124    }
2125}
2126
2127/// #134: a floor under the tiles, and the seams that honour it.
2128///
2129/// Every arrangement here is built with `insert`, which does not read the
2130/// minimum, and with drags at [`free`] -- no minimum, the tree as it was
2131/// before #134 -- and only then is a question asked with a minimum in force.
2132/// A fixture built under the floor would already have been bent by it, and
2133/// the question would be about the fixture.
2134#[cfg(test)]
2135mod minimum_tests {
2136    use super::{Axis, Edge, Tiling};
2137    use crate::{Minimum, Rect, Settings};
2138
2139    fn free(gap: f64) -> Settings {
2140        Settings {
2141            gap,
2142            split: 0.5,
2143            ..Settings::default()
2144        }
2145    }
2146
2147    fn floored(gap: f64, w: f64, h: f64) -> Settings {
2148        Settings {
2149            minimum: Minimum { w, h },
2150            ..free(gap)
2151        }
2152    }
2153
2154    /// What `config.tiling.minimum` ships as, and `config.gap`.
2155    fn shipped() -> Settings {
2156        floored(12.0, 160.0, 96.0)
2157    }
2158
2159    fn rect_in(tiling: &Tiling, id: u64, area: Rect, settings: Settings) -> Rect {
2160        tiling
2161            .layout(area, settings)
2162            .into_iter()
2163            .find(|(other, _)| *other == id)
2164            .expect("in the tree")
2165            .1
2166    }
2167
2168    fn every_rect(tiling: &Tiling, area: Rect, settings: Settings) -> Vec<(u64, Rect)> {
2169        let mut out = tiling.layout(area, settings);
2170        out.sort_by_key(|(id, _)| *id);
2171        out
2172    }
2173
2174    fn near(left: Rect, right: Rect) -> bool {
2175        (left.x - right.x).abs() < 0.5
2176            && (left.y - right.y).abs() < 0.5
2177            && (left.w - right.w).abs() < 0.5
2178            && (left.h - right.h).abs() < 0.5
2179    }
2180
2181    /// **The tile under the pointer tries the other direction first.** A wide,
2182    /// short tile with no room side by side still has room one above the
2183    /// other, and that is still the tile the user pointed at.
2184    ///
2185    /// 300x200 at no gap: side by side is two 150-wide halves, under a
2186    /// 160-wide floor; one above the other is two 300x100s, over a 96-high one.
2187    /// `insert` -- the rule before #134, and `"allow"` now -- cuts it side by
2188    /// side regardless, which is the contrast that makes this a test of the
2189    /// turn rather than of the arrangement.
2190    #[test]
2191    fn a_tile_with_no_room_side_by_side_splits_one_above_the_other() {
2192        let area = Rect::new(0.0, 0.0, 300.0, 200.0);
2193        let settings = floored(0.0, 160.0, 96.0);
2194        let mut tiling = Tiling::new();
2195        tiling.insert(1, None, None, area, settings);
2196
2197        let mut allowed = tiling.clone();
2198        allowed.insert(2, None, Some((150.0, 150.0)), area, settings);
2199        assert!(
2200            (rect_in(&allowed, 2, area, settings).w - 150.0).abs() < 0.5,
2201            "the premise: `insert` cuts this tile side by side"
2202        );
2203
2204        // The pointer in the lower half, so the new window goes below.
2205        assert!(tiling.insert_fitting(2, None, Some((150.0, 150.0)), area, settings));
2206        let (one, two) = (
2207            rect_in(&tiling, 1, area, settings),
2208            rect_in(&tiling, 2, area, settings),
2209        );
2210        assert!(
2211            (one.w - 300.0).abs() < 0.5 && (two.w - 300.0).abs() < 0.5,
2212            "split one above the other, full width each: {one:?} {two:?}"
2213        );
2214        assert!(
2215            (one.h - 100.0).abs() < 0.5 && (two.h - 100.0).abs() < 0.5,
2216            "and half the height each: {one:?} {two:?}"
2217        );
2218        assert!(two.y > one.y, "on the pointer's side, below: {two:?}");
2219    }
2220
2221    /// **A tile with room neither way is left alone**, and so is the whole
2222    /// tree: `false` is only useful to a caller if trying somewhere else
2223    /// starts from where it was. Then `insert` still takes the window, once,
2224    /// which is what a node pushed and abandoned by the refusal would break --
2225    /// `contains` would find it, and `insert` would return without adding it.
2226    #[test]
2227    fn a_tile_with_no_room_either_way_is_left_alone() {
2228        let area = Rect::new(0.0, 0.0, 300.0, 180.0);
2229        let settings = floored(0.0, 160.0, 96.0);
2230        let mut tiling = Tiling::new();
2231        tiling.insert(1, None, None, area, settings);
2232        let before = every_rect(&tiling, area, settings);
2233
2234        assert!(!tiling.insert_fitting(2, None, Some((150.0, 90.0)), area, settings));
2235        assert!(!tiling.insert_largest(2, area, settings));
2236        assert!(!tiling.contains(2), "a refused window is not in the tree");
2237        assert_eq!(every_rect(&tiling, area, settings), before);
2238
2239        tiling.insert(2, None, Some((150.0, 90.0)), area, settings);
2240        assert_eq!(tiling.windows().len(), 2, "{:?}", tiling.windows());
2241    }
2242
2243    /// **Exactly the minimum is enough; a pixel less is not.** 356 wide less
2244    /// a 12 gap on each side is a 332 tile, and 332 less the 12 between the
2245    /// halves is two of exactly 160. One pixel narrower and each half is
2246    /// 159.5, and turning it does not help: 200 high splits into two of 94,
2247    /// under 96.
2248    #[test]
2249    fn a_split_that_lands_exactly_on_the_minimum_has_room() {
2250        let settings = shipped();
2251        let exact = Rect::new(0.0, 0.0, 356.0, 224.0);
2252        let mut tiling = Tiling::new();
2253        tiling.insert(1, None, None, exact, settings);
2254        assert!(
2255            tiling.insert_fitting(2, None, Some((300.0, 100.0)), exact, settings),
2256            "two halves of exactly the minimum were refused"
2257        );
2258        for id in [1, 2] {
2259            let rect = rect_in(&tiling, id, exact, settings);
2260            assert!(
2261                (rect.w - 160.0).abs() < 1e-9,
2262                "window {id} is not at the minimum: {rect:?}"
2263            );
2264        }
2265
2266        let short = Rect::new(0.0, 0.0, 355.0, 224.0);
2267        let mut tiling = Tiling::new();
2268        tiling.insert(1, None, None, short, settings);
2269        assert!(
2270            !tiling.insert_fitting(2, None, Some((300.0, 100.0)), short, settings),
2271            "halves of 159.5 were let under a 160 minimum"
2272        );
2273    }
2274
2275    /// An empty tree has room whatever size it is: its one window is not a
2276    /// division of anything, so there is no tile to keep above the floor.
2277    #[test]
2278    fn an_empty_tree_always_has_room() {
2279        let tiny = Rect::new(0.0, 0.0, 100.0, 50.0);
2280        let mut fitting = Tiling::new();
2281        assert!(fitting.insert_fitting(1, None, None, tiny, shipped()));
2282        let mut largest = Tiling::new();
2283        assert!(largest.insert_largest(1, tiny, shipped()));
2284        assert_eq!(fitting.windows(), vec![1]);
2285        assert_eq!(largest.windows(), vec![1]);
2286    }
2287
2288    /// Window 1 on the left, 599x700; windows 2 over 3 on the right, 601x401
2289    /// and 601x299. Built without a minimum, then asked about one of 300x360.
2290    ///
2291    /// Window 1 is the largest and has no room: across it is two of 299.5,
2292    /// under 300, and down it is two of 350, under 360. Window 3 is already
2293    /// shorter than 360 and no split makes it taller. Window 2 has room across,
2294    /// two of 300.5 by 401.
2295    fn crowded() -> (Tiling, Rect) {
2296        let area = Rect::new(0.0, 0.0, 1200.0, 700.0);
2297        let settings = free(0.0);
2298        let mut tiling = Tiling::new();
2299        tiling.insert(1, None, None, area, settings);
2300        tiling.insert(2, Some(1), None, area, settings);
2301        tiling.insert(3, Some(2), None, area, settings);
2302        tiling.drag_seam(1, Edge::Right, (599.0, 0.0), area, settings);
2303        tiling.drag_seam(2, Edge::Bottom, (0.0, 401.0), area, settings);
2304        (tiling, area)
2305    }
2306
2307    /// **`"largest"` splits the largest tile that has room, which is not
2308    /// always the largest tile.** See [`crowded`].
2309    #[test]
2310    fn largest_splits_the_largest_tile_that_has_room() {
2311        let (mut tiling, area) = crowded();
2312        let settings = floored(0.0, 300.0, 360.0);
2313        let one = rect_in(&tiling, 1, area, settings);
2314        let three = rect_in(&tiling, 3, area, settings);
2315        assert!(
2316            (one.w - 599.0).abs() < 0.5 && (three.h - 299.0).abs() < 0.5,
2317            "the fixture is not the one described: {:?}",
2318            every_rect(&tiling, area, settings)
2319        );
2320
2321        // Pointing into window 3, which has no room.
2322        assert!(!tiling.insert_fitting(4, None, Some((900.0, 600.0)), area, settings));
2323        assert!(tiling.insert_largest(4, area, settings));
2324
2325        let (two, four) = (
2326            rect_in(&tiling, 2, area, settings),
2327            rect_in(&tiling, 4, area, settings),
2328        );
2329        assert!(
2330            (two.w - 300.5).abs() < 0.5 && (four.w - 300.5).abs() < 0.5,
2331            "window 2's tile was not the one split: {:?}",
2332            every_rect(&tiling, area, settings)
2333        );
2334        assert!(four.x > two.x, "the new window takes the far side");
2335        assert!(near(rect_in(&tiling, 1, area, settings), one), "1 moved");
2336        assert!(near(rect_in(&tiling, 3, area, settings), three), "3 moved");
2337    }
2338
2339    /// Two columns of two at a real gap, with no minimum, exactly as
2340    /// `dragged_edge_tests::quad_at` builds them: 1 top-left, 2 top-right, 3
2341    /// bottom-left, 4 bottom-right, each 482x282 in a 1000x600 area.
2342    fn quad(settings: Settings) -> Tiling {
2343        let area = area();
2344        let mut tiling = Tiling::new();
2345        tiling.insert(1, None, None, area, settings);
2346        tiling.insert(2, Some(1), None, area, settings);
2347        tiling.insert(3, Some(1), None, area, settings);
2348        tiling.insert(4, Some(2), None, area, settings);
2349        tiling
2350    }
2351
2352    fn area() -> Rect {
2353        Rect::new(0.0, 0.0, 1000.0, 600.0)
2354    }
2355
2356    /// **No drag takes a tile under the minimum**, on any of the four sides
2357    /// that have a seam. The box is 976 wide less a 12 gap, so a 300 floor on
2358    /// either side of the root seam leaves 364 of travel, and each is dragged
2359    /// well past it.
2360    #[test]
2361    fn a_seam_cannot_be_dragged_to_make_a_tile_smaller_than_the_minimum() {
2362        let settings = floored(12.0, 300.0, 150.0);
2363        for (id, edge, to, measured, wanted) in [
2364            (1_u64, Edge::Right, (100.0, 150.0), 1_u64, 300.0),
2365            (2, Edge::Left, (950.0, 150.0), 2, 300.0),
2366            (1, Edge::Bottom, (250.0, 50.0), 1, 150.0),
2367            (3, Edge::Top, (250.0, 580.0), 3, 150.0),
2368        ] {
2369            let mut tiling = quad(free(12.0));
2370            tiling.drag_seam(id, edge, to, area(), settings);
2371            let rect = rect_in(&tiling, measured, area(), settings);
2372            let size = match edge.axis() {
2373                Axis::Vertical => rect.w,
2374                Axis::Horizontal => rect.h,
2375            };
2376            assert!(
2377                (size - wanted).abs() < 0.5,
2378                "dragging window {id}'s {edge:?} edge to {to:?} left window {measured} at \
2379                 {rect:?}, not stopped at the {wanted} minimum"
2380            );
2381        }
2382    }
2383
2384    /// **The floor counts every tile beyond the seam, not only the one beside
2385    /// it.** Window 4 is split again, so the right column holds 2 above a pair
2386    /// side by side, and the root seam -- window 4's left edge -- has to leave
2387    /// room for both halves of that pair: 160, the gap, 160.
2388    #[test]
2389    fn a_seam_leaves_room_for_every_tile_beyond_it() {
2390        let settings = shipped();
2391        let mut tiling = quad(free(12.0));
2392        tiling.insert(5, Some(4), None, area(), free(12.0));
2393        tiling.drag_seam(4, Edge::Left, (900.0, 450.0), area(), settings);
2394        let (four, five) = (
2395            rect_in(&tiling, 4, area(), settings),
2396            rect_in(&tiling, 5, area(), settings),
2397        );
2398        assert!(
2399            (four.w - 160.0).abs() < 0.5 && (five.w - 160.0).abs() < 0.5,
2400            "the pair beyond the seam was squeezed under the minimum: {four:?} {five:?}"
2401        );
2402        assert!(
2403            (five.x + five.w - 988.0).abs() < 0.5,
2404            "and the far edge stayed against the container: {five:?}"
2405        );
2406    }
2407
2408    /// **A drag on a seam that is already under the minimum does not jump.**
2409    /// The #124 class of bug, through a floor instead of a skew: clamping a
2410    /// ratio that is outside its range moves the seam on the first frame of
2411    /// the drag, with the pointer still where it pressed.
2412    ///
2413    /// Window 1 is dragged to 328 wide with no minimum, then a 600 minimum is
2414    /// put on it. Handed its own edge, nothing moves. Asked to shrink further,
2415    /// nothing moves. Asked to grow, it follows -- until window 2, at 636 and
2416    /// so over the floor, reaches 600 on the other side of the seam.
2417    #[test]
2418    fn a_drag_on_a_seam_already_under_the_minimum_does_not_jump() {
2419        let settings = floored(12.0, 600.0, 96.0);
2420        let lopsided = || {
2421            let mut tiling = quad(free(12.0));
2422            tiling.drag_seam(1, Edge::Right, (340.0, 150.0), area(), free(12.0));
2423            tiling
2424        };
2425        let before = every_rect(&lopsided(), area(), settings);
2426        let one = rect_in(&lopsided(), 1, area(), settings);
2427        assert!(
2428            (one.x + one.w - 340.0).abs() < 0.5,
2429            "the premise: window 1's right edge at 340: {one:?}"
2430        );
2431
2432        for (to, says) in [
2433            (340.0, "handed its own edge"),
2434            (300.0, "asked to shrink a tile already under the minimum"),
2435        ] {
2436            let mut tiling = lopsided();
2437            tiling.drag_seam(1, Edge::Right, (to, 150.0), area(), settings);
2438            assert_eq!(
2439                every_rect(&tiling, area(), settings),
2440                before,
2441                "{says}, the seam moved"
2442            );
2443        }
2444
2445        let mut tiling = lopsided();
2446        tiling.drag_seam(1, Edge::Right, (350.0, 150.0), area(), settings);
2447        let one = rect_in(&tiling, 1, area(), settings);
2448        assert!(
2449            (one.x + one.w - 350.0).abs() < 0.5,
2450            "growing the small side was refused: {one:?}"
2451        );
2452
2453        tiling.drag_seam(1, Edge::Right, (900.0, 150.0), area(), settings);
2454        let two = rect_in(&tiling, 2, area(), settings);
2455        assert!(
2456            (two.w - 600.0).abs() < 0.5,
2457            "the far side went under its own minimum: {two:?}"
2458        );
2459    }
2460
2461    /// With both sides of a seam under the minimum, any move shrinks one of
2462    /// them, so the seam holds still. The quad's columns are 482 each, both
2463    /// under 600.
2464    #[test]
2465    fn a_seam_with_both_sides_under_the_minimum_holds_still() {
2466        let settings = floored(12.0, 600.0, 96.0);
2467        for to in [100.0, 494.0, 900.0] {
2468            let mut tiling = quad(free(12.0));
2469            let before = every_rect(&tiling, area(), settings);
2470            tiling.drag_seam(1, Edge::Right, (to, 150.0), area(), settings);
2471            assert_eq!(
2472                every_rect(&tiling, area(), settings),
2473                before,
2474                "dragged to {to}, a seam with no room on either side moved"
2475            );
2476        }
2477    }
2478
2479    /// **The shipped minimum changes none of the #120 and #124 drags.** Each
2480    /// is the drag from `dragged_edge_tests` -- the four lopsided moves of
2481    /// `a_window_handed_its_own_edge_does_not_move`, then the window handed
2482    /// its own edge, and the two-sided 700 of the #120 case -- run once with
2483    /// no minimum and once at 160x96, and the two have to agree to the pixel.
2484    /// Every tile involved stays over 160x96, so a floor that moved any of
2485    /// them would be a floor clamping where it has no business to.
2486    #[test]
2487    fn the_shipped_minimum_leaves_every_existing_drag_where_it_was() {
2488        let cases: [(u64, Edge, (f64, f64)); 6] = [
2489            (1, Edge::Right, (340.0, 150.0)),
2490            (2, Edge::Left, (640.0, 150.0)),
2491            (1, Edge::Bottom, (250.0, 200.0)),
2492            (3, Edge::Top, (250.0, 420.0)),
2493            (1, Edge::Right, (700.0, 150.0)),
2494            (2, Edge::Left, (700.0, 150.0)),
2495        ];
2496        for (id, edge, to) in cases {
2497            let run = |settings: Settings| {
2498                let mut tiling = quad(settings);
2499                tiling.drag_seam(id, edge, to, area(), settings);
2500                let placed = rect_in(&tiling, id, area(), settings);
2501                let own = match edge {
2502                    Edge::Left => (placed.x, placed.y + placed.h / 2.0),
2503                    Edge::Right => (placed.x + placed.w, placed.y + placed.h / 2.0),
2504                    Edge::Top => (placed.x + placed.w / 2.0, placed.y),
2505                    Edge::Bottom => (placed.x + placed.w / 2.0, placed.y + placed.h),
2506                };
2507                tiling.drag_seam(id, edge, own, area(), settings);
2508                every_rect(&tiling, area(), settings)
2509            };
2510            let (without, with) = (run(free(12.0)), run(shipped()));
2511            assert!(
2512                without
2513                    .iter()
2514                    .zip(&with)
2515                    .all(|((a, left), (b, right))| a == b && near(*left, *right)),
2516                "window {id}'s {edge:?} drag to {to:?} came out differently under the \
2517                 shipped minimum: {without:?} against {with:?}"
2518            );
2519        }
2520    }
2521
2522    /// **The keyboard is held to the same floor**, and to the same rule for a
2523    /// tile already under it. At a 400 minimum window 1 shrinks to 400 and no
2524    /// further. Then, lopsided to 328 and put under a 600 minimum, a press
2525    /// that would shrink it does nothing and one that grows it stops where
2526    /// window 2 reaches 600.
2527    #[test]
2528    fn the_keyboard_does_not_shrink_a_tile_under_the_minimum() {
2529        let settings = floored(12.0, 400.0, 96.0);
2530        let mut tiling = quad(free(12.0));
2531        for _ in 0..5 {
2532            tiling.resize(1, Axis::Vertical, -0.05, area(), settings);
2533        }
2534        let one = rect_in(&tiling, 1, area(), settings);
2535        assert!((one.w - 400.0).abs() < 0.5, "shrunk past 400: {one:?}");
2536
2537        let settings = floored(12.0, 600.0, 96.0);
2538        let mut tiling = quad(free(12.0));
2539        tiling.drag_seam(1, Edge::Right, (340.0, 150.0), area(), free(12.0));
2540        let before = every_rect(&tiling, area(), settings);
2541        tiling.resize(1, Axis::Vertical, -0.05, area(), settings);
2542        assert_eq!(
2543            every_rect(&tiling, area(), settings),
2544            before,
2545            "a press shrank a tile already under the minimum"
2546        );
2547        tiling.resize(1, Axis::Vertical, 0.05, area(), settings);
2548        let two = rect_in(&tiling, 2, area(), settings);
2549        assert!(
2550            (two.w - 600.0).abs() < 0.5,
2551            "growing window 1 took window 2 under the minimum, or stopped short: {two:?}"
2552        );
2553    }
2554
2555    /// A split of NaN -- `split = 0/0` in a configuration -- is a seam whose
2556    /// ratio is NaN, and [`Tiling::room`] then cannot widen its range to take
2557    /// that ratio in. With a minimum no ratio can satisfy -- 600 on each side
2558    /// of a 964 span -- the range is empty, which is exactly where `f64::clamp`
2559    /// panics, and a panic here is the session gone. The press and the drag
2560    /// below have to come back; that is the assertion, with every rect finite
2561    /// besides.
2562    #[test]
2563    fn a_nan_split_leaves_the_clamp_standing() {
2564        let broken = Settings {
2565            split: f64::NAN,
2566            ..floored(12.0, 600.0, 96.0)
2567        };
2568        let mut tiling = Tiling::new();
2569        tiling.insert(1, None, None, area(), broken);
2570        tiling.insert(2, Some(1), None, area(), broken);
2571        tiling.resize(1, Axis::Vertical, 0.05, area(), broken);
2572        tiling.drag_seam(1, Edge::Right, (500.0, 300.0), area(), broken);
2573        for (id, rect) in tiling.layout(area(), broken) {
2574            assert!(
2575                rect.w.is_finite() && rect.h.is_finite(),
2576                "window {id} was laid out at {rect:?}"
2577            );
2578        }
2579    }
2580}
2581
2582/// **#115: a window's own floor**, as [`Tiling::set_floors`] hands it in.
2583///
2584/// A 1000x600 area at no gap unless a test says otherwise, so every number is
2585/// the one on the page. Each fixture is built with no floors -- the tree the
2586/// user made -- and the floors are handed in afterwards, which is the order a
2587/// session has them in: a client says how small it can go after its window
2588/// has a tile.
2589#[cfg(test)]
2590mod floor_tests {
2591    use super::{Axis, Edge, Tiling};
2592    use crate::{Floors, Minimum, Rect, Settings};
2593
2594    fn area() -> Rect {
2595        Rect::new(0.0, 0.0, 1000.0, 600.0)
2596    }
2597
2598    fn free() -> Settings {
2599        Settings {
2600            gap: 0.0,
2601            split: 0.5,
2602            ..Settings::default()
2603        }
2604    }
2605
2606    fn floored(w: f64, h: f64) -> Settings {
2607        Settings {
2608            minimum: Minimum { w, h },
2609            ..free()
2610        }
2611    }
2612
2613    fn floors(of: &[(u64, f64, f64)]) -> Floors {
2614        of.iter()
2615            .map(|&(id, w, h)| (id, Minimum { w, h }))
2616            .collect()
2617    }
2618
2619    fn rect_in(tiling: &Tiling, id: u64, area: Rect, settings: Settings) -> Rect {
2620        tiling
2621            .layout(area, settings)
2622            .into_iter()
2623            .find(|(other, _)| *other == id)
2624            .expect("in the tree")
2625            .1
2626    }
2627
2628    /// 1 on the left and 2 on the right, 500 each.
2629    fn pair() -> Tiling {
2630        let mut tiling = Tiling::new();
2631        tiling.insert(1, None, None, area(), free());
2632        tiling.insert(2, Some(1), None, area(), free());
2633        tiling
2634    }
2635
2636    /// **A window under its floor takes the room it needs from its sibling.**
2637    /// Window 1 says it cannot go under 700 wide in a 500 tile: the seam moves
2638    /// 200 to the right, and window 2 is what gives it.
2639    #[test]
2640    fn a_window_under_its_floor_takes_room_from_its_sibling() {
2641        let mut tiling = pair();
2642        tiling.set_floors(floors(&[(1, 700.0, 0.0)]));
2643        let (one, two) = (
2644            rect_in(&tiling, 1, area(), free()),
2645            rect_in(&tiling, 2, area(), free()),
2646        );
2647        assert!(
2648            (one.w - 700.0).abs() < 1e-6,
2649            "window 1 was not given its floor: {one:?}"
2650        );
2651        assert!(
2652            (two.x - 700.0).abs() < 1e-6 && (two.w - 300.0).abs() < 1e-6,
2653            "window 2 did not give up the difference: {two:?}"
2654        );
2655        assert!(!tiling.cramped(1, one) && !tiling.cramped(2, two));
2656    }
2657
2658    /// **And no more than the sibling holds**: a sibling gives down to its own
2659    /// floor or the minimum, whichever is larger, and not a pixel past it. At a
2660    /// 400-wide minimum window 1, asking for 800, gets the 600 that leaves
2661    /// window 2 at 400, and is laid out short -- cramped. Window 2, at the
2662    /// minimum exactly, is not; nor is a tile under the minimum with no floor
2663    /// of its own, which is `"allow"`'s business and not a refusal.
2664    #[test]
2665    fn a_sibling_gives_no_more_than_it_holds() {
2666        let settings = floored(400.0, 0.0);
2667        let mut tiling = pair();
2668        tiling.set_floors(floors(&[(1, 800.0, 0.0)]));
2669        let (one, two) = (
2670            rect_in(&tiling, 1, area(), settings),
2671            rect_in(&tiling, 2, area(), settings),
2672        );
2673        assert!((one.w - 600.0).abs() < 1e-6, "{one:?}");
2674        assert!(
2675            (two.w - 400.0).abs() < 1e-6,
2676            "window 2 went under the minimum: {two:?}"
2677        );
2678        assert!(
2679            tiling.cramped(1, one),
2680            "a window laid out under its floor is cramped"
2681        );
2682        assert!(!tiling.cramped(2, two), "a window at the minimum is not");
2683
2684        let under = Rect::new(0.0, 0.0, 100.0, 600.0);
2685        assert!(
2686            !tiling.cramped(2, under),
2687            "a tile under the minimum with no floor of its own is not cramped"
2688        );
2689    }
2690
2691    /// **The ratio the branch holds is left alone**, so a window whose floor
2692    /// goes away hands the room back. The floors are replaced whole: handing in
2693    /// a set without window 1 is window 1 having none.
2694    #[test]
2695    fn a_window_whose_floor_goes_away_hands_the_room_back() {
2696        let mut tiling = pair();
2697        tiling.set_floors(floors(&[(1, 700.0, 0.0)]));
2698        assert!((rect_in(&tiling, 1, area(), free()).w - 700.0).abs() < 1e-6);
2699
2700        tiling.set_floors(floors(&[(2, 0.0, 100.0)]));
2701        let one = rect_in(&tiling, 1, area(), free());
2702        assert!(
2703            (one.w - 500.0).abs() < 1e-6,
2704            "the seam stayed where the floor pushed it: {one:?}"
2705        );
2706    }
2707
2708    /// **Two windows that cannot both fit leave the seam where the user put
2709    /// it.** 700 and 700 do not go into 1000; moving the seam either way takes
2710    /// from one window short of room to give to the other.
2711    #[test]
2712    fn two_windows_that_cannot_both_fit_keep_the_users_seam() {
2713        let mut tiling = pair();
2714        tiling.drag_seam(1, Edge::Right, (450.0, 300.0), area(), free());
2715        tiling.set_floors(floors(&[(1, 700.0, 0.0), (2, 700.0, 0.0)]));
2716        let (one, two) = (
2717            rect_in(&tiling, 1, area(), free()),
2718            rect_in(&tiling, 2, area(), free()),
2719        );
2720        assert!((one.w - 450.0).abs() < 1e-6, "{one:?}");
2721        assert!((two.w - 550.0).abs() < 1e-6, "{two:?}");
2722        assert!(tiling.cramped(1, one) && tiling.cramped(2, two));
2723    }
2724
2725    /// **A floor deep in a subtree takes its room across the root**, and a
2726    /// subtree cut the same way gives down to what each window in it holds.
2727    ///
2728    /// On 1600x600 at a 100-wide minimum: 1 on the left, 800; 2 and 3 side by
2729    /// side on the right, 400 each. Window 3 asks for 750. The right side needs
2730    /// 750 for window 3 and 100 for window 2, 850 in all, so the root seam
2731    /// moves 50 left; then window 2 gives 350 of its 400 to window 3.
2732    ///
2733    /// Then cut the other way: 3 under 2 in the right column, and a floor of
2734    /// 900 on 3. A column is as narrow as its widest floor, so the root seam
2735    /// gives the whole column 900, and window 2 goes with it.
2736    #[test]
2737    fn a_floor_deep_in_a_subtree_takes_room_across_the_root() {
2738        let wide = Rect::new(0.0, 0.0, 1600.0, 600.0);
2739        let settings = floored(100.0, 0.0);
2740        let mut tiling = Tiling::new();
2741        tiling.insert(1, None, None, wide, free());
2742        tiling.insert(2, Some(1), None, wide, free());
2743        tiling.insert(3, Some(2), None, wide, free());
2744        assert!(
2745            (rect_in(&tiling, 3, wide, free()).w - 400.0).abs() < 1e-6,
2746            "the premise: 2 and 3 side by side on the right"
2747        );
2748        tiling.set_floors(floors(&[(3, 750.0, 0.0)]));
2749        let (one, two, three) = (
2750            rect_in(&tiling, 1, wide, settings),
2751            rect_in(&tiling, 2, wide, settings),
2752            rect_in(&tiling, 3, wide, settings),
2753        );
2754        assert!((one.w - 750.0).abs() < 1e-6, "the root seam: {one:?}");
2755        assert!(
2756            (two.w - 100.0).abs() < 1e-6,
2757            "window 2 held its minimum: {two:?}"
2758        );
2759        assert!(
2760            (three.w - 750.0).abs() < 1e-6,
2761            "window 3 has its floor: {three:?}"
2762        );
2763
2764        let mut column = Tiling::new();
2765        column.insert(1, None, None, area(), free());
2766        column.insert(2, Some(1), None, area(), free());
2767        column.insert(3, Some(2), None, area(), free());
2768        assert!(
2769            (rect_in(&column, 3, area(), free()).h - 300.0).abs() < 1e-6,
2770            "the premise: 3 under 2 on the right"
2771        );
2772        column.set_floors(floors(&[(3, 900.0, 0.0)]));
2773        let (one, two, three) = (
2774            rect_in(&column, 1, area(), settings),
2775            rect_in(&column, 2, area(), settings),
2776            rect_in(&column, 3, area(), settings),
2777        );
2778        assert!((one.w - 100.0).abs() < 1e-6, "{one:?}");
2779        assert!(
2780            (two.w - 900.0).abs() < 1e-6 && (three.w - 900.0).abs() < 1e-6,
2781            "the column is as wide as its widest floor: {two:?} {three:?}"
2782        );
2783    }
2784
2785    /// **A split that would leave a window under its own floor is refused**,
2786    /// by both of #134's inserts, and whichever of the two windows the floor
2787    /// is on. At a 400x400 minimum in 1000x600: side by side, a window that
2788    /// needs 700 gets 600 at most; one above the other is 300 high each. So
2789    /// window 1 with a floor has no room for window 2 either way, and nor does
2790    /// window 1 without one for a window 2 with it -- the new window's floor is
2791    /// read before it is in the tree.
2792    ///
2793    /// Without floors, the same split has room: 500 and 500 side by side.
2794    #[test]
2795    fn a_split_that_would_put_a_window_under_its_floor_is_refused() {
2796        let settings = floored(400.0, 400.0);
2797        for (owner, says) in [(1, "the window being split"), (2, "the window being added")] {
2798            let mut tiling = Tiling::new();
2799            tiling.insert(1, None, None, area(), settings);
2800            let mut control = tiling.clone();
2801            assert!(
2802                control.insert_fitting(2, None, Some((900.0, 300.0)), area(), settings),
2803                "the premise: without a floor there is room"
2804            );
2805
2806            tiling.set_floors(floors(&[(owner, 700.0, 0.0)]));
2807            assert!(
2808                !tiling.insert_fitting(2, None, Some((900.0, 300.0)), area(), settings),
2809                "a floor on {says} was not read by insert_fitting"
2810            );
2811            assert!(
2812                !tiling.insert_largest(2, area(), settings),
2813                "a floor on {says} was not read by insert_largest"
2814            );
2815            assert_eq!(tiling.windows(), vec![1]);
2816        }
2817    }
2818
2819    /// **A seam stops at a neighbour's own floor -- the seam itself, not only
2820    /// the picture of it.** Window 2 needs 600: dragging window 1's right edge
2821    /// to 900 stops at 400. Then window 2's floor goes away, and the seam is
2822    /// still where the drag stopped it; a seam that had followed the pointer
2823    /// out of sight would now jump to 900.
2824    ///
2825    /// And from the other side: window 1 needs 600, so dragging its right edge
2826    /// in to 100 leaves it at 600.
2827    #[test]
2828    fn a_seam_stops_at_a_neighbours_own_floor() {
2829        let mut tiling = pair();
2830        tiling.set_floors(floors(&[(2, 600.0, 0.0)]));
2831        tiling.drag_seam(1, Edge::Right, (900.0, 300.0), area(), free());
2832        let two = rect_in(&tiling, 2, area(), free());
2833        assert!(
2834            (two.w - 600.0).abs() < 1e-6,
2835            "window 2 went under its floor: {two:?}"
2836        );
2837        tiling.set_floors(Floors::new());
2838        let one = rect_in(&tiling, 1, area(), free());
2839        assert!(
2840            (one.w - 400.0).abs() < 1e-6,
2841            "the seam went on past where it was drawn: {one:?}"
2842        );
2843
2844        let mut tiling = pair();
2845        tiling.set_floors(floors(&[(1, 600.0, 0.0)]));
2846        tiling.drag_seam(1, Edge::Right, (100.0, 300.0), area(), free());
2847        let one = rect_in(&tiling, 1, area(), free());
2848        assert!((one.w - 600.0).abs() < 1e-6, "{one:?}");
2849    }
2850
2851    /// **A press stops at a window's own floor**, as a drag does: window 1
2852    /// needs 600, and five presses that would take it to 250 leave it there.
2853    #[test]
2854    fn a_press_stops_at_a_windows_own_floor() {
2855        let mut tiling = pair();
2856        tiling.set_floors(floors(&[(1, 600.0, 0.0)]));
2857        for _ in 0..5 {
2858            tiling.resize(1, Axis::Vertical, -0.05, area(), free());
2859        }
2860        tiling.set_floors(Floors::new());
2861        let one = rect_in(&tiling, 1, area(), free());
2862        assert!((one.w - 600.0).abs() < 1e-6, "{one:?}");
2863    }
2864
2865    /// **A press moves the seam from where it is drawn.** The tree holds 0.5
2866    /// and window 1's floor draws it at 0.7; `super+equal` grows window 1 by
2867    /// 0.05 of the box from there, to 750. From the ratio it holds, 0.55, the
2868    /// press would have changed nothing on screen.
2869    #[test]
2870    fn a_press_beside_a_floor_moves_the_seam_from_where_it_is_drawn() {
2871        let mut tiling = pair();
2872        tiling.set_floors(floors(&[(1, 700.0, 0.0)]));
2873        tiling.resize(1, Axis::Vertical, 0.05, area(), free());
2874        let one = rect_in(&tiling, 1, area(), free());
2875        assert!((one.w - 750.0).abs() < 1e-6, "{one:?}");
2876    }
2877}