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]
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#[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#[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#[derive(Clone, Copy, Debug, Default)]
277struct Col {
278 fit: f32,
281 grow: f32,
284 pct: f32,
287 calc: Option<f32>,
295 fixed: bool,
297 min: f32,
300 max: f32,
301 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
311fn 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
356fn 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
376fn 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
429fn 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
441fn table_resolve(tree: &mut Tree, i: u32) {
451 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 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 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 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
586fn 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
595fn 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
607fn 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 let (above, below, _) = line_baseline(tree, c, end);
644 cross = cross.max(above + below);
645 }
646 (main, cross)
647}
648
649fn 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
671fn 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
684fn 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 fn intrinsic(&mut self, id: crate::tree::TextId) -> Size;
716 fn wrapped(&mut self, id: crate::tree::TextId, max_w: f32) -> Size;
718 fn min_content(&mut self, _id: crate::tree::TextId) -> f32 {
723 0.0
724 }
725 fn edit_intrinsic(&mut self, _key: crate::key::Key) -> Size {
727 Size::ZERO
728 }
729 fn edit_wrapped(&mut self, _key: crate::key::Key, _max_w: f32) -> Size {
731 Size::ZERO
732 }
733 fn baseline(&mut self, _id: crate::tree::TextId) -> f32 {
738 f32::NAN
739 }
740 fn edit_baseline(&mut self, _key: crate::key::Key) -> f32 {
743 f32::NAN
744 }
745 fn image_size(&mut self, _id: crate::resources::ImageId) -> Size {
747 Size::ZERO
748 }
749 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 scale: f32,
762) {
763 if tree.is_empty() {
764 return;
765 }
766 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
795type Declared = (usize, Min, f32, Min, f32);
799
800fn 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
817fn 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 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 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
881fn 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
905fn 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
931pub(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#[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 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 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 let fit = if min_fit || width == Sizing::Fit {
995 fit_width(tree, i, text)
996 } else {
997 0.0
998 };
999 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 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1012 Sizing::Fit => spec.aspect_width().unwrap_or(fit),
1013 });
1014 }
1015}
1016
1017fn 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
1055fn 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 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#[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 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 _ if wraps(tree, i as u32) => wrap_measure(tree, i as u32).1 + spec.padding.y(),
1146 _ 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 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 let wrapped = text.wrapped(tid, tree.size[i].w.max(0.0));
1186 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 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 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 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
1279fn 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 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 } else if is_main {
1316 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 let total = distribute_run(tree, c, end, axis, content, spec.gap);
1347 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 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 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 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 if axis == AxisSel::Width && tree.any_table && spec.is_table() {
1448 table_resolve(tree, i);
1449 }
1450 }
1451
1452 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 (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 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
1502fn 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 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 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
1623fn 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 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 let spec = tree.specs[c as usize].layout;
1683 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 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 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 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#[inline(never)]
1839fn shrink_as_css(tree: &mut Tree, axis: AxisSel, deficit: f32, items: &mut [Give]) {
1840 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#[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#[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
1898fn 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#[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
1959fn 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
1976fn 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 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 if cfg.fit && cfg.anchor == FloatAnchor::Parent {
2012 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 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 let mut offset = Vec2::ZERO;
2110 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 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 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 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 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 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 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 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 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 #[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 #[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 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 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)); }
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 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 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 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 #[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 #[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 #[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 #[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 assert_eq!(at, [(0.0, 0.0), (60.0, 0.0), (0.0, 10.0)]);
2703 }
2704
2705 #[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 #[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 assert_eq!(t.size(0).h, 60.0);
2743 }
2744
2745 #[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 #[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 #[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 #[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 #[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 assert_eq!(t.size(other).h, 60.0);
2806 }
2807
2808 #[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); 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); 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); 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); 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 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 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 let build = |parent_w: f32| {
2930 let mut t = T::new(NodeSpec::column().width(px(parent_w)));
2931 let txt = t.text(0, 40); 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); }
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 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 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); t.run(1000.0, 1000.0);
2992 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); 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 for c in [1u32, 3u32] {
3024 assert_eq!(t.size(c).h, 80.0);
3025 }
3026 }
3027
3028 #[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 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 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 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 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 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 #[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}