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