1#![allow(missing_docs)]
8use std::any::Any;
52use std::time::Duration;
53
54use super::{Brick, BrickBudget};
55
56#[derive(Debug, Clone, Copy, Default, PartialEq)]
58pub struct WidgetPoint {
59 pub x: f32,
61 pub y: f32,
63}
64
65impl WidgetPoint {
66 #[must_use]
68 pub const fn new(x: f32, y: f32) -> Self {
69 Self { x, y }
70 }
71
72 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
74}
75
76#[derive(Debug, Clone, Copy, Default, PartialEq)]
78pub struct Size {
79 pub width: f32,
81 pub height: f32,
83}
84
85impl Size {
86 #[must_use]
88 pub const fn new(width: f32, height: f32) -> Self {
89 Self { width, height }
90 }
91
92 pub const ZERO: Self = Self {
94 width: 0.0,
95 height: 0.0,
96 };
97
98 #[must_use]
100 pub fn has_area(&self) -> bool {
101 self.width > 0.0 && self.height > 0.0
102 }
103}
104
105#[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 #[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 #[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 #[must_use]
139 pub const fn size(&self) -> Size {
140 Size {
141 width: self.width,
142 height: self.height,
143 }
144 }
145
146 #[must_use]
148 pub const fn origin(&self) -> WidgetPoint {
149 WidgetPoint {
150 x: self.x,
151 y: self.y,
152 }
153 }
154
155 #[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 #[must_use]
166 pub const fn to_array(&self) -> [f32; 4] {
167 [self.x, self.y, self.width, self.height]
168 }
169}
170
171#[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 #[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 #[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 pub const WHITE: Self = Self {
195 r: 1.0,
196 g: 1.0,
197 b: 1.0,
198 a: 1.0,
199 };
200
201 pub const BLACK: Self = Self {
203 r: 0.0,
204 g: 0.0,
205 b: 0.0,
206 a: 1.0,
207 };
208
209 pub const TRANSPARENT: Self = Self {
211 r: 0.0,
212 g: 0.0,
213 b: 0.0,
214 a: 0.0,
215 };
216
217 #[must_use]
219 pub const fn to_array(&self) -> [f32; 4] {
220 [self.r, self.g, self.b, self.a]
221 }
222
223 #[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#[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 #[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 pub const ZERO: Self = Self::uniform(0.0);
256}
257
258#[derive(Debug, Clone, Default)]
260pub struct TextStyle {
261 pub font_family: String,
263 pub font_size: f32,
265 pub font_weight: u16,
267 pub color: WidgetColor,
269 pub line_height: f32,
271}
272
273impl TextStyle {
274 #[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#[derive(Debug, Clone, Default)]
289pub struct StrokeStyle {
290 pub color: WidgetColor,
292 pub width: f32,
294 pub line_cap: LineCap,
296 pub line_join: LineJoin,
298}
299
300#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
302pub enum LineCap {
303 #[default]
305 Butt,
306 Round,
308 Square,
310}
311
312#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
314pub enum LineJoin {
315 #[default]
317 Miter,
318 Round,
320 Bevel,
322}
323
324#[derive(Debug, Clone, Copy, PartialEq)]
326pub struct Transform2D {
327 pub matrix: [f32; 6],
329}
330
331impl Default for Transform2D {
332 fn default() -> Self {
333 Self::identity()
334 }
335}
336
337impl Transform2D {
338 #[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 #[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 #[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 #[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#[derive(Debug, Clone)]
378pub enum DrawCommand {
379 Rect {
381 bounds: Rect,
382 color: WidgetColor,
383 radius: CornerRadius,
384 },
385 Circle {
387 center: WidgetPoint,
388 radius: f32,
389 color: WidgetColor,
390 },
391 Text {
393 content: String,
394 position: WidgetPoint,
395 style: TextStyle,
396 },
397 Path {
399 points: Vec<WidgetPoint>,
400 style: StrokeStyle,
401 closed: bool,
402 },
403 Image { data: Vec<u8>, bounds: Rect },
405 Group {
407 children: Vec<DrawCommand>,
408 transform: Transform2D,
409 },
410 Gradient {
412 bounds: Rect,
413 start_color: WidgetColor,
414 end_color: WidgetColor,
415 angle: f32,
416 },
417 Clear { bounds: Rect, color: WidgetColor },
419}
420
421#[derive(Debug, Clone, Default)]
423pub struct GpuInstance {
424 pub bounds: [f32; 4],
426 pub color: [f32; 4],
428 pub shape_type: u32,
430 pub corner_radius: f32,
432 pub params: [f32; 4],
434}
435
436#[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 DrawCommand::Text { .. } | DrawCommand::Path { .. } | DrawCommand::Image { .. } => {
499 }
501 DrawCommand::Group { children, .. } => {
502 instances.extend(commands_to_gpu_instances(children));
504 }
505 }
506 }
507
508 instances
509}
510
511#[derive(Debug, Clone, Copy, Default)]
513pub struct Constraints {
514 pub min_width: f32,
516 pub max_width: f32,
518 pub min_height: f32,
520 pub max_height: f32,
522}
523
524impl Constraints {
525 #[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 #[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 #[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 #[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 #[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#[derive(Debug, Clone, Default)]
579pub struct LayoutResult {
580 pub bounds: Rect,
582 pub success: bool,
584 pub error: Option<String>,
586}
587
588impl LayoutResult {
589 #[must_use]
591 pub fn success(bounds: Rect) -> Self {
592 Self {
593 bounds,
594 success: true,
595 error: None,
596 }
597 }
598
599 #[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#[derive(Debug, Clone)]
612pub enum Event {
613 Click {
615 position: WidgetPoint,
616 button: WidgetMouseButton,
617 },
618 MouseMove { position: WidgetPoint },
620 KeyPress { key: String, modifiers: Modifiers },
622 Focus,
624 Blur,
626 Scroll { delta_x: f32, delta_y: f32 },
628 TouchStart { position: WidgetPoint, id: u32 },
630 TouchMove { position: WidgetPoint, id: u32 },
632 TouchEnd { id: u32 },
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub enum WidgetMouseButton {
639 Left,
640 Right,
641 Middle,
642}
643
644#[derive(Debug, Clone, Copy, Default)]
646pub struct Modifiers {
647 pub shift: bool,
649 pub ctrl: bool,
651 pub alt: bool,
653 pub meta: bool,
655}
656
657pub trait Canvas: Send + Sync {
659 fn draw(&mut self, command: DrawCommand);
661
662 fn commands(&self) -> &[DrawCommand];
664
665 fn clear(&mut self);
667
668 fn with_transform(&self, transform: Transform2D) -> Box<dyn Canvas>;
670
671 fn size(&self) -> Size;
673}
674
675#[derive(Debug)]
677pub struct RecordingCanvas {
678 commands: Vec<DrawCommand>,
679 size: Size,
680}
681
682impl RecordingCanvas {
683 #[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
718struct TransformedCanvas {
720 inner: RecordingCanvas,
721 transform: Transform2D,
722}
723
724impl Canvas for TransformedCanvas {
725 fn draw(&mut self, command: DrawCommand) {
726 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 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
764pub trait Widget: Brick {
769 fn measure(&self, constraints: Constraints) -> Size;
771
772 fn layout(&mut self, bounds: Rect) -> LayoutResult;
774
775 fn paint(&self, canvas: &mut dyn Canvas);
777
778 fn event(&mut self, event: &Event) -> Option<Box<dyn Any>>;
780
781 fn children(&self) -> &[Box<dyn Widget>] {
783 &[]
784 }
785
786 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
788 &mut []
789 }
790}
791
792pub trait WidgetExt: Widget {
794 fn render(&self, canvas: &mut dyn Canvas) {
799 let verification = self.verify();
800 if !verification.is_valid() {
801 return;
803 }
804 self.paint(canvas);
805 }
806
807 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 fn render_full(&mut self, bounds: Rect, canvas: &mut dyn Canvas) -> LayoutResult
839 where
840 Self: Sized,
841 {
842 let verification = self.verify();
844 if !verification.is_valid() {
845 return LayoutResult::failure("Brick verification failed");
846 }
847
848 let layout_result = self.layout(bounds);
850 if !layout_result.success {
851 return layout_result;
852 }
853
854 self.paint(canvas);
856
857 layout_result
858 }
859}
860
861impl<W: Widget> WidgetExt for W {}
862
863#[derive(Debug, Clone, Default)]
865pub struct RenderMetrics {
866 pub verify_time: Duration,
868 pub paint_time: Duration,
870 pub total_time: Duration,
872 pub valid: bool,
874 pub command_count: usize,
876}
877
878impl RenderMetrics {
879 #[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 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 #[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 #[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 #[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 assert!(r.contains(WidgetPoint::new(50.0, 50.0)));
1111
1112 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 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 #[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 let red = WidgetColor::from_hex(0xFF0000);
1195 assert!((red.r - 1.0).abs() < f32::EPSILON);
1196
1197 let green = WidgetColor::from_hex(0x00FF00);
1199 assert!((green.g - 1.0).abs() < f32::EPSILON);
1200
1201 let blue = WidgetColor::from_hex(0x0000FF);
1203 assert!((blue.b - 1.0).abs() < f32::EPSILON);
1204
1205 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 #[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 #[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 #[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 #[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 #[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 let t = Transform2D::rotate(PI / 2.0);
1363 assert!((t.matrix[0]).abs() < 0.0001); assert!((t.matrix[1] - 1.0).abs() < 0.0001); }
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 #[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 #[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 #[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); assert_eq!(instances[1].shape_type, 1); }
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); }
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); }
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); }
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); }
1608
1609 #[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 let result = c.constrain(Size::new(10.0, 10.0));
1659 assert_eq!(result, Size::new(50.0, 25.0));
1660
1661 let result = c.constrain(Size::new(200.0, 200.0));
1663 assert_eq!(result, Size::new(150.0, 75.0));
1664
1665 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 #[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 #[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 #[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 #[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 #[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 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 #[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(""); let mut canvas = RecordingCanvas::new(Size::new(800.0, 600.0));
1967
1968 widget.render(&mut canvas);
1969
1970 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 #[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}