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;
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 taffy::style::Style {
255 display: self.display.into(),
256 overflow: self.overflow.into(),
257 scrollbar_width: self.scrollbar_width,
258 position: self.position.into(),
259 inset: self.inset.to_taffy(rem_size),
260 size: self.size.to_taffy(rem_size),
261 min_size: self.min_size.to_taffy(rem_size),
262 max_size: self.max_size.to_taffy(rem_size),
263 aspect_ratio: self.aspect_ratio,
264 margin: self.margin.to_taffy(rem_size),
265 padding: self.padding.to_taffy(rem_size),
266 border: self.border_widths.to_taffy(rem_size),
267 align_items: self.align_items.map(|x| x.into()),
268 align_self: self.align_self.map(|x| x.into()),
269 align_content: self.align_content.map(|x| x.into()),
270 justify_content: self.justify_content.map(|x| x.into()),
271 gap: self.gap.to_taffy(rem_size),
272 flex_direction: self.flex_direction.into(),
273 flex_wrap: self.flex_wrap.into(),
274 flex_basis: self.flex_basis.to_taffy(rem_size),
275 flex_grow: self.flex_grow,
276 flex_shrink: self.flex_shrink,
277 ..Default::default() // Ignore grid properties for now
278 }
279 }
280}
281
282impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
283 fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
284 match self {
285 Length::Definite(length) => length.to_taffy(rem_size),
286 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
287 }
288 }
289}
290
291impl ToTaffy<taffy::style::Dimension> for Length {
292 fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
293 match self {
294 Length::Definite(length) => length.to_taffy(rem_size),
295 Length::Auto => taffy::prelude::Dimension::auto(),
296 }
297 }
298}
299
300impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
301 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
302 match self {
303 DefiniteLength::Absolute(length) => match length {
304 AbsoluteLength::Pixels(pixels) => {
305 taffy::style::LengthPercentage::length(pixels.into())
306 }
307 AbsoluteLength::Rems(rems) => {
308 taffy::style::LengthPercentage::length((*rems * rem_size).into())
309 }
310 },
311 DefiniteLength::Fraction(fraction) => {
312 taffy::style::LengthPercentage::percent(*fraction)
313 }
314 }
315 }
316}
317
318impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
319 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
320 match self {
321 DefiniteLength::Absolute(length) => match length {
322 AbsoluteLength::Pixels(pixels) => {
323 taffy::style::LengthPercentageAuto::length(pixels.into())
324 }
325 AbsoluteLength::Rems(rems) => {
326 taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
327 }
328 },
329 DefiniteLength::Fraction(fraction) => {
330 taffy::style::LengthPercentageAuto::percent(*fraction)
331 }
332 }
333 }
334}
335
336impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
337 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
338 match self {
339 DefiniteLength::Absolute(length) => match length {
340 AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
341 AbsoluteLength::Rems(rems) => {
342 taffy::style::Dimension::length((*rems * rem_size).into())
343 }
344 },
345 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
346 }
347 }
348}
349
350impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
351 fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
352 match self {
353 AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
354 AbsoluteLength::Rems(rems) => {
355 taffy::style::LengthPercentage::length((*rems * rem_size).into())
356 }
357 }
358 }
359}
360
361impl<T, T2> From<TaffyPoint<T>> for Point<T2>
362where
363 T: Into<T2>,
364 T2: Clone + Debug + Default + PartialEq,
365{
366 fn from(point: TaffyPoint<T>) -> Point<T2> {
367 Point {
368 x: point.x.into(),
369 y: point.y.into(),
370 }
371 }
372}
373
374impl<T, T2> From<Point<T>> for TaffyPoint<T2>
375where
376 T: Into<T2> + Clone + Debug + Default + PartialEq,
377{
378 fn from(val: Point<T>) -> Self {
379 TaffyPoint {
380 x: val.x.into(),
381 y: val.y.into(),
382 }
383 }
384}
385
386impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
387where
388 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
389{
390 fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
391 TaffySize {
392 width: self.width.to_taffy(rem_size),
393 height: self.height.to_taffy(rem_size),
394 }
395 }
396}
397
398impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
399where
400 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
401{
402 fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
403 TaffyRect {
404 top: self.top.to_taffy(rem_size),
405 right: self.right.to_taffy(rem_size),
406 bottom: self.bottom.to_taffy(rem_size),
407 left: self.left.to_taffy(rem_size),
408 }
409 }
410}
411
412impl<T, U> From<TaffySize<T>> for Size<U>
413where
414 T: Into<U>,
415 U: Clone + Debug + Default + PartialEq,
416{
417 fn from(taffy_size: TaffySize<T>) -> Self {
418 Size {
419 width: taffy_size.width.into(),
420 height: taffy_size.height.into(),
421 }
422 }
423}
424
425impl<T, U> From<Size<T>> for TaffySize<U>
426where
427 T: Into<U> + Clone + Debug + Default + PartialEq,
428{
429 fn from(size: Size<T>) -> Self {
430 TaffySize {
431 width: size.width.into(),
432 height: size.height.into(),
433 }
434 }
435}
436
437/// The space available for an element to be laid out in
438#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
439pub enum AvailableSpace {
440 /// The amount of space available is the specified number of pixels
441 Definite(Pixels),
442 /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
443 #[default]
444 MinContent,
445 /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
446 MaxContent,
447}
448
449impl AvailableSpace {
450 /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
451 ///
452 /// This function is useful when you want to create a `Size` with the minimum content constraints
453 /// for both dimensions.
454 ///
455 /// # Examples
456 ///
457 /// ```
458 /// let min_content_size = AvailableSpace::min_size();
459 /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
460 /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
461 /// ```
462 pub const fn min_size() -> Size<Self> {
463 Size {
464 width: Self::MinContent,
465 height: Self::MinContent,
466 }
467 }
468}
469
470impl From<AvailableSpace> for TaffyAvailableSpace {
471 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
472 match space {
473 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
474 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
475 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
476 }
477 }
478}
479
480impl From<TaffyAvailableSpace> for AvailableSpace {
481 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
482 match space {
483 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
484 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
485 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
486 }
487 }
488}
489
490impl From<Pixels> for AvailableSpace {
491 fn from(pixels: Pixels) -> Self {
492 AvailableSpace::Definite(pixels)
493 }
494}
495
496impl From<Size<Pixels>> for Size<AvailableSpace> {
497 fn from(size: Size<Pixels>) -> Self {
498 Size {
499 width: AvailableSpace::Definite(size.width),
500 height: AvailableSpace::Definite(size.height),
501 }
502 }
503}