presentar_layout/
engine.rs

1//! Layout engine implementation.
2
3use presentar_core::{Constraints, Rect, Size, Widget};
4use std::collections::HashMap;
5
6use crate::cache::LayoutCache;
7
8/// Layout tree containing computed positions.
9#[derive(Debug, Default)]
10pub struct LayoutTree {
11    /// Computed sizes for each widget
12    pub sizes: HashMap<u64, Size>,
13    /// Computed positions for each widget
14    pub positions: HashMap<u64, Rect>,
15}
16
17impl LayoutTree {
18    /// Get the size for a widget by ID.
19    #[must_use]
20    pub fn get_size(&self, id: u64) -> Option<Size> {
21        self.sizes.get(&id).copied()
22    }
23
24    /// Get the position for a widget by ID.
25    #[must_use]
26    pub fn get_position(&self, id: u64) -> Option<Rect> {
27        self.positions.get(&id).copied()
28    }
29
30    /// Get widget count.
31    #[must_use]
32    pub fn widget_count(&self) -> usize {
33        self.positions.len()
34    }
35}
36
37/// Layout engine with memoization.
38#[derive(Debug, Default)]
39pub struct LayoutEngine {
40    cache: LayoutCache,
41    next_id: u64,
42}
43
44impl LayoutEngine {
45    /// Create a new layout engine.
46    #[must_use]
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Compute layout for the widget tree.
52    ///
53    /// This performs a two-phase layout:
54    /// 1. Measure phase (bottom-up): Determine intrinsic sizes
55    /// 2. Layout phase (top-down): Assign final positions and sizes
56    pub fn compute(&mut self, root: &mut dyn Widget, viewport: Size) -> LayoutTree {
57        self.cache.clear();
58        self.next_id = 0;
59
60        let constraints = Constraints::loose(viewport);
61
62        // Phase 1: Measure (bottom-up)
63        let mut sizes = HashMap::new();
64        self.measure_tree(root, constraints, &mut sizes);
65
66        // Reset ID counter for layout phase
67        self.next_id = 0;
68
69        // Phase 2: Layout (top-down)
70        let mut positions = HashMap::new();
71        let bounds = Rect::from_size(viewport);
72        self.layout_tree(root, bounds, &mut positions);
73
74        LayoutTree { sizes, positions }
75    }
76
77    /// Compute layout with read-only widget tree (for measurement only).
78    pub fn compute_readonly(&mut self, root: &dyn Widget, viewport: Size) -> LayoutTree {
79        self.cache.clear();
80        self.next_id = 0;
81
82        let constraints = Constraints::loose(viewport);
83
84        // Phase 1: Measure (bottom-up)
85        let mut sizes = HashMap::new();
86        self.measure_tree(root, constraints, &mut sizes);
87
88        // Reset ID counter
89        self.next_id = 0;
90
91        // Phase 2: Position (simplified for read-only)
92        let mut positions = HashMap::new();
93        let bounds = Rect::from_size(viewport);
94        self.position_tree_readonly(root, bounds, &mut positions);
95
96        LayoutTree { sizes, positions }
97    }
98
99    fn measure_tree(
100        &mut self,
101        widget: &dyn Widget,
102        constraints: Constraints,
103        sizes: &mut HashMap<u64, Size>,
104    ) -> Size {
105        let id = self.next_id;
106        self.next_id += 1;
107
108        // Measure children first (bottom-up)
109        for child in widget.children() {
110            self.measure_tree(child.as_ref(), constraints, sizes);
111        }
112
113        // Then measure self
114        let size = widget.measure(constraints);
115        sizes.insert(id, size);
116        size
117    }
118
119    fn layout_tree(
120        &mut self,
121        widget: &mut dyn Widget,
122        bounds: Rect,
123        positions: &mut HashMap<u64, Rect>,
124    ) {
125        let id = self.next_id;
126        self.next_id += 1;
127
128        // Call layout on the widget - this allows it to position its children
129        let result = widget.layout(bounds);
130        positions.insert(
131            id,
132            Rect::new(bounds.x, bounds.y, result.size.width, result.size.height),
133        );
134
135        // Recursively layout children (they should already be positioned by parent's layout)
136        for child in widget.children_mut() {
137            // Children get their bounds from the parent's layout
138            // We still need to traverse to record positions
139            self.collect_child_positions(child.as_mut(), positions);
140        }
141    }
142
143    fn collect_child_positions(
144        &mut self,
145        widget: &mut dyn Widget,
146        positions: &mut HashMap<u64, Rect>,
147    ) {
148        let id = self.next_id;
149        self.next_id += 1;
150
151        // The widget should already have been laid out by its parent
152        // We just record its current bounds
153        // Note: In a real implementation, we'd need to track bounds per widget
154        // For now, we assume the widget stores its own bounds
155
156        // Get bounds from recent layout (stored in widget)
157        // Since we can't easily get this, we'll use a placeholder
158        positions.insert(id, Rect::default());
159
160        for child in widget.children_mut() {
161            self.collect_child_positions(child.as_mut(), positions);
162        }
163    }
164
165    fn position_tree_readonly(
166        &mut self,
167        widget: &dyn Widget,
168        bounds: Rect,
169        positions: &mut HashMap<u64, Rect>,
170    ) {
171        let id = self.next_id;
172        self.next_id += 1;
173
174        positions.insert(id, bounds);
175
176        // For read-only, we estimate child positions based on measurement
177        for child in widget.children() {
178            // Give each child the parent bounds (simplified)
179            self.position_tree_readonly(child.as_ref(), bounds, positions);
180        }
181    }
182
183    /// Clear the layout cache.
184    pub fn clear_cache(&mut self) {
185        self.cache.clear();
186    }
187
188    /// Get cache statistics.
189    #[must_use]
190    pub const fn cache_stats(&self) -> (usize, usize) {
191        (self.cache.hits(), self.cache.misses())
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use presentar_core::widget::{AccessibleRole, LayoutResult};
199    use presentar_core::{Canvas, Event, TypeId};
200    use std::any::Any;
201
202    // Test widget for layout testing
203    struct TestWidget {
204        size: Size,
205        children: Vec<Box<dyn Widget>>,
206    }
207
208    impl TestWidget {
209        fn new(width: f32, height: f32) -> Self {
210            Self {
211                size: Size::new(width, height),
212                children: Vec::new(),
213            }
214        }
215
216        fn with_child(mut self, child: TestWidget) -> Self {
217            self.children.push(Box::new(child));
218            self
219        }
220    }
221
222    impl Widget for TestWidget {
223        fn type_id(&self) -> TypeId {
224            TypeId::of::<Self>()
225        }
226
227        fn measure(&self, constraints: Constraints) -> Size {
228            constraints.constrain(self.size)
229        }
230
231        fn layout(&mut self, bounds: Rect) -> LayoutResult {
232            LayoutResult {
233                size: bounds.size(),
234            }
235        }
236
237        fn paint(&self, _canvas: &mut dyn Canvas) {}
238
239        fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
240            None
241        }
242
243        fn children(&self) -> &[Box<dyn Widget>] {
244            &self.children
245        }
246
247        fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
248            &mut self.children
249        }
250
251        fn accessible_role(&self) -> AccessibleRole {
252            AccessibleRole::Generic
253        }
254    }
255
256    #[test]
257    fn test_layout_engine_new() {
258        let engine = LayoutEngine::new();
259        assert_eq!(engine.next_id, 0);
260    }
261
262    #[test]
263    fn test_layout_tree_default() {
264        let tree = LayoutTree::default();
265        assert!(tree.sizes.is_empty());
266        assert!(tree.positions.is_empty());
267        assert_eq!(tree.widget_count(), 0);
268    }
269
270    #[test]
271    fn test_layout_tree_get_size() {
272        let mut tree = LayoutTree::default();
273        tree.sizes.insert(0, Size::new(100.0, 50.0));
274        assert_eq!(tree.get_size(0), Some(Size::new(100.0, 50.0)));
275        assert_eq!(tree.get_size(1), None);
276    }
277
278    #[test]
279    fn test_layout_tree_get_position() {
280        let mut tree = LayoutTree::default();
281        tree.positions.insert(0, Rect::new(10.0, 20.0, 100.0, 50.0));
282        assert_eq!(
283            tree.get_position(0),
284            Some(Rect::new(10.0, 20.0, 100.0, 50.0))
285        );
286        assert_eq!(tree.get_position(1), None);
287    }
288
289    #[test]
290    fn test_layout_single_widget() {
291        let mut engine = LayoutEngine::new();
292        let mut widget = TestWidget::new(100.0, 50.0);
293        let viewport = Size::new(800.0, 600.0);
294
295        let tree = engine.compute(&mut widget, viewport);
296
297        assert_eq!(tree.widget_count(), 1);
298        assert!(tree.get_size(0).is_some());
299    }
300
301    #[test]
302    fn test_layout_widget_with_children() {
303        let mut engine = LayoutEngine::new();
304        let mut widget = TestWidget::new(200.0, 100.0)
305            .with_child(TestWidget::new(50.0, 50.0))
306            .with_child(TestWidget::new(50.0, 50.0));
307        let viewport = Size::new(800.0, 600.0);
308
309        let tree = engine.compute(&mut widget, viewport);
310
311        // Root + 2 children = 3 widgets
312        assert_eq!(tree.widget_count(), 3);
313    }
314
315    #[test]
316    fn test_layout_nested_children() {
317        let mut engine = LayoutEngine::new();
318        let mut widget = TestWidget::new(300.0, 200.0)
319            .with_child(TestWidget::new(100.0, 100.0).with_child(TestWidget::new(30.0, 30.0)));
320        let viewport = Size::new(800.0, 600.0);
321
322        let tree = engine.compute(&mut widget, viewport);
323
324        // Root + child + grandchild = 3 widgets
325        assert_eq!(tree.widget_count(), 3);
326    }
327
328    #[test]
329    fn test_layout_readonly() {
330        let mut engine = LayoutEngine::new();
331        let widget = TestWidget::new(100.0, 50.0);
332        let viewport = Size::new(800.0, 600.0);
333
334        let tree = engine.compute_readonly(&widget, viewport);
335
336        assert_eq!(tree.widget_count(), 1);
337        assert_eq!(tree.get_size(0), Some(Size::new(100.0, 50.0)));
338    }
339
340    #[test]
341    fn test_layout_cache_clear() {
342        let mut engine = LayoutEngine::new();
343        engine.clear_cache();
344        let (hits, misses) = engine.cache_stats();
345        assert_eq!(hits, 0);
346        assert_eq!(misses, 0);
347    }
348
349    #[test]
350    fn test_layout_viewport_constraint() {
351        let mut engine = LayoutEngine::new();
352        let mut widget = TestWidget::new(1000.0, 1000.0); // Larger than viewport
353        let viewport = Size::new(400.0, 300.0);
354
355        let tree = engine.compute(&mut widget, viewport);
356
357        // Size should be constrained to viewport
358        let size = tree.get_size(0).unwrap();
359        assert!(size.width <= viewport.width);
360        assert!(size.height <= viewport.height);
361    }
362
363    #[test]
364    fn test_layout_position_at_origin() {
365        let mut engine = LayoutEngine::new();
366        let mut widget = TestWidget::new(100.0, 50.0);
367        let viewport = Size::new(800.0, 600.0);
368
369        let tree = engine.compute(&mut widget, viewport);
370
371        let pos = tree.get_position(0).unwrap();
372        assert_eq!(pos.x, 0.0);
373        assert_eq!(pos.y, 0.0);
374    }
375
376    // =========================================================================
377    // LayoutTree Tests
378    // =========================================================================
379
380    #[test]
381    fn test_layout_tree_widget_count() {
382        let mut tree = LayoutTree::default();
383        assert_eq!(tree.widget_count(), 0);
384
385        tree.positions.insert(0, Rect::default());
386        tree.positions.insert(1, Rect::default());
387        tree.positions.insert(2, Rect::default());
388
389        assert_eq!(tree.widget_count(), 3);
390    }
391
392    #[test]
393    fn test_layout_tree_sizes_and_positions() {
394        let mut tree = LayoutTree::default();
395
396        tree.sizes.insert(0, Size::new(100.0, 50.0));
397        tree.positions.insert(0, Rect::new(10.0, 20.0, 100.0, 50.0));
398
399        tree.sizes.insert(1, Size::new(200.0, 100.0));
400        tree.positions
401            .insert(1, Rect::new(120.0, 20.0, 200.0, 100.0));
402
403        assert_eq!(tree.sizes.len(), 2);
404        assert_eq!(tree.positions.len(), 2);
405    }
406
407    #[test]
408    fn test_layout_tree_debug() {
409        let tree = LayoutTree::default();
410        let debug = format!("{:?}", tree);
411        assert!(debug.contains("LayoutTree"));
412    }
413
414    // =========================================================================
415    // LayoutEngine Default Tests
416    // =========================================================================
417
418    #[test]
419    fn test_layout_engine_default() {
420        let engine = LayoutEngine::default();
421        assert_eq!(engine.next_id, 0);
422    }
423
424    #[test]
425    fn test_layout_engine_debug() {
426        let engine = LayoutEngine::new();
427        let debug = format!("{:?}", engine);
428        assert!(debug.contains("LayoutEngine"));
429    }
430
431    // =========================================================================
432    // Multiple Compute Calls
433    // =========================================================================
434
435    #[test]
436    fn test_layout_multiple_computes() {
437        let mut engine = LayoutEngine::new();
438        let viewport = Size::new(800.0, 600.0);
439
440        // First compute
441        let mut widget1 = TestWidget::new(100.0, 50.0);
442        let tree1 = engine.compute(&mut widget1, viewport);
443        assert_eq!(tree1.widget_count(), 1);
444
445        // Second compute (should reset IDs)
446        let mut widget2 = TestWidget::new(200.0, 100.0);
447        let tree2 = engine.compute(&mut widget2, viewport);
448        assert_eq!(tree2.widget_count(), 1);
449    }
450
451    #[test]
452    fn test_layout_cache_cleared_on_compute() {
453        let mut engine = LayoutEngine::new();
454        let viewport = Size::new(800.0, 600.0);
455
456        let mut widget = TestWidget::new(100.0, 50.0);
457        engine.compute(&mut widget, viewport);
458
459        let (hits, misses) = engine.cache_stats();
460        assert_eq!(hits, 0);
461        assert_eq!(misses, 0);
462    }
463
464    // =========================================================================
465    // Viewport Size Variations
466    // =========================================================================
467
468    #[test]
469    fn test_layout_zero_viewport() {
470        let mut engine = LayoutEngine::new();
471        let mut widget = TestWidget::new(100.0, 50.0);
472        let viewport = Size::new(0.0, 0.0);
473
474        let tree = engine.compute(&mut widget, viewport);
475        assert_eq!(tree.widget_count(), 1);
476    }
477
478    #[test]
479    fn test_layout_very_large_viewport() {
480        let mut engine = LayoutEngine::new();
481        let mut widget = TestWidget::new(100.0, 50.0);
482        let viewport = Size::new(10000.0, 10000.0);
483
484        let tree = engine.compute(&mut widget, viewport);
485        let size = tree.get_size(0).unwrap();
486
487        // Widget should keep its intrinsic size within loose constraints
488        assert!(size.width <= 100.0);
489        assert!(size.height <= 50.0);
490    }
491
492    #[test]
493    fn test_layout_square_viewport() {
494        let mut engine = LayoutEngine::new();
495        let mut widget = TestWidget::new(100.0, 100.0);
496        let viewport = Size::new(500.0, 500.0);
497
498        let tree = engine.compute(&mut widget, viewport);
499        assert_eq!(tree.widget_count(), 1);
500    }
501
502    // =========================================================================
503    // Complex Widget Trees
504    // =========================================================================
505
506    #[test]
507    fn test_layout_deeply_nested() {
508        let mut engine = LayoutEngine::new();
509        let viewport = Size::new(800.0, 600.0);
510
511        let mut widget =
512            TestWidget::new(100.0, 100.0).with_child(TestWidget::new(80.0, 80.0).with_child(
513                TestWidget::new(60.0, 60.0).with_child(
514                    TestWidget::new(40.0, 40.0).with_child(TestWidget::new(20.0, 20.0)),
515                ),
516            ));
517
518        let tree = engine.compute(&mut widget, viewport);
519        assert_eq!(tree.widget_count(), 5); // 5 levels of nesting
520    }
521
522    #[test]
523    fn test_layout_wide_tree() {
524        let mut engine = LayoutEngine::new();
525        let viewport = Size::new(800.0, 600.0);
526
527        let mut widget = TestWidget::new(200.0, 100.0)
528            .with_child(TestWidget::new(30.0, 30.0))
529            .with_child(TestWidget::new(30.0, 30.0))
530            .with_child(TestWidget::new(30.0, 30.0))
531            .with_child(TestWidget::new(30.0, 30.0))
532            .with_child(TestWidget::new(30.0, 30.0));
533
534        let tree = engine.compute(&mut widget, viewport);
535        assert_eq!(tree.widget_count(), 6); // Root + 5 children
536    }
537
538    #[test]
539    fn test_layout_mixed_tree() {
540        let mut engine = LayoutEngine::new();
541        let viewport = Size::new(800.0, 600.0);
542
543        let mut widget = TestWidget::new(300.0, 200.0)
544            .with_child(
545                TestWidget::new(100.0, 100.0)
546                    .with_child(TestWidget::new(30.0, 30.0))
547                    .with_child(TestWidget::new(30.0, 30.0)),
548            )
549            .with_child(TestWidget::new(100.0, 100.0))
550            .with_child(TestWidget::new(100.0, 100.0).with_child(TestWidget::new(30.0, 30.0)));
551
552        let tree = engine.compute(&mut widget, viewport);
553        // Root + 3 children + 2 grandchildren + 1 grandchild = 7
554        assert_eq!(tree.widget_count(), 7);
555    }
556
557    // =========================================================================
558    // Read-only Compute Tests
559    // =========================================================================
560
561    #[test]
562    fn test_layout_readonly_with_children() {
563        let mut engine = LayoutEngine::new();
564        let viewport = Size::new(800.0, 600.0);
565
566        let widget = TestWidget::new(200.0, 100.0)
567            .with_child(TestWidget::new(50.0, 50.0))
568            .with_child(TestWidget::new(50.0, 50.0));
569
570        let tree = engine.compute_readonly(&widget, viewport);
571        assert_eq!(tree.widget_count(), 3);
572    }
573
574    #[test]
575    fn test_layout_readonly_nested() {
576        let mut engine = LayoutEngine::new();
577        let viewport = Size::new(800.0, 600.0);
578
579        let widget = TestWidget::new(100.0, 100.0)
580            .with_child(TestWidget::new(80.0, 80.0).with_child(TestWidget::new(60.0, 60.0)));
581
582        let tree = engine.compute_readonly(&widget, viewport);
583        assert_eq!(tree.widget_count(), 3);
584    }
585
586    // =========================================================================
587    // Cache Stats Tests
588    // =========================================================================
589
590    #[test]
591    fn test_cache_stats_initial() {
592        let engine = LayoutEngine::new();
593        let (hits, misses) = engine.cache_stats();
594        assert_eq!(hits, 0);
595        assert_eq!(misses, 0);
596    }
597
598    #[test]
599    fn test_cache_stats_after_clear() {
600        let mut engine = LayoutEngine::new();
601        engine.clear_cache();
602        let (hits, misses) = engine.cache_stats();
603        assert_eq!(hits, 0);
604        assert_eq!(misses, 0);
605    }
606
607    // =========================================================================
608    // Edge Cases
609    // =========================================================================
610
611    #[test]
612    fn test_layout_widget_larger_than_viewport() {
613        let mut engine = LayoutEngine::new();
614        let mut widget = TestWidget::new(2000.0, 1500.0);
615        let viewport = Size::new(800.0, 600.0);
616
617        let tree = engine.compute(&mut widget, viewport);
618        let size = tree.get_size(0).unwrap();
619
620        // Should be constrained
621        assert!(size.width <= viewport.width);
622        assert!(size.height <= viewport.height);
623    }
624
625    #[test]
626    fn test_layout_widget_fractional_size() {
627        let mut engine = LayoutEngine::new();
628        let mut widget = TestWidget::new(100.5, 50.25);
629        let viewport = Size::new(800.0, 600.0);
630
631        let tree = engine.compute(&mut widget, viewport);
632        assert_eq!(tree.widget_count(), 1);
633    }
634}