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);
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) {
913 let Some(cfg) = tree.specs[c].layout.float else {
914 return;
915 };
916 let cs = tree.size[c];
917 tree.pos[c] = Vec2::new(
918 attach(
919 anchor.x,
920 anchor.w,
921 cs.w,
922 cfg.anchor_point.0,
923 cfg.self_point.0,
924 cfg.offset.x,
925 ),
926 attach(
927 anchor.y,
928 anchor.h,
929 cs.h,
930 cfg.anchor_point.1,
931 cfg.self_point.1,
932 cfg.offset.y,
933 ),
934 );
935}
936
937pub(crate) fn reposition(tree: &mut Tree, scroll: &mut ScrollStore, viewport: Size, scale: f32) {
940 positions(tree, scroll, viewport, scale, 0..tree.len());
941 if tree.any_node_float {
942 for (c, end, key) in node_floats(tree) {
943 if let Some(a) = tree.index_of(key) {
944 let anchor = Rect::from_pos_size(tree.pos[a], tree.size[a]);
945 place_anchored(tree, c, anchor);
946 positions(tree, scroll, viewport, scale, c..end);
947 }
948 }
949 }
950}
951
952#[inline(always)]
956fn fit_width(tree: &Tree, i: usize, text: &mut dyn TextMeasure) -> f32 {
957 let spec = &tree.specs[i].layout;
958 match tree.content[i] {
959 NodeContent::Edit(key) => text.edit_intrinsic(key).w + spec.padding.x(),
960 NodeContent::Image(id, _) => text.image_size(id).w,
962 NodeContent::Cells(id) => text.cells_size(id).w + spec.padding.x(),
963 _ => {
964 let mut w = 0.0f32;
965 let mut n = 0u32;
966 for c in tree.children(i as u32) {
967 if is_float(tree, c) {
968 continue;
969 }
970 let cw = tree.size[c as usize].w;
971 if spec.dir == Dir::Row {
972 w += cw;
973 } else {
974 w = w.max(cw);
975 }
976 n += 1;
977 }
978 if spec.dir == Dir::Row && n > 1 {
979 w += spec.gap * (n - 1) as f32;
980 }
981 w + spec.padding.x()
982 }
983 }
984}
985
986fn fit_widths(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
987 for i in range.rev() {
988 if let NodeContent::Text(tid) = tree.content[i] {
989 tree.size[i].w = text.intrinsic(tid).w;
990 continue;
991 }
992 if tree.any_table && tree.specs[i].layout.is_table() {
995 table_fit(tree, i as u32);
996 }
997 let width = tree.specs[i].layout.width;
998 let min_fit = tree.specs[i].layout.min_w.is_fit();
999 let fit = if min_fit || width == Sizing::Fit {
1001 fit_width(tree, i, text)
1002 } else {
1003 0.0
1004 };
1005 if min_fit {
1010 tree.specs[i].layout.min_w = Min::px(fit);
1011 }
1012 let spec = &tree.specs[i].layout;
1013 tree.size[i].w = spec.clamp_w(match width {
1014 Sizing::Fixed(px) => px,
1015 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1018 Sizing::Fit => spec.aspect_width().unwrap_or(fit),
1019 });
1020 }
1021}
1022
1023fn min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1035 let spec = &tree.specs[i].layout;
1036 let (pad, scrolls) = match axis {
1037 AxisSel::Width => (spec.padding.x(), spec.scroll_x),
1038 AxisSel::Height => (spec.padding.y(), spec.scroll_y),
1039 };
1040 let own = match (tree.content[i], axis) {
1041 (NodeContent::Text(tid), AxisSel::Width) => return text.min_content(tid),
1042 (
1043 NodeContent::Text(_) | NodeContent::Image(..) | NodeContent::Cells(_),
1044 AxisSel::Height,
1045 ) => {
1046 return tree.size[i].h;
1047 }
1048 (NodeContent::Edit(_), AxisSel::Width) => 0.0,
1049 (NodeContent::Edit(key), AxisSel::Height) => {
1050 text.edit_wrapped(key, (tree.size[i].w - spec.padding.x()).max(0.0))
1051 .h
1052 }
1053 (NodeContent::Image(id, _), AxisSel::Width) => text.image_size(id).w,
1054 (NodeContent::Cells(id), AxisSel::Width) => text.cells_size(id).w,
1055 _ if scrolls || spec.clip => 0.0,
1056 _ => children_min_content(tree, text, i, axis),
1057 };
1058 own + pad
1059}
1060
1061fn children_min_content(tree: &Tree, text: &mut dyn TextMeasure, i: usize, axis: AxisSel) -> f32 {
1071 let spec = &tree.specs[i].layout;
1072 let main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1073 let adds = main && !(spec.wrap && axis == AxisSel::Width);
1074 let lines = axis == AxisSel::Height && wraps(tree, i as u32);
1075 let (mut total, mut n) = (0.0f32, 0u32);
1076 let (mut line, mut tallest, mut stacked) = (None, 0.0f32, 0u32);
1078 let mut c = tree.first_child[i];
1079 while c != NIL {
1080 if !is_float(tree, c) {
1081 let l = &tree.specs[c as usize].layout;
1082 let (sizing, min, max) = match axis {
1083 AxisSel::Width => (l.width, l.min_w, l.max_w_px()),
1084 AxisSel::Height => (l.height, l.min_h, l.max_h_px()),
1085 };
1086 let own = match sizing {
1087 Sizing::Fixed(px) => px,
1088 _ => min_content(tree, text, c as usize, axis),
1089 };
1090 let v = own.min(max).max(min.resolved());
1091 if lines {
1092 let l = tree.line[c as usize];
1093 if line != Some(l) {
1094 total += tallest;
1095 (line, tallest) = (Some(l), 0.0);
1096 stacked += 1;
1097 }
1098 tallest = tallest.max(v);
1099 } else if adds {
1100 total += v;
1101 } else {
1102 total = total.max(v);
1103 }
1104 n += 1;
1105 }
1106 c = tree.next_sibling[c as usize];
1107 }
1108 if lines {
1109 total += tallest + spec.cross_gap * stacked.saturating_sub(1) as f32;
1110 } else if adds && n > 1 {
1111 total += spec.gap * (n - 1) as f32;
1112 }
1113 total
1114}
1115
1116#[inline(always)]
1122fn fit_height(tree: &Tree, i: usize, text: &mut dyn TextMeasure, edit: Size) -> f32 {
1123 let spec = &tree.specs[i].layout;
1124 match tree.content[i] {
1125 NodeContent::Edit(_) => edit.h + spec.padding.y(),
1126 NodeContent::Cells(id) => text.cells_size(id).h + spec.padding.y(),
1127 NodeContent::Image(id, _) => {
1134 let intrinsic = text.image_size(id);
1135 if intrinsic.w <= 0.0 {
1136 return 0.0;
1137 }
1138 let w = if is_table_cell(tree, i as u32) {
1139 match spec.width {
1140 Sizing::Fixed(px) => spec.clamp_w(px),
1141 Sizing::Fit => spec.clamp_w(intrinsic.w),
1142 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => tree.size[i].w,
1143 }
1144 } else {
1145 tree.size[i].w
1146 };
1147 intrinsic.h * w / intrinsic.w
1148 }
1149 _ if wraps(tree, i as u32) => wrap_measure(tree, i as u32).1 + spec.padding.y(),
1152 _ if tree.any_baseline && baseline_row(tree, i as u32) => {
1155 let f = first_in_flow(tree, i as u32);
1156 line_extents(tree, f, NIL, spec.gap).1 + spec.padding.y()
1157 }
1158 _ => {
1159 let mut h = 0.0f32;
1160 let mut n = 0u32;
1161 for c in tree.children(i as u32) {
1162 if is_float(tree, c) {
1163 continue;
1164 }
1165 let ch = tree.size[c as usize].h;
1166 if spec.dir == Dir::Column {
1167 h += ch;
1168 } else {
1169 h = h.max(ch);
1170 }
1171 n += 1;
1172 }
1173 if spec.dir == Dir::Column && n > 1 {
1174 h += spec.gap * (n - 1) as f32;
1175 }
1176 h + spec.padding.y()
1177 }
1178 }
1179}
1180
1181fn fit_heights(tree: &mut Tree, text: &mut dyn TextMeasure, range: std::ops::Range<usize>) {
1182 if tree.any_baseline {
1183 let n = tree.len();
1186 tree.baseline.resize(n, f32::NAN);
1187 }
1188 for i in range.rev() {
1189 if let NodeContent::Text(tid) = tree.content[i] {
1190 let wrapped = text.wrapped(tid, tree.size[i].w.max(0.0));
1192 if is_table_cell(tree, i as u32) {
1197 tree.size[i].h = wrapped.h;
1198 tree.size[i].w = tree.size[i].w.max(wrapped.w);
1199 } else {
1200 tree.size[i] = wrapped;
1201 }
1202 if tree.any_baseline {
1203 tree.baseline[i] = text.baseline(tid);
1204 }
1205 continue;
1206 }
1207 let edit = if let NodeContent::Edit(key) = tree.content[i] {
1210 let inner = (tree.size[i].w - tree.specs[i].layout.padding.x()).max(0.0);
1211 let wrapped = text.edit_wrapped(key, inner);
1212 if tree.any_baseline {
1213 tree.baseline[i] = tree.specs[i].layout.padding.t + text.edit_baseline(key);
1214 }
1215 wrapped
1216 } else {
1217 Size::default()
1218 };
1219 let height = tree.specs[i].layout.height;
1220 let min_fit = tree.specs[i].layout.min_h.is_fit();
1221 let fit = if min_fit || height == Sizing::Fit {
1224 fit_height(tree, i, text, edit)
1225 } else {
1226 0.0
1227 };
1228 if min_fit {
1229 tree.specs[i].layout.min_h = Min::px(fit);
1230 }
1231 let spec = &tree.specs[i].layout;
1232 tree.size[i].h = spec.clamp_h(match height {
1233 Sizing::Fixed(px) => px,
1234 Sizing::Grow(_) | Sizing::Percent(_) | Sizing::Calc(_) => 0.0,
1235 Sizing::Fit if spec.aspect_height() => tree.size[i].w / spec.aspect,
1238 Sizing::Fit => fit,
1239 });
1240 }
1241}
1242
1243fn grow_widths(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1244 for i in 0..tree.len() {
1245 if tree.parent[i] == NIL {
1246 if tree.any_calc_bound {
1247 resolve_bounds(tree, i as u32, AxisSel::Width, viewport.w);
1248 }
1249 let spec = tree.specs[i].layout;
1250 tree.size[i].w = spec.clamp_w(resolve_root(spec.width, tree.size[i].w, viewport.w));
1251 }
1252 distribute_axis(tree, text, i as u32, AxisSel::Width, viewport);
1253 }
1254}
1255
1256fn grow_heights(tree: &mut Tree, text: &mut dyn TextMeasure, viewport: Size) {
1257 for i in 0..tree.len() {
1258 if tree.parent[i] == NIL {
1259 if tree.any_calc_bound {
1260 resolve_bounds(tree, i as u32, AxisSel::Height, viewport.h);
1261 }
1262 let spec = tree.specs[i].layout;
1263 tree.size[i].h = spec.clamp_h(resolve_root(spec.height, tree.size[i].h, viewport.h));
1264 }
1265 distribute_axis(tree, text, i as u32, AxisSel::Height, viewport);
1266 }
1267}
1268
1269fn resolve_root(sizing: Sizing, fitted: f32, viewport: f32) -> f32 {
1270 match sizing {
1271 Sizing::Grow(_) => viewport,
1272 Sizing::Percent(p) => viewport * p,
1273 Sizing::Calc(c) => c.resolve(viewport),
1274 Sizing::Fixed(px) => px,
1275 Sizing::Fit => fitted,
1276 }
1277}
1278
1279#[derive(Clone, Copy, PartialEq)]
1280enum AxisSel {
1281 Width,
1282 Height,
1283}
1284
1285fn distribute_axis(
1288 tree: &mut Tree,
1289 text: &mut dyn TextMeasure,
1290 i: u32,
1291 axis: AxisSel,
1292 viewport: Size,
1293) {
1294 let spec = tree.specs[i as usize].layout;
1295 let (own, pad) = match axis {
1296 AxisSel::Width => (tree.size[i as usize].w, spec.padding.x()),
1297 AxisSel::Height => (tree.size[i as usize].h, spec.padding.y()),
1298 };
1299 let content = (own - pad).max(0.0);
1300 let is_main = (spec.dir == Dir::Row) == (axis == AxisSel::Width);
1301
1302 if tree.any_calc_bound {
1308 let mut c = tree.first_child[i as usize];
1309 while c != NIL {
1310 if !is_float(tree, c) && resolve_bounds(tree, c, axis, content) {
1311 let now = get_axis(tree, c, axis);
1312 set_axis_clamped(tree, c, axis, now);
1313 }
1314 c = tree.next_sibling[c as usize];
1315 }
1316 }
1317
1318 if is_main && axis == AxisSel::Width && is_table_row(tree, i) {
1319 } else if is_main {
1322 let mut share = false;
1330 let mut c = tree.first_child[i as usize];
1331 while c != NIL {
1332 if !is_float(tree, c)
1333 && let Some(px) = of_room(child_sizing(tree, c, axis), content)
1334 {
1335 set_axis_clamped(tree, c, axis, px);
1336 share = true;
1337 }
1338 c = tree.next_sibling[c as usize];
1339 }
1340 let scrolls = match axis {
1341 AxisSel::Width => spec.scroll_x,
1342 AxisSel::Height => spec.scroll_y,
1343 };
1344 if wraps(tree, i) {
1345 break_lines(tree, i, content, spec.gap);
1346 let mut c = first_in_flow(tree, i);
1347 while c != NIL {
1348 let end = line_end(tree, c);
1349 let line = tree.line[c as usize];
1350 let total = distribute_run(tree, c, end, axis, content, spec.gap);
1353 let deficit = total - content;
1363 if deficit > 0.5 {
1364 shrink_axis(tree, text, i, axis, deficit, Some(line), share);
1365 }
1366 c = end;
1367 }
1368 } else {
1369 let total = distribute_run(
1372 tree,
1373 tree.first_child[i as usize],
1374 NIL,
1375 axis,
1376 content,
1377 spec.gap,
1378 );
1379 let deficit = total - content;
1380 if deficit > 0.5 && !scrolls {
1381 shrink_axis(tree, text, i, axis, deficit, None, share);
1382 }
1383 }
1384 } else if wraps(tree, i) {
1385 let (lines, stacked, _) = wrap_measure(tree, i);
1390 let stretch = line_stretch(lines, stacked, content);
1391 let mut c = first_in_flow(tree, i);
1392 while c != NIL {
1393 let end = line_end(tree, c);
1394 let extent = line_extents(tree, c, end, spec.gap).1 + stretch;
1395 let mut k = c;
1396 while k != end && k != NIL {
1397 if !is_float(tree, k) {
1398 match child_sizing(tree, k, axis) {
1399 Sizing::Grow(_) => set_axis_clamped(tree, k, axis, extent),
1400 s => {
1401 if let Some(px) = of_room(s, extent) {
1402 set_axis_clamped(tree, k, axis, px);
1403 }
1404 }
1405 }
1406 }
1407 k = tree.next_sibling[k as usize];
1408 }
1409 c = end;
1410 }
1411 } else {
1412 let fits = axis == AxisSel::Width && !spec.scroll_x;
1425 let mut c = tree.first_child[i as usize];
1426 while c != NIL {
1427 if !is_float(tree, c) {
1428 match child_sizing(tree, c, axis) {
1429 Sizing::Grow(_) => set_axis_clamped(tree, c, axis, content),
1430 Sizing::Fit
1431 if fits
1432 && tree.size[c as usize].w > content
1433 && !matches!(
1434 tree.content[c as usize],
1435 NodeContent::Text(_)
1436 | NodeContent::Cells(_)
1437 | NodeContent::Image(..)
1438 )
1439 && tree.specs[c as usize].layout.aspect_width().is_none() =>
1440 {
1441 set_axis_clamped(tree, c, axis, content)
1442 }
1443 s => {
1444 if let Some(px) = of_room(s, content) {
1445 set_axis_clamped(tree, c, axis, px);
1446 }
1447 }
1448 }
1449 }
1450 c = tree.next_sibling[c as usize];
1451 }
1452 if axis == AxisSel::Width && tree.any_table && spec.is_table() {
1454 table_resolve(tree, i);
1455 }
1456 }
1457
1458 let mut c = if tree.any_float {
1462 tree.first_child[i as usize]
1463 } else {
1464 NIL
1465 };
1466 while c != NIL {
1467 if let Some(cfg) = tree.specs[c as usize].layout.float {
1468 let vp = float_viewport(tree, c, viewport);
1469 let anchor_dim = match (cfg.anchor, axis) {
1470 (FloatAnchor::Parent, AxisSel::Width) => tree.size[i as usize].w,
1471 (FloatAnchor::Parent, AxisSel::Height) => tree.size[i as usize].h,
1472 (FloatAnchor::Viewport, AxisSel::Width) => vp.w,
1473 (FloatAnchor::Viewport, AxisSel::Height) => vp.h,
1474 (FloatAnchor::Node(_), _) => 0.0,
1476 };
1477 if tree.any_calc_bound && resolve_bounds(tree, c, axis, anchor_dim) {
1478 let now = get_axis(tree, c, axis);
1479 set_axis_clamped(tree, c, axis, now);
1480 }
1481 match child_sizing(tree, c, axis) {
1482 Sizing::Grow(_) => set_axis_clamped(tree, c, axis, anchor_dim),
1483 s => {
1484 if let Some(px) = of_room(s, anchor_dim) {
1485 set_axis_clamped(tree, c, axis, px);
1486 }
1487 }
1488 }
1489 }
1490 c = tree.next_sibling[c as usize];
1491 }
1492
1493 if axis == AxisSel::Width && tree.any_text {
1496 let mut c = tree.first_child[i as usize];
1497 while c != NIL {
1498 if matches!(tree.content[c as usize], NodeContent::Text(_))
1499 && tree.size[c as usize].w > content
1500 {
1501 tree.size[c as usize].w = content;
1502 }
1503 c = tree.next_sibling[c as usize];
1504 }
1505 }
1506}
1507
1508fn distribute_run(
1512 tree: &mut Tree,
1513 start: u32,
1514 end: u32,
1515 axis: AxisSel,
1516 content: f32,
1517 gap: f32,
1518) -> f32 {
1519 let mut used = 0.0f32;
1520 let mut grow_total = 0.0f32;
1521 let mut n = 0u32;
1522 let mut c = start;
1523 while c != end && c != NIL {
1524 if !is_float(tree, c) {
1525 match child_sizing(tree, c, axis) {
1526 Sizing::Grow(f) => grow_total += f.max(0.0),
1527 _ => used += get_axis(tree, c, axis),
1528 }
1529 n += 1;
1530 }
1531 c = tree.next_sibling[c as usize];
1532 }
1533 if n > 1 {
1534 used += gap * (n - 1) as f32;
1535 }
1536 let mut total = used;
1537 if grow_total > 0.0 {
1538 const FROZEN: u8 = 1;
1558 const MIN_VIOLATOR: u8 = 2;
1559 const MAX_VIOLATOR: u8 = 4;
1560 let mut scratch = std::mem::take(&mut tree.grow_scratch);
1561 scratch.clear();
1562 scratch.resize(n as usize, 0);
1563 loop {
1564 let remain = (content - used).max(0.0);
1565 let mut violation = 0.0f32;
1566 let mut unfrozen = 0.0f32;
1567 let mut k = 0usize;
1568 let mut c = start;
1569 while c != end && c != NIL {
1570 if !is_float(tree, c) {
1571 if scratch[k] & FROZEN == 0
1572 && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1573 {
1574 let share = remain * f.max(0.0) / grow_total;
1575 set_axis_clamped(tree, c, axis, share);
1576 let got = get_axis(tree, c, axis);
1577 unfrozen += got;
1578 let off = got - share;
1579 scratch[k] = if off > 0.01 {
1580 violation += off;
1581 MIN_VIOLATOR
1582 } else if off < -0.01 {
1583 violation += off;
1584 MAX_VIOLATOR
1585 } else {
1586 0
1587 };
1588 }
1589 k += 1;
1590 }
1591 c = tree.next_sibling[c as usize];
1592 }
1593 if violation.abs() <= 0.01 {
1594 total = used + unfrozen;
1597 break;
1598 }
1599 let freeze = if violation > 0.0 {
1600 MIN_VIOLATOR
1601 } else {
1602 MAX_VIOLATOR
1603 };
1604 let mut k = 0usize;
1605 let mut c = start;
1606 while c != end && c != NIL {
1607 if !is_float(tree, c) {
1608 if scratch[k] & freeze != 0
1609 && let Sizing::Grow(f) = child_sizing(tree, c, axis)
1610 {
1611 scratch[k] = FROZEN;
1612 used += get_axis(tree, c, axis);
1613 grow_total -= f.max(0.0);
1614 }
1615 k += 1;
1616 }
1617 c = tree.next_sibling[c as usize];
1618 }
1619 if grow_total <= 0.0 {
1620 total = used;
1621 break;
1622 }
1623 }
1624 tree.grow_scratch = scratch;
1625 }
1626 total
1627}
1628
1629fn shrink_axis(
1657 tree: &mut Tree,
1658 text: &mut dyn TextMeasure,
1659 i: u32,
1660 axis: AxisSel,
1661 mut deficit: f32,
1662 only_line: Option<u32>,
1663 share: bool,
1664) {
1665 let shrinkable = |tree: &Tree, c: u32| -> Option<f32> {
1666 if is_float(tree, c)
1667 || !matches!(
1668 child_sizing(tree, c, axis),
1669 Sizing::Fit | Sizing::Percent(_) | Sizing::Calc(_)
1670 )
1671 {
1672 return None;
1673 }
1674 if only_line.is_some_and(|l| tree.line[c as usize] != l) {
1675 return None;
1676 }
1677 if axis == AxisSel::Height
1680 && matches!(
1681 tree.content[c as usize],
1682 NodeContent::Text(_) | NodeContent::Edit(_) | NodeContent::Image(..)
1683 )
1684 {
1685 return None;
1686 }
1687 let spec = tree.specs[c as usize].layout;
1689 let derived = match axis {
1692 AxisSel::Width => spec.aspect_width().is_some(),
1693 AxisSel::Height => spec.aspect_height(),
1694 };
1695 if derived {
1696 return None;
1697 }
1698 Some(match axis {
1699 AxisSel::Width => spec.min_w.resolved(),
1700 AxisSel::Height => spec.min_h.resolved(),
1701 })
1702 };
1703
1704 if share {
1712 let mut c = tree.first_child[i as usize];
1713 let mut items = std::mem::take(&mut tree.shrink_scratch);
1714 items.clear();
1715 let mut share = false;
1716 while c != NIL {
1717 if let Some(declared) = shrinkable(tree, c) {
1718 share |= matches!(
1719 child_sizing(tree, c, axis),
1720 Sizing::Percent(_) | Sizing::Calc(_)
1721 );
1722 let l = &tree.specs[c as usize].layout;
1723 let (min, scrolls) = match axis {
1724 AxisSel::Width => (l.min_w, l.scroll_x),
1725 AxisSel::Height => (l.min_h, l.scroll_y),
1726 };
1727 let base = get_axis(tree, c, axis);
1728 let floor = match () {
1732 _ if !min.is_auto() => declared,
1733 _ if scrolls => 0.0,
1734 _ => min_content(tree, text, c as usize, axis),
1735 };
1736 items.push(Give {
1737 c,
1738 base,
1739 floor: floor.min(base),
1740 size: base,
1741 held: false,
1742 });
1743 }
1744 c = tree.next_sibling[c as usize];
1745 }
1746 if share {
1747 shrink_as_css(tree, axis, deficit, &mut items);
1748 }
1749 tree.shrink_scratch = items;
1750 if share {
1751 return;
1752 }
1753 }
1754
1755 let mut items = std::mem::take(&mut tree.shrink_scratch);
1765 items.clear();
1766 let mut c = tree.first_child[i as usize];
1767 while c != NIL {
1768 if let Some(declared) = shrinkable(tree, c) {
1769 let base = get_axis(tree, c, axis);
1770 let l = &tree.specs[c as usize].layout;
1771 let floor = match axis {
1772 AxisSel::Height if l.min_h.is_auto() => {
1773 if l.scroll_y || l.clip {
1774 0.0
1775 } else {
1776 min_content(tree, text, c as usize, axis)
1777 }
1778 }
1779 _ => declared,
1780 };
1781 items.push(Give {
1782 c,
1783 base,
1784 floor: floor.min(base),
1785 size: base,
1786 held: false,
1787 });
1788 }
1789 c = tree.next_sibling[c as usize];
1790 }
1791 let mut guard = 0;
1792 while deficit > 0.5 && guard < 128 {
1793 guard += 1;
1794 let mut largest = f32::NEG_INFINITY;
1795 let mut second = 0.0f32;
1796 let mut count = 0u32;
1797 for g in items.iter().filter(|g| g.size > g.floor + 0.01) {
1798 let s = g.size;
1799 if s > largest + 0.01 {
1800 second = if largest.is_finite() {
1801 largest.max(second)
1802 } else {
1803 second
1804 };
1805 largest = s;
1806 count = 1;
1807 } else if s > largest - 0.01 {
1808 count += 1;
1809 } else if s > second {
1810 second = s;
1811 }
1812 }
1813 if count == 0 {
1814 break;
1815 }
1816 let target = (largest - deficit / count as f32).max(second).max(0.0);
1817 let mut shrunk_any = false;
1818 for g in items.iter_mut().filter(|g| g.size > largest - 0.01) {
1819 let new = target.max(g.floor);
1820 if new < g.size {
1821 deficit -= g.size - new;
1822 g.size = new;
1823 shrunk_any = true;
1824 }
1825 }
1826 if !shrunk_any {
1827 break;
1828 }
1829 }
1830 for g in items.iter() {
1831 if g.size < g.base {
1832 set_axis(tree, g.c, axis, g.size);
1833 }
1834 }
1835 tree.shrink_scratch = items;
1836}
1837
1838#[inline(never)]
1845fn shrink_as_css(tree: &mut Tree, axis: AxisSel, deficit: f32, items: &mut [Give]) {
1846 for _ in 0..=items.len() {
1849 let (mut paid, mut weight) = (0.0f32, 0.0f32);
1850 for g in items.iter() {
1851 if g.held {
1852 paid += g.base - g.size;
1853 } else {
1854 weight += g.base;
1855 }
1856 }
1857 let left = deficit - paid;
1858 if left <= 0.0 || weight <= 0.0 {
1859 break;
1860 }
1861 let mut under = false;
1862 for g in items.iter_mut().filter(|g| !g.held) {
1863 let want = g.base - left * g.base / weight;
1864 if want < g.floor {
1865 (g.size, g.held, under) = (g.floor, true, true);
1866 } else {
1867 g.size = want;
1868 }
1869 }
1870 if !under {
1871 break;
1872 }
1873 }
1874 for g in items.iter() {
1875 if g.size < g.base {
1876 set_axis(tree, g.c, axis, g.size);
1877 }
1878 }
1879}
1880
1881#[derive(Clone, Copy, Debug)]
1884pub(crate) struct Give {
1885 c: u32,
1886 base: f32,
1887 floor: f32,
1888 size: f32,
1889 held: bool,
1890}
1891
1892#[inline]
1896fn of_room(sizing: Sizing, room: f32) -> Option<f32> {
1897 match sizing {
1898 Sizing::Percent(p) => Some(room * p),
1899 Sizing::Calc(c) => Some(c.resolve(room)),
1900 _ => None,
1901 }
1902}
1903
1904fn resolve_bounds(tree: &mut Tree, c: u32, axis: AxisSel, room: f32) -> bool {
1911 let l = &mut tree.specs[c as usize].layout;
1912 let (min, max) = match axis {
1913 AxisSel::Width => (&mut l.min_w, &mut l.max_w),
1914 AxisSel::Height => (&mut l.min_h, &mut l.max_h),
1915 };
1916 let mut any = false;
1917 if let Some(k) = min.as_calc() {
1918 *min = Min::px(k.resolve(room));
1919 any = true;
1920 }
1921 if let Some(k) = crate::spec::max_calc(*max) {
1922 *max = k.resolve(room);
1923 any = true;
1924 }
1925 any
1926}
1927
1928fn child_sizing(tree: &Tree, c: u32, axis: AxisSel) -> Sizing {
1929 if matches!(tree.content[c as usize], NodeContent::Text(_)) {
1930 return Sizing::Fit;
1931 }
1932 match axis {
1933 AxisSel::Width => tree.specs[c as usize].layout.width,
1934 AxisSel::Height => tree.specs[c as usize].layout.height,
1935 }
1936}
1937
1938fn get_axis(tree: &Tree, c: u32, axis: AxisSel) -> f32 {
1939 match axis {
1940 AxisSel::Width => tree.size[c as usize].w,
1941 AxisSel::Height => tree.size[c as usize].h,
1942 }
1943}
1944
1945fn set_axis(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1946 match axis {
1947 AxisSel::Width => tree.size[c as usize].w = v,
1948 AxisSel::Height => tree.size[c as usize].h = v,
1949 }
1950}
1951
1952#[inline]
1956fn set_axis_clamped(tree: &mut Tree, c: u32, axis: AxisSel, v: f32) {
1957 let spec = &tree.specs[c as usize].layout;
1958 let v = match axis {
1959 AxisSel::Width => spec.clamp_w(v),
1960 AxisSel::Height => spec.clamp_h(v),
1961 };
1962 set_axis(tree, c, axis, v);
1963}
1964
1965fn float_viewport(tree: &Tree, c: u32, viewport: Size) -> Rect {
1972 use crate::tree::OriginId;
1973 let window = Rect::new(0.0, 0.0, viewport.w, viewport.h);
1974 let origin = tree.origins[c as usize];
1975 if tree.host_area.w <= 0.0 || origin == OriginId::DEVTOOLS || origin == OriginId::MENU {
1976 window
1977 } else {
1978 tree.host_area
1979 }
1980}
1981
1982fn place_float(
1984 tree: &mut Tree,
1985 cfg: crate::spec::FloatConfig,
1986 c: u32,
1987 parent: Rect,
1988 viewport: Size,
1989) {
1990 let vp = float_viewport(tree, c, viewport);
1991 let anchor = match cfg.anchor {
1992 FloatAnchor::Parent => parent,
1993 FloatAnchor::Viewport => vp,
1994 FloatAnchor::Node(_) => return,
1996 };
1997 let cs = tree.size[c as usize];
1998 let mut x = attach(
1999 anchor.x,
2000 anchor.w,
2001 cs.w,
2002 cfg.anchor_point.0,
2003 cfg.self_point.0,
2004 cfg.offset.x,
2005 );
2006 let mut y = attach(
2007 anchor.y,
2008 anchor.h,
2009 cs.h,
2010 cfg.anchor_point.1,
2011 cfg.self_point.1,
2012 cfg.offset.y,
2013 );
2014 if cfg.fit && cfg.anchor == FloatAnchor::Parent {
2018 let fx = attach(
2023 anchor.x,
2024 anchor.w,
2025 cs.w,
2026 mirror(cfg.anchor_point.0),
2027 mirror(cfg.self_point.0),
2028 -cfg.offset.x,
2029 );
2030 if overflow(x - vp.x, cs.w, vp.w) > overflow(fx - vp.x, cs.w, vp.w) {
2031 x = fx;
2032 }
2033 let fy = attach(
2034 anchor.y,
2035 anchor.h,
2036 cs.h,
2037 mirror(cfg.anchor_point.1),
2038 mirror(cfg.self_point.1),
2039 -cfg.offset.y,
2040 );
2041 if overflow(y - vp.y, cs.h, vp.h) > overflow(fy - vp.y, cs.h, vp.h) {
2042 y = fy;
2043 }
2044 }
2045 if cfg.fit {
2046 x = x.min(vp.x + vp.w - cs.w).max(vp.x);
2047 y = y.min(vp.y + vp.h - cs.h).max(vp.y);
2048 }
2049 tree.pos[c as usize] = Vec2::new(x, y);
2050}
2051
2052fn positions(
2053 tree: &mut Tree,
2054 scroll: &mut ScrollStore,
2055 viewport: Size,
2056 scale: f32,
2057 range: std::ops::Range<usize>,
2058) {
2059 for i in range {
2060 if tree.parent[i] == NIL {
2061 tree.pos[i] = Vec2::ZERO;
2062 }
2063 let spec = tree.specs[i].layout;
2064 let origin = tree.pos[i];
2065 let size = tree.size[i];
2066 let wrap = wraps(tree, i as u32);
2067
2068 let (main_content, cross_content, main_pad_start, cross_pad_start) = match spec.dir {
2069 Dir::Row => (
2070 size.w - spec.padding.x(),
2071 size.h - spec.padding.y(),
2072 spec.padding.l,
2073 spec.padding.t,
2074 ),
2075 Dir::Column => (
2076 size.h - spec.padding.y(),
2077 size.w - spec.padding.x(),
2078 spec.padding.t,
2079 spec.padding.l,
2080 ),
2081 };
2082
2083 let mut stretch = 0.0f32;
2087 let (total_main, max_cross) = if wrap {
2088 let (lines, stacked, widest) = wrap_measure(tree, i as u32);
2089 stretch = line_stretch(lines, stacked, cross_content);
2090 (widest, stacked + stretch * lines as f32)
2091 } else {
2092 let mut total_main = 0.0f32;
2093 let mut max_cross = 0.0f32;
2094 let mut n = 0u32;
2095 for c in tree.children(i as u32) {
2096 if is_float(tree, c) {
2097 continue;
2098 }
2099 let (c_main, c_cross) = match spec.dir {
2100 Dir::Row => (tree.size[c as usize].w, tree.size[c as usize].h),
2101 Dir::Column => (tree.size[c as usize].h, tree.size[c as usize].w),
2102 };
2103 total_main += c_main;
2104 max_cross = max_cross.max(c_cross);
2105 n += 1;
2106 }
2107 if n > 1 {
2108 total_main += spec.gap * (n - 1) as f32;
2109 }
2110 (total_main, max_cross)
2111 };
2112
2113 let mut offset = Vec2::ZERO;
2116 let anchors = spec.anchor
2124 && !wrap
2125 && match spec.dir {
2126 Dir::Row => spec.scroll_x,
2127 Dir::Column => spec.scroll_y,
2128 };
2129 let free_main = (main_content - total_main).max(0.0);
2130 let n_main = if matches!(spec.main_align, Align::Start | Align::Center | Align::End) {
2134 0
2135 } else {
2136 tree.children(i as u32)
2137 .filter(|&c| !is_float(tree, c))
2138 .count() as u32
2139 };
2140 let (lead_main, between_main) = main_spread(spec.main_align, free_main, n_main);
2141 let content_start = main_pad_start + lead_main;
2142 if anchors && let Some((anchor, was_at)) = scroll.anchor(tree.keys[i]) {
2143 let mut at = content_start;
2144 let mut c = first_in_flow(tree, i as u32);
2145 while c != NIL {
2146 if !is_float(tree, c) {
2147 if tree.keys[c as usize] == anchor {
2148 let delta = at - was_at;
2149 if delta != 0.0 {
2150 scroll.scroll_by(
2151 tree.keys[i],
2152 match spec.dir {
2153 Dir::Row => Vec2::new(delta, 0.0),
2154 Dir::Column => Vec2::new(0.0, delta),
2155 },
2156 );
2157 }
2158 break;
2159 }
2160 let c_main = match spec.dir {
2161 Dir::Row => tree.size[c as usize].w,
2162 Dir::Column => tree.size[c as usize].h,
2163 };
2164 at += c_main + spec.gap + between_main;
2165 }
2166 c = tree.next_sibling[c as usize];
2167 }
2168 }
2169 if spec.scroll_x || spec.scroll_y {
2170 let (content_w, content_h) = match spec.dir {
2171 Dir::Row => (total_main + spec.padding.x(), max_cross + spec.padding.y()),
2172 Dir::Column => (max_cross + spec.padding.x(), total_main + spec.padding.y()),
2173 };
2174 let max = Vec2::new(
2175 if spec.scroll_x {
2176 (content_w - size.w).max(0.0)
2177 } else {
2178 0.0
2179 },
2180 if spec.scroll_y {
2181 (content_h - size.h).max(0.0)
2182 } else {
2183 0.0
2184 },
2185 );
2186 tree.scroll_max[i] = max;
2187 offset = scroll
2195 .resolve(
2196 tree.keys[i],
2197 Rect::from_pos_size(origin, size),
2198 Size::new(content_w, content_h),
2199 max,
2200 tree.specs[i].transition,
2201 )
2202 .snapped(scale);
2203 }
2204 let (main_scroll, cross_scroll) = match spec.dir {
2205 Dir::Row => (offset.x, offset.y),
2206 Dir::Column => (offset.y, offset.x),
2207 };
2208 if anchors {
2209 let mut next = None;
2213 let mut at = content_start;
2214 let mut c = first_in_flow(tree, i as u32);
2215 while c != NIL {
2216 if !is_float(tree, c) {
2217 let c_main = match spec.dir {
2218 Dir::Row => tree.size[c as usize].w,
2219 Dir::Column => tree.size[c as usize].h,
2220 };
2221 if at + c_main > main_scroll {
2222 next = Some((tree.keys[c as usize], at));
2223 break;
2224 }
2225 at += c_main + spec.gap + between_main;
2226 }
2227 c = tree.next_sibling[c as usize];
2228 }
2229 scroll.set_anchor(tree.keys[i], next);
2230 }
2231
2232 let mut c = if tree.any_float {
2235 tree.first_child[i]
2236 } else {
2237 NIL
2238 };
2239 while c != NIL {
2240 if let Some(cfg) = tree.specs[c as usize].layout.float {
2241 place_float(tree, cfg, c, Rect::from_pos_size(origin, size), viewport);
2242 }
2243 c = tree.next_sibling[c as usize];
2244 }
2245
2246 let mut cross_cursor = cross_pad_start - cross_scroll;
2252 let mut line_start = first_in_flow(tree, i as u32);
2253 while line_start != NIL {
2254 let (end, extent, run_main) = if wrap {
2255 let end = line_end(tree, line_start);
2256 let (main, cross) = line_extents(tree, line_start, end, spec.gap);
2257 (end, cross + stretch, main)
2258 } else {
2259 (NIL, cross_content, total_main)
2260 };
2261 let free = (main_content - run_main).max(0.0);
2262 let (lead, between) = if wrap {
2263 let mut n = 0u32;
2264 let mut k = line_start;
2265 while k != end && k != NIL {
2266 n += u32::from(!is_float(tree, k));
2267 k = tree.next_sibling[k as usize];
2268 }
2269 main_spread(spec.main_align, free, n)
2270 } else {
2271 (lead_main, between_main)
2272 };
2273 let base_above =
2275 if tree.any_baseline && spec.cross_align == Align::Baseline && spec.dir == Dir::Row
2276 {
2277 Some(line_baseline(tree, line_start, end).0)
2278 } else {
2279 None
2280 };
2281 let mut cursor = main_pad_start + lead - main_scroll;
2282 let mut c = line_start;
2283 while c != end && c != NIL {
2284 if is_float(tree, c) {
2285 c = tree.next_sibling[c as usize];
2286 continue;
2287 }
2288 let cs = tree.size[c as usize];
2289 let (c_main, c_cross) = match spec.dir {
2290 Dir::Row => (cs.w, cs.h),
2291 Dir::Column => (cs.h, cs.w),
2292 };
2293 let cross_off = match base_above {
2294 Some(above) if aligns_by_baseline(tree, c) => {
2295 let b = first_baseline(tree, c).unwrap_or(c_cross);
2296 cross_cursor + above - b
2297 }
2298 Some(_) => cross_cursor,
2299 None => {
2300 cross_cursor + align_factor(spec.cross_align) * (extent - c_cross).max(0.0)
2301 }
2302 };
2303 tree.pos[c as usize] = match spec.dir {
2304 Dir::Row => Vec2::new(origin.x + cursor, origin.y + cross_off),
2305 Dir::Column => Vec2::new(origin.x + cross_off, origin.y + cursor),
2306 };
2307 cursor += c_main + spec.gap + between;
2308 c = tree.next_sibling[c as usize];
2309 }
2310 cross_cursor += extent + spec.cross_gap;
2311 line_start = end;
2312 }
2313 }
2314}
2315
2316#[cfg(test)]
2317mod tests {
2318 use super::*;
2319 use crate::geom::Edges;
2320 use crate::key::Key;
2321 use crate::spec::{FloatConfig, NodeSpec};
2322 use crate::tree::{NodeContent, OriginId, TextId};
2323
2324 struct StubText;
2327
2328 impl TextMeasure for StubText {
2329 fn intrinsic(&mut self, id: TextId) -> Size {
2330 Size::new(10.0 * id.0 as f32, 20.0)
2331 }
2332 fn wrapped(&mut self, id: TextId, max_w: f32) -> Size {
2333 let full = 10.0 * id.0 as f32;
2334 if max_w <= 0.0 || full <= max_w {
2335 return Size::new(full, 20.0);
2336 }
2337 let lines = (full / max_w).ceil();
2338 Size::new(max_w, lines * 20.0)
2339 }
2340 fn baseline(&mut self, _id: TextId) -> f32 {
2342 15.0
2343 }
2344 }
2345
2346 struct T {
2347 tree: Tree,
2348 }
2349
2350 impl T {
2351 fn new(root_spec: NodeSpec) -> Self {
2352 let mut tree = Tree::new();
2353 tree.push(
2354 NIL,
2355 Key::ROOT,
2356 OriginId::HOST,
2357 root_spec,
2358 NodeContent::Container,
2359 );
2360 T { tree }
2361 }
2362
2363 fn node(&mut self, parent: u32, spec: NodeSpec) -> u32 {
2364 let key = Key::ROOT.index(self.tree.len() as u64);
2365 self.tree
2366 .push(parent, key, OriginId::HOST, spec, NodeContent::Container)
2367 }
2368
2369 fn text(&mut self, parent: u32, chars: u32) -> u32 {
2370 let key = Key::ROOT.index(self.tree.len() as u64);
2371 self.tree.push(
2372 parent,
2373 key,
2374 OriginId::HOST,
2375 NodeSpec::default(),
2376 NodeContent::Text(TextId(chars)),
2377 )
2378 }
2379
2380 fn run(&mut self, vw: f32, vh: f32) {
2381 let mut scroll = ScrollStore::default();
2382 compute(
2383 &mut self.tree,
2384 &mut StubText,
2385 &mut scroll,
2386 Size::new(vw, vh),
2387 1.0,
2388 );
2389 }
2390
2391 fn size(&self, i: u32) -> Size {
2392 self.tree.size[i as usize]
2393 }
2394
2395 fn pos(&self, i: u32) -> Vec2 {
2396 self.tree.pos[i as usize]
2397 }
2398 }
2399
2400 fn px(v: f32) -> Sizing {
2401 Sizing::Fixed(v)
2402 }
2403
2404 #[test]
2409 fn a_grow_childs_clamp_is_its_siblings_room() {
2410 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2411 let a = t.node(0, NodeSpec::row().grow_height());
2412 let b = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2413 t.run(1000.0, 1000.0);
2414 assert_eq!(t.size(b).h, 100.0);
2415 assert_eq!(t.size(a).h, 500.0);
2416
2417 let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(50.0)));
2418 let a = t.node(0, NodeSpec::row().grow_width());
2419 let b = t.node(0, NodeSpec::row().grow_width().min_width(200.0));
2420 let c = t.node(0, NodeSpec::row().grow_width().max_width(20.0));
2421 t.run(1000.0, 1000.0);
2422 assert_eq!(t.size(b).w, 200.0);
2423 assert_eq!(t.size(c).w, 20.0);
2424 assert_eq!(t.size(a).w, 80.0);
2425 assert_eq!(t.pos(c).x, 280.0);
2426 }
2427
2428 #[test]
2438 fn a_pass_freezes_only_the_violators_of_the_dominant_sign() {
2439 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2440 let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2441 let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(500.0)));
2442 let c = t.node(0, NodeSpec::row().grow_height());
2443 t.run(1000.0, 1000.0);
2444 assert_eq!(t.size(a).h, 50.0);
2445 assert_eq!(t.size(b).h, 500.0);
2446 assert_eq!(t.size(c).h, 50.0);
2447 assert_eq!(t.pos(c).y, 550.0);
2448
2449 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(600.0)));
2450 let a = t.node(0, NodeSpec::row().grow_height().max_height(100.0));
2451 let b = t.node(0, NodeSpec::row().grow_height().min_height(Min::px(210.0)));
2452 let c = t.node(0, NodeSpec::row().grow_height());
2453 t.run(1000.0, 1000.0);
2454 assert_eq!(t.size(a).h, 100.0);
2455 assert_eq!(t.size(b).h, 250.0);
2456 assert_eq!(t.size(c).h, 250.0);
2457 }
2458
2459 #[test]
2460 fn fit_row_sums_children_and_gaps() {
2461 let mut t = T::new(NodeSpec::row().pad(10.0).gap(5.0));
2462 let r = 0;
2463 t.node(r, NodeSpec::column().width(px(30.0)).height(px(40.0)));
2464 t.node(r, NodeSpec::column().width(px(20.0)).height(px(25.0)));
2465 t.run(1000.0, 1000.0);
2466 assert_eq!(t.size(r), Size::new(75.0, 60.0));
2468 }
2469
2470 #[test]
2471 fn fit_column_sums_heights() {
2472 let mut t = T::new(NodeSpec::column().gap(4.0));
2473 t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2474 t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0)));
2475 t.node(0, NodeSpec::row().width(px(30.0)).height(px(10.0)));
2476 t.run(1000.0, 1000.0);
2477 assert_eq!(t.size(0), Size::new(50.0, 38.0));
2478 }
2479
2480 #[test]
2481 fn grow_splits_remaining_space_by_factor() {
2482 let mut t = T::new(NodeSpec::row().width(px(300.0)).height(px(100.0)).gap(10.0));
2483 let a = t.node(0, NodeSpec::column().width(px(50.0)).height(px(10.0)));
2484 let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2485 let c = t.node(
2486 0,
2487 NodeSpec::column().width(Sizing::Grow(2.0)).height(px(10.0)),
2488 );
2489 t.run(1000.0, 1000.0);
2490 let bw = t.size(b).w;
2492 let cw = t.size(c).w;
2493 assert!((bw - 230.0 / 3.0).abs() < 0.01, "b={bw}");
2494 assert!((cw - 460.0 / 3.0).abs() < 0.01, "c={cw}");
2495 assert_eq!(t.size(a).w, 50.0);
2496 }
2497
2498 #[test]
2499 fn percent_resolves_against_content_box() {
2500 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(100.0)).pad(10.0));
2501 let a = t.node(
2502 0,
2503 NodeSpec::column()
2504 .width(Sizing::Percent(0.5))
2505 .height(Sizing::Percent(1.0)),
2506 );
2507 t.run(1000.0, 1000.0);
2508 assert_eq!(t.size(a), Size::new(90.0, 80.0)); }
2510
2511 #[test]
2512 fn cross_axis_grow_fills_content() {
2513 let mut t = T::new(
2514 NodeSpec::column()
2515 .width(px(120.0))
2516 .height(px(200.0))
2517 .pad(8.0),
2518 );
2519 let a = t.node(0, NodeSpec::row().grow_width().height(px(30.0)));
2520 t.run(1000.0, 1000.0);
2521 assert_eq!(t.size(a).w, 104.0);
2522 }
2523
2524 #[test]
2525 fn nested_fit_propagates_up() {
2526 let mut t = T::new(NodeSpec::column());
2527 let mid = t.node(0, NodeSpec::row().pad(5.0).gap(2.0));
2528 t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2529 t.node(mid, NodeSpec::column().width(px(10.0)).height(px(10.0)));
2530 t.run(1000.0, 1000.0);
2531 assert_eq!(t.size(mid), Size::new(32.0, 20.0));
2532 assert_eq!(t.size(0), Size::new(32.0, 20.0));
2533 }
2534
2535 #[test]
2536 fn root_grow_takes_viewport() {
2537 let mut t = T::new(NodeSpec::column().fill());
2538 t.run(800.0, 600.0);
2539 assert_eq!(t.size(0), Size::new(800.0, 600.0));
2540 }
2541
2542 #[test]
2543 fn positions_row_with_gap_and_padding() {
2544 let mut t = T::new(
2545 NodeSpec::row()
2546 .width(px(300.0))
2547 .height(px(100.0))
2548 .pad(10.0)
2549 .gap(5.0),
2550 );
2551 let a = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2552 let b = t.node(0, NodeSpec::column().width(px(40.0)).height(px(20.0)));
2553 t.run(1000.0, 1000.0);
2554 assert_eq!(t.pos(a), Vec2::new(10.0, 10.0));
2555 assert_eq!(t.pos(b), Vec2::new(55.0, 10.0));
2556 }
2557
2558 #[test]
2559 fn main_center_alignment_offsets_children() {
2560 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(50.0)).center());
2561 let a = t.node(0, NodeSpec::column().width(px(60.0)).height(px(20.0)));
2562 t.run(1000.0, 1000.0);
2563 assert_eq!(t.pos(a), Vec2::new(70.0, 15.0));
2564 }
2565
2566 #[test]
2567 fn main_end_alignment() {
2568 let mut t = T::new(
2569 NodeSpec::column()
2570 .width(px(100.0))
2571 .height(px(100.0))
2572 .main_align(Align::End)
2573 .gap(10.0),
2574 );
2575 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2576 let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2577 t.run(1000.0, 1000.0);
2578 assert_eq!(t.pos(a).y, 50.0);
2579 assert_eq!(t.pos(b).y, 80.0);
2580 }
2581
2582 fn spread(a: Align, gap: f32) -> Vec<f32> {
2585 let mut t = T::new(
2586 NodeSpec::row()
2587 .width(px(100.0))
2588 .height(px(10.0))
2589 .gap(gap)
2590 .main_align(a),
2591 );
2592 let kids: Vec<u32> = (0..3)
2593 .map(|_| t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0))))
2594 .collect();
2595 t.run(1000.0, 1000.0);
2596 kids.iter().map(|&k| t.pos(k).x).collect()
2597 }
2598
2599 #[test]
2600 fn space_between_puts_the_free_space_between_the_children() {
2601 assert_eq!(spread(Align::SpaceBetween, 0.0), [0.0, 45.0, 90.0]);
2602 assert_eq!(spread(Align::SpaceBetween, 10.0), [0.0, 45.0, 90.0]);
2604 }
2605
2606 #[test]
2607 fn space_around_gives_the_ends_half_a_share() {
2608 let x = spread(Align::SpaceAround, 0.0);
2610 let share = 70.0 / 3.0;
2611 for (i, want) in [share / 2.0, share * 1.5 + 10.0, share * 2.5 + 20.0]
2612 .into_iter()
2613 .enumerate()
2614 {
2615 assert!((x[i] - want).abs() < 1e-3, "{x:?}");
2616 }
2617 }
2618
2619 #[test]
2620 fn space_evenly_makes_every_gap_and_both_ends_equal() {
2621 assert_eq!(spread(Align::SpaceEvenly, 0.0), [17.5, 45.0, 72.5]);
2622 }
2623
2624 #[test]
2627 fn a_lone_child_under_a_spread_starts_or_centres() {
2628 for (a, want) in [
2629 (Align::SpaceBetween, 0.0),
2630 (Align::SpaceAround, 45.0),
2631 (Align::SpaceEvenly, 45.0),
2632 ] {
2633 let mut t = T::new(NodeSpec::row().width(px(100.0)).main_align(a));
2634 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2635 t.run(1000.0, 1000.0);
2636 assert_eq!(t.pos(c).x, want, "{a:?}");
2637 }
2638 }
2639
2640 #[test]
2643 fn a_spread_with_nothing_free_is_the_gaps_alone() {
2644 let mut t = T::new(
2645 NodeSpec::row()
2646 .width(px(100.0))
2647 .gap(5.0)
2648 .main_align(Align::SpaceBetween),
2649 );
2650 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2651 let b = t.node(0, NodeSpec::row().grow_width().height(px(10.0)));
2652 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2653 t.run(1000.0, 1000.0);
2654 assert_eq!((t.pos(a).x, t.pos(b).x, t.pos(c).x), (0.0, 15.0, 90.0));
2655
2656 let mut t = T::new(
2657 NodeSpec::row()
2658 .width(px(100.0))
2659 .gap(5.0)
2660 .scroll_x()
2661 .main_align(Align::SpaceEvenly),
2662 );
2663 let kids: Vec<u32> = (0..3)
2664 .map(|_| t.node(0, NodeSpec::row().width(px(50.0)).height(px(10.0))))
2665 .collect();
2666 t.run(1000.0, 1000.0);
2667 let x: Vec<f32> = kids.iter().map(|&k| t.pos(k).x).collect();
2668 assert_eq!(x, [0.0, 55.0, 110.0]);
2669 }
2670
2671 #[test]
2673 fn a_column_spreads_its_height_and_floats_take_no_share() {
2674 let mut t = T::new(
2675 NodeSpec::column()
2676 .width(px(10.0))
2677 .height(px(100.0))
2678 .main_align(Align::SpaceBetween),
2679 );
2680 let a = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2681 t.node(
2682 0,
2683 NodeSpec::row()
2684 .width(px(5.0))
2685 .height(px(5.0))
2686 .float(FloatConfig::below()),
2687 );
2688 let b = t.node(0, NodeSpec::row().width(px(10.0)).height(px(20.0)));
2689 t.run(1000.0, 1000.0);
2690 assert_eq!((t.pos(a).y, t.pos(b).y), (0.0, 80.0));
2691 }
2692
2693 #[test]
2695 fn a_wrapping_row_spreads_each_line_by_itself() {
2696 let mut t = T::new(
2697 NodeSpec::row()
2698 .width(px(100.0))
2699 .wrap()
2700 .main_align(Align::SpaceBetween),
2701 );
2702 let kids: Vec<u32> = (0..3)
2703 .map(|_| t.node(0, NodeSpec::row().width(px(40.0)).height(px(10.0))))
2704 .collect();
2705 t.run(1000.0, 1000.0);
2706 let at: Vec<(f32, f32)> = kids.iter().map(|&k| (t.pos(k).x, t.pos(k).y)).collect();
2707 assert_eq!(at, [(0.0, 0.0), (60.0, 0.0), (0.0, 10.0)]);
2709 }
2710
2711 #[test]
2715 fn baseline_lines_up_text_with_a_box_s_bottom_edge() {
2716 let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2717 let txt = t.text(0, 3);
2718 let bx = t.node(0, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2719 t.run(1000.0, 1000.0);
2720 assert_eq!(t.pos(bx).y, 0.0);
2721 assert_eq!(t.pos(txt).y, 25.0);
2722 assert_eq!(t.size(0).h, 45.0);
2723 }
2724
2725 #[test]
2729 fn a_container_s_baseline_is_its_first_text_s() {
2730 let mut t = T::new(NodeSpec::row().cross_align(Align::Baseline));
2731 let bare = t.text(0, 3);
2732 let col = t.node(
2733 0,
2734 NodeSpec::column().padding(Edges {
2735 l: 0.0,
2736 r: 0.0,
2737 t: 10.0,
2738 b: 0.0,
2739 }),
2740 );
2741 let inner = t.text(col, 3);
2742 t.node(col, NodeSpec::row().width(px(10.0)).height(px(30.0)));
2743 t.run(1000.0, 1000.0);
2744 assert_eq!(t.pos(col).y, 0.0);
2745 assert_eq!(t.pos(inner).y, 10.0);
2746 assert_eq!(t.pos(bare).y, 10.0);
2747 assert_eq!(t.size(0).h, 60.0);
2749 }
2750
2751 #[test]
2753 fn a_grow_height_child_of_a_baseline_row_fills_from_the_top() {
2754 let mut t = T::new(
2755 NodeSpec::row()
2756 .height(px(50.0))
2757 .cross_align(Align::Baseline),
2758 );
2759 let txt = t.text(0, 3);
2760 let g = t.node(0, NodeSpec::row().width(px(10.0)).grow_height());
2761 t.run(1000.0, 1000.0);
2762 assert_eq!((t.pos(g).y, t.size(g).h), (0.0, 50.0));
2763 assert_eq!(t.pos(txt).y, 0.0);
2764 }
2765
2766 #[test]
2768 fn baseline_on_a_column_is_start() {
2769 let mut t = T::new(
2770 NodeSpec::column()
2771 .width(px(100.0))
2772 .cross_align(Align::Baseline),
2773 );
2774 let c = t.node(0, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2775 t.run(1000.0, 1000.0);
2776 assert_eq!(t.pos(c).x, 0.0);
2777 }
2778
2779 #[test]
2782 fn a_ratio_sizes_a_fit_height_from_the_final_width() {
2783 let mut t = T::new(NodeSpec::column().width(px(320.0)));
2784 let v = t.node(0, NodeSpec::column().grow_width().aspect_ratio(16.0 / 9.0));
2785 t.node(v, NodeSpec::row().width(px(10.0)).height(px(500.0)));
2786 t.run(1000.0, 1000.0);
2787 assert_eq!(t.size(v), Size::new(320.0, 180.0));
2788 }
2789
2790 #[test]
2792 fn a_ratio_sizes_a_fit_width_from_a_fixed_height() {
2793 let mut t = T::new(NodeSpec::row());
2794 let sq = t.node(0, NodeSpec::row().height(px(24.0)).aspect_ratio(1.0));
2795 t.run(1000.0, 1000.0);
2796 assert_eq!(t.size(sq), Size::new(24.0, 24.0));
2797 }
2798
2799 #[test]
2802 fn a_ratio_s_derived_height_is_not_shrunk() {
2803 let mut t = T::new(NodeSpec::column().width(px(100.0)).height(px(100.0)));
2804 let r = t.node(0, NodeSpec::column().grow_width().aspect_ratio(1.25));
2805 let other = t.node(0, NodeSpec::column().height(Sizing::Fit));
2806 t.node(other, NodeSpec::row().width(px(10.0)).height(px(60.0)));
2807 t.run(1000.0, 1000.0);
2808 assert_eq!(t.size(r).h, 80.0);
2809 assert_eq!(t.size(other).h, 60.0);
2812 }
2813
2814 #[test]
2816 fn min_fit_floors_a_ratio_height_at_its_children() {
2817 let mut t = T::new(NodeSpec::column().width(px(100.0)));
2818 let r = t.node(
2819 0,
2820 NodeSpec::column()
2821 .grow_width()
2822 .aspect_ratio(4.0)
2823 .min_height(Min::FIT),
2824 );
2825 t.node(r, NodeSpec::row().width(px(10.0)).height(px(40.0)));
2826 t.run(1000.0, 1000.0);
2827 assert_eq!(t.size(r).h, 40.0);
2828 }
2829
2830 #[test]
2831 fn text_gets_intrinsic_size_when_it_fits() {
2832 let mut t = T::new(NodeSpec::column().width(px(500.0)).height(px(500.0)));
2833 let txt = t.text(0, 8); t.run(1000.0, 1000.0);
2835 assert_eq!(t.size(txt), Size::new(80.0, 20.0));
2836 }
2837
2838 #[test]
2839 fn text_wraps_when_clamped_by_parent() {
2840 let mut t = T::new(
2841 NodeSpec::column()
2842 .width(px(100.0))
2843 .height(px(500.0))
2844 .pad(10.0),
2845 );
2846 let txt = t.text(0, 20); t.run(1000.0, 1000.0);
2848 assert_eq!(t.size(txt).w, 80.0);
2849 assert_eq!(t.size(txt).h, 60.0);
2850 }
2851
2852 #[test]
2853 fn text_wrapping_grows_fit_parent_height() {
2854 let mut t = T::new(NodeSpec::column().width(px(100.0)));
2855 let txt = t.text(0, 30); t.run(1000.0, 1000.0);
2857 assert_eq!(t.size(txt).h, 60.0);
2858 assert_eq!(t.size(0).h, 60.0);
2859 }
2860
2861 #[test]
2862 fn grow_with_no_space_left_gets_zero() {
2863 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2864 let a = t.node(0, NodeSpec::column().width(px(120.0)).height(px(10.0)));
2865 let b = t.node(0, NodeSpec::column().grow_width().height(px(10.0)));
2866 t.run(1000.0, 1000.0);
2867 assert_eq!(t.size(a).w, 120.0); assert_eq!(t.size(b).w, 0.0);
2869 }
2870
2871 #[test]
2872 fn deep_nesting_positions_accumulate() {
2873 let mut t = T::new(NodeSpec::column().pad(10.0));
2874 let l1 = t.node(0, NodeSpec::column().pad(10.0));
2875 let l2 = t.node(l1, NodeSpec::column().pad(10.0));
2876 let leaf = t.node(l2, NodeSpec::row().width(px(10.0)).height(px(10.0)));
2877 t.run(1000.0, 1000.0);
2878 assert_eq!(t.pos(leaf), Vec2::new(30.0, 30.0));
2879 assert_eq!(t.size(0), Size::new(70.0, 70.0));
2880 }
2881
2882 #[test]
2883 fn grow_respects_max_width() {
2884 let mut t = T::new(NodeSpec::row().width(px(800.0)).height(px(100.0)));
2885 let a = t.node(
2886 0,
2887 NodeSpec::column()
2888 .grow_width()
2889 .max_width(560.0)
2890 .height(px(10.0)),
2891 );
2892 t.run(1000.0, 1000.0);
2893 assert_eq!(t.size(a).w, 560.0);
2894 let mut t = T::new(NodeSpec::row().width(px(400.0)).height(px(100.0)));
2896 let a = t.node(
2897 0,
2898 NodeSpec::column()
2899 .grow_width()
2900 .max_width(560.0)
2901 .height(px(10.0)),
2902 );
2903 t.run(1000.0, 1000.0);
2904 assert_eq!(t.size(a).w, 400.0);
2905 }
2906
2907 #[test]
2908 fn min_width_forces_fit_up() {
2909 let mut t = T::new(NodeSpec::column());
2910 let a = t.node(0, NodeSpec::row().min_width(120.0).height(px(10.0)));
2911 t.node(a, NodeSpec::column().width(px(30.0)).height(px(10.0)));
2912 t.run(1000.0, 1000.0);
2913 assert_eq!(t.size(a).w, 120.0);
2914 assert_eq!(t.size(0).w, 120.0);
2916 }
2917
2918 #[test]
2919 fn percent_respects_max() {
2920 let mut t = T::new(NodeSpec::column().width(px(1000.0)).height(px(1000.0)));
2921 let a = t.node(
2922 0,
2923 NodeSpec::row()
2924 .width(Sizing::Percent(0.9))
2925 .max_width(300.0)
2926 .height(px(10.0)),
2927 );
2928 t.run(1000.0, 1000.0);
2929 assert_eq!(t.size(a).w, 300.0);
2930 }
2931
2932 #[test]
2933 fn text_rewraps_when_capped_parent_shrinks() {
2934 let build = |parent_w: f32| {
2936 let mut t = T::new(NodeSpec::column().width(px(parent_w)));
2937 let txt = t.text(0, 40); t.run(1000.0, 1000.0);
2939 t.size(txt)
2940 };
2941 let wide = build(500.0);
2942 let narrow = build(100.0);
2943 assert_eq!(wide.h, 20.0);
2944 assert_eq!(narrow.h, 80.0); }
2946
2947 #[test]
2948 fn shrink_compresses_largest_fit_child_first() {
2949 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2950 let a = t.node(0, NodeSpec::column());
2951 t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2952 let b = t.node(0, NodeSpec::column());
2953 t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2954 t.run(1000.0, 1000.0);
2955 assert_eq!(t.size(a).w, 60.0);
2957 assert_eq!(t.size(b).w, 40.0);
2958 }
2959
2960 #[test]
2961 fn shrink_respects_min_and_spills_to_the_next() {
2962 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2963 let a = t.node(0, NodeSpec::column().min_width(70.0));
2964 t.node(a, NodeSpec::row().width(px(80.0)).height(px(10.0)));
2965 let b = t.node(0, NodeSpec::column());
2966 t.node(b, NodeSpec::row().width(px(40.0)).height(px(10.0)));
2967 t.run(1000.0, 1000.0);
2968 assert_eq!(t.size(a).w, 70.0);
2970 assert_eq!(t.size(b).w, 30.0);
2971 }
2972
2973 #[test]
2974 fn equal_children_shrink_equally() {
2975 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(50.0)));
2976 let mut kids = Vec::new();
2977 for _ in 0..3 {
2978 let c = t.node(0, NodeSpec::column());
2979 t.node(c, NodeSpec::row().width(px(60.0)).height(px(10.0)));
2980 kids.push(c);
2981 }
2982 t.run(1000.0, 1000.0);
2983 for c in kids {
2984 assert!(
2985 (t.size(c).w - 100.0 / 3.0).abs() < 0.1,
2986 "got {}",
2987 t.size(c).w
2988 );
2989 }
2990 }
2991
2992 #[test]
2993 fn shrunk_text_rewraps() {
2994 let mut t = T::new(NodeSpec::row().width(px(200.0)).height(px(500.0)));
2995 t.node(0, NodeSpec::column().width(px(80.0)).height(px(10.0)));
2996 let txt = t.text(0, 20); t.run(1000.0, 1000.0);
2998 assert_eq!(t.size(txt).w, 120.0);
3000 assert_eq!(t.size(txt).h, 40.0);
3001 }
3002
3003 #[test]
3004 fn text_never_shrinks_vertically() {
3005 let mut t = T::new(NodeSpec::column().width(px(200.0)).height(px(30.0)));
3006 let txt = t.text(0, 30); t.run(1000.0, 1000.0);
3008 assert_eq!(
3009 t.size(txt).h,
3010 40.0,
3011 "text overflows rather than clipping lines"
3012 );
3013 }
3014
3015 #[test]
3016 fn scroll_axis_skips_shrink() {
3017 let mut t = T::new(
3018 NodeSpec::column()
3019 .width(px(100.0))
3020 .height(px(100.0))
3021 .scroll_y(),
3022 );
3023 for _ in 0..2 {
3024 let c = t.node(0, NodeSpec::column());
3025 t.node(c, NodeSpec::row().width(px(10.0)).height(px(80.0)));
3026 }
3027 t.run(1000.0, 1000.0);
3028 for c in [1u32, 3u32] {
3030 assert_eq!(t.size(c).h, 80.0);
3031 }
3032 }
3033
3034 #[test]
3040 fn grow_tabs_split_evenly_then_scroll_at_their_min() {
3041 let bar = || NodeSpec::row().width(px(600.0)).height(px(30.0)).scroll_x();
3042 let tab = || {
3043 NodeSpec::column()
3044 .grow_width()
3045 .min_width(80.0)
3046 .height(px(30.0))
3047 };
3048 let mut t = T::new(bar());
3050 let tabs: Vec<u32> = (0..3).map(|_| t.node(0, tab())).collect();
3051 t.run(1000.0, 1000.0);
3052 for (k, &c) in tabs.iter().enumerate() {
3053 assert_eq!(t.size(c).w, 200.0);
3054 assert_eq!(t.pos(c).x, 200.0 * k as f32);
3055 }
3056 assert_eq!(t.tree.scroll_max[0].x, 0.0);
3057 let mut t = T::new(bar());
3059 let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3060 t.run(1000.0, 1000.0);
3061 for (k, &c) in tabs.iter().enumerate() {
3062 assert_eq!(t.size(c).w, 80.0);
3063 assert_eq!(t.pos(c).x, 80.0 * k as f32);
3064 }
3065 assert_eq!(t.tree.scroll_max[0].x, 200.0);
3066 let mut t = T::new(NodeSpec::row().width(px(600.0)).height(px(30.0)));
3069 let tabs: Vec<u32> = (0..10).map(|_| t.node(0, tab())).collect();
3070 t.run(1000.0, 1000.0);
3071 for &c in &tabs {
3072 assert_eq!(t.size(c).w, 80.0);
3073 }
3074 let mut t = T::new(bar());
3079 let tabs: Vec<u32> = (0..10)
3080 .map(|_| {
3081 let c = t.node(0, tab().min_width(Min::FIT));
3082 t.text(c, 8);
3083 c
3084 })
3085 .collect();
3086 t.run(1000.0, 1000.0);
3087 for (k, &c) in tabs.iter().enumerate() {
3088 assert_eq!(t.size(c).w, 80.0);
3089 assert_eq!(t.pos(c).x, 80.0 * k as f32);
3090 assert_eq!(t.tree.specs[c as usize].layout.min_w, Min::px(80.0));
3091 }
3092 assert_eq!(t.tree.scroll_max[0].x, 200.0);
3093 let mut t = T::new(bar());
3095 let tabs: Vec<u32> = (0..3)
3096 .map(|_| {
3097 let c = t.node(0, tab().min_width(Min::FIT));
3098 t.text(c, 8);
3099 c
3100 })
3101 .collect();
3102 t.run(1000.0, 1000.0);
3103 for &c in &tabs {
3104 assert_eq!(t.size(c).w, 200.0);
3105 }
3106 }
3107
3108 #[test]
3112 fn min_fit_floors_a_percent_height_at_its_content() {
3113 let mut t = T::new(NodeSpec::row().width(px(100.0)).height(px(20.0)));
3114 let a = t.node(
3115 0,
3116 NodeSpec::column()
3117 .width(px(10.0))
3118 .height(Sizing::Percent(0.5))
3119 .min_height(Min::FIT),
3120 );
3121 t.node(a, NodeSpec::column().width(px(10.0)).height(px(16.0)));
3122 let b = t.node(
3123 0,
3124 NodeSpec::column()
3125 .width(px(10.0))
3126 .height(Sizing::Percent(0.5))
3127 .min_height(Min::FIT),
3128 );
3129 t.run(1000.0, 1000.0);
3130 assert_eq!(t.size(a).h, 16.0);
3131 assert_eq!(t.size(b).h, 10.0);
3132 }
3133
3134 #[test]
3135 fn padding_asymmetric() {
3136 let mut t = T::new(
3137 NodeSpec::column()
3138 .width(px(100.0))
3139 .height(px(100.0))
3140 .padding(Edges {
3141 l: 1.0,
3142 r: 2.0,
3143 t: 3.0,
3144 b: 4.0,
3145 }),
3146 );
3147 let a = t.node(0, NodeSpec::row().fill());
3148 t.run(1000.0, 1000.0);
3149 assert_eq!(t.pos(a), Vec2::new(1.0, 3.0));
3150 assert_eq!(t.size(a), Size::new(97.0, 93.0));
3151 }
3152}