1use crate::{
2 AbsoluteLength, App, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, Window,
3};
4use collections::{FxHashMap, FxHashSet};
5use smallvec::SmallVec;
6use stacksafe::{StackSafe, stacksafe};
7use std::{fmt::Debug, ops::Range};
8use taffy::{
9 TaffyTree, TraversePartialTree as _,
10 geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
11 style::AvailableSpace as TaffyAvailableSpace,
12 tree::NodeId,
13};
14
15type NodeMeasureFn = StackSafe<
16 Box<
17 dyn FnMut(
18 Size<Option<Pixels>>,
19 Size<AvailableSpace>,
20 &mut Window,
21 &mut App,
22 ) -> Size<Pixels>,
23 >,
24>;
25
26struct NodeContext {
27 measure: NodeMeasureFn,
28}
29pub struct TaffyLayoutEngine {
30 taffy: TaffyTree<NodeContext>,
31 absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
32 computed_layouts: FxHashSet<LayoutId>,
33}
34
35const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
36
37impl TaffyLayoutEngine {
38 pub fn new() -> Self {
39 let mut taffy = TaffyTree::new();
40 taffy.disable_rounding();
41 TaffyLayoutEngine {
42 taffy,
43 absolute_layout_bounds: FxHashMap::default(),
44 computed_layouts: FxHashSet::default(),
45 }
46 }
47
48 pub fn clear(&mut self) {
49 self.taffy.clear();
50 self.absolute_layout_bounds.clear();
51 self.computed_layouts.clear();
52 }
53
54 pub fn request_layout(
55 &mut self,
56 style: Style,
57 rem_size: Pixels,
58 children: &[LayoutId],
59 ) -> LayoutId {
60 let taffy_style = style.to_taffy(rem_size);
61 let layout_id = if children.is_empty() {
62 self.taffy
63 .new_leaf(taffy_style)
64 .expect(EXPECT_MESSAGE)
65 .into()
66 } else {
67 let parent_id = self
68 .taffy
69 // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
70 .new_with_children(taffy_style, unsafe {
71 std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(children)
72 })
73 .expect(EXPECT_MESSAGE)
74 .into();
75 parent_id
76 };
77 layout_id
78 }
79
80 pub fn request_measured_layout(
81 &mut self,
82 style: Style,
83 rem_size: Pixels,
84 measure: impl FnMut(
85 Size<Option<Pixels>>,
86 Size<AvailableSpace>,
87 &mut Window,
88 &mut App,
89 ) -> Size<Pixels>
90 + 'static,
91 ) -> LayoutId {
92 let taffy_style = style.to_taffy(rem_size);
93
94 let layout_id = self
95 .taffy
96 .new_leaf_with_context(
97 taffy_style,
98 NodeContext {
99 measure: StackSafe::new(Box::new(measure)),
100 },
101 )
102 .expect(EXPECT_MESSAGE)
103 .into();
104 layout_id
105 }
106
107 // Used to understand performance
108 #[allow(dead_code)]
109 fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
110 let mut count = 0;
111
112 for child in self.taffy.children(parent.0)? {
113 // Count this child.
114 count += 1;
115
116 // Count all of this child's children.
117 count += self.count_all_children(LayoutId(child))?
118 }
119
120 Ok(count)
121 }
122
123 // Used to understand performance
124 #[allow(dead_code)]
125 fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
126 println!(
127 "{parent:?} at depth {depth} has {} children",
128 self.taffy.child_count(parent.0)
129 );
130
131 let mut max_child_depth = 0;
132
133 for child in self.taffy.children(parent.0)? {
134 max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
135 }
136
137 Ok(depth + 1 + max_child_depth)
138 }
139
140 // Used to understand performance
141 #[allow(dead_code)]
142 fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
143 let mut edges = Vec::new();
144
145 for child in self.taffy.children(parent.0)? {
146 edges.push((parent, LayoutId(child)));
147
148 edges.extend(self.get_edges(LayoutId(child))?);
149 }
150
151 Ok(edges)
152 }
153
154 #[stacksafe]
155 pub fn compute_layout(
156 &mut self,
157 id: LayoutId,
158 available_space: Size<AvailableSpace>,
159 window: &mut Window,
160 cx: &mut App,
161 ) {
162 // Leaving this here until we have a better instrumentation approach.
163 // println!("Laying out {} children", self.count_all_children(id)?);
164 // println!("Max layout depth: {}", self.max_depth(0, id)?);
165
166 // Output the edges (branches) of the tree in Mermaid format for visualization.
167 // println!("Edges:");
168 // for (a, b) in self.get_edges(id)? {
169 // println!("N{} --> N{}", u64::from(a), u64::from(b));
170 // }
171 // println!("");
172 //
173
174 if !self.computed_layouts.insert(id) {
175 let mut stack = SmallVec::<[LayoutId; 64]>::new();
176 stack.push(id);
177 while let Some(id) = stack.pop() {
178 self.absolute_layout_bounds.remove(&id);
179 stack.extend(
180 self.taffy
181 .children(id.into())
182 .expect(EXPECT_MESSAGE)
183 .into_iter()
184 .map(Into::into),
185 );
186 }
187 }
188
189 // let started_at = std::time::Instant::now();
190 self.taffy
191 .compute_layout_with_measure(
192 id.into(),
193 available_space.into(),
194 |known_dimensions, available_space, _id, node_context, _style| {
195 let Some(node_context) = node_context else {
196 return taffy::geometry::Size::default();
197 };
198
199 let known_dimensions = Size {
200 width: known_dimensions.width.map(Pixels),
201 height: known_dimensions.height.map(Pixels),
202 };
203
204 (node_context.measure)(known_dimensions, available_space.into(), window, cx)
205 .into()
206 },
207 )
208 .expect(EXPECT_MESSAGE);
209
210 // println!("compute_layout took {:?}", started_at.elapsed());
211 }
212
213 pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {
214 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
215 return layout;
216 }
217
218 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
219 let mut bounds = Bounds {
220 origin: layout.location.into(),
221 size: layout.size.into(),
222 };
223
224 if let Some(parent_id) = self.taffy.parent(id.0) {
225 let parent_bounds = self.layout_bounds(parent_id.into());
226 bounds.origin += parent_bounds.origin;
227 }
228 self.absolute_layout_bounds.insert(id, bounds);
229
230 bounds
231 }
232}
233
234/// A unique identifier for a layout node, generated when requesting a layout from Taffy
235#[derive(Copy, Clone, Eq, PartialEq, Debug)]
236#[repr(transparent)]
237pub struct LayoutId(NodeId);
238
239impl std::hash::Hash for LayoutId {
240 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
241 u64::from(self.0).hash(state);
242 }
243}
244
245impl From<NodeId> for LayoutId {
246 fn from(node_id: NodeId) -> Self {
247 Self(node_id)
248 }
249}
250
251impl From<LayoutId> for NodeId {
252 fn from(layout_id: LayoutId) -> NodeId {
253 layout_id.0
254 }
255}
256
257trait ToTaffy<Output> {
258 fn to_taffy(&self, rem_size: Pixels) -> Output;
259}
260
261impl ToTaffy<taffy::style::Style> for Style {
262 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
263 use taffy::style_helpers::{fr, length, minmax, repeat};
264
265 fn to_grid_line(
266 placement: &Range<crate::GridPlacement>,
267 ) -> taffy::Line<taffy::GridPlacement> {
268 taffy::Line {
269 start: placement.start.into(),
270 end: placement.end.into(),
271 }
272 }
273
274 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
275 unit: &Option<u16>,
276 ) -> Vec<taffy::GridTemplateComponent<T>> {
277 // grid-template-columns: repeat(<number>, minmax(0, 1fr));
278 unit.map(|count| vec![repeat(count, vec![minmax(length(0.0), fr(1.0))])])
279 .unwrap_or_default()
280 }
281
282 taffy::style::Style {
283 display: self.display.into(),
284 overflow: self.overflow.into(),
285 scrollbar_width: self.scrollbar_width,
286 position: self.position.into(),
287 inset: self.inset.to_taffy(rem_size),
288 size: self.size.to_taffy(rem_size),
289 min_size: self.min_size.to_taffy(rem_size),
290 max_size: self.max_size.to_taffy(rem_size),
291 aspect_ratio: self.aspect_ratio,
292 margin: self.margin.to_taffy(rem_size),
293 padding: self.padding.to_taffy(rem_size),
294 border: self.border_widths.to_taffy(rem_size),
295 align_items: self.align_items.map(|x| x.into()),
296 align_self: self.align_self.map(|x| x.into()),
297 align_content: self.align_content.map(|x| x.into()),
298 justify_content: self.justify_content.map(|x| x.into()),
299 gap: self.gap.to_taffy(rem_size),
300 flex_direction: self.flex_direction.into(),
301 flex_wrap: self.flex_wrap.into(),
302 flex_basis: self.flex_basis.to_taffy(rem_size),
303 flex_grow: self.flex_grow,
304 flex_shrink: self.flex_shrink,
305 grid_template_rows: to_grid_repeat(&self.grid_rows),
306 grid_template_columns: to_grid_repeat(&self.grid_cols),
307 grid_row: self
308 .grid_location
309 .as_ref()
310 .map(|location| to_grid_line(&location.row))
311 .unwrap_or_default(),
312 grid_column: self
313 .grid_location
314 .as_ref()
315 .map(|location| to_grid_line(&location.column))
316 .unwrap_or_default(),
317 ..Default::default()
318 }
319 }
320}
321
322impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
323 fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
324 match self {
325 Length::Definite(length) => length.to_taffy(rem_size),
326 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
327 }
328 }
329}
330
331impl ToTaffy<taffy::style::Dimension> for Length {
332 fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
333 match self {
334 Length::Definite(length) => length.to_taffy(rem_size),
335 Length::Auto => taffy::prelude::Dimension::auto(),
336 }
337 }
338}
339
340impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
341 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
342 match self {
343 DefiniteLength::Absolute(length) => match length {
344 AbsoluteLength::Pixels(pixels) => {
345 taffy::style::LengthPercentage::length(pixels.into())
346 }
347 AbsoluteLength::Rems(rems) => {
348 taffy::style::LengthPercentage::length((*rems * rem_size).into())
349 }
350 },
351 DefiniteLength::Fraction(fraction) => {
352 taffy::style::LengthPercentage::percent(*fraction)
353 }
354 }
355 }
356}
357
358impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
359 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
360 match self {
361 DefiniteLength::Absolute(length) => match length {
362 AbsoluteLength::Pixels(pixels) => {
363 taffy::style::LengthPercentageAuto::length(pixels.into())
364 }
365 AbsoluteLength::Rems(rems) => {
366 taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
367 }
368 },
369 DefiniteLength::Fraction(fraction) => {
370 taffy::style::LengthPercentageAuto::percent(*fraction)
371 }
372 }
373 }
374}
375
376impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
377 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
378 match self {
379 DefiniteLength::Absolute(length) => match length {
380 AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
381 AbsoluteLength::Rems(rems) => {
382 taffy::style::Dimension::length((*rems * rem_size).into())
383 }
384 },
385 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
386 }
387 }
388}
389
390impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
391 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
392 match self {
393 AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
394 AbsoluteLength::Rems(rems) => {
395 taffy::style::LengthPercentage::length((*rems * rem_size).into())
396 }
397 }
398 }
399}
400
401impl<T, T2> From<TaffyPoint<T>> for Point<T2>
402where
403 T: Into<T2>,
404 T2: Clone + Debug + Default + PartialEq,
405{
406 fn from(point: TaffyPoint<T>) -> Point<T2> {
407 Point {
408 x: point.x.into(),
409 y: point.y.into(),
410 }
411 }
412}
413
414impl<T, T2> From<Point<T>> for TaffyPoint<T2>
415where
416 T: Into<T2> + Clone + Debug + Default + PartialEq,
417{
418 fn from(val: Point<T>) -> Self {
419 TaffyPoint {
420 x: val.x.into(),
421 y: val.y.into(),
422 }
423 }
424}
425
426impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
427where
428 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
429{
430 fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
431 TaffySize {
432 width: self.width.to_taffy(rem_size),
433 height: self.height.to_taffy(rem_size),
434 }
435 }
436}
437
438impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
439where
440 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
441{
442 fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
443 TaffyRect {
444 top: self.top.to_taffy(rem_size),
445 right: self.right.to_taffy(rem_size),
446 bottom: self.bottom.to_taffy(rem_size),
447 left: self.left.to_taffy(rem_size),
448 }
449 }
450}
451
452impl<T, U> From<TaffySize<T>> for Size<U>
453where
454 T: Into<U>,
455 U: Clone + Debug + Default + PartialEq,
456{
457 fn from(taffy_size: TaffySize<T>) -> Self {
458 Size {
459 width: taffy_size.width.into(),
460 height: taffy_size.height.into(),
461 }
462 }
463}
464
465impl<T, U> From<Size<T>> for TaffySize<U>
466where
467 T: Into<U> + Clone + Debug + Default + PartialEq,
468{
469 fn from(size: Size<T>) -> Self {
470 TaffySize {
471 width: size.width.into(),
472 height: size.height.into(),
473 }
474 }
475}
476
477/// The space available for an element to be laid out in
478#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
479pub enum AvailableSpace {
480 /// The amount of space available is the specified number of pixels
481 Definite(Pixels),
482 /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
483 #[default]
484 MinContent,
485 /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
486 MaxContent,
487}
488
489impl AvailableSpace {
490 /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
491 ///
492 /// This function is useful when you want to create a `Size` with the minimum content constraints
493 /// for both dimensions.
494 ///
495 /// # Examples
496 ///
497 /// ```
498 /// let min_content_size = AvailableSpace::min_size();
499 /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
500 /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
501 /// ```
502 pub const fn min_size() -> Size<Self> {
503 Size {
504 width: Self::MinContent,
505 height: Self::MinContent,
506 }
507 }
508}
509
510impl From<AvailableSpace> for TaffyAvailableSpace {
511 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
512 match space {
513 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
514 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
515 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
516 }
517 }
518}
519
520impl From<TaffyAvailableSpace> for AvailableSpace {
521 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
522 match space {
523 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
524 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
525 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
526 }
527 }
528}
529
530impl From<Pixels> for AvailableSpace {
531 fn from(pixels: Pixels) -> Self {
532 AvailableSpace::Definite(pixels)
533 }
534}
535
536impl From<Size<Pixels>> for Size<AvailableSpace> {
537 fn from(size: Size<Pixels>) -> Self {
538 Size {
539 width: AvailableSpace::Definite(size.width),
540 height: AvailableSpace::Definite(size.height),
541 }
542 }
543}