Skip to main content

kui_core/
layout.rs

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