1use crate::{
2 AbsoluteLength, App, Bounds, DefiniteLength, Edges, GridTemplate, Length, Pixels, Point, Size,
3 Style, Window, size,
4 util::{
5 ceil_to_device_pixel, round_half_toward_zero, round_stroke_to_device_pixel,
6 round_to_device_pixel,
7 },
8};
9use collections::{FxHashMap, FxHashSet};
10use stacksafe::{StackSafe, stacksafe};
11use std::{fmt::Debug, ops::Range};
12use taffy::{
13 TaffyTree, TraversePartialTree as _,
14 geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
15 prelude::{max_content, min_content},
16 style::AvailableSpace as TaffyAvailableSpace,
17 tree::NodeId,
18};
19
20type NodeMeasureFn = StackSafe<
21 Box<
22 dyn FnMut(
23 Size<Option<Pixels>>,
24 Size<AvailableSpace>,
25 &mut Window,
26 &mut App,
27 ) -> Size<Pixels>,
28 >,
29>;
30
31struct NodeContext {
32 measure: NodeMeasureFn,
33}
34pub struct TaffyLayoutEngine {
35 taffy: TaffyTree<NodeContext>,
36 absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
37 absolute_outer_origins: FxHashMap<LayoutId, Point<f32>>,
39 computed_layouts: FxHashSet<LayoutId>,
40 layout_bounds_scratch_space: Vec<LayoutId>,
41}
42
43const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
44
45impl TaffyLayoutEngine {
46 pub fn new() -> Self {
47 let mut taffy = TaffyTree::new();
48 taffy.disable_rounding();
49 TaffyLayoutEngine {
50 taffy,
51 absolute_layout_bounds: FxHashMap::default(),
52 absolute_outer_origins: FxHashMap::default(),
53 computed_layouts: FxHashSet::default(),
54 layout_bounds_scratch_space: Vec::new(),
55 }
56 }
57
58 pub fn clear(&mut self) {
59 self.taffy.clear();
60 self.absolute_layout_bounds.clear();
61 self.absolute_outer_origins.clear();
62 self.computed_layouts.clear();
63 }
64
65 pub fn request_layout(
66 &mut self,
67 style: Style,
68 rem_size: Pixels,
69 scale_factor: f32,
70 children: &[LayoutId],
71 ) -> LayoutId {
72 let taffy_style = style.to_taffy(rem_size, scale_factor);
73
74 if children.is_empty() {
75 self.taffy
76 .new_leaf(taffy_style)
77 .expect(EXPECT_MESSAGE)
78 .into()
79 } else {
80 self.taffy
81 .new_with_children(taffy_style, LayoutId::to_taffy_slice(children))
83 .expect(EXPECT_MESSAGE)
84 .into()
85 }
86 }
87
88 pub fn request_measured_layout(
89 &mut self,
90 style: Style,
91 rem_size: Pixels,
92 scale_factor: f32,
93 measure: impl FnMut(
94 Size<Option<Pixels>>,
95 Size<AvailableSpace>,
96 &mut Window,
97 &mut App,
98 ) -> Size<Pixels>
99 + 'static,
100 ) -> LayoutId {
101 let taffy_style = style.to_taffy(rem_size, scale_factor);
102
103 self.taffy
104 .new_leaf_with_context(
105 taffy_style,
106 NodeContext {
107 measure: StackSafe::new(Box::new(measure)),
108 },
109 )
110 .expect(EXPECT_MESSAGE)
111 .into()
112 }
113
114 #[allow(dead_code)]
116 fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
117 let mut count = 0;
118
119 for child in self.taffy.children(parent.0)? {
120 count += 1;
122
123 count += self.count_all_children(LayoutId(child))?
125 }
126
127 Ok(count)
128 }
129
130 #[allow(dead_code)]
132 fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
133 println!(
134 "{parent:?} at depth {depth} has {} children",
135 self.taffy.child_count(parent.0)
136 );
137
138 let mut max_child_depth = 0;
139
140 for child in self.taffy.children(parent.0)? {
141 max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
142 }
143
144 Ok(depth + 1 + max_child_depth)
145 }
146
147 #[allow(dead_code)]
149 fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
150 let mut edges = Vec::new();
151
152 for child in self.taffy.children(parent.0)? {
153 edges.push((parent, LayoutId(child)));
154
155 edges.extend(self.get_edges(LayoutId(child))?);
156 }
157
158 Ok(edges)
159 }
160
161 #[stacksafe]
162 pub fn compute_layout(
163 &mut self,
164 id: LayoutId,
165 available_space: Size<AvailableSpace>,
166 window: &mut Window,
167 cx: &mut App,
168 ) {
169 if !self.computed_layouts.insert(id) {
181 let stack = &mut self.layout_bounds_scratch_space;
182 stack.push(id);
183 while let Some(id) = stack.pop() {
184 self.absolute_layout_bounds.remove(&id);
185 self.absolute_outer_origins.remove(&id);
186 stack.extend(
187 self.taffy
188 .children(id.into())
189 .expect(EXPECT_MESSAGE)
190 .into_iter()
191 .map(LayoutId::from),
192 );
193 }
194 }
195
196 let scale_factor = window.scale_factor();
197
198 let transform = |v: AvailableSpace| match v {
199 AvailableSpace::Definite(pixels) => {
200 AvailableSpace::Definite(Pixels(pixels.0 * scale_factor))
201 }
202 AvailableSpace::MinContent => AvailableSpace::MinContent,
203 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
204 };
205 let available_space = size(
206 transform(available_space.width),
207 transform(available_space.height),
208 );
209
210 self.taffy
211 .compute_layout_with_measure(
212 id.into(),
213 available_space.into(),
214 |known_dimensions, available_space, _id, node_context, _style| {
215 let Some(node_context) = node_context else {
216 return taffy::geometry::Size::default();
217 };
218
219 let known_dimensions = Size {
220 width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
221 height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
222 };
223
224 let available_space: Size<AvailableSpace> = available_space.into();
225 let untransform = |ev: AvailableSpace| match ev {
226 AvailableSpace::Definite(pixels) => {
227 AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
228 }
229 AvailableSpace::MinContent => AvailableSpace::MinContent,
230 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
231 };
232 let available_space = size(
233 untransform(available_space.width),
234 untransform(available_space.height),
235 );
236
237 let measured_size: Size<Pixels> =
238 (node_context.measure)(known_dimensions, available_space, window, cx);
239 snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
240 },
241 )
242 .expect(EXPECT_MESSAGE);
243 }
244
245 pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
321 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
322 return layout;
323 }
324
325 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
326 let layout_location = layout.location;
327 let layout_size = layout.size;
328 let parent = self.taffy.parent(id.0);
329
330 let absolute_outer_origin = match parent {
331 Some(parent_id) => {
332 let parent_id = LayoutId::from(parent_id);
333 self.layout_bounds(parent_id, scale_factor);
334 let parent_origin = *self
335 .absolute_outer_origins
336 .get(&parent_id)
337 .expect("parent absolute outer origin should be cached");
338 parent_origin + Point::from(layout_location)
339 }
340 None => Point::from(layout_location),
341 };
342 self.absolute_outer_origins
343 .insert(id, absolute_outer_origin);
344
345 let absolute_far = absolute_outer_origin + Point::from(Size::from(layout_size));
346 let snapped_bounds = Bounds::from_corners(
347 absolute_outer_origin.map(round_half_toward_zero),
348 absolute_far.map(round_half_toward_zero),
349 );
350
351 let bounds = (snapped_bounds / scale_factor).map(Pixels);
352 self.absolute_layout_bounds.insert(id, bounds);
353 bounds
354 }
355}
356
357#[derive(Copy, Clone, Eq, PartialEq, Debug)]
359#[repr(transparent)]
360pub struct LayoutId(NodeId);
361
362impl LayoutId {
363 fn to_taffy_slice(node_ids: &[Self]) -> &[taffy::NodeId] {
364 unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(node_ids) }
366 }
367}
368
369impl std::hash::Hash for LayoutId {
370 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
371 u64::from(self.0).hash(state);
372 }
373}
374
375impl From<NodeId> for LayoutId {
376 fn from(node_id: NodeId) -> Self {
377 Self(node_id)
378 }
379}
380
381impl From<LayoutId> for NodeId {
382 fn from(layout_id: LayoutId) -> NodeId {
383 layout_id.0
384 }
385}
386
387fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
388 size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
389}
390
391fn border_widths_to_taffy(
392 widths: &Edges<AbsoluteLength>,
393 rem_size: Pixels,
394 scale_factor: f32,
395) -> TaffyRect<taffy::style::LengthPercentage> {
396 let snap = |w: &AbsoluteLength| {
397 taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
398 w.to_pixels(rem_size).0,
399 scale_factor,
400 ))
401 };
402 TaffyRect {
403 top: snap(&widths.top),
404 right: snap(&widths.right),
405 bottom: snap(&widths.bottom),
406 left: snap(&widths.left),
407 }
408}
409
410trait ToTaffy<Output> {
411 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
412}
413
414impl ToTaffy<taffy::style::Style> for Style {
415 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
416 use taffy::style_helpers::{fr, length, minmax, repeat};
417
418 fn to_grid_line(
419 placement: &Range<crate::GridPlacement>,
420 ) -> taffy::Line<taffy::GridPlacement> {
421 taffy::Line {
422 start: placement.start.into(),
423 end: placement.end.into(),
424 }
425 }
426
427 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
428 unit: &Option<GridTemplate>,
429 ) -> Vec<taffy::GridTemplateComponent<T>> {
430 unit.map(|template| {
431 match template.min_size {
432 crate::TemplateColumnMinSize::Zero => {
434 vec![repeat(
435 template.repeat,
436 vec![minmax(length(0.0_f32), fr(1.0_f32))],
437 )]
438 }
439 crate::TemplateColumnMinSize::MinContent => {
441 vec![repeat(
442 template.repeat,
443 vec![minmax(min_content(), fr(1.0_f32))],
444 )]
445 }
446 crate::TemplateColumnMinSize::MaxContent => {
448 vec![repeat(
449 template.repeat,
450 vec![minmax(length(0.0_f32), max_content())],
451 )]
452 }
453 }
454 })
455 .unwrap_or_default()
456 }
457
458 taffy::style::Style {
459 display: self.display.into(),
460 overflow: self.overflow.into(),
461 scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
462 position: self.position.into(),
463 inset: self.inset.to_taffy(rem_size, scale_factor),
464 size: self.size.to_taffy(rem_size, scale_factor),
465 min_size: self.min_size.to_taffy(rem_size, scale_factor),
466 max_size: self.max_size.to_taffy(rem_size, scale_factor),
467 aspect_ratio: self.aspect_ratio,
468 margin: self.margin.to_taffy(rem_size, scale_factor),
469 padding: self.padding.to_taffy(rem_size, scale_factor),
470 border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
471 align_items: self.align_items.map(|x| x.into()),
472 align_self: self.align_self.map(|x| x.into()),
473 align_content: self.align_content.map(|x| x.into()),
474 justify_content: self.justify_content.map(|x| x.into()),
475 gap: self.gap.to_taffy(rem_size, scale_factor),
476 flex_direction: self.flex_direction.into(),
477 flex_wrap: self.flex_wrap.into(),
478 flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
479 flex_grow: self.flex_grow,
480 flex_shrink: self.flex_shrink,
481 grid_template_rows: to_grid_repeat(&self.grid_rows),
482 grid_template_columns: to_grid_repeat(&self.grid_cols),
483 grid_row: self
484 .grid_location
485 .as_ref()
486 .map(|location| to_grid_line(&location.row))
487 .unwrap_or_default(),
488 grid_column: self
489 .grid_location
490 .as_ref()
491 .map(|location| to_grid_line(&location.column))
492 .unwrap_or_default(),
493 ..Default::default()
494 }
495 }
496}
497
498impl ToTaffy<f32> for AbsoluteLength {
499 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
500 round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
501 }
502}
503
504impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
505 fn to_taffy(
506 &self,
507 rem_size: Pixels,
508 scale_factor: f32,
509 ) -> taffy::prelude::LengthPercentageAuto {
510 match self {
511 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
512 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
513 }
514 }
515}
516
517impl ToTaffy<taffy::style::Dimension> for Length {
518 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
519 match self {
520 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
521 Length::Auto => taffy::prelude::Dimension::auto(),
522 }
523 }
524}
525
526impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
527 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
528 match self {
529 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
530 DefiniteLength::Fraction(fraction) => {
531 taffy::style::LengthPercentage::percent(*fraction)
532 }
533 }
534 }
535}
536
537impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
538 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
539 match self {
540 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
541 DefiniteLength::Fraction(fraction) => {
542 taffy::style::LengthPercentageAuto::percent(*fraction)
543 }
544 }
545 }
546}
547
548impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
549 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
550 match self {
551 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
552 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
553 }
554 }
555}
556
557impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
558 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
559 taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
560 }
561}
562
563impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
564 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
565 taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
566 }
567}
568
569impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
570 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
571 taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
572 }
573}
574
575impl<T, T2> From<TaffyPoint<T>> for Point<T2>
576where
577 T: Into<T2>,
578 T2: Clone + Debug + Default + PartialEq,
579{
580 fn from(point: TaffyPoint<T>) -> Point<T2> {
581 Point {
582 x: point.x.into(),
583 y: point.y.into(),
584 }
585 }
586}
587
588impl<T, T2> From<Point<T>> for TaffyPoint<T2>
589where
590 T: Into<T2> + Clone + Debug + Default + PartialEq,
591{
592 fn from(val: Point<T>) -> Self {
593 TaffyPoint {
594 x: val.x.into(),
595 y: val.y.into(),
596 }
597 }
598}
599
600impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
601where
602 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
603{
604 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
605 TaffySize {
606 width: self.width.to_taffy(rem_size, scale_factor),
607 height: self.height.to_taffy(rem_size, scale_factor),
608 }
609 }
610}
611
612impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
613where
614 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
615{
616 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
617 TaffyRect {
618 top: self.top.to_taffy(rem_size, scale_factor),
619 right: self.right.to_taffy(rem_size, scale_factor),
620 bottom: self.bottom.to_taffy(rem_size, scale_factor),
621 left: self.left.to_taffy(rem_size, scale_factor),
622 }
623 }
624}
625
626impl<T, U> From<TaffySize<T>> for Size<U>
627where
628 T: Into<U>,
629 U: Clone + Debug + Default + PartialEq,
630{
631 fn from(taffy_size: TaffySize<T>) -> Self {
632 Size {
633 width: taffy_size.width.into(),
634 height: taffy_size.height.into(),
635 }
636 }
637}
638
639impl<T, U> From<Size<T>> for TaffySize<U>
640where
641 T: Into<U> + Clone + Debug + Default + PartialEq,
642{
643 fn from(size: Size<T>) -> Self {
644 TaffySize {
645 width: size.width.into(),
646 height: size.height.into(),
647 }
648 }
649}
650
651#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
653pub enum AvailableSpace {
654 Definite(Pixels),
656 #[default]
658 MinContent,
659 MaxContent,
661}
662
663impl AvailableSpace {
664 pub const fn min_size() -> Size<Self> {
678 Size {
679 width: Self::MinContent,
680 height: Self::MinContent,
681 }
682 }
683}
684
685impl From<AvailableSpace> for TaffyAvailableSpace {
686 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
687 match space {
688 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
689 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
690 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
691 }
692 }
693}
694
695impl From<TaffyAvailableSpace> for AvailableSpace {
696 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
697 match space {
698 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
699 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
700 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
701 }
702 }
703}
704
705impl From<Pixels> for AvailableSpace {
706 fn from(pixels: Pixels) -> Self {
707 AvailableSpace::Definite(pixels)
708 }
709}
710
711impl From<Size<Pixels>> for Size<AvailableSpace> {
712 fn from(size: Size<Pixels>) -> Self {
713 Size {
714 width: AvailableSpace::Definite(size.width),
715 height: AvailableSpace::Definite(size.height),
716 }
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn border_widths_to_taffy_use_stroke_snapping() {
726 let border_widths = Edges {
727 top: Pixels(0.0).into(),
728 right: Pixels(0.4).into(),
729 bottom: Pixels(0.5).into(),
730 left: Pixels(1.6).into(),
731 };
732 let taffy_border = border_widths_to_taffy(&border_widths, Pixels(16.0), 1.0);
733
734 assert_eq!(
735 taffy_border.top,
736 taffy::style::LengthPercentage::length(0.0)
737 );
738 assert_eq!(
739 taffy_border.right,
740 taffy::style::LengthPercentage::length(1.0)
741 );
742 assert_eq!(
743 taffy_border.bottom,
744 taffy::style::LengthPercentage::length(1.0)
745 );
746 assert_eq!(
747 taffy_border.left,
748 taffy::style::LengthPercentage::length(2.0)
749 );
750 }
751}