presentar_widgets/
stack.rs

1//! Stack widget for z-axis overlapping children.
2
3use presentar_core::{
4    widget::{Brick, BrickAssertion, BrickBudget, BrickVerification, LayoutResult},
5    Canvas, Constraints, Event, Rect, Size, TypeId, Widget,
6};
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9use std::time::Duration;
10
11/// How to align children within the stack.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
13pub enum StackAlignment {
14    /// Align to top-left corner
15    #[default]
16    TopLeft,
17    /// Align to top center
18    TopCenter,
19    /// Align to top-right corner
20    TopRight,
21    /// Align to center-left
22    CenterLeft,
23    /// Center both axes
24    Center,
25    /// Align to center-right
26    CenterRight,
27    /// Align to bottom-left corner
28    BottomLeft,
29    /// Align to bottom center
30    BottomCenter,
31    /// Align to bottom-right corner
32    BottomRight,
33}
34
35impl StackAlignment {
36    /// Get horizontal offset ratio (0.0 = left, 0.5 = center, 1.0 = right).
37    #[must_use]
38    pub const fn horizontal_ratio(&self) -> f32 {
39        match self {
40            Self::TopLeft | Self::CenterLeft | Self::BottomLeft => 0.0,
41            Self::TopCenter | Self::Center | Self::BottomCenter => 0.5,
42            Self::TopRight | Self::CenterRight | Self::BottomRight => 1.0,
43        }
44    }
45
46    /// Get vertical offset ratio (0.0 = top, 0.5 = center, 1.0 = bottom).
47    #[must_use]
48    pub const fn vertical_ratio(&self) -> f32 {
49        match self {
50            Self::TopLeft | Self::TopCenter | Self::TopRight => 0.0,
51            Self::CenterLeft | Self::Center | Self::CenterRight => 0.5,
52            Self::BottomLeft | Self::BottomCenter | Self::BottomRight => 1.0,
53        }
54    }
55}
56
57/// How to size the stack.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
59pub enum StackFit {
60    /// Size to the largest child
61    #[default]
62    Loose,
63    /// Expand to fill available space
64    Expand,
65}
66
67/// Stack widget for overlaying children.
68///
69/// Children are painted in order, with later children on top.
70#[derive(Serialize, Deserialize)]
71pub struct Stack {
72    /// Alignment for non-positioned children
73    alignment: StackAlignment,
74    /// How to size the stack
75    fit: StackFit,
76    /// Children widgets
77    #[serde(skip)]
78    children: Vec<Box<dyn Widget>>,
79    /// Test ID
80    test_id_value: Option<String>,
81    /// Cached bounds
82    #[serde(skip)]
83    bounds: Rect,
84}
85
86impl Default for Stack {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl Stack {
93    /// Create a new empty stack.
94    #[must_use]
95    pub fn new() -> Self {
96        Self {
97            alignment: StackAlignment::TopLeft,
98            fit: StackFit::Loose,
99            children: Vec::new(),
100            test_id_value: None,
101            bounds: Rect::default(),
102        }
103    }
104
105    /// Set alignment.
106    #[must_use]
107    pub const fn alignment(mut self, alignment: StackAlignment) -> Self {
108        self.alignment = alignment;
109        self
110    }
111
112    /// Set fit mode.
113    #[must_use]
114    pub const fn fit(mut self, fit: StackFit) -> Self {
115        self.fit = fit;
116        self
117    }
118
119    /// Add a child widget.
120    pub fn child(mut self, widget: impl Widget + 'static) -> Self {
121        self.children.push(Box::new(widget));
122        self
123    }
124
125    /// Set test ID.
126    #[must_use]
127    pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
128        self.test_id_value = Some(id.into());
129        self
130    }
131
132    /// Get alignment.
133    #[must_use]
134    pub const fn get_alignment(&self) -> StackAlignment {
135        self.alignment
136    }
137
138    /// Get fit mode.
139    #[must_use]
140    pub const fn get_fit(&self) -> StackFit {
141        self.fit
142    }
143}
144
145impl Widget for Stack {
146    fn type_id(&self) -> TypeId {
147        TypeId::of::<Self>()
148    }
149
150    fn measure(&self, constraints: Constraints) -> Size {
151        if self.children.is_empty() {
152            return match self.fit {
153                StackFit::Loose => Size::ZERO,
154                StackFit::Expand => Size::new(constraints.max_width, constraints.max_height),
155            };
156        }
157
158        let mut max_width = 0.0f32;
159        let mut max_height = 0.0f32;
160
161        // Measure all children - size is the largest child
162        for child in &self.children {
163            let child_size = child.measure(constraints);
164            max_width = max_width.max(child_size.width);
165            max_height = max_height.max(child_size.height);
166        }
167
168        match self.fit {
169            StackFit::Loose => constraints.constrain(Size::new(max_width, max_height)),
170            StackFit::Expand => Size::new(constraints.max_width, constraints.max_height),
171        }
172    }
173
174    fn layout(&mut self, bounds: Rect) -> LayoutResult {
175        self.bounds = bounds;
176
177        // Layout all children with alignment
178        for child in &mut self.children {
179            let child_constraints = Constraints::loose(bounds.size());
180            let child_size = child.measure(child_constraints);
181
182            // Calculate position based on alignment
183            let x = (bounds.width - child_size.width)
184                .mul_add(self.alignment.horizontal_ratio(), bounds.x);
185            let y = (bounds.height - child_size.height)
186                .mul_add(self.alignment.vertical_ratio(), bounds.y);
187
188            let child_bounds = Rect::new(x, y, child_size.width, child_size.height);
189            child.layout(child_bounds);
190        }
191
192        LayoutResult {
193            size: bounds.size(),
194        }
195    }
196
197    fn paint(&self, canvas: &mut dyn Canvas) {
198        // Paint children in order (first = bottom, last = top)
199        for child in &self.children {
200            child.paint(canvas);
201        }
202    }
203
204    fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
205        // Process events in reverse order (top-most first)
206        for child in self.children.iter_mut().rev() {
207            if let Some(msg) = child.event(event) {
208                return Some(msg);
209            }
210        }
211        None
212    }
213
214    fn children(&self) -> &[Box<dyn Widget>] {
215        &self.children
216    }
217
218    fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
219        &mut self.children
220    }
221
222    fn test_id(&self) -> Option<&str> {
223        self.test_id_value.as_deref()
224    }
225}
226
227// PROBAR-SPEC-009: Brick Architecture - Tests define interface
228impl Brick for Stack {
229    fn brick_name(&self) -> &'static str {
230        "Stack"
231    }
232
233    fn assertions(&self) -> &[BrickAssertion] {
234        &[BrickAssertion::MaxLatencyMs(16)]
235    }
236
237    fn budget(&self) -> BrickBudget {
238        BrickBudget::uniform(16)
239    }
240
241    fn verify(&self) -> BrickVerification {
242        BrickVerification {
243            passed: self.assertions().to_vec(),
244            failed: vec![],
245            verification_time: Duration::from_micros(10),
246        }
247    }
248
249    fn to_html(&self) -> String {
250        r#"<div class="brick-stack"></div>"#.to_string()
251    }
252
253    fn to_css(&self) -> String {
254        ".brick-stack { display: block; position: relative; }".to_string()
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use presentar_core::Widget;
262
263    // =========================================================================
264    // StackAlignment Tests - TESTS FIRST
265    // =========================================================================
266
267    #[test]
268    fn test_stack_alignment_default() {
269        assert_eq!(StackAlignment::default(), StackAlignment::TopLeft);
270    }
271
272    #[test]
273    fn test_stack_alignment_horizontal_ratio() {
274        // Left alignments
275        assert_eq!(StackAlignment::TopLeft.horizontal_ratio(), 0.0);
276        assert_eq!(StackAlignment::CenterLeft.horizontal_ratio(), 0.0);
277        assert_eq!(StackAlignment::BottomLeft.horizontal_ratio(), 0.0);
278
279        // Center alignments
280        assert_eq!(StackAlignment::TopCenter.horizontal_ratio(), 0.5);
281        assert_eq!(StackAlignment::Center.horizontal_ratio(), 0.5);
282        assert_eq!(StackAlignment::BottomCenter.horizontal_ratio(), 0.5);
283
284        // Right alignments
285        assert_eq!(StackAlignment::TopRight.horizontal_ratio(), 1.0);
286        assert_eq!(StackAlignment::CenterRight.horizontal_ratio(), 1.0);
287        assert_eq!(StackAlignment::BottomRight.horizontal_ratio(), 1.0);
288    }
289
290    #[test]
291    fn test_stack_alignment_vertical_ratio() {
292        // Top alignments
293        assert_eq!(StackAlignment::TopLeft.vertical_ratio(), 0.0);
294        assert_eq!(StackAlignment::TopCenter.vertical_ratio(), 0.0);
295        assert_eq!(StackAlignment::TopRight.vertical_ratio(), 0.0);
296
297        // Center alignments
298        assert_eq!(StackAlignment::CenterLeft.vertical_ratio(), 0.5);
299        assert_eq!(StackAlignment::Center.vertical_ratio(), 0.5);
300        assert_eq!(StackAlignment::CenterRight.vertical_ratio(), 0.5);
301
302        // Bottom alignments
303        assert_eq!(StackAlignment::BottomLeft.vertical_ratio(), 1.0);
304        assert_eq!(StackAlignment::BottomCenter.vertical_ratio(), 1.0);
305        assert_eq!(StackAlignment::BottomRight.vertical_ratio(), 1.0);
306    }
307
308    // =========================================================================
309    // StackFit Tests - TESTS FIRST
310    // =========================================================================
311
312    #[test]
313    fn test_stack_fit_default() {
314        assert_eq!(StackFit::default(), StackFit::Loose);
315    }
316
317    // =========================================================================
318    // Stack Construction Tests - TESTS FIRST
319    // =========================================================================
320
321    #[test]
322    fn test_stack_new() {
323        let stack = Stack::new();
324        assert_eq!(stack.get_alignment(), StackAlignment::TopLeft);
325        assert_eq!(stack.get_fit(), StackFit::Loose);
326        assert!(stack.children().is_empty());
327    }
328
329    #[test]
330    fn test_stack_default() {
331        let stack = Stack::default();
332        assert_eq!(stack.get_alignment(), StackAlignment::TopLeft);
333        assert_eq!(stack.get_fit(), StackFit::Loose);
334    }
335
336    #[test]
337    fn test_stack_builder() {
338        let stack = Stack::new()
339            .alignment(StackAlignment::Center)
340            .fit(StackFit::Expand)
341            .with_test_id("my-stack");
342
343        assert_eq!(stack.get_alignment(), StackAlignment::Center);
344        assert_eq!(stack.get_fit(), StackFit::Expand);
345        assert_eq!(Widget::test_id(&stack), Some("my-stack"));
346    }
347
348    // =========================================================================
349    // Stack Measure Tests - TESTS FIRST
350    // =========================================================================
351
352    #[test]
353    fn test_stack_empty_loose() {
354        let stack = Stack::new().fit(StackFit::Loose);
355        let size = stack.measure(Constraints::loose(Size::new(100.0, 100.0)));
356        assert_eq!(size, Size::ZERO);
357    }
358
359    #[test]
360    fn test_stack_empty_expand() {
361        let stack = Stack::new().fit(StackFit::Expand);
362        let size = stack.measure(Constraints::loose(Size::new(100.0, 100.0)));
363        assert_eq!(size, Size::new(100.0, 100.0));
364    }
365
366    // =========================================================================
367    // Stack Widget Trait Tests - TESTS FIRST
368    // =========================================================================
369
370    #[test]
371    fn test_stack_type_id() {
372        let stack = Stack::new();
373        let type_id = Widget::type_id(&stack);
374        assert_eq!(type_id, TypeId::of::<Stack>());
375    }
376
377    #[test]
378    fn test_stack_test_id_none() {
379        let stack = Stack::new();
380        assert_eq!(Widget::test_id(&stack), None);
381    }
382
383    #[test]
384    fn test_stack_test_id_some() {
385        let stack = Stack::new().with_test_id("test-stack");
386        assert_eq!(Widget::test_id(&stack), Some("test-stack"));
387    }
388
389    // =========================================================================
390    // StackAlignment Tests - TESTS FIRST
391    // =========================================================================
392
393    #[test]
394    fn test_stack_alignment_horizontal_ratios() {
395        assert_eq!(StackAlignment::TopLeft.horizontal_ratio(), 0.0);
396        assert_eq!(StackAlignment::TopCenter.horizontal_ratio(), 0.5);
397        assert_eq!(StackAlignment::TopRight.horizontal_ratio(), 1.0);
398        assert_eq!(StackAlignment::Center.horizontal_ratio(), 0.5);
399        assert_eq!(StackAlignment::BottomRight.horizontal_ratio(), 1.0);
400    }
401
402    #[test]
403    fn test_stack_alignment_vertical_ratios() {
404        assert_eq!(StackAlignment::TopLeft.vertical_ratio(), 0.0);
405        assert_eq!(StackAlignment::CenterLeft.vertical_ratio(), 0.5);
406        assert_eq!(StackAlignment::BottomLeft.vertical_ratio(), 1.0);
407        assert_eq!(StackAlignment::Center.vertical_ratio(), 0.5);
408        assert_eq!(StackAlignment::BottomRight.vertical_ratio(), 1.0);
409    }
410
411    #[test]
412    fn test_stack_alignment_default_is_top_left() {
413        let align = StackAlignment::default();
414        assert_eq!(align, StackAlignment::TopLeft);
415    }
416
417    #[test]
418    fn test_stack_fit_default_is_loose() {
419        let fit = StackFit::default();
420        assert_eq!(fit, StackFit::Loose);
421    }
422
423    #[test]
424    fn test_stack_layout_sets_bounds() {
425        let mut stack = Stack::new();
426        let result = stack.layout(Rect::new(10.0, 20.0, 100.0, 80.0));
427        assert_eq!(result.size, Size::new(100.0, 80.0));
428        assert_eq!(stack.bounds, Rect::new(10.0, 20.0, 100.0, 80.0));
429    }
430
431    #[test]
432    fn test_stack_children_empty() {
433        let stack = Stack::new();
434        assert!(stack.children().is_empty());
435    }
436
437    #[test]
438    fn test_stack_event_no_children() {
439        let mut stack = Stack::new();
440        stack.layout(Rect::new(0.0, 0.0, 100.0, 100.0));
441        let result = stack.event(&Event::MouseEnter);
442        assert!(result.is_none());
443    }
444
445    // =========================================================================
446    // Brick Trait Tests
447    // =========================================================================
448
449    #[test]
450    fn test_stack_brick_name() {
451        let stack = Stack::new();
452        assert_eq!(stack.brick_name(), "Stack");
453    }
454
455    #[test]
456    fn test_stack_brick_assertions() {
457        let stack = Stack::new();
458        let assertions = stack.assertions();
459        assert!(!assertions.is_empty());
460        assert!(matches!(assertions[0], BrickAssertion::MaxLatencyMs(16)));
461    }
462
463    #[test]
464    fn test_stack_brick_budget() {
465        let stack = Stack::new();
466        let budget = stack.budget();
467        // Verify budget has reasonable values
468        assert!(budget.layout_ms > 0);
469        assert!(budget.paint_ms > 0);
470    }
471
472    #[test]
473    fn test_stack_brick_verify() {
474        let stack = Stack::new();
475        let verification = stack.verify();
476        assert!(!verification.passed.is_empty());
477        assert!(verification.failed.is_empty());
478    }
479
480    #[test]
481    fn test_stack_brick_to_html() {
482        let stack = Stack::new();
483        let html = stack.to_html();
484        assert!(html.contains("brick-stack"));
485    }
486
487    #[test]
488    fn test_stack_brick_to_css() {
489        let stack = Stack::new();
490        let css = stack.to_css();
491        assert!(css.contains(".brick-stack"));
492        assert!(css.contains("display: block"));
493        assert!(css.contains("position: relative"));
494    }
495
496    // =========================================================================
497    // StackAlignment Comprehensive Tests
498    // =========================================================================
499
500    #[test]
501    fn test_stack_alignment_all_variants() {
502        let alignments = [
503            StackAlignment::TopLeft,
504            StackAlignment::TopCenter,
505            StackAlignment::TopRight,
506            StackAlignment::CenterLeft,
507            StackAlignment::Center,
508            StackAlignment::CenterRight,
509            StackAlignment::BottomLeft,
510            StackAlignment::BottomCenter,
511            StackAlignment::BottomRight,
512        ];
513        assert_eq!(alignments.len(), 9);
514    }
515
516    #[test]
517    fn test_stack_alignment_debug() {
518        let alignment = StackAlignment::Center;
519        let debug_str = format!("{:?}", alignment);
520        assert!(debug_str.contains("Center"));
521    }
522
523    #[test]
524    fn test_stack_alignment_eq() {
525        assert_eq!(StackAlignment::Center, StackAlignment::Center);
526        assert_ne!(StackAlignment::TopLeft, StackAlignment::BottomRight);
527    }
528
529    #[test]
530    fn test_stack_alignment_clone() {
531        let alignment = StackAlignment::BottomCenter;
532        let cloned = alignment;
533        assert_eq!(cloned, StackAlignment::BottomCenter);
534    }
535
536    // =========================================================================
537    // StackFit Tests
538    // =========================================================================
539
540    #[test]
541    fn test_stack_fit_eq() {
542        assert_eq!(StackFit::Loose, StackFit::Loose);
543        assert_ne!(StackFit::Loose, StackFit::Expand);
544    }
545
546    #[test]
547    fn test_stack_fit_debug() {
548        let fit = StackFit::Expand;
549        let debug_str = format!("{:?}", fit);
550        assert!(debug_str.contains("Expand"));
551    }
552
553    #[test]
554    fn test_stack_fit_clone() {
555        let fit = StackFit::Expand;
556        let cloned = fit;
557        assert_eq!(cloned, StackFit::Expand);
558    }
559
560    // =========================================================================
561    // Measure with Children (requires mock widget)
562    // =========================================================================
563
564    // Simple mock widget for testing
565    struct MockWidget {
566        size: Size,
567    }
568
569    impl Brick for MockWidget {
570        fn brick_name(&self) -> &'static str {
571            "MockWidget"
572        }
573
574        fn assertions(&self) -> &[BrickAssertion] {
575            &[]
576        }
577
578        fn budget(&self) -> BrickBudget {
579            BrickBudget::uniform(16)
580        }
581
582        fn verify(&self) -> BrickVerification {
583            BrickVerification {
584                passed: vec![],
585                failed: vec![],
586                verification_time: Duration::from_micros(1),
587            }
588        }
589
590        fn to_html(&self) -> String {
591            String::new()
592        }
593
594        fn to_css(&self) -> String {
595            String::new()
596        }
597    }
598
599    impl Widget for MockWidget {
600        fn type_id(&self) -> TypeId {
601            TypeId::of::<Self>()
602        }
603
604        fn measure(&self, _constraints: Constraints) -> Size {
605            self.size
606        }
607
608        fn layout(&mut self, _bounds: Rect) -> LayoutResult {
609            LayoutResult { size: self.size }
610        }
611
612        fn paint(&self, _canvas: &mut dyn Canvas) {}
613
614        fn event(&mut self, _event: &Event) -> Option<Box<dyn std::any::Any + Send>> {
615            None
616        }
617
618        fn children(&self) -> &[Box<dyn Widget>] {
619            &[]
620        }
621
622        fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
623            &mut []
624        }
625    }
626
627    #[test]
628    fn test_stack_measure_with_children_loose() {
629        let stack = Stack::new()
630            .fit(StackFit::Loose)
631            .child(MockWidget {
632                size: Size::new(50.0, 30.0),
633            })
634            .child(MockWidget {
635                size: Size::new(100.0, 60.0),
636            });
637
638        let size = stack.measure(Constraints::loose(Size::new(500.0, 500.0)));
639        // Should be the largest child
640        assert_eq!(size.width, 100.0);
641        assert_eq!(size.height, 60.0);
642    }
643
644    #[test]
645    fn test_stack_measure_with_children_expand() {
646        let stack = Stack::new().fit(StackFit::Expand).child(MockWidget {
647            size: Size::new(50.0, 30.0),
648        });
649
650        let size = stack.measure(Constraints::loose(Size::new(500.0, 400.0)));
651        // Should expand to fill available space
652        assert_eq!(size.width, 500.0);
653        assert_eq!(size.height, 400.0);
654    }
655
656    #[test]
657    fn test_stack_layout_with_children_top_left() {
658        let mut stack = Stack::new()
659            .alignment(StackAlignment::TopLeft)
660            .child(MockWidget {
661                size: Size::new(50.0, 30.0),
662            });
663
664        stack.layout(Rect::new(0.0, 0.0, 200.0, 150.0));
665
666        // Child should be at top-left corner
667        // (Verified through layout result, can't directly access child bounds)
668        assert_eq!(stack.bounds, Rect::new(0.0, 0.0, 200.0, 150.0));
669    }
670
671    #[test]
672    fn test_stack_layout_with_children_center() {
673        let mut stack = Stack::new()
674            .alignment(StackAlignment::Center)
675            .child(MockWidget {
676                size: Size::new(50.0, 30.0),
677            });
678
679        let result = stack.layout(Rect::new(0.0, 0.0, 200.0, 150.0));
680        assert_eq!(result.size, Size::new(200.0, 150.0));
681    }
682
683    #[test]
684    fn test_stack_layout_with_children_bottom_right() {
685        let mut stack = Stack::new()
686            .alignment(StackAlignment::BottomRight)
687            .child(MockWidget {
688                size: Size::new(50.0, 30.0),
689            });
690
691        let result = stack.layout(Rect::new(0.0, 0.0, 200.0, 150.0));
692        assert_eq!(result.size, Size::new(200.0, 150.0));
693    }
694
695    #[test]
696    fn test_stack_children_count() {
697        let stack = Stack::new()
698            .child(MockWidget {
699                size: Size::new(50.0, 30.0),
700            })
701            .child(MockWidget {
702                size: Size::new(100.0, 60.0),
703            })
704            .child(MockWidget {
705                size: Size::new(75.0, 45.0),
706            });
707
708        assert_eq!(stack.children().len(), 3);
709    }
710
711    #[test]
712    fn test_stack_children_mut_count() {
713        let mut stack = Stack::new()
714            .child(MockWidget {
715                size: Size::new(50.0, 30.0),
716            })
717            .child(MockWidget {
718                size: Size::new(100.0, 60.0),
719            });
720
721        assert_eq!(stack.children_mut().len(), 2);
722    }
723
724    // =========================================================================
725    // Test ID Tests
726    // =========================================================================
727
728    #[test]
729    fn test_stack_widget_test_id() {
730        let stack = Stack::new().with_test_id("stack-1");
731        assert_eq!(Brick::test_id(&stack), None); // Brick::test_id is different method
732    }
733
734    #[test]
735    fn test_stack_debug() {
736        let stack = Stack::new();
737        // Stack doesn't derive Debug, but we can test it compiles
738        let _ = stack;
739    }
740
741    // =========================================================================
742    // Default Implementation Tests
743    // =========================================================================
744
745    #[test]
746    fn test_stack_default_impl() {
747        let stack = Stack::default();
748        assert_eq!(stack.get_alignment(), StackAlignment::TopLeft);
749        assert_eq!(stack.get_fit(), StackFit::Loose);
750        assert!(stack.children().is_empty());
751    }
752
753    // =========================================================================
754    // Event Handling Tests
755    // =========================================================================
756
757    struct EventCapturingWidget {
758        size: Size,
759    }
760
761    impl Brick for EventCapturingWidget {
762        fn brick_name(&self) -> &'static str {
763            "EventCapturingWidget"
764        }
765
766        fn assertions(&self) -> &[BrickAssertion] {
767            &[]
768        }
769
770        fn budget(&self) -> BrickBudget {
771            BrickBudget::uniform(16)
772        }
773
774        fn verify(&self) -> BrickVerification {
775            BrickVerification {
776                passed: vec![],
777                failed: vec![],
778                verification_time: Duration::from_micros(1),
779            }
780        }
781
782        fn to_html(&self) -> String {
783            String::new()
784        }
785
786        fn to_css(&self) -> String {
787            String::new()
788        }
789    }
790
791    impl Widget for EventCapturingWidget {
792        fn type_id(&self) -> TypeId {
793            TypeId::of::<Self>()
794        }
795
796        fn measure(&self, _constraints: Constraints) -> Size {
797            self.size
798        }
799
800        fn layout(&mut self, _bounds: Rect) -> LayoutResult {
801            LayoutResult { size: self.size }
802        }
803
804        fn paint(&self, _canvas: &mut dyn Canvas) {}
805
806        fn event(&mut self, _event: &Event) -> Option<Box<dyn std::any::Any + Send>> {
807            // Return a message to indicate event was handled
808            Some(Box::new("handled".to_string()))
809        }
810
811        fn children(&self) -> &[Box<dyn Widget>] {
812            &[]
813        }
814
815        fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
816            &mut []
817        }
818    }
819
820    #[test]
821    fn test_stack_event_propagates_to_children() {
822        let mut stack = Stack::new().child(EventCapturingWidget {
823            size: Size::new(50.0, 30.0),
824        });
825
826        stack.layout(Rect::new(0.0, 0.0, 200.0, 150.0));
827        let result = stack.event(&Event::MouseEnter);
828
829        // Event should be captured by child
830        assert!(result.is_some());
831    }
832
833    #[test]
834    fn test_stack_event_reverse_order() {
835        // Events should be processed in reverse order (top-most child first)
836        let mut stack = Stack::new()
837            .child(MockWidget {
838                size: Size::new(50.0, 30.0),
839            })
840            .child(EventCapturingWidget {
841                size: Size::new(50.0, 30.0),
842            });
843
844        stack.layout(Rect::new(0.0, 0.0, 200.0, 150.0));
845        let result = stack.event(&Event::MouseEnter);
846
847        // Last child (EventCapturingWidget) should handle it first
848        assert!(result.is_some());
849    }
850
851    // =========================================================================
852    // Measure Edge Cases
853    // =========================================================================
854
855    #[test]
856    fn test_stack_measure_loose_with_constraints() {
857        let stack = Stack::new().fit(StackFit::Loose).child(MockWidget {
858            size: Size::new(500.0, 400.0),
859        });
860
861        // Constraint smaller than child
862        let size = stack.measure(Constraints {
863            min_width: 0.0,
864            min_height: 0.0,
865            max_width: 200.0,
866            max_height: 150.0,
867        });
868
869        // Should be constrained
870        assert_eq!(size.width, 200.0);
871        assert_eq!(size.height, 150.0);
872    }
873
874    #[test]
875    fn test_stack_measure_multiple_children_different_sizes() {
876        let stack = Stack::new()
877            .fit(StackFit::Loose)
878            .child(MockWidget {
879                size: Size::new(50.0, 100.0),
880            })
881            .child(MockWidget {
882                size: Size::new(100.0, 50.0),
883            })
884            .child(MockWidget {
885                size: Size::new(75.0, 75.0),
886            });
887
888        let size = stack.measure(Constraints::loose(Size::new(500.0, 500.0)));
889        // Should take maximum of each dimension
890        assert_eq!(size.width, 100.0);
891        assert_eq!(size.height, 100.0);
892    }
893}