1use std::collections::HashMap;
13
14use taffy::{
15 NodeId, TaffyTree, TraversePartialTree,
16 geometry::{Point, Rect as TaffyRect, Size},
17 style::{
18 AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display as TaffyDisplay,
19 FlexDirection, FlexWrap as TaffyFlexWrap, JustifyContent, LengthPercentage,
20 LengthPercentageAuto, Overflow as TaffyOverflow, Position as TaffyPosition,
21 Style as TaffyStyle,
22 },
23};
24
25use crate::dom::{
26 Align, ContentAlign, Dim, Display, FlexDir, FlexWrap, Lp, Overflow, Position, Style,
27};
28
29use super::engine::{LayoutEngine, MeasureFn, Rect};
30
31pub fn style_to_taffy(s: &Style) -> TaffyStyle {
44 TaffyStyle {
45 position: map_position(s.position),
53
54 inset: TaffyRect {
56 top: map_dim_lpa(s.top.as_ref()),
57 right: map_dim_lpa(s.right.as_ref()),
58 bottom: map_dim_lpa(s.bottom.as_ref()),
59 left: map_dim_lpa(s.left.as_ref()),
60 },
61
62 margin: TaffyRect {
66 top: map_lpa(
67 s.margin_top
68 .as_ref()
69 .or(s.margin_y.as_ref())
70 .or(s.margin.as_ref()),
71 ),
72 bottom: map_lpa(
73 s.margin_bottom
74 .as_ref()
75 .or(s.margin_y.as_ref())
76 .or(s.margin.as_ref()),
77 ),
78 left: map_lpa(
79 s.margin_left
80 .as_ref()
81 .or(s.margin_x.as_ref())
82 .or(s.margin.as_ref()),
83 ),
84 right: map_lpa(
85 s.margin_right
86 .as_ref()
87 .or(s.margin_x.as_ref())
88 .or(s.margin.as_ref()),
89 ),
90 },
91
92 padding: TaffyRect {
94 top: map_lp(
95 s.padding_top
96 .as_ref()
97 .or(s.padding_y.as_ref())
98 .or(s.padding.as_ref()),
99 ),
100 bottom: map_lp(
101 s.padding_bottom
102 .as_ref()
103 .or(s.padding_y.as_ref())
104 .or(s.padding.as_ref()),
105 ),
106 left: map_lp(
107 s.padding_left
108 .as_ref()
109 .or(s.padding_x.as_ref())
110 .or(s.padding.as_ref()),
111 ),
112 right: map_lp(
113 s.padding_right
114 .as_ref()
115 .or(s.padding_x.as_ref())
116 .or(s.padding.as_ref()),
117 ),
118 },
119
120 border: {
126 let [top, right, bottom, left] = s.border_edges();
127 TaffyRect {
128 top: LengthPercentage::length(top as f32),
129 right: LengthPercentage::length(right as f32),
130 bottom: LengthPercentage::length(bottom as f32),
131 left: LengthPercentage::length(left as f32),
132 }
133 },
134
135 flex_direction: map_flex_dir(s.flex_direction),
140 flex_wrap: map_flex_wrap(s.flex_wrap),
141 flex_grow: s.flex_grow.unwrap_or(0.0),
142 flex_shrink: s.flex_shrink.unwrap_or(1.0),
143 flex_basis: map_dim(s.flex_basis.as_ref()),
144 align_items: s.align_items.map(map_align_items),
145 align_self: s.align_self.map(map_align_self),
147 align_content: Some(
153 s.align_content
154 .map(map_content_align_content)
155 .unwrap_or(AlignContent::FlexStart),
156 ),
157 justify_content: s.justify_content.map(map_content_align_justify),
158
159 size: Size {
161 width: map_dim(s.width.as_ref()),
162 height: map_dim(s.height.as_ref()),
163 },
164 min_size: Size {
169 width: map_dim(s.min_width.as_ref()),
170 height: map_dim(s.min_height.as_ref()),
171 },
172 max_size: Size {
173 width: map_dim(s.max_width.as_ref()),
174 height: map_dim(s.max_height.as_ref()),
175 },
176 aspect_ratio: s.aspect_ratio,
180
181 display: map_display(s.display),
183
184 gap: Size {
187 width: map_gap(s.column_gap.or(s.gap)),
188 height: map_gap(s.row_gap.or(s.gap)),
189 },
190
191 overflow: Point {
193 x: map_overflow(s.overflow_x),
194 y: map_overflow(s.overflow_y),
195 },
196
197 ..Default::default()
200 }
201}
202
203fn map_position(p: Option<Position>) -> TaffyPosition {
206 match p.unwrap_or(Position::Relative) {
207 Position::Absolute => TaffyPosition::Absolute,
208 Position::Relative | Position::Static => TaffyPosition::Relative,
209 }
210}
211
212fn map_dim(d: Option<&Dim>) -> Dimension {
213 match d {
214 None | Some(Dim::Auto) => Dimension::auto(),
215 Some(Dim::Points(v)) => Dimension::length(*v),
216 Some(Dim::Percent(p)) => Dimension::percent(p / 100.0),
217 }
218}
219
220fn map_dim_lpa(d: Option<&Dim>) -> LengthPercentageAuto {
221 match d {
222 None | Some(Dim::Auto) => LengthPercentageAuto::auto(),
223 Some(Dim::Points(v)) => LengthPercentageAuto::length(*v),
224 Some(Dim::Percent(p)) => LengthPercentageAuto::percent(p / 100.0),
225 }
226}
227
228fn map_lp(lp: Option<&Lp>) -> LengthPercentage {
229 match lp {
230 None => LengthPercentage::length(0.0),
231 Some(Lp::Points(v)) => LengthPercentage::length(*v),
232 Some(Lp::Percent(p)) => LengthPercentage::percent(p / 100.0),
233 }
234}
235
236fn map_lpa(lp: Option<&Lp>) -> LengthPercentageAuto {
240 match lp {
241 None => LengthPercentageAuto::length(0.0),
244 Some(Lp::Points(v)) => LengthPercentageAuto::length(*v),
245 Some(Lp::Percent(p)) => LengthPercentageAuto::percent(p / 100.0),
246 }
247}
248
249fn map_flex_dir(d: Option<FlexDir>) -> FlexDirection {
250 match d.unwrap_or(FlexDir::Row) {
251 FlexDir::Row => FlexDirection::Row,
252 FlexDir::Column => FlexDirection::Column,
253 FlexDir::RowReverse => FlexDirection::RowReverse,
254 FlexDir::ColumnReverse => FlexDirection::ColumnReverse,
255 }
256}
257
258fn map_flex_wrap(w: Option<FlexWrap>) -> TaffyFlexWrap {
259 match w.unwrap_or(FlexWrap::NoWrap) {
260 FlexWrap::NoWrap => TaffyFlexWrap::NoWrap,
261 FlexWrap::Wrap => TaffyFlexWrap::Wrap,
262 FlexWrap::WrapReverse => TaffyFlexWrap::WrapReverse,
263 }
264}
265
266fn map_align_items(a: Align) -> AlignItems {
267 match a {
268 Align::Stretch => AlignItems::Stretch,
269 Align::FlexStart => AlignItems::FlexStart,
270 Align::Center => AlignItems::Center,
271 Align::FlexEnd => AlignItems::FlexEnd,
272 Align::Baseline => AlignItems::Baseline,
273 }
274}
275
276fn map_align_self(a: Align) -> AlignSelf {
277 match a {
278 Align::Stretch => AlignSelf::Stretch,
279 Align::FlexStart => AlignSelf::FlexStart,
280 Align::Center => AlignSelf::Center,
281 Align::FlexEnd => AlignSelf::FlexEnd,
282 Align::Baseline => AlignSelf::Baseline,
283 }
284}
285
286fn map_content_align_content(c: ContentAlign) -> AlignContent {
287 match c {
288 ContentAlign::FlexStart => AlignContent::FlexStart,
289 ContentAlign::Center => AlignContent::Center,
290 ContentAlign::FlexEnd => AlignContent::FlexEnd,
291 ContentAlign::SpaceBetween => AlignContent::SpaceBetween,
292 ContentAlign::SpaceAround => AlignContent::SpaceAround,
293 ContentAlign::SpaceEvenly => AlignContent::SpaceEvenly,
294 ContentAlign::Stretch => AlignContent::Stretch,
295 }
296}
297
298fn map_content_align_justify(c: ContentAlign) -> JustifyContent {
299 match c {
300 ContentAlign::FlexStart => JustifyContent::FlexStart,
301 ContentAlign::Center => JustifyContent::Center,
302 ContentAlign::FlexEnd => JustifyContent::FlexEnd,
303 ContentAlign::SpaceBetween => JustifyContent::SpaceBetween,
304 ContentAlign::SpaceAround => JustifyContent::SpaceAround,
305 ContentAlign::SpaceEvenly => JustifyContent::SpaceEvenly,
306 ContentAlign::Stretch => JustifyContent::Stretch,
307 }
308}
309
310fn map_display(d: Option<Display>) -> TaffyDisplay {
311 match d.unwrap_or(Display::Flex) {
312 Display::Flex => TaffyDisplay::Flex,
313 Display::None => TaffyDisplay::None,
314 }
315}
316
317fn map_gap(g: Option<f32>) -> LengthPercentage {
318 match g {
319 None => LengthPercentage::length(0.0),
320 Some(v) => LengthPercentage::length(v),
321 }
322}
323
324fn map_overflow(o: Option<Overflow>) -> TaffyOverflow {
325 match o.unwrap_or(Overflow::Visible) {
326 Overflow::Visible => TaffyOverflow::Visible,
327 Overflow::Hidden => TaffyOverflow::Hidden,
328 }
329}
330
331pub struct TaffyEngine {
337 tree: TaffyTree<u32>,
343
344 id_map: HashMap<u32, NodeId>,
346
347 measures: HashMap<u32, Box<MeasureFn>>,
352
353 rounded: HashMap<u32, Rect>,
358
359 rounded_absolute: HashMap<u32, Rect>,
367}
368
369impl TaffyEngine {
370 pub fn new() -> Self {
372 let mut tree = TaffyTree::new();
373 tree.disable_rounding();
377 Self {
378 tree,
379 id_map: HashMap::new(),
380 measures: HashMap::new(),
381 rounded: HashMap::new(),
382 rounded_absolute: HashMap::new(),
383 }
384 }
385
386 fn taffy_id(&self, dom_id: u32) -> Result<NodeId, String> {
388 self.id_map
389 .get(&dom_id)
390 .copied()
391 .ok_or_else(|| format!("layout: unknown dom id {dom_id}"))
392 }
393
394 pub fn computed_absolute(&self, id: u32) -> Option<Rect> {
410 self.rounded_absolute.get(&id).copied()
411 }
412}
413
414fn inexact_equals(a: f64, b: f64) -> bool {
434 (a - b).abs() < 0.0001
435}
436
437fn round_value_to_pixel_grid(value: f64, force_ceil: bool, force_floor: bool) -> f64 {
453 let mut scaled = value;
454 let mut fractial = scaled % 1.0;
458 if fractial < 0.0 {
459 fractial += 1.0;
460 }
461 let delta = if inexact_equals(fractial, 0.0) {
473 -fractial
475 } else if inexact_equals(fractial, 1.0) || force_ceil {
476 1.0 - fractial
478 } else if force_floor {
479 -fractial
481 } else if fractial > 0.5 || inexact_equals(fractial, 0.5) {
482 1.0 - fractial
484 } else {
485 -fractial
487 };
488 scaled += delta;
489 scaled
490}
491
492struct RoundOrigin {
501 absolute_left: f64,
502 absolute_top: f64,
503 rounded_left: i32,
504 rounded_top: i32,
505}
506
507fn round_node(
522 tree: &TaffyTree<u32>,
523 is_text: &dyn Fn(u32) -> bool,
524 nid: NodeId,
525 dom_id: u32,
526 origin: RoundOrigin,
527 out: &mut HashMap<u32, Rect>,
528 out_absolute: &mut HashMap<u32, Rect>,
529) {
530 let RoundOrigin {
531 absolute_left,
532 absolute_top,
533 rounded_left,
534 rounded_top,
535 } = origin;
536 let Ok(layout) = tree.layout(nid) else {
538 return;
539 };
540 let node_left = f64::from(layout.location.x);
541 let node_top = f64::from(layout.location.y);
542 let node_width = f64::from(layout.size.width);
543 let node_height = f64::from(layout.size.height);
544
545 let absolute_node_left = absolute_left + node_left;
546 let absolute_node_top = absolute_top + node_top;
547 let absolute_node_right = absolute_node_left + node_width;
548 let absolute_node_bottom = absolute_node_top + node_height;
549
550 let text_rounding = is_text(dom_id);
554
555 let rx = round_value_to_pixel_grid(node_left, false, text_rounding);
558 let ry = round_value_to_pixel_grid(node_top, false, text_rounding);
559
560 let has_fractional_width =
564 !inexact_equals(node_width % 1.0, 0.0) && !inexact_equals(node_width % 1.0, 1.0);
565 let has_fractional_height =
566 !inexact_equals(node_height % 1.0, 0.0) && !inexact_equals(node_height % 1.0, 1.0);
567
568 let rw = round_value_to_pixel_grid(
569 absolute_node_right,
570 text_rounding && has_fractional_width,
571 text_rounding && !has_fractional_width,
572 ) - round_value_to_pixel_grid(absolute_node_left, false, text_rounding);
573 let rh = round_value_to_pixel_grid(
574 absolute_node_bottom,
575 text_rounding && has_fractional_height,
576 text_rounding && !has_fractional_height,
577 ) - round_value_to_pixel_grid(absolute_node_top, false, text_rounding);
578
579 let rect = Rect {
580 x: rx.round() as i32,
581 y: ry.round() as i32,
582 width: rw.max(0.0).round() as u16,
583 height: rh.max(0.0).round() as u16,
584 };
585 out.insert(dom_id, rect);
586
587 let abs_x = rounded_left + rect.x;
595 let abs_y = rounded_top + rect.y;
596 out_absolute.insert(
597 dom_id,
598 Rect {
599 x: abs_x,
600 y: abs_y,
601 ..rect
602 },
603 );
604
605 let child_count = tree.child_count(nid);
609 for index in 0..child_count {
610 let Ok(child_nid) = tree.child_at_index(nid, index) else {
611 continue;
612 };
613 let Some(child_dom) = tree.get_node_context(child_nid).copied() else {
615 continue;
616 };
617 round_node(
618 tree,
619 is_text,
620 child_nid,
621 child_dom,
622 RoundOrigin {
623 absolute_left: absolute_node_left,
624 absolute_top: absolute_node_top,
625 rounded_left: abs_x,
626 rounded_top: abs_y,
627 },
628 out,
629 out_absolute,
630 );
631 }
632}
633
634impl Default for TaffyEngine {
635 fn default() -> Self {
636 Self::new()
637 }
638}
639
640impl LayoutEngine for TaffyEngine {
641 fn create(&mut self, id: u32) -> Result<(), String> {
642 if self.id_map.contains_key(&id) {
645 return Ok(());
646 }
647 let nid = self
650 .tree
651 .new_leaf_with_context(TaffyStyle::default(), id)
652 .map_err(|e| e.to_string())?;
653 self.id_map.insert(id, nid);
654 Ok(())
655 }
656
657 fn apply_style(&mut self, id: u32, style: &Style) -> Result<(), String> {
658 let nid = self.taffy_id(id)?;
659 self.tree
660 .set_style(nid, style_to_taffy(style))
661 .map_err(|e| e.to_string())
662 }
663
664 fn set_measure(&mut self, id: u32, f: Box<MeasureFn>) {
665 self.measures.insert(id, f);
669 }
670
671 fn insert_child(&mut self, parent: u32, child: u32, index: usize) -> Result<(), String> {
672 let pnid = self.taffy_id(parent)?;
673 let cnid = self.taffy_id(child)?;
674
675 let child_count = self.tree.child_count(pnid);
676 if index >= child_count {
677 self.tree.add_child(pnid, cnid).map_err(|e| e.to_string())
679 } else {
680 self.tree
681 .insert_child_at_index(pnid, index, cnid)
682 .map_err(|e| e.to_string())
683 }
684 }
685
686 fn remove_child(&mut self, parent: u32, child: u32) -> Result<(), String> {
687 let pnid = self.taffy_id(parent)?;
688 let cnid = self.taffy_id(child)?;
689 self.tree
690 .remove_child(pnid, cnid)
691 .map(|_| ())
692 .map_err(|e| e.to_string())
693 }
694
695 fn destroy(&mut self, id: u32) {
696 let Some(nid) = self.id_map.remove(&id) else {
698 return;
699 };
700 self.measures.remove(&id);
702 self.rounded.remove(&id);
709 self.rounded_absolute.remove(&id);
710 let _ = self.tree.remove(nid);
715 }
716
717 fn mark_dirty(&mut self, id: u32) -> Result<(), String> {
718 let nid = self.taffy_id(id)?;
719 self.tree.mark_dirty(nid).map_err(|e| e.to_string())
720 }
721
722 fn calculate(
723 &mut self,
724 root_id: u32,
725 viewport_width: f32,
726 viewport_height: Option<f32>,
727 ) -> Result<(), String> {
728 let root_nid = self.taffy_id(root_id)?;
729
730 if let Ok(mut s) = self.tree.style(root_nid).cloned() {
737 s.size.width = Dimension::length(viewport_width);
738 let _ = self.tree.set_style(root_nid, s);
739 }
740
741 let available = Size {
743 width: AvailableSpace::Definite(viewport_width),
744 height: match viewport_height {
745 Some(h) => AvailableSpace::Definite(h),
746 None => AvailableSpace::MaxContent,
747 },
748 };
749
750 self.tree
755 .compute_layout_with_measure(root_nid, available, |known, avail, _nid, ctx, _style| {
756 if let Some(&mut did) = ctx
758 && let Some(f) = self.measures.get_mut(&did)
759 {
760 return f(known, avail);
761 }
762 Size::ZERO
765 })
766 .map_err(|e| e.to_string())?;
767
768 self.rounded.clear();
774 self.rounded_absolute.clear();
775 let is_text = |dom_id: u32| self.measures.contains_key(&dom_id);
776 round_node(
777 &self.tree,
778 &is_text,
779 root_nid,
780 root_id,
781 RoundOrigin {
782 absolute_left: 0.0,
783 absolute_top: 0.0,
784 rounded_left: 0,
785 rounded_top: 0,
786 },
787 &mut self.rounded,
788 &mut self.rounded_absolute,
789 );
790 Ok(())
791 }
792
793 fn computed(&self, id: u32) -> Option<Rect> {
794 if let Some(r) = self.rounded.get(&id) {
798 return Some(*r);
799 }
800
801 let nid = self.id_map.get(&id).copied()?;
811 let lay = self.tree.layout(nid).ok()?;
812 Some(Rect {
813 x: lay.location.x as i32,
814 y: lay.location.y as i32,
815 width: lay.size.width as u16,
816 height: lay.size.height as u16,
817 })
818 }
819}
820
821#[cfg(test)]
824mod tests {
825 use super::*;
826 use crate::dom::BorderStyle;
827 use taffy::style::FlexDirection as TFD;
828
829 fn pt(v: f32) -> Dimension {
830 Dimension::length(v)
831 }
832
833 fn engine_with(ids: &[u32]) -> TaffyEngine {
835 let mut e = TaffyEngine::new();
836 for &id in ids {
837 e.create(id).unwrap();
838 }
839 e
840 }
841
842 #[test]
848 fn map_position_absolute() {
849 let s = Style {
850 position: Some(Position::Absolute),
851 ..Default::default()
852 };
853 assert_eq!(style_to_taffy(&s).position, TaffyPosition::Absolute);
854 }
855
856 #[test]
857 fn map_position_relative() {
858 let s = Style {
859 position: Some(Position::Relative),
860 ..Default::default()
861 };
862 assert_eq!(style_to_taffy(&s).position, TaffyPosition::Relative);
863 }
864
865 #[test]
866 fn map_position_static_maps_to_relative() {
867 let s = Style {
869 position: Some(Position::Static),
870 ..Default::default()
871 };
872 assert_eq!(style_to_taffy(&s).position, TaffyPosition::Relative);
873 }
874
875 #[test]
876 fn map_inset_points() {
877 let s = Style {
878 top: Some(Dim::Points(2.0)),
879 left: Some(Dim::Points(5.0)),
880 ..Default::default()
881 };
882 let t = style_to_taffy(&s);
883 assert_eq!(t.inset.top, LengthPercentageAuto::length(2.0));
884 assert_eq!(t.inset.left, LengthPercentageAuto::length(5.0));
885 assert_eq!(t.inset.right, LengthPercentageAuto::auto());
886 assert_eq!(t.inset.bottom, LengthPercentageAuto::auto());
887 }
888
889 #[test]
890 fn map_inset_percent() {
891 let s = Style {
892 top: Some(Dim::Percent(50.0)),
893 ..Default::default()
894 };
895 assert_eq!(
897 style_to_taffy(&s).inset.top,
898 LengthPercentageAuto::percent(0.5)
899 );
900 }
901
902 #[test]
906 fn map_margin_cascade() {
907 let s = Style {
908 margin: Some(Lp::Points(2.0)),
909 margin_x: Some(Lp::Points(4.0)),
910 margin_left: Some(Lp::Points(7.0)),
911 ..Default::default()
912 };
913 let t = style_to_taffy(&s);
914 assert_eq!(t.margin.top, LengthPercentageAuto::length(2.0));
915 assert_eq!(t.margin.bottom, LengthPercentageAuto::length(2.0));
916 assert_eq!(t.margin.right, LengthPercentageAuto::length(4.0));
917 assert_eq!(t.margin.left, LengthPercentageAuto::length(7.0));
918 }
919
920 #[test]
921 fn map_margin_y_shorthand() {
922 let s = Style {
923 margin: Some(Lp::Points(1.0)),
924 margin_y: Some(Lp::Points(3.0)),
925 ..Default::default()
926 };
927 let t = style_to_taffy(&s);
928 assert_eq!(t.margin.top, LengthPercentageAuto::length(3.0));
929 assert_eq!(t.margin.bottom, LengthPercentageAuto::length(3.0));
930 assert_eq!(t.margin.left, LengthPercentageAuto::length(1.0));
931 assert_eq!(t.margin.right, LengthPercentageAuto::length(1.0));
932 }
933
934 #[test]
935 fn map_margin_percent() {
936 let s = Style {
938 margin: Some(Lp::Percent(50.0)),
939 ..Default::default()
940 };
941 let t = style_to_taffy(&s);
942 assert_eq!(t.margin.top, LengthPercentageAuto::percent(0.5));
943 }
944
945 #[test]
947 fn map_padding_cascade() {
948 let s = Style {
949 padding: Some(Lp::Points(2.0)),
950 padding_x: Some(Lp::Points(4.0)),
951 padding_top: Some(Lp::Points(1.0)),
952 ..Default::default()
953 };
954 let t = style_to_taffy(&s);
955 assert_eq!(t.padding.top, LengthPercentage::length(1.0));
956 assert_eq!(t.padding.bottom, LengthPercentage::length(2.0));
957 assert_eq!(t.padding.left, LengthPercentage::length(4.0));
958 assert_eq!(t.padding.right, LengthPercentage::length(4.0));
959 }
960
961 #[test]
963 fn map_flex_direction_column() {
964 let s = Style {
965 flex_direction: Some(FlexDir::Column),
966 ..Default::default()
967 };
968 assert_eq!(style_to_taffy(&s).flex_direction, FlexDirection::Column);
969 }
970
971 #[test]
972 fn map_flex_direction_row_reverse() {
973 let s = Style {
974 flex_direction: Some(FlexDir::RowReverse),
975 ..Default::default()
976 };
977 assert_eq!(style_to_taffy(&s).flex_direction, FlexDirection::RowReverse);
978 }
979
980 #[test]
981 fn map_flex_wrap() {
982 let s = Style {
983 flex_wrap: Some(FlexWrap::Wrap),
984 ..Default::default()
985 };
986 assert_eq!(style_to_taffy(&s).flex_wrap, TaffyFlexWrap::Wrap);
987 }
988
989 #[test]
990 fn map_flex_grow_shrink() {
991 let s = Style {
992 flex_grow: Some(2.0),
993 flex_shrink: Some(0.5),
994 ..Default::default()
995 };
996 let t = style_to_taffy(&s);
997 assert_eq!(t.flex_grow, 2.0);
998 assert_eq!(t.flex_shrink, 0.5);
999 }
1000
1001 #[test]
1002 fn map_flex_basis_points() {
1003 let s = Style {
1004 flex_basis: Some(Dim::Points(40.0)),
1005 ..Default::default()
1006 };
1007 assert_eq!(style_to_taffy(&s).flex_basis, Dimension::length(40.0));
1008 }
1009
1010 #[test]
1011 fn map_flex_basis_percent() {
1012 let s = Style {
1014 flex_basis: Some(Dim::Percent(50.0)),
1015 ..Default::default()
1016 };
1017 assert_eq!(style_to_taffy(&s).flex_basis, Dimension::percent(0.5));
1018 }
1019
1020 #[test]
1021 fn map_flex_basis_auto() {
1022 let s = Style {
1023 flex_basis: Some(Dim::Auto),
1024 ..Default::default()
1025 };
1026 assert_eq!(style_to_taffy(&s).flex_basis, Dimension::auto());
1027 }
1028
1029 #[test]
1030 fn map_align_items_center() {
1031 let s = Style {
1032 align_items: Some(Align::Center),
1033 ..Default::default()
1034 };
1035 assert_eq!(style_to_taffy(&s).align_items, Some(AlignItems::Center));
1036 }
1037
1038 #[test]
1039 fn map_align_self_none_is_auto() {
1040 let s = Style {
1042 align_self: None,
1043 ..Default::default()
1044 };
1045 assert_eq!(style_to_taffy(&s).align_self, None);
1046 }
1047
1048 #[test]
1049 fn map_align_content_space_between() {
1050 let s = Style {
1051 align_content: Some(ContentAlign::SpaceBetween),
1052 ..Default::default()
1053 };
1054 assert_eq!(
1055 style_to_taffy(&s).align_content,
1056 Some(AlignContent::SpaceBetween)
1057 );
1058 }
1059
1060 #[test]
1061 fn map_justify_content_center() {
1062 let s = Style {
1063 justify_content: Some(ContentAlign::Center),
1064 ..Default::default()
1065 };
1066 assert_eq!(
1067 style_to_taffy(&s).justify_content,
1068 Some(JustifyContent::Center)
1069 );
1070 }
1071
1072 #[test]
1076 fn map_width_height_points() {
1077 let s = Style {
1078 width: Some(Dim::Points(80.0)),
1079 height: Some(Dim::Points(24.0)),
1080 ..Default::default()
1081 };
1082 let t = style_to_taffy(&s);
1083 assert_eq!(t.size.width, Dimension::length(80.0));
1084 assert_eq!(t.size.height, Dimension::length(24.0));
1085 }
1086
1087 #[test]
1088 fn map_width_percent() {
1089 let s = Style {
1090 width: Some(Dim::Percent(50.0)),
1091 ..Default::default()
1092 };
1093 assert_eq!(style_to_taffy(&s).size.width, Dimension::percent(0.5));
1094 }
1095
1096 #[test]
1097 fn map_width_auto() {
1098 let s = Style {
1099 width: Some(Dim::Auto),
1100 ..Default::default()
1101 };
1102 assert_eq!(style_to_taffy(&s).size.width, Dimension::auto());
1103 }
1104
1105 #[test]
1106 fn map_min_max_size() {
1107 let s = Style {
1108 min_width: Some(Dim::Points(10.0)),
1109 max_width: Some(Dim::Points(100.0)),
1110 min_height: Some(Dim::Points(5.0)),
1111 max_height: Some(Dim::Points(50.0)),
1112 ..Default::default()
1113 };
1114 let t = style_to_taffy(&s);
1115 assert_eq!(t.min_size.width, Dimension::length(10.0));
1116 assert_eq!(t.max_size.width, Dimension::length(100.0));
1117 assert_eq!(t.min_size.height, Dimension::length(5.0));
1118 assert_eq!(t.max_size.height, Dimension::length(50.0));
1119 }
1120
1121 #[test]
1122 fn map_aspect_ratio() {
1123 let s = Style {
1124 aspect_ratio: Some(16.0 / 9.0),
1125 ..Default::default()
1126 };
1127 let t = style_to_taffy(&s);
1128 assert!((t.aspect_ratio.unwrap() - 16.0 / 9.0).abs() < f32::EPSILON);
1129 }
1130
1131 #[test]
1133 fn map_display_flex() {
1134 let s = Style {
1135 display: Some(Display::Flex),
1136 ..Default::default()
1137 };
1138 assert_eq!(style_to_taffy(&s).display, TaffyDisplay::Flex);
1139 }
1140
1141 #[test]
1142 fn map_display_none() {
1143 let s = Style {
1144 display: Some(Display::None),
1145 ..Default::default()
1146 };
1147 assert_eq!(style_to_taffy(&s).display, TaffyDisplay::None);
1148 }
1149
1150 #[test]
1155 fn map_border_all_edges_active() {
1156 let s = Style {
1157 border_style: Some(BorderStyle::Named("single".into())),
1158 ..Default::default()
1159 };
1160 let t = style_to_taffy(&s);
1161 assert_eq!(t.border.top, LengthPercentage::length(1.0));
1162 assert_eq!(t.border.right, LengthPercentage::length(1.0));
1163 assert_eq!(t.border.bottom, LengthPercentage::length(1.0));
1164 assert_eq!(t.border.left, LengthPercentage::length(1.0));
1165 }
1166
1167 #[test]
1168 fn map_border_top_disabled() {
1169 let s = Style {
1170 border_style: Some(BorderStyle::Named("single".into())),
1171 border_top: Some(false),
1172 ..Default::default()
1173 };
1174 let t = style_to_taffy(&s);
1175 assert_eq!(t.border.top, LengthPercentage::length(0.0));
1176 assert_eq!(t.border.bottom, LengthPercentage::length(1.0));
1177 assert_eq!(t.border.left, LengthPercentage::length(1.0));
1178 assert_eq!(t.border.right, LengthPercentage::length(1.0));
1179 }
1180
1181 #[test]
1182 fn map_border_none_when_no_border_style() {
1183 let s = Style {
1184 border_style: None,
1185 ..Default::default()
1186 };
1187 let t = style_to_taffy(&s);
1188 assert_eq!(t.border.top, LengthPercentage::length(0.0));
1189 assert_eq!(t.border.right, LengthPercentage::length(0.0));
1190 assert_eq!(t.border.bottom, LengthPercentage::length(0.0));
1191 assert_eq!(t.border.left, LengthPercentage::length(0.0));
1192 }
1193
1194 #[test]
1198 fn map_gap_all() {
1199 let s = Style {
1200 gap: Some(2.0),
1201 ..Default::default()
1202 };
1203 let t = style_to_taffy(&s);
1204 assert_eq!(t.gap.width, LengthPercentage::length(2.0));
1205 assert_eq!(t.gap.height, LengthPercentage::length(2.0));
1206 }
1207
1208 #[test]
1209 fn map_gap_per_axis() {
1210 let s = Style {
1211 column_gap: Some(4.0),
1212 row_gap: Some(1.0),
1213 ..Default::default()
1214 };
1215 let t = style_to_taffy(&s);
1216 assert_eq!(t.gap.width, LengthPercentage::length(4.0));
1217 assert_eq!(t.gap.height, LengthPercentage::length(1.0));
1218 }
1219
1220 #[test]
1221 fn map_gap_per_axis_overrides_all() {
1222 let s = Style {
1224 gap: Some(2.0),
1225 column_gap: Some(4.0),
1226 ..Default::default()
1227 };
1228 let t = style_to_taffy(&s);
1229 assert_eq!(t.gap.width, LengthPercentage::length(4.0));
1230 assert_eq!(t.gap.height, LengthPercentage::length(2.0));
1231 }
1232
1233 #[test]
1235 fn map_overflow_hidden() {
1236 let s = Style {
1237 overflow_x: Some(Overflow::Hidden),
1238 ..Default::default()
1239 };
1240 assert_eq!(style_to_taffy(&s).overflow.x, TaffyOverflow::Hidden);
1241 assert_eq!(style_to_taffy(&s).overflow.y, TaffyOverflow::Visible);
1242 }
1243
1244 #[test]
1252 fn apply_style_width_height() {
1253 let mut e = TaffyEngine::new();
1254 e.create(0).unwrap();
1255 let s = Style {
1256 width: Some(Dim::Points(80.0)),
1257 height: Some(Dim::Points(24.0)),
1258 ..Default::default()
1259 };
1260 e.apply_style(0, &s).unwrap();
1261 e.calculate(0, 80.0, Some(24.0)).unwrap();
1262 assert_eq!(
1263 e.computed(0).unwrap(),
1264 Rect {
1265 x: 0,
1266 y: 0,
1267 width: 80,
1268 height: 24
1269 }
1270 );
1271 }
1272
1273 #[test]
1276 fn apply_style_flex_direction_column() {
1277 let mut e = engine_with(&[0, 1, 2]);
1278 e.insert_child(0, 1, 0).unwrap();
1279 e.insert_child(0, 2, 1).unwrap();
1280
1281 e.apply_style(
1282 0,
1283 &Style {
1284 width: Some(Dim::Points(80.0)),
1285 height: Some(Dim::Points(24.0)),
1286 flex_direction: Some(FlexDir::Column),
1287 ..Default::default()
1288 },
1289 )
1290 .unwrap();
1291 e.apply_style(
1292 1,
1293 &Style {
1294 width: Some(Dim::Points(80.0)),
1295 height: Some(Dim::Points(12.0)),
1296 ..Default::default()
1297 },
1298 )
1299 .unwrap();
1300 e.apply_style(
1301 2,
1302 &Style {
1303 width: Some(Dim::Points(80.0)),
1304 height: Some(Dim::Points(12.0)),
1305 ..Default::default()
1306 },
1307 )
1308 .unwrap();
1309
1310 e.calculate(0, 80.0, Some(24.0)).unwrap();
1311 let r1 = e.computed(1).unwrap();
1312 let r2 = e.computed(2).unwrap();
1313 assert_eq!(
1314 r1,
1315 Rect {
1316 x: 0,
1317 y: 0,
1318 width: 80,
1319 height: 12
1320 }
1321 );
1322 assert_eq!(
1323 r2,
1324 Rect {
1325 x: 0,
1326 y: 12,
1327 width: 80,
1328 height: 12
1329 }
1330 );
1331 }
1332
1333 #[test]
1337 fn apply_style_gap() {
1338 let mut e = engine_with(&[0, 1, 2]);
1339 e.insert_child(0, 1, 0).unwrap();
1340 e.insert_child(0, 2, 1).unwrap();
1341
1342 e.apply_style(
1343 0,
1344 &Style {
1345 width: Some(Dim::Points(80.0)),
1346 height: Some(Dim::Points(24.0)),
1347 gap: Some(2.0),
1348 align_items: Some(Align::FlexStart),
1349 ..Default::default()
1350 },
1351 )
1352 .unwrap();
1353 e.apply_style(
1354 1,
1355 &Style {
1356 width: Some(Dim::Points(10.0)),
1357 height: Some(Dim::Points(5.0)),
1358 ..Default::default()
1359 },
1360 )
1361 .unwrap();
1362 e.apply_style(
1363 2,
1364 &Style {
1365 width: Some(Dim::Points(10.0)),
1366 height: Some(Dim::Points(5.0)),
1367 ..Default::default()
1368 },
1369 )
1370 .unwrap();
1371
1372 e.calculate(0, 80.0, Some(24.0)).unwrap();
1373 let c1 = e.computed(1).unwrap();
1374 let c2 = e.computed(2).unwrap();
1375 assert_eq!(c1.x, 0);
1376 assert_eq!(c2.x, 12); }
1378
1379 #[test]
1382 fn apply_style_padding() {
1383 let mut e = engine_with(&[0, 1]);
1384 e.insert_child(0, 1, 0).unwrap();
1385
1386 e.apply_style(
1387 0,
1388 &Style {
1389 width: Some(Dim::Points(80.0)),
1390 height: Some(Dim::Points(24.0)),
1391 padding: Some(Lp::Points(2.0)),
1392 align_items: Some(Align::FlexStart),
1393 ..Default::default()
1394 },
1395 )
1396 .unwrap();
1397 e.apply_style(
1398 1,
1399 &Style {
1400 width: Some(Dim::Points(10.0)),
1401 height: Some(Dim::Points(5.0)),
1402 ..Default::default()
1403 },
1404 )
1405 .unwrap();
1406
1407 e.calculate(0, 80.0, Some(24.0)).unwrap();
1408 let child = e.computed(1).unwrap();
1409 assert_eq!(child.x, 2);
1410 assert_eq!(child.y, 2);
1411 }
1412
1413 #[test]
1418 fn apply_style_border() {
1419 let mut e = engine_with(&[0, 1]);
1420 e.insert_child(0, 1, 0).unwrap();
1421
1422 e.apply_style(
1423 0,
1424 &Style {
1425 width: Some(Dim::Points(10.0)),
1426 height: Some(Dim::Points(5.0)),
1427 border_style: Some(BorderStyle::Named("single".into())),
1428 align_items: Some(Align::FlexStart),
1429 ..Default::default()
1430 },
1431 )
1432 .unwrap();
1433 e.apply_style(
1434 1,
1435 &Style {
1436 width: Some(Dim::Points(6.0)),
1437 height: Some(Dim::Points(3.0)),
1438 ..Default::default()
1439 },
1440 )
1441 .unwrap();
1442
1443 e.calculate(0, 10.0, Some(5.0)).unwrap();
1444 let child = e.computed(1).unwrap();
1445 assert_eq!(child.x, 1);
1447 assert_eq!(child.y, 1);
1448 }
1449
1450 #[test]
1460 fn computed_absolute_nested_offsets() {
1461 let mut e = engine_with(&[0, 1, 2]);
1462 e.insert_child(0, 1, 0).unwrap();
1463 e.insert_child(1, 2, 0).unwrap();
1464
1465 e.apply_style(
1466 0,
1467 &Style {
1468 width: Some(Dim::Points(40.0)),
1469 height: Some(Dim::Points(10.0)),
1470 padding: Some(Lp::Points(2.0)),
1471 align_items: Some(Align::FlexStart),
1472 ..Default::default()
1473 },
1474 )
1475 .unwrap();
1476 e.apply_style(
1477 1,
1478 &Style {
1479 width: Some(Dim::Points(20.0)),
1480 height: Some(Dim::Points(6.0)),
1481 margin_left: Some(Lp::Points(3.0)),
1482 margin_top: Some(Lp::Points(2.0)),
1483 align_items: Some(Align::FlexStart),
1484 ..Default::default()
1485 },
1486 )
1487 .unwrap();
1488 e.apply_style(
1489 2,
1490 &Style {
1491 width: Some(Dim::Points(6.0)),
1492 height: Some(Dim::Points(2.0)),
1493 margin_left: Some(Lp::Points(4.0)),
1494 margin_top: Some(Lp::Points(1.0)),
1495 ..Default::default()
1496 },
1497 )
1498 .unwrap();
1499
1500 e.calculate(0, 40.0, Some(10.0)).unwrap();
1501
1502 assert_eq!(
1504 e.computed(1).unwrap(),
1505 Rect {
1506 x: 5,
1507 y: 4,
1508 width: 20,
1509 height: 6
1510 }
1511 );
1512 assert_eq!(
1513 e.computed(2).unwrap(),
1514 Rect {
1515 x: 4,
1516 y: 1,
1517 width: 6,
1518 height: 2
1519 }
1520 );
1521
1522 assert_eq!(
1524 e.computed_absolute(0).unwrap(),
1525 Rect {
1526 x: 0,
1527 y: 0,
1528 width: 40,
1529 height: 10
1530 }
1531 );
1532 assert_eq!(
1533 e.computed_absolute(1).unwrap(),
1534 Rect {
1535 x: 5,
1536 y: 4,
1537 width: 20,
1538 height: 6
1539 }
1540 );
1541 assert_eq!(
1542 e.computed_absolute(2).unwrap(),
1543 Rect {
1544 x: 9,
1545 y: 5,
1546 width: 6,
1547 height: 2
1548 }
1549 );
1550
1551 assert_ne!(e.computed_absolute(2), e.computed(2));
1553
1554 assert_eq!(e.computed_absolute(99), None);
1556 }
1557
1558 #[test]
1566 fn single_root_fills_viewport() {
1567 let mut e = TaffyEngine::new();
1568 e.create(0).unwrap();
1569
1570 let nid = e.id_map[&0];
1571 e.tree
1572 .set_style(
1573 nid,
1574 TaffyStyle {
1575 size: taffy::geometry::Size {
1576 width: pt(80.0),
1577 height: pt(24.0),
1578 },
1579 ..Default::default()
1580 },
1581 )
1582 .unwrap();
1583
1584 e.calculate(0, 80.0, Some(24.0)).unwrap();
1585
1586 let r = e.computed(0).unwrap();
1587 assert_eq!(
1588 r,
1589 Rect {
1590 x: 0,
1591 y: 0,
1592 width: 80,
1593 height: 24
1594 }
1595 );
1596 }
1597
1598 #[test]
1605 fn root_two_default_children() {
1606 let mut e = engine_with(&[0, 1, 2]);
1607 e.insert_child(0, 1, 0).unwrap();
1608 e.insert_child(0, 2, 1).unwrap();
1609
1610 let root_nid = e.id_map[&0];
1611 let c1_nid = e.id_map[&1];
1612 let c2_nid = e.id_map[&2];
1613 e.tree
1614 .set_style(
1615 root_nid,
1616 TaffyStyle {
1617 size: taffy::geometry::Size {
1618 width: pt(80.0),
1619 height: pt(24.0),
1620 },
1621 ..Default::default()
1622 },
1623 )
1624 .unwrap();
1625 e.tree
1626 .set_style(
1627 c1_nid,
1628 TaffyStyle {
1629 size: taffy::geometry::Size {
1630 width: pt(10.0),
1631 height: pt(24.0),
1632 },
1633 ..Default::default()
1634 },
1635 )
1636 .unwrap();
1637 e.tree
1638 .set_style(
1639 c2_nid,
1640 TaffyStyle {
1641 size: taffy::geometry::Size {
1642 width: pt(20.0),
1643 height: pt(24.0),
1644 },
1645 ..Default::default()
1646 },
1647 )
1648 .unwrap();
1649
1650 e.calculate(0, 80.0, Some(24.0)).unwrap();
1651
1652 assert_eq!(
1653 e.computed(0).unwrap(),
1654 Rect {
1655 x: 0,
1656 y: 0,
1657 width: 80,
1658 height: 24
1659 }
1660 );
1661
1662 let c1 = e.computed(1).unwrap();
1663 let c2 = e.computed(2).unwrap();
1664 assert_eq!(c1.x, 0);
1665 assert_eq!(c1.y, 0);
1666 assert_eq!(c2.y, 0); assert_eq!(c1.width, 10);
1668 assert_eq!(c2.width, 20);
1669 assert_eq!(c2.x, c1.x + i32::from(c1.width));
1670 }
1671
1672 #[test]
1675 fn nested_box() {
1676 let mut e = engine_with(&[0, 1, 2]);
1677 e.insert_child(0, 1, 0).unwrap();
1678 e.insert_child(1, 2, 0).unwrap();
1679
1680 let root_nid = e.id_map[&0];
1681 let outer_nid = e.id_map[&1];
1682 let inner_nid = e.id_map[&2];
1683
1684 e.tree
1685 .set_style(
1686 root_nid,
1687 TaffyStyle {
1688 size: taffy::geometry::Size {
1689 width: pt(80.0),
1690 height: pt(24.0),
1691 },
1692 ..Default::default()
1693 },
1694 )
1695 .unwrap();
1696 e.tree
1697 .set_style(
1698 outer_nid,
1699 TaffyStyle {
1700 size: taffy::geometry::Size {
1701 width: pt(40.0),
1702 height: pt(24.0),
1703 },
1704 ..Default::default()
1705 },
1706 )
1707 .unwrap();
1708 e.tree
1709 .set_style(
1710 inner_nid,
1711 TaffyStyle {
1712 size: taffy::geometry::Size {
1713 width: pt(20.0),
1714 height: pt(24.0),
1715 },
1716 ..Default::default()
1717 },
1718 )
1719 .unwrap();
1720
1721 e.calculate(0, 80.0, Some(24.0)).unwrap();
1722
1723 assert_eq!(
1724 e.computed(0).unwrap(),
1725 Rect {
1726 x: 0,
1727 y: 0,
1728 width: 80,
1729 height: 24
1730 }
1731 );
1732 assert_eq!(
1733 e.computed(1).unwrap(),
1734 Rect {
1735 x: 0,
1736 y: 0,
1737 width: 40,
1738 height: 24
1739 }
1740 );
1741 assert_eq!(
1742 e.computed(2).unwrap(),
1743 Rect {
1744 x: 0,
1745 y: 0,
1746 width: 20,
1747 height: 24
1748 }
1749 );
1750 }
1751
1752 #[test]
1755 fn recalculate_after_style_change() {
1756 let mut e = TaffyEngine::new();
1757 e.create(0).unwrap();
1758
1759 let initial = Style {
1760 width: Some(Dim::Points(80.0)),
1761 height: Some(Dim::Points(24.0)),
1762 ..Default::default()
1763 };
1764 e.apply_style(0, &initial).unwrap();
1765 e.calculate(0, 80.0, Some(24.0)).unwrap();
1766 assert_eq!(
1767 e.computed(0).unwrap(),
1768 Rect {
1769 x: 0,
1770 y: 0,
1771 width: 80,
1772 height: 24
1773 }
1774 );
1775
1776 let resized = Style {
1778 width: Some(Dim::Points(40.0)),
1779 height: Some(Dim::Points(12.0)),
1780 ..Default::default()
1781 };
1782 e.apply_style(0, &resized).unwrap();
1783 e.mark_dirty(0).unwrap();
1784 e.calculate(0, 40.0, Some(12.0)).unwrap();
1785 assert_eq!(
1786 e.computed(0).unwrap(),
1787 Rect {
1788 x: 0,
1789 y: 0,
1790 width: 40,
1791 height: 12
1792 }
1793 );
1794 }
1795
1796 #[test]
1798 fn computed_unknown_id_returns_none() {
1799 let e = TaffyEngine::new();
1800 assert_eq!(e.computed(999), None);
1801 }
1802
1803 #[test]
1805 fn create_duplicate_is_noop() {
1806 let mut e = TaffyEngine::new();
1807 e.create(0).unwrap();
1808 let nid_first = e.id_map[&0];
1809 e.create(0).unwrap();
1810 assert_eq!(e.id_map[&0], nid_first);
1811 }
1812
1813 #[test]
1815 fn measure_fn_is_invoked() {
1816 let mut e = TaffyEngine::new();
1817 e.create(0).unwrap();
1818 e.create(1).unwrap();
1819
1820 let root_nid = e.id_map[&0];
1821 e.tree
1822 .set_style(
1823 root_nid,
1824 TaffyStyle {
1825 size: taffy::geometry::Size {
1826 width: pt(80.0),
1827 height: pt(24.0),
1828 },
1829 align_items: Some(AlignItems::FlexStart),
1830 ..Default::default()
1831 },
1832 )
1833 .unwrap();
1834
1835 e.insert_child(0, 1, 0).unwrap();
1836
1837 e.set_measure(
1838 1,
1839 Box::new(|_known, _avail| Size {
1840 width: 10.0,
1841 height: 3.0,
1842 }),
1843 );
1844
1845 e.calculate(0, 80.0, Some(24.0)).unwrap();
1846
1847 let leaf = e.computed(1).unwrap();
1848 assert_eq!(leaf.width, 10);
1849 assert_eq!(leaf.height, 3);
1850 }
1851
1852 #[test]
1854 fn insert_child_index_clamp_appends() {
1855 let mut e = engine_with(&[0, 1, 2]);
1856 e.insert_child(0, 1, 0).unwrap();
1857 e.insert_child(0, 2, 9999).unwrap();
1858
1859 let root_nid = e.id_map[&0];
1860 let root_nid_children = e.tree.children(root_nid).unwrap();
1861 assert_eq!(root_nid_children, vec![e.id_map[&1], e.id_map[&2]]);
1862 }
1863
1864 #[test]
1869 fn destroy_removes_node() {
1870 let mut e = TaffyEngine::new();
1871 e.create(0).unwrap();
1872 e.create(1).unwrap();
1873 e.set_measure(
1874 1,
1875 Box::new(|_, _| Size {
1876 width: 5.0,
1877 height: 1.0,
1878 }),
1879 );
1880 assert!(e.computed(1).is_some());
1882
1883 e.insert_child(0, 1, 0).unwrap();
1887 let root_s = crate::dom::Style {
1888 width: Some(crate::dom::Dim::Points(80.0)),
1889 height: Some(crate::dom::Dim::Points(24.0)),
1890 ..Default::default()
1891 };
1892 e.apply_style(0, &root_s).unwrap();
1893 e.calculate(0, 80.0, Some(24.0)).unwrap();
1894 assert!(e.computed(1).is_some());
1896
1897 e.destroy(1);
1898 assert!(e.computed(1).is_none());
1899 assert!(!e.id_map.contains_key(&1));
1901 assert!(!e.measures.contains_key(&1));
1902 }
1903
1904 #[test]
1907 fn destroy_unknown_id_is_noop() {
1908 let mut e = TaffyEngine::new();
1909 e.create(0).unwrap();
1910 e.destroy(999); assert!(e.computed(0).is_some());
1912 }
1913
1914 #[test]
1918 fn destroy_then_recreate_clean_state() {
1919 let mut e = TaffyEngine::new();
1920 e.create(0).unwrap();
1921 e.create(1).unwrap();
1922 e.insert_child(0, 1, 0).unwrap();
1923
1924 let root_s = crate::dom::Style {
1925 width: Some(crate::dom::Dim::Points(80.0)),
1926 height: Some(crate::dom::Dim::Points(24.0)),
1927 align_items: Some(crate::dom::Align::FlexStart),
1928 ..Default::default()
1929 };
1930 e.apply_style(0, &root_s).unwrap();
1931 e.set_measure(
1932 1,
1933 Box::new(|_, _| Size {
1934 width: 5.0,
1935 height: 1.0,
1936 }),
1937 );
1938 e.calculate(0, 80.0, Some(24.0)).unwrap();
1939 assert_eq!(e.computed(1).unwrap().width, 5);
1940
1941 e.destroy(1);
1942 e.destroy(0);
1943
1944 e.create(0).unwrap();
1946 e.create(1).unwrap();
1947 e.insert_child(0, 1, 0).unwrap();
1948 e.apply_style(0, &root_s).unwrap();
1949 e.set_measure(
1950 1,
1951 Box::new(|_, _| Size {
1952 width: 7.0,
1953 height: 1.0,
1954 }),
1955 );
1956 e.calculate(0, 80.0, Some(24.0)).unwrap();
1957 assert_eq!(e.computed(1).unwrap().width, 7);
1958 }
1959
1960 #[test]
1962 fn remove_child_detaches() {
1963 let mut e = engine_with(&[0, 1]);
1964 e.insert_child(0, 1, 0).unwrap();
1965 e.remove_child(0, 1).unwrap();
1966
1967 assert!(e.id_map.contains_key(&1));
1968 let root_nid = e.id_map[&0];
1969 assert_eq!(e.tree.child_count(root_nid), 0);
1970 }
1971
1972 #[test]
1974 fn flex_column_two_children() {
1975 let mut e = engine_with(&[0, 1, 2]);
1976 e.insert_child(0, 1, 0).unwrap();
1977 e.insert_child(0, 2, 1).unwrap();
1978
1979 let root_nid = e.id_map[&0];
1980 let c1_nid = e.id_map[&1];
1981 let c2_nid = e.id_map[&2];
1982
1983 e.tree
1984 .set_style(
1985 root_nid,
1986 TaffyStyle {
1987 size: taffy::geometry::Size {
1988 width: pt(80.0),
1989 height: pt(24.0),
1990 },
1991 flex_direction: TFD::Column,
1992 ..Default::default()
1993 },
1994 )
1995 .unwrap();
1996 e.tree
1997 .set_style(
1998 c1_nid,
1999 TaffyStyle {
2000 size: taffy::geometry::Size {
2001 width: pt(80.0),
2002 height: pt(12.0),
2003 },
2004 ..Default::default()
2005 },
2006 )
2007 .unwrap();
2008 e.tree
2009 .set_style(
2010 c2_nid,
2011 TaffyStyle {
2012 size: taffy::geometry::Size {
2013 width: pt(80.0),
2014 height: pt(12.0),
2015 },
2016 ..Default::default()
2017 },
2018 )
2019 .unwrap();
2020
2021 e.calculate(0, 80.0, Some(24.0)).unwrap();
2022
2023 let r1 = e.computed(1).unwrap();
2024 let r2 = e.computed(2).unwrap();
2025
2026 assert_eq!(
2027 r1,
2028 Rect {
2029 x: 0,
2030 y: 0,
2031 width: 80,
2032 height: 12
2033 }
2034 );
2035 assert_eq!(
2036 r2,
2037 Rect {
2038 x: 0,
2039 y: 12,
2040 width: 80,
2041 height: 12
2042 }
2043 );
2044 }
2045
2046 #[test]
2057 fn kernel_fractional_near_zero_floors_to_whole() {
2058 assert_eq!(round_value_to_pixel_grid(5.00005, false, false), 5.0);
2060 assert_eq!(round_value_to_pixel_grid(5.00005, true, false), 5.0);
2062 assert_eq!(round_value_to_pixel_grid(5.00005, false, true), 5.0);
2064 }
2065
2066 #[test]
2071 fn kernel_fractional_near_one_ceils_before_force_floor() {
2072 assert_eq!(round_value_to_pixel_grid(4.99995, false, false), 5.0);
2074 assert_eq!(round_value_to_pixel_grid(4.99995, false, true), 5.0);
2076 }
2077
2078 #[test]
2080 fn kernel_force_ceil_rounds_up() {
2081 assert_eq!(round_value_to_pixel_grid(4.3, true, false), 5.0);
2083 }
2084
2085 #[test]
2087 fn kernel_force_floor_rounds_down() {
2088 assert_eq!(round_value_to_pixel_grid(4.7, false, true), 4.0);
2090 }
2091
2092 #[test]
2094 fn kernel_round_half_up_tie() {
2095 assert_eq!(round_value_to_pixel_grid(4.5, false, false), 5.0);
2097 assert_eq!(round_value_to_pixel_grid(4.49, false, false), 4.0);
2099 assert_eq!(round_value_to_pixel_grid(4.51, false, false), 5.0);
2101 }
2102
2103 #[test]
2106 fn kernel_half_epsilon_tie_ceils() {
2107 assert_eq!(round_value_to_pixel_grid(4.49995, false, false), 5.0);
2108 }
2109
2110 #[test]
2113 fn kernel_negative_values() {
2114 assert_eq!(round_value_to_pixel_grid(-4.5, false, false), -4.0);
2116 assert_eq!(round_value_to_pixel_grid(-4.3, false, false), -4.0);
2118 assert_eq!(round_value_to_pixel_grid(-4.7, false, false), -5.0);
2120 }
2121
2122 #[test]
2127 fn inexact_equals_at_threshold_is_not_equal() {
2128 assert!(!inexact_equals(0.0001, 0.0));
2130 }
2131
2132 #[test]
2133 fn inexact_equals_just_under_threshold_is_equal() {
2134 assert!(inexact_equals(0.00009, 0.0));
2136 }
2137}