1use std::collections::HashMap;
8
9use taffy::prelude::*;
10
11use crate::focus::WidgetId;
12use crate::geometry::Rect;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub struct LayoutRect {
17 pub x: u16,
19 pub y: u16,
21 pub width: u16,
23 pub height: u16,
25}
26
27impl LayoutRect {
28 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
30 Self {
31 x,
32 y,
33 width,
34 height,
35 }
36 }
37
38 pub const fn to_rect(self) -> Rect {
40 Rect::new(self.x, self.y, self.width, self.height)
41 }
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum LayoutError {
47 WidgetNotFound(WidgetId),
49 TaffyError(String),
51 NoRoot,
53}
54
55impl std::fmt::Display for LayoutError {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Self::WidgetNotFound(id) => write!(f, "widget not found: {id}"),
59 Self::TaffyError(e) => write!(f, "taffy error: {e}"),
60 Self::NoRoot => write!(f, "no root node set"),
61 }
62 }
63}
64
65impl std::error::Error for LayoutError {}
66
67pub struct LayoutEngine {
72 taffy: TaffyTree<()>,
73 widget_to_node: HashMap<WidgetId, NodeId>,
74 node_to_widget: HashMap<NodeId, WidgetId>,
75 root: Option<NodeId>,
76}
77
78impl LayoutEngine {
79 pub fn new() -> Self {
81 Self {
82 taffy: TaffyTree::new(),
83 widget_to_node: HashMap::new(),
84 node_to_widget: HashMap::new(),
85 root: None,
86 }
87 }
88
89 pub fn add_node(&mut self, widget_id: WidgetId, style: Style) -> Result<(), LayoutError> {
91 let node = self
92 .taffy
93 .new_leaf(style)
94 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
95 self.widget_to_node.insert(widget_id, node);
96 self.node_to_widget.insert(node, widget_id);
97 Ok(())
98 }
99
100 pub fn add_node_with_children(
102 &mut self,
103 widget_id: WidgetId,
104 style: Style,
105 children: &[WidgetId],
106 ) -> Result<(), LayoutError> {
107 let child_nodes: Vec<NodeId> = children
108 .iter()
109 .map(|id| {
110 self.widget_to_node
111 .get(id)
112 .copied()
113 .ok_or(LayoutError::WidgetNotFound(*id))
114 })
115 .collect::<Result<Vec<_>, _>>()?;
116
117 let node = self
118 .taffy
119 .new_with_children(style, &child_nodes)
120 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
121 self.widget_to_node.insert(widget_id, node);
122 self.node_to_widget.insert(node, widget_id);
123 Ok(())
124 }
125
126 pub fn set_root(&mut self, widget_id: WidgetId) -> Result<(), LayoutError> {
128 let node = self
129 .widget_to_node
130 .get(&widget_id)
131 .copied()
132 .ok_or(LayoutError::WidgetNotFound(widget_id))?;
133 self.root = Some(node);
134 Ok(())
135 }
136
137 pub fn update_style(&mut self, widget_id: WidgetId, style: Style) -> Result<(), LayoutError> {
139 let node = self
140 .widget_to_node
141 .get(&widget_id)
142 .copied()
143 .ok_or(LayoutError::WidgetNotFound(widget_id))?;
144 self.taffy
145 .set_style(node, style)
146 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
147 Ok(())
148 }
149
150 pub fn remove_node(&mut self, widget_id: WidgetId) -> Result<(), LayoutError> {
152 let node = self
153 .widget_to_node
154 .remove(&widget_id)
155 .ok_or(LayoutError::WidgetNotFound(widget_id))?;
156 self.node_to_widget.remove(&node);
157 self.taffy
158 .remove(node)
159 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
160 if self.root == Some(node) {
161 self.root = None;
162 }
163 Ok(())
164 }
165
166 pub fn compute(
168 &mut self,
169 available_width: u16,
170 available_height: u16,
171 ) -> Result<(), LayoutError> {
172 let root = self.root.ok_or(LayoutError::NoRoot)?;
173 let available = taffy::Size {
174 width: AvailableSpace::Definite(f32::from(available_width)),
175 height: AvailableSpace::Definite(f32::from(available_height)),
176 };
177 self.taffy
178 .compute_layout(root, available)
179 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
180 Ok(())
181 }
182
183 pub fn layout(&self, widget_id: WidgetId) -> Result<LayoutRect, LayoutError> {
185 let node = self
186 .widget_to_node
187 .get(&widget_id)
188 .copied()
189 .ok_or(LayoutError::WidgetNotFound(widget_id))?;
190 let layout = self
191 .taffy
192 .layout(node)
193 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
194
195 Ok(LayoutRect {
196 x: round_position(layout.location.x),
197 y: round_position(layout.location.y),
198 width: round_size(layout.size.width),
199 height: round_size(layout.size.height),
200 })
201 }
202
203 pub fn layout_rect(&self, widget_id: WidgetId) -> Result<Rect, LayoutError> {
205 self.layout(widget_id).map(|lr| lr.to_rect())
206 }
207
208 pub fn has_node(&self, widget_id: WidgetId) -> bool {
210 self.widget_to_node.contains_key(&widget_id)
211 }
212
213 pub fn node_count(&self) -> usize {
215 self.widget_to_node.len()
216 }
217}
218
219impl Default for LayoutEngine {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225pub fn round_position(value: f32) -> u16 {
227 if value < 0.0 {
228 0
229 } else if value > f32::from(u16::MAX) {
230 u16::MAX
231 } else {
232 value.floor() as u16
233 }
234}
235
236pub fn round_size(value: f32) -> u16 {
238 if value < 0.0 {
239 0
240 } else if value > f32::from(u16::MAX) {
241 u16::MAX
242 } else {
243 value.round() as u16
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::geometry::Rect;
251 use taffy::prelude::{
252 AlignItems, Dimension, Display, FlexDirection, GridPlacement, JustifyContent,
253 LengthPercentage, LengthPercentageAuto, Line, Style, auto, fr, length,
254 };
255
256 fn wid(n: u64) -> WidgetId {
257 n
258 }
259
260 #[test]
261 fn empty_engine() {
262 let engine = LayoutEngine::new();
263 assert_eq!(engine.node_count(), 0);
264 assert!(!engine.has_node(wid(1)));
265 }
266
267 #[test]
268 fn add_leaf_node() {
269 let mut engine = LayoutEngine::new();
270 let result = engine.add_node(wid(1), Style::default());
271 assert!(result.is_ok());
272 assert!(engine.has_node(wid(1)));
273 assert_eq!(engine.node_count(), 1);
274 }
275
276 #[test]
277 fn add_with_children() {
278 let mut engine = LayoutEngine::new();
279 engine.add_node(wid(1), Style::default()).ok();
280 engine.add_node(wid(2), Style::default()).ok();
281 let result = engine.add_node_with_children(wid(3), Style::default(), &[wid(1), wid(2)]);
282 assert!(result.is_ok());
283 assert_eq!(engine.node_count(), 3);
284 }
285
286 #[test]
287 fn set_root() {
288 let mut engine = LayoutEngine::new();
289 engine.add_node(wid(1), Style::default()).ok();
290 let result = engine.set_root(wid(1));
291 assert!(result.is_ok());
292 }
293
294 #[test]
295 fn remove_node() {
296 let mut engine = LayoutEngine::new();
297 engine.add_node(wid(1), Style::default()).ok();
298 assert!(engine.has_node(wid(1)));
299 let result = engine.remove_node(wid(1));
300 assert!(result.is_ok());
301 assert!(!engine.has_node(wid(1)));
302 assert_eq!(engine.node_count(), 0);
303 }
304
305 #[test]
306 fn update_style() {
307 let mut engine = LayoutEngine::new();
308 engine.add_node(wid(1), Style::default()).ok();
309 let new_style = Style {
310 size: taffy::Size {
311 width: Dimension::Length(50.0),
312 height: Dimension::Length(25.0),
313 },
314 ..Default::default()
315 };
316 let result = engine.update_style(wid(1), new_style);
317 assert!(result.is_ok());
318 }
319
320 #[test]
321 fn compute_single_node() {
322 let mut engine = LayoutEngine::new();
323 engine
324 .add_node(
325 wid(1),
326 Style {
327 size: taffy::Size {
328 width: Dimension::Length(80.0),
329 height: Dimension::Length(24.0),
330 },
331 ..Default::default()
332 },
333 )
334 .ok();
335 engine.set_root(wid(1)).ok();
336 let result = engine.compute(80, 24);
337 assert!(result.is_ok());
338
339 let layout = engine.layout(wid(1));
340 assert!(layout.is_ok());
341 let rect = layout.ok();
342 assert!(rect.is_some());
343 let rect = rect.unwrap_or_default();
344 assert_eq!(rect.width, 80);
345 assert_eq!(rect.height, 24);
346 }
347
348 #[test]
349 fn compute_two_children_row() {
350 let mut engine = LayoutEngine::new();
351 engine
352 .add_node(
353 wid(1),
354 Style {
355 flex_grow: 1.0,
356 ..Default::default()
357 },
358 )
359 .ok();
360 engine
361 .add_node(
362 wid(2),
363 Style {
364 flex_grow: 1.0,
365 ..Default::default()
366 },
367 )
368 .ok();
369 engine
370 .add_node_with_children(
371 wid(3),
372 Style {
373 size: taffy::Size {
374 width: Dimension::Length(80.0),
375 height: Dimension::Length(24.0),
376 },
377 ..Default::default()
378 },
379 &[wid(1), wid(2)],
380 )
381 .ok();
382 engine.set_root(wid(3)).ok();
383 engine.compute(80, 24).ok();
384
385 let l1 = engine.layout(wid(1)).unwrap_or_default();
386 let l2 = engine.layout(wid(2)).unwrap_or_default();
387 assert_eq!(l1.width, 40);
388 assert_eq!(l2.width, 40);
389 assert_eq!(l1.height, 24);
390 }
391
392 #[test]
393 fn compute_two_children_column() {
394 let mut engine = LayoutEngine::new();
395 engine
396 .add_node(
397 wid(1),
398 Style {
399 flex_grow: 1.0,
400 ..Default::default()
401 },
402 )
403 .ok();
404 engine
405 .add_node(
406 wid(2),
407 Style {
408 flex_grow: 1.0,
409 ..Default::default()
410 },
411 )
412 .ok();
413 engine
414 .add_node_with_children(
415 wid(3),
416 Style {
417 flex_direction: FlexDirection::Column,
418 size: taffy::Size {
419 width: Dimension::Length(80.0),
420 height: Dimension::Length(24.0),
421 },
422 ..Default::default()
423 },
424 &[wid(1), wid(2)],
425 )
426 .ok();
427 engine.set_root(wid(3)).ok();
428 engine.compute(80, 24).ok();
429
430 let l1 = engine.layout(wid(1)).unwrap_or_default();
431 let l2 = engine.layout(wid(2)).unwrap_or_default();
432 assert_eq!(l1.height, 12);
433 assert_eq!(l2.height, 12);
434 assert_eq!(l1.width, 80);
435 }
436
437 #[test]
438 fn layout_rect_conversion() {
439 let lr = LayoutRect::new(5, 10, 40, 20);
440 let rect = lr.to_rect();
441 assert_eq!(rect, Rect::new(5, 10, 40, 20));
442 }
443
444 #[test]
445 fn widget_not_found_error() {
446 let engine = LayoutEngine::new();
447 let result = engine.layout(wid(999));
448 assert!(result.is_err());
449 match result {
450 Err(LayoutError::WidgetNotFound(id)) => assert_eq!(id, wid(999)),
451 _ => unreachable!(),
452 }
453 }
454
455 #[test]
456 fn no_root_error() {
457 let mut engine = LayoutEngine::new();
458 let result = engine.compute(80, 24);
459 assert!(result.is_err());
460 match result {
461 Err(LayoutError::NoRoot) => {}
462 _ => unreachable!(),
463 }
464 }
465
466 #[test]
467 fn round_position_values() {
468 assert_eq!(round_position(0.0), 0);
469 assert_eq!(round_position(5.7), 5); assert_eq!(round_position(10.99), 10); assert_eq!(round_position(-1.0), 0); }
473
474 #[test]
475 fn round_size_values() {
476 assert_eq!(round_size(0.0), 0);
477 assert_eq!(round_size(5.4), 5); assert_eq!(round_size(5.5), 6); assert_eq!(round_size(-1.0), 0); }
481
482 #[test]
483 fn children_not_found_error() {
484 let mut engine = LayoutEngine::new();
485 let result = engine.add_node_with_children(wid(1), Style::default(), &[wid(999)]);
486 assert!(result.is_err());
487 match result {
488 Err(LayoutError::WidgetNotFound(id)) => assert_eq!(id, wid(999)),
489 _ => unreachable!(),
490 }
491 }
492
493 #[test]
494 fn remove_root_clears_root() {
495 let mut engine = LayoutEngine::new();
496 engine.add_node(wid(1), Style::default()).ok();
497 engine.set_root(wid(1)).ok();
498 engine.remove_node(wid(1)).ok();
499 let result = engine.compute(80, 24);
500 assert!(matches!(result, Err(LayoutError::NoRoot)));
501 }
502
503 #[test]
506 fn flex_row_equal_grow() {
507 let mut engine = LayoutEngine::new();
508 for i in 1..=3 {
509 engine
510 .add_node(
511 wid(i),
512 Style {
513 flex_grow: 1.0,
514 ..Default::default()
515 },
516 )
517 .ok();
518 }
519 engine
520 .add_node_with_children(
521 wid(10),
522 Style {
523 size: taffy::Size {
524 width: Dimension::Length(90.0),
525 height: Dimension::Length(30.0),
526 },
527 ..Default::default()
528 },
529 &[wid(1), wid(2), wid(3)],
530 )
531 .ok();
532 engine.set_root(wid(10)).ok();
533 engine.compute(90, 30).ok();
534
535 for i in 1..=3 {
536 let l = engine.layout(wid(i)).unwrap_or_default();
537 assert_eq!(l.width, 30, "child {i} width should be 30");
538 }
539 }
540
541 #[test]
542 fn flex_column_equal_grow() {
543 let mut engine = LayoutEngine::new();
544 for i in 1..=3 {
545 engine
546 .add_node(
547 wid(i),
548 Style {
549 flex_grow: 1.0,
550 ..Default::default()
551 },
552 )
553 .ok();
554 }
555 engine
556 .add_node_with_children(
557 wid(10),
558 Style {
559 flex_direction: FlexDirection::Column,
560 size: taffy::Size {
561 width: Dimension::Length(60.0),
562 height: Dimension::Length(30.0),
563 },
564 ..Default::default()
565 },
566 &[wid(1), wid(2), wid(3)],
567 )
568 .ok();
569 engine.set_root(wid(10)).ok();
570 engine.compute(60, 30).ok();
571
572 for i in 1..=3 {
573 let l = engine.layout(wid(i)).unwrap_or_default();
574 assert_eq!(l.height, 10, "child {i} height should be 10");
575 }
576 }
577
578 #[test]
579 fn flex_row_unequal_grow() {
580 let mut engine = LayoutEngine::new();
581 engine
582 .add_node(
583 wid(1),
584 Style {
585 flex_grow: 1.0,
586 ..Default::default()
587 },
588 )
589 .ok();
590 engine
591 .add_node(
592 wid(2),
593 Style {
594 flex_grow: 2.0,
595 ..Default::default()
596 },
597 )
598 .ok();
599 engine
600 .add_node(
601 wid(3),
602 Style {
603 flex_grow: 1.0,
604 ..Default::default()
605 },
606 )
607 .ok();
608 engine
609 .add_node_with_children(
610 wid(10),
611 Style {
612 size: taffy::Size {
613 width: Dimension::Length(80.0),
614 height: Dimension::Length(20.0),
615 },
616 ..Default::default()
617 },
618 &[wid(1), wid(2), wid(3)],
619 )
620 .ok();
621 engine.set_root(wid(10)).ok();
622 engine.compute(80, 20).ok();
623
624 let l1 = engine.layout(wid(1)).unwrap_or_default();
625 let l2 = engine.layout(wid(2)).unwrap_or_default();
626 let l3 = engine.layout(wid(3)).unwrap_or_default();
627 assert_eq!(l1.width, 20);
628 assert_eq!(l2.width, 40);
629 assert_eq!(l3.width, 20);
630 }
631
632 #[test]
633 fn flex_column_fixed_and_grow() {
634 let mut engine = LayoutEngine::new();
635 engine
636 .add_node(
637 wid(1),
638 Style {
639 size: taffy::Size {
640 width: auto(),
641 height: Dimension::Length(5.0),
642 },
643 ..Default::default()
644 },
645 )
646 .ok();
647 engine
648 .add_node(
649 wid(2),
650 Style {
651 flex_grow: 1.0,
652 ..Default::default()
653 },
654 )
655 .ok();
656 engine
657 .add_node_with_children(
658 wid(10),
659 Style {
660 flex_direction: FlexDirection::Column,
661 size: taffy::Size {
662 width: Dimension::Length(80.0),
663 height: Dimension::Length(25.0),
664 },
665 ..Default::default()
666 },
667 &[wid(1), wid(2)],
668 )
669 .ok();
670 engine.set_root(wid(10)).ok();
671 engine.compute(80, 25).ok();
672
673 let l1 = engine.layout(wid(1)).unwrap_or_default();
674 let l2 = engine.layout(wid(2)).unwrap_or_default();
675 assert_eq!(l1.height, 5);
676 assert_eq!(l2.height, 20);
677 }
678
679 #[test]
680 fn flex_justify_center() {
681 let mut engine = LayoutEngine::new();
682 engine
683 .add_node(
684 wid(1),
685 Style {
686 size: taffy::Size {
687 width: Dimension::Length(20.0),
688 height: Dimension::Length(10.0),
689 },
690 ..Default::default()
691 },
692 )
693 .ok();
694 engine
695 .add_node_with_children(
696 wid(10),
697 Style {
698 justify_content: Some(JustifyContent::Center),
699 size: taffy::Size {
700 width: Dimension::Length(80.0),
701 height: Dimension::Length(10.0),
702 },
703 ..Default::default()
704 },
705 &[wid(1)],
706 )
707 .ok();
708 engine.set_root(wid(10)).ok();
709 engine.compute(80, 10).ok();
710
711 let l = engine.layout(wid(1)).unwrap_or_default();
712 assert_eq!(l.x, 30); }
714
715 #[test]
716 fn flex_justify_space_between() {
717 let mut engine = LayoutEngine::new();
718 engine
719 .add_node(
720 wid(1),
721 Style {
722 size: taffy::Size {
723 width: Dimension::Length(10.0),
724 height: Dimension::Length(10.0),
725 },
726 ..Default::default()
727 },
728 )
729 .ok();
730 engine
731 .add_node(
732 wid(2),
733 Style {
734 size: taffy::Size {
735 width: Dimension::Length(10.0),
736 height: Dimension::Length(10.0),
737 },
738 ..Default::default()
739 },
740 )
741 .ok();
742 engine
743 .add_node_with_children(
744 wid(10),
745 Style {
746 justify_content: Some(JustifyContent::SpaceBetween),
747 size: taffy::Size {
748 width: Dimension::Length(80.0),
749 height: Dimension::Length(10.0),
750 },
751 ..Default::default()
752 },
753 &[wid(1), wid(2)],
754 )
755 .ok();
756 engine.set_root(wid(10)).ok();
757 engine.compute(80, 10).ok();
758
759 let l1 = engine.layout(wid(1)).unwrap_or_default();
760 let l2 = engine.layout(wid(2)).unwrap_or_default();
761 assert_eq!(l1.x, 0);
762 assert_eq!(l2.x, 70); }
764
765 #[test]
766 fn flex_align_items_center() {
767 let mut engine = LayoutEngine::new();
768 engine
769 .add_node(
770 wid(1),
771 Style {
772 size: taffy::Size {
773 width: Dimension::Length(20.0),
774 height: Dimension::Length(10.0),
775 },
776 ..Default::default()
777 },
778 )
779 .ok();
780 engine
781 .add_node_with_children(
782 wid(10),
783 Style {
784 align_items: Some(AlignItems::Center),
785 size: taffy::Size {
786 width: Dimension::Length(80.0),
787 height: Dimension::Length(30.0),
788 },
789 ..Default::default()
790 },
791 &[wid(1)],
792 )
793 .ok();
794 engine.set_root(wid(10)).ok();
795 engine.compute(80, 30).ok();
796
797 let l = engine.layout(wid(1)).unwrap_or_default();
798 assert_eq!(l.y, 10); }
800
801 #[test]
802 fn flex_nested() {
803 let mut engine = LayoutEngine::new();
804 engine
806 .add_node(
807 wid(1),
808 Style {
809 flex_grow: 1.0,
810 ..Default::default()
811 },
812 )
813 .ok();
814 engine
815 .add_node(
816 wid(2),
817 Style {
818 flex_grow: 1.0,
819 ..Default::default()
820 },
821 )
822 .ok();
823 engine
825 .add_node_with_children(
826 wid(3),
827 Style {
828 flex_direction: FlexDirection::Column,
829 flex_grow: 1.0,
830 ..Default::default()
831 },
832 &[wid(1), wid(2)],
833 )
834 .ok();
835 engine
837 .add_node(
838 wid(4),
839 Style {
840 flex_grow: 1.0,
841 ..Default::default()
842 },
843 )
844 .ok();
845 engine
847 .add_node_with_children(
848 wid(10),
849 Style {
850 size: taffy::Size {
851 width: Dimension::Length(80.0),
852 height: Dimension::Length(20.0),
853 },
854 ..Default::default()
855 },
856 &[wid(3), wid(4)],
857 )
858 .ok();
859 engine.set_root(wid(10)).ok();
860 engine.compute(80, 20).ok();
861
862 let l3 = engine.layout(wid(3)).unwrap_or_default();
863 let l4 = engine.layout(wid(4)).unwrap_or_default();
864 assert_eq!(l3.width, 40);
865 assert_eq!(l4.width, 40);
866 let l1 = engine.layout(wid(1)).unwrap_or_default();
867 let l2 = engine.layout(wid(2)).unwrap_or_default();
868 assert_eq!(l1.height, 10);
869 assert_eq!(l2.height, 10);
870 }
871
872 #[test]
873 fn flex_with_gap() {
874 let mut engine = LayoutEngine::new();
875 engine
876 .add_node(
877 wid(1),
878 Style {
879 size: taffy::Size {
880 width: Dimension::Length(20.0),
881 height: Dimension::Length(10.0),
882 },
883 ..Default::default()
884 },
885 )
886 .ok();
887 engine
888 .add_node(
889 wid(2),
890 Style {
891 size: taffy::Size {
892 width: Dimension::Length(20.0),
893 height: Dimension::Length(10.0),
894 },
895 ..Default::default()
896 },
897 )
898 .ok();
899 engine
900 .add_node_with_children(
901 wid(10),
902 Style {
903 gap: taffy::Size {
904 width: LengthPercentage::Length(10.0),
905 height: LengthPercentage::Length(0.0),
906 },
907 size: taffy::Size {
908 width: Dimension::Length(80.0),
909 height: Dimension::Length(10.0),
910 },
911 ..Default::default()
912 },
913 &[wid(1), wid(2)],
914 )
915 .ok();
916 engine.set_root(wid(10)).ok();
917 engine.compute(80, 10).ok();
918
919 let l1 = engine.layout(wid(1)).unwrap_or_default();
920 let l2 = engine.layout(wid(2)).unwrap_or_default();
921 assert_eq!(l1.x, 0);
922 assert_eq!(l2.x, 30); }
924
925 #[test]
928 fn grid_two_columns_equal() {
929 let mut engine = LayoutEngine::new();
930 engine.add_node(wid(1), Style::default()).ok();
931 engine.add_node(wid(2), Style::default()).ok();
932 engine
933 .add_node_with_children(
934 wid(10),
935 Style {
936 display: Display::Grid,
937 grid_template_columns: vec![fr(1.0), fr(1.0)],
938 size: taffy::Size {
939 width: Dimension::Length(80.0),
940 height: Dimension::Length(20.0),
941 },
942 ..Default::default()
943 },
944 &[wid(1), wid(2)],
945 )
946 .ok();
947 engine.set_root(wid(10)).ok();
948 engine.compute(80, 20).ok();
949
950 let l1 = engine.layout(wid(1)).unwrap_or_default();
951 let l2 = engine.layout(wid(2)).unwrap_or_default();
952 assert_eq!(l1.width, 40);
953 assert_eq!(l2.width, 40);
954 }
955
956 #[test]
957 fn grid_three_columns_fr() {
958 let mut engine = LayoutEngine::new();
959 for i in 1..=3 {
960 engine.add_node(wid(i), Style::default()).ok();
961 }
962 engine
963 .add_node_with_children(
964 wid(10),
965 Style {
966 display: Display::Grid,
967 grid_template_columns: vec![fr(1.0), fr(2.0), fr(1.0)],
968 size: taffy::Size {
969 width: Dimension::Length(80.0),
970 height: Dimension::Length(20.0),
971 },
972 ..Default::default()
973 },
974 &[wid(1), wid(2), wid(3)],
975 )
976 .ok();
977 engine.set_root(wid(10)).ok();
978 engine.compute(80, 20).ok();
979
980 let l1 = engine.layout(wid(1)).unwrap_or_default();
981 let l2 = engine.layout(wid(2)).unwrap_or_default();
982 let l3 = engine.layout(wid(3)).unwrap_or_default();
983 assert_eq!(l1.width, 20);
984 assert_eq!(l2.width, 40);
985 assert_eq!(l3.width, 20);
986 }
987
988 #[test]
989 fn grid_columns_mixed_units() {
990 let mut engine = LayoutEngine::new();
991 engine.add_node(wid(1), Style::default()).ok();
992 engine.add_node(wid(2), Style::default()).ok();
993 engine
994 .add_node_with_children(
995 wid(10),
996 Style {
997 display: Display::Grid,
998 grid_template_columns: vec![length(20.0), fr(1.0)],
999 size: taffy::Size {
1000 width: Dimension::Length(80.0),
1001 height: Dimension::Length(20.0),
1002 },
1003 ..Default::default()
1004 },
1005 &[wid(1), wid(2)],
1006 )
1007 .ok();
1008 engine.set_root(wid(10)).ok();
1009 engine.compute(80, 20).ok();
1010
1011 let l1 = engine.layout(wid(1)).unwrap_or_default();
1012 let l2 = engine.layout(wid(2)).unwrap_or_default();
1013 assert_eq!(l1.width, 20);
1014 assert_eq!(l2.width, 60);
1015 }
1016
1017 #[test]
1018 fn grid_rows_and_columns() {
1019 let mut engine = LayoutEngine::new();
1020 for i in 1..=4 {
1021 engine.add_node(wid(i), Style::default()).ok();
1022 }
1023 engine
1024 .add_node_with_children(
1025 wid(10),
1026 Style {
1027 display: Display::Grid,
1028 grid_template_columns: vec![fr(1.0), fr(1.0)],
1029 grid_template_rows: vec![fr(1.0), fr(1.0)],
1030 size: taffy::Size {
1031 width: Dimension::Length(80.0),
1032 height: Dimension::Length(20.0),
1033 },
1034 ..Default::default()
1035 },
1036 &[wid(1), wid(2), wid(3), wid(4)],
1037 )
1038 .ok();
1039 engine.set_root(wid(10)).ok();
1040 engine.compute(80, 20).ok();
1041
1042 let l1 = engine.layout(wid(1)).unwrap_or_default();
1043 let l4 = engine.layout(wid(4)).unwrap_or_default();
1044 assert_eq!(l1.width, 40);
1045 assert_eq!(l1.height, 10);
1046 assert_eq!(l4.x, 40);
1047 assert_eq!(l4.y, 10);
1048 }
1049
1050 #[test]
1051 fn grid_placement_span() {
1052 let mut engine = LayoutEngine::new();
1053 engine
1055 .add_node(
1056 wid(1),
1057 Style {
1058 grid_column: Line {
1059 start: GridPlacement::from_span(2),
1060 end: GridPlacement::Auto,
1061 },
1062 ..Default::default()
1063 },
1064 )
1065 .ok();
1066 engine.add_node(wid(2), Style::default()).ok();
1067 engine.add_node(wid(3), Style::default()).ok();
1068 engine
1069 .add_node_with_children(
1070 wid(10),
1071 Style {
1072 display: Display::Grid,
1073 grid_template_columns: vec![fr(1.0), fr(1.0)],
1074 size: taffy::Size {
1075 width: Dimension::Length(80.0),
1076 height: Dimension::Length(30.0),
1077 },
1078 ..Default::default()
1079 },
1080 &[wid(1), wid(2), wid(3)],
1081 )
1082 .ok();
1083 engine.set_root(wid(10)).ok();
1084 engine.compute(80, 30).ok();
1085
1086 let l1 = engine.layout(wid(1)).unwrap_or_default();
1087 assert_eq!(l1.width, 80); }
1089
1090 #[test]
1091 fn box_model_padding_shrinks_content() {
1092 let mut engine = LayoutEngine::new();
1093 engine
1094 .add_node(
1095 wid(1),
1096 Style {
1097 flex_grow: 1.0,
1098 ..Default::default()
1099 },
1100 )
1101 .ok();
1102 engine
1103 .add_node_with_children(
1104 wid(10),
1105 Style {
1106 padding: taffy::Rect {
1107 left: LengthPercentage::Length(5.0),
1108 right: LengthPercentage::Length(5.0),
1109 top: LengthPercentage::Length(2.0),
1110 bottom: LengthPercentage::Length(2.0),
1111 },
1112 size: taffy::Size {
1113 width: Dimension::Length(80.0),
1114 height: Dimension::Length(24.0),
1115 },
1116 ..Default::default()
1117 },
1118 &[wid(1)],
1119 )
1120 .ok();
1121 engine.set_root(wid(10)).ok();
1122 engine.compute(80, 24).ok();
1123
1124 let l = engine.layout(wid(1)).unwrap_or_default();
1125 assert_eq!(l.x, 5);
1126 assert_eq!(l.y, 2);
1127 assert_eq!(l.width, 70); assert_eq!(l.height, 20); }
1130
1131 #[test]
1132 fn box_model_margin_creates_space() {
1133 let mut engine = LayoutEngine::new();
1134 engine
1135 .add_node(
1136 wid(1),
1137 Style {
1138 size: taffy::Size {
1139 width: Dimension::Length(30.0),
1140 height: Dimension::Length(10.0),
1141 },
1142 margin: taffy::Rect {
1143 left: LengthPercentageAuto::Length(0.0),
1144 right: LengthPercentageAuto::Length(5.0),
1145 top: LengthPercentageAuto::Length(0.0),
1146 bottom: LengthPercentageAuto::Length(0.0),
1147 },
1148 ..Default::default()
1149 },
1150 )
1151 .ok();
1152 engine
1153 .add_node(
1154 wid(2),
1155 Style {
1156 size: taffy::Size {
1157 width: Dimension::Length(30.0),
1158 height: Dimension::Length(10.0),
1159 },
1160 ..Default::default()
1161 },
1162 )
1163 .ok();
1164 engine
1165 .add_node_with_children(
1166 wid(10),
1167 Style {
1168 size: taffy::Size {
1169 width: Dimension::Length(80.0),
1170 height: Dimension::Length(10.0),
1171 },
1172 ..Default::default()
1173 },
1174 &[wid(1), wid(2)],
1175 )
1176 .ok();
1177 engine.set_root(wid(10)).ok();
1178 engine.compute(80, 10).ok();
1179
1180 let l2 = engine.layout(wid(2)).unwrap_or_default();
1181 assert_eq!(l2.x, 35); }
1183
1184 #[test]
1185 fn box_model_border_width() {
1186 let mut engine = LayoutEngine::new();
1187 engine
1188 .add_node(
1189 wid(1),
1190 Style {
1191 flex_grow: 1.0,
1192 ..Default::default()
1193 },
1194 )
1195 .ok();
1196 engine
1197 .add_node_with_children(
1198 wid(10),
1199 Style {
1200 border: taffy::Rect {
1201 left: LengthPercentage::Length(1.0),
1202 right: LengthPercentage::Length(1.0),
1203 top: LengthPercentage::Length(1.0),
1204 bottom: LengthPercentage::Length(1.0),
1205 },
1206 size: taffy::Size {
1207 width: Dimension::Length(80.0),
1208 height: Dimension::Length(24.0),
1209 },
1210 ..Default::default()
1211 },
1212 &[wid(1)],
1213 )
1214 .ok();
1215 engine.set_root(wid(10)).ok();
1216 engine.compute(80, 24).ok();
1217
1218 let l = engine.layout(wid(1)).unwrap_or_default();
1219 assert_eq!(l.x, 1);
1220 assert_eq!(l.y, 1);
1221 assert_eq!(l.width, 78); assert_eq!(l.height, 22); }
1224
1225 #[test]
1226 fn box_model_combined() {
1227 let mut engine = LayoutEngine::new();
1228 engine
1229 .add_node(
1230 wid(1),
1231 Style {
1232 flex_grow: 1.0,
1233 ..Default::default()
1234 },
1235 )
1236 .ok();
1237 engine
1238 .add_node_with_children(
1239 wid(10),
1240 Style {
1241 padding: taffy::Rect {
1242 left: LengthPercentage::Length(2.0),
1243 right: LengthPercentage::Length(2.0),
1244 top: LengthPercentage::Length(1.0),
1245 bottom: LengthPercentage::Length(1.0),
1246 },
1247 border: taffy::Rect {
1248 left: LengthPercentage::Length(1.0),
1249 right: LengthPercentage::Length(1.0),
1250 top: LengthPercentage::Length(1.0),
1251 bottom: LengthPercentage::Length(1.0),
1252 },
1253 size: taffy::Size {
1254 width: Dimension::Length(80.0),
1255 height: Dimension::Length(24.0),
1256 },
1257 ..Default::default()
1258 },
1259 &[wid(1)],
1260 )
1261 .ok();
1262 engine.set_root(wid(10)).ok();
1263 engine.compute(80, 24).ok();
1264
1265 let l = engine.layout(wid(1)).unwrap_or_default();
1266 assert_eq!(l.x, 3);
1268 assert_eq!(l.y, 2); assert_eq!(l.width, 74); assert_eq!(l.height, 20); }
1272
1273 #[test]
1274 fn layout_error_display() {
1275 let e1 = LayoutError::WidgetNotFound(wid(42));
1276 assert!(format!("{e1}").contains("42"));
1277
1278 let e2 = LayoutError::TaffyError("boom".into());
1279 assert!(format!("{e2}").contains("boom"));
1280
1281 let e3 = LayoutError::NoRoot;
1282 assert!(format!("{e3}").contains("no root"));
1283 }
1284}