Skip to main content

jugar_probar/brick/
widget.rs

1//! Widget Integration: Verify-Measure-Layout-Paint (PROBAR-SPEC-009-P12)
2//!
3//! This module unifies the Brick and Widget concepts so that every widget
4//! IS a brick, ensuring all UI components have verifiable assertions.
5
6// Allow missing docs for geometry primitives - fields are self-explanatory
7#![allow(missing_docs)]
8//!
9//! # Widget Lifecycle
10//!
11//! ```text
12//! ┌─────────────────────────────────────────────────────────────┐
13//! │                    WIDGET LIFECYCLE                          │
14//! ├─────────────────────────────────────────────────────────────┤
15//! │                                                              │
16//! │  1. VERIFY (Brick)                                          │
17//! │     ├── Check all assertions                                │
18//! │     ├── Validate budget constraints                         │
19//! │     └── Return BrickVerification                            │
20//! │              │                                               │
21//! │              ▼ (only if valid)                              │
22//! │  2. MEASURE (Widget)                                        │
23//! │     ├── Compute intrinsic size                              │
24//! │     └── Return Size                                         │
25//! │              │                                               │
26//! │              ▼                                               │
27//! │  3. LAYOUT (Widget)                                         │
28//! │     ├── Position self within bounds                         │
29//! │     ├── Layout children recursively                         │
30//! │     └── Return LayoutResult                                 │
31//! │              │                                               │
32//! │              ▼                                               │
33//! │  4. PAINT (Widget)                                          │
34//! │     ├── Generate DrawCommands                               │
35//! │     ├── Record to Canvas                                    │
36//! │     └── Batch for GPU rendering                             │
37//! │                                                              │
38//! └─────────────────────────────────────────────────────────────┘
39//! ```
40//!
41//! # Jidoka Pattern
42//!
43//! The verify step implements Jidoka (stop-the-line): if any assertion
44//! fails, rendering is blocked. This prevents invalid UI states from
45//! ever being displayed.
46//!
47//! # References
48//!
49//! - PROBAR-SPEC-009-P12: Widget Integration - Presentar Unification
50
51use std::any::Any;
52use std::time::Duration;
53
54use super::{Brick, BrickBudget};
55
56/// 2D point coordinate for widget rendering
57#[derive(Debug, Clone, Copy, Default, PartialEq)]
58pub struct WidgetPoint {
59    /// X coordinate
60    pub x: f32,
61    /// Y coordinate
62    pub y: f32,
63}
64
65impl WidgetPoint {
66    /// Create a new point
67    #[must_use]
68    pub const fn new(x: f32, y: f32) -> Self {
69        Self { x, y }
70    }
71
72    /// Origin point (0, 0)
73    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
74}
75
76/// 2D size (width, height)
77#[derive(Debug, Clone, Copy, Default, PartialEq)]
78pub struct Size {
79    /// Width in pixels
80    pub width: f32,
81    /// Height in pixels
82    pub height: f32,
83}
84
85impl Size {
86    /// Create a new size
87    #[must_use]
88    pub const fn new(width: f32, height: f32) -> Self {
89        Self { width, height }
90    }
91
92    /// Zero size
93    pub const ZERO: Self = Self {
94        width: 0.0,
95        height: 0.0,
96    };
97
98    /// Check if size has positive area
99    #[must_use]
100    pub fn has_area(&self) -> bool {
101        self.width > 0.0 && self.height > 0.0
102    }
103}
104
105/// Rectangle with position and size
106#[derive(Debug, Clone, Copy, Default, PartialEq)]
107pub struct Rect {
108    pub x: f32,
109    pub y: f32,
110    pub width: f32,
111    pub height: f32,
112}
113
114impl Rect {
115    /// Create a new rectangle
116    #[must_use]
117    pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
118        Self {
119            x,
120            y,
121            width,
122            height,
123        }
124    }
125
126    /// Create rectangle from origin and size
127    #[must_use]
128    pub const fn from_size(size: Size) -> Self {
129        Self {
130            x: 0.0,
131            y: 0.0,
132            width: size.width,
133            height: size.height,
134        }
135    }
136
137    /// Get the size of this rectangle
138    #[must_use]
139    pub const fn size(&self) -> Size {
140        Size {
141            width: self.width,
142            height: self.height,
143        }
144    }
145
146    /// Get the origin point
147    #[must_use]
148    pub const fn origin(&self) -> WidgetPoint {
149        WidgetPoint {
150            x: self.x,
151            y: self.y,
152        }
153    }
154
155    /// Check if point is inside rectangle
156    #[must_use]
157    pub fn contains(&self, point: WidgetPoint) -> bool {
158        point.x >= self.x
159            && point.x < self.x + self.width
160            && point.y >= self.y
161            && point.y < self.y + self.height
162    }
163
164    /// Convert to array [x, y, width, height]
165    #[must_use]
166    pub const fn to_array(&self) -> [f32; 4] {
167        [self.x, self.y, self.width, self.height]
168    }
169}
170
171/// WidgetColor with RGBA components for widget rendering
172#[derive(Debug, Clone, Copy, Default, PartialEq)]
173pub struct WidgetColor {
174    pub r: f32,
175    pub g: f32,
176    pub b: f32,
177    pub a: f32,
178}
179
180impl WidgetColor {
181    /// Create a new color
182    #[must_use]
183    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
184        Self { r, g, b, a }
185    }
186
187    /// Create from RGB with full opacity
188    #[must_use]
189    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
190        Self { r, g, b, a: 1.0 }
191    }
192
193    /// Pure white
194    pub const WHITE: Self = Self {
195        r: 1.0,
196        g: 1.0,
197        b: 1.0,
198        a: 1.0,
199    };
200
201    /// Pure black
202    pub const BLACK: Self = Self {
203        r: 0.0,
204        g: 0.0,
205        b: 0.0,
206        a: 1.0,
207    };
208
209    /// Transparent
210    pub const TRANSPARENT: Self = Self {
211        r: 0.0,
212        g: 0.0,
213        b: 0.0,
214        a: 0.0,
215    };
216
217    /// Convert to array [r, g, b, a]
218    #[must_use]
219    pub const fn to_array(&self) -> [f32; 4] {
220        [self.r, self.g, self.b, self.a]
221    }
222
223    /// Create from hex (0xRRGGBB)
224    #[must_use]
225    pub fn from_hex(hex: u32) -> Self {
226        let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
227        let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
228        let b = (hex & 0xFF) as f32 / 255.0;
229        Self::rgb(r, g, b)
230    }
231}
232
233/// Corner radius for rounded rectangles
234#[derive(Debug, Clone, Copy, Default, PartialEq)]
235pub struct CornerRadius {
236    pub top_left: f32,
237    pub top_right: f32,
238    pub bottom_left: f32,
239    pub bottom_right: f32,
240}
241
242impl CornerRadius {
243    /// Create uniform corner radius
244    #[must_use]
245    pub const fn uniform(radius: f32) -> Self {
246        Self {
247            top_left: radius,
248            top_right: radius,
249            bottom_left: radius,
250            bottom_right: radius,
251        }
252    }
253
254    /// No rounding
255    pub const ZERO: Self = Self::uniform(0.0);
256}
257
258/// Text styling options
259#[derive(Debug, Clone, Default)]
260pub struct TextStyle {
261    /// Font family
262    pub font_family: String,
263    /// Font size in pixels
264    pub font_size: f32,
265    /// Font weight (100-900)
266    pub font_weight: u16,
267    /// Text color
268    pub color: WidgetColor,
269    /// Line height multiplier
270    pub line_height: f32,
271}
272
273impl TextStyle {
274    /// Create a basic text style
275    #[must_use]
276    pub fn new(font_size: f32, color: WidgetColor) -> Self {
277        Self {
278            font_family: "sans-serif".into(),
279            font_size,
280            font_weight: 400,
281            color,
282            line_height: 1.2,
283        }
284    }
285}
286
287/// Stroke styling for paths and shapes
288#[derive(Debug, Clone, Default)]
289pub struct StrokeStyle {
290    /// Stroke color
291    pub color: WidgetColor,
292    /// Stroke width
293    pub width: f32,
294    /// Line cap style
295    pub line_cap: LineCap,
296    /// Line join style
297    pub line_join: LineJoin,
298}
299
300/// Line cap style
301#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
302pub enum LineCap {
303    /// Flat line end
304    #[default]
305    Butt,
306    /// Rounded line end
307    Round,
308    /// Square line end
309    Square,
310}
311
312/// Line join style
313#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
314pub enum LineJoin {
315    /// Mitered corner
316    #[default]
317    Miter,
318    /// Rounded corner
319    Round,
320    /// Beveled corner
321    Bevel,
322}
323
324/// 2D transformation matrix
325#[derive(Debug, Clone, Copy, PartialEq)]
326pub struct Transform2D {
327    /// Matrix components [a, b, c, d, e, f]
328    pub matrix: [f32; 6],
329}
330
331impl Default for Transform2D {
332    fn default() -> Self {
333        Self::identity()
334    }
335}
336
337impl Transform2D {
338    /// Identity transform
339    #[must_use]
340    pub const fn identity() -> Self {
341        Self {
342            matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
343        }
344    }
345
346    /// Translation transform
347    #[must_use]
348    pub fn translate(x: f32, y: f32) -> Self {
349        Self {
350            matrix: [1.0, 0.0, 0.0, 1.0, x, y],
351        }
352    }
353
354    /// Scale transform
355    #[must_use]
356    pub fn scale(sx: f32, sy: f32) -> Self {
357        Self {
358            matrix: [sx, 0.0, 0.0, sy, 0.0, 0.0],
359        }
360    }
361
362    /// Rotation transform (radians)
363    #[must_use]
364    pub fn rotate(angle: f32) -> Self {
365        let c = angle.cos();
366        let s = angle.sin();
367        Self {
368            matrix: [c, s, -s, c, 0.0, 0.0],
369        }
370    }
371}
372
373/// Draw command for GPU batching
374///
375/// All paint operations become commands that can be batched
376/// for efficient GPU rendering.
377#[derive(Debug, Clone)]
378pub enum DrawCommand {
379    /// Draw a rectangle
380    Rect {
381        bounds: Rect,
382        color: WidgetColor,
383        radius: CornerRadius,
384    },
385    /// Draw a circle
386    Circle {
387        center: WidgetPoint,
388        radius: f32,
389        color: WidgetColor,
390    },
391    /// Draw text
392    Text {
393        content: String,
394        position: WidgetPoint,
395        style: TextStyle,
396    },
397    /// Draw a path
398    Path {
399        points: Vec<WidgetPoint>,
400        style: StrokeStyle,
401        closed: bool,
402    },
403    /// Draw an image/tensor
404    Image { data: Vec<u8>, bounds: Rect },
405    /// Group of commands with transform
406    Group {
407        children: Vec<DrawCommand>,
408        transform: Transform2D,
409    },
410    /// Fill rect with gradient
411    Gradient {
412        bounds: Rect,
413        start_color: WidgetColor,
414        end_color: WidgetColor,
415        angle: f32,
416    },
417    /// Clear a region
418    Clear { bounds: Rect, color: WidgetColor },
419}
420
421/// GPU instance for batched rendering
422#[derive(Debug, Clone, Default)]
423pub struct GpuInstance {
424    /// Bounds [x, y, width, height]
425    pub bounds: [f32; 4],
426    /// WidgetColor [r, g, b, a]
427    pub color: [f32; 4],
428    /// Shape type (0=rect, 1=circle, 2=text, etc.)
429    pub shape_type: u32,
430    /// Corner radius for rects
431    pub corner_radius: f32,
432    /// Additional parameters
433    pub params: [f32; 4],
434}
435
436/// Convert draw commands to GPU instances for batched rendering
437#[must_use]
438pub fn commands_to_gpu_instances(commands: &[DrawCommand]) -> Vec<GpuInstance> {
439    let mut instances = Vec::with_capacity(commands.len());
440
441    for cmd in commands {
442        match cmd {
443            DrawCommand::Rect {
444                bounds,
445                color,
446                radius,
447            } => {
448                instances.push(GpuInstance {
449                    bounds: bounds.to_array(),
450                    color: color.to_array(),
451                    shape_type: 0,
452                    corner_radius: radius.top_left,
453                    params: [0.0; 4],
454                });
455            }
456            DrawCommand::Circle {
457                center,
458                radius,
459                color,
460            } => {
461                instances.push(GpuInstance {
462                    bounds: [
463                        center.x - radius,
464                        center.y - radius,
465                        radius * 2.0,
466                        radius * 2.0,
467                    ],
468                    color: color.to_array(),
469                    shape_type: 1,
470                    corner_radius: *radius,
471                    params: [0.0; 4],
472                });
473            }
474            DrawCommand::Clear { bounds, color } => {
475                instances.push(GpuInstance {
476                    bounds: bounds.to_array(),
477                    color: color.to_array(),
478                    shape_type: 3,
479                    corner_radius: 0.0,
480                    params: [0.0; 4],
481                });
482            }
483            DrawCommand::Gradient {
484                bounds,
485                start_color,
486                end_color,
487                angle,
488            } => {
489                instances.push(GpuInstance {
490                    bounds: bounds.to_array(),
491                    color: start_color.to_array(),
492                    shape_type: 4,
493                    corner_radius: 0.0,
494                    params: [end_color.r, end_color.g, end_color.b, *angle],
495                });
496            }
497            // Text and Path require more complex handling
498            DrawCommand::Text { .. } | DrawCommand::Path { .. } | DrawCommand::Image { .. } => {
499                // These need separate render passes
500            }
501            DrawCommand::Group { children, .. } => {
502                // Recursively process children
503                instances.extend(commands_to_gpu_instances(children));
504            }
505        }
506    }
507
508    instances
509}
510
511/// Layout constraints from parent
512#[derive(Debug, Clone, Copy, Default)]
513pub struct Constraints {
514    /// Minimum width
515    pub min_width: f32,
516    /// Maximum width
517    pub max_width: f32,
518    /// Minimum height
519    pub min_height: f32,
520    /// Maximum height
521    pub max_height: f32,
522}
523
524impl Constraints {
525    /// Create unbounded constraints
526    #[must_use]
527    pub fn unbounded() -> Self {
528        Self {
529            min_width: 0.0,
530            max_width: f32::INFINITY,
531            min_height: 0.0,
532            max_height: f32::INFINITY,
533        }
534    }
535
536    /// Create tight constraints (exact size)
537    #[must_use]
538    pub fn tight(size: Size) -> Self {
539        Self {
540            min_width: size.width,
541            max_width: size.width,
542            min_height: size.height,
543            max_height: size.height,
544        }
545    }
546
547    /// Create loose constraints (max size)
548    #[must_use]
549    pub fn loose(size: Size) -> Self {
550        Self {
551            min_width: 0.0,
552            max_width: size.width,
553            min_height: 0.0,
554            max_height: size.height,
555        }
556    }
557
558    /// Constrain a size to these constraints
559    #[must_use]
560    pub fn constrain(&self, size: Size) -> Size {
561        Size {
562            width: size.width.clamp(self.min_width, self.max_width),
563            height: size.height.clamp(self.min_height, self.max_height),
564        }
565    }
566
567    /// Check if size satisfies constraints
568    #[must_use]
569    pub fn is_satisfied_by(&self, size: Size) -> bool {
570        size.width >= self.min_width
571            && size.width <= self.max_width
572            && size.height >= self.min_height
573            && size.height <= self.max_height
574    }
575}
576
577/// Result of layout phase
578#[derive(Debug, Clone, Default)]
579pub struct LayoutResult {
580    /// Final bounds after layout
581    pub bounds: Rect,
582    /// Whether layout succeeded
583    pub success: bool,
584    /// Error message if failed
585    pub error: Option<String>,
586}
587
588impl LayoutResult {
589    /// Create a successful layout result
590    #[must_use]
591    pub fn success(bounds: Rect) -> Self {
592        Self {
593            bounds,
594            success: true,
595            error: None,
596        }
597    }
598
599    /// Create a failed layout result
600    #[must_use]
601    pub fn failure(error: impl Into<String>) -> Self {
602        Self {
603            bounds: Rect::default(),
604            success: false,
605            error: Some(error.into()),
606        }
607    }
608}
609
610/// UI event for widget interaction
611#[derive(Debug, Clone)]
612pub enum Event {
613    /// Mouse click
614    Click {
615        position: WidgetPoint,
616        button: WidgetMouseButton,
617    },
618    /// Mouse move
619    MouseMove { position: WidgetPoint },
620    /// Key press
621    KeyPress { key: String, modifiers: Modifiers },
622    /// Focus gained
623    Focus,
624    /// Focus lost
625    Blur,
626    /// Scroll
627    Scroll { delta_x: f32, delta_y: f32 },
628    /// Touch start
629    TouchStart { position: WidgetPoint, id: u32 },
630    /// Touch move
631    TouchMove { position: WidgetPoint, id: u32 },
632    /// Touch end
633    TouchEnd { id: u32 },
634}
635
636/// Mouse button for widget events
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub enum WidgetMouseButton {
639    Left,
640    Right,
641    Middle,
642}
643
644/// Keyboard modifiers
645#[derive(Debug, Clone, Copy, Default)]
646pub struct Modifiers {
647    /// Shift key pressed
648    pub shift: bool,
649    /// Control key pressed
650    pub ctrl: bool,
651    /// Alt/Option key pressed
652    pub alt: bool,
653    /// Meta/Command key pressed
654    pub meta: bool,
655}
656
657/// Canvas for recording draw commands
658pub trait Canvas: Send + Sync {
659    /// Record a draw command
660    fn draw(&mut self, command: DrawCommand);
661
662    /// Get all recorded commands
663    fn commands(&self) -> &[DrawCommand];
664
665    /// Clear all commands
666    fn clear(&mut self);
667
668    /// Create a sub-canvas with transform
669    fn with_transform(&self, transform: Transform2D) -> Box<dyn Canvas>;
670
671    /// Get canvas size
672    fn size(&self) -> Size;
673}
674
675/// Simple canvas implementation for recording commands
676#[derive(Debug)]
677pub struct RecordingCanvas {
678    commands: Vec<DrawCommand>,
679    size: Size,
680}
681
682impl RecordingCanvas {
683    /// Create a new recording canvas
684    #[must_use]
685    pub fn new(size: Size) -> Self {
686        Self {
687            commands: Vec::new(),
688            size,
689        }
690    }
691}
692
693impl Canvas for RecordingCanvas {
694    fn draw(&mut self, command: DrawCommand) {
695        self.commands.push(command);
696    }
697
698    fn commands(&self) -> &[DrawCommand] {
699        &self.commands
700    }
701
702    fn clear(&mut self) {
703        self.commands.clear();
704    }
705
706    fn with_transform(&self, transform: Transform2D) -> Box<dyn Canvas> {
707        Box::new(TransformedCanvas {
708            inner: RecordingCanvas::new(self.size),
709            transform,
710        })
711    }
712
713    fn size(&self) -> Size {
714        self.size
715    }
716}
717
718/// Canvas with applied transform
719struct TransformedCanvas {
720    inner: RecordingCanvas,
721    transform: Transform2D,
722}
723
724impl Canvas for TransformedCanvas {
725    fn draw(&mut self, command: DrawCommand) {
726        // Wrap command in group with transform
727        self.inner.draw(DrawCommand::Group {
728            children: vec![command],
729            transform: self.transform,
730        });
731    }
732
733    fn commands(&self) -> &[DrawCommand] {
734        self.inner.commands()
735    }
736
737    fn clear(&mut self) {
738        self.inner.clear();
739    }
740
741    fn with_transform(&self, transform: Transform2D) -> Box<dyn Canvas> {
742        // Compose transforms
743        let composed = Transform2D {
744            matrix: [
745                self.transform.matrix[0] * transform.matrix[0],
746                self.transform.matrix[1],
747                self.transform.matrix[2],
748                self.transform.matrix[3] * transform.matrix[3],
749                self.transform.matrix[4] + transform.matrix[4],
750                self.transform.matrix[5] + transform.matrix[5],
751            ],
752        };
753        Box::new(TransformedCanvas {
754            inner: RecordingCanvas::new(self.inner.size),
755            transform: composed,
756        })
757    }
758
759    fn size(&self) -> Size {
760        self.inner.size
761    }
762}
763
764/// Widget trait: Every widget must also be a Brick (PROBAR-SPEC-009)
765///
766/// This unifies UI components with verifiable assertions, ensuring
767/// that no widget can render without passing its assertions.
768pub trait Widget: Brick {
769    /// Step 1: Compute intrinsic size given constraints
770    fn measure(&self, constraints: Constraints) -> Size;
771
772    /// Step 2: Position self and children within bounds
773    fn layout(&mut self, bounds: Rect) -> LayoutResult;
774
775    /// Step 3: Generate draw commands (only called if verified!)
776    fn paint(&self, canvas: &mut dyn Canvas);
777
778    /// Handle interaction events
779    fn event(&mut self, event: &Event) -> Option<Box<dyn Any>>;
780
781    /// Get children widgets (for tree traversal)
782    fn children(&self) -> &[Box<dyn Widget>] {
783        &[]
784    }
785
786    /// Get mutable children
787    fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
788        &mut []
789    }
790}
791
792/// Extension trait for verified rendering
793pub trait WidgetExt: Widget {
794    /// Render widget with Jidoka verification
795    ///
796    /// Only paints if all assertions pass. This is the Jidoka
797    /// (stop-the-line) pattern for UI rendering.
798    fn render(&self, canvas: &mut dyn Canvas) {
799        let verification = self.verify();
800        if !verification.is_valid() {
801            // Jidoka: Stop the line - don't render invalid state
802            return;
803        }
804        self.paint(canvas);
805    }
806
807    /// Render with timing and return metrics
808    fn render_timed(&self, canvas: &mut dyn Canvas) -> RenderMetrics {
809        let start = std::time::Instant::now();
810
811        let verification = self.verify();
812        let verify_time = start.elapsed();
813
814        if !verification.is_valid() {
815            return RenderMetrics {
816                verify_time,
817                paint_time: Duration::ZERO,
818                total_time: verify_time,
819                valid: false,
820                command_count: 0,
821            };
822        }
823
824        let paint_start = std::time::Instant::now();
825        self.paint(canvas);
826        let paint_time = paint_start.elapsed();
827
828        RenderMetrics {
829            verify_time,
830            paint_time,
831            total_time: start.elapsed(),
832            valid: true,
833            command_count: canvas.commands().len(),
834        }
835    }
836
837    /// Full layout-paint cycle with verification
838    fn render_full(&mut self, bounds: Rect, canvas: &mut dyn Canvas) -> LayoutResult
839    where
840        Self: Sized,
841    {
842        // Verify first
843        let verification = self.verify();
844        if !verification.is_valid() {
845            return LayoutResult::failure("Brick verification failed");
846        }
847
848        // Layout
849        let layout_result = self.layout(bounds);
850        if !layout_result.success {
851            return layout_result;
852        }
853
854        // Paint
855        self.paint(canvas);
856
857        layout_result
858    }
859}
860
861impl<W: Widget> WidgetExt for W {}
862
863/// Metrics from rendering
864#[derive(Debug, Clone, Default)]
865pub struct RenderMetrics {
866    /// Time spent verifying
867    pub verify_time: Duration,
868    /// Time spent painting
869    pub paint_time: Duration,
870    /// Total render time
871    pub total_time: Duration,
872    /// Whether verification passed
873    pub valid: bool,
874    /// Number of draw commands generated
875    pub command_count: usize,
876}
877
878impl RenderMetrics {
879    /// Check if render was within budget
880    #[must_use]
881    pub fn within_budget(&self, budget: BrickBudget) -> bool {
882        self.total_time <= budget.as_duration()
883    }
884}
885
886#[cfg(test)]
887#[allow(clippy::unwrap_used, clippy::expect_used)]
888mod tests {
889    use super::*;
890    use crate::brick::{BrickAssertion, BrickVerification};
891
892    // ============================================================
893    // Test Widget Implementation
894    // ============================================================
895
896    /// Test widget implementation
897    struct TestWidget {
898        text: String,
899        size: Size,
900        assertions: Vec<BrickAssertion>,
901    }
902
903    impl TestWidget {
904        fn new(text: &str) -> Self {
905            Self {
906                text: text.to_string(),
907                size: Size::new(100.0, 50.0),
908                assertions: vec![
909                    BrickAssertion::TextVisible,
910                    BrickAssertion::ContrastRatio(4.5),
911                ],
912            }
913        }
914    }
915
916    impl Brick for TestWidget {
917        fn brick_name(&self) -> &'static str {
918            "TestWidget"
919        }
920
921        fn assertions(&self) -> &[BrickAssertion] {
922            &self.assertions
923        }
924
925        fn budget(&self) -> BrickBudget {
926            BrickBudget::uniform(16)
927        }
928
929        fn verify(&self) -> BrickVerification {
930            let mut passed = Vec::new();
931            let mut failed = Vec::new();
932
933            for assertion in &self.assertions {
934                match assertion {
935                    BrickAssertion::TextVisible => {
936                        if !self.text.is_empty() {
937                            passed.push(assertion.clone());
938                        } else {
939                            failed.push((assertion.clone(), "Empty text".into()));
940                        }
941                    }
942                    _ => passed.push(assertion.clone()),
943                }
944            }
945
946            BrickVerification {
947                passed,
948                failed,
949                verification_time: Duration::from_micros(50),
950            }
951        }
952
953        fn to_html(&self) -> String {
954            format!("<div class=\"widget\">{}</div>", self.text)
955        }
956
957        fn to_css(&self) -> String {
958            ".widget { display: flex; }".into()
959        }
960    }
961
962    impl Widget for TestWidget {
963        fn measure(&self, constraints: Constraints) -> Size {
964            constraints.constrain(self.size)
965        }
966
967        fn layout(&mut self, bounds: Rect) -> LayoutResult {
968            LayoutResult::success(bounds)
969        }
970
971        fn paint(&self, canvas: &mut dyn Canvas) {
972            canvas.draw(DrawCommand::Rect {
973                bounds: Rect::new(0.0, 0.0, self.size.width, self.size.height),
974                color: WidgetColor::WHITE,
975                radius: CornerRadius::ZERO,
976            });
977            canvas.draw(DrawCommand::Text {
978                content: self.text.clone(),
979                position: WidgetPoint::new(10.0, 25.0),
980                style: TextStyle::new(16.0, WidgetColor::BLACK),
981            });
982        }
983
984        fn event(&mut self, event: &Event) -> Option<Box<dyn Any>> {
985            match event {
986                Event::Click { .. } => Some(Box::new("clicked")),
987                _ => None,
988            }
989        }
990    }
991
992    // ============================================================
993    // WidgetPoint tests
994    // ============================================================
995
996    #[test]
997    fn test_point() {
998        let p = WidgetPoint::new(10.0, 20.0);
999        assert_eq!(p.x, 10.0);
1000        assert_eq!(p.y, 20.0);
1001        assert_eq!(WidgetPoint::ZERO, WidgetPoint::new(0.0, 0.0));
1002    }
1003
1004    #[test]
1005    fn test_point_default() {
1006        let p = WidgetPoint::default();
1007        assert_eq!(p.x, 0.0);
1008        assert_eq!(p.y, 0.0);
1009    }
1010
1011    #[test]
1012    fn test_point_debug_and_clone() {
1013        let p = WidgetPoint::new(1.0, 2.0);
1014        let cloned = p;
1015        assert!(format!("{:?}", cloned).contains("WidgetPoint"));
1016    }
1017
1018    #[test]
1019    fn test_point_equality() {
1020        let p1 = WidgetPoint::new(10.0, 20.0);
1021        let p2 = WidgetPoint::new(10.0, 20.0);
1022        let p3 = WidgetPoint::new(10.0, 30.0);
1023
1024        assert_eq!(p1, p2);
1025        assert_ne!(p1, p3);
1026    }
1027
1028    // ============================================================
1029    // Size tests
1030    // ============================================================
1031
1032    #[test]
1033    fn test_size() {
1034        let s = Size::new(100.0, 50.0);
1035        assert!(s.has_area());
1036        assert!(!Size::ZERO.has_area());
1037    }
1038
1039    #[test]
1040    fn test_size_default() {
1041        let s = Size::default();
1042        assert_eq!(s.width, 0.0);
1043        assert_eq!(s.height, 0.0);
1044    }
1045
1046    #[test]
1047    fn test_size_has_area_edge_cases() {
1048        assert!(!Size::new(0.0, 100.0).has_area());
1049        assert!(!Size::new(100.0, 0.0).has_area());
1050        assert!(!Size::new(-1.0, 100.0).has_area());
1051        assert!(Size::new(0.001, 0.001).has_area());
1052    }
1053
1054    #[test]
1055    fn test_size_debug_and_clone() {
1056        let s = Size::new(50.0, 100.0);
1057        let cloned = s;
1058        assert!(format!("{:?}", cloned).contains("Size"));
1059    }
1060
1061    #[test]
1062    fn test_size_equality() {
1063        assert_eq!(Size::new(10.0, 20.0), Size::new(10.0, 20.0));
1064        assert_ne!(Size::new(10.0, 20.0), Size::new(10.0, 30.0));
1065    }
1066
1067    // ============================================================
1068    // Rect tests
1069    // ============================================================
1070
1071    #[test]
1072    fn test_rect() {
1073        let r = Rect::new(10.0, 20.0, 100.0, 50.0);
1074        assert!(r.contains(WidgetPoint::new(50.0, 30.0)));
1075        assert!(!r.contains(WidgetPoint::new(5.0, 30.0)));
1076        assert_eq!(r.size(), Size::new(100.0, 50.0));
1077    }
1078
1079    #[test]
1080    fn test_rect_default() {
1081        let r = Rect::default();
1082        assert_eq!(r.x, 0.0);
1083        assert_eq!(r.y, 0.0);
1084        assert_eq!(r.width, 0.0);
1085        assert_eq!(r.height, 0.0);
1086    }
1087
1088    #[test]
1089    fn test_rect_from_size() {
1090        let r = Rect::from_size(Size::new(100.0, 50.0));
1091        assert_eq!(r.x, 0.0);
1092        assert_eq!(r.y, 0.0);
1093        assert_eq!(r.width, 100.0);
1094        assert_eq!(r.height, 50.0);
1095    }
1096
1097    #[test]
1098    fn test_rect_origin() {
1099        let r = Rect::new(10.0, 20.0, 100.0, 50.0);
1100        let origin = r.origin();
1101        assert_eq!(origin.x, 10.0);
1102        assert_eq!(origin.y, 20.0);
1103    }
1104
1105    #[test]
1106    fn test_rect_contains_edge_cases() {
1107        let r = Rect::new(0.0, 0.0, 100.0, 100.0);
1108
1109        // Inside
1110        assert!(r.contains(WidgetPoint::new(50.0, 50.0)));
1111
1112        // On edges (inclusive at start, exclusive at end)
1113        assert!(r.contains(WidgetPoint::new(0.0, 0.0)));
1114        assert!(r.contains(WidgetPoint::new(99.9, 99.9)));
1115        assert!(!r.contains(WidgetPoint::new(100.0, 50.0)));
1116        assert!(!r.contains(WidgetPoint::new(50.0, 100.0)));
1117
1118        // Outside
1119        assert!(!r.contains(WidgetPoint::new(-1.0, 50.0)));
1120        assert!(!r.contains(WidgetPoint::new(50.0, -1.0)));
1121    }
1122
1123    #[test]
1124    fn test_rect_to_array() {
1125        let r = Rect::new(10.0, 20.0, 100.0, 50.0);
1126        assert_eq!(r.to_array(), [10.0, 20.0, 100.0, 50.0]);
1127    }
1128
1129    #[test]
1130    fn test_rect_debug_and_clone() {
1131        let r = Rect::new(1.0, 2.0, 3.0, 4.0);
1132        let cloned = r;
1133        assert!(format!("{:?}", cloned).contains("Rect"));
1134    }
1135
1136    // ============================================================
1137    // WidgetColor tests
1138    // ============================================================
1139
1140    #[test]
1141    fn test_color() {
1142        let c = WidgetColor::from_hex(0xFF0000);
1143        assert!((c.r - 1.0).abs() < f32::EPSILON);
1144        assert!(c.g.abs() < f32::EPSILON);
1145        assert!(c.b.abs() < f32::EPSILON);
1146    }
1147
1148    #[test]
1149    fn test_color_new() {
1150        let c = WidgetColor::new(0.5, 0.6, 0.7, 0.8);
1151        assert!((c.r - 0.5).abs() < f32::EPSILON);
1152        assert!((c.g - 0.6).abs() < f32::EPSILON);
1153        assert!((c.b - 0.7).abs() < f32::EPSILON);
1154        assert!((c.a - 0.8).abs() < f32::EPSILON);
1155    }
1156
1157    #[test]
1158    fn test_color_rgb() {
1159        let c = WidgetColor::rgb(0.1, 0.2, 0.3);
1160        assert!((c.r - 0.1).abs() < f32::EPSILON);
1161        assert!((c.g - 0.2).abs() < f32::EPSILON);
1162        assert!((c.b - 0.3).abs() < f32::EPSILON);
1163        assert!((c.a - 1.0).abs() < f32::EPSILON);
1164    }
1165
1166    #[test]
1167    fn test_color_constants() {
1168        assert_eq!(WidgetColor::WHITE.r, 1.0);
1169        assert_eq!(WidgetColor::WHITE.g, 1.0);
1170        assert_eq!(WidgetColor::WHITE.b, 1.0);
1171        assert_eq!(WidgetColor::WHITE.a, 1.0);
1172
1173        assert_eq!(WidgetColor::BLACK.r, 0.0);
1174        assert_eq!(WidgetColor::BLACK.g, 0.0);
1175        assert_eq!(WidgetColor::BLACK.b, 0.0);
1176        assert_eq!(WidgetColor::BLACK.a, 1.0);
1177
1178        assert_eq!(WidgetColor::TRANSPARENT.a, 0.0);
1179    }
1180
1181    #[test]
1182    fn test_color_to_array() {
1183        let c = WidgetColor::new(0.1, 0.2, 0.3, 0.4);
1184        let arr = c.to_array();
1185        assert!((arr[0] - 0.1).abs() < f32::EPSILON);
1186        assert!((arr[1] - 0.2).abs() < f32::EPSILON);
1187        assert!((arr[2] - 0.3).abs() < f32::EPSILON);
1188        assert!((arr[3] - 0.4).abs() < f32::EPSILON);
1189    }
1190
1191    #[test]
1192    fn test_color_from_hex_all_colors() {
1193        // Red
1194        let red = WidgetColor::from_hex(0xFF0000);
1195        assert!((red.r - 1.0).abs() < f32::EPSILON);
1196
1197        // Green
1198        let green = WidgetColor::from_hex(0x00FF00);
1199        assert!((green.g - 1.0).abs() < f32::EPSILON);
1200
1201        // Blue
1202        let blue = WidgetColor::from_hex(0x0000FF);
1203        assert!((blue.b - 1.0).abs() < f32::EPSILON);
1204
1205        // Gray
1206        let gray = WidgetColor::from_hex(0x808080);
1207        assert!((gray.r - 0.5).abs() < 0.01);
1208    }
1209
1210    #[test]
1211    fn test_color_default() {
1212        let c = WidgetColor::default();
1213        assert_eq!(c.r, 0.0);
1214        assert_eq!(c.g, 0.0);
1215        assert_eq!(c.b, 0.0);
1216        assert_eq!(c.a, 0.0);
1217    }
1218
1219    // ============================================================
1220    // CornerRadius tests
1221    // ============================================================
1222
1223    #[test]
1224    fn test_corner_radius_uniform() {
1225        let r = CornerRadius::uniform(10.0);
1226        assert_eq!(r.top_left, 10.0);
1227        assert_eq!(r.top_right, 10.0);
1228        assert_eq!(r.bottom_left, 10.0);
1229        assert_eq!(r.bottom_right, 10.0);
1230    }
1231
1232    #[test]
1233    fn test_corner_radius_zero() {
1234        let r = CornerRadius::ZERO;
1235        assert_eq!(r.top_left, 0.0);
1236        assert_eq!(r.top_right, 0.0);
1237        assert_eq!(r.bottom_left, 0.0);
1238        assert_eq!(r.bottom_right, 0.0);
1239    }
1240
1241    #[test]
1242    fn test_corner_radius_default() {
1243        let r = CornerRadius::default();
1244        assert_eq!(r.top_left, 0.0);
1245    }
1246
1247    #[test]
1248    fn test_corner_radius_debug_and_clone() {
1249        let r = CornerRadius::uniform(5.0);
1250        let cloned = r;
1251        assert!(format!("{:?}", cloned).contains("CornerRadius"));
1252    }
1253
1254    // ============================================================
1255    // TextStyle tests
1256    // ============================================================
1257
1258    #[test]
1259    fn test_text_style() {
1260        let style = TextStyle::new(16.0, WidgetColor::BLACK);
1261        assert_eq!(style.font_size, 16.0);
1262        assert_eq!(style.font_family, "sans-serif");
1263    }
1264
1265    #[test]
1266    fn test_text_style_default() {
1267        let style = TextStyle::default();
1268        assert!(style.font_family.is_empty());
1269        assert_eq!(style.font_size, 0.0);
1270        assert_eq!(style.font_weight, 0);
1271    }
1272
1273    #[test]
1274    fn test_text_style_full() {
1275        let style = TextStyle::new(24.0, WidgetColor::WHITE);
1276        assert_eq!(style.font_size, 24.0);
1277        assert_eq!(style.font_weight, 400);
1278        assert!((style.line_height - 1.2).abs() < f32::EPSILON);
1279    }
1280
1281    #[test]
1282    fn test_text_style_debug_and_clone() {
1283        let style = TextStyle::new(12.0, WidgetColor::BLACK);
1284        let cloned = style;
1285        assert!(format!("{:?}", cloned).contains("TextStyle"));
1286    }
1287
1288    // ============================================================
1289    // StrokeStyle tests
1290    // ============================================================
1291
1292    #[test]
1293    fn test_stroke_style_defaults() {
1294        let style = StrokeStyle::default();
1295        assert!(matches!(style.line_cap, LineCap::Butt));
1296        assert!(matches!(style.line_join, LineJoin::Miter));
1297    }
1298
1299    #[test]
1300    fn test_stroke_style_debug_and_clone() {
1301        let style = StrokeStyle::default();
1302        let cloned = style;
1303        assert!(format!("{:?}", cloned).contains("StrokeStyle"));
1304    }
1305
1306    // ============================================================
1307    // LineCap and LineJoin tests
1308    // ============================================================
1309
1310    #[test]
1311    fn test_line_cap_variants() {
1312        let butt = LineCap::Butt;
1313        let round = LineCap::Round;
1314        let square = LineCap::Square;
1315
1316        assert_eq!(butt, LineCap::default());
1317        assert_ne!(round, square);
1318    }
1319
1320    #[test]
1321    fn test_line_join_variants() {
1322        let miter = LineJoin::Miter;
1323        let round = LineJoin::Round;
1324        let bevel = LineJoin::Bevel;
1325
1326        assert_eq!(miter, LineJoin::default());
1327        assert_ne!(round, bevel);
1328    }
1329
1330    // ============================================================
1331    // Transform2D tests
1332    // ============================================================
1333
1334    #[test]
1335    fn test_transform() {
1336        let t = Transform2D::translate(10.0, 20.0);
1337        assert_eq!(t.matrix[4], 10.0);
1338        assert_eq!(t.matrix[5], 20.0);
1339
1340        let s = Transform2D::scale(2.0, 3.0);
1341        assert_eq!(s.matrix[0], 2.0);
1342        assert_eq!(s.matrix[3], 3.0);
1343    }
1344
1345    #[test]
1346    fn test_transform_identity() {
1347        let t = Transform2D::identity();
1348        assert_eq!(t.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
1349    }
1350
1351    #[test]
1352    fn test_transform_default() {
1353        let t = Transform2D::default();
1354        assert_eq!(t.matrix, Transform2D::identity().matrix);
1355    }
1356
1357    #[test]
1358    fn test_transform_rotate() {
1359        use std::f32::consts::PI;
1360
1361        // 90 degree rotation
1362        let t = Transform2D::rotate(PI / 2.0);
1363        assert!((t.matrix[0]).abs() < 0.0001); // cos(90) = 0
1364        assert!((t.matrix[1] - 1.0).abs() < 0.0001); // sin(90) = 1
1365    }
1366
1367    #[test]
1368    fn test_transform_debug_and_clone() {
1369        let t = Transform2D::translate(1.0, 2.0);
1370        let cloned = t;
1371        assert!(format!("{:?}", cloned).contains("Transform2D"));
1372    }
1373
1374    #[test]
1375    fn test_transform_equality() {
1376        let t1 = Transform2D::translate(10.0, 20.0);
1377        let t2 = Transform2D::translate(10.0, 20.0);
1378        let t3 = Transform2D::scale(2.0, 2.0);
1379
1380        assert_eq!(t1, t2);
1381        assert_ne!(t1, t3);
1382    }
1383
1384    // ============================================================
1385    // DrawCommand tests
1386    // ============================================================
1387
1388    #[test]
1389    fn test_draw_command_rect() {
1390        let cmd = DrawCommand::Rect {
1391            bounds: Rect::new(0.0, 0.0, 100.0, 50.0),
1392            color: WidgetColor::WHITE,
1393            radius: CornerRadius::uniform(5.0),
1394        };
1395
1396        assert!(format!("{:?}", cmd).contains("Rect"));
1397    }
1398
1399    #[test]
1400    fn test_draw_command_circle() {
1401        let cmd = DrawCommand::Circle {
1402            center: WidgetPoint::new(50.0, 50.0),
1403            radius: 25.0,
1404            color: WidgetColor::BLACK,
1405        };
1406
1407        assert!(format!("{:?}", cmd).contains("Circle"));
1408    }
1409
1410    #[test]
1411    fn test_draw_command_text() {
1412        let cmd = DrawCommand::Text {
1413            content: "Hello".to_string(),
1414            position: WidgetPoint::new(10.0, 20.0),
1415            style: TextStyle::new(16.0, WidgetColor::BLACK),
1416        };
1417
1418        assert!(format!("{:?}", cmd).contains("Text"));
1419    }
1420
1421    #[test]
1422    fn test_draw_command_path() {
1423        let cmd = DrawCommand::Path {
1424            points: vec![WidgetPoint::new(0.0, 0.0), WidgetPoint::new(100.0, 100.0)],
1425            style: StrokeStyle::default(),
1426            closed: false,
1427        };
1428
1429        assert!(format!("{:?}", cmd).contains("Path"));
1430    }
1431
1432    #[test]
1433    fn test_draw_command_image() {
1434        let cmd = DrawCommand::Image {
1435            data: vec![0, 1, 2, 3],
1436            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1437        };
1438
1439        assert!(format!("{:?}", cmd).contains("Image"));
1440    }
1441
1442    #[test]
1443    fn test_draw_command_group() {
1444        let cmd = DrawCommand::Group {
1445            children: vec![DrawCommand::Clear {
1446                bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1447                color: WidgetColor::WHITE,
1448            }],
1449            transform: Transform2D::identity(),
1450        };
1451
1452        assert!(format!("{:?}", cmd).contains("Group"));
1453    }
1454
1455    #[test]
1456    fn test_draw_command_gradient() {
1457        let cmd = DrawCommand::Gradient {
1458            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1459            start_color: WidgetColor::WHITE,
1460            end_color: WidgetColor::BLACK,
1461            angle: 45.0,
1462        };
1463
1464        assert!(format!("{:?}", cmd).contains("Gradient"));
1465    }
1466
1467    #[test]
1468    fn test_draw_command_clear() {
1469        let cmd = DrawCommand::Clear {
1470            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1471            color: WidgetColor::TRANSPARENT,
1472        };
1473
1474        assert!(format!("{:?}", cmd).contains("Clear"));
1475    }
1476
1477    #[test]
1478    fn test_draw_command_clone() {
1479        let cmd = DrawCommand::Rect {
1480            bounds: Rect::new(0.0, 0.0, 100.0, 50.0),
1481            color: WidgetColor::WHITE,
1482            radius: CornerRadius::ZERO,
1483        };
1484
1485        let _cloned = cmd;
1486    }
1487
1488    // ============================================================
1489    // GpuInstance tests
1490    // ============================================================
1491
1492    #[test]
1493    fn test_gpu_instance_default() {
1494        let instance = GpuInstance::default();
1495        assert_eq!(instance.shape_type, 0);
1496        assert_eq!(instance.corner_radius, 0.0);
1497    }
1498
1499    #[test]
1500    fn test_gpu_instance_debug_and_clone() {
1501        let instance = GpuInstance {
1502            bounds: [0.0, 0.0, 100.0, 50.0],
1503            color: [1.0, 1.0, 1.0, 1.0],
1504            shape_type: 0,
1505            corner_radius: 5.0,
1506            params: [0.0; 4],
1507        };
1508
1509        let cloned = instance;
1510        assert!(format!("{:?}", cloned).contains("GpuInstance"));
1511    }
1512
1513    // ============================================================
1514    // commands_to_gpu_instances tests
1515    // ============================================================
1516
1517    #[test]
1518    fn test_gpu_instances() {
1519        let commands = vec![
1520            DrawCommand::Rect {
1521                bounds: Rect::new(0.0, 0.0, 100.0, 50.0),
1522                color: WidgetColor::WHITE,
1523                radius: CornerRadius::uniform(5.0),
1524            },
1525            DrawCommand::Circle {
1526                center: WidgetPoint::new(50.0, 50.0),
1527                radius: 25.0,
1528                color: WidgetColor::BLACK,
1529            },
1530        ];
1531
1532        let instances = commands_to_gpu_instances(&commands);
1533        assert_eq!(instances.len(), 2);
1534        assert_eq!(instances[0].shape_type, 0); // Rect
1535        assert_eq!(instances[1].shape_type, 1); // Circle
1536    }
1537
1538    #[test]
1539    fn test_gpu_instances_clear() {
1540        let commands = vec![DrawCommand::Clear {
1541            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1542            color: WidgetColor::WHITE,
1543        }];
1544
1545        let instances = commands_to_gpu_instances(&commands);
1546        assert_eq!(instances.len(), 1);
1547        assert_eq!(instances[0].shape_type, 3); // Clear
1548    }
1549
1550    #[test]
1551    fn test_gpu_instances_gradient() {
1552        let commands = vec![DrawCommand::Gradient {
1553            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1554            start_color: WidgetColor::WHITE,
1555            end_color: WidgetColor::BLACK,
1556            angle: 45.0,
1557        }];
1558
1559        let instances = commands_to_gpu_instances(&commands);
1560        assert_eq!(instances.len(), 1);
1561        assert_eq!(instances[0].shape_type, 4); // Gradient
1562    }
1563
1564    #[test]
1565    fn test_gpu_instances_group_recursive() {
1566        let commands = vec![DrawCommand::Group {
1567            children: vec![
1568                DrawCommand::Rect {
1569                    bounds: Rect::new(0.0, 0.0, 50.0, 50.0),
1570                    color: WidgetColor::WHITE,
1571                    radius: CornerRadius::ZERO,
1572                },
1573                DrawCommand::Circle {
1574                    center: WidgetPoint::new(25.0, 25.0),
1575                    radius: 10.0,
1576                    color: WidgetColor::BLACK,
1577                },
1578            ],
1579            transform: Transform2D::identity(),
1580        }];
1581
1582        let instances = commands_to_gpu_instances(&commands);
1583        assert_eq!(instances.len(), 2); // Children are flattened
1584    }
1585
1586    #[test]
1587    fn test_gpu_instances_skips_text_path_image() {
1588        let commands = vec![
1589            DrawCommand::Text {
1590                content: "Hello".to_string(),
1591                position: WidgetPoint::ZERO,
1592                style: TextStyle::default(),
1593            },
1594            DrawCommand::Path {
1595                points: vec![],
1596                style: StrokeStyle::default(),
1597                closed: false,
1598            },
1599            DrawCommand::Image {
1600                data: vec![],
1601                bounds: Rect::default(),
1602            },
1603        ];
1604
1605        let instances = commands_to_gpu_instances(&commands);
1606        assert_eq!(instances.len(), 0); // These need separate render passes
1607    }
1608
1609    // ============================================================
1610    // Constraints tests
1611    // ============================================================
1612
1613    #[test]
1614    fn test_constraints() {
1615        let constraints = Constraints::loose(Size::new(200.0, 100.0));
1616        let result = constraints.constrain(Size::new(300.0, 50.0));
1617        assert_eq!(result.width, 200.0);
1618        assert_eq!(result.height, 50.0);
1619    }
1620
1621    #[test]
1622    fn test_constraints_unbounded() {
1623        let c = Constraints::unbounded();
1624        assert_eq!(c.min_width, 0.0);
1625        assert_eq!(c.min_height, 0.0);
1626        assert_eq!(c.max_width, f32::INFINITY);
1627        assert_eq!(c.max_height, f32::INFINITY);
1628    }
1629
1630    #[test]
1631    fn test_constraints_tight() {
1632        let c = Constraints::tight(Size::new(100.0, 50.0));
1633        assert_eq!(c.min_width, 100.0);
1634        assert_eq!(c.max_width, 100.0);
1635        assert_eq!(c.min_height, 50.0);
1636        assert_eq!(c.max_height, 50.0);
1637    }
1638
1639    #[test]
1640    fn test_constraints_loose() {
1641        let c = Constraints::loose(Size::new(100.0, 50.0));
1642        assert_eq!(c.min_width, 0.0);
1643        assert_eq!(c.max_width, 100.0);
1644        assert_eq!(c.min_height, 0.0);
1645        assert_eq!(c.max_height, 50.0);
1646    }
1647
1648    #[test]
1649    fn test_constraints_constrain() {
1650        let c = Constraints {
1651            min_width: 50.0,
1652            max_width: 150.0,
1653            min_height: 25.0,
1654            max_height: 75.0,
1655        };
1656
1657        // Below min
1658        let result = c.constrain(Size::new(10.0, 10.0));
1659        assert_eq!(result, Size::new(50.0, 25.0));
1660
1661        // Above max
1662        let result = c.constrain(Size::new(200.0, 200.0));
1663        assert_eq!(result, Size::new(150.0, 75.0));
1664
1665        // Within range
1666        let result = c.constrain(Size::new(100.0, 50.0));
1667        assert_eq!(result, Size::new(100.0, 50.0));
1668    }
1669
1670    #[test]
1671    fn test_constraints_is_satisfied_by() {
1672        let c = Constraints {
1673            min_width: 50.0,
1674            max_width: 150.0,
1675            min_height: 25.0,
1676            max_height: 75.0,
1677        };
1678
1679        assert!(c.is_satisfied_by(Size::new(100.0, 50.0)));
1680        assert!(c.is_satisfied_by(Size::new(50.0, 25.0)));
1681        assert!(c.is_satisfied_by(Size::new(150.0, 75.0)));
1682        assert!(!c.is_satisfied_by(Size::new(40.0, 50.0)));
1683        assert!(!c.is_satisfied_by(Size::new(100.0, 80.0)));
1684    }
1685
1686    #[test]
1687    fn test_constraints_default() {
1688        let c = Constraints::default();
1689        assert_eq!(c.min_width, 0.0);
1690        assert_eq!(c.min_height, 0.0);
1691        assert_eq!(c.max_width, 0.0);
1692        assert_eq!(c.max_height, 0.0);
1693    }
1694
1695    #[test]
1696    fn test_constraints_debug_and_clone() {
1697        let c = Constraints::loose(Size::new(100.0, 100.0));
1698        let cloned = c;
1699        assert!(format!("{:?}", cloned).contains("Constraints"));
1700    }
1701
1702    // ============================================================
1703    // LayoutResult tests
1704    // ============================================================
1705
1706    #[test]
1707    fn test_layout_result() {
1708        let success = LayoutResult::success(Rect::new(0.0, 0.0, 100.0, 50.0));
1709        assert!(success.success);
1710
1711        let failure = LayoutResult::failure("Test error");
1712        assert!(!failure.success);
1713        assert_eq!(failure.error, Some("Test error".to_string()));
1714    }
1715
1716    #[test]
1717    fn test_layout_result_default() {
1718        let r = LayoutResult::default();
1719        assert!(!r.success);
1720        assert!(r.error.is_none());
1721    }
1722
1723    #[test]
1724    fn test_layout_result_debug_and_clone() {
1725        let r = LayoutResult::success(Rect::default());
1726        let cloned = r;
1727        assert!(format!("{:?}", cloned).contains("LayoutResult"));
1728    }
1729
1730    // ============================================================
1731    // Event tests
1732    // ============================================================
1733
1734    #[test]
1735    fn test_event_click() {
1736        let event = Event::Click {
1737            position: WidgetPoint::new(10.0, 20.0),
1738            button: WidgetMouseButton::Left,
1739        };
1740
1741        assert!(format!("{:?}", event).contains("Click"));
1742    }
1743
1744    #[test]
1745    fn test_event_mouse_move() {
1746        let event = Event::MouseMove {
1747            position: WidgetPoint::new(50.0, 50.0),
1748        };
1749
1750        assert!(format!("{:?}", event).contains("MouseMove"));
1751    }
1752
1753    #[test]
1754    fn test_event_key_press() {
1755        let event = Event::KeyPress {
1756            key: "Enter".to_string(),
1757            modifiers: Modifiers {
1758                shift: true,
1759                ctrl: false,
1760                alt: false,
1761                meta: false,
1762            },
1763        };
1764
1765        assert!(format!("{:?}", event).contains("KeyPress"));
1766    }
1767
1768    #[test]
1769    fn test_event_focus_blur() {
1770        let focus = Event::Focus;
1771        let blur = Event::Blur;
1772
1773        assert!(format!("{:?}", focus).contains("Focus"));
1774        assert!(format!("{:?}", blur).contains("Blur"));
1775    }
1776
1777    #[test]
1778    fn test_event_scroll() {
1779        let event = Event::Scroll {
1780            delta_x: 10.0,
1781            delta_y: -20.0,
1782        };
1783
1784        assert!(format!("{:?}", event).contains("Scroll"));
1785    }
1786
1787    #[test]
1788    fn test_event_touch() {
1789        let start = Event::TouchStart {
1790            position: WidgetPoint::new(100.0, 200.0),
1791            id: 1,
1792        };
1793        let move_ev = Event::TouchMove {
1794            position: WidgetPoint::new(110.0, 210.0),
1795            id: 1,
1796        };
1797        let end = Event::TouchEnd { id: 1 };
1798
1799        assert!(format!("{:?}", start).contains("TouchStart"));
1800        assert!(format!("{:?}", move_ev).contains("TouchMove"));
1801        assert!(format!("{:?}", end).contains("TouchEnd"));
1802    }
1803
1804    #[test]
1805    fn test_event_clone() {
1806        let event = Event::Click {
1807            position: WidgetPoint::ZERO,
1808            button: WidgetMouseButton::Right,
1809        };
1810
1811        let _cloned = event;
1812    }
1813
1814    // ============================================================
1815    // WidgetMouseButton tests
1816    // ============================================================
1817
1818    #[test]
1819    fn test_mouse_button() {
1820        assert_eq!(WidgetMouseButton::Left, WidgetMouseButton::Left);
1821        assert_ne!(WidgetMouseButton::Left, WidgetMouseButton::Right);
1822        assert_ne!(WidgetMouseButton::Right, WidgetMouseButton::Middle);
1823    }
1824
1825    #[test]
1826    fn test_mouse_button_debug_and_clone() {
1827        let btn = WidgetMouseButton::Middle;
1828        let cloned = btn;
1829        assert!(format!("{:?}", cloned).contains("Middle"));
1830    }
1831
1832    // ============================================================
1833    // Modifiers tests
1834    // ============================================================
1835
1836    #[test]
1837    fn test_modifiers_default() {
1838        let m = Modifiers::default();
1839        assert!(!m.shift);
1840        assert!(!m.ctrl);
1841        assert!(!m.alt);
1842        assert!(!m.meta);
1843    }
1844
1845    #[test]
1846    fn test_modifiers_debug_and_clone() {
1847        let m = Modifiers {
1848            shift: true,
1849            ctrl: true,
1850            alt: false,
1851            meta: true,
1852        };
1853        let cloned = m;
1854        assert!(format!("{:?}", cloned).contains("Modifiers"));
1855    }
1856
1857    // ============================================================
1858    // RecordingCanvas tests
1859    // ============================================================
1860
1861    #[test]
1862    fn test_recording_canvas_new() {
1863        let canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1864        assert_eq!(canvas.size(), Size::new(800.0, 600.0));
1865        assert!(canvas.commands().is_empty());
1866    }
1867
1868    #[test]
1869    fn test_canvas_clear() {
1870        let mut canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1871        canvas.draw(DrawCommand::Clear {
1872            bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
1873            color: WidgetColor::WHITE,
1874        });
1875        assert_eq!(canvas.commands().len(), 1);
1876        canvas.clear();
1877        assert_eq!(canvas.commands().len(), 0);
1878    }
1879
1880    #[test]
1881    fn test_canvas_draw_multiple() {
1882        let mut canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1883
1884        canvas.draw(DrawCommand::Rect {
1885            bounds: Rect::new(0.0, 0.0, 50.0, 50.0),
1886            color: WidgetColor::WHITE,
1887            radius: CornerRadius::ZERO,
1888        });
1889        canvas.draw(DrawCommand::Circle {
1890            center: WidgetPoint::new(75.0, 75.0),
1891            radius: 20.0,
1892            color: WidgetColor::BLACK,
1893        });
1894
1895        assert_eq!(canvas.commands().len(), 2);
1896    }
1897
1898    #[test]
1899    fn test_canvas_with_transform() {
1900        let canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1901        let mut transformed = canvas.with_transform(Transform2D::translate(10.0, 20.0));
1902
1903        transformed.draw(DrawCommand::Rect {
1904            bounds: Rect::new(0.0, 0.0, 50.0, 50.0),
1905            color: WidgetColor::WHITE,
1906            radius: CornerRadius::ZERO,
1907        });
1908
1909        assert_eq!(transformed.commands().len(), 1);
1910        // The command should be wrapped in a Group
1911        if let DrawCommand::Group { transform, .. } = &transformed.commands()[0] {
1912            assert_eq!(transform.matrix[4], 10.0);
1913            assert_eq!(transform.matrix[5], 20.0);
1914        } else {
1915            panic!("Expected Group command");
1916        }
1917    }
1918
1919    #[test]
1920    fn test_transformed_canvas_with_nested_transform() {
1921        let canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1922        let transformed = canvas.with_transform(Transform2D::translate(10.0, 10.0));
1923        let nested = transformed.with_transform(Transform2D::translate(5.0, 5.0));
1924
1925        assert_eq!(nested.size(), Size::new(100.0, 100.0));
1926    }
1927
1928    #[test]
1929    fn test_transformed_canvas_clear() {
1930        let canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1931        let mut transformed = canvas.with_transform(Transform2D::identity());
1932
1933        transformed.draw(DrawCommand::Rect {
1934            bounds: Rect::default(),
1935            color: WidgetColor::WHITE,
1936            radius: CornerRadius::ZERO,
1937        });
1938        transformed.clear();
1939
1940        assert_eq!(transformed.commands().len(), 0);
1941    }
1942
1943    #[test]
1944    fn test_recording_canvas_debug() {
1945        let canvas = RecordingCanvas::new(Size::new(100.0, 100.0));
1946        assert!(format!("{:?}", canvas).contains("RecordingCanvas"));
1947    }
1948
1949    // ============================================================
1950    // Widget trait tests
1951    // ============================================================
1952
1953    #[test]
1954    fn test_widget_render() {
1955        let widget = TestWidget::new("Hello");
1956        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1957
1958        widget.render(&mut canvas);
1959
1960        assert_eq!(canvas.commands().len(), 2);
1961    }
1962
1963    #[test]
1964    fn test_widget_render_invalid() {
1965        let widget = TestWidget::new(""); // Empty text = invalid
1966        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1967
1968        widget.render(&mut canvas);
1969
1970        // Should not paint due to failed verification
1971        assert_eq!(canvas.commands().len(), 0);
1972    }
1973
1974    #[test]
1975    fn test_widget_render_timed() {
1976        let widget = TestWidget::new("Hello");
1977        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1978
1979        let metrics = widget.render_timed(&mut canvas);
1980
1981        assert!(metrics.valid);
1982        assert!(metrics.total_time >= Duration::ZERO);
1983        assert_eq!(metrics.command_count, 2);
1984    }
1985
1986    #[test]
1987    fn test_widget_render_timed_invalid() {
1988        let widget = TestWidget::new("");
1989        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1990
1991        let metrics = widget.render_timed(&mut canvas);
1992
1993        assert!(!metrics.valid);
1994        assert_eq!(metrics.command_count, 0);
1995        assert_eq!(metrics.paint_time, Duration::ZERO);
1996    }
1997
1998    #[test]
1999    fn test_widget_render_full() {
2000        let mut widget = TestWidget::new("Hello");
2001        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
2002
2003        let result = widget.render_full(Rect::new(0.0, 0.0, 100.0, 50.0), &mut canvas);
2004
2005        assert!(result.success);
2006        assert_eq!(canvas.commands().len(), 2);
2007    }
2008
2009    #[test]
2010    fn test_widget_render_full_invalid() {
2011        let mut widget = TestWidget::new("");
2012        let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
2013
2014        let result = widget.render_full(Rect::new(0.0, 0.0, 100.0, 50.0), &mut canvas);
2015
2016        assert!(!result.success);
2017        assert_eq!(result.error, Some("Brick verification failed".to_string()));
2018    }
2019
2020    #[test]
2021    fn test_widget_event() {
2022        let mut widget = TestWidget::new("Hello");
2023
2024        let result = widget.event(&Event::Click {
2025            position: WidgetPoint::new(50.0, 25.0),
2026            button: WidgetMouseButton::Left,
2027        });
2028
2029        assert!(result.is_some());
2030    }
2031
2032    #[test]
2033    fn test_widget_event_unhandled() {
2034        let mut widget = TestWidget::new("Hello");
2035
2036        let result = widget.event(&Event::Focus);
2037        assert!(result.is_none());
2038
2039        let result = widget.event(&Event::Blur);
2040        assert!(result.is_none());
2041
2042        let result = widget.event(&Event::Scroll {
2043            delta_x: 0.0,
2044            delta_y: 10.0,
2045        });
2046        assert!(result.is_none());
2047    }
2048
2049    #[test]
2050    fn test_widget_measure() {
2051        let widget = TestWidget::new("Hello");
2052        let size = widget.measure(Constraints::unbounded());
2053        assert_eq!(size, Size::new(100.0, 50.0));
2054    }
2055
2056    #[test]
2057    fn test_widget_measure_constrained() {
2058        let widget = TestWidget::new("Hello");
2059        let size = widget.measure(Constraints::tight(Size::new(50.0, 25.0)));
2060        assert_eq!(size, Size::new(50.0, 25.0));
2061    }
2062
2063    #[test]
2064    fn test_widget_layout() {
2065        let mut widget = TestWidget::new("Hello");
2066        let result = widget.layout(Rect::new(10.0, 20.0, 100.0, 50.0));
2067
2068        assert!(result.success);
2069        assert_eq!(result.bounds, Rect::new(10.0, 20.0, 100.0, 50.0));
2070    }
2071
2072    #[test]
2073    fn test_widget_children_default() {
2074        let widget = TestWidget::new("Hello");
2075        assert!(widget.children().is_empty());
2076    }
2077
2078    #[test]
2079    fn test_widget_children_mut_default() {
2080        let mut widget = TestWidget::new("Hello");
2081        assert!(widget.children_mut().is_empty());
2082    }
2083
2084    // ============================================================
2085    // RenderMetrics tests
2086    // ============================================================
2087
2088    #[test]
2089    fn test_render_metrics_budget() {
2090        let metrics = RenderMetrics {
2091            verify_time: Duration::from_millis(1),
2092            paint_time: Duration::from_millis(5),
2093            total_time: Duration::from_millis(6),
2094            valid: true,
2095            command_count: 10,
2096        };
2097
2098        assert!(metrics.within_budget(BrickBudget::uniform(16)));
2099        assert!(!metrics.within_budget(BrickBudget::uniform(5)));
2100    }
2101
2102    #[test]
2103    fn test_render_metrics_default() {
2104        let metrics = RenderMetrics::default();
2105        assert!(!metrics.valid);
2106        assert_eq!(metrics.command_count, 0);
2107        assert_eq!(metrics.total_time, Duration::ZERO);
2108    }
2109
2110    #[test]
2111    fn test_render_metrics_debug_and_clone() {
2112        let metrics = RenderMetrics {
2113            verify_time: Duration::from_millis(1),
2114            paint_time: Duration::from_millis(2),
2115            total_time: Duration::from_millis(3),
2116            valid: true,
2117            command_count: 5,
2118        };
2119
2120        let cloned = metrics;
2121        assert!(format!("{:?}", cloned).contains("RenderMetrics"));
2122    }
2123}