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