1use crate::{
2 AbsoluteLength, App, Bounds, DefiniteLength, Edges, GridTemplate, Length, Pixels, Point, Size,
3 Style, Window, point, 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 smallvec::SmallVec;
11use stacksafe::{StackSafe, stacksafe};
12use std::{fmt::Debug, ops::Range};
13use taffy::{
14 TaffyTree, TraversePartialTree as _,
15 geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
16 prelude::{max_content, min_content},
17 style::AvailableSpace as TaffyAvailableSpace,
18 tree::{LayoutInput, NodeId},
19};
20
21type NodeMeasureFn = StackSafe<
22 Box<
23 dyn FnMut(
24 Size<Option<Pixels>>,
25 Size<AvailableSpace>,
26 &mut Window,
27 &mut App,
28 ) -> Size<Pixels>,
29 >,
30>;
31
32struct NodeContext {
33 measure: NodeMeasureFn,
34}
35pub struct TaffyLayoutEngine {
36 taffy: TaffyTree<NodeContext>,
37 absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
38 absolute_outer_origins: FxHashMap<LayoutId, Point<f32>>,
39 computed_layouts: FxHashSet<LayoutId>,
40}
41
42const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
43
44impl TaffyLayoutEngine {
45 pub fn new() -> Self {
46 let mut taffy = TaffyTree::new();
47 taffy.disable_rounding();
48 TaffyLayoutEngine {
49 taffy,
50 absolute_layout_bounds: FxHashMap::default(),
51 absolute_outer_origins: FxHashMap::default(),
52 computed_layouts: FxHashSet::default(),
53 }
54 }
55
56 pub fn clear(&mut self) {
57 self.taffy.clear();
58 self.absolute_layout_bounds.clear();
59 self.absolute_outer_origins.clear();
60 self.computed_layouts.clear();
61 }
62
63 pub fn request_layout(
64 &mut self,
65 style: Style,
66 rem_size: Pixels,
67 scale_factor: f32,
68 children: &[LayoutId],
69 ) -> LayoutId {
70 let taffy_style = style.to_taffy(rem_size, scale_factor);
71
72 if children.is_empty() {
73 self.taffy
74 .new_leaf(taffy_style)
75 .expect(EXPECT_MESSAGE)
76 .into()
77 } else {
78 self.taffy
79 .new_with_children(taffy_style, unsafe {
81 std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(children)
82 })
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 pub fn stretch_auto_size_to_fill(
117 &mut self,
118 id: LayoutId,
119 size: Size<Pixels>,
120 scale_factor: f32,
121 ) {
122 let style = self.taffy.style(id.0).expect(EXPECT_MESSAGE);
123 let stretch_width = style.size.width.is_auto();
124 let stretch_height = style.size.height.is_auto();
125 if !stretch_width && !stretch_height {
126 return;
127 }
128
129 let mut style = style.clone();
130 if stretch_width {
131 style.size.width =
132 taffy::style::Dimension::length(round_to_device_pixel(size.width.0, scale_factor));
133 }
134 if stretch_height {
135 style.size.height =
136 taffy::style::Dimension::length(round_to_device_pixel(size.height.0, scale_factor));
137 }
138 self.taffy.set_style(id.0, style).expect(EXPECT_MESSAGE);
139 }
140
141 #[allow(dead_code)]
143 fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
144 let mut count = 0;
145
146 for child in self.taffy.children(parent.0)? {
147 count += 1;
149
150 count += self.count_all_children(LayoutId(child))?
152 }
153
154 Ok(count)
155 }
156
157 #[allow(dead_code)]
159 fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
160 println!(
161 "{parent:?} at depth {depth} has {} children",
162 self.taffy.child_count(parent.0)
163 );
164
165 let mut max_child_depth = 0;
166
167 for child in self.taffy.children(parent.0)? {
168 max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
169 }
170
171 Ok(depth + 1 + max_child_depth)
172 }
173
174 #[allow(dead_code)]
176 fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
177 let mut edges = Vec::new();
178
179 for child in self.taffy.children(parent.0)? {
180 edges.push((parent, LayoutId(child)));
181
182 edges.extend(self.get_edges(LayoutId(child))?);
183 }
184
185 Ok(edges)
186 }
187
188 #[stacksafe]
189 pub fn compute_layout(
190 &mut self,
191 id: LayoutId,
192 available_space: Size<AvailableSpace>,
193 window: &mut Window,
194 cx: &mut App,
195 ) {
196 if !self.computed_layouts.insert(id) {
208 let mut stack = SmallVec::<[LayoutId; 64]>::new();
209 stack.push(id);
210 while let Some(id) = stack.pop() {
211 self.absolute_layout_bounds.remove(&id);
212 self.absolute_outer_origins.remove(&id);
213 stack.extend(
214 self.taffy
215 .children(id.into())
216 .expect(EXPECT_MESSAGE)
217 .into_iter()
218 .map(Into::into),
219 );
220 }
221 }
222
223 let scale_factor = window.scale_factor();
224
225 let transform = |v: AvailableSpace| match v {
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 transform(available_space.width),
234 transform(available_space.height),
235 );
236
237 self.taffy
238 .compute_layout_with_measure(
239 id.into(),
240 available_space.into(),
241 |inputs: LayoutInput, _id, node_context, style| {
242 taffy::compute_leaf_layout(
248 inputs,
249 style,
250 |_, _| 0.0,
251 |known_dimensions, available_space| {
252 let Some(node_context) = node_context else {
253 return TaffySize::ZERO;
254 };
255
256 let known_dimensions = Size {
257 width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
258 height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
259 };
260
261 let available_space: Size<AvailableSpace> = available_space.into();
262 let untransform = |ev: AvailableSpace| match ev {
263 AvailableSpace::Definite(pixels) => {
264 AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
265 }
266 AvailableSpace::MinContent => AvailableSpace::MinContent,
267 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
268 };
269 let available_space = size(
270 untransform(available_space.width),
271 untransform(available_space.height),
272 );
273
274 let measured_size: Size<Pixels> = (node_context.measure)(
275 known_dimensions,
276 available_space,
277 window,
278 cx,
279 );
280 snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
281 },
282 )
283 },
284 )
285 .expect(EXPECT_MESSAGE);
286 }
287
288 pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
293 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
294 return layout;
295 }
296
297 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
298 let layout_location = layout.location;
299 let layout_size = layout.size;
300 let parent = self.taffy.parent(id.0);
301
302 let absolute_outer_origin = match parent {
303 Some(parent_id) => {
304 let parent_id = LayoutId::from(parent_id);
305 self.layout_bounds(parent_id, scale_factor);
306 let parent_origin = *self
307 .absolute_outer_origins
308 .get(&parent_id)
309 .expect("parent absolute outer origin should be cached");
310 parent_origin + Point::from(layout_location)
311 }
312 None => Point::from(layout_location),
313 };
314 self.absolute_outer_origins
315 .insert(id, absolute_outer_origin);
316
317 let layout_size = Size::from(layout_size);
318 let absolute_far = absolute_outer_origin + point(layout_size.width, layout_size.height);
319 let snapped_bounds = Bounds::from_corners(
320 absolute_outer_origin.map(round_half_toward_zero),
321 absolute_far.map(round_half_toward_zero),
322 );
323
324 let bounds = (snapped_bounds / scale_factor).map(Pixels);
325 self.absolute_layout_bounds.insert(id, bounds);
326
327 bounds
328 }
329}
330
331#[derive(Copy, Clone, Eq, PartialEq, Debug)]
333#[repr(transparent)]
334pub struct LayoutId(NodeId);
335
336impl std::hash::Hash for LayoutId {
337 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
338 u64::from(self.0).hash(state);
339 }
340}
341
342impl From<NodeId> for LayoutId {
343 fn from(node_id: NodeId) -> Self {
344 Self(node_id)
345 }
346}
347
348impl From<LayoutId> for NodeId {
349 fn from(layout_id: LayoutId) -> NodeId {
350 layout_id.0
351 }
352}
353
354trait ToTaffy<Output> {
355 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
356}
357
358fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
359 size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
360}
361
362fn border_widths_to_taffy(
363 widths: &Edges<AbsoluteLength>,
364 rem_size: Pixels,
365 scale_factor: f32,
366) -> TaffyRect<taffy::style::LengthPercentage> {
367 let snap = |w: &AbsoluteLength| {
368 taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
369 w.to_pixels(rem_size).0,
370 scale_factor,
371 ))
372 };
373 TaffyRect {
374 top: snap(&widths.top),
375 right: snap(&widths.right),
376 bottom: snap(&widths.bottom),
377 left: snap(&widths.left),
378 }
379}
380
381impl ToTaffy<taffy::style::Style> for Style {
382 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
383 use taffy::style_helpers::{fr, length, minmax, repeat};
384
385 fn to_grid_line(
386 placement: &Range<crate::GridPlacement>,
387 ) -> taffy::Line<taffy::GridPlacement> {
388 taffy::Line {
389 start: placement.start.into(),
390 end: placement.end.into(),
391 }
392 }
393
394 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
395 unit: &Option<GridTemplate>,
396 ) -> Vec<taffy::GridTemplateComponent<T>> {
397 unit.map(|template| match template.min_size {
398 crate::TemplateColumnMinSize::Zero => {
399 vec![repeat(
400 template.repeat,
401 vec![minmax(length(0.0_f32), fr(1.0_f32))],
402 )]
403 }
404 crate::TemplateColumnMinSize::MinContent => {
405 vec![repeat(
406 template.repeat,
407 vec![minmax(min_content(), fr(1.0_f32))],
408 )]
409 }
410 crate::TemplateColumnMinSize::MaxContent => {
411 vec![repeat(
412 template.repeat,
413 vec![minmax(length(0.0_f32), max_content())],
414 )]
415 }
416 })
417 .unwrap_or_default()
418 }
419
420 taffy::style::Style {
421 display: self.display.into(),
422 overflow: self.overflow.into(),
423 scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
424 position: self.position.into(),
425 inset: self.inset.to_taffy(rem_size, scale_factor),
426 size: self.size.to_taffy(rem_size, scale_factor),
427 min_size: self.min_size.to_taffy(rem_size, scale_factor),
428 max_size: self.max_size.to_taffy(rem_size, scale_factor),
429 aspect_ratio: self.aspect_ratio,
430 margin: self.margin.to_taffy(rem_size, scale_factor),
431 padding: self.padding.to_taffy(rem_size, scale_factor),
432 border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
433 align_items: self.align_items.map(|x| x.into()),
434 align_self: self.align_self.map(|x| x.into()),
435 align_content: self.align_content.map(|x| x.into()),
436 justify_content: self.justify_content.map(|x| x.into()),
437 gap: self.gap.to_taffy(rem_size, scale_factor),
438 flex_direction: self.flex_direction.into(),
439 flex_wrap: self.flex_wrap.into(),
440 flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
441 flex_grow: self.flex_grow,
442 flex_shrink: self.flex_shrink,
443 grid_template_rows: to_grid_repeat(&self.grid_rows),
444 grid_template_columns: to_grid_repeat(&self.grid_cols),
445 grid_row: self
446 .grid_location
447 .as_ref()
448 .map(|location| to_grid_line(&location.row))
449 .unwrap_or_default(),
450 grid_column: self
451 .grid_location
452 .as_ref()
453 .map(|location| to_grid_line(&location.column))
454 .unwrap_or_default(),
455 ..Default::default()
456 }
457 }
458}
459
460impl ToTaffy<f32> for AbsoluteLength {
461 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
462 round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
463 }
464}
465
466impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
467 fn to_taffy(
468 &self,
469 rem_size: Pixels,
470 scale_factor: f32,
471 ) -> taffy::prelude::LengthPercentageAuto {
472 match self {
473 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
474 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
475 }
476 }
477}
478
479impl ToTaffy<taffy::style::Dimension> for Length {
480 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
481 match self {
482 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
483 Length::Auto => taffy::prelude::Dimension::auto(),
484 }
485 }
486}
487
488impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
489 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
490 match self {
491 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
492 DefiniteLength::Fraction(fraction) => {
493 taffy::style::LengthPercentage::percent(*fraction)
494 }
495 }
496 }
497}
498
499impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
500 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
501 match self {
502 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
503 DefiniteLength::Fraction(fraction) => {
504 taffy::style::LengthPercentageAuto::percent(*fraction)
505 }
506 }
507 }
508}
509
510impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
511 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
512 match self {
513 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
514 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
515 }
516 }
517}
518
519impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
520 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
521 taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
522 }
523}
524
525impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
526 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
527 taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
528 }
529}
530
531impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
532 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
533 taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
534 }
535}
536
537impl<T, T2> From<TaffyPoint<T>> for Point<T2>
538where
539 T: Into<T2>,
540 T2: Clone + Debug + Default + PartialEq,
541{
542 fn from(point: TaffyPoint<T>) -> Point<T2> {
543 Point {
544 x: point.x.into(),
545 y: point.y.into(),
546 }
547 }
548}
549
550impl<T, T2> From<Point<T>> for TaffyPoint<T2>
551where
552 T: Into<T2> + Clone + Debug + Default + PartialEq,
553{
554 fn from(val: Point<T>) -> Self {
555 TaffyPoint {
556 x: val.x.into(),
557 y: val.y.into(),
558 }
559 }
560}
561
562impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
563where
564 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
565{
566 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
567 TaffySize {
568 width: self.width.to_taffy(rem_size, scale_factor),
569 height: self.height.to_taffy(rem_size, scale_factor),
570 }
571 }
572}
573
574impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
575where
576 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
577{
578 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
579 TaffyRect {
580 top: self.top.to_taffy(rem_size, scale_factor),
581 right: self.right.to_taffy(rem_size, scale_factor),
582 bottom: self.bottom.to_taffy(rem_size, scale_factor),
583 left: self.left.to_taffy(rem_size, scale_factor),
584 }
585 }
586}
587
588impl<T, U> From<TaffySize<T>> for Size<U>
589where
590 T: Into<U>,
591 U: Clone + Debug + Default + PartialEq,
592{
593 fn from(taffy_size: TaffySize<T>) -> Self {
594 Size {
595 width: taffy_size.width.into(),
596 height: taffy_size.height.into(),
597 }
598 }
599}
600
601impl<T, U> From<Size<T>> for TaffySize<U>
602where
603 T: Into<U> + Clone + Debug + Default + PartialEq,
604{
605 fn from(size: Size<T>) -> Self {
606 TaffySize {
607 width: size.width.into(),
608 height: size.height.into(),
609 }
610 }
611}
612
613#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
615pub enum AvailableSpace {
616 Definite(Pixels),
618 #[default]
620 MinContent,
621 MaxContent,
623}
624
625pub(crate) fn available_space_for_optional_width(width: Option<Pixels>) -> AvailableSpace {
626 width.map_or(AvailableSpace::MaxContent, AvailableSpace::Definite)
627}
628
629impl AvailableSpace {
630 pub const fn min_size() -> Size<Self> {
644 Size {
645 width: Self::MinContent,
646 height: Self::MinContent,
647 }
648 }
649}
650
651impl From<AvailableSpace> for TaffyAvailableSpace {
652 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
653 match space {
654 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
655 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
656 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
657 }
658 }
659}
660
661impl From<TaffyAvailableSpace> for AvailableSpace {
662 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
663 match space {
664 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
665 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
666 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
667 }
668 }
669}
670
671impl From<Pixels> for AvailableSpace {
672 fn from(pixels: Pixels) -> Self {
673 AvailableSpace::Definite(pixels)
674 }
675}
676
677impl From<Size<Pixels>> for Size<AvailableSpace> {
678 fn from(size: Size<Pixels>) -> Self {
679 Size {
680 width: AvailableSpace::Definite(size.width),
681 height: AvailableSpace::Definite(size.height),
682 }
683 }
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689 use crate::px;
690
691 #[test]
692 fn optional_width_uses_max_content_when_unbounded() {
693 assert_eq!(
694 available_space_for_optional_width(None),
695 AvailableSpace::MaxContent
696 );
697 assert_eq!(
698 available_space_for_optional_width(Some(px(320.0))),
699 AvailableSpace::Definite(px(320.0))
700 );
701 }
702}