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