1use crate::constraints::Constraints;
44use crate::event::Event;
45use crate::geometry::{Rect, Size};
46use serde::{Deserialize, Serialize};
47use std::any::Any;
48
49pub use jugar_probar::brick::{
51 Brick, BrickAssertion, BrickBudget, BrickError, BrickPhase, BrickResult, BrickVerification,
52 BudgetViolation,
53};
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub struct WidgetId(pub u64);
58
59impl WidgetId {
60 #[must_use]
62 pub const fn new(id: u64) -> Self {
63 Self(id)
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub struct TypeId(std::any::TypeId);
70
71impl TypeId {
72 #[must_use]
74 pub fn of<T: 'static>() -> Self {
75 Self(std::any::TypeId::of::<T>())
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default)]
81pub struct LayoutResult {
82 pub size: Size,
84}
85
86pub trait Widget: Brick + Send + Sync {
103 fn type_id(&self) -> TypeId;
105
106 fn measure(&self, constraints: Constraints) -> Size;
108
109 fn layout(&mut self, bounds: Rect) -> LayoutResult;
111
112 fn paint(&self, canvas: &mut dyn Canvas);
118
119 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>>;
121
122 fn children(&self) -> &[Box<dyn Widget>];
124
125 fn children_mut(&mut self) -> &mut [Box<dyn Widget>];
127
128 fn is_interactive(&self) -> bool {
130 false
131 }
132
133 fn is_focusable(&self) -> bool {
135 false
136 }
137
138 fn accessible_name(&self) -> Option<&str> {
140 None
141 }
142
143 fn accessible_role(&self) -> AccessibleRole {
145 AccessibleRole::Generic
146 }
147
148 fn test_id(&self) -> Option<&str> {
150 None
151 }
152
153 fn bounds(&self) -> Rect {
155 Rect::new(0.0, 0.0, 0.0, 0.0)
156 }
157}
158
159pub trait Canvas {
167 fn fill_rect(&mut self, rect: Rect, color: crate::Color);
169
170 fn stroke_rect(&mut self, rect: Rect, color: crate::Color, width: f32);
172
173 fn draw_text(&mut self, text: &str, position: crate::Point, style: &TextStyle);
175
176 fn draw_line(&mut self, from: crate::Point, to: crate::Point, color: crate::Color, width: f32);
178
179 fn fill_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color);
181
182 fn stroke_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color, width: f32);
184
185 fn fill_arc(
187 &mut self,
188 center: crate::Point,
189 radius: f32,
190 start_angle: f32,
191 end_angle: f32,
192 color: crate::Color,
193 );
194
195 fn draw_path(&mut self, points: &[crate::Point], color: crate::Color, width: f32);
197
198 fn fill_polygon(&mut self, points: &[crate::Point], color: crate::Color);
200
201 fn push_clip(&mut self, rect: Rect);
203
204 fn pop_clip(&mut self);
206
207 fn push_transform(&mut self, transform: Transform2D);
209
210 fn pop_transform(&mut self);
212}
213
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
235pub struct TextStyle {
236 pub size: f32,
238 pub color: crate::Color,
240 pub weight: FontWeight,
242 pub style: FontStyle,
244}
245
246impl Default for TextStyle {
247 fn default() -> Self {
248 Self {
249 size: 16.0,
250 color: crate::Color::BLACK,
251 weight: FontWeight::Normal,
252 style: FontStyle::Normal,
253 }
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
259pub enum FontWeight {
260 Thin,
262 Light,
264 Normal,
266 Medium,
268 Semibold,
270 Bold,
272 Black,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
278pub enum FontStyle {
279 Normal,
281 Italic,
283}
284
285#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
287pub struct Transform2D {
288 pub matrix: [f32; 6],
293}
294
295impl Transform2D {
296 pub const IDENTITY: Self = Self {
298 matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
299 };
300
301 #[must_use]
303 pub const fn translate(x: f32, y: f32) -> Self {
304 Self {
305 matrix: [1.0, 0.0, 0.0, 1.0, x, y],
306 }
307 }
308
309 #[must_use]
311 pub const fn scale(sx: f32, sy: f32) -> Self {
312 Self {
313 matrix: [sx, 0.0, 0.0, sy, 0.0, 0.0],
314 }
315 }
316
317 #[must_use]
319 pub fn rotate(angle: f32) -> Self {
320 let (sin, cos) = angle.sin_cos();
321 Self {
322 matrix: [cos, sin, -sin, cos, 0.0, 0.0],
323 }
324 }
325}
326
327impl Default for Transform2D {
328 fn default() -> Self {
329 Self::IDENTITY
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
335pub enum AccessibleRole {
336 #[default]
338 Generic,
339 Button,
341 Checkbox,
343 TextInput,
345 Link,
347 Heading,
349 Image,
351 List,
353 ListItem,
355 Table,
357 TableRow,
359 TableCell,
361 Menu,
363 MenuItem,
365 ComboBox,
367 Slider,
369 ProgressBar,
371 Tab,
373 TabPanel,
375 RadioGroup,
377 Radio,
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_widget_id() {
387 let id = WidgetId::new(42);
388 assert_eq!(id.0, 42);
389 }
390
391 #[test]
392 fn test_widget_id_eq() {
393 let id1 = WidgetId::new(1);
394 let id2 = WidgetId::new(1);
395 let id3 = WidgetId::new(2);
396 assert_eq!(id1, id2);
397 assert_ne!(id1, id3);
398 }
399
400 #[test]
401 fn test_widget_id_hash() {
402 use std::collections::HashSet;
403 let mut set = HashSet::new();
404 set.insert(WidgetId::new(1));
405 set.insert(WidgetId::new(2));
406 assert_eq!(set.len(), 2);
407 assert!(set.contains(&WidgetId::new(1)));
408 }
409
410 #[test]
411 fn test_type_id() {
412 let id1 = TypeId::of::<u32>();
413 let id2 = TypeId::of::<u32>();
414 let id3 = TypeId::of::<String>();
415
416 assert_eq!(id1, id2);
417 assert_ne!(id1, id3);
418 }
419
420 #[test]
421 fn test_type_id_hash() {
422 use std::collections::HashSet;
423 let mut set = HashSet::new();
424 set.insert(TypeId::of::<u32>());
425 set.insert(TypeId::of::<String>());
426 assert_eq!(set.len(), 2);
427 }
428
429 #[test]
430 fn test_transform2d_identity() {
431 let t = Transform2D::IDENTITY;
432 assert_eq!(t.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
433 }
434
435 #[test]
436 fn test_transform2d_default() {
437 let t = Transform2D::default();
438 assert_eq!(t.matrix, Transform2D::IDENTITY.matrix);
439 }
440
441 #[test]
442 fn test_transform2d_translate() {
443 let t = Transform2D::translate(10.0, 20.0);
444 assert_eq!(t.matrix[4], 10.0);
445 assert_eq!(t.matrix[5], 20.0);
446 }
447
448 #[test]
449 fn test_transform2d_scale() {
450 let t = Transform2D::scale(2.0, 3.0);
451 assert_eq!(t.matrix[0], 2.0);
452 assert_eq!(t.matrix[3], 3.0);
453 }
454
455 #[test]
456 fn test_transform2d_rotate() {
457 let t = Transform2D::rotate(std::f32::consts::PI / 2.0);
458 assert!((t.matrix[0] - 0.0).abs() < 1e-6);
460 assert!((t.matrix[1] - 1.0).abs() < 1e-6);
461 assert!((t.matrix[2] - (-1.0)).abs() < 1e-6);
462 assert!((t.matrix[3] - 0.0).abs() < 1e-6);
463 }
464
465 #[test]
466 fn test_text_style_default() {
467 let style = TextStyle::default();
468 assert_eq!(style.size, 16.0);
469 assert_eq!(style.weight, FontWeight::Normal);
470 assert_eq!(style.style, FontStyle::Normal);
471 assert_eq!(style.color, crate::Color::BLACK);
472 }
473
474 #[test]
475 fn test_text_style_eq() {
476 let s1 = TextStyle::default();
477 let s2 = TextStyle::default();
478 assert_eq!(s1, s2);
479 }
480
481 #[test]
482 fn test_text_style_custom() {
483 let style = TextStyle {
484 size: 24.0,
485 color: crate::Color::RED,
486 weight: FontWeight::Bold,
487 style: FontStyle::Italic,
488 };
489 assert_eq!(style.size, 24.0);
490 assert_eq!(style.weight, FontWeight::Bold);
491 assert_eq!(style.style, FontStyle::Italic);
492 }
493
494 #[test]
495 fn test_font_weight_variants() {
496 let weights = [
497 FontWeight::Thin,
498 FontWeight::Light,
499 FontWeight::Normal,
500 FontWeight::Medium,
501 FontWeight::Semibold,
502 FontWeight::Bold,
503 FontWeight::Black,
504 ];
505 assert_eq!(weights.len(), 7);
506 }
507
508 #[test]
509 fn test_font_style_variants() {
510 assert_ne!(FontStyle::Normal, FontStyle::Italic);
511 }
512
513 #[test]
514 fn test_accessible_role_default() {
515 assert_eq!(AccessibleRole::default(), AccessibleRole::Generic);
516 }
517
518 #[test]
519 fn test_accessible_role_variants() {
520 let roles = [
521 AccessibleRole::Generic,
522 AccessibleRole::Button,
523 AccessibleRole::Checkbox,
524 AccessibleRole::TextInput,
525 AccessibleRole::Link,
526 AccessibleRole::Heading,
527 AccessibleRole::Image,
528 AccessibleRole::List,
529 AccessibleRole::ListItem,
530 AccessibleRole::Table,
531 AccessibleRole::TableRow,
532 AccessibleRole::TableCell,
533 AccessibleRole::Menu,
534 AccessibleRole::MenuItem,
535 AccessibleRole::ComboBox,
536 AccessibleRole::Slider,
537 AccessibleRole::ProgressBar,
538 AccessibleRole::Tab,
539 AccessibleRole::TabPanel,
540 AccessibleRole::RadioGroup,
541 AccessibleRole::Radio,
542 ];
543 assert_eq!(roles.len(), 21);
544 }
545
546 #[test]
547 fn test_layout_result_default() {
548 let result = LayoutResult::default();
549 assert_eq!(result.size, Size::new(0.0, 0.0));
550 }
551
552 #[test]
553 fn test_layout_result_with_size() {
554 let result = LayoutResult {
555 size: Size::new(100.0, 50.0),
556 };
557 assert_eq!(result.size.width, 100.0);
558 assert_eq!(result.size.height, 50.0);
559 }
560}