1use crate::ir::layout::{Breakpoint, LayoutConstraints};
2use crate::ir::span::Span;
3use crate::ir::style::StyleProperties;
4use crate::ir::theme::WidgetState;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
9pub struct WidgetNode {
10 pub kind: WidgetKind,
11 pub id: Option<String>,
12 pub attributes: HashMap<String, AttributeValue>,
13 pub events: Vec<EventBinding>,
14 pub children: Vec<WidgetNode>,
15 pub span: Span,
16
17 pub style: Option<StyleProperties>,
19 pub layout: Option<LayoutConstraints>,
20 pub theme_ref: Option<AttributeValue>,
21 pub classes: Vec<String>,
22 pub breakpoint_attributes: HashMap<Breakpoint, HashMap<String, AttributeValue>>,
23 #[serde(default)]
25 pub inline_state_variants: HashMap<WidgetState, StyleProperties>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
30pub enum WidgetKind {
31 #[default]
32 Column,
33 Row,
34 Container,
35 Scrollable,
36 Stack,
37 Text,
38 Image,
39 Svg,
40 Button,
41 TextInput,
42 Checkbox,
43 Slider,
44 PickList,
45 Toggler,
46 Space,
47 Rule,
48 Radio,
49 ComboBox,
51 ProgressBar,
52 Tooltip,
53 Grid,
54 Canvas,
55 Float,
56 For,
58 Custom(String),
59}
60
61#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
63pub enum AttributeValue {
64 Static(String),
65 Binding(crate::expr::BindingExpr),
66 Interpolated(Vec<InterpolatedPart>),
67}
68
69impl Default for AttributeValue {
70 fn default() -> Self {
71 AttributeValue::Static(String::new())
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
77pub enum InterpolatedPart {
78 Literal(String),
79 Binding(crate::expr::BindingExpr),
80}
81
82impl Default for InterpolatedPart {
83 fn default() -> Self {
84 InterpolatedPart::Literal(String::new())
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
90pub struct ComboBoxAttributes {
91 pub options: Vec<String>,
92 pub selected: Option<crate::expr::BindingExpr>,
93 pub placeholder: Option<String>,
94 pub on_select: Option<String>,
95}
96
97#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
98pub struct PickListAttributes {
99 pub options: Vec<String>,
100 pub selected: Option<crate::expr::BindingExpr>,
101 pub placeholder: Option<String>,
102 pub on_select: Option<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
106pub struct CanvasAttributes {
107 pub width: f32,
108 pub height: f32,
109 pub program: Option<crate::expr::BindingExpr>,
110 pub on_click: Option<String>,
111}
112
113#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
114pub struct ProgressBarAttributes {
115 pub min: Option<f32>,
116 pub max: Option<f32>,
117 pub value: crate::expr::BindingExpr,
118 pub style: Option<ProgressBarStyle>,
119}
120
121#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
122pub enum ProgressBarStyle {
123 Primary,
124 Success,
125 Warning,
126 Danger,
127 Secondary,
128}
129
130#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
131pub struct TooltipAttributes {
132 pub message: String,
133 pub position: Option<TooltipPosition>,
134 pub delay: Option<u64>,
135}
136
137#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
138pub enum TooltipPosition {
139 FollowCursor,
140 Top,
141 Bottom,
142 Left,
143 Right,
144}
145
146#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
147pub struct GridAttributes {
148 pub columns: u32,
149 pub spacing: Option<f32>,
150 pub padding: Option<f32>,
151}
152
153#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
154pub struct FloatAttributes {
155 pub position: Option<FloatPosition>,
156 pub offset_x: Option<f32>,
157 pub offset_y: Option<f32>,
158 pub z_index: Option<u32>,
159}
160
161#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
162pub enum FloatPosition {
163 TopLeft,
164 TopRight,
165 BottomLeft,
166 BottomRight,
167}
168
169#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
171pub struct EventBinding {
172 pub event: EventKind,
173 pub handler: String,
174 pub param: Option<crate::expr::BindingExpr>,
176 pub span: Span,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
181pub enum EventKind {
182 #[default]
183 Click,
184 Press,
185 Release,
186 Change,
187 Input,
188 Submit,
189 Select,
190 Toggle,
191 Scroll,
192}
193
194impl WidgetKind {
195 pub fn minimum_version(&self) -> crate::ir::SchemaVersion {
220 match self {
223 WidgetKind::Canvas => crate::ir::SchemaVersion { major: 1, minor: 1 },
224 _ => crate::ir::SchemaVersion { major: 1, minor: 0 },
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::ir::style::StyleProperties;
233 use crate::ir::theme::WidgetState;
234
235 #[test]
236 fn test_widget_node_default_has_empty_inline_state_variants() {
237 let node = WidgetNode::default();
238 assert!(node.inline_state_variants.is_empty());
239 }
240
241 #[test]
242 fn test_widget_node_inline_state_variants_serialization() {
243 let mut node = WidgetNode {
244 kind: WidgetKind::Button,
245 id: Some("test-button".to_string()),
246 attributes: Default::default(),
247 events: Default::default(),
248 children: Default::default(),
249 span: Default::default(),
250 style: Default::default(),
251 layout: Default::default(),
252 theme_ref: Default::default(),
253 classes: Default::default(),
254 breakpoint_attributes: Default::default(),
255 inline_state_variants: Default::default(),
256 };
257
258 node.inline_state_variants.insert(
260 WidgetState::Hover,
261 StyleProperties {
262 opacity: Some(0.8),
263 ..Default::default()
264 },
265 );
266
267 let json = serde_json::to_string(&node).expect("Should serialize");
269 let deserialized: WidgetNode = serde_json::from_str(&json).expect("Should deserialize");
270
271 assert_eq!(deserialized.inline_state_variants.len(), 1);
273 assert!(
274 deserialized
275 .inline_state_variants
276 .contains_key(&WidgetState::Hover)
277 );
278 }
279
280 #[test]
281 fn test_widget_node_inline_state_variants_multiple_states() {
282 let mut node = WidgetNode::default();
283
284 node.inline_state_variants.insert(
285 WidgetState::Hover,
286 StyleProperties {
287 opacity: Some(0.9),
288 ..Default::default()
289 },
290 );
291
292 node.inline_state_variants.insert(
293 WidgetState::Active,
294 StyleProperties {
295 opacity: Some(0.7),
296 ..Default::default()
297 },
298 );
299
300 node.inline_state_variants.insert(
301 WidgetState::Disabled,
302 StyleProperties {
303 opacity: Some(0.5),
304 ..Default::default()
305 },
306 );
307
308 assert_eq!(node.inline_state_variants.len(), 3);
309 assert!(node.inline_state_variants.contains_key(&WidgetState::Hover));
310 assert!(
311 node.inline_state_variants
312 .contains_key(&WidgetState::Active)
313 );
314 assert!(
315 node.inline_state_variants
316 .contains_key(&WidgetState::Disabled)
317 );
318 }
319}