1use taffy::prelude::*;
14use taffy::{
15 compute_block_layout, compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_hidden_layout, compute_leaf_layout,
16 compute_root_layout, round_layout, CacheTree, LayoutInput, LayoutOutput, RunMode,
17};
18
19#[derive(Debug, Clone, Copy)]
35struct CacheEntry<T> {
36 known: Size<Option<f32>>,
37 available: Size<AvailableSpace>,
38 parent: Size<Option<f32>>,
39 axis: taffy::RequestedAxis,
40 sizing: taffy::SizingMode,
41 content: T,
42}
43
44impl<T> CacheEntry<T> {
45 fn matches(&self, input: &LayoutInput, percent: bool) -> bool {
46 let known = input.known_dimensions;
47 known.width == self.known.width
48 && known.height == self.known.height
49 && (known.width.is_some() || self.available.width.is_roughly_equal(input.available_space.width))
50 && (known.height.is_some() || self.available.height.is_roughly_equal(input.available_space.height))
51 && (!percent || self.parent == input.parent_size)
52 && (self.axis == input.axis || self.axis == taffy::RequestedAxis::Both)
53 && self.sizing == input.sizing_mode
54 }
55}
56
57#[derive(Debug, Clone, Default)]
61struct Cache {
62 layout: Option<CacheEntry<LayoutOutput>>,
63 sizes: Vec<(u8, CacheEntry<Size<f32>>)>,
66}
67
68impl Cache {
69 fn slot(input: &LayoutInput, percent: bool) -> u8 {
75 use AvailableSpace::MinContent;
76 let (known, available) = (input.known_dimensions, input.available_space);
77 let base = match (known.width.is_some(), known.height.is_some()) {
78 (true, true) => 0,
79 (true, false) => 1 + (available.height == MinContent) as u8,
80 (false, true) => 3 + (available.width == MinContent) as u8,
81 (false, false) => 5 + (available.height == MinContent) as u8 + 2 * (available.width == MinContent) as u8,
82 };
83 let parent = if percent && input.parent_size.width.is_some() { 9 } else { 0 };
84 let sizing = if input.sizing_mode == taffy::SizingMode::ContentSize { 18 } else { 0 };
85 base + parent + sizing
86 }
87
88 fn get(&self, input: &LayoutInput, percent: bool) -> Option<LayoutOutput> {
89 match input.run_mode {
90 RunMode::PerformLayout => self.layout.filter(|e| e.matches(input, percent)).map(|e| e.content),
91 RunMode::ComputeSize => self.sizes.iter().find(|(_, e)| e.matches(input, percent)).map(|(_, e)| LayoutOutput::from_outer_size(e.content)),
92 RunMode::PerformHiddenLayout => None,
93 }
94 }
95
96 fn store(&mut self, input: &LayoutInput, output: LayoutOutput, percent: bool) {
97 let (known, available, parent, axis, sizing) = (input.known_dimensions, input.available_space, input.parent_size, input.axis, input.sizing_mode);
98 match input.run_mode {
99 RunMode::PerformLayout => self.layout = Some(CacheEntry { known, available, parent, axis, sizing, content: output }),
100 RunMode::ComputeSize => {
101 let entry = CacheEntry { known, available, parent, axis, sizing, content: output.size };
102 let slot = Self::slot(input, percent);
103 match self.sizes.iter_mut().find(|(s, _)| *s == slot) {
104 Some(held) => held.1 = entry,
105 None => self.sizes.push((slot, entry)),
106 }
107 }
108 RunMode::PerformHiddenLayout => {}
109 }
110 }
111
112 fn clear(&mut self) {
113 self.layout = None;
114 self.sizes.clear();
115 }
116}
117
118fn percent_sensitive(style: &Style) -> bool {
120 let rect = |r: &Rect<LengthPercentage>| [r.left, r.right, r.top, r.bottom].iter().any(|v| v.into_raw().uses_percentage());
121 let rect_auto = |r: &Rect<LengthPercentageAuto>| [r.left, r.right, r.top, r.bottom].iter().any(|v| v.into_raw().uses_percentage());
122 let size = |s: &Size<Dimension>| s.width.into_raw().uses_percentage() || s.height.into_raw().uses_percentage();
123 size(&style.size)
124 || size(&style.min_size)
125 || size(&style.max_size)
126 || rect(&style.padding)
127 || rect(&style.border)
128 || rect_auto(&style.margin)
129 || rect_auto(&style.inset)
130 || style.flex_basis.into_raw().uses_percentage()
131 || style.gap.width.into_raw().uses_percentage()
132 || style.gap.height.into_raw().uses_percentage()
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Default)]
137pub struct LeafAnswer {
138 pub size: Size<f32>,
139 pub baseline: Option<f32>,
142}
143
144#[derive(Debug, Clone)]
145struct ENode {
146 style: Style,
147 cache: Cache,
148 unrounded: Layout,
149 rounded: Layout,
150 children: Vec<u32>,
151 parent: Option<u32>,
152 measured: bool,
154 percent: bool,
156}
157
158impl Default for ENode {
159 fn default() -> Self {
160 ENode {
161 style: Style::default(),
162 cache: Cache::default(),
163 unrounded: Layout::with_order(0),
164 rounded: Layout::with_order(0),
165 children: Vec::new(),
166 parent: None,
167 measured: false,
168 percent: false,
169 }
170 }
171}
172
173#[derive(Debug, Clone, Default)]
175pub struct Engine {
176 nodes: Vec<ENode>,
177 rounding: bool,
178 #[cfg(test)]
180 no_cache: bool,
181}
182
183pub type MeasureFn<'a> = dyn FnMut(u32, Size<Option<f32>>, Size<AvailableSpace>) -> LeafAnswer + 'a;
185
186struct View<'t, 'm> {
187 engine: &'t mut Engine,
188 measure: &'t mut MeasureFn<'m>,
189}
190
191pub struct ChildIter<'a>(std::slice::Iter<'a, u32>);
192
193impl Iterator for ChildIter<'_> {
194 type Item = NodeId;
195 fn next(&mut self) -> Option<NodeId> {
196 self.0.next().map(|i| NodeId::from(*i as usize))
197 }
198}
199
200fn slot(id: NodeId) -> usize {
201 usize::from(id)
202}
203
204impl taffy::TraversePartialTree for View<'_, '_> {
205 type ChildIter<'a>
206 = ChildIter<'a>
207 where
208 Self: 'a;
209
210 fn child_ids(&self, node: NodeId) -> Self::ChildIter<'_> {
211 ChildIter(self.engine.nodes[slot(node)].children.iter())
212 }
213
214 fn child_count(&self, node: NodeId) -> usize {
215 self.engine.nodes[slot(node)].children.len()
216 }
217
218 fn get_child_id(&self, node: NodeId, index: usize) -> NodeId {
219 NodeId::from(self.engine.nodes[slot(node)].children[index] as usize)
220 }
221}
222
223impl taffy::TraverseTree for View<'_, '_> {}
224
225impl taffy::LayoutPartialTree for View<'_, '_> {
226 type CoreContainerStyle<'a>
227 = &'a Style
228 where
229 Self: 'a;
230 type CustomIdent = String;
231
232 fn get_core_container_style(&self, node: NodeId) -> Self::CoreContainerStyle<'_> {
233 &self.engine.nodes[slot(node)].style
234 }
235
236 fn set_unrounded_layout(&mut self, node: NodeId, layout: &Layout) {
237 self.engine.nodes[slot(node)].unrounded = *layout;
238 }
239
240 fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 {
241 0.0
242 }
243
244 fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
245 crate::deep(|| self.compute_child(node, inputs))
246 }
247}
248
249impl View<'_, '_> {
250 fn compute_child(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
251 if inputs.run_mode == RunMode::PerformHiddenLayout {
252 return compute_hidden_layout(self, node);
253 }
254 compute_cached_layout(self, node, inputs, |view, node, inputs| {
255 let n = &view.engine.nodes[slot(node)];
256 let display = n.style.display;
257 let has_children = !n.children.is_empty();
258 match (display, has_children) {
259 (Display::None, _) => compute_hidden_layout(view, node),
260 (Display::Block, true) => compute_block_layout(view, node, inputs, None),
261 (Display::Flex, true) => compute_flexbox_layout(view, node, inputs),
262 (Display::Grid, true) => compute_grid_layout(view, node, inputs),
263 (_, false) => {
264 let index = slot(node) as u32;
265 let View { engine, measure } = view;
266 let n = &engine.nodes[slot(node)];
267 let measured = n.measured;
268 let ratio = n.style.aspect_ratio.filter(|r| r.is_finite() && *r > 0.0);
269 let mut baseline = None;
270 let mut output = compute_leaf_layout(inputs, &n.style, |_, _| 0.0, |known, available| {
271 if let (Some(r), Some(w), None) = (ratio, known.width, known.height) {
275 return Size { width: w, height: w / r };
276 }
277 if !measured {
278 return Size::ZERO;
279 }
280 let answer = measure(index, known, available);
281 baseline = answer.baseline;
282 answer.size
283 });
284 if let Some(b) = baseline {
285 output.first_baselines.y = Some(b);
286 }
287 output
288 }
289 }
290 })
291 }
292}
293
294impl CacheTree for View<'_, '_> {
295 fn cache_get(&self, node: NodeId, input: &LayoutInput) -> Option<LayoutOutput> {
296 #[cfg(test)]
297 if self.engine.no_cache {
298 return None;
299 }
300 let n = &self.engine.nodes[slot(node)];
301 n.cache.get(input, n.percent)
302 }
303
304 fn cache_store(&mut self, node: NodeId, input: &LayoutInput, output: LayoutOutput) {
305 let n = &mut self.engine.nodes[slot(node)];
306 n.cache.store(input, output, n.percent)
307 }
308
309 fn cache_clear(&mut self, node: NodeId) {
310 self.engine.nodes[slot(node)].cache.clear();
311 }
312}
313
314impl taffy::LayoutFlexboxContainer for View<'_, '_> {
315 type FlexboxContainerStyle<'a>
316 = &'a Style
317 where
318 Self: 'a;
319 type FlexboxItemStyle<'a>
320 = &'a Style
321 where
322 Self: 'a;
323
324 fn get_flexbox_container_style(&self, node: NodeId) -> Self::FlexboxContainerStyle<'_> {
325 &self.engine.nodes[slot(node)].style
326 }
327
328 fn get_flexbox_child_style(&self, child: NodeId) -> Self::FlexboxItemStyle<'_> {
329 &self.engine.nodes[slot(child)].style
330 }
331}
332
333impl taffy::LayoutGridContainer for View<'_, '_> {
334 type GridContainerStyle<'a>
335 = &'a Style
336 where
337 Self: 'a;
338 type GridItemStyle<'a>
339 = &'a Style
340 where
341 Self: 'a;
342
343 fn get_grid_container_style(&self, node: NodeId) -> Self::GridContainerStyle<'_> {
344 &self.engine.nodes[slot(node)].style
345 }
346
347 fn get_grid_child_style(&self, child: NodeId) -> Self::GridItemStyle<'_> {
348 &self.engine.nodes[slot(child)].style
349 }
350}
351
352impl taffy::LayoutBlockContainer for View<'_, '_> {
353 type BlockContainerStyle<'a>
354 = &'a Style
355 where
356 Self: 'a;
357 type BlockItemStyle<'a>
358 = &'a Style
359 where
360 Self: 'a;
361
362 fn get_block_container_style(&self, node: NodeId) -> Self::BlockContainerStyle<'_> {
363 &self.engine.nodes[slot(node)].style
364 }
365
366 fn get_block_child_style(&self, child: NodeId) -> Self::BlockItemStyle<'_> {
367 &self.engine.nodes[slot(child)].style
368 }
369}
370
371impl taffy::RoundTree for View<'_, '_> {
372 fn get_unrounded_layout(&self, node: NodeId) -> Layout {
373 self.engine.nodes[slot(node)].unrounded
374 }
375
376 fn set_final_layout(&mut self, node: NodeId, layout: &Layout) {
377 self.engine.nodes[slot(node)].rounded = *layout;
378 }
379}
380
381impl Engine {
382 pub fn new() -> Engine {
383 Engine::default()
384 }
385
386 pub fn set_rounding(&mut self, on: bool) {
388 self.rounding = on;
389 }
390
391 pub fn len(&self) -> usize {
392 self.nodes.len()
393 }
394
395 pub fn is_empty(&self) -> bool {
396 self.nodes.is_empty()
397 }
398
399 pub fn resize(&mut self, n: usize) {
401 self.nodes.resize_with(n, ENode::default);
402 }
403
404 pub fn clear(&mut self) {
405 self.nodes.clear();
406 }
407
408 pub fn set_style(&mut self, i: u32, style: Style) -> bool {
411 if self.nodes[i as usize].style == style {
412 return false;
413 }
414 self.nodes[i as usize].percent = percent_sensitive(&style);
415 self.nodes[i as usize].style = style;
416 self.mark_dirty(i);
417 true
418 }
419
420 pub fn style(&self, i: u32) -> &Style {
421 &self.nodes[i as usize].style
422 }
423
424 pub fn set_children(&mut self, i: u32, children: &[u32]) {
426 if self.nodes[i as usize].children == children {
427 return;
428 }
429 for c in std::mem::take(&mut self.nodes[i as usize].children) {
430 if self.nodes[c as usize].parent == Some(i) {
431 self.nodes[c as usize].parent = None;
432 }
433 }
434 for &c in children {
435 self.nodes[c as usize].parent = Some(i);
436 }
437 self.nodes[i as usize].children = children.to_vec();
438 self.mark_dirty(i);
439 }
440
441 pub fn children(&self, i: u32) -> &[u32] {
442 &self.nodes[i as usize].children
443 }
444
445 pub fn set_measured(&mut self, i: u32, measured: bool) {
446 if self.nodes[i as usize].measured != measured {
447 self.nodes[i as usize].measured = measured;
448 self.mark_dirty(i);
449 }
450 }
451
452 pub fn mark_dirty(&mut self, i: u32) {
454 let mut cur = Some(i);
455 while let Some(n) = cur {
456 let node = &mut self.nodes[n as usize];
457 node.cache.clear();
458 cur = node.parent;
459 }
460 }
461
462 pub fn mark_all_dirty(&mut self) {
463 for n in &mut self.nodes {
464 n.cache.clear();
465 }
466 }
467
468 pub fn compute(&mut self, root: u32, available: Size<AvailableSpace>, measure: &mut MeasureFn) {
470 let rounding = self.rounding;
471 let mut view = View { engine: self, measure };
472 compute_root_layout(&mut view, NodeId::from(root as usize), available);
473 if rounding {
474 round_layout(&mut view, NodeId::from(root as usize));
475 }
476 }
477
478 pub fn layout(&self, i: u32) -> &Layout {
480 let n = &self.nodes[i as usize];
481 if self.rounding {
482 &n.rounded
483 } else {
484 &n.unrounded
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn baseline_alignment_uses_the_measured_first_baseline() {
495 let mut e = Engine::new();
496 e.resize(3);
497 e.set_style(0, Style { display: Display::Flex, align_items: Some(AlignItems::BASELINE), ..Style::default() });
498 e.set_children(0, &[1, 2]);
499 e.set_measured(1, true);
500 e.set_measured(2, true);
501 let mut measure = |i: u32, _k: Size<Option<f32>>, _a: Size<AvailableSpace>| match i {
504 1 => LeafAnswer { size: Size { width: 80.0, height: 40.0 }, baseline: Some(26.0) },
505 _ => LeafAnswer { size: Size { width: 50.0, height: 20.0 }, baseline: Some(15.0) },
506 };
507 e.compute(0, Size { width: AvailableSpace::Definite(300.0), height: AvailableSpace::MaxContent }, &mut measure);
508 assert_eq!(e.layout(1).location.y, 0.0);
509 assert_eq!(e.layout(2).location.y, 11.0);
510 let mut bottoms = |i: u32, _k: Size<Option<f32>>, _a: Size<AvailableSpace>| match i {
512 1 => LeafAnswer { size: Size { width: 80.0, height: 40.0 }, baseline: None },
513 _ => LeafAnswer { size: Size { width: 50.0, height: 20.0 }, baseline: None },
514 };
515 e.mark_all_dirty();
516 e.compute(0, Size { width: AvailableSpace::Definite(300.0), height: AvailableSpace::MaxContent }, &mut bottoms);
517 assert_eq!(e.layout(2).location.y, 20.0);
518 }
519
520 #[test]
521 fn dirty_marking_climbs_to_the_root_only() {
522 let mut e = Engine::new();
523 e.resize(4);
524 e.set_style(0, Style { display: Display::Flex, flex_direction: FlexDirection::Column, ..Style::default() });
525 e.set_children(0, &[1, 2]);
526 e.set_style(2, Style { display: Display::Flex, ..Style::default() });
527 e.set_children(2, &[3]);
528 for i in [1, 3] {
529 e.set_measured(i, true);
530 }
531 let calls = std::cell::Cell::new(0);
532 let mut measure = |_i: u32, _k: Size<Option<f32>>, _a: Size<AvailableSpace>| {
533 calls.set(calls.get() + 1);
534 LeafAnswer { size: Size { width: 10.0, height: 10.0 }, baseline: None }
535 };
536 e.compute(0, Size { width: AvailableSpace::Definite(100.0), height: AvailableSpace::MaxContent }, &mut measure);
537 let first = calls.get();
538 e.compute(0, Size { width: AvailableSpace::Definite(100.0), height: AvailableSpace::MaxContent }, &mut measure);
539 assert_eq!(calls.get(), first, "a clean tree answers from its caches");
540 e.mark_dirty(3);
541 e.compute(0, Size { width: AvailableSpace::Definite(100.0), height: AvailableSpace::MaxContent }, &mut measure);
542 assert!(calls.get() > first, "the dirty leaf is measured again");
543 }
544}
545
546
547#[cfg(test)]
548mod cache_tests {
549 use super::*;
550
551 #[test]
555 fn nested_flex_columns_measure_the_leaf_a_constant_number_of_times() {
556 for percent in [false, true] {
557 for depth in [4usize, 30, 50] {
558 let mut e = Engine::new();
559 e.resize(depth + 1);
560 for i in 0..depth {
561 let mut style = Style { display: Display::Flex, flex_direction: FlexDirection::Column, ..Style::default() };
562 if percent {
563 style.size.width = Dimension::percent(1.0);
564 style.padding.left = LengthPercentage::percent(0.01);
565 }
566 e.set_style(i as u32, style);
567 e.set_children(i as u32, &[(i + 1) as u32]);
568 }
569 e.set_measured(depth as u32, true);
570 let calls = std::cell::Cell::new(0);
571 let mut measure = |_i: u32, _k: Size<Option<f32>>, _a: Size<AvailableSpace>| {
572 calls.set(calls.get() + 1);
573 LeafAnswer { size: Size { width: 10.0, height: 10.0 }, baseline: None }
574 };
575 e.compute(0, Size { width: AvailableSpace::Definite(400.0), height: AvailableSpace::MaxContent }, &mut measure);
576 assert!(calls.get() <= 8, "depth {depth} (percent {percent}): {} measures", calls.get());
577 assert_eq!(e.layout(depth as u32).size.height, 10.0);
578 }
579 }
580 }
581
582 #[test]
587 fn nested_block_boxes_over_a_wrapping_leaf_measure_it_a_constant_number_of_times() {
588 for display in [Display::Block, Display::Flex] {
589 for depth in [4usize, 30, 50] {
590 let mut e = Engine::new();
591 e.resize(depth + 1);
592 for i in 0..depth {
593 e.set_style(i as u32, Style { display, flex_direction: FlexDirection::Column, ..Style::default() });
594 e.set_children(i as u32, &[(i + 1) as u32]);
595 }
596 e.set_measured(depth as u32, true);
597 let calls = std::cell::Cell::new(0);
598 let mut measure = |_i: u32, k: Size<Option<f32>>, a: Size<AvailableSpace>| {
599 calls.set(calls.get() + 1);
600 let width = k.width.unwrap_or(match a.width {
601 AvailableSpace::Definite(w) => w.min(300.0),
602 AvailableSpace::MinContent => 60.0,
603 AvailableSpace::MaxContent => 300.0,
604 });
605 LeafAnswer { size: Size { width, height: k.height.unwrap_or((300.0 / width).ceil() * 16.0) }, baseline: None }
606 };
607 e.compute(0, Size { width: AvailableSpace::Definite(200.0), height: AvailableSpace::MaxContent }, &mut measure);
608 assert!(calls.get() <= 12, "{display:?} depth {depth}: {} measures", calls.get());
609 assert_eq!(e.layout(depth as u32).size.height, 32.0);
610 }
611 }
612 }
613}
614
615#[cfg(test)]
616mod differential_tests {
617 use super::*;
618
619 struct Rng(u64);
620 impl Rng {
621 fn n(&mut self, m: u64) -> u64 {
622 self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
623 (self.0 >> 33) % m
624 }
625 }
626
627 fn dim(r: &mut Rng) -> Dimension {
628 match r.n(4) {
629 0 => Dimension::length((r.n(20) * 10) as f32),
630 1 => Dimension::percent((r.n(10) as f32 + 1.0) / 10.0),
631 _ => Dimension::auto(),
632 }
633 }
634
635 fn lp(r: &mut Rng) -> LengthPercentage {
636 match r.n(3) {
637 0 => LengthPercentage::length(r.n(8) as f32),
638 1 => LengthPercentage::percent(r.n(5) as f32 / 100.0),
639 _ => LengthPercentage::length(0.0),
640 }
641 }
642
643 fn style(r: &mut Rng, leaf: bool) -> Style {
644 let mut s = Style::default();
645 s.display = if leaf {
646 Display::Flex
647 } else {
648 match r.n(3) {
649 0 => Display::Block,
650 1 => Display::Grid,
651 _ => Display::Flex,
652 }
653 };
654 s.flex_direction = if r.n(2) == 0 { FlexDirection::Row } else { FlexDirection::Column };
655 if r.n(3) == 0 {
656 s.flex_wrap = FlexWrap::Wrap;
657 }
658 s.size = Size { width: dim(r), height: if r.n(3) == 0 { dim(r) } else { Dimension::auto() } };
659 if r.n(4) == 0 {
660 s.min_size.width = dim(r);
661 }
662 if r.n(4) == 0 {
663 s.max_size.width = dim(r);
664 }
665 s.padding = Rect { left: lp(r), right: lp(r), top: lp(r), bottom: lp(r) };
666 s.flex_grow = r.n(3) as f32;
667 s.flex_shrink = if r.n(4) == 0 { 0.0 } else { 1.0 };
668 if r.n(4) == 0 {
669 s.flex_basis = dim(r);
670 }
671 if r.n(4) == 0 {
672 s.align_items = Some(AlignItems::FLEX_START);
673 }
674 if r.n(5) == 0 {
675 s.gap = Size { width: lp(r), height: lp(r) };
676 }
677 s
678 }
679
680 fn text(words: u32, known: Size<Option<f32>>, avail: Size<AvailableSpace>) -> Size<f32> {
682 let total = words as f32 * 13.0;
683 let w = known.width.unwrap_or(match avail.width {
684 AvailableSpace::Definite(w) => w.min(total).max(13.0),
685 AvailableSpace::MinContent => 13.0,
686 AvailableSpace::MaxContent => total,
687 });
688 let per = (w / 13.0).floor().max(1.0);
689 Size { width: w, height: known.height.unwrap_or((words as f32 / per).ceil() * 16.0) }
690 }
691
692 fn build(styles: &[Style], kids: &[Vec<u32>], no_cache: bool) -> Engine {
693 let mut e = Engine::new();
694 e.no_cache = no_cache;
695 e.resize(styles.len());
696 for i in 0..styles.len() {
697 e.set_style(i as u32, styles[i].clone());
698 e.set_children(i as u32, &kids[i]);
699 if kids[i].is_empty() {
700 e.set_measured(i as u32, true);
701 }
702 }
703 e
704 }
705
706 #[test]
710 fn grid_items_keep_their_heights_under_the_cache() {
711 let grid = Style { display: Display::Grid, ..Style::default() };
712 let a = Style { size: Size { width: Dimension::length(180.0), height: Dimension::auto() }, ..Style::default() };
713 let tall = Style { size: Size { width: Dimension::auto(), height: Dimension::length(160.0) }, ..Style::default() };
714 let styles = vec![grid, a, tall, Style::default()];
715 let kids = vec![vec![1, 3], vec![2], vec![], vec![]];
716 let mut e = build(&styles, &kids, false);
717 e.set_measured(2, false);
718 let mut m = |_i: u32, k: Size<Option<f32>>, a: Size<AvailableSpace>| LeafAnswer { size: text(5, k, a), baseline: None };
719 e.compute(0, Size { width: AvailableSpace::Definite(400.0), height: AvailableSpace::MaxContent }, &mut m);
720 assert_eq!(e.layout(1).size.height, 160.0);
721 assert_eq!(e.layout(3).location.y, 160.0);
722 }
723
724 #[test]
728 fn cached_layout_equals_uncached_layout_on_random_trees() {
729 let viewports = [(400.0f32, None), (250.0, Some(300.0)), (400.0, None), (137.0, Some(300.0)), (400.0, Some(90.0))];
730 for seed in 0..1000u64 {
731 let mut r = Rng(seed * 7919 + 1);
732 let n = if seed % 2 == 0 { 2 + r.n(4) as usize } else { 3 + r.n(25) as usize };
733 let mut kids: Vec<Vec<u32>> = vec![vec![]; n];
734 for i in 1..n {
735 kids[r.n(i as u64) as usize].push(i as u32);
736 }
737 let styles: Vec<Style> = (0..n).map(|i| style(&mut r, kids[i].is_empty())).collect();
738 let words: Vec<u32> = (0..n).map(|_| 1 + r.n(30) as u32).collect();
739 let mut cached = build(&styles, &kids, false);
740 for (pass, &(w, h)) in viewports.iter().enumerate() {
741 let avail = Size { width: AvailableSpace::Definite(w), height: h.map_or(AvailableSpace::MaxContent, AvailableSpace::Definite) };
742 let mut m = |i: u32, k: Size<Option<f32>>, a: Size<AvailableSpace>| LeafAnswer { size: text(words[i as usize], k, a), baseline: None };
743 cached.compute(0, avail, &mut m);
744 let mut reference = build(&styles, &kids, true);
745 reference.compute(0, avail, &mut m);
746 for i in 0..n as u32 {
747 let (a, b) = (cached.layout(i), reference.layout(i));
748 let d = (a.size.width - b.size.width).abs()
749 + (a.size.height - b.size.height).abs()
750 + (a.location.x - b.location.x).abs()
751 + (a.location.y - b.location.y).abs();
752 assert!(d <= 0.01, "seed {seed} pass {pass} node {i}: cached {:?}@{:?} uncached {:?}@{:?}", a.size, a.location, b.size, b.location);
753 }
754 }
755 }
756 }
757
758 #[test]
762 fn nested_grid_and_block_measure_the_leaf_a_bounded_number_of_times() {
763 for display in [Display::Grid, Display::Block, Display::Flex] {
764 let depth = 30usize;
765 let mut e = Engine::new();
766 e.resize(depth + 1);
767 for i in 0..depth {
768 e.set_style(i as u32, Style { display, flex_direction: FlexDirection::Column, ..Style::default() });
769 e.set_children(i as u32, &[(i + 1) as u32]);
770 }
771 e.set_measured(depth as u32, true);
772 let calls = std::cell::Cell::new(0u64);
773 let mut measure = |_i: u32, _k: Size<Option<f32>>, _a: Size<AvailableSpace>| {
774 calls.set(calls.get() + 1);
775 LeafAnswer { size: Size { width: 10.0, height: 10.0 }, baseline: None }
776 };
777 e.compute(0, Size { width: AvailableSpace::Definite(400.0), height: AvailableSpace::MaxContent }, &mut measure);
778 assert!(calls.get() <= 16, "{display:?} depth {depth}: {} measures", calls.get());
779 assert_eq!(e.layout(depth as u32).size.height, 10.0);
780 }
781 }
782}