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 std::{fmt::Debug, ops::Range};
11use taffy::{
12 TaffyTree, TraversePartialTree as _,
13 geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
14 prelude::{max_content, min_content},
15 style::AvailableSpace as TaffyAvailableSpace,
16 tree::NodeId,
17};
18
19#[cfg(feature = "stacker")]
20type StackSafe<T> = stacksafe::StackSafe<T>;
21#[cfg(not(feature = "stacker"))]
22type StackSafe<T> = T;
23
24type MeasureFn =
25 dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>;
26type NodeMeasureFn = StackSafe<Box<MeasureFn>>;
27
28struct NodeContext {
29 measure: NodeMeasureFn,
30}
31pub struct TaffyLayoutEngine {
32 taffy: TaffyTree<NodeContext>,
33 absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
34 absolute_outer_origins: FxHashMap<LayoutId, Point<f32>>,
36 computed_layouts: FxHashSet<LayoutId>,
37 layout_bounds_scratch_space: Vec<LayoutId>,
38}
39
40const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
41
42impl TaffyLayoutEngine {
43 pub fn new() -> Self {
44 let mut taffy = TaffyTree::new();
45 taffy.disable_rounding();
46 TaffyLayoutEngine {
47 taffy,
48 absolute_layout_bounds: FxHashMap::default(),
49 absolute_outer_origins: FxHashMap::default(),
50 computed_layouts: FxHashSet::default(),
51 layout_bounds_scratch_space: Vec::new(),
52 }
53 }
54
55 pub fn clear(&mut self) {
56 self.taffy.clear();
57 self.absolute_layout_bounds.clear();
58 self.absolute_outer_origins.clear();
59 self.computed_layouts.clear();
60 }
61
62 pub fn request_layout(
63 &mut self,
64 style: Style,
65 rem_size: Pixels,
66 scale_factor: f32,
67 children: &[LayoutId],
68 ) -> LayoutId {
69 let taffy_style = style.to_taffy(rem_size, scale_factor);
70
71 if children.is_empty() {
72 self.taffy
73 .new_leaf(taffy_style)
74 .expect(EXPECT_MESSAGE)
75 .into()
76 } else {
77 self.taffy
78 .new_with_children(taffy_style, LayoutId::to_taffy_slice(children))
80 .expect(EXPECT_MESSAGE)
81 .into()
82 }
83 }
84
85 pub fn request_measured_layout(
86 &mut self,
87 style: Style,
88 rem_size: Pixels,
89 scale_factor: f32,
90 measure: impl FnMut(
91 Size<Option<Pixels>>,
92 Size<AvailableSpace>,
93 &mut Window,
94 &mut App,
95 ) -> Size<Pixels>
96 + 'static,
97 ) -> LayoutId {
98 let taffy_style = style.to_taffy(rem_size, scale_factor);
99 let measure = Box::new(measure) as Box<MeasureFn>;
100 #[cfg(feature = "stacker")]
101 let measure = StackSafe::new(measure);
102
103 self.taffy
104 .new_leaf_with_context(taffy_style, NodeContext { measure })
105 .expect(EXPECT_MESSAGE)
106 .into()
107 }
108
109 pub fn stretch_auto_size_to_fill(
116 &mut self,
117 id: LayoutId,
118 size: Size<Pixels>,
119 scale_factor: f32,
120 ) {
121 let style = self.taffy.style(id.0).expect(EXPECT_MESSAGE);
122 let stretch_width = style.size.width.is_auto();
123 let stretch_height = style.size.height.is_auto();
124 if !stretch_width && !stretch_height {
125 return;
126 }
127 let mut style = style.clone();
128 if stretch_width {
129 style.size.width =
130 taffy::style::Dimension::length(round_to_device_pixel(size.width.0, scale_factor));
131 }
132 if stretch_height {
133 style.size.height =
134 taffy::style::Dimension::length(round_to_device_pixel(size.height.0, scale_factor));
135 }
136 self.taffy.set_style(id.0, style).expect(EXPECT_MESSAGE);
137 }
138
139 #[allow(dead_code)]
141 fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
142 let mut count = 0;
143
144 for child in self.taffy.children(parent.0)? {
145 count += 1;
147
148 count += self.count_all_children(LayoutId(child))?
150 }
151
152 Ok(count)
153 }
154
155 #[allow(dead_code)]
157 fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
158 println!(
159 "{parent:?} at depth {depth} has {} children",
160 self.taffy.child_count(parent.0)
161 );
162
163 let mut max_child_depth = 0;
164
165 for child in self.taffy.children(parent.0)? {
166 max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
167 }
168
169 Ok(depth + 1 + max_child_depth)
170 }
171
172 #[allow(dead_code)]
174 fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
175 let mut edges = Vec::new();
176
177 for child in self.taffy.children(parent.0)? {
178 edges.push((parent, LayoutId(child)));
179
180 edges.extend(self.get_edges(LayoutId(child))?);
181 }
182
183 Ok(edges)
184 }
185
186 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
187 pub fn compute_layout(
188 &mut self,
189 id: LayoutId,
190 available_space: Size<AvailableSpace>,
191 window: &mut Window,
192 cx: &mut App,
193 ) {
194 if !self.computed_layouts.insert(id) {
206 let stack = &mut self.layout_bounds_scratch_space;
207 stack.push(id);
208 while let Some(id) = stack.pop() {
209 self.absolute_layout_bounds.remove(&id);
210 self.absolute_outer_origins.remove(&id);
211 stack.extend(
212 self.taffy
213 .children(id.into())
214 .expect(EXPECT_MESSAGE)
215 .into_iter()
216 .map(LayoutId::from),
217 );
218 }
219 }
220
221 let scale_factor = window.scale_factor();
222
223 let transform = |v: AvailableSpace| match v {
224 AvailableSpace::Definite(pixels) => {
225 AvailableSpace::Definite(Pixels(pixels.0 * scale_factor))
226 }
227 AvailableSpace::MinContent => AvailableSpace::MinContent,
228 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
229 };
230 let available_space = size(
231 transform(available_space.width),
232 transform(available_space.height),
233 );
234
235 self.taffy
236 .compute_layout_with_measure(
237 id.into(),
238 available_space.into(),
239 |input, _id, node_context, style| {
240 taffy::compute_leaf_layout(
241 input,
242 style,
243 |_, _| 0.0,
244 |known_dimensions, available_space| {
245 let Some(node_context) = node_context else {
246 return taffy::geometry::Size::default();
247 };
248
249 let known_dimensions = Size {
250 width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
251 height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
252 };
253
254 let available_space: Size<AvailableSpace> = available_space.into();
255 let untransform = |ev: AvailableSpace| match ev {
256 AvailableSpace::Definite(pixels) => {
257 AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
258 }
259 AvailableSpace::MinContent => AvailableSpace::MinContent,
260 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
261 };
262 let available_space = size(
263 untransform(available_space.width),
264 untransform(available_space.height),
265 );
266
267 let measured_size: Size<Pixels> = (node_context.measure)(
268 known_dimensions,
269 available_space,
270 window,
271 cx,
272 );
273 snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
274 },
275 )
276 },
277 )
278 .expect(EXPECT_MESSAGE);
279 }
280
281 pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
357 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
358 return layout;
359 }
360
361 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
362 let layout_location = layout.location;
363 let layout_size = layout.size;
364 let parent = self.taffy.parent(id.0);
365
366 let absolute_outer_origin = match parent {
367 Some(parent_id) => {
368 let parent_id = LayoutId::from(parent_id);
369 self.layout_bounds(parent_id, scale_factor);
370 let parent_origin = *self
371 .absolute_outer_origins
372 .get(&parent_id)
373 .expect("parent absolute outer origin should be cached");
374 parent_origin + Point::from(layout_location)
375 }
376 None => Point::from(layout_location),
377 };
378 self.absolute_outer_origins
379 .insert(id, absolute_outer_origin);
380
381 let absolute_far = absolute_outer_origin + Point::from(Size::from(layout_size));
382 let snapped_bounds = Bounds::from_corners(
383 absolute_outer_origin.map(round_half_toward_zero),
384 absolute_far.map(round_half_toward_zero),
385 );
386
387 let bounds = (snapped_bounds / scale_factor).map(Pixels);
388 self.absolute_layout_bounds.insert(id, bounds);
389 bounds
390 }
391}
392
393#[derive(Copy, Clone, Eq, PartialEq, Debug)]
395#[repr(transparent)]
396pub struct LayoutId(NodeId);
397
398impl LayoutId {
399 fn to_taffy_slice(node_ids: &[Self]) -> &[taffy::NodeId] {
400 unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(node_ids) }
402 }
403}
404
405impl std::hash::Hash for LayoutId {
406 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
407 u64::from(self.0).hash(state);
408 }
409}
410
411impl From<NodeId> for LayoutId {
412 fn from(node_id: NodeId) -> Self {
413 Self(node_id)
414 }
415}
416
417impl From<LayoutId> for NodeId {
418 fn from(layout_id: LayoutId) -> NodeId {
419 layout_id.0
420 }
421}
422
423fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
424 size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
425}
426
427fn border_widths_to_taffy(
428 widths: &Edges<AbsoluteLength>,
429 rem_size: Pixels,
430 scale_factor: f32,
431) -> TaffyRect<taffy::style::LengthPercentage> {
432 let snap = |w: &AbsoluteLength| {
433 taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
434 w.to_pixels(rem_size).0,
435 scale_factor,
436 ))
437 };
438 TaffyRect {
439 top: snap(&widths.top),
440 right: snap(&widths.right),
441 bottom: snap(&widths.bottom),
442 left: snap(&widths.left),
443 }
444}
445
446trait ToTaffy<Output> {
447 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
448}
449
450impl ToTaffy<taffy::style::Style> for Style {
451 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
452 use taffy::style_helpers::{fr, length, minmax, repeat};
453
454 fn to_grid_line(
455 placement: &Range<crate::GridPlacement>,
456 ) -> taffy::Line<taffy::GridPlacement> {
457 taffy::Line {
458 start: placement.start.into(),
459 end: placement.end.into(),
460 }
461 }
462
463 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
464 unit: &Option<GridTemplate>,
465 ) -> Vec<taffy::GridTemplateComponent<T>> {
466 unit.map(|template| {
467 match template.min_size {
468 crate::GridTemplateMinSize::Zero => {
470 vec![repeat(
471 template.repeat,
472 vec![minmax(length(0.0_f32), fr(1.0_f32))],
473 )]
474 }
475 crate::GridTemplateMinSize::MinContent => {
477 vec![repeat(
478 template.repeat,
479 vec![minmax(min_content(), fr(1.0_f32))],
480 )]
481 }
482 crate::GridTemplateMinSize::MaxContent => {
484 vec![repeat(
485 template.repeat,
486 vec![minmax(length(0.0_f32), max_content())],
487 )]
488 }
489 }
490 })
491 .unwrap_or_default()
492 }
493
494 taffy::style::Style {
495 display: self.display.into(),
496 overflow: self.overflow.into(),
497 scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
498 position: self.position.into(),
499 inset: self.inset.to_taffy(rem_size, scale_factor),
500 size: self.size.to_taffy(rem_size, scale_factor),
501 min_size: self.min_size.to_taffy(rem_size, scale_factor),
502 max_size: self.max_size.to_taffy(rem_size, scale_factor),
503 aspect_ratio: self.aspect_ratio,
504 margin: self.margin.to_taffy(rem_size, scale_factor),
505 padding: self.padding.to_taffy(rem_size, scale_factor),
506 border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
507 align_items: self.align_items.map(Into::into).unwrap_or_default(),
508 align_self: self.align_self.map(|x| x.into()),
509 align_content: self.align_content.map(Into::into).unwrap_or_default(),
510 justify_content: self.justify_content.map(Into::into).unwrap_or_default(),
511 gap: self.gap.to_taffy(rem_size, scale_factor),
512 flex_direction: self.flex_direction.into(),
513 flex_wrap: self.flex_wrap.into(),
514 flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
515 flex_grow: self.flex_grow,
516 flex_shrink: self.flex_shrink,
517 grid_template_rows: to_grid_repeat(&self.grid_rows),
518 grid_template_columns: to_grid_repeat(&self.grid_cols),
519 grid_row: self
520 .grid_location
521 .as_ref()
522 .map(|location| to_grid_line(&location.row))
523 .unwrap_or_default(),
524 grid_column: self
525 .grid_location
526 .as_ref()
527 .map(|location| to_grid_line(&location.column))
528 .unwrap_or_default(),
529 ..Default::default()
530 }
531 }
532}
533
534impl ToTaffy<f32> for AbsoluteLength {
535 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
536 round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
537 }
538}
539
540impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
541 fn to_taffy(
542 &self,
543 rem_size: Pixels,
544 scale_factor: f32,
545 ) -> taffy::prelude::LengthPercentageAuto {
546 match self {
547 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
548 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
549 }
550 }
551}
552
553impl ToTaffy<taffy::style::Dimension> for Length {
554 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
555 match self {
556 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
557 Length::Auto => taffy::prelude::Dimension::auto(),
558 }
559 }
560}
561
562impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
563 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
564 match self {
565 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
566 DefiniteLength::Fraction(fraction) => {
567 taffy::style::LengthPercentage::percent(*fraction)
568 }
569 }
570 }
571}
572
573impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
574 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
575 match self {
576 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
577 DefiniteLength::Fraction(fraction) => {
578 taffy::style::LengthPercentageAuto::percent(*fraction)
579 }
580 }
581 }
582}
583
584impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
585 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
586 match self {
587 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
588 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
589 }
590 }
591}
592
593impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
594 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
595 taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
596 }
597}
598
599impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
600 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
601 taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
602 }
603}
604
605impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
606 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
607 taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
608 }
609}
610
611impl<T, T2> From<TaffyPoint<T>> for Point<T2>
612where
613 T: Into<T2>,
614 T2: Clone + Debug + Default + PartialEq,
615{
616 fn from(point: TaffyPoint<T>) -> Point<T2> {
617 Point {
618 x: point.x.into(),
619 y: point.y.into(),
620 }
621 }
622}
623
624impl<T, T2> From<Point<T>> for TaffyPoint<T2>
625where
626 T: Into<T2> + Clone + Debug + Default + PartialEq,
627{
628 fn from(val: Point<T>) -> Self {
629 TaffyPoint {
630 x: val.x.into(),
631 y: val.y.into(),
632 }
633 }
634}
635
636impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
637where
638 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
639{
640 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
641 TaffySize {
642 width: self.width.to_taffy(rem_size, scale_factor),
643 height: self.height.to_taffy(rem_size, scale_factor),
644 }
645 }
646}
647
648impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
649where
650 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
651{
652 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
653 TaffyRect {
654 top: self.top.to_taffy(rem_size, scale_factor),
655 right: self.right.to_taffy(rem_size, scale_factor),
656 bottom: self.bottom.to_taffy(rem_size, scale_factor),
657 left: self.left.to_taffy(rem_size, scale_factor),
658 }
659 }
660}
661
662impl<T, U> From<TaffySize<T>> for Size<U>
663where
664 T: Into<U>,
665 U: Clone + Debug + Default + PartialEq,
666{
667 fn from(taffy_size: TaffySize<T>) -> Self {
668 Size {
669 width: taffy_size.width.into(),
670 height: taffy_size.height.into(),
671 }
672 }
673}
674
675impl<T, U> From<Size<T>> for TaffySize<U>
676where
677 T: Into<U> + Clone + Debug + Default + PartialEq,
678{
679 fn from(size: Size<T>) -> Self {
680 TaffySize {
681 width: size.width.into(),
682 height: size.height.into(),
683 }
684 }
685}
686
687#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
689pub enum AvailableSpace {
690 Definite(Pixels),
692 #[default]
694 MinContent,
695 MaxContent,
697}
698
699impl AvailableSpace {
700 pub const fn min_size() -> Size<Self> {
714 Size {
715 width: Self::MinContent,
716 height: Self::MinContent,
717 }
718 }
719}
720
721impl From<AvailableSpace> for TaffyAvailableSpace {
722 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
723 match space {
724 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
725 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
726 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
727 }
728 }
729}
730
731impl From<TaffyAvailableSpace> for AvailableSpace {
732 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
733 match space {
734 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
735 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
736 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
737 }
738 }
739}
740
741impl From<Pixels> for AvailableSpace {
742 fn from(pixels: Pixels) -> Self {
743 AvailableSpace::Definite(pixels)
744 }
745}
746
747impl From<Size<Pixels>> for Size<AvailableSpace> {
748 fn from(size: Size<Pixels>) -> Self {
749 Size {
750 width: AvailableSpace::Definite(size.width),
751 height: AvailableSpace::Definite(size.height),
752 }
753 }
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759
760 #[test]
761 fn border_widths_to_taffy_use_stroke_snapping() {
762 let border_widths = Edges {
763 top: Pixels(0.0).into(),
764 right: Pixels(0.4).into(),
765 bottom: Pixels(0.5).into(),
766 left: Pixels(1.6).into(),
767 };
768 let taffy_border = border_widths_to_taffy(&border_widths, Pixels(16.0), 1.0);
769
770 assert_eq!(
771 taffy_border.top,
772 taffy::style::LengthPercentage::length(0.0)
773 );
774 assert_eq!(
775 taffy_border.right,
776 taffy::style::LengthPercentage::length(1.0)
777 );
778 assert_eq!(
779 taffy_border.bottom,
780 taffy::style::LengthPercentage::length(1.0)
781 );
782 assert_eq!(
783 taffy_border.left,
784 taffy::style::LengthPercentage::length(2.0)
785 );
786 }
787}