Skip to main content

kui_core/
layout.rs

1//! The flex solver: clay-style layout over the flat tree, run by
2//! [`compute`] once per frame.
3//!
4//! Each node declares a [`Sizing`] per axis (`Fit`,
5//! `Grow`, `Fixed`, `Percent` or a size expression), optional clamps, a
6//! direction, padding, gap and alignment; see
7//! [`LayoutSpec`](crate::spec::LayoutSpec). The solver resolves them in
8//! five passes over the preorder tree:
9//!
10//! 1. fit widths (reverse: children before parents)
11//! 2. grow widths (forward: parents before children)
12//! 3. fit heights (reverse; text wraps at its final width here)
13//! 4. grow heights (forward)
14//! 5. positions (forward)
15//!
16//! A wrapping row breaks its children into lines in pass 2 and every later
17//! pass reads that grouping, which is why wrapping is rows-only: a row's
18//! width is final before its height is measured, where a column would need
19//! its height first. A sixth pass runs only on a frame with a float
20//! anchored to a node by key (`FloatAnchor::Node`), since such a float's
21//! anchor may come later in preorder; its subtree is laid out again from
22//! the anchor's final rect.
23//!
24//! A table (`LayoutSpec::table`) is a column whose rows' cells line up:
25//! pass 1 reaches the table after its rows and cells and sets every fit
26//! cell to its column's widest and every row to the columns' width, so the
27//! table's own fit width is the aligned columns; pass 2 reaches it before
28//! its rows, resolves the columns against the widest row once and writes
29//! each column's width into its cells, and a row of a table then leaves
30//! its children alone. A column, a table or a leaf straight under the
31//! table has no cells.
32//!
33//! Text measurement goes through [`TextMeasure`], so the solver is
34//! testable with a deterministic stub and never depends on system fonts.
35
36use crate::geom::{Rect, Size, Vec2};
37use crate::scroll::ScrollStore;
38use crate::spec::{Align, Dir, FloatAnchor, Min, Sizing};
39use crate::tree::{NIL, NodeContent, Tree};
40
41/// Gated on the tree-level flag first: on a frame with no floats this is
42/// one predicted branch rather than a read through every child's spec.
43#[inline]
44fn is_float(tree: &Tree, i: u32) -> bool {
45    tree.any_float && tree.specs[i as usize].layout.float.is_some()
46}
47
48/// Where in the free space one thing sits: 0 at the start, 1 at the end.
49/// The spreads and `Baseline` mean nothing for a single placement — a
50/// cross axis, a float's attach point — and land where a spread puts a
51/// lone child (`diag::ALIGN_IGNORED` says so where one is declared).
52fn align_factor(a: Align) -> f32 {
53    match a {
54        Align::Start | Align::SpaceBetween | Align::Baseline => 0.0,
55        Align::Center | Align::SpaceAround | Align::SpaceEvenly => 0.5,
56        Align::End => 1.0,
57    }
58}
59
60/// How the main axis's free space is dealt out to `n` in-flow children:
61/// what goes before the first, and what goes between each two on top of
62/// the gap. Nothing is dealt when nothing is free, so a
63/// run that overflows or holds a grow child is laid out by its gaps
64/// alone, whatever the alignment.
65fn main_spread(a: Align, free: f32, n: u32) -> (f32, f32) {
66    let n = n as f32;
67    match a {
68        Align::SpaceBetween if n > 1.0 => (0.0, free / (n - 1.0)),
69        Align::SpaceAround if n > 0.0 => (free / (2.0 * n), free / n),
70        Align::SpaceEvenly => (free / (n + 1.0), free / (n + 1.0)),
71        _ => (align_factor(a) * free, 0.0),
72    }
73}
74
75fn mirror(a: Align) -> Align {
76    match a {
77        Align::Start => Align::End,
78        Align::End => Align::Start,
79        other => other,
80    }
81}
82
83/// Whether the in-flow children of `i` line up by their baselines: a row
84/// that says so. A column's cross axis is horizontal, where a baseline is
85/// not a line, so it lays out as `Start` there (as CSS does).
86#[inline]
87fn baseline_row(tree: &Tree, i: u32) -> bool {
88    let l = &tree.specs[i as usize].layout;
89    l.cross_align == Align::Baseline && l.dir == Dir::Row
90}
91
92/// Whether child `c` of a baseline row takes part in the alignment: a
93/// child whose height is `Grow` or `Percent` is sized against the line
94/// and fills it, so it sits at the line's top and its height does not
95/// count toward the line's (as in `line_extents`).
96#[inline]
97fn aligns_by_baseline(tree: &Tree, c: u32) -> bool {
98    !matches!(
99        child_sizing(tree, c, AxisSel::Height),
100        Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_)
101    )
102}
103
104/// The first baseline of node `i`, logical px below its top edge, or
105/// `None` when nothing inside it is text. A text node's
106/// and an editor's are measured (`TextMeasure::baseline`, stored by
107/// `fit_heights` on a frame that has a baseline row); a container's is
108/// its first in-flow child's, carried down through where that child
109/// sits in it — which is why this reads sizes only, and can answer in
110/// the fit pass as well as in `positions`. Scrolling is ignored: a
111/// scrolled list's baseline is its unscrolled first row's.
112fn first_baseline(tree: &Tree, i: u32) -> Option<f32> {
113    match tree.content[i as usize] {
114        NodeContent::Text(_) | NodeContent::Edit(_) => tree
115            .baseline
116            .get(i as usize)
117            .copied()
118            .filter(|b| b.is_finite()),
119        NodeContent::Container => {
120            let f = first_in_flow(tree, i);
121            if f == NIL {
122                return None;
123            }
124            let spec = &tree.specs[i as usize].layout;
125            let size = tree.size[i as usize];
126            let fs = tree.size[f as usize];
127            match spec.dir {
128                Dir::Column => {
129                    let fb = first_baseline(tree, f)?;
130                    let content = (size.h - spec.padding.y()).max(0.0);
131                    let (mut used, mut n) = (0.0f32, 0u32);
132                    for c in tree.children(i) {
133                        if !is_float(tree, c) {
134                            used += tree.size[c as usize].h;
135                            n += 1;
136                        }
137                    }
138                    if n > 1 {
139                        used += spec.gap * (n - 1) as f32;
140                    }
141                    let (lead, _) = main_spread(spec.main_align, (content - used).max(0.0), n);
142                    Some(spec.padding.t + lead + fb)
143                }
144                Dir::Row => {
145                    let end = if wraps(tree, i) {
146                        line_end(tree, f)
147                    } else {
148                        NIL
149                    };
150                    if spec.cross_align == Align::Baseline {
151                        // The row's own shared baseline, when anything in
152                        // its first line has one.
153                        let (above, _, any) = line_baseline(tree, f, end);
154                        return any.then_some(spec.padding.t + above);
155                    }
156                    let fb = first_baseline(tree, f)?;
157                    let extent = if end == NIL {
158                        (size.h - spec.padding.y()).max(0.0)
159                    } else {
160                        line_extents(tree, f, end, spec.gap).1
161                    };
162                    let off = align_factor(spec.cross_align) * (extent - fs.h).max(0.0);
163                    Some(spec.padding.t + off + fb)
164                }
165            }
166        }
167        _ => None,
168    }
169}
170
171/// A baseline line `[c, end)`'s shared baseline: the most any taking
172/// part reaches above it, the most any hangs below it, and whether any
173/// child had a baseline of its own. A child with none aligns its bottom
174/// edge (CSS's synthesized baseline), so all of it is above.
175fn line_baseline(tree: &Tree, c: u32, end: u32) -> (f32, f32, bool) {
176    let (mut above, mut below, mut any) = (0.0f32, 0.0f32, false);
177    let mut k = c;
178    while k != end && k != NIL {
179        if !is_float(tree, k) && aligns_by_baseline(tree, k) {
180            let h = tree.size[k as usize].h;
181            let b = match first_baseline(tree, k) {
182                Some(b) => {
183                    any = true;
184                    b
185                }
186                None => h,
187            };
188            above = above.max(b);
189            below = below.max(h - b);
190        }
191        k = tree.next_sibling[k as usize];
192    }
193    (above, below, any)
194}
195
196/// One axis of float attachment: anchor point minus self point, plus offset.
197fn attach(
198    anchor_pos: f32,
199    anchor_len: f32,
200    self_len: f32,
201    anchor_pt: Align,
202    self_pt: Align,
203    off: f32,
204) -> f32 {
205    anchor_pos + align_factor(anchor_pt) * anchor_len - align_factor(self_pt) * self_len + off
206}
207
208/// How far `[pos, pos+len]` sticks out of `[0, limit]`.
209fn overflow(pos: f32, len: f32, limit: f32) -> f32 {
210    (-pos).max(0.0) + (pos + len - limit).max(0.0)
211}
212
213/// Whether `i` breaks its children into wrap lines.
214///
215/// Rows only, and never on a main axis that scrolls. Breaking needs a
216/// definite main size to break against, and the pass order hands a row one
217/// — its width is final in pass 2, before its height is measured in pass 3,
218/// so a line's cross extent is known by the time anything needs it. A
219/// column is the mirror image and does not work: its main size is not final
220/// until pass 4, two passes after the cross-axis fit that would have to sum
221/// the lines. `scroll_x` says the same thing a different way — an axis that
222/// scrolls is unbounded, and an unbounded axis has nothing to break
223/// against. `diag::WRAP_IGNORED` reports both. Always inlined: asked of
224/// every node in three passes, as a call it cost the 10k grid 2% (C48).
225#[inline(always)]
226fn wraps(tree: &Tree, i: u32) -> bool {
227    if !tree.any_wrap {
228        return false;
229    }
230    let s = &tree.specs[i as usize].layout;
231    s.wrap && s.dir == Dir::Row && !s.scroll_x && !is_table_row(tree, i)
232}
233
234/// Whether `i` is a row of a table: an in-flow `Row` container child of
235/// a table. Anything else straight under the table — a heading text
236/// beside the rows, a `column` section wrapping a heading over a row, a
237/// nested table — has no cells and keeps its own width, and its children
238/// are its own. Gated on the tree-level flag first, as
239/// [`is_float`] is. The rules (`emit_rules`) ask it too, so a table's
240/// grid is drawn over the rows its columns were laid across.
241#[inline]
242pub(crate) fn is_table_row(tree: &Tree, i: u32) -> bool {
243    if !tree.any_table {
244        return false;
245    }
246    let p = tree.parent[i as usize];
247    p != NIL
248        && tree.specs[p as usize].layout.is_table()
249        && row_shaped(
250            &tree.specs[i as usize].layout,
251            matches!(tree.content[i as usize], NodeContent::Container),
252        )
253}
254
255/// The half of [`is_table_row`] a node answers alone: an in-flow `Row`
256/// container (`container` false for a text, an image, any leaf). A
257/// departing table's ghost has no tree to ask, and asks this of the
258/// children it copied.
259#[inline]
260pub(crate) fn row_shaped(spec: &crate::spec::LayoutSpec, container: bool) -> bool {
261    spec.dir == Dir::Row && container && spec.float.is_none()
262}
263
264/// Whether `i` is a cell of a table: an in-flow child of a table row.
265#[inline]
266fn is_table_cell(tree: &Tree, i: u32) -> bool {
267    if !tree.any_table {
268        return false;
269    }
270    let p = tree.parent[i as usize];
271    p != NIL && is_table_row(tree, p) && !is_float(tree, i)
272}
273
274/// One column of a table, as its cells declared it.
275#[derive(Clone, Copy, Debug, Default)]
276struct Col {
277    /// The widest cell's fitted width: a `Fixed` cell's px, a `Fit`
278    /// cell's content, a `Grow` or `Percent` cell's floor.
279    fit: f32,
280    /// The largest `Grow` factor among the cells, 0 for none: the column
281    /// grows with the table when any cell asked to.
282    grow: f32,
283    /// The largest `Percent` among the cells, 0 for none; read only when
284    /// nothing grows.
285    pct: f32,
286    /// The largest size expression among the cells, in px
287    /// of the room the columns are laid across: the column is the larger
288    /// of it and `pct` of that room. Each cell's is resolved and the
289    /// largest kept, as `pct` keeps the largest percentage — two
290    /// expressions cannot be compared until they are numbers, and the
291    /// first row's once stood for the column. In the fit pass, before
292    /// there is a room, only whether there is one (`Some(0.0)`).
293    calc: Option<f32>,
294    /// Whether any cell is `Fixed`: a fixed column is never shrunk.
295    fixed: bool,
296    /// The strictest clamps its cells declared: the largest floor and
297    /// the smallest ceiling.
298    min: f32,
299    max: f32,
300    /// The width resolved for it.
301    w: f32,
302}
303
304impl Col {
305    fn clamp(&self, w: f32) -> f32 {
306        w.clamp(self.min, self.max.max(self.min))
307    }
308}
309
310/// The columns of table `i`, read off its cells' current widths and
311/// specs: the nth in-flow child of each in-flow row is a cell of column
312/// n, and a row with fewer cells fills the first columns. A text cell
313/// has no spec sizing and reads as `Fit`. `room` is what a calc cell
314/// resolves against — `None` in the fit pass, which reads no calc.
315fn table_columns(tree: &Tree, i: u32, room: Option<f32>) -> Vec<Col> {
316    let mut cols: Vec<Col> = Vec::new();
317    for row in tree.children(i) {
318        if !is_table_row(tree, row) {
319            continue;
320        }
321        let mut j = 0usize;
322        for cell in tree.children(row) {
323            if is_float(tree, cell) {
324                continue;
325            }
326            if j == cols.len() {
327                cols.push(Col {
328                    max: f32::INFINITY,
329                    ..Col::default()
330                });
331            }
332            let col = &mut cols[j];
333            let spec = tree.specs[cell as usize].layout;
334            col.fit = col.fit.max(tree.size[cell as usize].w);
335            match child_sizing(tree, cell, AxisSel::Width) {
336                Sizing::Grow(f) => col.grow = col.grow.max(f.max(0.0)),
337                Sizing::Percent(p) => col.pct = col.pct.max(p),
338                Sizing::Calc(c) => {
339                    let px = room.map_or(0.0, |r| c.resolve(r));
340                    col.calc = Some(col.calc.map_or(px, |w| w.max(px)));
341                }
342                Sizing::Fixed(_) => col.fixed = true,
343                Sizing::Fit => {}
344            }
345            if !matches!(tree.content[cell as usize], NodeContent::Text(_)) {
346                col.min = col.min.max(spec.min_w.resolved());
347                col.max = col.max.min(spec.max_w_px());
348            }
349            j += 1;
350        }
351    }
352    cols
353}
354
355/// The width row `row` needs for `cols` — its cells at the columns'
356/// widths, the gaps between them and its padding — which is what a
357/// `Fit` row of a table is.
358fn table_row_fit(tree: &Tree, row: u32, cols: &[Col]) -> f32 {
359    let spec = tree.specs[row as usize].layout;
360    let mut w = 0.0f32;
361    let mut n = 0u32;
362    for cell in tree.children(row) {
363        if is_float(tree, cell) {
364            continue;
365        }
366        w += cols.get(n as usize).map_or(0.0, |c| c.w);
367        n += 1;
368    }
369    if n > 1 {
370        w += spec.gap * (n - 1) as f32;
371    }
372    w + spec.padding.x()
373}
374
375/// Writes each column's width into its cells and sizes the rows to them.
376/// A text cell takes the width too — `fit_heights` keeps it.
377///
378/// Which rows: in pass 1 (`fitting`) every row but a `Fixed` one — the
379/// `grow` and percent rows included, whose own pass-1 width is 0 — so
380/// the table's fit width, read next, is its columns' and a `Fit` table
381/// of `grow` rows is the aligned list and not nothing. Pass 2 sizes them
382/// for good, and the fit is only the number the table reads.
383/// In pass 2 the `Fit` rows, and — when the table scrolls x — every row
384/// widened to its columns if they overflow it, since a `grow` row is the
385/// table's own width and `positions` measures a scroll container's
386/// content from its children's boxes: without this the overflow the
387/// table kept was clipped and `scroll_max.x` was 0. A
388/// table that does not scroll leaves its rows' boxes alone, as any row
389/// is left when fixed children overflow it.
390fn table_apply(tree: &mut Tree, i: u32, cols: &[Col], fitting: bool) {
391    let scrolls = tree.specs[i as usize].layout.scroll_x;
392    let mut row = tree.first_child[i as usize];
393    while row != NIL {
394        if is_table_row(tree, row) {
395            let mut j = 0usize;
396            let mut cell = tree.first_child[row as usize];
397            while cell != NIL {
398                if !is_float(tree, cell) {
399                    tree.size[cell as usize].w = cols[j].w;
400                    j += 1;
401                }
402                cell = tree.next_sibling[cell as usize];
403            }
404            let spec = tree.specs[row as usize].layout;
405            match spec.width {
406                Sizing::Fit => {
407                    let fit = table_row_fit(tree, row, cols);
408                    tree.size[row as usize].w = spec.clamp_w(fit);
409                }
410                Sizing::Fixed(_) => {}
411                Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) if fitting => {
412                    let fit = table_row_fit(tree, row, cols);
413                    tree.size[row as usize].w = spec.clamp_w(fit);
414                }
415                Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => {}
416            }
417            if scrolls && !fitting {
418                let fit = table_row_fit(tree, row, cols);
419                let w = &mut tree.size[row as usize].w;
420                *w = w.max(fit);
421            }
422        }
423        row = tree.next_sibling[row as usize];
424    }
425}
426
427/// Pass 1's table step: every column at its fit — the widest cell — and
428/// the rows fitted to that, so the table's own fit width (read next, by
429/// the caller) is the aligned one. A growing column sits at its floor
430/// here, as a grow child of any row does.
431fn table_fit(tree: &mut Tree, i: u32) {
432    let mut cols = table_columns(tree, i, None);
433    for col in &mut cols {
434        col.w = col.clamp(col.fit);
435    }
436    table_apply(tree, i, &cols, true);
437}
438
439/// Pass 2's table step, at the table node, before its rows: the columns
440/// resolved once against the widest row's content — `Percent` columns
441/// take their cut, `Fixed` and `Fit` ones sit at their fit, `Grow`
442/// columns split what is left in the freeze loop `distribute_run` runs
443/// (a clamped column is frozen and the rest re-share) — and, when the
444/// fits alone overflow the row and the table does not scroll x, the
445/// `Fit` columns compressed toward their floors largest first, as
446/// `shrink_axis` compresses a row's children. Then written into every
447/// cell, so the rows have nothing left to distribute.
448fn table_resolve(tree: &mut Tree, i: u32) {
449    // How many columns: the most cells any row has, as `table_columns`
450    // counts them — known before the columns are read, since they read
451    // the clamps resolved just below.
452    let n = tree
453        .children(i)
454        .filter(|&row| is_table_row(tree, row))
455        .map(|row| tree.children(row).filter(|&c| !is_float(tree, c)).count())
456        .max()
457        .unwrap_or(0);
458    if n == 0 {
459        return;
460    }
461    // The widest row's content: what the columns are laid across. Rows
462    // are usually `grow`, and then this is the table's content box less
463    // the row's own padding and gaps.
464    let mut avail = 0.0f32;
465    for row in tree.children(i) {
466        if !is_table_row(tree, row) {
467            continue;
468        }
469        let spec = tree.specs[row as usize].layout;
470        let chrome = spec.padding.x() + spec.gap * (n as f32 - 1.0);
471        avail = avail.max(tree.size[row as usize].w - chrome);
472    }
473    let avail = avail.max(0.0);
474    // A cell's size-expression clamps, against the room its column's
475    // width takes its cut of: a `Percent` or calc width resolves against
476    // `avail` below, and in a plain row a child's width and its clamps
477    // share one room, so a cell's do too — `maxWidth "50%"` and `width
478    // "50%"` beside it are one number. They once took the row's content
479    // with the gaps in, and the clamp came out wider than the width. The
480    // rows are wide by now, and the columns read the clamps next. A float
481    // in a row is no cell: its clamps are its anchor's, in `distribute_axis`.
482    if tree.any_calc_bound {
483        for row in tree.children(i).collect::<Vec<_>>() {
484            if !is_table_row(tree, row) {
485                continue;
486            }
487            for cell in tree.children(row).collect::<Vec<_>>() {
488                if !is_float(tree, cell) {
489                    resolve_bounds(tree, cell, AxisSel::Width, avail);
490                }
491            }
492        }
493    }
494    let mut cols = table_columns(tree, i, Some(avail));
495    debug_assert_eq!(cols.len(), n);
496    let mut used = 0.0f32;
497    let mut grow_total = 0.0f32;
498    for col in &mut cols {
499        if col.grow > 0.0 {
500            grow_total += col.grow;
501            col.w = col.clamp(0.0);
502        } else if col.pct > 0.0 || col.calc.is_some() {
503            let calc = col.calc.unwrap_or(0.0);
504            col.w = col.clamp((avail * col.pct).max(calc));
505            used += col.w;
506        } else {
507            col.w = col.clamp(col.fit);
508            used += col.w;
509        }
510    }
511    if grow_total > 0.0 {
512        let mut frozen = vec![false; cols.len()];
513        loop {
514            let remain = (avail - used).max(0.0);
515            let mut froze = false;
516            for (j, col) in cols.iter_mut().enumerate() {
517                if col.grow <= 0.0 || frozen[j] {
518                    continue;
519                }
520                let share = remain * col.grow / grow_total;
521                col.w = col.clamp(share);
522                if (col.w - share).abs() > 0.01 {
523                    frozen[j] = true;
524                    used += col.w;
525                    grow_total -= col.grow;
526                    froze = true;
527                }
528            }
529            if !froze || grow_total <= 0.0 {
530                break;
531            }
532        }
533    }
534    // The shrink: the fit columns pay the overflow, largest first, down
535    // to their floors — never a fixed, a percent or a growing one.
536    let total: f32 = cols.iter().map(|c| c.w).sum();
537    let mut deficit = total - avail;
538    if deficit > 0.5 && !tree.specs[i as usize].layout.scroll_x {
539        let shrinkable = |c: &Col| !c.fixed && c.grow <= 0.0 && c.pct <= 0.0 && c.calc.is_none();
540        let mut guard = 0;
541        while deficit > 0.5 && guard < 128 {
542            guard += 1;
543            let mut largest = f32::NEG_INFINITY;
544            let mut second = 0.0f32;
545            let mut count = 0u32;
546            for c in cols.iter().filter(|c| shrinkable(c) && c.w > c.min + 0.01) {
547                if c.w > largest + 0.01 {
548                    second = if largest.is_finite() {
549                        largest.max(second)
550                    } else {
551                        second
552                    };
553                    largest = c.w;
554                    count = 1;
555                } else if c.w > largest - 0.01 {
556                    count += 1;
557                } else if c.w > second {
558                    second = c.w;
559                }
560            }
561            if count == 0 {
562                break;
563            }
564            let target = (largest - deficit / count as f32).max(second).max(0.0);
565            let mut shrunk_any = false;
566            for c in cols.iter_mut().filter(|c| shrinkable(c)) {
567                if c.w > largest - 0.01 {
568                    let new = target.max(c.min);
569                    if new < c.w {
570                        deficit -= c.w - new;
571                        c.w = new;
572                        shrunk_any = true;
573                    }
574                }
575            }
576            if !shrunk_any {
577                break;
578            }
579        }
580    }
581    table_apply(tree, i, &cols, false);
582}
583
584/// The first in-flow child of `i` (`NIL` when it has none).
585fn first_in_flow(tree: &Tree, i: u32) -> u32 {
586    let mut c = tree.first_child[i as usize];
587    while c != NIL && is_float(tree, c) {
588        c = tree.next_sibling[c as usize];
589    }
590    c
591}
592
593/// The first in-flow child after `c`'s wrap line (`NIL` at the end). Line
594/// numbers only ever go up in child order, so one line's in-flow children
595/// are a contiguous sibling run and `[c, line_end(c))` is the whole line.
596fn line_end(tree: &Tree, c: u32) -> u32 {
597    let l = tree.line[c as usize];
598    let mut n = tree.next_sibling[c as usize];
599    while n != NIL && (is_float(tree, n) || tree.line[n as usize] == l) {
600        n = tree.next_sibling[n as usize];
601    }
602    n
603}
604
605/// Extents of the line `[c, end)`, in one walk: `(main, cross)`.
606///
607/// Main is its children plus the gaps between them. Cross is its tallest
608/// child that has a cross size of its own — Grow and Percent children are
609/// skipped by *sizing*, not by their current number, because they are
610/// sized against the extent this returns and reading them back would make
611/// a line's height depend on whether pass 4 had run yet. Skipping them
612/// measures the same thing in every pass, and matches an unwrapped row,
613/// where a grow child contributes nothing to a fit height either.
614/// Wrapping is rows-only, so main is width and cross is height.
615fn line_extents(tree: &Tree, c: u32, end: u32, gap: f32) -> (f32, f32) {
616    let baseline = tree.any_baseline && c != NIL && baseline_row(tree, tree.parent[c as usize]);
617    let mut main = 0.0f32;
618    let mut cross = 0.0f32;
619    let mut n = 0u32;
620    let mut k = c;
621    while k != end && k != NIL {
622        if !is_float(tree, k) {
623            let size = tree.size[k as usize];
624            main += size.w;
625            if !matches!(
626                child_sizing(tree, k, AxisSel::Height),
627                Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_)
628            ) {
629                cross = cross.max(size.h);
630            }
631            n += 1;
632        }
633        k = tree.next_sibling[k as usize];
634    }
635    if n > 1 {
636        main += gap * (n - 1) as f32;
637    }
638    if baseline {
639        // Aligned on one line, the children reach from the highest top
640        // to the lowest bottom, which is more than the tallest of them.
641        let (above, below, _) = line_baseline(tree, c, end);
642        cross = cross.max(above + below);
643    }
644    (main, cross)
645}
646
647/// A wrapping row's lines in one walk: how many, their stacked cross extent
648/// with the cross gaps between them, and the widest line's main extent.
649fn wrap_measure(tree: &Tree, i: u32) -> (u32, f32, f32) {
650    let spec = tree.specs[i as usize].layout;
651    let mut lines = 0u32;
652    let mut stacked = 0.0f32;
653    let mut widest = 0.0f32;
654    let mut c = first_in_flow(tree, i);
655    while c != NIL {
656        let end = line_end(tree, c);
657        let (main, cross) = line_extents(tree, c, end, spec.gap);
658        stacked += cross;
659        widest = widest.max(main);
660        lines += 1;
661        c = end;
662    }
663    if lines > 1 {
664        stacked += spec.cross_gap * (lines - 1) as f32;
665    }
666    (lines, stacked, widest)
667}
668
669/// Cross space every line gains beyond its content extent: the container's
670/// leftover, shared equally — CSS's `align-content: stretch`, and the
671/// reason a wrapping row that happens to fit on one line lays out exactly
672/// like an unwrapped one. Zero for a Fit height, whose lines already fill
673/// it by construction, and zero when the lines overflow.
674fn line_stretch(lines: u32, stacked: f32, cross_content: f32) -> f32 {
675    if lines == 0 {
676        0.0
677    } else {
678        (cross_content - stacked).max(0.0) / lines as f32
679    }
680}
681
682/// Greedy main-axis line breaking, in child order: a child that no longer
683/// fits the content box starts the next line, and a child too wide to fit
684/// on its own gets a line to itself (and is then the shrink pass's
685/// problem). Grow children break on whatever pass 1 left them — zero, or
686/// their `min_w` — since a grow child has no size of its own until a line
687/// is chosen for it; it then fills what is left of the line it landed on.
688fn break_lines(tree: &mut Tree, i: u32, content: f32, gap: f32) {
689    let mut line = 0u32;
690    let mut used = 0.0f32;
691    let mut n = 0u32;
692    let mut c = tree.first_child[i as usize];
693    while c != NIL {
694        if !is_float(tree, c) {
695            let base = tree.size[c as usize].w;
696            let needed = if n > 0 { gap + base } else { base };
697            if n > 0 && used + needed > content + 0.01 {
698                line += 1;
699                used = base;
700                n = 1;
701            } else {
702                used += needed;
703                n += 1;
704            }
705            tree.line[c as usize] = line;
706        }
707        c = tree.next_sibling[c as usize];
708    }
709}
710
711/// What the solver asks about text, editors, images and cell grids: the
712/// sizes it cannot compute from specs alone. The core implements it over
713/// its text system; a test may implement it with fixed numbers.
714pub trait TextMeasure {
715    /// Unwrapped preferred size.
716    fn intrinsic(&mut self, id: crate::tree::TextId) -> Size;
717    /// Size when wrapped to `max_w` logical pixels.
718    fn wrapped(&mut self, id: crate::tree::TextId, max_w: f32) -> Size;
719    /// The widest stretch of the text no break falls inside, logical px
720    /// — CSS's min-content: the longest word, a whole unwrapped line.
721    /// Asked only on a frame with a share of the room; 0, the default, is
722    /// a text that rewraps to any width.
723    fn min_content(&mut self, _id: crate::tree::TextId) -> f32 {
724        0.0
725    }
726    /// Unwrapped content size of an editable text node.
727    fn edit_intrinsic(&mut self, _key: crate::key::Key) -> Size {
728        Size::ZERO
729    }
730    /// Content size of an editable text node wrapped to `max_w`.
731    fn edit_wrapped(&mut self, _key: crate::key::Key, _max_w: f32) -> Size {
732        Size::ZERO
733    }
734    /// The first line's baseline of a text last wrapped by `wrapped`,
735    /// logical px below its top: what `crossAlign: baseline` lines up.
736    /// Asked only on a frame with a baseline row. `NaN` = not known, and
737    /// the text aligns by its bottom edge.
738    fn baseline(&mut self, _id: crate::tree::TextId) -> f32 {
739        f32::NAN
740    }
741    /// An editor's first baseline, below the top of its text (its box's
742    /// padding is added by the caller). `NaN` = not known.
743    fn edit_baseline(&mut self, _key: crate::key::Key) -> f32 {
744        f32::NAN
745    }
746    /// Pixel dimensions of a registered image (ZERO when unknown).
747    fn image_size(&mut self, _id: crate::resources::ImageId) -> Size {
748        Size::ZERO
749    }
750    /// The laid-out size of a cell grid (`rows × cols` cells).
751    fn cells_size(&mut self, _id: crate::cells::CellsId) -> Size {
752        Size::ZERO
753    }
754}
755
756/// Lays `tree` out into `viewport` logical px: sizes and positions every
757/// node, breaks wrapping rows into lines, resolves tables and floats, and
758/// clamps the scroll offsets in `scroll`. `Core::finish_frame` calls it
759/// once per frame; it is public for a custom runner or a test that drives
760/// the solver directly.
761pub fn compute(
762    tree: &mut Tree,
763    text: &mut dyn TextMeasure,
764    scroll: &mut ScrollStore,
765    viewport: Size,
766    // Physical pixels per logical one: `positions` snaps a scroll offset to them.
767    scale: f32,
768) {
769    if tree.is_empty() {
770        return;
771    }
772    // A `Min::FIT` floor is written back into the spec as the number it
773    // resolved to (`fit_widths`, `fit_heights`), and a size-expression
774    // clamp as the px it came to against its room (`resolve_bounds`);
775    // a node-anchored float is laid out again in the sixth pass, and
776    // would read what the first run left as declared numbers. For a fit
777    // floor that is one measured before the float had a width — a
778    // paragraph folded into a column of one word. For a calc clamp it is
779    // one resolved against a room of 0: the first run meets such a float
780    // before its anchor is placed and gives it no room, so a `maxWidth
781    // "50%"` on it — or a `maxWidth "100%"` on a child of it, resolved
782    // against the float's own 0 — came out 0 and the sixth pass, finding
783    // no expression left to resolve, kept it. Every clamp
784    // in those subtrees that layout writes over is remembered here, as
785    // declared, and put back before the re-run.
786    let declared = if tree.any_node_float {
787        declared_clamps(tree)
788    } else {
789        Vec::new()
790    };
791    fit_widths(tree, text, 0..tree.len());
792    grow_widths(tree, text, viewport);
793    fit_heights(tree, text, 0..tree.len());
794    grow_heights(tree, text, viewport);
795    positions(tree, scroll, viewport, scale, 0..tree.len());
796    if tree.any_node_float {
797        anchored(tree, text, scroll, viewport, scale, &declared);
798    }
799}
800
801/// The four clamps of a node as its spec declared them, for one layout
802/// writes over (a fit floor, a size expression): `(node, min_w, max_w,
803/// min_h, max_h)`.
804type Declared = (usize, Min, f32, Min, f32);
805
806/// The clamps of every node inside a node-anchored float's subtree that
807/// layout resolves in place — the ones the sixth pass must see again as
808/// declared. Read off the raw numbers, a negative
809/// being what both forms are, so no node pays a table lookup.
810fn declared_clamps(tree: &Tree) -> Vec<Declared> {
811    let mut out = Vec::new();
812    for (c, end, _) in node_floats(tree) {
813        for i in c..end {
814            let l = &tree.specs[i].layout;
815            if l.min_w.deferred() || l.min_h.deferred() || l.max_w < 0.0 || l.max_h < 0.0 {
816                out.push((i, l.min_w, l.max_w, l.min_h, l.max_h));
817            }
818        }
819    }
820    out
821}
822
823/// The sixth pass: every float anchored to a node by key, laid out again
824/// against that node's final rect. A subtree is a contiguous index range
825/// in preorder — every descendant's parent index is at or after the
826/// root's — so the five passes run over the range alone: the float's own
827/// size is resolved against the anchor the way a `Parent` float's is
828/// against its parent, and the rest is what the passes always do. An
829/// anchor the frame does not have leaves the float at zero size, which
830/// paints nothing and takes no input; a caller that built the content
831/// without its body has nothing to show it in.
832fn anchored(
833    tree: &mut Tree,
834    text: &mut dyn TextMeasure,
835    scroll: &mut ScrollStore,
836    viewport: Size,
837    scale: f32,
838    declared: &[Declared],
839) {
840    for (c, end, key) in node_floats(tree) {
841        let Some(a) = tree.index_of(key) else {
842            tree.size[c] = Size::default();
843            continue;
844        };
845        let anchor = Rect::from_pos_size(tree.pos[a], tree.size[a]);
846        // The fit floors and size-expression clamps declared in this
847        // subtree, as declared again.
848        for &(i, min_w, max_w, min_h, max_h) in
849            declared.iter().filter(|(i, ..)| (c..end).contains(i))
850        {
851            let l = &mut tree.specs[i].layout;
852            (l.min_w, l.max_w, l.min_h, l.max_h) = (min_w, max_w, min_h, max_h);
853        }
854        // The root's spec is read *after* each fit pass, which is where
855        // a `Min::FIT` floor of its own — just declared again above —
856        // resolves to its number; a copy taken before it clamped with a
857        // floor of 0 and the float lost its own floor.
858        fit_widths(tree, text, c..end);
859        if tree.any_calc_bound {
860            resolve_bounds(tree, c as u32, AxisSel::Width, anchor.w);
861        }
862        let spec = tree.specs[c].layout;
863        tree.size[c].w = spec.clamp_w(match spec.width {
864            Sizing::Grow(_) => anchor.w,
865            s => of_room(s, anchor.w).unwrap_or(tree.size[c].w),
866        });
867        for i in c..end {
868            distribute_axis(tree, text, i as u32, AxisSel::Width, viewport);
869        }
870        fit_heights(tree, text, c..end);
871        if tree.any_calc_bound {
872            resolve_bounds(tree, c as u32, AxisSel::Height, anchor.h);
873        }
874        let spec = tree.specs[c].layout;
875        tree.size[c].h = spec.clamp_h(match spec.height {
876            Sizing::Grow(_) => anchor.h,
877            s => of_room(s, anchor.h).unwrap_or(tree.size[c].h),
878        });
879        for i in c..end {
880            distribute_axis(tree, text, i as u32, AxisSel::Height, viewport);
881        }
882        place_anchored(tree, c, anchor);
883        positions(tree, scroll, viewport, scale, c..end);
884    }
885}
886
887/// `(root, end, anchor key)` of every node-anchored float, in tree
888/// order, each subtree the index range `root..end`.
889fn node_floats(tree: &Tree) -> Vec<(usize, usize, crate::key::Key)> {
890    let mut out = Vec::new();
891    let mut c = 0usize;
892    while c < tree.len() {
893        if let Some(crate::spec::FloatConfig {
894            anchor: FloatAnchor::Node(key),
895            ..
896        }) = tree.specs[c].layout.float
897        {
898            let mut end = c + 1;
899            while end < tree.len() && (tree.parent[end] as usize) >= c {
900                end += 1;
901            }
902            out.push((c, end, key));
903            c = end;
904        } else {
905            c += 1;
906        }
907    }
908    out
909}
910
911/// Attaches the sized float `c` to `anchor`, its config's points.
912fn place_anchored(tree: &mut Tree, c: usize, anchor: Rect) {
913    let Some(cfg) = tree.specs[c].layout.float else {
914        return;
915    };
916    let cs = tree.size[c];
917    tree.pos[c] = Vec2::new(
918        attach(
919            anchor.x,
920            anchor.w,
921            cs.w,
922            cfg.anchor_point.0,
923            cfg.self_point.0,
924            cfg.offset.x,
925        ),
926        attach(
927            anchor.y,
928            anchor.h,
929            cs.h,
930            cfg.anchor_point.1,
931            cfg.self_point.1,
932            cfg.offset.y,
933        ),
934    );
935}
936
937/// Pass 5 again, with the sizes kept: what a scroll that moved a node
938/// re-runs. The node-anchored floats follow their anchors.
939pub(crate) fn reposition(tree: &mut Tree, scroll: &mut ScrollStore, viewport: Size, scale: f32) {
940    positions(tree, scroll, viewport, scale, 0..tree.len());
941    if tree.any_node_float {
942        for (c, end, key) in node_floats(tree) {
943            if let Some(a) = tree.index_of(key) {
944                let anchor = Rect::from_pos_size(tree.pos[a], tree.size[a]);
945                place_anchored(tree, c, anchor);
946                positions(tree, scroll, viewport, scale, c..end);
947            }
948        }
949    }
950}
951
952/// The fit width of `i`, a non-text node: what its content wants on its
953/// own. Read for a `Fit` width, and for a `Min::FIT` floor under any other
954/// sizing — a `Grow` tab that must never be narrower than its label.
955#[inline(always)]
956fn fit_width(tree: &Tree, i: usize, text: &mut dyn TextMeasure) -> f32 {
957    let spec = &tree.specs[i].layout;
958    match tree.content[i] {
959        NodeContent::Edit(key) => text.edit_intrinsic(key).w + spec.padding.x(),
960        // Image pixels as logical px (1:1 at scale 1).
961        NodeContent::Image(id, _) => text.image_size(id).w,
962        NodeContent::Cells(id) => text.cells_size(id).w + spec.padding.x(),
963        _ => {
964            let mut w = 0.0f32;
965            let mut n = 0u32;
966            for c in tree.children(i as u32) {
967                if is_float(tree, c) {
968                    continue;
969                }
970                let cw = tree.size[c as usize].w;
971                if spec.dir == Dir::Row {
972                    w += cw;
973                } else {
974                    w = w.max(cw);
975                }
976                n += 1;
977            }
978            if spec.dir == Dir::Row && n > 1 {
979                w += spec.gap * (n - 1) as f32;
980            }
981            w + spec.padding.x()
982        }
983    }
984}
985
986fn fit_widths(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
987    for i in range.rev() {
988        if let NodeContent::Text(tid) = tree.content[i] {
989            tree.size[i].w = text.intrinsic(tid).w;
990            continue;
991        }
992        // A table's rows and cells are fitted by now (children first):
993        // align them, so the fit read next is the aligned one.
994        if tree.any_table && tree.specs[i].layout.is_table() {
995            table_fit(tree, i as u32);
996        }
997        let width = tree.specs[i].layout.width;
998        let min_fit = tree.specs[i].layout.min_w.is_fit();
999        // Measured once for both uses: the Fit sizing, and the Fit floor.
1000        let fit = if min_fit || width == Sizing::Fit {
1001            fit_width(tree, i, text)
1002        } else {
1003            0.0
1004        };
1005        // A `Min::FIT` floor resolves here, once, to the number every later
1006        // clamp on this axis reads — written back into the spec so
1007        // `set_axis_clamped` in the grow pass and `break_lines` need no
1008        // second form.
1009        if min_fit {
1010            tree.specs[i].layout.min_w = Min::px(fit);
1011        }
1012        let spec = &tree.specs[i].layout;
1013        tree.size[i].w = spec.clamp_w(match width {
1014            Sizing::Fixed(px) => px,
1015            // Resolved against the parent later; contributes nothing to fit
1016            // beyond its own floor.
1017            Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1018            Sizing::Fit => spec.aspect_width().unwrap_or(fit),
1019        });
1020    }
1021}
1022
1023/// Node `i`'s min-content on `axis` — CSS's: the widest (or tallest)
1024/// thing in it that cannot wrap, its padding on top — asked by a shrink
1025/// that gives CSS's way, for the children of a run that overflowed and
1026/// no other node, so a frame whose runs all fit measures nothing.
1027/// Across, a text is its longest word
1028/// (`TextMeasure::min_content`); down, it is its lines at the width it
1029/// has, which the fit pass of that axis measured, as an image's and a
1030/// grid's size is theirs. A box scrolling that axis, or clipping, needs
1031/// nothing of what it holds — CSS's automatic minimum of a box whose
1032/// overflow is not visible. Asked too by the shrink of a column of fit
1033/// children, down, for its children alone.
1034fn min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1035    let spec = &tree.specs[i].layout;
1036    let (pad, scrolls) = match axis {
1037        AxisSel::Width => (spec.padding.x(), spec.scroll_x),
1038        AxisSel::Height => (spec.padding.y(), spec.scroll_y),
1039    };
1040    let own = match (tree.content[i], axis) {
1041        (NodeContent::Text(tid), AxisSel::Width) => return text.min_content(tid),
1042        (
1043            NodeContent::Text(_) | NodeContent::Image(..) | NodeContent::Cells(_),
1044            AxisSel::Height,
1045        ) => {
1046            return tree.size[i].h;
1047        }
1048        (NodeContent::Edit(_), AxisSel::Width) => 0.0,
1049        (NodeContent::Edit(key), AxisSel::Height) => {
1050            text.edit_wrapped(key, (tree.size[i].w - spec.padding.x()).max(0.0))
1051                .h
1052        }
1053        (NodeContent::Image(id, _), AxisSel::Width) => text.image_size(id).w,
1054        (NodeContent::Cells(id), AxisSel::Width) => text.cells_size(id).w,
1055        _ if scrolls || spec.clip => 0.0,
1056        _ => children_min_content(tree, text, i, axis),
1057    };
1058    own + pad
1059}
1060
1061/// What node `i`'s in-flow children need on `axis` at the least — CSS's
1062/// min-content of a box, less its padding: each child's
1063/// contribution is its fixed size or its own min-content, held to its
1064/// clamps, and the contributions add up with the gaps along a main axis
1065/// that does not wrap, where they cannot be put side by side any other
1066/// way, and are the widest across it or when each may take a line of its
1067/// own. Down a wrapping row, whose lines the width pass has broken, they
1068/// are each line's tallest stacked with the cross gaps, as the row's fit
1069/// height is.
1070fn children_min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1071    let spec = &tree.specs[i].layout;
1072    let main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1073    let adds = main && !(spec.wrap && axis == AxisSel::Width);
1074    let lines = axis == AxisSel::Height && wraps(tree, i as u32);
1075    let (mut total, mut n) = (0.0f32, 0u32);
1076    // The line being read down a wrapping row, and its tallest so far.
1077    let (mut line, mut tallest, mut stacked) = (None, 0.0f32, 0u32);
1078    let mut c = tree.first_child[i];
1079    while c != NIL {
1080        if !is_float(tree, c) {
1081            let l = &tree.specs[c as usize].layout;
1082            let (sizing, min, max) = match axis {
1083                AxisSel::Width => (l.width, l.min_w, l.max_w_px()),
1084                AxisSel::Height => (l.height, l.min_h, l.max_h_px()),
1085            };
1086            let own = match sizing {
1087                Sizing::Fixed(px) => px,
1088                _ => min_content(tree, text, c as usize, axis),
1089            };
1090            let v = own.min(max).max(min.resolved());
1091            if lines {
1092                let l = tree.line[c as usize];
1093                if line != Some(l) {
1094                    total += tallest;
1095                    (line, tallest) = (Some(l), 0.0);
1096                    stacked += 1;
1097                }
1098                tallest = tallest.max(v);
1099            } else if adds {
1100                total += v;
1101            } else {
1102                total = total.max(v);
1103            }
1104            n += 1;
1105        }
1106        c = tree.next_sibling[c as usize];
1107    }
1108    if lines {
1109        total += tallest + spec.cross_gap * stacked.saturating_sub(1) as f32;
1110    } else if adds && n > 1 {
1111        total += spec.gap * (n - 1) as f32;
1112    }
1113    total
1114}
1115
1116/// The fit height of `i`, a non-text node, against its final width:
1117/// `fit_width`'s mirror, read for a `Fit` height and for a `Min::FIT`
1118/// floor. An editor's is its wrapped extent, which the caller has already
1119/// measured (emission and input must share one line layout, whatever the
1120/// sizing) and passes in as `edit`.
1121#[inline(always)]
1122fn fit_height(tree: &Tree, i: usize, text: &mut dyn TextMeasure, edit: Size) -> f32 {
1123    let spec = &tree.specs[i].layout;
1124    match tree.content[i] {
1125        NodeContent::Edit(_) => edit.h + spec.padding.y(),
1126        NodeContent::Cells(id) => text.cells_size(id).h + spec.padding.y(),
1127        // Width is final by now: a Fit height preserves the aspect. A
1128        // cell's width is its column's, which the image did not ask for:
1129        // its height is its aspect at the width its own sizing gave it —
1130        // a 16 px icon in a 200 px column is a 200 x 16 box, not a 200 x
1131        // 200 one — and how the pixels meet the wider box
1132        // is the image's `fit` row.
1133        NodeContent::Image(id, _) => {
1134            let intrinsic = text.image_size(id);
1135            if intrinsic.w <= 0.0 {
1136                return 0.0;
1137            }
1138            let w = if is_table_cell(tree, i as u32) {
1139                match spec.width {
1140                    Sizing::Fixed(px) => spec.clamp_w(px),
1141                    Sizing::Fit => spec.clamp_w(intrinsic.w),
1142                    Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => tree.size[i].w,
1143                }
1144            } else {
1145                tree.size[i].w
1146            };
1147            intrinsic.h * w / intrinsic.w
1148        }
1149        // A wrapping row is as tall as its lines stacked: the lines were
1150        // chosen in pass 2, against a width that is already final.
1151        _ if wraps(tree, i as u32) => wrap_measure(tree, i as u32).1 + spec.padding.y(),
1152        // One line, as tall as its children reach once their baselines
1153        // line up: `line_extents` measures exactly that.
1154        _ if tree.any_baseline && baseline_row(tree, i as u32) => {
1155            let f = first_in_flow(tree, i as u32);
1156            line_extents(tree, f, NIL, spec.gap).1 + spec.padding.y()
1157        }
1158        _ => {
1159            let mut h = 0.0f32;
1160            let mut n = 0u32;
1161            for c in tree.children(i as u32) {
1162                if is_float(tree, c) {
1163                    continue;
1164                }
1165                let ch = tree.size[c as usize].h;
1166                if spec.dir == Dir::Column {
1167                    h += ch;
1168                } else {
1169                    h = h.max(ch);
1170                }
1171                n += 1;
1172            }
1173            if spec.dir == Dir::Column && n > 1 {
1174                h += spec.gap * (n - 1) as f32;
1175            }
1176            h + spec.padding.y()
1177        }
1178    }
1179}
1180
1181fn fit_heights(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
1182    if tree.any_baseline {
1183        // One per node, `NaN` where no text measured one; kept only on a
1184        // frame that has a baseline row.
1185        let n = tree.len();
1186        tree.baseline.resize(n, f32::NAN);
1187    }
1188    for i in range.rev() {
1189        if let NodeContent::Text(tid) = tree.content[i] {
1190            // Width is final by now: wrap to it.
1191            let wrapped = text.wrapped(tid, tree.size[i].w.max(0.0));
1192            // The wrapped measurement is authoritative for both axes (a long
1193            // unbroken word may still exceed the clamp; report it truthfully)
1194            // — except that a text which is a table's cell keeps the column
1195            // width pass 2 gave it, or the cells after it would close up.
1196            if is_table_cell(tree, i as u32) {
1197                tree.size[i].h = wrapped.h;
1198                tree.size[i].w = tree.size[i].w.max(wrapped.w);
1199            } else {
1200                tree.size[i] = wrapped;
1201            }
1202            if tree.any_baseline {
1203                tree.baseline[i] = text.baseline(tid);
1204            }
1205            continue;
1206        }
1207        // Always wrap an editor to the final content width so emission and
1208        // input hit the same line layout, whatever the height sizing is.
1209        let edit = if let NodeContent::Edit(key) = tree.content[i] {
1210            let inner = (tree.size[i].w - tree.specs[i].layout.padding.x()).max(0.0);
1211            let wrapped = text.edit_wrapped(key, inner);
1212            if tree.any_baseline {
1213                tree.baseline[i] = tree.specs[i].layout.padding.t + text.edit_baseline(key);
1214            }
1215            wrapped
1216        } else {
1217            Size::default()
1218        };
1219        let height = tree.specs[i].layout.height;
1220        let min_fit = tree.specs[i].layout.min_h.is_fit();
1221        // Same resolution as `fit_widths`: measured once, the floor written
1222        // back as a number.
1223        let fit = if min_fit || height == Sizing::Fit {
1224            fit_height(tree, i, text, edit)
1225        } else {
1226            0.0
1227        };
1228        if min_fit {
1229            tree.specs[i].layout.min_h = Min::px(fit);
1230        }
1231        let spec = &tree.specs[i].layout;
1232        tree.size[i].h = spec.clamp_h(match height {
1233            Sizing::Fixed(px) => px,
1234            Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1235            // Width is final by now: a declared ratio reads it, as an
1236            // image's pixels do below it.
1237            Sizing::Fit if spec.aspect_height() => tree.size[i].w / spec.aspect,
1238            Sizing::Fit => fit,
1239        });
1240    }
1241}
1242
1243fn grow_widths(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1244    for i in 0..tree.len() {
1245        if tree.parent[i] == NIL {
1246            if tree.any_calc_bound {
1247                resolve_bounds(tree, i as u32, AxisSel::Width, viewport.w);
1248            }
1249            let spec = tree.specs[i].layout;
1250            tree.size[i].w = spec.clamp_w(resolve_root(spec.width, tree.size[i].w, viewport.w));
1251        }
1252        distribute_axis(tree, text, i as u32, AxisSel::Width, viewport);
1253    }
1254}
1255
1256fn grow_heights(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1257    for i in 0..tree.len() {
1258        if tree.parent[i] == NIL {
1259            if tree.any_calc_bound {
1260                resolve_bounds(tree, i as u32, AxisSel::Height, viewport.h);
1261            }
1262            let spec = tree.specs[i].layout;
1263            tree.size[i].h = spec.clamp_h(resolve_root(spec.height, tree.size[i].h, viewport.h));
1264        }
1265        distribute_axis(tree, text, i as u32, AxisSel::Height, viewport);
1266    }
1267}
1268
1269fn resolve_root(sizing: Sizing, fitted: f32, viewport: f32) -> f32 {
1270    match sizing {
1271        Sizing::Grow(_) => viewport,
1272        Sizing::Percent(p) => viewport * p,
1273        Sizing::Calc(c) => c.resolve(viewport),
1274        Sizing::Fixed(px) => px,
1275        Sizing::Fit => fitted,
1276    }
1277}
1278
1279#[derive(Clone, Copy, PartialEq)]
1280enum AxisSel {
1281    Width,
1282    Height,
1283}
1284
1285/// Resolves Grow/Percent children of `i` along the given axis, assuming `i`'s
1286/// own size on that axis is final.
1287fn distribute_axis(
1288    tree: &mut Tree,
1289    text: &mut dyn TextMeasure,
1290    i: u32,
1291    axis: AxisSel,
1292    viewport: Size,
1293) {
1294    let spec = tree.specs[i as usize].layout;
1295    let (own, pad) = match axis {
1296        AxisSel::Width => (tree.size[i as usize].w, spec.padding.x()),
1297        AxisSel::Height => (tree.size[i as usize].h, spec.padding.y()),
1298    };
1299    let content = (own - pad).max(0.0);
1300    let is_main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1301
1302    // Size-expression clamps against the content box, the
1303    // room a percentage takes its cut of, before anything below is sized
1304    // by them; a child whose size is its own — fixed, fit — is clamped
1305    // again here, since its fit pass ran with no such clamp. A float's
1306    // room is its anchor, below.
1307    if tree.any_calc_bound {
1308        let mut c = tree.first_child[i as usize];
1309        while c != NIL {
1310            if !is_float(tree, c) && resolve_bounds(tree, c, axis, content) {
1311                let now = get_axis(tree, c, axis);
1312                set_axis_clamped(tree, c, axis, now);
1313            }
1314            c = tree.next_sibling[c as usize];
1315        }
1316    }
1317
1318    if is_main && axis == AxisSel::Width && is_table_row(tree, i) {
1319        // The cells were sized by the table (`table_resolve`), the same
1320        // in every row: nothing to grow, cut or shrink here.
1321    } else if is_main {
1322        // Percent takes its cut of the content box first: a wrap line
1323        // breaks on sizes that are already resolved against the container,
1324        // not against the line it is about to land on.
1325        // Noting on the way whether any child is such a share, the one
1326        // thing the shrink below needs to know before it picks a rule
1327        // — here, where every child is read anyway: a scan
1328        // of its own cost a column of a thousand fixed rows 2%.
1329        let mut share = false;
1330        let mut c = tree.first_child[i as usize];
1331        while c != NIL {
1332            if !is_float(tree, c)
1333                && let Some(px) = of_room(child_sizing(tree, c, axis), content)
1334            {
1335                set_axis_clamped(tree, c, axis, px);
1336                share = true;
1337            }
1338            c = tree.next_sibling[c as usize];
1339        }
1340        let scrolls = match axis {
1341            AxisSel::Width => spec.scroll_x,
1342            AxisSel::Height => spec.scroll_y,
1343        };
1344        if wraps(tree, i) {
1345            break_lines(tree, i, content, spec.gap);
1346            let mut c = first_in_flow(tree, i);
1347            while c != NIL {
1348                let end = line_end(tree, c);
1349                let line = tree.line[c as usize];
1350                // Each line is its own main-axis box: Grow splits what is
1351                // left of *its* line.
1352                let total = distribute_run(tree, c, end, axis, content, spec.gap);
1353                // Wrapping and shrinking answer the same overflow, and
1354                // wrapping answers it first: greedy breaking never puts a
1355                // second child on a line that is already full, so the only
1356                // line that can still overflow is one holding a single
1357                // child too wide for the box. Nothing can be broken off
1358                // that, which is exactly when shrinking is the remaining
1359                // answer — applied to that line alone, so a wide chip
1360                // compresses without dragging its neighbours on other
1361                // lines down with it.
1362                let deficit = total - content;
1363                if deficit > 0.5 {
1364                    shrink_axis(tree, text, i, axis, deficit, Some(line), share);
1365                }
1366                c = end;
1367            }
1368        } else {
1369            // Fixed/Fit keep their size, Percent has taken its cut, Grow
1370            // splits the rest.
1371            let total = distribute_run(
1372                tree,
1373                tree.first_child[i as usize],
1374                NIL,
1375                axis,
1376                content,
1377                spec.gap,
1378            );
1379            let deficit = total - content;
1380            if deficit > 0.5 && !scrolls {
1381                shrink_axis(tree, text, i, axis, deficit, None, share);
1382            }
1383        }
1384    } else if wraps(tree, i) {
1385        // Cross axis of a wrapping row: a Grow child fills *its line*, not
1386        // the container. The lines share the container's leftover equally
1387        // (see `line_stretch`), so with one line this is the branch below
1388        // exactly, and a row that happens not to wrap keeps its old layout.
1389        let (lines, stacked, _) = wrap_measure(tree, i);
1390        let stretch = line_stretch(lines, stacked, content);
1391        let mut c = first_in_flow(tree, i);
1392        while c != NIL {
1393            let end = line_end(tree, c);
1394            let extent = line_extents(tree, c, end, spec.gap).1 + stretch;
1395            let mut k = c;
1396            while k != end && k != NIL {
1397                if !is_float(tree, k) {
1398                    match child_sizing(tree, k, axis) {
1399                        Sizing::Grow(_) => set_axis_clamped(tree, k, axis, extent),
1400                        s => {
1401                            if let Some(px) = of_room(s, extent) {
1402                                set_axis_clamped(tree, k, axis, px);
1403                            }
1404                        }
1405                    }
1406                }
1407                k = tree.next_sibling[k as usize];
1408            }
1409            c = end;
1410        }
1411    } else {
1412        // Cross axis: Grow/Percent resolve against the content box directly.
1413        // A Fit box across a column is its content's width but no wider
1414        // than the box — CSS's `fit-content` — down to its declared min,
1415        // none undeclared, as a row's fit children give.
1416        // Pass 1 summed it bottom-up with no room in sight; held here,
1417        // before its own children are distributed, its run gives and its
1418        // text wraps. Not across a column that scrolls x, whose overflow
1419        // is the point, nor a width a ratio derives from the height; a
1420        // text has its own clamp, below; and a grid or an image is its
1421        // content's size, CSS's replaced element: a held box would not
1422        // take a column off the grid the app laid out, and would stretch
1423        // an image under a fixed height.
1424        let fits = axis == AxisSel::Width && !spec.scroll_x;
1425        let mut c = tree.first_child[i as usize];
1426        while c != NIL {
1427            if !is_float(tree, c) {
1428                match child_sizing(tree, c, axis) {
1429                    Sizing::Grow(_) => set_axis_clamped(tree, c, axis, content),
1430                    Sizing::Fit
1431                        if fits
1432                            && tree.size[c as usize].w > content
1433                            && !matches!(
1434                                tree.content[c as usize],
1435                                NodeContent::Text(_)
1436                                    | NodeContent::Cells(_)
1437                                    | NodeContent::Image(..)
1438                            )
1439                            && tree.specs[c as usize].layout.aspect_width().is_none() =>
1440                    {
1441                        set_axis_clamped(tree, c, axis, content)
1442                    }
1443                    s => {
1444                        if let Some(px) = of_room(s, content) {
1445                            set_axis_clamped(tree, c, axis, px);
1446                        }
1447                    }
1448                }
1449            }
1450            c = tree.next_sibling[c as usize];
1451        }
1452        // A table's rows are wide by now: lay its columns across them.
1453        if axis == AxisSel::Width && tree.any_table && spec.is_table() {
1454            table_resolve(tree, i);
1455        }
1456    }
1457
1458    // Floating children size Grow/Percent against their anchor. A frame
1459    // with no floats skips the walk: it would read every child's spec to
1460    // find none.
1461    let mut c = if tree.any_float {
1462        tree.first_child[i as usize]
1463    } else {
1464        NIL
1465    };
1466    while c != NIL {
1467        if let Some(cfg) = tree.specs[c as usize].layout.float {
1468            let vp = float_viewport(tree, c, viewport);
1469            let anchor_dim = match (cfg.anchor, axis) {
1470                (FloatAnchor::Parent, AxisSel::Width) => tree.size[i as usize].w,
1471                (FloatAnchor::Parent, AxisSel::Height) => tree.size[i as usize].h,
1472                (FloatAnchor::Viewport, AxisSel::Width) => vp.w,
1473                (FloatAnchor::Viewport, AxisSel::Height) => vp.h,
1474                // Sized in the sixth pass, once the anchor is placed.
1475                (FloatAnchor::Node(_), _) => 0.0,
1476            };
1477            if tree.any_calc_bound && resolve_bounds(tree, c, axis, anchor_dim) {
1478                let now = get_axis(tree, c, axis);
1479                set_axis_clamped(tree, c, axis, now);
1480            }
1481            match child_sizing(tree, c, axis) {
1482                Sizing::Grow(_) => set_axis_clamped(tree, c, axis, anchor_dim),
1483                s => {
1484                    if let Some(px) = of_room(s, anchor_dim) {
1485                        set_axis_clamped(tree, c, axis, px);
1486                    }
1487                }
1488            }
1489        }
1490        c = tree.next_sibling[c as usize];
1491    }
1492
1493    // Text children have no spec sizing; clamp their width to the content box
1494    // so fit_heights wraps them. Same walk, same gate: no text, no clamp.
1495    if axis == AxisSel::Width && tree.any_text {
1496        let mut c = tree.first_child[i as usize];
1497        while c != NIL {
1498            if matches!(tree.content[c as usize], NodeContent::Text(_))
1499                && tree.size[c as usize].w > content
1500            {
1501                tree.size[c as usize].w = content;
1502            }
1503            c = tree.next_sibling[c as usize];
1504        }
1505    }
1506}
1507
1508/// Resolves the Grow children of one main-axis run — a whole child list
1509/// (`end == NIL`) or one wrap line — into `content`, and returns what the
1510/// run ends up occupying, gaps included.
1511fn distribute_run(
1512    tree: &mut Tree,
1513    start: u32,
1514    end: u32,
1515    axis: AxisSel,
1516    content: f32,
1517    gap: f32,
1518) -> f32 {
1519    let mut used = 0.0f32;
1520    let mut grow_total = 0.0f32;
1521    let mut n = 0u32;
1522    let mut c = start;
1523    while c != end && c != NIL {
1524        if !is_float(tree, c) {
1525            match child_sizing(tree, c, axis) {
1526                Sizing::Grow(f) => grow_total += f.max(0.0),
1527                _ => used += get_axis(tree, c, axis),
1528            }
1529            n += 1;
1530        }
1531        c = tree.next_sibling[c as usize];
1532    }
1533    if n > 1 {
1534        used += gap * (n - 1) as f32;
1535    }
1536    let mut total = used;
1537    if grow_total > 0.0 {
1538        // Flexbox's freeze loop (CSS Flexible Box §9.7, step 6). A grow
1539        // child's share is `remain` split by factor; one whose own min or
1540        // max holds it off that share is a violator, min or max by the
1541        // sign of the difference. A pass sums its violations and freezes
1542        // only the violators of the dominant sign — the min ones when the
1543        // sum is positive, the max ones when negative — each at its
1544        // clamp, its size moved into `used`; the rest, the other sign's
1545        // violators included, share what is left in the next pass, until
1546        // a pass's violations sum to nothing. A max on one child is room
1547        // for its siblings, not a hole at the end of the run (a devtools
1548        // inspector capped at 300 left the node list above it short of
1549        // the panel by the same 300); and a min on one is not the cue to
1550        // freeze a capped sibling at its cap while a plain one gets
1551        // nothing (RG5: 600 over A max 100, B min 500, C — 50 / 500 / 50,
1552        // not 100 / 500 / 0). At most one pass per grow child, since each
1553        // pass past the first froze one. The per-child state is a byte in
1554        // `grow_scratch`, indexed by the child's place in the run, so a
1555        // pass over n children costs n whichever way the earlier ones
1556        // went.
1557        const FROZEN: u8 = 1;
1558        const MIN_VIOLATOR: u8 = 2;
1559        const MAX_VIOLATOR: u8 = 4;
1560        let mut scratch = std::mem::take(&mut tree.grow_scratch);
1561        scratch.clear();
1562        scratch.resize(n as usize, 0);
1563        loop {
1564            let remain = (content - used).max(0.0);
1565            let mut violation = 0.0f32;
1566            let mut unfrozen = 0.0f32;
1567            let mut k = 0usize;
1568            let mut c = start;
1569            while c != end && c != NIL {
1570                if !is_float(tree, c) {
1571                    if scratch[k] & FROZEN == 0
1572                        && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1573                    {
1574                        let share = remain * f.max(0.0) / grow_total;
1575                        set_axis_clamped(tree, c, axis, share);
1576                        let got = get_axis(tree, c, axis);
1577                        unfrozen += got;
1578                        let off = got - share;
1579                        scratch[k] = if off > 0.01 {
1580                            violation += off;
1581                            MIN_VIOLATOR
1582                        } else if off < -0.01 {
1583                            violation += off;
1584                            MAX_VIOLATOR
1585                        } else {
1586                            0
1587                        };
1588                    }
1589                    k += 1;
1590                }
1591                c = tree.next_sibling[c as usize];
1592            }
1593            if violation.abs() <= 0.01 {
1594                // Nothing to freeze: every unfrozen child is at its
1595                // share, or the clamps cancel and the run adds up.
1596                total = used + unfrozen;
1597                break;
1598            }
1599            let freeze = if violation > 0.0 {
1600                MIN_VIOLATOR
1601            } else {
1602                MAX_VIOLATOR
1603            };
1604            let mut k = 0usize;
1605            let mut c = start;
1606            while c != end && c != NIL {
1607                if !is_float(tree, c) {
1608                    if scratch[k] & freeze != 0
1609                        && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1610                    {
1611                        scratch[k] = FROZEN;
1612                        used += get_axis(tree, c, axis);
1613                        grow_total -= f.max(0.0);
1614                    }
1615                    k += 1;
1616                }
1617                c = tree.next_sibling[c as usize];
1618            }
1619            if grow_total <= 0.0 {
1620                total = used;
1621                break;
1622            }
1623        }
1624        tree.grow_scratch = scratch;
1625    }
1626    total
1627}
1628
1629/// The shrink pass: pays off `deficit` (how far in-flow children overflow
1630/// the parent's main-axis content box) by compressing Fit-sized children
1631/// and the shares of the room — `Percent` and a size expression. A share
1632/// was cut from the content box before the gaps between the
1633/// children took theirs, so two `"50%"` children and a gap overflow until
1634/// this gives. Two rules, by what the run holds:
1635///
1636/// - With a share in it, CSS's ([`shrink_as_css`]): every
1637///   shrinkable child gives in proportion to its size, down to its floor —
1638///   its declared min, or where none was declared ([`Min::AUTO`]) its
1639///   min-content ([`min_content`], measured here and only here), none for
1640///   a child that scrolls that axis — `min-width: auto`.
1641/// - Fit children alone, clay's: largest first, so equal children end up
1642///   equal, each toward its declared min — 0 across when none was
1643///   declared, and down its min-content, none for a child
1644///   that scrolls or clips that axis: `min-height: auto`, so a row is
1645///   never squeezed below the text in it.
1646///
1647/// Fixed keeps its declared size; Grow never overflows. Text shrinks in
1648/// width (it rewraps at the new width in fit_heights) but never in height.
1649/// Scroll axes skip this entirely — overflow is the point of a scroll
1650/// container.
1651///
1652/// `only_line` restricts it to one wrap line. A wrapping container reaches
1653/// here only for a line it could not break any further (a single child
1654/// wider than the box), so the compression stays on that line instead of
1655/// squeezing children that are already comfortable on other ones.
1656fn shrink_axis(
1657    tree: &mut Tree,
1658    text: &mut dyn TextMeasure,
1659    i: u32,
1660    axis: AxisSel,
1661    mut deficit: f32,
1662    only_line: Option<u32>,
1663    share: bool,
1664) {
1665    let shrinkable = |tree: &Tree, c: u32| -> Option<f32> {
1666        if is_float(tree, c)
1667            || !matches!(
1668                child_sizing(tree, c, axis),
1669                Sizing::Fit | Sizing::Percent(_) | Sizing::Calc(_)
1670            )
1671        {
1672            return None;
1673        }
1674        if only_line.is_some_and(|l| tree.line[c as usize] != l) {
1675            return None;
1676        }
1677        // Squashing text/editors vertically would clip lines, and images
1678        // would distort; width shrink rewraps (and re-aspects) instead.
1679        if axis == AxisSel::Height
1680            && matches!(
1681                tree.content[c as usize],
1682                NodeContent::Text(_) | NodeContent::Edit(_) | NodeContent::Image(..)
1683            )
1684        {
1685            return None;
1686        }
1687        // Resolved to a number by the fit pass of this axis, which ran.
1688        let spec = tree.specs[c as usize].layout;
1689        // An axis a declared ratio set would come out of shrinking at some
1690        // other ratio; like an image, it keeps its size.
1691        let derived = match axis {
1692            AxisSel::Width => spec.aspect_width().is_some(),
1693            AxisSel::Height => spec.aspect_height(),
1694        };
1695        if derived {
1696            return None;
1697        }
1698        Some(match axis {
1699            AxisSel::Width => spec.min_w.resolved(),
1700            AxisSel::Height => spec.min_h.resolved(),
1701        })
1702    };
1703
1704    // A run holding a share of the room gives as CSS's flex items do,
1705    // every shrinkable child of it with the share; a run
1706    // of fit children alone keeps clay's rule below. `share` is what the
1707    // caller saw resolving the shares, so a run without one — a column
1708    // of a thousand fixed rows overflowing its box, the common one here —
1709    // walks its children once, not once more to look (6% of such a frame
1710    // when it did).
1711    if share {
1712        let mut c = tree.first_child[i as usize];
1713        let mut items = std::mem::take(&mut tree.shrink_scratch);
1714        items.clear();
1715        let mut share = false;
1716        while c != NIL {
1717            if let Some(declared) = shrinkable(tree, c) {
1718                share |= matches!(
1719                    child_sizing(tree, c, axis),
1720                    Sizing::Percent(_) | Sizing::Calc(_)
1721                );
1722                let l = &tree.specs[c as usize].layout;
1723                let (min, scrolls) = match axis {
1724                    AxisSel::Width => (l.min_w, l.scroll_x),
1725                    AxisSel::Height => (l.min_h, l.scroll_y),
1726                };
1727                let base = get_axis(tree, c, axis);
1728                // `min-width: auto`: the content's, where nothing was
1729                // declared — no more than the child's own size, and none
1730                // for a child that scrolls its overflow, as CSS has it.
1731                let floor = match () {
1732                    _ if !min.is_auto() => declared,
1733                    _ if scrolls => 0.0,
1734                    _ => min_content(tree, text, c as usize, axis),
1735                };
1736                items.push(Give {
1737                    c,
1738                    base,
1739                    floor: floor.min(base),
1740                    size: base,
1741                    held: false,
1742                });
1743            }
1744            c = tree.next_sibling[c as usize];
1745        }
1746        if share {
1747            shrink_as_css(tree, axis, deficit, &mut items);
1748        }
1749        tree.shrink_scratch = items;
1750        if share {
1751            return;
1752        }
1753    }
1754
1755    // Largest-first, like grow in reverse: pull the biggest children down
1756    // to the second-biggest, repeat until the deficit is paid or every
1757    // shrinkable child sits at its floor. Across, the floor is the
1758    // declared min (0 undeclared), so a row of labels squeezes them into
1759    // their ellipses. Down, an undeclared one is the child's min-content
1760    // — CSS's `min-height: auto` — since nothing in a box
1761    // gives vertically but a scroller or a clip: a row squeezed below
1762    // its text painted the text over the rows under it. One that scrolls
1763    // or clips that axis goes to 0.
1764    let mut items = std::mem::take(&mut tree.shrink_scratch);
1765    items.clear();
1766    let mut c = tree.first_child[i as usize];
1767    while c != NIL {
1768        if let Some(declared) = shrinkable(tree, c) {
1769            let base = get_axis(tree, c, axis);
1770            let l = &tree.specs[c as usize].layout;
1771            let floor = match axis {
1772                AxisSel::Height if l.min_h.is_auto() => {
1773                    if l.scroll_y || l.clip {
1774                        0.0
1775                    } else {
1776                        min_content(tree, text, c as usize, axis)
1777                    }
1778                }
1779                _ => declared,
1780            };
1781            items.push(Give {
1782                c,
1783                base,
1784                floor: floor.min(base),
1785                size: base,
1786                held: false,
1787            });
1788        }
1789        c = tree.next_sibling[c as usize];
1790    }
1791    let mut guard = 0;
1792    while deficit > 0.5 && guard < 128 {
1793        guard += 1;
1794        let mut largest = f32::NEG_INFINITY;
1795        let mut second = 0.0f32;
1796        let mut count = 0u32;
1797        for g in items.iter().filter(|g| g.size > g.floor + 0.01) {
1798            let s = g.size;
1799            if s > largest + 0.01 {
1800                second = if largest.is_finite() {
1801                    largest.max(second)
1802                } else {
1803                    second
1804                };
1805                largest = s;
1806                count = 1;
1807            } else if s > largest - 0.01 {
1808                count += 1;
1809            } else if s > second {
1810                second = s;
1811            }
1812        }
1813        if count == 0 {
1814            break;
1815        }
1816        let target = (largest - deficit / count as f32).max(second).max(0.0);
1817        let mut shrunk_any = false;
1818        for g in items.iter_mut().filter(|g| g.size > largest - 0.01) {
1819            let new = target.max(g.floor);
1820            if new < g.size {
1821                deficit -= g.size - new;
1822                g.size = new;
1823                shrunk_any = true;
1824            }
1825        }
1826        if !shrunk_any {
1827            break;
1828        }
1829    }
1830    for g in items.iter() {
1831        if g.size < g.base {
1832            set_axis(tree, g.c, axis, g.size);
1833        }
1834    }
1835    tree.shrink_scratch = items;
1836}
1837
1838/// CSS's shrink of flex items (css-flexbox §9.7, `flex-shrink: 1`),
1839/// over `items`: each gives in proportion to its
1840/// size — its scaled shrink factor is its base — and one that would go
1841/// under its floor is held there and the rest share what it could not
1842/// pay, until nothing is under its floor or every child is held. What is
1843/// left unpaid overflows, as it does in CSS.
1844#[inline(never)]
1845fn shrink_as_css(tree: &mut Tree, axis: AxisSel, deficit: f32, items: &mut [Give]) {
1846    // Each round holds at least one more child or ends, so `n + 1` rounds
1847    // settle it.
1848    for _ in 0..=items.len() {
1849        let (mut paid, mut weight) = (0.0f32, 0.0f32);
1850        for g in items.iter() {
1851            if g.held {
1852                paid += g.base - g.size;
1853            } else {
1854                weight += g.base;
1855            }
1856        }
1857        let left = deficit - paid;
1858        if left <= 0.0 || weight <= 0.0 {
1859            break;
1860        }
1861        let mut under = false;
1862        for g in items.iter_mut().filter(|g| !g.held) {
1863            let want = g.base - left * g.base / weight;
1864            if want < g.floor {
1865                (g.size, g.held, under) = (g.floor, true, true);
1866            } else {
1867                g.size = want;
1868            }
1869        }
1870        if !under {
1871            break;
1872        }
1873    }
1874    for g in items.iter() {
1875        if g.size < g.base {
1876            set_axis(tree, g.c, axis, g.size);
1877        }
1878    }
1879}
1880
1881/// One child of a run giving CSS's way ([`shrink_as_css`]): its size
1882/// before, its floor, the size it is given, and whether it is held there.
1883#[derive(Clone, Copy, Debug)]
1884pub(crate) struct Give {
1885    c: u32,
1886    base: f32,
1887    floor: f32,
1888    size: f32,
1889    held: bool,
1890}
1891
1892/// A sizing that takes its size from the room once the parent's is
1893/// known — a `Percent`, or a size expression — in px of
1894/// `room`; `None` for the others.
1895#[inline]
1896fn of_room(sizing: Sizing, room: f32) -> Option<f32> {
1897    match sizing {
1898        Sizing::Percent(p) => Some(room * p),
1899        Sizing::Calc(c) => Some(c.resolve(room)),
1900        _ => None,
1901    }
1902}
1903
1904/// Writes node `c`'s size-expression clamps on `axis` as px of `room`
1905/// into the spec's, so every later clamp — and every
1906/// reader of `min_w` / `max_w` — reads a number, as a `Min::FIT` floor
1907/// is written back once its fit pass ran. Before this a calc clamp is
1908/// none, as a percentage clamp is in CSS's intrinsic sizing. `true` when
1909/// the node had one to write.
1910fn resolve_bounds(tree: &mut Tree, c: u32, axis: AxisSel, room: f32) -> bool {
1911    let l = &mut tree.specs[c as usize].layout;
1912    let (min, max) = match axis {
1913        AxisSel::Width => (&mut l.min_w, &mut l.max_w),
1914        AxisSel::Height => (&mut l.min_h, &mut l.max_h),
1915    };
1916    let mut any = false;
1917    if let Some(k) = min.as_calc() {
1918        *min = Min::px(k.resolve(room));
1919        any = true;
1920    }
1921    if let Some(k) = crate::spec::max_calc(*max) {
1922        *max = k.resolve(room);
1923        any = true;
1924    }
1925    any
1926}
1927
1928fn child_sizing(tree: &Tree, c: u32, axis: AxisSel) -> Sizing {
1929    if matches!(tree.content[c as usize], NodeContent::Text(_)) {
1930        return Sizing::Fit;
1931    }
1932    match axis {
1933        AxisSel::Width => tree.specs[c as usize].layout.width,
1934        AxisSel::Height => tree.specs[c as usize].layout.height,
1935    }
1936}
1937
1938fn get_axis(tree: &Tree, c: u32, axis: AxisSel) -> f32 {
1939    match axis {
1940        AxisSel::Width => tree.size[c as usize].w,
1941        AxisSel::Height => tree.size[c as usize].h,
1942    }
1943}
1944
1945fn set_axis(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1946    match axis {
1947        AxisSel::Width => tree.size[c as usize].w = v,
1948        AxisSel::Height => tree.size[c as usize].h = v,
1949    }
1950}
1951
1952/// set_axis clamped by the child's own min/max on that axis. Inlined,
1953/// and borrowing the spec rather than copying it: a call per child that
1954/// copied the whole layout spec cost the 10k grid 2% (C48).
1955#[inline]
1956fn set_axis_clamped(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1957    let spec = &tree.specs[c as usize].layout;
1958    let v = match axis {
1959        AxisSel::Width => spec.clamp_w(v),
1960        AxisSel::Height => spec.clamp_h(v),
1961    };
1962    set_axis(tree, c, axis, v);
1963}
1964
1965/// The "viewport" a float of `c` means: the window for the devtools' own
1966/// nodes and for the core's menu (a transient the platform's own would
1967/// not confine either — and one the panel's select opens *in* the dock,
1968/// where the host area would push it into the app), and for everyone
1969/// else the host area — the window less the devtools' dock — when one
1970/// is set (`Tree::host_area`).
1971fn float_viewport(tree: &Tree, c: u32, viewport: Size) -> Rect {
1972    use crate::tree::OriginId;
1973    let window = Rect::new(0.0, 0.0, viewport.w, viewport.h);
1974    let origin = tree.origins[c as usize];
1975    if tree.host_area.w <= 0.0 || origin == OriginId::DEVTOOLS || origin == OriginId::MENU {
1976        window
1977    } else {
1978        tree.host_area
1979    }
1980}
1981
1982/// Places `c`, which is out of flow, against its anchor.
1983fn place_float(
1984    tree: &mut Tree,
1985    cfg: crate::spec::FloatConfig,
1986    c: u32,
1987    parent: Rect,
1988    viewport: Size,
1989) {
1990    let vp = float_viewport(tree, c, viewport);
1991    let anchor = match cfg.anchor {
1992        FloatAnchor::Parent => parent,
1993        FloatAnchor::Viewport => vp,
1994        // Placed in the sixth pass, once the anchor is.
1995        FloatAnchor::Node(_) => return,
1996    };
1997    let cs = tree.size[c as usize];
1998    let mut x = attach(
1999        anchor.x,
2000        anchor.w,
2001        cs.w,
2002        cfg.anchor_point.0,
2003        cfg.self_point.0,
2004        cfg.offset.x,
2005    );
2006    let mut y = attach(
2007        anchor.y,
2008        anchor.h,
2009        cs.h,
2010        cfg.anchor_point.1,
2011        cfg.self_point.1,
2012        cfg.offset.y,
2013    );
2014    // Mirroring across the viewport itself would teleport a
2015    // cursor-anchored float to the opposite side of the window,
2016    // so viewport floats only clamp.
2017    if cfg.fit && cfg.anchor == FloatAnchor::Parent {
2018        // Mirror the attachment across the anchor per axis when
2019        // the mirrored side is less off-screen (ties keep the
2020        // declared side), then clamp the rest. Clamp order pins
2021        // the top/left edge on screen when nothing fits.
2022        let fx = attach(
2023            anchor.x,
2024            anchor.w,
2025            cs.w,
2026            mirror(cfg.anchor_point.0),
2027            mirror(cfg.self_point.0),
2028            -cfg.offset.x,
2029        );
2030        if overflow(x - vp.x, cs.w, vp.w) > overflow(fx - vp.x, cs.w, vp.w) {
2031            x = fx;
2032        }
2033        let fy = attach(
2034            anchor.y,
2035            anchor.h,
2036            cs.h,
2037            mirror(cfg.anchor_point.1),
2038            mirror(cfg.self_point.1),
2039            -cfg.offset.y,
2040        );
2041        if overflow(y - vp.y, cs.h, vp.h) > overflow(fy - vp.y, cs.h, vp.h) {
2042            y = fy;
2043        }
2044    }
2045    if cfg.fit {
2046        x = x.min(vp.x + vp.w - cs.w).max(vp.x);
2047        y = y.min(vp.y + vp.h - cs.h).max(vp.y);
2048    }
2049    tree.pos[c as usize] = Vec2::new(x, y);
2050}
2051
2052fn positions(
2053    tree: &mut Tree,
2054    scroll: &mut ScrollStore,
2055    viewport: Size,
2056    scale: f32,
2057    range: std::ops::Range<usize>,
2058) {
2059    for i in range {
2060        if tree.parent[i] == NIL {
2061            tree.pos[i] = Vec2::ZERO;
2062        }
2063        let spec = tree.specs[i].layout;
2064        let origin = tree.pos[i];
2065        let size = tree.size[i];
2066        let wrap = wraps(tree, i as u32);
2067
2068        let (main_content, cross_content, main_pad_start, cross_pad_start) = match spec.dir {
2069            Dir::Row => (
2070                size.w - spec.padding.x(),
2071                size.h - spec.padding.y(),
2072                spec.padding.l,
2073                spec.padding.t,
2074            ),
2075            Dir::Column => (
2076                size.h - spec.padding.y(),
2077                size.w - spec.padding.x(),
2078                spec.padding.t,
2079                spec.padding.l,
2080            ),
2081        };
2082
2083        // The content box the children occupy, in main/cross terms. For a
2084        // wrapping row that is the widest line by the longest stack of
2085        // lines; for everything else the one run of children.
2086        let mut stretch = 0.0f32;
2087        let (total_main, max_cross) = if wrap {
2088            let (lines, stacked, widest) = wrap_measure(tree, i as u32);
2089            stretch = line_stretch(lines, stacked, cross_content);
2090            (widest, stacked + stretch * lines as f32)
2091        } else {
2092            let mut total_main = 0.0f32;
2093            let mut max_cross = 0.0f32;
2094            let mut n = 0u32;
2095            for c in tree.children(i as u32) {
2096                if is_float(tree, c) {
2097                    continue;
2098                }
2099                let (c_main, c_cross) = match spec.dir {
2100                    Dir::Row => (tree.size[c as usize].w, tree.size[c as usize].h),
2101                    Dir::Column => (tree.size[c as usize].h, tree.size[c as usize].w),
2102                };
2103                total_main += c_main;
2104                max_cross = max_cross.max(c_cross);
2105                n += 1;
2106            }
2107            if n > 1 {
2108                total_main += spec.gap * (n - 1) as f32;
2109            }
2110            (total_main, max_cross)
2111        };
2112
2113        // Scroll containers: clamp the retained offset to this frame's
2114        // overflow and shift children by it.
2115        let mut offset = Vec2::ZERO;
2116        // Anchoring: the scroll axis is the main axis,
2117        // the container is not wrapping, and a previous layout recorded
2118        // which child was first in view and where its leading edge sat in
2119        // the content. Where that edge sits *now* is the same walk the
2120        // placement below makes, minus the offset; the difference is added
2121        // to the retained offset before it is clamped, so the child stays
2122        // where it was on screen whatever grew or shrank before it.
2123        let anchors = spec.anchor
2124            && !wrap
2125            && match spec.dir {
2126                Dir::Row => spec.scroll_x,
2127                Dir::Column => spec.scroll_y,
2128            };
2129        let free_main = (main_content - total_main).max(0.0);
2130        // The unwrapped run's spread; a wrapping row deals out each line's
2131        // own below. Counting the children is skipped for the three
2132        // alignments that need no count.
2133        let n_main = if matches!(spec.main_align, Align::Start | Align::Center | Align::End) {
2134            0
2135        } else {
2136            tree.children(i as u32)
2137                .filter(|&c| !is_float(tree, c))
2138                .count() as u32
2139        };
2140        let (lead_main, between_main) = main_spread(spec.main_align, free_main, n_main);
2141        let content_start = main_pad_start + lead_main;
2142        if anchors && let Some((anchor, was_at)) = scroll.anchor(tree.keys[i]) {
2143            let mut at = content_start;
2144            let mut c = first_in_flow(tree, i as u32);
2145            while c != NIL {
2146                if !is_float(tree, c) {
2147                    if tree.keys[c as usize] == anchor {
2148                        let delta = at - was_at;
2149                        if delta != 0.0 {
2150                            scroll.scroll_by(
2151                                tree.keys[i],
2152                                match spec.dir {
2153                                    Dir::Row => Vec2::new(delta, 0.0),
2154                                    Dir::Column => Vec2::new(0.0, delta),
2155                                },
2156                            );
2157                        }
2158                        break;
2159                    }
2160                    let c_main = match spec.dir {
2161                        Dir::Row => tree.size[c as usize].w,
2162                        Dir::Column => tree.size[c as usize].h,
2163                    };
2164                    at += c_main + spec.gap + between_main;
2165                }
2166                c = tree.next_sibling[c as usize];
2167            }
2168        }
2169        if spec.scroll_x || spec.scroll_y {
2170            let (content_w, content_h) = match spec.dir {
2171                Dir::Row => (total_main + spec.padding.x(), max_cross + spec.padding.y()),
2172                Dir::Column => (max_cross + spec.padding.x(), total_main + spec.padding.y()),
2173            };
2174            let max = Vec2::new(
2175                if spec.scroll_x {
2176                    (content_w - size.w).max(0.0)
2177                } else {
2178                    0.0
2179                },
2180                if spec.scroll_y {
2181                    (content_h - size.h).max(0.0)
2182                } else {
2183                    0.0
2184                },
2185            );
2186            tree.scroll_max[i] = max;
2187            // The one place the container's resolved box and its content
2188            // size exist together; the store keeps a copy, since the tree
2189            // holding them is cleared before the next view reads it.
2190            // The retained offset stays exact — a wheel notch of 0.3 px is
2191            // not lost, it accumulates — and what the children are *moved*
2192            // by is whole physical pixels, so a row's text does not wobble
2193            // inside the row while the list scrolls (`Vec2::snapped`).
2194            offset = scroll
2195                .resolve(
2196                    tree.keys[i],
2197                    Rect::from_pos_size(origin, size),
2198                    Size::new(content_w, content_h),
2199                    max,
2200                    tree.specs[i].transition,
2201                )
2202                .snapped(scale);
2203        }
2204        let (main_scroll, cross_scroll) = match spec.dir {
2205            Dir::Row => (offset.x, offset.y),
2206            Dir::Column => (offset.y, offset.x),
2207        };
2208        if anchors {
2209            // The anchor for the next layout: the first in-flow child whose
2210            // trailing edge is past the offset — the first one in view —
2211            // and where its leading edge sits in the content.
2212            let mut next = None;
2213            let mut at = content_start;
2214            let mut c = first_in_flow(tree, i as u32);
2215            while c != NIL {
2216                if !is_float(tree, c) {
2217                    let c_main = match spec.dir {
2218                        Dir::Row => tree.size[c as usize].w,
2219                        Dir::Column => tree.size[c as usize].h,
2220                    };
2221                    if at + c_main > main_scroll {
2222                        next = Some((tree.keys[c as usize], at));
2223                        break;
2224                    }
2225                    at += c_main + spec.gap + between_main;
2226                }
2227                c = tree.next_sibling[c as usize];
2228            }
2229            scroll.set_anchor(tree.keys[i], next);
2230        }
2231
2232        // Out of flow first, so the in-flow walk is one shape whether or
2233        // not it goes line by line. A frame with no floats skips the walk.
2234        let mut c = if tree.any_float {
2235            tree.first_child[i]
2236        } else {
2237            NIL
2238        };
2239        while c != NIL {
2240            if let Some(cfg) = tree.specs[c as usize].layout.float {
2241                place_float(tree, cfg, c, Rect::from_pos_size(origin, size), viewport);
2242            }
2243            c = tree.next_sibling[c as usize];
2244        }
2245
2246        // One line for an unwrapped container, N for a wrapping row. Main
2247        // alignment places each line's children in the content box the way
2248        // it places the single run's, and cross alignment places a child in
2249        // its own line; with one line the two compose back into the
2250        // unwrapped placement exactly.
2251        let mut cross_cursor = cross_pad_start - cross_scroll;
2252        let mut line_start = first_in_flow(tree, i as u32);
2253        while line_start != NIL {
2254            let (end, extent, run_main) = if wrap {
2255                let end = line_end(tree, line_start);
2256                let (main, cross) = line_extents(tree, line_start, end, spec.gap);
2257                (end, cross + stretch, main)
2258            } else {
2259                (NIL, cross_content, total_main)
2260            };
2261            let free = (main_content - run_main).max(0.0);
2262            let (lead, between) = if wrap {
2263                let mut n = 0u32;
2264                let mut k = line_start;
2265                while k != end && k != NIL {
2266                    n += u32::from(!is_float(tree, k));
2267                    k = tree.next_sibling[k as usize];
2268                }
2269                main_spread(spec.main_align, free, n)
2270            } else {
2271                (lead_main, between_main)
2272            };
2273            // A baseline line's shared baseline, from the line's top.
2274            let base_above =
2275                if tree.any_baseline && spec.cross_align == Align::Baseline && spec.dir == Dir::Row
2276                {
2277                    Some(line_baseline(tree, line_start, end).0)
2278                } else {
2279                    None
2280                };
2281            let mut cursor = main_pad_start + lead - main_scroll;
2282            let mut c = line_start;
2283            while c != end && c != NIL {
2284                if is_float(tree, c) {
2285                    c = tree.next_sibling[c as usize];
2286                    continue;
2287                }
2288                let cs = tree.size[c as usize];
2289                let (c_main, c_cross) = match spec.dir {
2290                    Dir::Row => (cs.w, cs.h),
2291                    Dir::Column => (cs.h, cs.w),
2292                };
2293                let cross_off = match base_above {
2294                    Some(above) if aligns_by_baseline(tree, c) => {
2295                        let b = first_baseline(tree, c).unwrap_or(c_cross);
2296                        cross_cursor + above - b
2297                    }
2298                    Some(_) => cross_cursor,
2299                    None => {
2300                        cross_cursor + align_factor(spec.cross_align) * (extent - c_cross).max(0.0)
2301                    }
2302                };
2303                tree.pos[c as usize] = match spec.dir {
2304                    Dir::Row => Vec2::new(origin.x + cursor, origin.y + cross_off),
2305                    Dir::Column => Vec2::new(origin.x + cross_off, origin.y + cursor),
2306                };
2307                cursor += c_main + spec.gap + between;
2308                c = tree.next_sibling[c as usize];
2309            }
2310            cross_cursor += extent + spec.cross_gap;
2311            line_start = end;
2312        }
2313    }
2314}
2315
2316#[cfg(test)]
2317mod tests {
2318    use super::*;
2319    use crate::geom::Edges;
2320    use crate::key::Key;
2321    use crate::spec::{FloatConfig, NodeSpec};
2322    use crate::tree::{NodeContent, OriginId, TextId};
2323
2324    /// Deterministic measurer: every text is `10px * chars_hint` wide and wraps
2325    /// into 20px lines. The TextId encodes the char count for test purposes.
2326    struct StubText;
2327
2328    impl TextMeasure for StubText {
2329        fn intrinsic(&mut self, id: TextId) -> Size {
2330            Size::new(10.0 * id.0 as f32, 20.0)
2331        }
2332        fn wrapped(&mut self, id: TextId, max_w: f32) -> Size {
2333            let full = 10.0 * id.0 as f32;
2334            if max_w <= 0.0 || full <= max_w {
2335                return Size::new(full, 20.0);
2336            }
2337            let lines = (full / max_w).ceil();
2338            Size::new(max_w, lines * 20.0)
2339        }
2340        /// A 20 px line with 5 px of it below the baseline.
2341        fn baseline(&mut self, _id: TextId) -> f32 {
2342            15.0
2343        }
2344    }
2345
2346    struct T {
2347        tree: Tree,
2348    }
2349
2350    impl T {
2351        fn new(root_spec: NodeSpec) -> Self {
2352            let mut tree = Tree::new();
2353            tree.push(
2354                NIL,
2355                Key::ROOT,
2356                OriginId::HOST,
2357                root_spec,
2358                NodeContent::Container,
2359            );
2360            T { tree }
2361        }
2362
2363        fn node(&mut self, parent: u32, spec: NodeSpec) -> u32 {
2364            let key = Key::ROOT.index(self.tree.len() as u64);
2365            self.tree
2366                .push(parent, key, OriginId::HOST, spec, NodeContent::Container)
2367        }
2368
2369        fn text(&mut self, parent: u32, chars: u32) -> u32 {
2370            let key = Key::ROOT.index(self.tree.len() as u64);
2371            self.tree.push(
2372                parent,
2373                key,
2374                OriginId::HOST,
2375                NodeSpec::default(),
2376                NodeContent::Text(TextId(chars)),
2377            )
2378        }
2379
2380        fn run(&mut self, vw: f32, vh: f32) {
2381            let mut scroll = ScrollStore::default();
2382            compute(
2383                &mut self.tree,
2384                &mut StubText,
2385                &mut scroll,
2386                Size::new(vw, vh),
2387                1.0,
2388            );
2389        }
2390
2391        fn size(&self, i: u32) -> Size {
2392            self.tree.size[i as usize]
2393        }
2394
2395        fn pos(&self, i: u32) -> Vec2 {
2396            self.tree.pos[i as usize]
2397        }
2398    }
2399
2400    fn px(v: f32) -> Sizing {
2401        Sizing::Fixed(v)
2402    }
2403
2404    /// Two grow children in a 600 column, one capped at 100: the cap is
2405    /// the other's room, not a hole. A min that holds a child past its
2406    /// share takes from its siblings the same way, and a run of clamps
2407    /// resolves in one layout.
2408    #[test]
2409    fn a_grow_childs_clamp_is_its_siblings_room() {
2410        let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2411        let a = t.node(0, NodeSpec::row().grow_height());
2412        let b = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2413        t.run(1000.0, 1000.0);
2414        assert_eq!(t.size(b).h, 100.0);
2415        assert_eq!(t.size(a).h, 500.0);
2416
2417        let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(50.0)));
2418        let a = t.node(0, NodeSpec::row().grow_width());
2419        let b = t.node(0, NodeSpec::row().grow_width().min_width(200.0));
2420        let c = t.node(0, NodeSpec::row().grow_width().max_width(20.0));
2421        t.run(1000.0, 1000.0);
2422        assert_eq!(t.size(b).w, 200.0);
2423        assert_eq!(t.size(c).w, 20.0);
2424        assert_eq!(t.size(a).w, 80.0);
2425        assert_eq!(t.pos(c).x, 280.0);
2426    }
2427
2428    /// Flexbox's sign rule (CSS Flexible Box §9.7, step 6): a pass sums
2429    /// its violations and freezes only the violators of the dominant
2430    /// sign, then re-shares. A 600 column of three grow rows, A capped
2431    /// at 100, B held to 500, C plain: the first pass shares 200, A's
2432    /// −100 and B's +300 sum positive, so only B is frozen and A shares
2433    /// the remaining 100 with C — 50 / 500 / 50, not A at its cap and C
2434    /// empty (RG5). Mirrored: A capped at 100, B held to 210, the sum
2435    /// −90 is negative, so only A is frozen, and B's re-share of 250
2436    /// clears its min on its own — 100 / 250 / 250, not 100 / 210 / 290.
2437    #[test]
2438    fn a_pass_freezes_only_the_violators_of_the_dominant_sign() {
2439        let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2440        let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2441        let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(500.0)));
2442        let c = t.node(0, NodeSpec::row().grow_height());
2443        t.run(1000.0, 1000.0);
2444        assert_eq!(t.size(a).h, 50.0);
2445        assert_eq!(t.size(b).h, 500.0);
2446        assert_eq!(t.size(c).h, 50.0);
2447        assert_eq!(t.pos(c).y, 550.0);
2448
2449        let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2450        let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2451        let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(210.0)));
2452        let c = t.node(0, NodeSpec::row().grow_height());
2453        t.run(1000.0, 1000.0);
2454        assert_eq!(t.size(a).h, 100.0);
2455        assert_eq!(t.size(b).h, 250.0);
2456        assert_eq!(t.size(c).h, 250.0);
2457    }
2458
2459    #[test]
2460    fn fit_row_sums_children_and_gaps() {
2461        let mut t = T::new(NodeSpec::row().pad(10.0).gap(5.0));
2462        let r = 0;
2463        t.node(r, NodeSpec::column().width(px(30.0)).height(px(40.0)));
2464        t.node(r, NodeSpec::column().width(px(20.0)).height(px(25.0)));
2465        t.run(1000.0, 1000.0);
2466        // 30 + 5 + 20 + 2*10 pad = 75; height = max(40,25) + 20 = 60
2467        assert_eq!(t.size(r), Size::new(75.0, 60.0));
2468    }
2469
2470    #[test]
2471    fn fit_column_sums_heights() {
2472        let mut t = T::new(NodeSpec::column().gap(4.0));
2473        t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2474        t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0)));
2475        t.node(0, NodeSpec::row().width(px(30.0)).height(px(10.0)));
2476        t.run(1000.0, 1000.0);
2477        assert_eq!(t.size(0), Size::new(50.0, 38.0));
2478    }
2479
2480    #[test]
2481    fn grow_splits_remaining_space_by_factor() {
2482        let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(100.0)).gap(10.0));
2483        let a = t.node(0, NodeSpec::column().width(px(50.0)).height(px(10.0)));
2484        let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2485        let c = t.node(
2486            0,
2487            NodeSpec::column().width(Sizing::Grow(2.0)).height(px(10.0)),
2488        );
2489        t.run(1000.0, 1000.0);
2490        // content 300, fixed 50, gaps 20 -> remain 230 split 1:2
2491        let bw = t.size(b).w;
2492        let cw = t.size(c).w;
2493        assert!((bw - 230.0 / 3.0).abs() < 0.01, "b={bw}");
2494        assert!((cw - 460.0 / 3.0).abs() < 0.01, "c={cw}");
2495        assert_eq!(t.size(a).w, 50.0);
2496    }
2497
2498    #[test]
2499    fn percent_resolves_against_content_box() {
2500        let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(100.0)).pad(10.0));
2501        let a = t.node(
2502            0,
2503            NodeSpec::column()
2504                .width(Sizing::Percent(0.5))
2505                .height(Sizing::Percent(1.0)),
2506        );
2507        t.run(1000.0, 1000.0);
2508        assert_eq!(t.size(a), Size::new(90.0, 80.0)); // (200-20)*0.5, (100-20)*1.0
2509    }
2510
2511    #[test]
2512    fn cross_axis_grow_fills_content() {
2513        let mut t = T::new(
2514            NodeSpec::column()
2515                .width(px(120.0))
2516                .height(px(200.0))
2517                .pad(8.0),
2518        );
2519        let a = t.node(0, NodeSpec::row().grow_width().height(px(30.0)));
2520        t.run(1000.0, 1000.0);
2521        assert_eq!(t.size(a).w, 104.0);
2522    }
2523
2524    #[test]
2525    fn nested_fit_propagates_up() {
2526        let mut t = T::new(NodeSpec::column());
2527        let mid = t.node(0, NodeSpec::row().pad(5.0).gap(2.0));
2528        t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2529        t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2530        t.run(1000.0, 1000.0);
2531        assert_eq!(t.size(mid), Size::new(32.0, 20.0));
2532        assert_eq!(t.size(0), Size::new(32.0, 20.0));
2533    }
2534
2535    #[test]
2536    fn root_grow_takes_viewport() {
2537        let mut t = T::new(NodeSpec::column().fill());
2538        t.run(800.0, 600.0);
2539        assert_eq!(t.size(0), Size::new(800.0, 600.0));
2540    }
2541
2542    #[test]
2543    fn positions_row_with_gap_and_padding() {
2544        let mut t = T::new(
2545            NodeSpec::row()
2546                .width(px(300.0))
2547                .height(px(100.0))
2548                .pad(10.0)
2549                .gap(5.0),
2550        );
2551        let a = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2552        let b = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2553        t.run(1000.0, 1000.0);
2554        assert_eq!(t.pos(a), Vec2::new(10.0, 10.0));
2555        assert_eq!(t.pos(b), Vec2::new(55.0, 10.0));
2556    }
2557
2558    #[test]
2559    fn main_center_alignment_offsets_children() {
2560        let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(50.0)).center());
2561        let a = t.node(0, NodeSpec::column().width(px(60.0)).height(px(20.0)));
2562        t.run(1000.0, 1000.0);
2563        assert_eq!(t.pos(a), Vec2::new(70.0, 15.0));
2564    }
2565
2566    #[test]
2567    fn main_end_alignment() {
2568        let mut t = T::new(
2569            NodeSpec::column()
2570                .width(px(100.0))
2571                .height(px(100.0))
2572                .main_align(Align::End)
2573                .gap(10.0),
2574        );
2575        let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2576        let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2577        t.run(1000.0, 1000.0);
2578        assert_eq!(t.pos(a).y, 50.0);
2579        assert_eq!(t.pos(b).y, 80.0);
2580    }
2581
2582    /// Three 10 px children in a 100 px row: 70 px free, dealt out the
2583    /// way each spread says.
2584    fn spread(a: Align, gap: f32) -> Vec<f32> {
2585        let mut t = T::new(
2586            NodeSpec::row()
2587                .width(px(100.0))
2588                .height(px(10.0))
2589                .gap(gap)
2590                .main_align(a),
2591        );
2592        let kids: Vec<u32> = (0..3)
2593            .map(|_| t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0))))
2594            .collect();
2595        t.run(1000.0, 1000.0);
2596        kids.iter().map(|&k| t.pos(k).x).collect()
2597    }
2598
2599    #[test]
2600    fn space_between_puts_the_free_space_between_the_children() {
2601        assert_eq!(spread(Align::SpaceBetween, 0.0), [0.0, 45.0, 90.0]);
2602        // The spread is on top of the gap: 50 free, 25 each.
2603        assert_eq!(spread(Align::SpaceBetween, 10.0), [0.0, 45.0, 90.0]);
2604    }
2605
2606    #[test]
2607    fn space_around_gives_the_ends_half_a_share() {
2608        // 70 / 3 each, half of it on either side of a child.
2609        let x = spread(Align::SpaceAround, 0.0);
2610        let share = 70.0 / 3.0;
2611        for (i, want) in [share / 2.0, share * 1.5 + 10.0, share * 2.5 + 20.0]
2612            .into_iter()
2613            .enumerate()
2614        {
2615            assert!((x[i] - want).abs() < 1e-3, "{x:?}");
2616        }
2617    }
2618
2619    #[test]
2620    fn space_evenly_makes_every_gap_and_both_ends_equal() {
2621        assert_eq!(spread(Align::SpaceEvenly, 0.0), [17.5, 45.0, 72.5]);
2622    }
2623
2624    /// One child: `space-between` has nothing to go between and starts it,
2625    /// the other two centre it, as CSS does.
2626    #[test]
2627    fn a_lone_child_under_a_spread_starts_or_centres() {
2628        for (a, want) in [
2629            (Align::SpaceBetween, 0.0),
2630            (Align::SpaceAround, 45.0),
2631            (Align::SpaceEvenly, 45.0),
2632        ] {
2633            let mut t = T::new(NodeSpec::row().width(px(100.0)).main_align(a));
2634            let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2635            t.run(1000.0, 1000.0);
2636            assert_eq!(t.pos(c).x, want, "{a:?}");
2637        }
2638    }
2639
2640    /// Nothing free, nothing dealt: a grow child takes the space, and a run
2641    /// that overflows keeps its plain gaps.
2642    #[test]
2643    fn a_spread_with_nothing_free_is_the_gaps_alone() {
2644        let mut t = T::new(
2645            NodeSpec::row()
2646                .width(px(100.0))
2647                .gap(5.0)
2648                .main_align(Align::SpaceBetween),
2649        );
2650        let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2651        let b = t.node(0, NodeSpec::row().grow_width().height(px(10.0)));
2652        let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2653        t.run(1000.0, 1000.0);
2654        assert_eq!((t.pos(a).x, t.pos(b).x, t.pos(c).x), (0.0, 15.0, 90.0));
2655
2656        let mut t = T::new(
2657            NodeSpec::row()
2658                .width(px(100.0))
2659                .gap(5.0)
2660                .scroll_x()
2661                .main_align(Align::SpaceEvenly),
2662        );
2663        let kids: Vec<u32> = (0..3)
2664            .map(|_| t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0))))
2665            .collect();
2666        t.run(1000.0, 1000.0);
2667        let x: Vec<f32> = kids.iter().map(|&k| t.pos(k).x).collect();
2668        assert_eq!(x, [0.0, 55.0, 110.0]);
2669    }
2670
2671    /// A column spreads down, and floats take no share.
2672    #[test]
2673    fn a_column_spreads_its_height_and_floats_take_no_share() {
2674        let mut t = T::new(
2675            NodeSpec::column()
2676                .width(px(10.0))
2677                .height(px(100.0))
2678                .main_align(Align::SpaceBetween),
2679        );
2680        let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2681        t.node(
2682            0,
2683            NodeSpec::row()
2684                .width(px(5.0))
2685                .height(px(5.0))
2686                .float(FloatConfig::below()),
2687        );
2688        let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2689        t.run(1000.0, 1000.0);
2690        assert_eq!((t.pos(a).y, t.pos(b).y), (0.0, 80.0));
2691    }
2692
2693    /// A wrapping row deals out each line's own free space.
2694    #[test]
2695    fn a_wrapping_row_spreads_each_line_by_itself() {
2696        let mut t = T::new(
2697            NodeSpec::row()
2698                .width(px(100.0))
2699                .wrap()
2700                .main_align(Align::SpaceBetween),
2701        );
2702        let kids: Vec<u32> = (0..3)
2703            .map(|_| t.node(0, NodeSpec::row().width(px(40.0)).height(px(10.0))))
2704            .collect();
2705        t.run(1000.0, 1000.0);
2706        let at: Vec<(f32, f32)> = kids.iter().map(|&k| (t.pos(k).x, t.pos(k).y)).collect();
2707        // Line one holds two with 20 between; line two one, at the start.
2708        assert_eq!(at, [(0.0, 0.0), (60.0, 0.0), (0.0, 10.0)]);
2709    }
2710
2711    /// A 20 px text (baseline 15) and a 40 px box on one row: the box has
2712    /// no text, so its bottom edge is its baseline, and the text drops to
2713    /// meet it. The fit row holds both: 40 above, the text's 5 below.
2714    #[test]
2715    fn baseline_lines_up_text_with_a_box_s_bottom_edge() {
2716        let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2717        let txt = t.text(0, 3);
2718        let bx = t.node(0, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2719        t.run(1000.0, 1000.0);
2720        assert_eq!(t.pos(bx).y, 0.0);
2721        assert_eq!(t.pos(txt).y, 25.0);
2722        assert_eq!(t.size(0).h, 45.0);
2723    }
2724
2725    /// A container's baseline is its first child's, carried down through
2726    /// where that child sits in it: a column padded 10 on top holds its
2727    /// text 10 lower, so a bare text beside it drops 10 to meet it.
2728    #[test]
2729    fn a_container_s_baseline_is_its_first_text_s() {
2730        let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2731        let bare = t.text(0, 3);
2732        let col = t.node(
2733            0,
2734            NodeSpec::column().padding(Edges {
2735                l: 0.0,
2736                r: 0.0,
2737                t: 10.0,
2738                b: 0.0,
2739            }),
2740        );
2741        let inner = t.text(col, 3);
2742        t.node(col, NodeSpec::row().width(px(10.0)).height(px(30.0)));
2743        t.run(1000.0, 1000.0);
2744        assert_eq!(t.pos(col).y, 0.0);
2745        assert_eq!(t.pos(inner).y, 10.0);
2746        assert_eq!(t.pos(bare).y, 10.0);
2747        // The column (60 tall, baseline 25) sets the row's height.
2748        assert_eq!(t.size(0).h, 60.0);
2749    }
2750
2751    /// A grow height fills the line from its top instead of aligning.
2752    #[test]
2753    fn a_grow_height_child_of_a_baseline_row_fills_from_the_top() {
2754        let mut t = T::new(
2755            NodeSpec::row()
2756                .height(px(50.0))
2757                .cross_align(Align::Baseline),
2758        );
2759        let txt = t.text(0, 3);
2760        let g = t.node(0, NodeSpec::row().width(px(10.0)).grow_height());
2761        t.run(1000.0, 1000.0);
2762        assert_eq!((t.pos(g).y, t.size(g).h), (0.0, 50.0));
2763        assert_eq!(t.pos(txt).y, 0.0);
2764    }
2765
2766    /// A column's cross axis is horizontal: `baseline` there is `start`.
2767    #[test]
2768    fn baseline_on_a_column_is_start() {
2769        let mut t = T::new(
2770            NodeSpec::column()
2771                .width(px(100.0))
2772                .cross_align(Align::Baseline),
2773        );
2774        let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2775        t.run(1000.0, 1000.0);
2776        assert_eq!(t.pos(c).x, 0.0);
2777    }
2778
2779    /// `width: grow` and a ratio keeps its shape: the fit height is the
2780    /// final width over the ratio, and the children overflow it.
2781    #[test]
2782    fn a_ratio_sizes_a_fit_height_from_the_final_width() {
2783        let mut t = T::new(NodeSpec::column().width(px(320.0)));
2784        let v = t.node(0, NodeSpec::column().grow_width().aspect_ratio(16.0 / 9.0));
2785        t.node(v, NodeSpec::row().width(px(10.0)).height(px(500.0)));
2786        t.run(1000.0, 1000.0);
2787        assert_eq!(t.size(v), Size::new(320.0, 180.0));
2788    }
2789
2790    /// Under a fixed height, a fit width is the height times the ratio.
2791    #[test]
2792    fn a_ratio_sizes_a_fit_width_from_a_fixed_height() {
2793        let mut t = T::new(NodeSpec::row());
2794        let sq = t.node(0, NodeSpec::row().height(px(24.0)).aspect_ratio(1.0));
2795        t.run(1000.0, 1000.0);
2796        assert_eq!(t.size(sq), Size::new(24.0, 24.0));
2797    }
2798
2799    /// A derived height is not shrunk: a column too short for a ratio box
2800    /// shrinks its other fit children and leaves the box its shape.
2801    #[test]
2802    fn a_ratio_s_derived_height_is_not_shrunk() {
2803        let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(100.0)));
2804        let r = t.node(0, NodeSpec::column().grow_width().aspect_ratio(1.25));
2805        let other = t.node(0, NodeSpec::column().height(Sizing::Fit));
2806        t.node(other, NodeSpec::row().width(px(10.0)).height(px(60.0)));
2807        t.run(1000.0, 1000.0);
2808        assert_eq!(t.size(r).h, 80.0);
2809        // Nor is its sibling below the 60 px it holds: the
2810        // column overflows, where the sibling was squeezed to 20.
2811        assert_eq!(t.size(other).h, 60.0);
2812    }
2813
2814    /// `minHeight: fit` floors a derived height at the children.
2815    #[test]
2816    fn min_fit_floors_a_ratio_height_at_its_children() {
2817        let mut t = T::new(NodeSpec::column().width(px(100.0)));
2818        let r = t.node(
2819            0,
2820            NodeSpec::column()
2821                .grow_width()
2822                .aspect_ratio(4.0)
2823                .min_height(Min::FIT),
2824        );
2825        t.node(r, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2826        t.run(1000.0, 1000.0);
2827        assert_eq!(t.size(r).h, 40.0);
2828    }
2829
2830    #[test]
2831    fn text_gets_intrinsic_size_when_it_fits() {
2832        let mut t = T::new(NodeSpec::column().width(px(500.0)).height(px(500.0)));
2833        let txt = t.text(0, 8); // 80px wide
2834        t.run(1000.0, 1000.0);
2835        assert_eq!(t.size(txt), Size::new(80.0, 20.0));
2836    }
2837
2838    #[test]
2839    fn text_wraps_when_clamped_by_parent() {
2840        let mut t = T::new(
2841            NodeSpec::column()
2842                .width(px(100.0))
2843                .height(px(500.0))
2844                .pad(10.0),
2845        );
2846        let txt = t.text(0, 20); // 200px intrinsic, clamped to 80 -> 3 lines
2847        t.run(1000.0, 1000.0);
2848        assert_eq!(t.size(txt).w, 80.0);
2849        assert_eq!(t.size(txt).h, 60.0);
2850    }
2851
2852    #[test]
2853    fn text_wrapping_grows_fit_parent_height() {
2854        let mut t = T::new(NodeSpec::column().width(px(100.0)));
2855        let txt = t.text(0, 30); // 300px intrinsic -> wraps to 100 -> 3 lines
2856        t.run(1000.0, 1000.0);
2857        assert_eq!(t.size(txt).h, 60.0);
2858        assert_eq!(t.size(0).h, 60.0);
2859    }
2860
2861    #[test]
2862    fn grow_with_no_space_left_gets_zero() {
2863        let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2864        let a = t.node(0, NodeSpec::column().width(px(120.0)).height(px(10.0)));
2865        let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2866        t.run(1000.0, 1000.0);
2867        assert_eq!(t.size(a).w, 120.0); // no shrinking in v0
2868        assert_eq!(t.size(b).w, 0.0);
2869    }
2870
2871    #[test]
2872    fn deep_nesting_positions_accumulate() {
2873        let mut t = T::new(NodeSpec::column().pad(10.0));
2874        let l1 = t.node(0, NodeSpec::column().pad(10.0));
2875        let l2 = t.node(l1, NodeSpec::column().pad(10.0));
2876        let leaf = t.node(l2, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2877        t.run(1000.0, 1000.0);
2878        assert_eq!(t.pos(leaf), Vec2::new(30.0, 30.0));
2879        assert_eq!(t.size(0), Size::new(70.0, 70.0));
2880    }
2881
2882    #[test]
2883    fn grow_respects_max_width() {
2884        let mut t = T::new(NodeSpec::row().width(px(800.0)).height(px(100.0)));
2885        let a = t.node(
2886            0,
2887            NodeSpec::column()
2888                .grow_width()
2889                .max_width(560.0)
2890                .height(px(10.0)),
2891        );
2892        t.run(1000.0, 1000.0);
2893        assert_eq!(t.size(a).w, 560.0);
2894        // And tracks the parent when it's smaller than the cap.
2895        let mut t = T::new(NodeSpec::row().width(px(400.0)).height(px(100.0)));
2896        let a = t.node(
2897            0,
2898            NodeSpec::column()
2899                .grow_width()
2900                .max_width(560.0)
2901                .height(px(10.0)),
2902        );
2903        t.run(1000.0, 1000.0);
2904        assert_eq!(t.size(a).w, 400.0);
2905    }
2906
2907    #[test]
2908    fn min_width_forces_fit_up() {
2909        let mut t = T::new(NodeSpec::column());
2910        let a = t.node(0, NodeSpec::row().min_width(120.0).height(px(10.0)));
2911        t.node(a, NodeSpec::column().width(px(30.0)).height(px(10.0)));
2912        t.run(1000.0, 1000.0);
2913        assert_eq!(t.size(a).w, 120.0);
2914        // Min propagates into the fit parent.
2915        assert_eq!(t.size(0).w, 120.0);
2916    }
2917
2918    #[test]
2919    fn percent_respects_max() {
2920        let mut t = T::new(NodeSpec::column().width(px(1000.0)).height(px(1000.0)));
2921        let a = t.node(
2922            0,
2923            NodeSpec::row()
2924                .width(Sizing::Percent(0.9))
2925                .max_width(300.0)
2926                .height(px(10.0)),
2927        );
2928        t.run(1000.0, 1000.0);
2929        assert_eq!(t.size(a).w, 300.0);
2930    }
2931
2932    #[test]
2933    fn text_rewraps_when_capped_parent_shrinks() {
2934        // Same tree shape at two parent widths: the narrow one wraps taller.
2935        let build = |parent_w: f32| {
2936            let mut t = T::new(NodeSpec::column().width(px(parent_w)));
2937            let txt = t.text(0, 40); // 400px intrinsic
2938            t.run(1000.0, 1000.0);
2939            t.size(txt)
2940        };
2941        let wide = build(500.0);
2942        let narrow = build(100.0);
2943        assert_eq!(wide.h, 20.0);
2944        assert_eq!(narrow.h, 80.0); // 400 / 100 -> 4 lines
2945    }
2946
2947    #[test]
2948    fn shrink_compresses_largest_fit_child_first() {
2949        let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2950        let a = t.node(0, NodeSpec::column());
2951        t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2952        let b = t.node(0, NodeSpec::column());
2953        t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2954        t.run(1000.0, 1000.0);
2955        // 120 into 100: the 80 child pays the whole 20px deficit.
2956        assert_eq!(t.size(a).w, 60.0);
2957        assert_eq!(t.size(b).w, 40.0);
2958    }
2959
2960    #[test]
2961    fn shrink_respects_min_and_spills_to_the_next() {
2962        let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2963        let a = t.node(0, NodeSpec::column().min_width(70.0));
2964        t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2965        let b = t.node(0, NodeSpec::column());
2966        t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2967        t.run(1000.0, 1000.0);
2968        // a stops at its min; b pays the rest.
2969        assert_eq!(t.size(a).w, 70.0);
2970        assert_eq!(t.size(b).w, 30.0);
2971    }
2972
2973    #[test]
2974    fn equal_children_shrink_equally() {
2975        let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2976        let mut kids = Vec::new();
2977        for _ in 0..3 {
2978            let c = t.node(0, NodeSpec::column());
2979            t.node(c, NodeSpec::row().width(px(60.0)).height(px(10.0)));
2980            kids.push(c);
2981        }
2982        t.run(1000.0, 1000.0);
2983        for c in kids {
2984            assert!(
2985                (t.size(c).w - 100.0 / 3.0).abs() < 0.1,
2986                "got {}",
2987                t.size(c).w
2988            );
2989        }
2990    }
2991
2992    #[test]
2993    fn shrunk_text_rewraps() {
2994        let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(500.0)));
2995        t.node(0, NodeSpec::column().width(px(80.0)).height(px(10.0)));
2996        let txt = t.text(0, 20); // 200px intrinsic
2997        t.run(1000.0, 1000.0);
2998        // 280 into 200: text pays the deficit, then wraps at 120 -> 2 lines.
2999        assert_eq!(t.size(txt).w, 120.0);
3000        assert_eq!(t.size(txt).h, 40.0);
3001    }
3002
3003    #[test]
3004    fn text_never_shrinks_vertically() {
3005        let mut t = T::new(NodeSpec::column().width(px(200.0)).height(px(30.0)));
3006        let txt = t.text(0, 30); // wraps to 200 -> 2 lines = 40 > 30 parent
3007        t.run(1000.0, 1000.0);
3008        assert_eq!(
3009            t.size(txt).h,
3010            40.0,
3011            "text overflows rather than clipping lines"
3012        );
3013    }
3014
3015    #[test]
3016    fn scroll_axis_skips_shrink() {
3017        let mut t = T::new(
3018            NodeSpec::column()
3019                .width(px(100.0))
3020                .height(px(100.0))
3021                .scroll_y(),
3022        );
3023        for _ in 0..2 {
3024            let c = t.node(0, NodeSpec::column());
3025            t.node(c, NodeSpec::row().width(px(10.0)).height(px(80.0)));
3026        }
3027        t.run(1000.0, 1000.0);
3028        // 160 of content in a 100 box stays 160: it scrolls instead.
3029        for c in [1u32, 3u32] {
3030            assert_eq!(t.size(c).h, 80.0);
3031        }
3032    }
3033
3034    /// An i3-style tab bar: every tab `grow` with a `min_width`, on a row
3035    /// that scrolls x. With room the tabs split the bar evenly; past it
3036    /// each sits at its min and the bar scrolls by the overflow — the
3037    /// clamp in `distribute_run` and the shrink pass a scroll axis skips
3038    /// are what make one declaration cover both regimes.
3039    #[test]
3040    fn grow_tabs_split_evenly_then_scroll_at_their_min() {
3041        let bar = || NodeSpec::row().width(px(600.0)).height(px(30.0)).scroll_x();
3042        let tab = || {
3043            NodeSpec::column()
3044                .grow_width()
3045                .min_width(80.0)
3046                .height(px(30.0))
3047        };
3048        // Three tabs: 200 each, nothing to scroll.
3049        let mut t = T::new(bar());
3050        let tabs: Vec<u32> = (0..3).map(|_| t.node(0, tab())).collect();
3051        t.run(1000.0, 1000.0);
3052        for (k, &c) in tabs.iter().enumerate() {
3053            assert_eq!(t.size(c).w, 200.0);
3054            assert_eq!(t.pos(c).x, 200.0 * k as f32);
3055        }
3056        assert_eq!(t.tree.scroll_max[0].x, 0.0);
3057        // Ten tabs: 800 of min in a 600 bar, every tab at 80, 200 to scroll.
3058        let mut t = T::new(bar());
3059        let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3060        t.run(1000.0, 1000.0);
3061        for (k, &c) in tabs.iter().enumerate() {
3062            assert_eq!(t.size(c).w, 80.0);
3063            assert_eq!(t.pos(c).x, 80.0 * k as f32);
3064        }
3065        assert_eq!(t.tree.scroll_max[0].x, 200.0);
3066        // Same bar without scroll_x: the mins still hold (grow is not
3067        // shrinkable), so the row overflows and clips instead.
3068        let mut t = T::new(NodeSpec::row().width(px(600.0)).height(px(30.0)));
3069        let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3070        t.run(1000.0, 1000.0);
3071        for &c in &tabs {
3072            assert_eq!(t.size(c).w, 80.0);
3073        }
3074        // The floor as the tab's own content: `Min::FIT` under `Grow`.
3075        // Ten tabs each around an 80 px label split a 600 bar as the
3076        // numeric min did — the label is the min — and the fit resolves
3077        // to a number the spec keeps.
3078        let mut t = T::new(bar());
3079        let tabs: Vec<u32> = (0..10)
3080            .map(|_| {
3081                let c = t.node(0, tab().min_width(Min::FIT));
3082                t.text(c, 8);
3083                c
3084            })
3085            .collect();
3086        t.run(1000.0, 1000.0);
3087        for (k, &c) in tabs.iter().enumerate() {
3088            assert_eq!(t.size(c).w, 80.0);
3089            assert_eq!(t.pos(c).x, 80.0 * k as f32);
3090            assert_eq!(t.tree.specs[c as usize].layout.min_w, Min::px(80.0));
3091        }
3092        assert_eq!(t.tree.scroll_max[0].x, 200.0);
3093        // And with room, the same tabs split it: 3 × 200, the floor idle.
3094        let mut t = T::new(bar());
3095        let tabs: Vec<u32> = (0..3)
3096            .map(|_| {
3097                let c = t.node(0, tab().min_width(Min::FIT));
3098                t.text(c, 8);
3099                c
3100            })
3101            .collect();
3102        t.run(1000.0, 1000.0);
3103        for &c in &tabs {
3104            assert_eq!(t.size(c).w, 200.0);
3105        }
3106    }
3107
3108    /// `Min::FIT` on the cross axis and under a percent: a 50%-tall cell
3109    /// in a 20-tall row floors at its 16-tall child, and a fit floor with
3110    /// nothing inside is no floor.
3111    #[test]
3112    fn min_fit_floors_a_percent_height_at_its_content() {
3113        let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(20.0)));
3114        let a = t.node(
3115            0,
3116            NodeSpec::column()
3117                .width(px(10.0))
3118                .height(Sizing::Percent(0.5))
3119                .min_height(Min::FIT),
3120        );
3121        t.node(a, NodeSpec::column().width(px(10.0)).height(px(16.0)));
3122        let b = t.node(
3123            0,
3124            NodeSpec::column()
3125                .width(px(10.0))
3126                .height(Sizing::Percent(0.5))
3127                .min_height(Min::FIT),
3128        );
3129        t.run(1000.0, 1000.0);
3130        assert_eq!(t.size(a).h, 16.0);
3131        assert_eq!(t.size(b).h, 10.0);
3132    }
3133
3134    #[test]
3135    fn padding_asymmetric() {
3136        let mut t = T::new(
3137            NodeSpec::column()
3138                .width(px(100.0))
3139                .height(px(100.0))
3140                .padding(Edges {
3141                    l: 1.0,
3142                    r: 2.0,
3143                    t: 3.0,
3144                    b: 4.0,
3145                }),
3146        );
3147        let a = t.node(0, NodeSpec::row().fill());
3148        t.run(1000.0, 1000.0);
3149        assert_eq!(t.pos(a), Vec2::new(1.0, 3.0));
3150        assert_eq!(t.size(a), Size::new(97.0, 93.0));
3151    }
3152}