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(
243 inputs,
244 style,
245 |_, _| 0.0,
246 |known_dimensions, available_space| {
247 let Some(node_context) = node_context else {
248 return TaffySize::ZERO;
249 };
250
251 let known_dimensions = Size {
252 width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
253 height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
254 };
255
256 let available_space: Size<AvailableSpace> = available_space.into();
257 let untransform = |ev: AvailableSpace| match ev {
258 AvailableSpace::Definite(pixels) => {
259 AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
260 }
261 AvailableSpace::MinContent => AvailableSpace::MinContent,
262 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
263 };
264 let available_space = size(
265 untransform(available_space.width),
266 untransform(available_space.height),
267 );
268
269 let measured_size: Size<Pixels> = (node_context.measure)(
270 known_dimensions,
271 available_space,
272 window,
273 cx,
274 );
275 snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
276 },
277 )
278 },
279 )
280 .expect(EXPECT_MESSAGE);
281 }
282
283 pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
288 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
289 return layout;
290 }
291
292 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
293 let layout_location = layout.location;
294 let layout_size = layout.size;
295 let parent = self.taffy.parent(id.0);
296
297 let absolute_outer_origin = match parent {
298 Some(parent_id) => {
299 let parent_id = LayoutId::from(parent_id);
300 self.layout_bounds(parent_id, scale_factor);
301 let parent_origin = *self
302 .absolute_outer_origins
303 .get(&parent_id)
304 .expect("parent absolute outer origin should be cached");
305 parent_origin + Point::from(layout_location)
306 }
307 None => Point::from(layout_location),
308 };
309 self.absolute_outer_origins
310 .insert(id, absolute_outer_origin);
311
312 let layout_size = Size::from(layout_size);
313 let absolute_far = absolute_outer_origin + point(layout_size.width, layout_size.height);
314 let snapped_bounds = Bounds::from_corners(
315 absolute_outer_origin.map(round_half_toward_zero),
316 absolute_far.map(round_half_toward_zero),
317 );
318
319 let bounds = (snapped_bounds / scale_factor).map(Pixels);
320 self.absolute_layout_bounds.insert(id, bounds);
321
322 bounds
323 }
324}
325
326#[derive(Copy, Clone, Eq, PartialEq, Debug)]
328#[repr(transparent)]
329pub struct LayoutId(NodeId);
330
331impl std::hash::Hash for LayoutId {
332 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
333 u64::from(self.0).hash(state);
334 }
335}
336
337impl From<NodeId> for LayoutId {
338 fn from(node_id: NodeId) -> Self {
339 Self(node_id)
340 }
341}
342
343impl From<LayoutId> for NodeId {
344 fn from(layout_id: LayoutId) -> NodeId {
345 layout_id.0
346 }
347}
348
349trait ToTaffy<Output> {
350 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
351}
352
353fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
354 size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
355}
356
357fn border_widths_to_taffy(
358 widths: &Edges<AbsoluteLength>,
359 rem_size: Pixels,
360 scale_factor: f32,
361) -> TaffyRect<taffy::style::LengthPercentage> {
362 let snap = |w: &AbsoluteLength| {
363 taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
364 w.to_pixels(rem_size).0,
365 scale_factor,
366 ))
367 };
368 TaffyRect {
369 top: snap(&widths.top),
370 right: snap(&widths.right),
371 bottom: snap(&widths.bottom),
372 left: snap(&widths.left),
373 }
374}
375
376impl ToTaffy<taffy::style::Style> for Style {
377 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
378 use taffy::style_helpers::{fr, length, minmax, repeat};
379
380 fn to_grid_line(
381 placement: &Range<crate::GridPlacement>,
382 ) -> taffy::Line<taffy::GridPlacement> {
383 taffy::Line {
384 start: placement.start.into(),
385 end: placement.end.into(),
386 }
387 }
388
389 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
390 unit: &Option<GridTemplate>,
391 ) -> Vec<taffy::GridTemplateComponent<T>> {
392 unit.map(|template| match template.min_size {
393 crate::TemplateColumnMinSize::Zero => {
394 vec![repeat(
395 template.repeat,
396 vec![minmax(length(0.0_f32), fr(1.0_f32))],
397 )]
398 }
399 crate::TemplateColumnMinSize::MinContent => {
400 vec![repeat(
401 template.repeat,
402 vec![minmax(min_content(), fr(1.0_f32))],
403 )]
404 }
405 crate::TemplateColumnMinSize::MaxContent => {
406 vec![repeat(
407 template.repeat,
408 vec![minmax(length(0.0_f32), max_content())],
409 )]
410 }
411 })
412 .unwrap_or_default()
413 }
414
415 taffy::style::Style {
416 display: self.display.into(),
417 overflow: self.overflow.into(),
418 scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
419 position: self.position.into(),
420 inset: self.inset.to_taffy(rem_size, scale_factor),
421 size: self.size.to_taffy(rem_size, scale_factor),
422 min_size: self.min_size.to_taffy(rem_size, scale_factor),
423 max_size: self.max_size.to_taffy(rem_size, scale_factor),
424 aspect_ratio: self.aspect_ratio,
425 margin: self.margin.to_taffy(rem_size, scale_factor),
426 padding: self.padding.to_taffy(rem_size, scale_factor),
427 border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
428 align_items: self.align_items.map(Into::into).unwrap_or_default(),
429 align_self: self.align_self.map(|x| x.into()),
430 align_content: self.align_content.map(Into::into).unwrap_or_default(),
431 justify_content: self.justify_content.map(Into::into).unwrap_or_default(),
432 gap: self.gap.to_taffy(rem_size, scale_factor),
433 flex_direction: self.flex_direction.into(),
434 flex_wrap: self.flex_wrap.into(),
435 flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
436 flex_grow: self.flex_grow,
437 flex_shrink: self.flex_shrink,
438 grid_template_rows: to_grid_repeat(&self.grid_rows),
439 grid_template_columns: to_grid_repeat(&self.grid_cols),
440 grid_row: self
441 .grid_location
442 .as_ref()
443 .map(|location| to_grid_line(&location.row))
444 .unwrap_or_default(),
445 grid_column: self
446 .grid_location
447 .as_ref()
448 .map(|location| to_grid_line(&location.column))
449 .unwrap_or_default(),
450 ..Default::default()
451 }
452 }
453}
454
455impl ToTaffy<f32> for AbsoluteLength {
456 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
457 round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
458 }
459}
460
461impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
462 fn to_taffy(
463 &self,
464 rem_size: Pixels,
465 scale_factor: f32,
466 ) -> taffy::prelude::LengthPercentageAuto {
467 match self {
468 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
469 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
470 }
471 }
472}
473
474impl ToTaffy<taffy::style::Dimension> for Length {
475 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
476 match self {
477 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
478 Length::Auto => taffy::prelude::Dimension::auto(),
479 }
480 }
481}
482
483impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
484 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
485 match self {
486 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
487 DefiniteLength::Fraction(fraction) => {
488 taffy::style::LengthPercentage::percent(*fraction)
489 }
490 }
491 }
492}
493
494impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
495 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
496 match self {
497 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
498 DefiniteLength::Fraction(fraction) => {
499 taffy::style::LengthPercentageAuto::percent(*fraction)
500 }
501 }
502 }
503}
504
505impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
506 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
507 match self {
508 DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
509 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
510 }
511 }
512}
513
514impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
515 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
516 taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
517 }
518}
519
520impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
521 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
522 taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
523 }
524}
525
526impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
527 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
528 taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
529 }
530}
531
532impl<T, T2> From<TaffyPoint<T>> for Point<T2>
533where
534 T: Into<T2>,
535 T2: Clone + Debug + Default + PartialEq,
536{
537 fn from(point: TaffyPoint<T>) -> Point<T2> {
538 Point {
539 x: point.x.into(),
540 y: point.y.into(),
541 }
542 }
543}
544
545impl<T, T2> From<Point<T>> for TaffyPoint<T2>
546where
547 T: Into<T2> + Clone + Debug + Default + PartialEq,
548{
549 fn from(val: Point<T>) -> Self {
550 TaffyPoint {
551 x: val.x.into(),
552 y: val.y.into(),
553 }
554 }
555}
556
557impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
558where
559 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
560{
561 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
562 TaffySize {
563 width: self.width.to_taffy(rem_size, scale_factor),
564 height: self.height.to_taffy(rem_size, scale_factor),
565 }
566 }
567}
568
569impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
570where
571 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
572{
573 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
574 TaffyRect {
575 top: self.top.to_taffy(rem_size, scale_factor),
576 right: self.right.to_taffy(rem_size, scale_factor),
577 bottom: self.bottom.to_taffy(rem_size, scale_factor),
578 left: self.left.to_taffy(rem_size, scale_factor),
579 }
580 }
581}
582
583impl<T, U> From<TaffySize<T>> for Size<U>
584where
585 T: Into<U>,
586 U: Clone + Debug + Default + PartialEq,
587{
588 fn from(taffy_size: TaffySize<T>) -> Self {
589 Size {
590 width: taffy_size.width.into(),
591 height: taffy_size.height.into(),
592 }
593 }
594}
595
596impl<T, U> From<Size<T>> for TaffySize<U>
597where
598 T: Into<U> + Clone + Debug + Default + PartialEq,
599{
600 fn from(size: Size<T>) -> Self {
601 TaffySize {
602 width: size.width.into(),
603 height: size.height.into(),
604 }
605 }
606}
607
608#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
610pub enum AvailableSpace {
611 Definite(Pixels),
613 #[default]
615 MinContent,
616 MaxContent,
618}
619
620pub(crate) fn available_space_for_optional_width(width: Option<Pixels>) -> AvailableSpace {
621 width.map_or(AvailableSpace::MaxContent, AvailableSpace::Definite)
622}
623
624impl AvailableSpace {
625 pub const fn min_size() -> Size<Self> {
639 Size {
640 width: Self::MinContent,
641 height: Self::MinContent,
642 }
643 }
644}
645
646impl From<AvailableSpace> for TaffyAvailableSpace {
647 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
648 match space {
649 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
650 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
651 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
652 }
653 }
654}
655
656impl From<TaffyAvailableSpace> for AvailableSpace {
657 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
658 match space {
659 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
660 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
661 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
662 }
663 }
664}
665
666impl From<Pixels> for AvailableSpace {
667 fn from(pixels: Pixels) -> Self {
668 AvailableSpace::Definite(pixels)
669 }
670}
671
672impl From<Size<Pixels>> for Size<AvailableSpace> {
673 fn from(size: Size<Pixels>) -> Self {
674 Size {
675 width: AvailableSpace::Definite(size.width),
676 height: AvailableSpace::Definite(size.height),
677 }
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use crate::px;
685
686 #[test]
687 fn optional_width_uses_max_content_when_unbounded() {
688 assert_eq!(
689 available_space_for_optional_width(None),
690 AvailableSpace::MaxContent
691 );
692 assert_eq!(
693 available_space_for_optional_width(Some(px(320.0))),
694 AvailableSpace::Definite(px(320.0))
695 );
696 }
697}