1use crate::tree::{NodeId, ShadowTree};
8use crate::platform::PlatformBridge;
9use rustc_hash::FxHashMap;
10use serde::{Serialize, Deserialize};
11use taffy::prelude::*;
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct LayoutStyle {
21 #[serde(default)]
22 pub display: Display,
23 #[serde(default)]
24 pub position: Position,
25 #[serde(default)]
26 pub flex_direction: FlexDirection,
27 #[serde(default)]
28 pub flex_wrap: FlexWrap,
29 #[serde(default)]
30 pub flex_grow: f32,
31 #[serde(default = "default_flex_shrink")]
32 pub flex_shrink: f32,
33 #[serde(default)]
34 pub justify_content: Option<JustifyContent>,
35 #[serde(default)]
36 pub align_items: Option<AlignItems>,
37 #[serde(default)]
38 pub width: Dimension,
39 #[serde(default)]
40 pub height: Dimension,
41 #[serde(default)]
42 pub min_width: Dimension,
43 #[serde(default)]
44 pub min_height: Dimension,
45 #[serde(default)]
46 pub max_width: Dimension,
47 #[serde(default)]
48 pub max_height: Dimension,
49 #[serde(default)]
50 pub aspect_ratio: Option<f32>,
51 #[serde(default)]
52 pub margin: Edges,
53 #[serde(default)]
54 pub padding: Edges,
55 #[serde(default)]
56 pub gap: f32,
57 #[serde(default)]
58 pub overflow: Overflow,
59}
60
61fn default_flex_shrink() -> f32 { 1.0 }
62
63#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
64pub enum Display { #[default] Flex, Grid, None }
65
66#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
67pub enum Position { #[default] Relative, Absolute }
68
69#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
70pub enum FlexDirection { #[default] Column, Row, ColumnReverse, RowReverse }
71
72#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
73pub enum FlexWrap { #[default] NoWrap, Wrap, WrapReverse }
74
75#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
76pub enum JustifyContent { FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly }
77
78#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
79pub enum AlignItems { FlexStart, FlexEnd, Center, Stretch, Baseline }
80
81#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
82pub enum Dimension { #[default] Auto, Points(f32), Percent(f32) }
83
84#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
85pub enum Overflow { #[default] Visible, Hidden, Scroll }
86
87#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
88pub struct Edges {
89 #[serde(default)]
90 pub top: f32,
91 #[serde(default)]
92 pub right: f32,
93 #[serde(default)]
94 pub bottom: f32,
95 #[serde(default)]
96 pub left: f32,
97}
98
99#[derive(Debug, Clone, Copy, Default)]
101pub struct ComputedLayout {
102 pub x: f32,
103 pub y: f32,
104 pub width: f32,
105 pub height: f32,
106}
107
108pub struct LayoutEngine {
113 taffy: TaffyTree<NodeId>,
114 node_map: FxHashMap<NodeId, taffy::NodeId>,
115 computed: FxHashMap<NodeId, ComputedLayout>,
116 root: Option<NodeId>,
117}
118
119impl LayoutEngine {
120 pub fn new() -> Self {
121 Self {
122 taffy: TaffyTree::new(),
123 node_map: FxHashMap::default(),
124 computed: FxHashMap::default(),
125 root: None,
126 }
127 }
128
129 pub fn set_root(&mut self, id: NodeId) {
130 self.root = Some(id);
131 }
132
133 pub fn create_node(&mut self, id: NodeId, style: &LayoutStyle) -> Result<(), LayoutError> {
134 let taffy_style = convert_style(style);
135 let taffy_node = self.taffy
136 .new_leaf_with_context(taffy_style, id)
137 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
138 self.node_map.insert(id, taffy_node);
139 Ok(())
140 }
141
142 pub fn update_style(&mut self, id: NodeId, style: &LayoutStyle) -> Result<(), LayoutError> {
143 let taffy_node = self.node_map.get(&id)
144 .ok_or(LayoutError::NodeNotFound(id))?;
145 let taffy_style = convert_style(style);
146 self.taffy.set_style(*taffy_node, taffy_style)
147 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
148 Ok(())
149 }
150
151 pub fn remove_node(&mut self, id: NodeId) {
152 if let Some(taffy_node) = self.node_map.remove(&id) {
153 let _ = self.taffy.remove(taffy_node);
154 }
155 self.computed.remove(&id);
156 }
157
158 pub fn set_children_from_tree(
160 &mut self,
161 parent_id: NodeId,
162 tree: &ShadowTree,
163 ) -> Result<(), LayoutError> {
164 let parent_taffy = *self.node_map.get(&parent_id)
165 .ok_or(LayoutError::NodeNotFound(parent_id))?;
166
167 let children: Vec<taffy::NodeId> = tree.children_of(parent_id)
168 .iter()
169 .filter_map(|id| self.node_map.get(id).copied())
170 .collect();
171
172 self.taffy.set_children(parent_taffy, &children)
173 .map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
174 Ok(())
175 }
176
177 pub fn compute(
179 &mut self,
180 tree: &ShadowTree,
181 screen_width: f32,
182 screen_height: f32,
183 platform: &dyn PlatformBridge,
184 ) -> Result<(), LayoutError> {
185 let root_id = self.root.ok_or(LayoutError::NoRoot)?;
186 let root_taffy = *self.node_map.get(&root_id)
187 .ok_or(LayoutError::NodeNotFound(root_id))?;
188
189 let available = taffy::Size {
190 width: AvailableSpace::Definite(screen_width),
191 height: AvailableSpace::Definite(screen_height),
192 };
193
194 self.taffy.compute_layout_with_measure(
196 root_taffy,
197 available,
198 |known_dims, available_space, _node_id, node_context, _style| {
199 if let Some(framework_id) = node_context {
200 if let Some(node) = tree.get(*framework_id) {
202 if node.view_type == crate::platform::ViewType::Text {
203 if let Some(crate::platform::PropValue::String(text)) = node.props.get("text") {
204 let max_w = match available_space.width {
205 AvailableSpace::Definite(w) => w,
206 _ => f32::INFINITY,
207 };
208 let metrics = platform.measure_text(
209 text,
210 &crate::platform::TextStyle::default(),
211 max_w,
212 );
213 return taffy::Size {
214 width: metrics.width,
215 height: metrics.height,
216 };
217 }
218 }
219 }
220 }
221 taffy::Size::ZERO
222 },
223 ).map_err(|e| LayoutError::TaffyError(format!("{e}")))?;
224
225 self.computed.clear();
227 self.collect_layouts(root_taffy, 0.0, 0.0);
228
229 Ok(())
230 }
231
232 fn collect_layouts(&mut self, node: taffy::NodeId, parent_x: f32, parent_y: f32) {
233 let layout = self.taffy.layout(node).unwrap();
234 let abs_x = parent_x + layout.location.x;
235 let abs_y = parent_y + layout.location.y;
236
237 let framework_id = self.taffy.get_node_context(node).copied();
238 if let Some(fid) = framework_id {
239 self.computed.insert(fid, ComputedLayout {
240 x: abs_x,
241 y: abs_y,
242 width: layout.size.width,
243 height: layout.size.height,
244 });
245 }
246
247 let children = self.taffy.children(node).unwrap();
248 for &child in &children {
249 self.collect_layouts(child, abs_x, abs_y);
250 }
251 }
252
253 pub fn get_computed(&self, id: NodeId) -> Option<&ComputedLayout> {
255 self.computed.get(&id)
256 }
257
258 pub fn hit_test(&self, x: f32, y: f32) -> Vec<NodeId> {
261 let mut hits: Vec<(NodeId, f32)> = self.computed.iter()
262 .filter(|(_, layout)| {
263 x >= layout.x && x <= layout.x + layout.width &&
264 y >= layout.y && y <= layout.y + layout.height
265 })
266 .map(|(&id, layout)| (id, layout.width * layout.height))
267 .collect();
268
269 hits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
270 hits.into_iter().map(|(id, _)| id).collect()
271 }
272}
273
274fn convert_style(style: &LayoutStyle) -> taffy::Style {
279 taffy::Style {
280 display: match style.display {
281 Display::Flex => taffy::Display::Flex,
282 Display::Grid => taffy::Display::Grid,
283 Display::None => taffy::Display::None,
284 },
285 position: match style.position {
286 Position::Relative => taffy::Position::Relative,
287 Position::Absolute => taffy::Position::Absolute,
288 },
289 flex_direction: match style.flex_direction {
290 FlexDirection::Row => taffy::FlexDirection::Row,
291 FlexDirection::Column => taffy::FlexDirection::Column,
292 FlexDirection::RowReverse => taffy::FlexDirection::RowReverse,
293 FlexDirection::ColumnReverse => taffy::FlexDirection::ColumnReverse,
294 },
295 flex_wrap: match style.flex_wrap {
296 FlexWrap::NoWrap => taffy::FlexWrap::NoWrap,
297 FlexWrap::Wrap => taffy::FlexWrap::Wrap,
298 FlexWrap::WrapReverse => taffy::FlexWrap::WrapReverse,
299 },
300 flex_grow: style.flex_grow,
301 flex_shrink: style.flex_shrink,
302 justify_content: style.justify_content.map(|j| match j {
303 JustifyContent::FlexStart => taffy::JustifyContent::FlexStart,
304 JustifyContent::FlexEnd => taffy::JustifyContent::FlexEnd,
305 JustifyContent::Center => taffy::JustifyContent::Center,
306 JustifyContent::SpaceBetween => taffy::JustifyContent::SpaceBetween,
307 JustifyContent::SpaceAround => taffy::JustifyContent::SpaceAround,
308 JustifyContent::SpaceEvenly => taffy::JustifyContent::SpaceEvenly,
309 }),
310 align_items: style.align_items.map(|a| match a {
311 AlignItems::FlexStart => taffy::AlignItems::FlexStart,
312 AlignItems::FlexEnd => taffy::AlignItems::FlexEnd,
313 AlignItems::Center => taffy::AlignItems::Center,
314 AlignItems::Stretch => taffy::AlignItems::Stretch,
315 AlignItems::Baseline => taffy::AlignItems::Baseline,
316 }),
317 size: taffy::Size {
318 width: convert_dimension(style.width),
319 height: convert_dimension(style.height),
320 },
321 min_size: taffy::Size {
322 width: convert_dimension(style.min_width),
323 height: convert_dimension(style.min_height),
324 },
325 max_size: taffy::Size {
326 width: convert_dimension(style.max_width),
327 height: convert_dimension(style.max_height),
328 },
329 aspect_ratio: style.aspect_ratio,
330 margin: taffy::Rect {
331 top: length(style.margin.top),
332 right: length(style.margin.right),
333 bottom: length(style.margin.bottom),
334 left: length(style.margin.left),
335 },
336 padding: taffy::Rect {
337 top: length_padding(style.padding.top),
338 right: length_padding(style.padding.right),
339 bottom: length_padding(style.padding.bottom),
340 left: length_padding(style.padding.left),
341 },
342 gap: taffy::Size {
343 width: taffy::LengthPercentage::Length(style.gap),
344 height: taffy::LengthPercentage::Length(style.gap),
345 },
346 overflow: taffy::Point {
347 x: convert_overflow(style.overflow),
348 y: convert_overflow(style.overflow),
349 },
350 ..Default::default()
351 }
352}
353
354fn convert_dimension(dim: Dimension) -> taffy::Dimension {
355 match dim {
356 Dimension::Auto => taffy::Dimension::Auto,
357 Dimension::Points(v) => taffy::Dimension::Length(v),
358 Dimension::Percent(v) => taffy::Dimension::Percent(v / 100.0),
359 }
360}
361
362fn length(v: f32) -> taffy::LengthPercentageAuto {
363 taffy::LengthPercentageAuto::Length(v)
364}
365
366fn length_padding(v: f32) -> taffy::LengthPercentage {
367 taffy::LengthPercentage::Length(v)
368}
369
370fn convert_overflow(o: Overflow) -> taffy::Overflow {
371 match o {
372 Overflow::Visible => taffy::Overflow::Visible,
373 Overflow::Hidden => taffy::Overflow::Hidden,
374 Overflow::Scroll => taffy::Overflow::Scroll,
375 }
376}
377
378#[derive(Debug, thiserror::Error)]
383pub enum LayoutError {
384 #[error("Taffy error: {0}")]
385 TaffyError(String),
386
387 #[error("Node not found: {0}")]
388 NodeNotFound(NodeId),
389
390 #[error("No root node set")]
391 NoRoot,
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::platform::mock::MockPlatform;
398 use crate::platform::ViewType;
399 use std::collections::HashMap;
400 use std::sync::Arc;
401
402 #[test]
403 fn test_basic_layout() {
404 let platform = Arc::new(MockPlatform::new());
405 let mut tree = ShadowTree::new();
406 let mut layout = LayoutEngine::new();
407
408 tree.create_node(NodeId(1), ViewType::Container, HashMap::new());
410 tree.create_node(NodeId(2), ViewType::Container, HashMap::new());
411 tree.set_root(NodeId(1));
412 tree.append_child(NodeId(1), NodeId(2));
413
414 layout.create_node(NodeId(1), &LayoutStyle {
415 width: Dimension::Points(390.0),
416 height: Dimension::Points(844.0),
417 ..Default::default()
418 }).unwrap();
419
420 layout.create_node(NodeId(2), &LayoutStyle {
421 width: Dimension::Points(100.0),
422 height: Dimension::Points(50.0),
423 ..Default::default()
424 }).unwrap();
425
426 layout.set_root(NodeId(1));
427 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
428 layout.compute(&tree, 390.0, 844.0, &*platform).unwrap();
429
430 let root_layout = layout.get_computed(NodeId(1)).unwrap();
431 assert_eq!(root_layout.width, 390.0);
432 assert_eq!(root_layout.height, 844.0);
433
434 let child_layout = layout.get_computed(NodeId(2)).unwrap();
435 assert_eq!(child_layout.width, 100.0);
436 assert_eq!(child_layout.height, 50.0);
437 assert_eq!(child_layout.x, 0.0);
438 assert_eq!(child_layout.y, 0.0);
439 }
440
441 #[test]
442 fn stress_deep_nesting() {
443 let platform = Arc::new(MockPlatform::new());
445 let mut tree = ShadowTree::new();
446 let mut layout = LayoutEngine::new();
447 let depth = 50;
448
449 for i in 1..=(depth as u64) {
450 tree.create_node(NodeId(i), ViewType::Container, HashMap::new());
451 let style = if i == 1 {
452 LayoutStyle {
453 width: Dimension::Points(400.0),
454 height: Dimension::Points(800.0),
455 ..Default::default()
456 }
457 } else {
458 LayoutStyle {
459 flex_grow: 1.0,
460 ..Default::default()
461 }
462 };
463 layout.create_node(NodeId(i), &style).unwrap();
464 }
465
466 tree.set_root(NodeId(1));
467 layout.set_root(NodeId(1));
468
469 for i in 1..(depth as u64) {
470 tree.append_child(NodeId(i), NodeId(i + 1));
471 layout.set_children_from_tree(NodeId(i), &tree).unwrap();
472 }
473
474 layout.compute(&tree, 400.0, 800.0, &*platform).unwrap();
475
476 let root = layout.get_computed(NodeId(1)).unwrap();
478 assert_eq!(root.width, 400.0);
479 assert_eq!(root.height, 800.0);
480
481 let deepest = layout.get_computed(NodeId(depth as u64)).unwrap();
483 assert!(deepest.width > 0.0, "Deepest node should have non-zero width");
484 assert!(deepest.height > 0.0, "Deepest node should have non-zero height");
485 }
486
487 #[test]
488 fn stress_wide_tree() {
489 let platform = Arc::new(MockPlatform::new());
491 let mut tree = ShadowTree::new();
492 let mut layout = LayoutEngine::new();
493 let child_count = 200u64;
494
495 tree.create_node(NodeId(1), ViewType::Container, HashMap::new());
496 layout.create_node(NodeId(1), &LayoutStyle {
497 width: Dimension::Points(1000.0),
498 height: Dimension::Points(100.0),
499 flex_direction: FlexDirection::Row,
500 ..Default::default()
501 }).unwrap();
502
503 tree.set_root(NodeId(1));
504 layout.set_root(NodeId(1));
505
506 for i in 2..=(child_count + 1) {
507 tree.create_node(NodeId(i), ViewType::Container, HashMap::new());
508 layout.create_node(NodeId(i), &LayoutStyle {
509 width: Dimension::Points(5.0),
510 height: Dimension::Points(20.0),
511 ..Default::default()
512 }).unwrap();
513 tree.append_child(NodeId(1), NodeId(i));
514 }
515
516 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
517 layout.compute(&tree, 1000.0, 100.0, &*platform).unwrap();
518
519 for i in 2..=(child_count + 1) {
521 let cl = layout.get_computed(NodeId(i));
522 assert!(cl.is_some(), "Child {} should have computed layout", i);
523 }
524
525 let first = layout.get_computed(NodeId(2)).unwrap();
527 let second = layout.get_computed(NodeId(3)).unwrap();
528 assert!(second.x > first.x, "Second child should be to the right of first");
529 }
530
531 #[test]
532 fn stress_rapid_mutations() {
533 let platform = Arc::new(MockPlatform::new());
535 let mut tree = ShadowTree::new();
536 let mut layout = LayoutEngine::new();
537
538 tree.create_node(NodeId(1), ViewType::Container, HashMap::new());
540 layout.create_node(NodeId(1), &LayoutStyle {
541 width: Dimension::Points(400.0),
542 height: Dimension::Points(800.0),
543 ..Default::default()
544 }).unwrap();
545 tree.set_root(NodeId(1));
546 layout.set_root(NodeId(1));
547
548 for i in 2..=11u64 {
549 tree.create_node(NodeId(i), ViewType::Container, HashMap::new());
550 layout.create_node(NodeId(i), &LayoutStyle {
551 height: Dimension::Points(50.0),
552 ..Default::default()
553 }).unwrap();
554 tree.append_child(NodeId(1), NodeId(i));
555 }
556 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
557 layout.compute(&tree, 400.0, 800.0, &*platform).unwrap();
558 assert_eq!(tree.len(), 11);
559
560 for i in (7..=11u64).rev() {
562 tree.remove_child(NodeId(1), NodeId(i));
563 layout.remove_node(NodeId(i));
564 }
565 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
566 layout.compute(&tree, 400.0, 800.0, &*platform).unwrap();
567 assert_eq!(tree.len(), 6);
568
569 for i in 100..=104u64 {
571 tree.create_node(NodeId(i), ViewType::Container, HashMap::new());
572 layout.create_node(NodeId(i), &LayoutStyle {
573 height: Dimension::Points(30.0),
574 ..Default::default()
575 }).unwrap();
576 tree.append_child(NodeId(1), NodeId(i));
577 }
578 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
579 layout.compute(&tree, 400.0, 800.0, &*platform).unwrap();
580 assert_eq!(tree.len(), 11);
581
582 for &id in tree.children_of(NodeId(1)) {
584 assert!(layout.get_computed(id).is_some());
585 }
586 }
587
588 #[test]
589 fn stress_mixed_dimensions() {
590 let platform = Arc::new(MockPlatform::new());
592 let mut tree = ShadowTree::new();
593 let mut layout = LayoutEngine::new();
594
595 tree.create_node(NodeId(1), ViewType::Container, HashMap::new());
596 layout.create_node(NodeId(1), &LayoutStyle {
597 width: Dimension::Points(400.0),
598 height: Dimension::Points(600.0),
599 ..Default::default()
600 }).unwrap();
601 tree.set_root(NodeId(1));
602 layout.set_root(NodeId(1));
603
604 tree.create_node(NodeId(2), ViewType::Container, HashMap::new());
606 layout.create_node(NodeId(2), &LayoutStyle {
607 width: Dimension::Points(100.0),
608 height: Dimension::Points(100.0),
609 ..Default::default()
610 }).unwrap();
611 tree.append_child(NodeId(1), NodeId(2));
612
613 tree.create_node(NodeId(3), ViewType::Container, HashMap::new());
615 layout.create_node(NodeId(3), &LayoutStyle {
616 width: Dimension::Percent(50.0),
617 height: Dimension::Points(80.0),
618 ..Default::default()
619 }).unwrap();
620 tree.append_child(NodeId(1), NodeId(3));
621
622 tree.create_node(NodeId(4), ViewType::Container, HashMap::new());
624 layout.create_node(NodeId(4), &LayoutStyle {
625 flex_grow: 1.0,
626 ..Default::default()
627 }).unwrap();
628 tree.append_child(NodeId(1), NodeId(4));
629
630 layout.set_children_from_tree(NodeId(1), &tree).unwrap();
631 layout.compute(&tree, 400.0, 600.0, &*platform).unwrap();
632
633 let c1 = layout.get_computed(NodeId(2)).unwrap();
634 assert_eq!(c1.width, 100.0);
635 assert_eq!(c1.height, 100.0);
636
637 let c2 = layout.get_computed(NodeId(3)).unwrap();
638 assert_eq!(c2.width, 200.0); assert_eq!(c2.height, 80.0);
640
641 let c3 = layout.get_computed(NodeId(4)).unwrap();
642 assert!(c3.height > 0.0, "Flex-grow child should expand");
643 }
644}