1use 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#[inline]
44fn is_float(tree: &Tree, i: u32) -> bool {
45 tree.any_float && tree.specs[i as usize].layout.float.is_some()
46}
47
48fn 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
60fn 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#[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#[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
104fn 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 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
171fn 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
196fn 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
208fn overflow(pos: f32, len: f32, limit: f32) -> f32 {
210 (-pos).max(0.0) + (pos + len - limit).max(0.0)
211}
212
213#[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#[inline]
242pub(crate) fn is_table_row(tree: &Tree, i: u32) -> bool {
243 if !tree.any_table {
244 return false;
245 }
246 let p = tree.parent[i as usize];
247 p != NIL
248 && tree.specs[p as usize].layout.is_table()
249 && row_shaped(
250 &tree.specs[i as usize].layout,
251 matches!(tree.content[i as usize], NodeContent::Container),
252 )
253}
254
255#[inline]
260pub(crate) fn row_shaped(spec: &crate::spec::LayoutSpec, container: bool) -> bool {
261 spec.dir == Dir::Row && container && spec.float.is_none()
262}
263
264#[inline]
266fn is_table_cell(tree: &Tree, i: u32) -> bool {
267 if !tree.any_table {
268 return false;
269 }
270 let p = tree.parent[i as usize];
271 p != NIL && is_table_row(tree, p) && !is_float(tree, i)
272}
273
274#[derive(Clone, Copy, Debug, Default)]
276struct Col {
277 fit: f32,
280 grow: f32,
283 pct: f32,
286 calc: Option<f32>,
294 fixed: bool,
296 min: f32,
299 max: f32,
300 w: f32,
302}
303
304impl Col {
305 fn clamp(&self, w: f32) -> f32 {
306 w.clamp(self.min, self.max.max(self.min))
307 }
308}
309
310fn table_columns(tree: &Tree, i: u32, room: Option<f32>) -> Vec<Col> {
316 let mut cols: Vec<Col> = Vec::new();
317 for row in tree.children(i) {
318 if !is_table_row(tree, row) {
319 continue;
320 }
321 let mut j = 0usize;
322 for cell in tree.children(row) {
323 if is_float(tree, cell) {
324 continue;
325 }
326 if j == cols.len() {
327 cols.push(Col {
328 max: f32::INFINITY,
329 ..Col::default()
330 });
331 }
332 let col = &mut cols[j];
333 let spec = tree.specs[cell as usize].layout;
334 col.fit = col.fit.max(tree.size[cell as usize].w);
335 match child_sizing(tree, cell, AxisSel::Width) {
336 Sizing::Grow(f) => col.grow = col.grow.max(f.max(0.0)),
337 Sizing::Percent(p) => col.pct = col.pct.max(p),
338 Sizing::Calc(c) => {
339 let px = room.map_or(0.0, |r| c.resolve(r));
340 col.calc = Some(col.calc.map_or(px, |w| w.max(px)));
341 }
342 Sizing::Fixed(_) => col.fixed = true,
343 Sizing::Fit => {}
344 }
345 if !matches!(tree.content[cell as usize], NodeContent::Text(_)) {
346 col.min = col.min.max(spec.min_w.resolved());
347 col.max = col.max.min(spec.max_w_px());
348 }
349 j += 1;
350 }
351 }
352 cols
353}
354
355fn table_row_fit(tree: &Tree, row: u32, cols: &[Col]) -> f32 {
359 let spec = tree.specs[row as usize].layout;
360 let mut w = 0.0f32;
361 let mut n = 0u32;
362 for cell in tree.children(row) {
363 if is_float(tree, cell) {
364 continue;
365 }
366 w += cols.get(n as usize).map_or(0.0, |c| c.w);
367 n += 1;
368 }
369 if n > 1 {
370 w += spec.gap * (n - 1) as f32;
371 }
372 w + spec.padding.x()
373}
374
375fn table_apply(tree: &mut Tree, i: u32, cols: &[Col], fitting: bool) {
391 let scrolls = tree.specs[i as usize].layout.scroll_x;
392 let mut row = tree.first_child[i as usize];
393 while row != NIL {
394 if is_table_row(tree, row) {
395 let mut j = 0usize;
396 let mut cell = tree.first_child[row as usize];
397 while cell != NIL {
398 if !is_float(tree, cell) {
399 tree.size[cell as usize].w = cols[j].w;
400 j += 1;
401 }
402 cell = tree.next_sibling[cell as usize];
403 }
404 let spec = tree.specs[row as usize].layout;
405 match spec.width {
406 Sizing::Fit => {
407 let fit = table_row_fit(tree, row, cols);
408 tree.size[row as usize].w = spec.clamp_w(fit);
409 }
410 Sizing::Fixed(_) => {}
411 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) if fitting => {
412 let fit = table_row_fit(tree, row, cols);
413 tree.size[row as usize].w = spec.clamp_w(fit);
414 }
415 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => {}
416 }
417 if scrolls && !fitting {
418 let fit = table_row_fit(tree, row, cols);
419 let w = &mut tree.size[row as usize].w;
420 *w = w.max(fit);
421 }
422 }
423 row = tree.next_sibling[row as usize];
424 }
425}
426
427fn table_fit(tree: &mut Tree, i: u32) {
432 let mut cols = table_columns(tree, i, None);
433 for col in &mut cols {
434 col.w = col.clamp(col.fit);
435 }
436 table_apply(tree, i, &cols, true);
437}
438
439fn table_resolve(tree: &mut Tree, i: u32) {
449 let n = tree
453 .children(i)
454 .filter(|&row| is_table_row(tree, row))
455 .map(|row| tree.children(row).filter(|&c| !is_float(tree, c)).count())
456 .max()
457 .unwrap_or(0);
458 if n == 0 {
459 return;
460 }
461 let mut avail = 0.0f32;
465 for row in tree.children(i) {
466 if !is_table_row(tree, row) {
467 continue;
468 }
469 let spec = tree.specs[row as usize].layout;
470 let chrome = spec.padding.x() + spec.gap * (n as f32 - 1.0);
471 avail = avail.max(tree.size[row as usize].w - chrome);
472 }
473 let avail = avail.max(0.0);
474 if tree.any_calc_bound {
483 for row in tree.children(i).collect::<Vec<_>>() {
484 if !is_table_row(tree, row) {
485 continue;
486 }
487 for cell in tree.children(row).collect::<Vec<_>>() {
488 if !is_float(tree, cell) {
489 resolve_bounds(tree, cell, AxisSel::Width, avail);
490 }
491 }
492 }
493 }
494 let mut cols = table_columns(tree, i, Some(avail));
495 debug_assert_eq!(cols.len(), n);
496 let mut used = 0.0f32;
497 let mut grow_total = 0.0f32;
498 for col in &mut cols {
499 if col.grow > 0.0 {
500 grow_total += col.grow;
501 col.w = col.clamp(0.0);
502 } else if col.pct > 0.0 || col.calc.is_some() {
503 let calc = col.calc.unwrap_or(0.0);
504 col.w = col.clamp((avail * col.pct).max(calc));
505 used += col.w;
506 } else {
507 col.w = col.clamp(col.fit);
508 used += col.w;
509 }
510 }
511 if grow_total > 0.0 {
512 let mut frozen = vec![false; cols.len()];
513 loop {
514 let remain = (avail - used).max(0.0);
515 let mut froze = false;
516 for (j, col) in cols.iter_mut().enumerate() {
517 if col.grow <= 0.0 || frozen[j] {
518 continue;
519 }
520 let share = remain * col.grow / grow_total;
521 col.w = col.clamp(share);
522 if (col.w - share).abs() > 0.01 {
523 frozen[j] = true;
524 used += col.w;
525 grow_total -= col.grow;
526 froze = true;
527 }
528 }
529 if !froze || grow_total <= 0.0 {
530 break;
531 }
532 }
533 }
534 let total: f32 = cols.iter().map(|c| c.w).sum();
537 let mut deficit = total - avail;
538 if deficit > 0.5 && !tree.specs[i as usize].layout.scroll_x {
539 let shrinkable = |c: &Col| !c.fixed && c.grow <= 0.0 && c.pct <= 0.0 && c.calc.is_none();
540 let mut guard = 0;
541 while deficit > 0.5 && guard < 128 {
542 guard += 1;
543 let mut largest = f32::NEG_INFINITY;
544 let mut second = 0.0f32;
545 let mut count = 0u32;
546 for c in cols.iter().filter(|c| shrinkable(c) && c.w > c.min + 0.01) {
547 if c.w > largest + 0.01 {
548 second = if largest.is_finite() {
549 largest.max(second)
550 } else {
551 second
552 };
553 largest = c.w;
554 count = 1;
555 } else if c.w > largest - 0.01 {
556 count += 1;
557 } else if c.w > second {
558 second = c.w;
559 }
560 }
561 if count == 0 {
562 break;
563 }
564 let target = (largest - deficit / count as f32).max(second).max(0.0);
565 let mut shrunk_any = false;
566 for c in cols.iter_mut().filter(|c| shrinkable(c)) {
567 if c.w > largest - 0.01 {
568 let new = target.max(c.min);
569 if new < c.w {
570 deficit -= c.w - new;
571 c.w = new;
572 shrunk_any = true;
573 }
574 }
575 }
576 if !shrunk_any {
577 break;
578 }
579 }
580 }
581 table_apply(tree, i, &cols, false);
582}
583
584fn first_in_flow(tree: &Tree, i: u32) -> u32 {
586 let mut c = tree.first_child[i as usize];
587 while c != NIL && is_float(tree, c) {
588 c = tree.next_sibling[c as usize];
589 }
590 c
591}
592
593fn line_end(tree: &Tree, c: u32) -> u32 {
597 let l = tree.line[c as usize];
598 let mut n = tree.next_sibling[c as usize];
599 while n != NIL && (is_float(tree, n) || tree.line[n as usize] == l) {
600 n = tree.next_sibling[n as usize];
601 }
602 n
603}
604
605fn line_extents(tree: &Tree, c: u32, end: u32, gap: f32) -> (f32, f32) {
616 let baseline = tree.any_baseline && c != NIL && baseline_row(tree, tree.parent[c as usize]);
617 let mut main = 0.0f32;
618 let mut cross = 0.0f32;
619 let mut n = 0u32;
620 let mut k = c;
621 while k != end && k != NIL {
622 if !is_float(tree, k) {
623 let size = tree.size[k as usize];
624 main += size.w;
625 if !matches!(
626 child_sizing(tree, k, AxisSel::Height),
627 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_)
628 ) {
629 cross = cross.max(size.h);
630 }
631 n += 1;
632 }
633 k = tree.next_sibling[k as usize];
634 }
635 if n > 1 {
636 main += gap * (n - 1) as f32;
637 }
638 if baseline {
639 let (above, below, _) = line_baseline(tree, c, end);
642 cross = cross.max(above + below);
643 }
644 (main, cross)
645}
646
647fn wrap_measure(tree: &Tree, i: u32) -> (u32, f32, f32) {
650 let spec = tree.specs[i as usize].layout;
651 let mut lines = 0u32;
652 let mut stacked = 0.0f32;
653 let mut widest = 0.0f32;
654 let mut c = first_in_flow(tree, i);
655 while c != NIL {
656 let end = line_end(tree, c);
657 let (main, cross) = line_extents(tree, c, end, spec.gap);
658 stacked += cross;
659 widest = widest.max(main);
660 lines += 1;
661 c = end;
662 }
663 if lines > 1 {
664 stacked += spec.cross_gap * (lines - 1) as f32;
665 }
666 (lines, stacked, widest)
667}
668
669fn line_stretch(lines: u32, stacked: f32, cross_content: f32) -> f32 {
675 if lines == 0 {
676 0.0
677 } else {
678 (cross_content - stacked).max(0.0) / lines as f32
679 }
680}
681
682fn break_lines(tree: &mut Tree, i: u32, content: f32, gap: f32) {
689 let mut line = 0u32;
690 let mut used = 0.0f32;
691 let mut n = 0u32;
692 let mut c = tree.first_child[i as usize];
693 while c != NIL {
694 if !is_float(tree, c) {
695 let base = tree.size[c as usize].w;
696 let needed = if n > 0 { gap + base } else { base };
697 if n > 0 && used + needed > content + 0.01 {
698 line += 1;
699 used = base;
700 n = 1;
701 } else {
702 used += needed;
703 n += 1;
704 }
705 tree.line[c as usize] = line;
706 }
707 c = tree.next_sibling[c as usize];
708 }
709}
710
711pub trait TextMeasure {
715 fn intrinsic(&mut self, id: crate::tree::TextId) -> Size;
717 fn wrapped(&mut self, id: crate::tree::TextId, max_w: f32) -> Size;
719 fn min_content(&mut self, _id: crate::tree::TextId) -> f32 {
724 0.0
725 }
726 fn edit_intrinsic(&mut self, _key: crate::key::Key) -> Size {
728 Size::ZERO
729 }
730 fn edit_wrapped(&mut self, _key: crate::key::Key, _max_w: f32) -> Size {
732 Size::ZERO
733 }
734 fn baseline(&mut self, _id: crate::tree::TextId) -> f32 {
739 f32::NAN
740 }
741 fn edit_baseline(&mut self, _key: crate::key::Key) -> f32 {
744 f32::NAN
745 }
746 fn image_size(&mut self, _id: crate::resources::ImageId) -> Size {
748 Size::ZERO
749 }
750 fn cells_size(&mut self, _id: crate::cells::CellsId) -> Size {
752 Size::ZERO
753 }
754}
755
756pub fn compute(
762 tree: &mut Tree,
763 text: &mut dyn TextMeasure,
764 scroll: &mut ScrollStore,
765 viewport: Size,
766 scale: f32,
768) {
769 if tree.is_empty() {
770 return;
771 }
772 let declared = if tree.any_node_float {
787 declared_clamps(tree)
788 } else {
789 Vec::new()
790 };
791 fit_widths(tree, text, 0..tree.len());
792 grow_widths(tree, text, viewport);
793 fit_heights(tree, text, 0..tree.len());
794 grow_heights(tree, text, viewport);
795 positions(tree, scroll, viewport, scale, 0..tree.len());
796 if tree.any_node_float {
797 anchored(tree, text, scroll, viewport, scale, &declared);
798 }
799}
800
801type Declared = (usize, Min, f32, Min, f32);
805
806fn declared_clamps(tree: &Tree) -> Vec<Declared> {
811 let mut out = Vec::new();
812 for (c, end, _) in node_floats(tree) {
813 for i in c..end {
814 let l = &tree.specs[i].layout;
815 if l.min_w.deferred() || l.min_h.deferred() || l.max_w < 0.0 || l.max_h < 0.0 {
816 out.push((i, l.min_w, l.max_w, l.min_h, l.max_h));
817 }
818 }
819 }
820 out
821}
822
823fn anchored(
833 tree: &mut Tree,
834 text: &mut dyn TextMeasure,
835 scroll: &mut ScrollStore,
836 viewport: Size,
837 scale: f32,
838 declared: &[Declared],
839) {
840 for (c, end, key) in node_floats(tree) {
841 let Some(a) = tree.index_of(key) else {
842 tree.size[c] = Size::default();
843 continue;
844 };
845 let anchor = Rect::from_pos_size(tree.pos[a], tree.size[a]);
846 for &(i, min_w, max_w, min_h, max_h) in
849 declared.iter().filter(|(i, ..)| (c..end).contains(i))
850 {
851 let l = &mut tree.specs[i].layout;
852 (l.min_w, l.max_w, l.min_h, l.max_h) = (min_w, max_w, min_h, max_h);
853 }
854 fit_widths(tree, text, c..end);
859 if tree.any_calc_bound {
860 resolve_bounds(tree, c as u32, AxisSel::Width, anchor.w);
861 }
862 let spec = tree.specs[c].layout;
863 tree.size[c].w = spec.clamp_w(match spec.width {
864 Sizing::Grow(_) => anchor.w,
865 s => of_room(s, anchor.w).unwrap_or(tree.size[c].w),
866 });
867 for i in c..end {
868 distribute_axis(tree, text, i as u32, AxisSel::Width, viewport);
869 }
870 fit_heights(tree, text, c..end);
871 if tree.any_calc_bound {
872 resolve_bounds(tree, c as u32, AxisSel::Height, anchor.h);
873 }
874 let spec = tree.specs[c].layout;
875 tree.size[c].h = spec.clamp_h(match spec.height {
876 Sizing::Grow(_) => anchor.h,
877 s => of_room(s, anchor.h).unwrap_or(tree.size[c].h),
878 });
879 for i in c..end {
880 distribute_axis(tree, text, i as u32, AxisSel::Height, viewport);
881 }
882 place_anchored(tree, c, anchor, viewport);
883 positions(tree, scroll, viewport, scale, c..end);
884 }
885}
886
887fn node_floats(tree: &Tree) -> Vec<(usize, usize, crate::key::Key)> {
890 let mut out = Vec::new();
891 let mut c = 0usize;
892 while c < tree.len() {
893 if let Some(crate::spec::FloatConfig {
894 anchor: FloatAnchor::Node(key),
895 ..
896 }) = tree.specs[c].layout.float
897 {
898 let mut end = c + 1;
899 while end < tree.len() && (tree.parent[end] as usize) >= c {
900 end += 1;
901 }
902 out.push((c, end, key));
903 c = end;
904 } else {
905 c += 1;
906 }
907 }
908 out
909}
910
911fn place_anchored(tree: &mut Tree, c: usize, anchor: Rect, viewport: Size) {
915 let Some(cfg) = tree.specs[c].layout.float else {
916 return;
917 };
918 let vp = float_viewport(tree, c as u32, viewport);
919 tree.pos[c] = attach_fitted(&cfg, anchor, tree.size[c], vp, true);
920}
921
922pub(crate) fn reposition(tree: &mut Tree, scroll: &mut ScrollStore, viewport: Size, scale: f32) {
925 positions(tree, scroll, viewport, scale, 0..tree.len());
926 if tree.any_node_float {
927 for (c, end, key) in node_floats(tree) {
928 if let Some(a) = tree.index_of(key) {
929 let anchor = Rect::from_pos_size(tree.pos[a], tree.size[a]);
930 place_anchored(tree, c, anchor, viewport);
931 positions(tree, scroll, viewport, scale, c..end);
932 }
933 }
934 }
935}
936
937#[inline(always)]
941fn fit_width(tree: &Tree, i: usize, text: &mut dyn TextMeasure) -> f32 {
942 let spec = &tree.specs[i].layout;
943 match tree.content[i] {
944 NodeContent::Edit(key) => text.edit_intrinsic(key).w + spec.padding.x(),
945 NodeContent::Image(id, _) => text.image_size(id).w,
947 NodeContent::Cells(id) => text.cells_size(id).w + spec.padding.x(),
948 _ => {
949 let mut w = 0.0f32;
950 let mut n = 0u32;
951 for c in tree.children(i as u32) {
952 if is_float(tree, c) {
953 continue;
954 }
955 let cw = tree.size[c as usize].w;
956 if spec.dir == Dir::Row {
957 w += cw;
958 } else {
959 w = w.max(cw);
960 }
961 n += 1;
962 }
963 if spec.dir == Dir::Row && n > 1 {
964 w += spec.gap * (n - 1) as f32;
965 }
966 w + spec.padding.x()
967 }
968 }
969}
970
971fn fit_widths(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
972 for i in range.rev() {
973 if let NodeContent::Text(tid) = tree.content[i] {
974 tree.size[i].w = text.intrinsic(tid).w;
975 continue;
976 }
977 if tree.any_table && tree.specs[i].layout.is_table() {
980 table_fit(tree, i as u32);
981 }
982 let width = tree.specs[i].layout.width;
983 let min_fit = tree.specs[i].layout.min_w.is_fit();
984 let fit = if min_fit || width == Sizing::Fit {
986 fit_width(tree, i, text)
987 } else {
988 0.0
989 };
990 if min_fit {
995 tree.specs[i].layout.min_w = Min::px(fit);
996 }
997 let spec = &tree.specs[i].layout;
998 tree.size[i].w = spec.clamp_w(match width {
999 Sizing::Fixed(px) => px,
1000 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1003 Sizing::Fit => spec.aspect_width().unwrap_or(fit),
1004 });
1005 }
1006}
1007
1008fn min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1020 let spec = &tree.specs[i].layout;
1021 let (pad, scrolls) = match axis {
1022 AxisSel::Width => (spec.padding.x(), spec.scroll_x),
1023 AxisSel::Height => (spec.padding.y(), spec.scroll_y),
1024 };
1025 let own = match (tree.content[i], axis) {
1026 (NodeContent::Text(tid), AxisSel::Width) => return text.min_content(tid),
1027 (
1028 NodeContent::Text(_) | NodeContent::Image(..) | NodeContent::Cells(_),
1029 AxisSel::Height,
1030 ) => {
1031 return tree.size[i].h;
1032 }
1033 (NodeContent::Edit(_), AxisSel::Width) => 0.0,
1034 (NodeContent::Edit(key), AxisSel::Height) => {
1035 text.edit_wrapped(key, (tree.size[i].w - spec.padding.x()).max(0.0))
1036 .h
1037 }
1038 (NodeContent::Image(id, _), AxisSel::Width) => text.image_size(id).w,
1039 (NodeContent::Cells(id), AxisSel::Width) => text.cells_size(id).w,
1040 _ if scrolls || spec.clip => 0.0,
1041 _ => children_min_content(tree, text, i, axis),
1042 };
1043 own + pad
1044}
1045
1046fn children_min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1056 let spec = &tree.specs[i].layout;
1057 let main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1058 let adds = main && !(spec.wrap && axis == AxisSel::Width);
1059 let lines = axis == AxisSel::Height && wraps(tree, i as u32);
1060 let (mut total, mut n) = (0.0f32, 0u32);
1061 let (mut line, mut tallest, mut stacked) = (None, 0.0f32, 0u32);
1063 let mut c = tree.first_child[i];
1064 while c != NIL {
1065 if !is_float(tree, c) {
1066 let l = &tree.specs[c as usize].layout;
1067 let (sizing, min, max) = match axis {
1068 AxisSel::Width => (l.width, l.min_w, l.max_w_px()),
1069 AxisSel::Height => (l.height, l.min_h, l.max_h_px()),
1070 };
1071 let own = match sizing {
1072 Sizing::Fixed(px) => px,
1073 _ => min_content(tree, text, c as usize, axis),
1074 };
1075 let v = own.min(max).max(min.resolved());
1076 if lines {
1077 let l = tree.line[c as usize];
1078 if line != Some(l) {
1079 total += tallest;
1080 (line, tallest) = (Some(l), 0.0);
1081 stacked += 1;
1082 }
1083 tallest = tallest.max(v);
1084 } else if adds {
1085 total += v;
1086 } else {
1087 total = total.max(v);
1088 }
1089 n += 1;
1090 }
1091 c = tree.next_sibling[c as usize];
1092 }
1093 if lines {
1094 total += tallest + spec.cross_gap * stacked.saturating_sub(1) as f32;
1095 } else if adds && n > 1 {
1096 total += spec.gap * (n - 1) as f32;
1097 }
1098 total
1099}
1100
1101#[inline(always)]
1107fn fit_height(tree: &Tree, i: usize, text: &mut dyn TextMeasure, edit: Size) -> f32 {
1108 let spec = &tree.specs[i].layout;
1109 match tree.content[i] {
1110 NodeContent::Edit(_) => edit.h + spec.padding.y(),
1111 NodeContent::Cells(id) => text.cells_size(id).h + spec.padding.y(),
1112 NodeContent::Image(id, _) => {
1119 let intrinsic = text.image_size(id);
1120 if intrinsic.w <= 0.0 {
1121 return 0.0;
1122 }
1123 let w = if is_table_cell(tree, i as u32) {
1124 match spec.width {
1125 Sizing::Fixed(px) => spec.clamp_w(px),
1126 Sizing::Fit => spec.clamp_w(intrinsic.w),
1127 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => tree.size[i].w,
1128 }
1129 } else {
1130 tree.size[i].w
1131 };
1132 intrinsic.h * w / intrinsic.w
1133 }
1134 _ if wraps(tree, i as u32) => wrap_measure(tree, i as u32).1 + spec.padding.y(),
1137 _ if tree.any_baseline && baseline_row(tree, i as u32) => {
1140 let f = first_in_flow(tree, i as u32);
1141 line_extents(tree, f, NIL, spec.gap).1 + spec.padding.y()
1142 }
1143 _ => {
1144 let mut h = 0.0f32;
1145 let mut n = 0u32;
1146 for c in tree.children(i as u32) {
1147 if is_float(tree, c) {
1148 continue;
1149 }
1150 let ch = tree.size[c as usize].h;
1151 if spec.dir == Dir::Column {
1152 h += ch;
1153 } else {
1154 h = h.max(ch);
1155 }
1156 n += 1;
1157 }
1158 if spec.dir == Dir::Column && n > 1 {
1159 h += spec.gap * (n - 1) as f32;
1160 }
1161 h + spec.padding.y()
1162 }
1163 }
1164}
1165
1166fn fit_heights(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
1167 if tree.any_baseline {
1168 let n = tree.len();
1171 tree.baseline.resize(n, f32::NAN);
1172 }
1173 for i in range.rev() {
1174 if let NodeContent::Text(tid) = tree.content[i] {
1175 let wrapped = text.wrapped(tid, tree.size[i].w.max(0.0));
1177 if is_table_cell(tree, i as u32) {
1182 tree.size[i].h = wrapped.h;
1183 tree.size[i].w = tree.size[i].w.max(wrapped.w);
1184 } else {
1185 tree.size[i] = wrapped;
1186 }
1187 if tree.any_baseline {
1188 tree.baseline[i] = text.baseline(tid);
1189 }
1190 continue;
1191 }
1192 let edit = if let NodeContent::Edit(key) = tree.content[i] {
1195 let inner = (tree.size[i].w - tree.specs[i].layout.padding.x()).max(0.0);
1196 let wrapped = text.edit_wrapped(key, inner);
1197 if tree.any_baseline {
1198 tree.baseline[i] = tree.specs[i].layout.padding.t + text.edit_baseline(key);
1199 }
1200 wrapped
1201 } else {
1202 Size::default()
1203 };
1204 let height = tree.specs[i].layout.height;
1205 let min_fit = tree.specs[i].layout.min_h.is_fit();
1206 let fit = if min_fit || height == Sizing::Fit {
1209 fit_height(tree, i, text, edit)
1210 } else {
1211 0.0
1212 };
1213 if min_fit {
1214 tree.specs[i].layout.min_h = Min::px(fit);
1215 }
1216 let spec = &tree.specs[i].layout;
1217 tree.size[i].h = spec.clamp_h(match height {
1218 Sizing::Fixed(px) => px,
1219 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1220 Sizing::Fit if spec.aspect_height() => tree.size[i].w / spec.aspect,
1223 Sizing::Fit => fit,
1224 });
1225 }
1226}
1227
1228fn grow_widths(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1229 for i in 0..tree.len() {
1230 if tree.parent[i] == NIL {
1231 if tree.any_calc_bound {
1232 resolve_bounds(tree, i as u32, AxisSel::Width, viewport.w);
1233 }
1234 let spec = tree.specs[i].layout;
1235 tree.size[i].w = spec.clamp_w(resolve_root(spec.width, tree.size[i].w, viewport.w));
1236 }
1237 distribute_axis(tree, text, i as u32, AxisSel::Width, viewport);
1238 }
1239}
1240
1241fn grow_heights(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1242 for i in 0..tree.len() {
1243 if tree.parent[i] == NIL {
1244 if tree.any_calc_bound {
1245 resolve_bounds(tree, i as u32, AxisSel::Height, viewport.h);
1246 }
1247 let spec = tree.specs[i].layout;
1248 tree.size[i].h = spec.clamp_h(resolve_root(spec.height, tree.size[i].h, viewport.h));
1249 }
1250 distribute_axis(tree, text, i as u32, AxisSel::Height, viewport);
1251 }
1252}
1253
1254fn resolve_root(sizing: Sizing, fitted: f32, viewport: f32) -> f32 {
1255 match sizing {
1256 Sizing::Grow(_) => viewport,
1257 Sizing::Percent(p) => viewport * p,
1258 Sizing::Calc(c) => c.resolve(viewport),
1259 Sizing::Fixed(px) => px,
1260 Sizing::Fit => fitted,
1261 }
1262}
1263
1264#[derive(Clone, Copy, PartialEq)]
1265enum AxisSel {
1266 Width,
1267 Height,
1268}
1269
1270fn distribute_axis(
1273 tree: &mut Tree,
1274 text: &mut dyn TextMeasure,
1275 i: u32,
1276 axis: AxisSel,
1277 viewport: Size,
1278) {
1279 let spec = tree.specs[i as usize].layout;
1280 let (own, pad) = match axis {
1281 AxisSel::Width => (tree.size[i as usize].w, spec.padding.x()),
1282 AxisSel::Height => (tree.size[i as usize].h, spec.padding.y()),
1283 };
1284 let content = (own - pad).max(0.0);
1285 let is_main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1286
1287 if tree.any_calc_bound {
1293 let mut c = tree.first_child[i as usize];
1294 while c != NIL {
1295 if !is_float(tree, c) && resolve_bounds(tree, c, axis, content) {
1296 let now = get_axis(tree, c, axis);
1297 set_axis_clamped(tree, c, axis, now);
1298 }
1299 c = tree.next_sibling[c as usize];
1300 }
1301 }
1302
1303 if is_main && axis == AxisSel::Width && is_table_row(tree, i) {
1304 } else if is_main {
1307 let mut share = false;
1315 let mut c = tree.first_child[i as usize];
1316 while c != NIL {
1317 if !is_float(tree, c)
1318 && let Some(px) = of_room(child_sizing(tree, c, axis), content)
1319 {
1320 set_axis_clamped(tree, c, axis, px);
1321 share = true;
1322 }
1323 c = tree.next_sibling[c as usize];
1324 }
1325 let scrolls = match axis {
1326 AxisSel::Width => spec.scroll_x,
1327 AxisSel::Height => spec.scroll_y,
1328 };
1329 if wraps(tree, i) {
1330 break_lines(tree, i, content, spec.gap);
1331 let mut c = first_in_flow(tree, i);
1332 while c != NIL {
1333 let end = line_end(tree, c);
1334 let line = tree.line[c as usize];
1335 let total = distribute_run(tree, c, end, axis, content, spec.gap);
1338 let deficit = total - content;
1348 if deficit > 0.5 {
1349 shrink_axis(tree, text, i, axis, deficit, Some(line), share);
1350 }
1351 c = end;
1352 }
1353 } else {
1354 let total = distribute_run(
1357 tree,
1358 tree.first_child[i as usize],
1359 NIL,
1360 axis,
1361 content,
1362 spec.gap,
1363 );
1364 let deficit = total - content;
1365 if deficit > 0.5 && !scrolls {
1366 shrink_axis(tree, text, i, axis, deficit, None, share);
1367 }
1368 }
1369 } else if wraps(tree, i) {
1370 let (lines, stacked, _) = wrap_measure(tree, i);
1375 let stretch = line_stretch(lines, stacked, content);
1376 let mut c = first_in_flow(tree, i);
1377 while c != NIL {
1378 let end = line_end(tree, c);
1379 let extent = line_extents(tree, c, end, spec.gap).1 + stretch;
1380 let mut k = c;
1381 while k != end && k != NIL {
1382 if !is_float(tree, k) {
1383 match child_sizing(tree, k, axis) {
1384 Sizing::Grow(_) => set_axis_clamped(tree, k, axis, extent),
1385 s => {
1386 if let Some(px) = of_room(s, extent) {
1387 set_axis_clamped(tree, k, axis, px);
1388 }
1389 }
1390 }
1391 }
1392 k = tree.next_sibling[k as usize];
1393 }
1394 c = end;
1395 }
1396 } else {
1397 let fits = axis == AxisSel::Width && !spec.scroll_x;
1410 let mut c = tree.first_child[i as usize];
1411 while c != NIL {
1412 if !is_float(tree, c) {
1413 match child_sizing(tree, c, axis) {
1414 Sizing::Grow(_) => set_axis_clamped(tree, c, axis, content),
1415 Sizing::Fit
1416 if fits
1417 && tree.size[c as usize].w > content
1418 && !matches!(
1419 tree.content[c as usize],
1420 NodeContent::Text(_)
1421 | NodeContent::Cells(_)
1422 | NodeContent::Image(..)
1423 )
1424 && tree.specs[c as usize].layout.aspect_width().is_none() =>
1425 {
1426 set_axis_clamped(tree, c, axis, content)
1427 }
1428 s => {
1429 if let Some(px) = of_room(s, content) {
1430 set_axis_clamped(tree, c, axis, px);
1431 }
1432 }
1433 }
1434 }
1435 c = tree.next_sibling[c as usize];
1436 }
1437 if axis == AxisSel::Width && tree.any_table && spec.is_table() {
1439 table_resolve(tree, i);
1440 }
1441 }
1442
1443 let mut c = if tree.any_float {
1447 tree.first_child[i as usize]
1448 } else {
1449 NIL
1450 };
1451 while c != NIL {
1452 if let Some(cfg) = tree.specs[c as usize].layout.float {
1453 let vp = float_viewport(tree, c, viewport);
1454 let anchor_dim = match (cfg.anchor, axis) {
1455 (FloatAnchor::Parent, AxisSel::Width) => tree.size[i as usize].w,
1456 (FloatAnchor::Parent, AxisSel::Height) => tree.size[i as usize].h,
1457 (FloatAnchor::Viewport, AxisSel::Width) => vp.w,
1458 (FloatAnchor::Viewport, AxisSel::Height) => vp.h,
1459 (FloatAnchor::Node(_), _) => 0.0,
1461 };
1462 if tree.any_calc_bound && resolve_bounds(tree, c, axis, anchor_dim) {
1463 let now = get_axis(tree, c, axis);
1464 set_axis_clamped(tree, c, axis, now);
1465 }
1466 match child_sizing(tree, c, axis) {
1467 Sizing::Grow(_) => set_axis_clamped(tree, c, axis, anchor_dim),
1468 s => {
1469 if let Some(px) = of_room(s, anchor_dim) {
1470 set_axis_clamped(tree, c, axis, px);
1471 }
1472 }
1473 }
1474 }
1475 c = tree.next_sibling[c as usize];
1476 }
1477
1478 if axis == AxisSel::Width && tree.any_text {
1481 let mut c = tree.first_child[i as usize];
1482 while c != NIL {
1483 if matches!(tree.content[c as usize], NodeContent::Text(_))
1484 && tree.size[c as usize].w > content
1485 {
1486 tree.size[c as usize].w = content;
1487 }
1488 c = tree.next_sibling[c as usize];
1489 }
1490 }
1491}
1492
1493fn distribute_run(
1497 tree: &mut Tree,
1498 start: u32,
1499 end: u32,
1500 axis: AxisSel,
1501 content: f32,
1502 gap: f32,
1503) -> f32 {
1504 let mut used = 0.0f32;
1505 let mut grow_total = 0.0f32;
1506 let mut n = 0u32;
1507 let mut c = start;
1508 while c != end && c != NIL {
1509 if !is_float(tree, c) {
1510 match child_sizing(tree, c, axis) {
1511 Sizing::Grow(f) => grow_total += f.max(0.0),
1512 _ => used += get_axis(tree, c, axis),
1513 }
1514 n += 1;
1515 }
1516 c = tree.next_sibling[c as usize];
1517 }
1518 if n > 1 {
1519 used += gap * (n - 1) as f32;
1520 }
1521 let mut total = used;
1522 if grow_total > 0.0 {
1523 const FROZEN: u8 = 1;
1543 const MIN_VIOLATOR: u8 = 2;
1544 const MAX_VIOLATOR: u8 = 4;
1545 let mut scratch = std::mem::take(&mut tree.grow_scratch);
1546 scratch.clear();
1547 scratch.resize(n as usize, 0);
1548 loop {
1549 let remain = (content - used).max(0.0);
1550 let mut violation = 0.0f32;
1551 let mut unfrozen = 0.0f32;
1552 let mut k = 0usize;
1553 let mut c = start;
1554 while c != end && c != NIL {
1555 if !is_float(tree, c) {
1556 if scratch[k] & FROZEN == 0
1557 && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1558 {
1559 let share = remain * f.max(0.0) / grow_total;
1560 set_axis_clamped(tree, c, axis, share);
1561 let got = get_axis(tree, c, axis);
1562 unfrozen += got;
1563 let off = got - share;
1564 scratch[k] = if off > 0.01 {
1565 violation += off;
1566 MIN_VIOLATOR
1567 } else if off < -0.01 {
1568 violation += off;
1569 MAX_VIOLATOR
1570 } else {
1571 0
1572 };
1573 }
1574 k += 1;
1575 }
1576 c = tree.next_sibling[c as usize];
1577 }
1578 if violation.abs() <= 0.01 {
1579 total = used + unfrozen;
1582 break;
1583 }
1584 let freeze = if violation > 0.0 {
1585 MIN_VIOLATOR
1586 } else {
1587 MAX_VIOLATOR
1588 };
1589 let mut k = 0usize;
1590 let mut c = start;
1591 while c != end && c != NIL {
1592 if !is_float(tree, c) {
1593 if scratch[k] & freeze != 0
1594 && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1595 {
1596 scratch[k] = FROZEN;
1597 used += get_axis(tree, c, axis);
1598 grow_total -= f.max(0.0);
1599 }
1600 k += 1;
1601 }
1602 c = tree.next_sibling[c as usize];
1603 }
1604 if grow_total <= 0.0 {
1605 total = used;
1606 break;
1607 }
1608 }
1609 tree.grow_scratch = scratch;
1610 }
1611 total
1612}
1613
1614fn shrink_axis(
1642 tree: &mut Tree,
1643 text: &mut dyn TextMeasure,
1644 i: u32,
1645 axis: AxisSel,
1646 mut deficit: f32,
1647 only_line: Option<u32>,
1648 share: bool,
1649) {
1650 let shrinkable = |tree: &Tree, c: u32| -> Option<f32> {
1651 if is_float(tree, c)
1652 || !matches!(
1653 child_sizing(tree, c, axis),
1654 Sizing::Fit | Sizing::Percent(_) | Sizing::Calc(_)
1655 )
1656 {
1657 return None;
1658 }
1659 if only_line.is_some_and(|l| tree.line[c as usize] != l) {
1660 return None;
1661 }
1662 if axis == AxisSel::Height
1665 && matches!(
1666 tree.content[c as usize],
1667 NodeContent::Text(_) | NodeContent::Edit(_) | NodeContent::Image(..)
1668 )
1669 {
1670 return None;
1671 }
1672 let spec = tree.specs[c as usize].layout;
1674 let derived = match axis {
1677 AxisSel::Width => spec.aspect_width().is_some(),
1678 AxisSel::Height => spec.aspect_height(),
1679 };
1680 if derived {
1681 return None;
1682 }
1683 Some(match axis {
1684 AxisSel::Width => spec.min_w.resolved(),
1685 AxisSel::Height => spec.min_h.resolved(),
1686 })
1687 };
1688
1689 if share {
1697 let mut c = tree.first_child[i as usize];
1698 let mut items = std::mem::take(&mut tree.shrink_scratch);
1699 items.clear();
1700 let mut share = false;
1701 while c != NIL {
1702 if let Some(declared) = shrinkable(tree, c) {
1703 share |= matches!(
1704 child_sizing(tree, c, axis),
1705 Sizing::Percent(_) | Sizing::Calc(_)
1706 );
1707 let l = &tree.specs[c as usize].layout;
1708 let (min, scrolls) = match axis {
1709 AxisSel::Width => (l.min_w, l.scroll_x),
1710 AxisSel::Height => (l.min_h, l.scroll_y),
1711 };
1712 let base = get_axis(tree, c, axis);
1713 let floor = match () {
1717 _ if !min.is_auto() => declared,
1718 _ if scrolls => 0.0,
1719 _ => min_content(tree, text, c as usize, axis),
1720 };
1721 items.push(Give {
1722 c,
1723 base,
1724 floor: floor.min(base),
1725 size: base,
1726 held: false,
1727 });
1728 }
1729 c = tree.next_sibling[c as usize];
1730 }
1731 if share {
1732 shrink_as_css(tree, axis, deficit, &mut items);
1733 }
1734 tree.shrink_scratch = items;
1735 if share {
1736 return;
1737 }
1738 }
1739
1740 let mut items = std::mem::take(&mut tree.shrink_scratch);
1750 items.clear();
1751 let mut c = tree.first_child[i as usize];
1752 while c != NIL {
1753 if let Some(declared) = shrinkable(tree, c) {
1754 let base = get_axis(tree, c, axis);
1755 let l = &tree.specs[c as usize].layout;
1756 let floor = match axis {
1757 AxisSel::Height if l.min_h.is_auto() => {
1758 if l.scroll_y || l.clip {
1759 0.0
1760 } else {
1761 min_content(tree, text, c as usize, axis)
1762 }
1763 }
1764 _ => declared,
1765 };
1766 items.push(Give {
1767 c,
1768 base,
1769 floor: floor.min(base),
1770 size: base,
1771 held: false,
1772 });
1773 }
1774 c = tree.next_sibling[c as usize];
1775 }
1776 let mut guard = 0;
1777 while deficit > 0.5 && guard < 128 {
1778 guard += 1;
1779 let mut largest = f32::NEG_INFINITY;
1780 let mut second = 0.0f32;
1781 let mut count = 0u32;
1782 for g in items.iter().filter(|g| g.size > g.floor + 0.01) {
1783 let s = g.size;
1784 if s > largest + 0.01 {
1785 second = if largest.is_finite() {
1786 largest.max(second)
1787 } else {
1788 second
1789 };
1790 largest = s;
1791 count = 1;
1792 } else if s > largest - 0.01 {
1793 count += 1;
1794 } else if s > second {
1795 second = s;
1796 }
1797 }
1798 if count == 0 {
1799 break;
1800 }
1801 let target = (largest - deficit / count as f32).max(second).max(0.0);
1802 let mut shrunk_any = false;
1803 for g in items.iter_mut().filter(|g| g.size > largest - 0.01) {
1804 let new = target.max(g.floor);
1805 if new < g.size {
1806 deficit -= g.size - new;
1807 g.size = new;
1808 shrunk_any = true;
1809 }
1810 }
1811 if !shrunk_any {
1812 break;
1813 }
1814 }
1815 for g in items.iter() {
1816 if g.size < g.base {
1817 set_axis(tree, g.c, axis, g.size);
1818 }
1819 }
1820 tree.shrink_scratch = items;
1821}
1822
1823#[inline(never)]
1830fn shrink_as_css(tree: &mut Tree, axis: AxisSel, deficit: f32, items: &mut [Give]) {
1831 for _ in 0..=items.len() {
1834 let (mut paid, mut weight) = (0.0f32, 0.0f32);
1835 for g in items.iter() {
1836 if g.held {
1837 paid += g.base - g.size;
1838 } else {
1839 weight += g.base;
1840 }
1841 }
1842 let left = deficit - paid;
1843 if left <= 0.0 || weight <= 0.0 {
1844 break;
1845 }
1846 let mut under = false;
1847 for g in items.iter_mut().filter(|g| !g.held) {
1848 let want = g.base - left * g.base / weight;
1849 if want < g.floor {
1850 (g.size, g.held, under) = (g.floor, true, true);
1851 } else {
1852 g.size = want;
1853 }
1854 }
1855 if !under {
1856 break;
1857 }
1858 }
1859 for g in items.iter() {
1860 if g.size < g.base {
1861 set_axis(tree, g.c, axis, g.size);
1862 }
1863 }
1864}
1865
1866#[derive(Clone, Copy, Debug)]
1869pub(crate) struct Give {
1870 c: u32,
1871 base: f32,
1872 floor: f32,
1873 size: f32,
1874 held: bool,
1875}
1876
1877#[inline]
1881fn of_room(sizing: Sizing, room: f32) -> Option<f32> {
1882 match sizing {
1883 Sizing::Percent(p) => Some(room * p),
1884 Sizing::Calc(c) => Some(c.resolve(room)),
1885 _ => None,
1886 }
1887}
1888
1889fn resolve_bounds(tree: &mut Tree, c: u32, axis: AxisSel, room: f32) -> bool {
1896 let l = &mut tree.specs[c as usize].layout;
1897 let (min, max) = match axis {
1898 AxisSel::Width => (&mut l.min_w, &mut l.max_w),
1899 AxisSel::Height => (&mut l.min_h, &mut l.max_h),
1900 };
1901 let mut any = false;
1902 if let Some(k) = min.as_calc() {
1903 *min = Min::px(k.resolve(room));
1904 any = true;
1905 }
1906 if let Some(k) = crate::spec::max_calc(*max) {
1907 *max = k.resolve(room);
1908 any = true;
1909 }
1910 any
1911}
1912
1913fn child_sizing(tree: &Tree, c: u32, axis: AxisSel) -> Sizing {
1914 if matches!(tree.content[c as usize], NodeContent::Text(_)) {
1915 return Sizing::Fit;
1916 }
1917 match axis {
1918 AxisSel::Width => tree.specs[c as usize].layout.width,
1919 AxisSel::Height => tree.specs[c as usize].layout.height,
1920 }
1921}
1922
1923fn get_axis(tree: &Tree, c: u32, axis: AxisSel) -> f32 {
1924 match axis {
1925 AxisSel::Width => tree.size[c as usize].w,
1926 AxisSel::Height => tree.size[c as usize].h,
1927 }
1928}
1929
1930fn set_axis(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1931 match axis {
1932 AxisSel::Width => tree.size[c as usize].w = v,
1933 AxisSel::Height => tree.size[c as usize].h = v,
1934 }
1935}
1936
1937#[inline]
1941fn set_axis_clamped(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1942 let spec = &tree.specs[c as usize].layout;
1943 let v = match axis {
1944 AxisSel::Width => spec.clamp_w(v),
1945 AxisSel::Height => spec.clamp_h(v),
1946 };
1947 set_axis(tree, c, axis, v);
1948}
1949
1950fn float_viewport(tree: &Tree, c: u32, viewport: Size) -> Rect {
1957 use crate::tree::OriginId;
1958 let window = Rect::new(0.0, 0.0, viewport.w, viewport.h);
1959 let origin = tree.origins[c as usize];
1960 if tree.host_area.w <= 0.0 || origin == OriginId::DEVTOOLS || origin == OriginId::MENU {
1961 window
1962 } else {
1963 tree.host_area
1964 }
1965}
1966
1967fn place_float(
1969 tree: &mut Tree,
1970 cfg: crate::spec::FloatConfig,
1971 c: u32,
1972 parent: Rect,
1973 viewport: Size,
1974) {
1975 let vp = float_viewport(tree, c, viewport);
1976 let anchor = match cfg.anchor {
1977 FloatAnchor::Parent => parent,
1978 FloatAnchor::Viewport => vp,
1979 FloatAnchor::Node(_) => return,
1981 };
1982 let cs = tree.size[c as usize];
1983 let mirrors = cfg.anchor == FloatAnchor::Parent;
1987 tree.pos[c as usize] = attach_fitted(&cfg, anchor, cs, vp, mirrors);
1988}
1989
1990fn attach_fitted(
1996 cfg: &crate::spec::FloatConfig,
1997 anchor: Rect,
1998 cs: Size,
1999 vp: Rect,
2000 mirrors: bool,
2001) -> Vec2 {
2002 let mut x = attach(
2003 anchor.x,
2004 anchor.w,
2005 cs.w,
2006 cfg.anchor_point.0,
2007 cfg.self_point.0,
2008 cfg.offset.x,
2009 );
2010 let mut y = attach(
2011 anchor.y,
2012 anchor.h,
2013 cs.h,
2014 cfg.anchor_point.1,
2015 cfg.self_point.1,
2016 cfg.offset.y,
2017 );
2018 if cfg.fit && mirrors {
2019 let fx = attach(
2024 anchor.x,
2025 anchor.w,
2026 cs.w,
2027 mirror(cfg.anchor_point.0),
2028 mirror(cfg.self_point.0),
2029 -cfg.offset.x,
2030 );
2031 if overflow(x - vp.x, cs.w, vp.w) > overflow(fx - vp.x, cs.w, vp.w) {
2032 x = fx;
2033 }
2034 let fy = attach(
2035 anchor.y,
2036 anchor.h,
2037 cs.h,
2038 mirror(cfg.anchor_point.1),
2039 mirror(cfg.self_point.1),
2040 -cfg.offset.y,
2041 );
2042 if overflow(y - vp.y, cs.h, vp.h) > overflow(fy - vp.y, cs.h, vp.h) {
2043 y = fy;
2044 }
2045 }
2046 if cfg.fit {
2047 x = x.min(vp.x + vp.w - cs.w).max(vp.x);
2048 y = y.min(vp.y + vp.h - cs.h).max(vp.y);
2049 }
2050 Vec2::new(x, y)
2051}
2052
2053fn positions(
2054 tree: &mut Tree,
2055 scroll: &mut ScrollStore,
2056 viewport: Size,
2057 scale: f32,
2058 range: std::ops::Range<usize>,
2059) {
2060 for i in range {
2061 if tree.parent[i] == NIL {
2062 tree.pos[i] = Vec2::ZERO;
2063 }
2064 let spec = tree.specs[i].layout;
2065 let origin = tree.pos[i];
2066 let size = tree.size[i];
2067 let wrap = wraps(tree, i as u32);
2068
2069 let (main_content, cross_content, main_pad_start, cross_pad_start) = match spec.dir {
2070 Dir::Row => (
2071 size.w - spec.padding.x(),
2072 size.h - spec.padding.y(),
2073 spec.padding.l,
2074 spec.padding.t,
2075 ),
2076 Dir::Column => (
2077 size.h - spec.padding.y(),
2078 size.w - spec.padding.x(),
2079 spec.padding.t,
2080 spec.padding.l,
2081 ),
2082 };
2083
2084 let mut stretch = 0.0f32;
2088 let (total_main, max_cross) = if wrap {
2089 let (lines, stacked, widest) = wrap_measure(tree, i as u32);
2090 stretch = line_stretch(lines, stacked, cross_content);
2091 (widest, stacked + stretch * lines as f32)
2092 } else {
2093 let mut total_main = 0.0f32;
2094 let mut max_cross = 0.0f32;
2095 let mut n = 0u32;
2096 for c in tree.children(i as u32) {
2097 if is_float(tree, c) {
2098 continue;
2099 }
2100 let (c_main, c_cross) = match spec.dir {
2101 Dir::Row => (tree.size[c as usize].w, tree.size[c as usize].h),
2102 Dir::Column => (tree.size[c as usize].h, tree.size[c as usize].w),
2103 };
2104 total_main += c_main;
2105 max_cross = max_cross.max(c_cross);
2106 n += 1;
2107 }
2108 if n > 1 {
2109 total_main += spec.gap * (n - 1) as f32;
2110 }
2111 (total_main, max_cross)
2112 };
2113
2114 let mut offset = Vec2::ZERO;
2117 let anchors = spec.anchor
2125 && !wrap
2126 && match spec.dir {
2127 Dir::Row => spec.scroll_x,
2128 Dir::Column => spec.scroll_y,
2129 };
2130 let free_main = (main_content - total_main).max(0.0);
2131 let n_main = if matches!(spec.main_align, Align::Start | Align::Center | Align::End) {
2135 0
2136 } else {
2137 tree.children(i as u32)
2138 .filter(|&c| !is_float(tree, c))
2139 .count() as u32
2140 };
2141 let (lead_main, between_main) = main_spread(spec.main_align, free_main, n_main);
2142 let content_start = main_pad_start + lead_main;
2143 if anchors && let Some((anchor, was_at)) = scroll.anchor(tree.keys[i]) {
2144 let mut at = content_start;
2145 let mut c = first_in_flow(tree, i as u32);
2146 while c != NIL {
2147 if !is_float(tree, c) {
2148 if tree.keys[c as usize] == anchor {
2149 let delta = at - was_at;
2150 if delta != 0.0 {
2151 scroll.scroll_by(
2152 tree.keys[i],
2153 match spec.dir {
2154 Dir::Row => Vec2::new(delta, 0.0),
2155 Dir::Column => Vec2::new(0.0, delta),
2156 },
2157 );
2158 }
2159 break;
2160 }
2161 let c_main = match spec.dir {
2162 Dir::Row => tree.size[c as usize].w,
2163 Dir::Column => tree.size[c as usize].h,
2164 };
2165 at += c_main + spec.gap + between_main;
2166 }
2167 c = tree.next_sibling[c as usize];
2168 }
2169 }
2170 if spec.scroll_x || spec.scroll_y {
2171 let (content_w, content_h) = match spec.dir {
2172 Dir::Row => (total_main + spec.padding.x(), max_cross + spec.padding.y()),
2173 Dir::Column => (max_cross + spec.padding.x(), total_main + spec.padding.y()),
2174 };
2175 let max = Vec2::new(
2176 if spec.scroll_x {
2177 (content_w - size.w).max(0.0)
2178 } else {
2179 0.0
2180 },
2181 if spec.scroll_y {
2182 (content_h - size.h).max(0.0)
2183 } else {
2184 0.0
2185 },
2186 );
2187 tree.scroll_max[i] = max;
2188 offset = scroll
2196 .resolve(
2197 tree.keys[i],
2198 Rect::from_pos_size(origin, size),
2199 Size::new(content_w, content_h),
2200 max,
2201 tree.specs[i].transition,
2202 )
2203 .snapped(scale);
2204 }
2205 let (main_scroll, cross_scroll) = match spec.dir {
2206 Dir::Row => (offset.x, offset.y),
2207 Dir::Column => (offset.y, offset.x),
2208 };
2209 if anchors {
2210 let mut next = None;
2214 let mut at = content_start;
2215 let mut c = first_in_flow(tree, i as u32);
2216 while c != NIL {
2217 if !is_float(tree, c) {
2218 let c_main = match spec.dir {
2219 Dir::Row => tree.size[c as usize].w,
2220 Dir::Column => tree.size[c as usize].h,
2221 };
2222 if at + c_main > main_scroll {
2223 next = Some((tree.keys[c as usize], at));
2224 break;
2225 }
2226 at += c_main + spec.gap + between_main;
2227 }
2228 c = tree.next_sibling[c as usize];
2229 }
2230 scroll.set_anchor(tree.keys[i], next);
2231 }
2232
2233 let mut c = if tree.any_float {
2236 tree.first_child[i]
2237 } else {
2238 NIL
2239 };
2240 while c != NIL {
2241 if let Some(cfg) = tree.specs[c as usize].layout.float {
2242 place_float(tree, cfg, c, Rect::from_pos_size(origin, size), viewport);
2243 }
2244 c = tree.next_sibling[c as usize];
2245 }
2246
2247 let mut cross_cursor = cross_pad_start - cross_scroll;
2253 let mut line_start = first_in_flow(tree, i as u32);
2254 while line_start != NIL {
2255 let (end, extent, run_main) = if wrap {
2256 let end = line_end(tree, line_start);
2257 let (main, cross) = line_extents(tree, line_start, end, spec.gap);
2258 (end, cross + stretch, main)
2259 } else {
2260 (NIL, cross_content, total_main)
2261 };
2262 let free = (main_content - run_main).max(0.0);
2263 let (lead, between) = if wrap {
2264 let mut n = 0u32;
2265 let mut k = line_start;
2266 while k != end && k != NIL {
2267 n += u32::from(!is_float(tree, k));
2268 k = tree.next_sibling[k as usize];
2269 }
2270 main_spread(spec.main_align, free, n)
2271 } else {
2272 (lead_main, between_main)
2273 };
2274 let base_above =
2276 if tree.any_baseline && spec.cross_align == Align::Baseline && spec.dir == Dir::Row
2277 {
2278 Some(line_baseline(tree, line_start, end).0)
2279 } else {
2280 None
2281 };
2282 let mut cursor = main_pad_start + lead - main_scroll;
2283 let mut c = line_start;
2284 while c != end && c != NIL {
2285 if is_float(tree, c) {
2286 c = tree.next_sibling[c as usize];
2287 continue;
2288 }
2289 let cs = tree.size[c as usize];
2290 let (c_main, c_cross) = match spec.dir {
2291 Dir::Row => (cs.w, cs.h),
2292 Dir::Column => (cs.h, cs.w),
2293 };
2294 let cross_off = match base_above {
2295 Some(above) if aligns_by_baseline(tree, c) => {
2296 let b = first_baseline(tree, c).unwrap_or(c_cross);
2297 cross_cursor + above - b
2298 }
2299 Some(_) => cross_cursor,
2300 None => {
2301 cross_cursor + align_factor(spec.cross_align) * (extent - c_cross).max(0.0)
2302 }
2303 };
2304 tree.pos[c as usize] = match spec.dir {
2305 Dir::Row => Vec2::new(origin.x + cursor, origin.y + cross_off),
2306 Dir::Column => Vec2::new(origin.x + cross_off, origin.y + cursor),
2307 };
2308 cursor += c_main + spec.gap + between;
2309 c = tree.next_sibling[c as usize];
2310 }
2311 cross_cursor += extent + spec.cross_gap;
2312 line_start = end;
2313 }
2314 }
2315}
2316
2317#[cfg(test)]
2318mod tests {
2319 use super::*;
2320 use crate::geom::Edges;
2321 use crate::key::Key;
2322 use crate::spec::{FloatConfig, NodeSpec};
2323 use crate::tree::{NodeContent, OriginId, TextId};
2324
2325 struct StubText;
2328
2329 impl TextMeasure for StubText {
2330 fn intrinsic(&mut self, id: TextId) -> Size {
2331 Size::new(10.0 * id.0 as f32, 20.0)
2332 }
2333 fn wrapped(&mut self, id: TextId, max_w: f32) -> Size {
2334 let full = 10.0 * id.0 as f32;
2335 if max_w <= 0.0 || full <= max_w {
2336 return Size::new(full, 20.0);
2337 }
2338 let lines = (full / max_w).ceil();
2339 Size::new(max_w, lines * 20.0)
2340 }
2341 fn baseline(&mut self, _id: TextId) -> f32 {
2343 15.0
2344 }
2345 }
2346
2347 struct T {
2348 tree: Tree,
2349 }
2350
2351 impl T {
2352 fn new(root_spec: NodeSpec) -> Self {
2353 let mut tree = Tree::new();
2354 tree.push(
2355 NIL,
2356 Key::ROOT,
2357 OriginId::HOST,
2358 root_spec,
2359 NodeContent::Container,
2360 );
2361 T { tree }
2362 }
2363
2364 fn node(&mut self, parent: u32, spec: NodeSpec) -> u32 {
2365 let key = Key::ROOT.index(self.tree.len() as u64);
2366 self.tree
2367 .push(parent, key, OriginId::HOST, spec, NodeContent::Container)
2368 }
2369
2370 fn text(&mut self, parent: u32, chars: u32) -> u32 {
2371 let key = Key::ROOT.index(self.tree.len() as u64);
2372 self.tree.push(
2373 parent,
2374 key,
2375 OriginId::HOST,
2376 NodeSpec::default(),
2377 NodeContent::Text(TextId(chars)),
2378 )
2379 }
2380
2381 fn run(&mut self, vw: f32, vh: f32) {
2382 let mut scroll = ScrollStore::default();
2383 compute(
2384 &mut self.tree,
2385 &mut StubText,
2386 &mut scroll,
2387 Size::new(vw, vh),
2388 1.0,
2389 );
2390 }
2391
2392 fn size(&self, i: u32) -> Size {
2393 self.tree.size[i as usize]
2394 }
2395
2396 fn pos(&self, i: u32) -> Vec2 {
2397 self.tree.pos[i as usize]
2398 }
2399 }
2400
2401 fn px(v: f32) -> Sizing {
2402 Sizing::Fixed(v)
2403 }
2404
2405 #[test]
2410 fn a_grow_childs_clamp_is_its_siblings_room() {
2411 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2412 let a = t.node(0, NodeSpec::row().grow_height());
2413 let b = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2414 t.run(1000.0, 1000.0);
2415 assert_eq!(t.size(b).h, 100.0);
2416 assert_eq!(t.size(a).h, 500.0);
2417
2418 let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(50.0)));
2419 let a = t.node(0, NodeSpec::row().grow_width());
2420 let b = t.node(0, NodeSpec::row().grow_width().min_width(200.0));
2421 let c = t.node(0, NodeSpec::row().grow_width().max_width(20.0));
2422 t.run(1000.0, 1000.0);
2423 assert_eq!(t.size(b).w, 200.0);
2424 assert_eq!(t.size(c).w, 20.0);
2425 assert_eq!(t.size(a).w, 80.0);
2426 assert_eq!(t.pos(c).x, 280.0);
2427 }
2428
2429 #[test]
2439 fn a_pass_freezes_only_the_violators_of_the_dominant_sign() {
2440 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2441 let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2442 let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(500.0)));
2443 let c = t.node(0, NodeSpec::row().grow_height());
2444 t.run(1000.0, 1000.0);
2445 assert_eq!(t.size(a).h, 50.0);
2446 assert_eq!(t.size(b).h, 500.0);
2447 assert_eq!(t.size(c).h, 50.0);
2448 assert_eq!(t.pos(c).y, 550.0);
2449
2450 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2451 let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2452 let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(210.0)));
2453 let c = t.node(0, NodeSpec::row().grow_height());
2454 t.run(1000.0, 1000.0);
2455 assert_eq!(t.size(a).h, 100.0);
2456 assert_eq!(t.size(b).h, 250.0);
2457 assert_eq!(t.size(c).h, 250.0);
2458 }
2459
2460 #[test]
2461 fn fit_row_sums_children_and_gaps() {
2462 let mut t = T::new(NodeSpec::row().pad(10.0).gap(5.0));
2463 let r = 0;
2464 t.node(r, NodeSpec::column().width(px(30.0)).height(px(40.0)));
2465 t.node(r, NodeSpec::column().width(px(20.0)).height(px(25.0)));
2466 t.run(1000.0, 1000.0);
2467 assert_eq!(t.size(r), Size::new(75.0, 60.0));
2469 }
2470
2471 #[test]
2472 fn fit_column_sums_heights() {
2473 let mut t = T::new(NodeSpec::column().gap(4.0));
2474 t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2475 t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0)));
2476 t.node(0, NodeSpec::row().width(px(30.0)).height(px(10.0)));
2477 t.run(1000.0, 1000.0);
2478 assert_eq!(t.size(0), Size::new(50.0, 38.0));
2479 }
2480
2481 #[test]
2482 fn grow_splits_remaining_space_by_factor() {
2483 let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(100.0)).gap(10.0));
2484 let a = t.node(0, NodeSpec::column().width(px(50.0)).height(px(10.0)));
2485 let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2486 let c = t.node(
2487 0,
2488 NodeSpec::column().width(Sizing::Grow(2.0)).height(px(10.0)),
2489 );
2490 t.run(1000.0, 1000.0);
2491 let bw = t.size(b).w;
2493 let cw = t.size(c).w;
2494 assert!((bw - 230.0 / 3.0).abs() < 0.01, "b={bw}");
2495 assert!((cw - 460.0 / 3.0).abs() < 0.01, "c={cw}");
2496 assert_eq!(t.size(a).w, 50.0);
2497 }
2498
2499 #[test]
2500 fn percent_resolves_against_content_box() {
2501 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(100.0)).pad(10.0));
2502 let a = t.node(
2503 0,
2504 NodeSpec::column()
2505 .width(Sizing::Percent(0.5))
2506 .height(Sizing::Percent(1.0)),
2507 );
2508 t.run(1000.0, 1000.0);
2509 assert_eq!(t.size(a), Size::new(90.0, 80.0)); }
2511
2512 #[test]
2513 fn cross_axis_grow_fills_content() {
2514 let mut t = T::new(
2515 NodeSpec::column()
2516 .width(px(120.0))
2517 .height(px(200.0))
2518 .pad(8.0),
2519 );
2520 let a = t.node(0, NodeSpec::row().grow_width().height(px(30.0)));
2521 t.run(1000.0, 1000.0);
2522 assert_eq!(t.size(a).w, 104.0);
2523 }
2524
2525 #[test]
2526 fn nested_fit_propagates_up() {
2527 let mut t = T::new(NodeSpec::column());
2528 let mid = t.node(0, NodeSpec::row().pad(5.0).gap(2.0));
2529 t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2530 t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2531 t.run(1000.0, 1000.0);
2532 assert_eq!(t.size(mid), Size::new(32.0, 20.0));
2533 assert_eq!(t.size(0), Size::new(32.0, 20.0));
2534 }
2535
2536 #[test]
2537 fn root_grow_takes_viewport() {
2538 let mut t = T::new(NodeSpec::column().fill());
2539 t.run(800.0, 600.0);
2540 assert_eq!(t.size(0), Size::new(800.0, 600.0));
2541 }
2542
2543 #[test]
2544 fn positions_row_with_gap_and_padding() {
2545 let mut t = T::new(
2546 NodeSpec::row()
2547 .width(px(300.0))
2548 .height(px(100.0))
2549 .pad(10.0)
2550 .gap(5.0),
2551 );
2552 let a = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2553 let b = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2554 t.run(1000.0, 1000.0);
2555 assert_eq!(t.pos(a), Vec2::new(10.0, 10.0));
2556 assert_eq!(t.pos(b), Vec2::new(55.0, 10.0));
2557 }
2558
2559 #[test]
2560 fn main_center_alignment_offsets_children() {
2561 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(50.0)).center());
2562 let a = t.node(0, NodeSpec::column().width(px(60.0)).height(px(20.0)));
2563 t.run(1000.0, 1000.0);
2564 assert_eq!(t.pos(a), Vec2::new(70.0, 15.0));
2565 }
2566
2567 #[test]
2568 fn main_end_alignment() {
2569 let mut t = T::new(
2570 NodeSpec::column()
2571 .width(px(100.0))
2572 .height(px(100.0))
2573 .main_align(Align::End)
2574 .gap(10.0),
2575 );
2576 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2577 let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2578 t.run(1000.0, 1000.0);
2579 assert_eq!(t.pos(a).y, 50.0);
2580 assert_eq!(t.pos(b).y, 80.0);
2581 }
2582
2583 fn spread(a: Align, gap: f32) -> Vec<f32> {
2586 let mut t = T::new(
2587 NodeSpec::row()
2588 .width(px(100.0))
2589 .height(px(10.0))
2590 .gap(gap)
2591 .main_align(a),
2592 );
2593 let kids: Vec<u32> = (0..3)
2594 .map(|_| t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0))))
2595 .collect();
2596 t.run(1000.0, 1000.0);
2597 kids.iter().map(|&k| t.pos(k).x).collect()
2598 }
2599
2600 #[test]
2601 fn space_between_puts_the_free_space_between_the_children() {
2602 assert_eq!(spread(Align::SpaceBetween, 0.0), [0.0, 45.0, 90.0]);
2603 assert_eq!(spread(Align::SpaceBetween, 10.0), [0.0, 45.0, 90.0]);
2605 }
2606
2607 #[test]
2608 fn space_around_gives_the_ends_half_a_share() {
2609 let x = spread(Align::SpaceAround, 0.0);
2611 let share = 70.0 / 3.0;
2612 for (i, want) in [share / 2.0, share * 1.5 + 10.0, share * 2.5 + 20.0]
2613 .into_iter()
2614 .enumerate()
2615 {
2616 assert!((x[i] - want).abs() < 1e-3, "{x:?}");
2617 }
2618 }
2619
2620 #[test]
2621 fn space_evenly_makes_every_gap_and_both_ends_equal() {
2622 assert_eq!(spread(Align::SpaceEvenly, 0.0), [17.5, 45.0, 72.5]);
2623 }
2624
2625 #[test]
2628 fn a_lone_child_under_a_spread_starts_or_centres() {
2629 for (a, want) in [
2630 (Align::SpaceBetween, 0.0),
2631 (Align::SpaceAround, 45.0),
2632 (Align::SpaceEvenly, 45.0),
2633 ] {
2634 let mut t = T::new(NodeSpec::row().width(px(100.0)).main_align(a));
2635 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2636 t.run(1000.0, 1000.0);
2637 assert_eq!(t.pos(c).x, want, "{a:?}");
2638 }
2639 }
2640
2641 #[test]
2644 fn a_spread_with_nothing_free_is_the_gaps_alone() {
2645 let mut t = T::new(
2646 NodeSpec::row()
2647 .width(px(100.0))
2648 .gap(5.0)
2649 .main_align(Align::SpaceBetween),
2650 );
2651 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2652 let b = t.node(0, NodeSpec::row().grow_width().height(px(10.0)));
2653 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2654 t.run(1000.0, 1000.0);
2655 assert_eq!((t.pos(a).x, t.pos(b).x, t.pos(c).x), (0.0, 15.0, 90.0));
2656
2657 let mut t = T::new(
2658 NodeSpec::row()
2659 .width(px(100.0))
2660 .gap(5.0)
2661 .scroll_x()
2662 .main_align(Align::SpaceEvenly),
2663 );
2664 let kids: Vec<u32> = (0..3)
2665 .map(|_| t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0))))
2666 .collect();
2667 t.run(1000.0, 1000.0);
2668 let x: Vec<f32> = kids.iter().map(|&k| t.pos(k).x).collect();
2669 assert_eq!(x, [0.0, 55.0, 110.0]);
2670 }
2671
2672 #[test]
2674 fn a_column_spreads_its_height_and_floats_take_no_share() {
2675 let mut t = T::new(
2676 NodeSpec::column()
2677 .width(px(10.0))
2678 .height(px(100.0))
2679 .main_align(Align::SpaceBetween),
2680 );
2681 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2682 t.node(
2683 0,
2684 NodeSpec::row()
2685 .width(px(5.0))
2686 .height(px(5.0))
2687 .float(FloatConfig::below()),
2688 );
2689 let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2690 t.run(1000.0, 1000.0);
2691 assert_eq!((t.pos(a).y, t.pos(b).y), (0.0, 80.0));
2692 }
2693
2694 #[test]
2696 fn a_wrapping_row_spreads_each_line_by_itself() {
2697 let mut t = T::new(
2698 NodeSpec::row()
2699 .width(px(100.0))
2700 .wrap()
2701 .main_align(Align::SpaceBetween),
2702 );
2703 let kids: Vec<u32> = (0..3)
2704 .map(|_| t.node(0, NodeSpec::row().width(px(40.0)).height(px(10.0))))
2705 .collect();
2706 t.run(1000.0, 1000.0);
2707 let at: Vec<(f32, f32)> = kids.iter().map(|&k| (t.pos(k).x, t.pos(k).y)).collect();
2708 assert_eq!(at, [(0.0, 0.0), (60.0, 0.0), (0.0, 10.0)]);
2710 }
2711
2712 #[test]
2716 fn baseline_lines_up_text_with_a_box_s_bottom_edge() {
2717 let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2718 let txt = t.text(0, 3);
2719 let bx = t.node(0, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2720 t.run(1000.0, 1000.0);
2721 assert_eq!(t.pos(bx).y, 0.0);
2722 assert_eq!(t.pos(txt).y, 25.0);
2723 assert_eq!(t.size(0).h, 45.0);
2724 }
2725
2726 #[test]
2730 fn a_container_s_baseline_is_its_first_text_s() {
2731 let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2732 let bare = t.text(0, 3);
2733 let col = t.node(
2734 0,
2735 NodeSpec::column().padding(Edges {
2736 l: 0.0,
2737 r: 0.0,
2738 t: 10.0,
2739 b: 0.0,
2740 }),
2741 );
2742 let inner = t.text(col, 3);
2743 t.node(col, NodeSpec::row().width(px(10.0)).height(px(30.0)));
2744 t.run(1000.0, 1000.0);
2745 assert_eq!(t.pos(col).y, 0.0);
2746 assert_eq!(t.pos(inner).y, 10.0);
2747 assert_eq!(t.pos(bare).y, 10.0);
2748 assert_eq!(t.size(0).h, 60.0);
2750 }
2751
2752 #[test]
2754 fn a_grow_height_child_of_a_baseline_row_fills_from_the_top() {
2755 let mut t = T::new(
2756 NodeSpec::row()
2757 .height(px(50.0))
2758 .cross_align(Align::Baseline),
2759 );
2760 let txt = t.text(0, 3);
2761 let g = t.node(0, NodeSpec::row().width(px(10.0)).grow_height());
2762 t.run(1000.0, 1000.0);
2763 assert_eq!((t.pos(g).y, t.size(g).h), (0.0, 50.0));
2764 assert_eq!(t.pos(txt).y, 0.0);
2765 }
2766
2767 #[test]
2769 fn baseline_on_a_column_is_start() {
2770 let mut t = T::new(
2771 NodeSpec::column()
2772 .width(px(100.0))
2773 .cross_align(Align::Baseline),
2774 );
2775 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2776 t.run(1000.0, 1000.0);
2777 assert_eq!(t.pos(c).x, 0.0);
2778 }
2779
2780 #[test]
2783 fn a_ratio_sizes_a_fit_height_from_the_final_width() {
2784 let mut t = T::new(NodeSpec::column().width(px(320.0)));
2785 let v = t.node(0, NodeSpec::column().grow_width().aspect_ratio(16.0 / 9.0));
2786 t.node(v, NodeSpec::row().width(px(10.0)).height(px(500.0)));
2787 t.run(1000.0, 1000.0);
2788 assert_eq!(t.size(v), Size::new(320.0, 180.0));
2789 }
2790
2791 #[test]
2793 fn a_ratio_sizes_a_fit_width_from_a_fixed_height() {
2794 let mut t = T::new(NodeSpec::row());
2795 let sq = t.node(0, NodeSpec::row().height(px(24.0)).aspect_ratio(1.0));
2796 t.run(1000.0, 1000.0);
2797 assert_eq!(t.size(sq), Size::new(24.0, 24.0));
2798 }
2799
2800 #[test]
2803 fn a_ratio_s_derived_height_is_not_shrunk() {
2804 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(100.0)));
2805 let r = t.node(0, NodeSpec::column().grow_width().aspect_ratio(1.25));
2806 let other = t.node(0, NodeSpec::column().height(Sizing::Fit));
2807 t.node(other, NodeSpec::row().width(px(10.0)).height(px(60.0)));
2808 t.run(1000.0, 1000.0);
2809 assert_eq!(t.size(r).h, 80.0);
2810 assert_eq!(t.size(other).h, 60.0);
2813 }
2814
2815 #[test]
2817 fn min_fit_floors_a_ratio_height_at_its_children() {
2818 let mut t = T::new(NodeSpec::column().width(px(100.0)));
2819 let r = t.node(
2820 0,
2821 NodeSpec::column()
2822 .grow_width()
2823 .aspect_ratio(4.0)
2824 .min_height(Min::FIT),
2825 );
2826 t.node(r, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2827 t.run(1000.0, 1000.0);
2828 assert_eq!(t.size(r).h, 40.0);
2829 }
2830
2831 #[test]
2832 fn text_gets_intrinsic_size_when_it_fits() {
2833 let mut t = T::new(NodeSpec::column().width(px(500.0)).height(px(500.0)));
2834 let txt = t.text(0, 8); t.run(1000.0, 1000.0);
2836 assert_eq!(t.size(txt), Size::new(80.0, 20.0));
2837 }
2838
2839 #[test]
2840 fn text_wraps_when_clamped_by_parent() {
2841 let mut t = T::new(
2842 NodeSpec::column()
2843 .width(px(100.0))
2844 .height(px(500.0))
2845 .pad(10.0),
2846 );
2847 let txt = t.text(0, 20); t.run(1000.0, 1000.0);
2849 assert_eq!(t.size(txt).w, 80.0);
2850 assert_eq!(t.size(txt).h, 60.0);
2851 }
2852
2853 #[test]
2854 fn text_wrapping_grows_fit_parent_height() {
2855 let mut t = T::new(NodeSpec::column().width(px(100.0)));
2856 let txt = t.text(0, 30); t.run(1000.0, 1000.0);
2858 assert_eq!(t.size(txt).h, 60.0);
2859 assert_eq!(t.size(0).h, 60.0);
2860 }
2861
2862 #[test]
2863 fn grow_with_no_space_left_gets_zero() {
2864 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2865 let a = t.node(0, NodeSpec::column().width(px(120.0)).height(px(10.0)));
2866 let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2867 t.run(1000.0, 1000.0);
2868 assert_eq!(t.size(a).w, 120.0); assert_eq!(t.size(b).w, 0.0);
2870 }
2871
2872 #[test]
2873 fn deep_nesting_positions_accumulate() {
2874 let mut t = T::new(NodeSpec::column().pad(10.0));
2875 let l1 = t.node(0, NodeSpec::column().pad(10.0));
2876 let l2 = t.node(l1, NodeSpec::column().pad(10.0));
2877 let leaf = t.node(l2, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2878 t.run(1000.0, 1000.0);
2879 assert_eq!(t.pos(leaf), Vec2::new(30.0, 30.0));
2880 assert_eq!(t.size(0), Size::new(70.0, 70.0));
2881 }
2882
2883 #[test]
2884 fn grow_respects_max_width() {
2885 let mut t = T::new(NodeSpec::row().width(px(800.0)).height(px(100.0)));
2886 let a = t.node(
2887 0,
2888 NodeSpec::column()
2889 .grow_width()
2890 .max_width(560.0)
2891 .height(px(10.0)),
2892 );
2893 t.run(1000.0, 1000.0);
2894 assert_eq!(t.size(a).w, 560.0);
2895 let mut t = T::new(NodeSpec::row().width(px(400.0)).height(px(100.0)));
2897 let a = t.node(
2898 0,
2899 NodeSpec::column()
2900 .grow_width()
2901 .max_width(560.0)
2902 .height(px(10.0)),
2903 );
2904 t.run(1000.0, 1000.0);
2905 assert_eq!(t.size(a).w, 400.0);
2906 }
2907
2908 #[test]
2909 fn min_width_forces_fit_up() {
2910 let mut t = T::new(NodeSpec::column());
2911 let a = t.node(0, NodeSpec::row().min_width(120.0).height(px(10.0)));
2912 t.node(a, NodeSpec::column().width(px(30.0)).height(px(10.0)));
2913 t.run(1000.0, 1000.0);
2914 assert_eq!(t.size(a).w, 120.0);
2915 assert_eq!(t.size(0).w, 120.0);
2917 }
2918
2919 #[test]
2920 fn percent_respects_max() {
2921 let mut t = T::new(NodeSpec::column().width(px(1000.0)).height(px(1000.0)));
2922 let a = t.node(
2923 0,
2924 NodeSpec::row()
2925 .width(Sizing::Percent(0.9))
2926 .max_width(300.0)
2927 .height(px(10.0)),
2928 );
2929 t.run(1000.0, 1000.0);
2930 assert_eq!(t.size(a).w, 300.0);
2931 }
2932
2933 #[test]
2934 fn text_rewraps_when_capped_parent_shrinks() {
2935 let build = |parent_w: f32| {
2937 let mut t = T::new(NodeSpec::column().width(px(parent_w)));
2938 let txt = t.text(0, 40); t.run(1000.0, 1000.0);
2940 t.size(txt)
2941 };
2942 let wide = build(500.0);
2943 let narrow = build(100.0);
2944 assert_eq!(wide.h, 20.0);
2945 assert_eq!(narrow.h, 80.0); }
2947
2948 #[test]
2949 fn shrink_compresses_largest_fit_child_first() {
2950 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2951 let a = t.node(0, NodeSpec::column());
2952 t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2953 let b = t.node(0, NodeSpec::column());
2954 t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2955 t.run(1000.0, 1000.0);
2956 assert_eq!(t.size(a).w, 60.0);
2958 assert_eq!(t.size(b).w, 40.0);
2959 }
2960
2961 #[test]
2962 fn shrink_respects_min_and_spills_to_the_next() {
2963 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2964 let a = t.node(0, NodeSpec::column().min_width(70.0));
2965 t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2966 let b = t.node(0, NodeSpec::column());
2967 t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2968 t.run(1000.0, 1000.0);
2969 assert_eq!(t.size(a).w, 70.0);
2971 assert_eq!(t.size(b).w, 30.0);
2972 }
2973
2974 #[test]
2975 fn equal_children_shrink_equally() {
2976 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2977 let mut kids = Vec::new();
2978 for _ in 0..3 {
2979 let c = t.node(0, NodeSpec::column());
2980 t.node(c, NodeSpec::row().width(px(60.0)).height(px(10.0)));
2981 kids.push(c);
2982 }
2983 t.run(1000.0, 1000.0);
2984 for c in kids {
2985 assert!(
2986 (t.size(c).w - 100.0 / 3.0).abs() < 0.1,
2987 "got {}",
2988 t.size(c).w
2989 );
2990 }
2991 }
2992
2993 #[test]
2994 fn shrunk_text_rewraps() {
2995 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(500.0)));
2996 t.node(0, NodeSpec::column().width(px(80.0)).height(px(10.0)));
2997 let txt = t.text(0, 20); t.run(1000.0, 1000.0);
2999 assert_eq!(t.size(txt).w, 120.0);
3001 assert_eq!(t.size(txt).h, 40.0);
3002 }
3003
3004 #[test]
3005 fn text_never_shrinks_vertically() {
3006 let mut t = T::new(NodeSpec::column().width(px(200.0)).height(px(30.0)));
3007 let txt = t.text(0, 30); t.run(1000.0, 1000.0);
3009 assert_eq!(
3010 t.size(txt).h,
3011 40.0,
3012 "text overflows rather than clipping lines"
3013 );
3014 }
3015
3016 #[test]
3017 fn scroll_axis_skips_shrink() {
3018 let mut t = T::new(
3019 NodeSpec::column()
3020 .width(px(100.0))
3021 .height(px(100.0))
3022 .scroll_y(),
3023 );
3024 for _ in 0..2 {
3025 let c = t.node(0, NodeSpec::column());
3026 t.node(c, NodeSpec::row().width(px(10.0)).height(px(80.0)));
3027 }
3028 t.run(1000.0, 1000.0);
3029 for c in [1u32, 3u32] {
3031 assert_eq!(t.size(c).h, 80.0);
3032 }
3033 }
3034
3035 #[test]
3041 fn grow_tabs_split_evenly_then_scroll_at_their_min() {
3042 let bar = || NodeSpec::row().width(px(600.0)).height(px(30.0)).scroll_x();
3043 let tab = || {
3044 NodeSpec::column()
3045 .grow_width()
3046 .min_width(80.0)
3047 .height(px(30.0))
3048 };
3049 let mut t = T::new(bar());
3051 let tabs: Vec<u32> = (0..3).map(|_| t.node(0, tab())).collect();
3052 t.run(1000.0, 1000.0);
3053 for (k, &c) in tabs.iter().enumerate() {
3054 assert_eq!(t.size(c).w, 200.0);
3055 assert_eq!(t.pos(c).x, 200.0 * k as f32);
3056 }
3057 assert_eq!(t.tree.scroll_max[0].x, 0.0);
3058 let mut t = T::new(bar());
3060 let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3061 t.run(1000.0, 1000.0);
3062 for (k, &c) in tabs.iter().enumerate() {
3063 assert_eq!(t.size(c).w, 80.0);
3064 assert_eq!(t.pos(c).x, 80.0 * k as f32);
3065 }
3066 assert_eq!(t.tree.scroll_max[0].x, 200.0);
3067 let mut t = T::new(NodeSpec::row().width(px(600.0)).height(px(30.0)));
3070 let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3071 t.run(1000.0, 1000.0);
3072 for &c in &tabs {
3073 assert_eq!(t.size(c).w, 80.0);
3074 }
3075 let mut t = T::new(bar());
3080 let tabs: Vec<u32> = (0..10)
3081 .map(|_| {
3082 let c = t.node(0, tab().min_width(Min::FIT));
3083 t.text(c, 8);
3084 c
3085 })
3086 .collect();
3087 t.run(1000.0, 1000.0);
3088 for (k, &c) in tabs.iter().enumerate() {
3089 assert_eq!(t.size(c).w, 80.0);
3090 assert_eq!(t.pos(c).x, 80.0 * k as f32);
3091 assert_eq!(t.tree.specs[c as usize].layout.min_w, Min::px(80.0));
3092 }
3093 assert_eq!(t.tree.scroll_max[0].x, 200.0);
3094 let mut t = T::new(bar());
3096 let tabs: Vec<u32> = (0..3)
3097 .map(|_| {
3098 let c = t.node(0, tab().min_width(Min::FIT));
3099 t.text(c, 8);
3100 c
3101 })
3102 .collect();
3103 t.run(1000.0, 1000.0);
3104 for &c in &tabs {
3105 assert_eq!(t.size(c).w, 200.0);
3106 }
3107 }
3108
3109 #[test]
3113 fn min_fit_floors_a_percent_height_at_its_content() {
3114 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(20.0)));
3115 let a = t.node(
3116 0,
3117 NodeSpec::column()
3118 .width(px(10.0))
3119 .height(Sizing::Percent(0.5))
3120 .min_height(Min::FIT),
3121 );
3122 t.node(a, NodeSpec::column().width(px(10.0)).height(px(16.0)));
3123 let b = t.node(
3124 0,
3125 NodeSpec::column()
3126 .width(px(10.0))
3127 .height(Sizing::Percent(0.5))
3128 .min_height(Min::FIT),
3129 );
3130 t.run(1000.0, 1000.0);
3131 assert_eq!(t.size(a).h, 16.0);
3132 assert_eq!(t.size(b).h, 10.0);
3133 }
3134
3135 #[test]
3136 fn padding_asymmetric() {
3137 let mut t = T::new(
3138 NodeSpec::column()
3139 .width(px(100.0))
3140 .height(px(100.0))
3141 .padding(Edges {
3142 l: 1.0,
3143 r: 2.0,
3144 t: 3.0,
3145 b: 4.0,
3146 }),
3147 );
3148 let a = t.node(0, NodeSpec::row().fill());
3149 t.run(1000.0, 1000.0);
3150 assert_eq!(t.pos(a), Vec2::new(1.0, 3.0));
3151 assert_eq!(t.size(a), Size::new(97.0, 93.0));
3152 }
3153}