use crate::constraints::Constraints;
use crate::event::Event;
use crate::geometry::{Rect, Size};
use serde::{Deserialize, Serialize};
use std::any::Any;
pub use crate::brick_types::{
Brick, BrickAssertion, BrickBudget, BrickError, BrickPhase, BrickResult, BrickVerification,
BudgetViolation,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WidgetId(pub u64);
impl WidgetId {
#[must_use]
pub const fn new(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeId(std::any::TypeId);
impl TypeId {
#[must_use]
pub fn of<T: 'static>() -> Self {
Self(std::any::TypeId::of::<T>())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LayoutResult {
pub size: Size,
}
pub trait Widget: Brick + Send + Sync {
fn type_id(&self) -> TypeId;
fn measure(&self, constraints: Constraints) -> Size;
fn layout(&mut self, bounds: Rect) -> LayoutResult;
fn paint(&self, canvas: &mut dyn Canvas);
fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>>;
fn children(&self) -> &[Box<dyn Widget>];
fn children_mut(&mut self) -> &mut [Box<dyn Widget>];
fn is_interactive(&self) -> bool {
false
}
fn is_focusable(&self) -> bool {
false
}
fn accessible_name(&self) -> Option<&str> {
None
}
fn accessible_role(&self) -> AccessibleRole {
AccessibleRole::Generic
}
fn test_id(&self) -> Option<&str> {
None
}
fn bounds(&self) -> Rect {
Rect::new(0.0, 0.0, 0.0, 0.0)
}
}
pub trait Canvas {
fn fill_rect(&mut self, rect: Rect, color: crate::Color);
fn stroke_rect(&mut self, rect: Rect, color: crate::Color, width: f32);
fn draw_text(&mut self, text: &str, position: crate::Point, style: &TextStyle);
fn draw_line(&mut self, from: crate::Point, to: crate::Point, color: crate::Color, width: f32);
fn fill_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color);
fn stroke_circle(&mut self, center: crate::Point, radius: f32, color: crate::Color, width: f32);
fn fill_arc(
&mut self,
center: crate::Point,
radius: f32,
start_angle: f32,
end_angle: f32,
color: crate::Color,
);
fn draw_path(&mut self, points: &[crate::Point], color: crate::Color, width: f32);
fn fill_polygon(&mut self, points: &[crate::Point], color: crate::Color);
fn push_clip(&mut self, rect: Rect);
fn pop_clip(&mut self);
fn push_transform(&mut self, transform: Transform2D);
fn pop_transform(&mut self);
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextStyle {
pub size: f32,
pub color: crate::Color,
pub weight: FontWeight,
pub style: FontStyle,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
size: 16.0,
color: crate::Color::BLACK,
weight: FontWeight::Normal,
style: FontStyle::Normal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FontWeight {
Thin,
Light,
Normal,
Medium,
Semibold,
Bold,
Black,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FontStyle {
Normal,
Italic,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Transform2D {
pub matrix: [f32; 6],
}
impl Transform2D {
pub const IDENTITY: Self = Self {
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
};
#[must_use]
pub const fn translate(x: f32, y: f32) -> Self {
Self {
matrix: [1.0, 0.0, 0.0, 1.0, x, y],
}
}
#[must_use]
pub const fn scale(sx: f32, sy: f32) -> Self {
Self {
matrix: [sx, 0.0, 0.0, sy, 0.0, 0.0],
}
}
#[must_use]
pub fn rotate(angle: f32) -> Self {
let (sin, cos) = angle.sin_cos();
Self {
matrix: [cos, sin, -sin, cos, 0.0, 0.0],
}
}
}
impl Default for Transform2D {
fn default() -> Self {
Self::IDENTITY
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum AccessibleRole {
#[default]
Generic,
Button,
Checkbox,
TextInput,
Link,
Heading,
Image,
List,
ListItem,
Table,
TableRow,
TableCell,
Menu,
MenuItem,
ComboBox,
Slider,
ProgressBar,
Tab,
TabPanel,
RadioGroup,
Radio,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_widget_id() {
let id = WidgetId::new(42);
assert_eq!(id.0, 42);
}
#[test]
fn test_widget_id_eq() {
let id1 = WidgetId::new(1);
let id2 = WidgetId::new(1);
let id3 = WidgetId::new(2);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_widget_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(WidgetId::new(1));
set.insert(WidgetId::new(2));
assert_eq!(set.len(), 2);
assert!(set.contains(&WidgetId::new(1)));
}
#[test]
fn test_type_id() {
let id1 = TypeId::of::<u32>();
let id2 = TypeId::of::<u32>();
let id3 = TypeId::of::<String>();
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_type_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(TypeId::of::<u32>());
set.insert(TypeId::of::<String>());
assert_eq!(set.len(), 2);
}
#[test]
fn test_transform2d_identity() {
let t = Transform2D::IDENTITY;
assert_eq!(t.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
}
#[test]
fn test_transform2d_default() {
let t = Transform2D::default();
assert_eq!(t.matrix, Transform2D::IDENTITY.matrix);
}
#[test]
fn test_transform2d_translate() {
let t = Transform2D::translate(10.0, 20.0);
assert_eq!(t.matrix[4], 10.0);
assert_eq!(t.matrix[5], 20.0);
}
#[test]
fn test_transform2d_scale() {
let t = Transform2D::scale(2.0, 3.0);
assert_eq!(t.matrix[0], 2.0);
assert_eq!(t.matrix[3], 3.0);
}
#[test]
fn test_transform2d_rotate() {
let t = Transform2D::rotate(std::f32::consts::PI / 2.0);
assert!((t.matrix[0] - 0.0).abs() < 1e-6);
assert!((t.matrix[1] - 1.0).abs() < 1e-6);
assert!((t.matrix[2] - (-1.0)).abs() < 1e-6);
assert!((t.matrix[3] - 0.0).abs() < 1e-6);
}
#[test]
fn test_text_style_default() {
let style = TextStyle::default();
assert_eq!(style.size, 16.0);
assert_eq!(style.weight, FontWeight::Normal);
assert_eq!(style.style, FontStyle::Normal);
assert_eq!(style.color, crate::Color::BLACK);
}
#[test]
fn test_text_style_eq() {
let s1 = TextStyle::default();
let s2 = TextStyle::default();
assert_eq!(s1, s2);
}
#[test]
fn test_text_style_custom() {
let style = TextStyle {
size: 24.0,
color: crate::Color::RED,
weight: FontWeight::Bold,
style: FontStyle::Italic,
};
assert_eq!(style.size, 24.0);
assert_eq!(style.weight, FontWeight::Bold);
assert_eq!(style.style, FontStyle::Italic);
}
#[test]
fn test_font_weight_variants() {
let weights = [
FontWeight::Thin,
FontWeight::Light,
FontWeight::Normal,
FontWeight::Medium,
FontWeight::Semibold,
FontWeight::Bold,
FontWeight::Black,
];
assert_eq!(weights.len(), 7);
}
#[test]
fn test_font_style_variants() {
assert_ne!(FontStyle::Normal, FontStyle::Italic);
}
#[test]
fn test_accessible_role_default() {
assert_eq!(AccessibleRole::default(), AccessibleRole::Generic);
}
#[test]
fn test_accessible_role_variants() {
let roles = [
AccessibleRole::Generic,
AccessibleRole::Button,
AccessibleRole::Checkbox,
AccessibleRole::TextInput,
AccessibleRole::Link,
AccessibleRole::Heading,
AccessibleRole::Image,
AccessibleRole::List,
AccessibleRole::ListItem,
AccessibleRole::Table,
AccessibleRole::TableRow,
AccessibleRole::TableCell,
AccessibleRole::Menu,
AccessibleRole::MenuItem,
AccessibleRole::ComboBox,
AccessibleRole::Slider,
AccessibleRole::ProgressBar,
AccessibleRole::Tab,
AccessibleRole::TabPanel,
AccessibleRole::RadioGroup,
AccessibleRole::Radio,
];
assert_eq!(roles.len(), 21);
}
#[test]
fn test_layout_result_default() {
let result = LayoutResult::default();
assert_eq!(result.size, Size::new(0.0, 0.0));
}
#[test]
fn test_layout_result_with_size() {
let result = LayoutResult {
size: Size::new(100.0, 50.0),
};
assert_eq!(result.size.width, 100.0);
assert_eq!(result.size.height, 50.0);
}
}