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