1use crate::constraints::Constraints;
34use crate::event::Event;
35use crate::geometry::{Rect, Size};
36use serde::{Deserialize, Serialize};
37use std::any::Any;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub struct WidgetId(pub u64);
42
43impl WidgetId {
44 #[must_use]
46 pub const fn new(id: u64) -> Self {
47 Self(id)
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct TypeId(std::any::TypeId);
54
55impl TypeId {
56 #[must_use]
58 pub fn of<T: 'static>() -> Self {
59 Self(std::any::TypeId::of::<T>())
60 }
61}
62
63#[derive(Debug, Clone, Copy, Default)]
65pub struct LayoutResult {
66 pub size: Size,
68}
69
70pub trait Widget: Send + Sync {
77 fn type_id(&self) -> TypeId;
79
80 fn measure(&self, constraints: Constraints) -> Size;
84
85 fn layout(&mut self, bounds: Rect) -> LayoutResult;
89
90 fn paint(&self, canvas: &mut dyn Canvas);
94
95 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>>;
99
100 fn children(&self) -> &[Box<dyn Widget>];
102
103 fn children_mut(&mut self) -> &mut [Box<dyn Widget>];
105
106 fn is_interactive(&self) -> bool {
108 false
109 }
110
111 fn is_focusable(&self) -> bool {
113 false
114 }
115
116 fn accessible_name(&self) -> Option<&str> {
118 None
119 }
120
121 fn accessible_role(&self) -> AccessibleRole {
123 AccessibleRole::Generic
124 }
125
126 fn test_id(&self) -> Option<&str> {
128 None
129 }
130
131 fn bounds(&self) -> Rect {
136 Rect::new(0.0, 0.0, 0.0, 0.0)
137 }
138}
139
140pub trait Canvas {
144 fn fill_rect(&mut self, rect: Rect, color: crate::Color);
146
147 fn stroke_rect(&mut self, rect: Rect, color: crate::Color, width: f32);
149
150 fn draw_text(&mut self, text: &str, position: crate::Point, style: &TextStyle);
152
153 fn draw_line(&mut self, from: crate::Point, to: crate::Point, color: crate::Color, width: f32);
155
156 fn fill_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color);
158
159 fn stroke_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color, width: f32);
161
162 fn fill_arc(
164 &mut self,
165 center: crate::Point,
166 radius: f32,
167 start_angle: f32,
168 end_angle: f32,
169 color: crate::Color,
170 );
171
172 fn draw_path(&mut self, points: &[crate::Point], color: crate::Color, width: f32);
174
175 fn fill_polygon(&mut self, points: &[crate::Point], color: crate::Color);
177
178 fn push_clip(&mut self, rect: Rect);
180
181 fn pop_clip(&mut self);
183
184 fn push_transform(&mut self, transform: Transform2D);
186
187 fn pop_transform(&mut self);
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
212pub struct TextStyle {
213 pub size: f32,
215 pub color: crate::Color,
217 pub weight: FontWeight,
219 pub style: FontStyle,
221}
222
223impl Default for TextStyle {
224 fn default() -> Self {
225 Self {
226 size: 16.0,
227 color: crate::Color::BLACK,
228 weight: FontWeight::Normal,
229 style: FontStyle::Normal,
230 }
231 }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
236pub enum FontWeight {
237 Thin,
239 Light,
241 Normal,
243 Medium,
245 Semibold,
247 Bold,
249 Black,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
255pub enum FontStyle {
256 Normal,
258 Italic,
260}
261
262#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
264pub struct Transform2D {
265 pub matrix: [f32; 6],
270}
271
272impl Transform2D {
273 pub const IDENTITY: Self = Self {
275 matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
276 };
277
278 #[must_use]
280 pub const fn translate(x: f32, y: f32) -> Self {
281 Self {
282 matrix: [1.0, 0.0, 0.0, 1.0, x, y],
283 }
284 }
285
286 #[must_use]
288 pub const fn scale(sx: f32, sy: f32) -> Self {
289 Self {
290 matrix: [sx, 0.0, 0.0, sy, 0.0, 0.0],
291 }
292 }
293
294 #[must_use]
296 pub fn rotate(angle: f32) -> Self {
297 let (sin, cos) = angle.sin_cos();
298 Self {
299 matrix: [cos, sin, -sin, cos, 0.0, 0.0],
300 }
301 }
302}
303
304impl Default for Transform2D {
305 fn default() -> Self {
306 Self::IDENTITY
307 }
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
312pub enum AccessibleRole {
313 #[default]
315 Generic,
316 Button,
318 Checkbox,
320 TextInput,
322 Link,
324 Heading,
326 Image,
328 List,
330 ListItem,
332 Table,
334 TableRow,
336 TableCell,
338 Menu,
340 MenuItem,
342 ComboBox,
344 Slider,
346 ProgressBar,
348 Tab,
350 TabPanel,
352 RadioGroup,
354 Radio,
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_widget_id() {
364 let id = WidgetId::new(42);
365 assert_eq!(id.0, 42);
366 }
367
368 #[test]
369 fn test_widget_id_eq() {
370 let id1 = WidgetId::new(1);
371 let id2 = WidgetId::new(1);
372 let id3 = WidgetId::new(2);
373 assert_eq!(id1, id2);
374 assert_ne!(id1, id3);
375 }
376
377 #[test]
378 fn test_widget_id_hash() {
379 use std::collections::HashSet;
380 let mut set = HashSet::new();
381 set.insert(WidgetId::new(1));
382 set.insert(WidgetId::new(2));
383 assert_eq!(set.len(), 2);
384 assert!(set.contains(&WidgetId::new(1)));
385 }
386
387 #[test]
388 fn test_type_id() {
389 let id1 = TypeId::of::<u32>();
390 let id2 = TypeId::of::<u32>();
391 let id3 = TypeId::of::<String>();
392
393 assert_eq!(id1, id2);
394 assert_ne!(id1, id3);
395 }
396
397 #[test]
398 fn test_type_id_hash() {
399 use std::collections::HashSet;
400 let mut set = HashSet::new();
401 set.insert(TypeId::of::<u32>());
402 set.insert(TypeId::of::<String>());
403 assert_eq!(set.len(), 2);
404 }
405
406 #[test]
407 fn test_transform2d_identity() {
408 let t = Transform2D::IDENTITY;
409 assert_eq!(t.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
410 }
411
412 #[test]
413 fn test_transform2d_default() {
414 let t = Transform2D::default();
415 assert_eq!(t.matrix, Transform2D::IDENTITY.matrix);
416 }
417
418 #[test]
419 fn test_transform2d_translate() {
420 let t = Transform2D::translate(10.0, 20.0);
421 assert_eq!(t.matrix[4], 10.0);
422 assert_eq!(t.matrix[5], 20.0);
423 }
424
425 #[test]
426 fn test_transform2d_scale() {
427 let t = Transform2D::scale(2.0, 3.0);
428 assert_eq!(t.matrix[0], 2.0);
429 assert_eq!(t.matrix[3], 3.0);
430 }
431
432 #[test]
433 fn test_transform2d_rotate() {
434 let t = Transform2D::rotate(std::f32::consts::PI / 2.0);
435 assert!((t.matrix[0] - 0.0).abs() < 1e-6);
437 assert!((t.matrix[1] - 1.0).abs() < 1e-6);
438 assert!((t.matrix[2] - (-1.0)).abs() < 1e-6);
439 assert!((t.matrix[3] - 0.0).abs() < 1e-6);
440 }
441
442 #[test]
443 fn test_text_style_default() {
444 let style = TextStyle::default();
445 assert_eq!(style.size, 16.0);
446 assert_eq!(style.weight, FontWeight::Normal);
447 assert_eq!(style.style, FontStyle::Normal);
448 assert_eq!(style.color, crate::Color::BLACK);
449 }
450
451 #[test]
452 fn test_text_style_eq() {
453 let s1 = TextStyle::default();
454 let s2 = TextStyle::default();
455 assert_eq!(s1, s2);
456 }
457
458 #[test]
459 fn test_text_style_custom() {
460 let style = TextStyle {
461 size: 24.0,
462 color: crate::Color::RED,
463 weight: FontWeight::Bold,
464 style: FontStyle::Italic,
465 };
466 assert_eq!(style.size, 24.0);
467 assert_eq!(style.weight, FontWeight::Bold);
468 assert_eq!(style.style, FontStyle::Italic);
469 }
470
471 #[test]
472 fn test_font_weight_variants() {
473 let weights = [
474 FontWeight::Thin,
475 FontWeight::Light,
476 FontWeight::Normal,
477 FontWeight::Medium,
478 FontWeight::Semibold,
479 FontWeight::Bold,
480 FontWeight::Black,
481 ];
482 assert_eq!(weights.len(), 7);
483 }
484
485 #[test]
486 fn test_font_style_variants() {
487 assert_ne!(FontStyle::Normal, FontStyle::Italic);
488 }
489
490 #[test]
491 fn test_accessible_role_default() {
492 assert_eq!(AccessibleRole::default(), AccessibleRole::Generic);
493 }
494
495 #[test]
496 fn test_accessible_role_variants() {
497 let roles = [
498 AccessibleRole::Generic,
499 AccessibleRole::Button,
500 AccessibleRole::Checkbox,
501 AccessibleRole::TextInput,
502 AccessibleRole::Link,
503 AccessibleRole::Heading,
504 AccessibleRole::Image,
505 AccessibleRole::List,
506 AccessibleRole::ListItem,
507 AccessibleRole::Table,
508 AccessibleRole::TableRow,
509 AccessibleRole::TableCell,
510 AccessibleRole::Menu,
511 AccessibleRole::MenuItem,
512 AccessibleRole::ComboBox,
513 AccessibleRole::Slider,
514 AccessibleRole::ProgressBar,
515 AccessibleRole::Tab,
516 AccessibleRole::TabPanel,
517 AccessibleRole::RadioGroup,
518 AccessibleRole::Radio,
519 ];
520 assert_eq!(roles.len(), 21);
521 }
522
523 #[test]
524 fn test_layout_result_default() {
525 let result = LayoutResult::default();
526 assert_eq!(result.size, Size::new(0.0, 0.0));
527 }
528
529 #[test]
530 fn test_layout_result_with_size() {
531 let result = LayoutResult {
532 size: Size::new(100.0, 50.0),
533 };
534 assert_eq!(result.size.width, 100.0);
535 assert_eq!(result.size.height, 50.0);
536 }
537}