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::{
200        Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Event, TypeId,
201    };
202    use std::any::Any;
203    use std::time::Duration;
204
205    // Test widget for layout testing
206    struct TestWidget {
207        size: Size,
208        children: Vec<Box<dyn Widget>>,
209    }
210
211    impl TestWidget {
212        fn new(width: f32, height: f32) -> Self {
213            Self {
214                size: Size::new(width, height),
215                children: Vec::new(),
216            }
217        }
218
219        fn with_child(mut self, child: TestWidget) -> Self {
220            self.children.push(Box::new(child));
221            self
222        }
223    }
224
225    impl Brick for TestWidget {
226        fn brick_name(&self) -> &'static str {
227            "TestWidget"
228        }
229
230        fn assertions(&self) -> &[BrickAssertion] {
231            &[]
232        }
233
234        fn budget(&self) -> BrickBudget {
235            BrickBudget::uniform(16)
236        }
237
238        fn verify(&self) -> BrickVerification {
239            BrickVerification {
240                passed: vec![],
241                failed: vec![],
242                verification_time: Duration::from_micros(1),
243            }
244        }
245
246        fn to_html(&self) -> String {
247            String::new()
248        }
249
250        fn to_css(&self) -> String {
251            String::new()
252        }
253    }
254
255    impl Widget for TestWidget {
256        fn type_id(&self) -> TypeId {
257            TypeId::of::<Self>()
258        }
259
260        fn measure(&self, constraints: Constraints) -> Size {
261            constraints.constrain(self.size)
262        }
263
264        fn layout(&mut self, bounds: Rect) -> LayoutResult {
265            LayoutResult {
266                size: bounds.size(),
267            }
268        }
269
270        fn paint(&self, _canvas: &mut dyn Canvas) {}
271
272        fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
273            None
274        }
275
276        fn children(&self) -> &[Box<dyn Widget>] {
277            &self.children
278        }
279
280        fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
281            &mut self.children
282        }
283
284        fn accessible_role(&self) -> AccessibleRole {
285            AccessibleRole::Generic
286        }
287    }
288
289    #[test]
290    fn test_layout_engine_new() {
291        let engine = LayoutEngine::new();
292        assert_eq!(engine.next_id, 0);
293    }
294
295    #[test]
296    fn test_layout_tree_default() {
297        let tree = LayoutTree::default();
298        assert!(tree.sizes.is_empty());
299        assert!(tree.positions.is_empty());
300        assert_eq!(tree.widget_count(), 0);
301    }
302
303    #[test]
304    fn test_layout_tree_get_size() {
305        let mut tree = LayoutTree::default();
306        tree.sizes.insert(0, Size::new(100.0, 50.0));
307        assert_eq!(tree.get_size(0), Some(Size::new(100.0, 50.0)));
308        assert_eq!(tree.get_size(1), None);
309    }
310
311    #[test]
312    fn test_layout_tree_get_position() {
313        let mut tree = LayoutTree::default();
314        tree.positions.insert(0, Rect::new(10.0, 20.0, 100.0, 50.0));
315        assert_eq!(
316            tree.get_position(0),
317            Some(Rect::new(10.0, 20.0, 100.0, 50.0))
318        );
319        assert_eq!(tree.get_position(1), None);
320    }
321
322    #[test]
323    fn test_layout_single_widget() {
324        let mut engine = LayoutEngine::new();
325        let mut widget = TestWidget::new(100.0, 50.0);
326        let viewport = Size::new(800.0, 600.0);
327
328        let tree = engine.compute(&mut widget, viewport);
329
330        assert_eq!(tree.widget_count(), 1);
331        assert!(tree.get_size(0).is_some());
332    }
333
334    #[test]
335    fn test_layout_widget_with_children() {
336        let mut engine = LayoutEngine::new();
337        let mut widget = TestWidget::new(200.0, 100.0)
338            .with_child(TestWidget::new(50.0, 50.0))
339            .with_child(TestWidget::new(50.0, 50.0));
340        let viewport = Size::new(800.0, 600.0);
341
342        let tree = engine.compute(&mut widget, viewport);
343
344        // Root + 2 children = 3 widgets
345        assert_eq!(tree.widget_count(), 3);
346    }
347
348    #[test]
349    fn test_layout_nested_children() {
350        let mut engine = LayoutEngine::new();
351        let mut widget = TestWidget::new(300.0, 200.0)
352            .with_child(TestWidget::new(100.0, 100.0).with_child(TestWidget::new(30.0, 30.0)));
353        let viewport = Size::new(800.0, 600.0);
354
355        let tree = engine.compute(&mut widget, viewport);
356
357        // Root + child + grandchild = 3 widgets
358        assert_eq!(tree.widget_count(), 3);
359    }
360
361    #[test]
362    fn test_layout_readonly() {
363        let mut engine = LayoutEngine::new();
364        let widget = TestWidget::new(100.0, 50.0);
365        let viewport = Size::new(800.0, 600.0);
366
367        let tree = engine.compute_readonly(&widget, viewport);
368
369        assert_eq!(tree.widget_count(), 1);
370        assert_eq!(tree.get_size(0), Some(Size::new(100.0, 50.0)));
371    }
372
373    #[test]
374    fn test_layout_cache_clear() {
375        let mut engine = LayoutEngine::new();
376        engine.clear_cache();
377        let (hits, misses) = engine.cache_stats();
378        assert_eq!(hits, 0);
379        assert_eq!(misses, 0);
380    }
381
382    #[test]
383    fn test_layout_viewport_constraint() {
384        let mut engine = LayoutEngine::new();
385        let mut widget = TestWidget::new(1000.0, 1000.0); // Larger than viewport
386        let viewport = Size::new(400.0, 300.0);
387
388        let tree = engine.compute(&mut widget, viewport);
389
390        // Size should be constrained to viewport
391        let size = tree.get_size(0).unwrap();
392        assert!(size.width <= viewport.width);
393        assert!(size.height <= viewport.height);
394    }
395
396    #[test]
397    fn test_layout_position_at_origin() {
398        let mut engine = LayoutEngine::new();
399        let mut widget = TestWidget::new(100.0, 50.0);
400        let viewport = Size::new(800.0, 600.0);
401
402        let tree = engine.compute(&mut widget, viewport);
403
404        let pos = tree.get_position(0).unwrap();
405        assert_eq!(pos.x, 0.0);
406        assert_eq!(pos.y, 0.0);
407    }
408
409    // =========================================================================
410    // LayoutTree Tests
411    // =========================================================================
412
413    #[test]
414    fn test_layout_tree_widget_count() {
415        let mut tree = LayoutTree::default();
416        assert_eq!(tree.widget_count(), 0);
417
418        tree.positions.insert(0, Rect::default());
419        tree.positions.insert(1, Rect::default());
420        tree.positions.insert(2, Rect::default());
421
422        assert_eq!(tree.widget_count(), 3);
423    }
424
425    #[test]
426    fn test_layout_tree_sizes_and_positions() {
427        let mut tree = LayoutTree::default();
428
429        tree.sizes.insert(0, Size::new(100.0, 50.0));
430        tree.positions.insert(0, Rect::new(10.0, 20.0, 100.0, 50.0));
431
432        tree.sizes.insert(1, Size::new(200.0, 100.0));
433        tree.positions
434            .insert(1, Rect::new(120.0, 20.0, 200.0, 100.0));
435
436        assert_eq!(tree.sizes.len(), 2);
437        assert_eq!(tree.positions.len(), 2);
438    }
439
440    #[test]
441    fn test_layout_tree_debug() {
442        let tree = LayoutTree::default();
443        let debug = format!("{:?}", tree);
444        assert!(debug.contains("LayoutTree"));
445    }
446
447    // =========================================================================
448    // LayoutEngine Default Tests
449    // =========================================================================
450
451    #[test]
452    fn test_layout_engine_default() {
453        let engine = LayoutEngine::default();
454        assert_eq!(engine.next_id, 0);
455    }
456
457    #[test]
458    fn test_layout_engine_debug() {
459        let engine = LayoutEngine::new();
460        let debug = format!("{:?}", engine);
461        assert!(debug.contains("LayoutEngine"));
462    }
463
464    // =========================================================================
465    // Multiple Compute Calls
466    // =========================================================================
467
468    #[test]
469    fn test_layout_multiple_computes() {
470        let mut engine = LayoutEngine::new();
471        let viewport = Size::new(800.0, 600.0);
472
473        // First compute
474        let mut widget1 = TestWidget::new(100.0, 50.0);
475        let tree1 = engine.compute(&mut widget1, viewport);
476        assert_eq!(tree1.widget_count(), 1);
477
478        // Second compute (should reset IDs)
479        let mut widget2 = TestWidget::new(200.0, 100.0);
480        let tree2 = engine.compute(&mut widget2, viewport);
481        assert_eq!(tree2.widget_count(), 1);
482    }
483
484    #[test]
485    fn test_layout_cache_cleared_on_compute() {
486        let mut engine = LayoutEngine::new();
487        let viewport = Size::new(800.0, 600.0);
488
489        let mut widget = TestWidget::new(100.0, 50.0);
490        engine.compute(&mut widget, viewport);
491
492        let (hits, misses) = engine.cache_stats();
493        assert_eq!(hits, 0);
494        assert_eq!(misses, 0);
495    }
496
497    // =========================================================================
498    // Viewport Size Variations
499    // =========================================================================
500
501    #[test]
502    fn test_layout_zero_viewport() {
503        let mut engine = LayoutEngine::new();
504        let mut widget = TestWidget::new(100.0, 50.0);
505        let viewport = Size::new(0.0, 0.0);
506
507        let tree = engine.compute(&mut widget, viewport);
508        assert_eq!(tree.widget_count(), 1);
509    }
510
511    #[test]
512    fn test_layout_very_large_viewport() {
513        let mut engine = LayoutEngine::new();
514        let mut widget = TestWidget::new(100.0, 50.0);
515        let viewport = Size::new(10000.0, 10000.0);
516
517        let tree = engine.compute(&mut widget, viewport);
518        let size = tree.get_size(0).unwrap();
519
520        // Widget should keep its intrinsic size within loose constraints
521        assert!(size.width <= 100.0);
522        assert!(size.height <= 50.0);
523    }
524
525    #[test]
526    fn test_layout_square_viewport() {
527        let mut engine = LayoutEngine::new();
528        let mut widget = TestWidget::new(100.0, 100.0);
529        let viewport = Size::new(500.0, 500.0);
530
531        let tree = engine.compute(&mut widget, viewport);
532        assert_eq!(tree.widget_count(), 1);
533    }
534
535    // =========================================================================
536    // Complex Widget Trees
537    // =========================================================================
538
539    #[test]
540    fn test_layout_deeply_nested() {
541        let mut engine = LayoutEngine::new();
542        let viewport = Size::new(800.0, 600.0);
543
544        let mut widget =
545            TestWidget::new(100.0, 100.0).with_child(TestWidget::new(80.0, 80.0).with_child(
546                TestWidget::new(60.0, 60.0).with_child(
547                    TestWidget::new(40.0, 40.0).with_child(TestWidget::new(20.0, 20.0)),
548                ),
549            ));
550
551        let tree = engine.compute(&mut widget, viewport);
552        assert_eq!(tree.widget_count(), 5); // 5 levels of nesting
553    }
554
555    #[test]
556    fn test_layout_wide_tree() {
557        let mut engine = LayoutEngine::new();
558        let viewport = Size::new(800.0, 600.0);
559
560        let mut widget = TestWidget::new(200.0, 100.0)
561            .with_child(TestWidget::new(30.0, 30.0))
562            .with_child(TestWidget::new(30.0, 30.0))
563            .with_child(TestWidget::new(30.0, 30.0))
564            .with_child(TestWidget::new(30.0, 30.0))
565            .with_child(TestWidget::new(30.0, 30.0));
566
567        let tree = engine.compute(&mut widget, viewport);
568        assert_eq!(tree.widget_count(), 6); // Root + 5 children
569    }
570
571    #[test]
572    fn test_layout_mixed_tree() {
573        let mut engine = LayoutEngine::new();
574        let viewport = Size::new(800.0, 600.0);
575
576        let mut widget = TestWidget::new(300.0, 200.0)
577            .with_child(
578                TestWidget::new(100.0, 100.0)
579                    .with_child(TestWidget::new(30.0, 30.0))
580                    .with_child(TestWidget::new(30.0, 30.0)),
581            )
582            .with_child(TestWidget::new(100.0, 100.0))
583            .with_child(TestWidget::new(100.0, 100.0).with_child(TestWidget::new(30.0, 30.0)));
584
585        let tree = engine.compute(&mut widget, viewport);
586        // Root + 3 children + 2 grandchildren + 1 grandchild = 7
587        assert_eq!(tree.widget_count(), 7);
588    }
589
590    // =========================================================================
591    // Read-only Compute Tests
592    // =========================================================================
593
594    #[test]
595    fn test_layout_readonly_with_children() {
596        let mut engine = LayoutEngine::new();
597        let viewport = Size::new(800.0, 600.0);
598
599        let widget = TestWidget::new(200.0, 100.0)
600            .with_child(TestWidget::new(50.0, 50.0))
601            .with_child(TestWidget::new(50.0, 50.0));
602
603        let tree = engine.compute_readonly(&widget, viewport);
604        assert_eq!(tree.widget_count(), 3);
605    }
606
607    #[test]
608    fn test_layout_readonly_nested() {
609        let mut engine = LayoutEngine::new();
610        let viewport = Size::new(800.0, 600.0);
611
612        let widget = TestWidget::new(100.0, 100.0)
613            .with_child(TestWidget::new(80.0, 80.0).with_child(TestWidget::new(60.0, 60.0)));
614
615        let tree = engine.compute_readonly(&widget, viewport);
616        assert_eq!(tree.widget_count(), 3);
617    }
618
619    // =========================================================================
620    // Cache Stats Tests
621    // =========================================================================
622
623    #[test]
624    fn test_cache_stats_initial() {
625        let engine = LayoutEngine::new();
626        let (hits, misses) = engine.cache_stats();
627        assert_eq!(hits, 0);
628        assert_eq!(misses, 0);
629    }
630
631    #[test]
632    fn test_cache_stats_after_clear() {
633        let mut engine = LayoutEngine::new();
634        engine.clear_cache();
635        let (hits, misses) = engine.cache_stats();
636        assert_eq!(hits, 0);
637        assert_eq!(misses, 0);
638    }
639
640    // =========================================================================
641    // Edge Cases
642    // =========================================================================
643
644    #[test]
645    fn test_layout_widget_larger_than_viewport() {
646        let mut engine = LayoutEngine::new();
647        let mut widget = TestWidget::new(2000.0, 1500.0);
648        let viewport = Size::new(800.0, 600.0);
649
650        let tree = engine.compute(&mut widget, viewport);
651        let size = tree.get_size(0).unwrap();
652
653        // Should be constrained
654        assert!(size.width <= viewport.width);
655        assert!(size.height <= viewport.height);
656    }
657
658    #[test]
659    fn test_layout_widget_fractional_size() {
660        let mut engine = LayoutEngine::new();
661        let mut widget = TestWidget::new(100.5, 50.25);
662        let viewport = Size::new(800.0, 600.0);
663
664        let tree = engine.compute(&mut widget, viewport);
665        assert_eq!(tree.widget_count(), 1);
666    }
667}