use crate::ir::layout::{Breakpoint, LayoutConstraints};
use crate::ir::span::Span;
use crate::ir::style::StyleProperties;
use crate::ir::theme::WidgetState;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
pub struct WidgetNode {
pub kind: WidgetKind,
pub id: Option<String>,
pub attributes: HashMap<String, AttributeValue>,
pub events: Vec<EventBinding>,
pub children: Vec<WidgetNode>,
pub span: Span,
pub style: Option<StyleProperties>,
pub layout: Option<LayoutConstraints>,
pub theme_ref: Option<AttributeValue>,
pub classes: Vec<String>,
pub breakpoint_attributes: HashMap<Breakpoint, HashMap<String, AttributeValue>>,
#[serde(default)]
pub inline_state_variants: HashMap<WidgetState, StyleProperties>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
pub enum WidgetKind {
#[default]
Column,
Row,
Container,
Scrollable,
Stack,
Text,
Image,
Svg,
Button,
TextInput,
Checkbox,
Slider,
PickList,
Toggler,
Space,
Rule,
Radio,
ComboBox,
ProgressBar,
Tooltip,
Grid,
Canvas,
CanvasRect,
CanvasCircle,
CanvasLine,
CanvasText,
CanvasGroup,
DatePicker,
TimePicker,
ColorPicker,
Menu,
MenuItem,
MenuSeparator,
ContextMenu,
Float,
DataTable,
DataColumn,
TreeView,
TreeNode,
TabBar,
Tab,
For,
If,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum AttributeValue {
Static(String),
Binding(crate::expr::BindingExpr),
Interpolated(Vec<InterpolatedPart>),
}
impl Default for AttributeValue {
fn default() -> Self {
AttributeValue::Static(String::new())
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum InterpolatedPart {
Literal(String),
Binding(crate::expr::BindingExpr),
}
impl Default for InterpolatedPart {
fn default() -> Self {
InterpolatedPart::Literal(String::new())
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ComboBoxAttributes {
pub options: Vec<String>,
pub selected: Option<crate::expr::BindingExpr>,
pub placeholder: Option<String>,
pub on_select: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PickListAttributes {
pub options: Vec<String>,
pub selected: Option<crate::expr::BindingExpr>,
pub placeholder: Option<String>,
pub on_select: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CanvasAttributes {
pub width: f32,
pub height: f32,
pub program: Option<crate::expr::BindingExpr>,
pub on_click: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProgressBarAttributes {
pub min: Option<f32>,
pub max: Option<f32>,
pub value: crate::expr::BindingExpr,
pub style: Option<ProgressBarStyle>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ProgressBarStyle {
Primary,
Success,
Warning,
Danger,
Secondary,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TooltipAttributes {
pub message: String,
pub position: Option<TooltipPosition>,
pub delay: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum TooltipPosition {
FollowCursor,
Top,
Bottom,
Left,
Right,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GridAttributes {
pub columns: u32,
pub spacing: Option<f32>,
pub padding: Option<f32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FloatAttributes {
pub position: Option<FloatPosition>,
pub offset_x: Option<f32>,
pub offset_y: Option<f32>,
pub z_index: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FloatPosition {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
pub struct EventBinding {
pub event: EventKind,
pub handler: String,
pub param: Option<crate::expr::BindingExpr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
pub enum EventKind {
#[default]
Click,
Press,
Release,
Change,
Input,
Submit,
Select,
Toggle,
Scroll,
CanvasClick,
CanvasDrag,
CanvasMove,
CanvasRelease,
RowClick,
Cancel,
Open,
Close,
}
impl std::fmt::Display for WidgetKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
WidgetKind::Column => "column",
WidgetKind::Row => "row",
WidgetKind::Container => "container",
WidgetKind::Scrollable => "scrollable",
WidgetKind::Stack => "stack",
WidgetKind::Text => "text",
WidgetKind::Image => "image",
WidgetKind::Svg => "svg",
WidgetKind::Button => "button",
WidgetKind::TextInput => "text_input",
WidgetKind::Checkbox => "checkbox",
WidgetKind::Slider => "slider",
WidgetKind::PickList => "pick_list",
WidgetKind::Toggler => "toggler",
WidgetKind::Space => "space",
WidgetKind::Rule => "rule",
WidgetKind::Radio => "radio",
WidgetKind::ComboBox => "combobox",
WidgetKind::ProgressBar => "progress_bar",
WidgetKind::Tooltip => "tooltip",
WidgetKind::Grid => "grid",
WidgetKind::Canvas => "canvas",
WidgetKind::CanvasRect => "rect",
WidgetKind::CanvasCircle => "circle",
WidgetKind::CanvasLine => "line",
WidgetKind::CanvasText => "canvas_text",
WidgetKind::CanvasGroup => "group",
WidgetKind::DatePicker => "date_picker",
WidgetKind::TimePicker => "time_picker",
WidgetKind::ColorPicker => "color_picker",
WidgetKind::Menu => "menu",
WidgetKind::MenuItem => "menu_item",
WidgetKind::MenuSeparator => "menu_separator",
WidgetKind::ContextMenu => "context_menu",
WidgetKind::Float => "float",
WidgetKind::DataTable => "data_table",
WidgetKind::DataColumn => "data_column",
WidgetKind::TreeView => "tree_view",
WidgetKind::TreeNode => "tree_node",
WidgetKind::TabBar => "tab_bar",
WidgetKind::Tab => "tab",
WidgetKind::For => "for",
WidgetKind::If => "if",
WidgetKind::Custom(name) => return write!(f, "{}", name),
};
write!(f, "{}", name)
}
}
impl WidgetKind {
pub fn all_standard() -> &'static [&'static str] {
&[
"column",
"row",
"container",
"scrollable",
"stack",
"text",
"image",
"svg",
"button",
"text_input",
"checkbox",
"slider",
"pick_list",
"toggler",
"space",
"rule",
"radio",
"combobox",
"progress_bar",
"tooltip",
"grid",
"canvas",
"rect",
"circle",
"line",
"canvas_text",
"group",
"date_picker",
"time_picker",
"color_picker",
"menu",
"menu_item",
"menu_separator",
"context_menu",
"float",
"data_table",
"data_column",
"tree_view",
"tree_node",
"tab_bar",
"tab",
"for",
"if",
]
}
pub fn is_custom(&self) -> bool {
matches!(self, WidgetKind::Custom(_))
}
pub fn minimum_version(&self) -> crate::ir::SchemaVersion {
match self {
WidgetKind::Canvas => crate::ir::SchemaVersion { major: 1, minor: 1 },
WidgetKind::DatePicker
| WidgetKind::TimePicker
| WidgetKind::ColorPicker
| WidgetKind::Menu
| WidgetKind::MenuItem
| WidgetKind::MenuSeparator
| WidgetKind::ContextMenu
| WidgetKind::DataTable
| WidgetKind::DataColumn
| WidgetKind::TreeView
| WidgetKind::TreeNode
| WidgetKind::TabBar
| WidgetKind::Tab => crate::ir::SchemaVersion { major: 1, minor: 1 },
_ => crate::ir::SchemaVersion { major: 1, minor: 0 },
}
}
pub fn schema(&self) -> crate::schema::WidgetSchema {
crate::schema::get_widget_schema(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::style::StyleProperties;
use crate::ir::theme::WidgetState;
#[test]
fn test_widget_node_default_has_empty_inline_state_variants() {
let node = WidgetNode::default();
assert!(node.inline_state_variants.is_empty());
}
#[test]
fn test_widget_node_inline_state_variants_serialization() {
let mut node = WidgetNode {
kind: WidgetKind::Button,
id: Some("test-button".to_string()),
attributes: Default::default(),
events: Default::default(),
children: Default::default(),
span: Default::default(),
style: Default::default(),
layout: Default::default(),
theme_ref: Default::default(),
classes: Default::default(),
breakpoint_attributes: Default::default(),
inline_state_variants: Default::default(),
};
node.inline_state_variants.insert(
WidgetState::Hover,
StyleProperties {
opacity: Some(0.8),
..Default::default()
},
);
let json = serde_json::to_string(&node).expect("Should serialize");
let deserialized: WidgetNode = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(deserialized.inline_state_variants.len(), 1);
assert!(
deserialized
.inline_state_variants
.contains_key(&WidgetState::Hover)
);
}
#[test]
fn test_widget_node_inline_state_variants_multiple_states() {
let mut node = WidgetNode::default();
node.inline_state_variants.insert(
WidgetState::Hover,
StyleProperties {
opacity: Some(0.9),
..Default::default()
},
);
node.inline_state_variants.insert(
WidgetState::Active,
StyleProperties {
opacity: Some(0.7),
..Default::default()
},
);
node.inline_state_variants.insert(
WidgetState::Disabled,
StyleProperties {
opacity: Some(0.5),
..Default::default()
},
);
assert_eq!(node.inline_state_variants.len(), 3);
assert!(node.inline_state_variants.contains_key(&WidgetState::Hover));
assert!(
node.inline_state_variants
.contains_key(&WidgetState::Active)
);
assert!(
node.inline_state_variants
.contains_key(&WidgetState::Disabled)
);
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum CanvasShape {
Rect(RectShape),
Circle(CircleShape),
Line(LineShape),
Text(TextShape),
Group(GroupShape),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RectShape {
pub x: AttributeValue,
pub y: AttributeValue,
pub width: AttributeValue,
pub height: AttributeValue,
pub fill: Option<AttributeValue>,
pub stroke: Option<AttributeValue>,
pub stroke_width: Option<AttributeValue>,
pub radius: Option<AttributeValue>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CircleShape {
pub cx: AttributeValue,
pub cy: AttributeValue,
pub radius: AttributeValue,
pub fill: Option<AttributeValue>,
pub stroke: Option<AttributeValue>,
pub stroke_width: Option<AttributeValue>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct LineShape {
pub x1: AttributeValue,
pub y1: AttributeValue,
pub x2: AttributeValue,
pub y2: AttributeValue,
pub stroke: Option<AttributeValue>,
pub stroke_width: Option<AttributeValue>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TextShape {
pub x: AttributeValue,
pub y: AttributeValue,
pub content: AttributeValue,
pub size: Option<AttributeValue>,
pub color: Option<AttributeValue>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GroupShape {
pub transform: Option<Transform>,
pub children: Vec<CanvasShape>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Transform {
Translate(f32, f32),
Rotate(f32),
Scale(f32),
ScaleXY(f32, f32),
Matrix([f32; 6]),
}