Skip to main content

dampen_core/ir/
node.rs

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/// A node in the widget tree
8#[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    // Styling extensions
18    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    /// State-specific styles from inline attributes (e.g., hover:background="#ff0000")
24    #[serde(default)]
25    pub inline_state_variants: HashMap<WidgetState, StyleProperties>,
26}
27
28/// Enumeration of all supported widget types
29#[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    // Advanced widgets
50    ComboBox,
51    ProgressBar,
52    Tooltip,
53    Grid,
54    Canvas,
55    CanvasRect,
56    CanvasCircle,
57    CanvasLine,
58    CanvasText,
59    CanvasGroup,
60    /// Date selection widget with calendar overlay
61    DatePicker,
62    /// Time selection widget with hour/minute/second picker
63    TimePicker,
64    /// Color selection widget with color picker overlay
65    ColorPicker,
66    Menu,
67    MenuItem,
68    MenuSeparator,
69    ContextMenu,
70    Float,
71    // Data display
72    DataTable,
73    DataColumn,
74    // Tree widget
75    TreeView,
76    TreeNode,
77    // Control flow
78    For,
79    If,
80    Custom(String),
81}
82
83/// A value that can be either static or dynamically bound
84#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
85pub enum AttributeValue {
86    Static(String),
87    Binding(crate::expr::BindingExpr),
88    Interpolated(Vec<InterpolatedPart>),
89}
90
91impl Default for AttributeValue {
92    fn default() -> Self {
93        AttributeValue::Static(String::new())
94    }
95}
96
97/// Part of an interpolated string
98#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
99pub enum InterpolatedPart {
100    Literal(String),
101    Binding(crate::expr::BindingExpr),
102}
103
104impl Default for InterpolatedPart {
105    fn default() -> Self {
106        InterpolatedPart::Literal(String::new())
107    }
108}
109
110/// Attribute structures for advanced widgets
111#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
112pub struct ComboBoxAttributes {
113    pub options: Vec<String>,
114    pub selected: Option<crate::expr::BindingExpr>,
115    pub placeholder: Option<String>,
116    pub on_select: Option<String>,
117}
118
119#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
120pub struct PickListAttributes {
121    pub options: Vec<String>,
122    pub selected: Option<crate::expr::BindingExpr>,
123    pub placeholder: Option<String>,
124    pub on_select: Option<String>,
125}
126
127#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
128pub struct CanvasAttributes {
129    pub width: f32,
130    pub height: f32,
131    pub program: Option<crate::expr::BindingExpr>,
132    pub on_click: Option<String>,
133}
134
135#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
136pub struct ProgressBarAttributes {
137    pub min: Option<f32>,
138    pub max: Option<f32>,
139    pub value: crate::expr::BindingExpr,
140    pub style: Option<ProgressBarStyle>,
141}
142
143#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
144pub enum ProgressBarStyle {
145    Primary,
146    Success,
147    Warning,
148    Danger,
149    Secondary,
150}
151
152#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
153pub struct TooltipAttributes {
154    pub message: String,
155    pub position: Option<TooltipPosition>,
156    pub delay: Option<u64>,
157}
158
159#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
160pub enum TooltipPosition {
161    FollowCursor,
162    Top,
163    Bottom,
164    Left,
165    Right,
166}
167
168#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
169pub struct GridAttributes {
170    pub columns: u32,
171    pub spacing: Option<f32>,
172    pub padding: Option<f32>,
173}
174
175#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
176pub struct FloatAttributes {
177    pub position: Option<FloatPosition>,
178    pub offset_x: Option<f32>,
179    pub offset_y: Option<f32>,
180    pub z_index: Option<u32>,
181}
182
183#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
184pub enum FloatPosition {
185    TopLeft,
186    TopRight,
187    BottomLeft,
188    BottomRight,
189}
190
191/// An event binding from XML to a Rust handler
192#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
193pub struct EventBinding {
194    pub event: EventKind,
195    pub handler: String,
196    /// Optional parameter expression (e.g., for on_click="delete:{item.id}")
197    pub param: Option<crate::expr::BindingExpr>,
198    pub span: Span,
199}
200
201/// Supported event types
202#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
203pub enum EventKind {
204    #[default]
205    Click,
206    Press,
207    Release,
208    Change,
209    Input,
210    Submit,
211    Select,
212    Toggle,
213    Scroll,
214    CanvasClick,
215    CanvasDrag,
216    CanvasMove,
217    CanvasRelease,
218    RowClick,
219    Cancel,
220    Open,
221    Close,
222}
223
224impl std::fmt::Display for WidgetKind {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        let name = match self {
227            WidgetKind::Column => "column",
228            WidgetKind::Row => "row",
229            WidgetKind::Container => "container",
230            WidgetKind::Scrollable => "scrollable",
231            WidgetKind::Stack => "stack",
232            WidgetKind::Text => "text",
233            WidgetKind::Image => "image",
234            WidgetKind::Svg => "svg",
235            WidgetKind::Button => "button",
236            WidgetKind::TextInput => "text_input",
237            WidgetKind::Checkbox => "checkbox",
238            WidgetKind::Slider => "slider",
239            WidgetKind::PickList => "pick_list",
240            WidgetKind::Toggler => "toggler",
241            WidgetKind::Space => "space",
242            WidgetKind::Rule => "rule",
243            WidgetKind::Radio => "radio",
244            WidgetKind::ComboBox => "combobox",
245            WidgetKind::ProgressBar => "progress_bar",
246            WidgetKind::Tooltip => "tooltip",
247            WidgetKind::Grid => "grid",
248            WidgetKind::Canvas => "canvas",
249            WidgetKind::CanvasRect => "rect",
250            WidgetKind::CanvasCircle => "circle",
251            WidgetKind::CanvasLine => "line",
252            WidgetKind::CanvasText => "canvas_text",
253            WidgetKind::CanvasGroup => "group",
254            WidgetKind::DatePicker => "date_picker",
255            WidgetKind::TimePicker => "time_picker",
256            WidgetKind::ColorPicker => "color_picker",
257            WidgetKind::Menu => "menu",
258            WidgetKind::MenuItem => "menu_item",
259            WidgetKind::MenuSeparator => "menu_separator",
260            WidgetKind::ContextMenu => "context_menu",
261            WidgetKind::Float => "float",
262            WidgetKind::DataTable => "data_table",
263            WidgetKind::DataColumn => "data_column",
264            WidgetKind::TreeView => "tree_view",
265            WidgetKind::TreeNode => "tree_node",
266            WidgetKind::For => "for",
267            WidgetKind::If => "if",
268            WidgetKind::Custom(name) => return write!(f, "{}", name),
269        };
270        write!(f, "{}", name)
271    }
272}
273
274impl WidgetKind {
275    /// Returns a list of all standard widget tag names.
276    pub fn all_standard() -> &'static [&'static str] {
277        &[
278            "column",
279            "row",
280            "container",
281            "scrollable",
282            "stack",
283            "text",
284            "image",
285            "svg",
286            "button",
287            "text_input",
288            "checkbox",
289            "slider",
290            "pick_list",
291            "toggler",
292            "space",
293            "rule",
294            "radio",
295            "combobox",
296            "progress_bar",
297            "tooltip",
298            "grid",
299            "canvas",
300            "rect",
301            "circle",
302            "line",
303            "canvas_text",
304            "group",
305            "date_picker",
306            "time_picker",
307            "color_picker",
308            "menu",
309            "menu_item",
310            "menu_separator",
311            "context_menu",
312            "float",
313            "data_table",
314            "data_column",
315            "tree_view",
316            "tree_node",
317            "for",
318            "if",
319        ]
320    }
321
322    /// Returns true if this is a custom widget.
323    pub fn is_custom(&self) -> bool {
324        matches!(self, WidgetKind::Custom(_))
325    }
326
327    /// Returns the minimum schema version required for this widget type.
328    ///
329    /// This method provides infrastructure for version-gating widgets in future releases.
330    /// Currently, all widgets return version 1.0 as they are part of the initial release.
331    ///
332    /// # Future Usage
333    ///
334    /// When new widgets are added in future schema versions (e.g., 1.1, 1.2), this method
335    /// will be updated to return the appropriate minimum version for those widgets.
336    /// The parser can then validate that documents declaring older schema versions
337    /// do not use widgets that were introduced in later versions.
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// use dampen_core::{WidgetKind, SchemaVersion};
343    ///
344    /// let column = WidgetKind::Column;
345    /// assert_eq!(column.minimum_version(), SchemaVersion { major: 1, minor: 0 });
346    /// ```
347    ///
348    /// # Returns
349    ///
350    /// The minimum `SchemaVersion` required to use this widget type.
351    pub fn minimum_version(&self) -> crate::ir::SchemaVersion {
352        // Canvas is a v1.1 widget (experimental, not fully functional)
353        // All other widgets are part of v1.0
354        match self {
355            WidgetKind::Canvas => crate::ir::SchemaVersion { major: 1, minor: 1 },
356            WidgetKind::DatePicker
357            | WidgetKind::TimePicker
358            | WidgetKind::ColorPicker
359            | WidgetKind::Menu
360            | WidgetKind::MenuItem
361            | WidgetKind::MenuSeparator
362            | WidgetKind::ContextMenu
363            | WidgetKind::DataTable
364            | WidgetKind::DataColumn
365            | WidgetKind::TreeView
366            | WidgetKind::TreeNode => crate::ir::SchemaVersion { major: 1, minor: 1 },
367            _ => crate::ir::SchemaVersion { major: 1, minor: 0 },
368        }
369    }
370
371    /// Returns the validation schema for this widget type.
372    pub fn schema(&self) -> crate::schema::WidgetSchema {
373        crate::schema::get_widget_schema(self)
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::ir::style::StyleProperties;
381    use crate::ir::theme::WidgetState;
382
383    #[test]
384    fn test_widget_node_default_has_empty_inline_state_variants() {
385        let node = WidgetNode::default();
386        assert!(node.inline_state_variants.is_empty());
387    }
388
389    #[test]
390    fn test_widget_node_inline_state_variants_serialization() {
391        let mut node = WidgetNode {
392            kind: WidgetKind::Button,
393            id: Some("test-button".to_string()),
394            attributes: Default::default(),
395            events: Default::default(),
396            children: Default::default(),
397            span: Default::default(),
398            style: Default::default(),
399            layout: Default::default(),
400            theme_ref: Default::default(),
401            classes: Default::default(),
402            breakpoint_attributes: Default::default(),
403            inline_state_variants: Default::default(),
404        };
405
406        // Add state variant
407        node.inline_state_variants.insert(
408            WidgetState::Hover,
409            StyleProperties {
410                opacity: Some(0.8),
411                ..Default::default()
412            },
413        );
414
415        // Serialize and deserialize
416        let json = serde_json::to_string(&node).expect("Should serialize");
417        let deserialized: WidgetNode = serde_json::from_str(&json).expect("Should deserialize");
418
419        // Verify field preserved
420        assert_eq!(deserialized.inline_state_variants.len(), 1);
421        assert!(
422            deserialized
423                .inline_state_variants
424                .contains_key(&WidgetState::Hover)
425        );
426    }
427
428    #[test]
429    fn test_widget_node_inline_state_variants_multiple_states() {
430        let mut node = WidgetNode::default();
431
432        node.inline_state_variants.insert(
433            WidgetState::Hover,
434            StyleProperties {
435                opacity: Some(0.9),
436                ..Default::default()
437            },
438        );
439
440        node.inline_state_variants.insert(
441            WidgetState::Active,
442            StyleProperties {
443                opacity: Some(0.7),
444                ..Default::default()
445            },
446        );
447
448        node.inline_state_variants.insert(
449            WidgetState::Disabled,
450            StyleProperties {
451                opacity: Some(0.5),
452                ..Default::default()
453            },
454        );
455
456        assert_eq!(node.inline_state_variants.len(), 3);
457        assert!(node.inline_state_variants.contains_key(&WidgetState::Hover));
458        assert!(
459            node.inline_state_variants
460                .contains_key(&WidgetState::Active)
461        );
462        assert!(
463            node.inline_state_variants
464                .contains_key(&WidgetState::Disabled)
465        );
466    }
467}
468
469// Canvas Shape Types
470
471/// A declarative representation of a shape in a canvas widget.
472#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
473pub enum CanvasShape {
474    /// A rectangle shape.
475    Rect(RectShape),
476    /// A circle shape.
477    Circle(CircleShape),
478    /// A line segment.
479    Line(LineShape),
480    /// Text drawn on the canvas.
481    Text(TextShape),
482    /// A group of shapes with a common transformation.
483    Group(GroupShape),
484}
485
486/// Attributes for a declarative rectangle shape.
487#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
488pub struct RectShape {
489    /// The X coordinate of the top-left corner.
490    pub x: AttributeValue,
491    /// The Y coordinate of the top-left corner.
492    pub y: AttributeValue,
493    /// The width of the rectangle.
494    pub width: AttributeValue,
495    /// The height of the rectangle.
496    pub height: AttributeValue,
497    /// The fill color.
498    pub fill: Option<AttributeValue>,
499    /// The stroke color.
500    pub stroke: Option<AttributeValue>,
501    /// The width of the stroke.
502    pub stroke_width: Option<AttributeValue>,
503    /// The corner radius for rounded rectangles.
504    pub radius: Option<AttributeValue>,
505}
506
507/// Attributes for a declarative circle shape.
508#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
509pub struct CircleShape {
510    /// The X coordinate of the center.
511    pub cx: AttributeValue,
512    /// The Y coordinate of the center.
513    pub cy: AttributeValue,
514    /// The radius of the circle.
515    pub radius: AttributeValue,
516    /// The fill color.
517    pub fill: Option<AttributeValue>,
518    /// The stroke color.
519    pub stroke: Option<AttributeValue>,
520    /// The width of the stroke.
521    pub stroke_width: Option<AttributeValue>,
522}
523
524/// Attributes for a declarative line shape.
525#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
526pub struct LineShape {
527    /// The X coordinate of the starting point.
528    pub x1: AttributeValue,
529    /// The Y coordinate of the starting point.
530    pub y1: AttributeValue,
531    /// The X coordinate of the ending point.
532    pub x2: AttributeValue,
533    /// The Y coordinate of the ending point.
534    pub y2: AttributeValue,
535    /// The stroke color.
536    pub stroke: Option<AttributeValue>,
537    /// The width of the stroke.
538    pub stroke_width: Option<AttributeValue>,
539}
540
541/// Attributes for a declarative text element.
542#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
543pub struct TextShape {
544    /// The X coordinate of the text baseline.
545    pub x: AttributeValue,
546    /// The Y coordinate of the text baseline.
547    pub y: AttributeValue,
548    /// The content to be displayed.
549    pub content: AttributeValue,
550    /// The font size.
551    pub size: Option<AttributeValue>,
552    /// The text color.
553    pub color: Option<AttributeValue>,
554}
555
556/// Attributes for a group of canvas shapes.
557#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
558pub struct GroupShape {
559    /// An optional transformation to apply to all child shapes.
560    pub transform: Option<Transform>,
561    /// The child shapes within this group.
562    pub children: Vec<CanvasShape>,
563}
564
565/// Geometric transformations for canvas groups.
566#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
567pub enum Transform {
568    /// Translate by (x, y).
569    Translate(f32, f32),
570    /// Rotate by an angle in radians.
571    Rotate(f32),
572    /// Uniformly scale.
573    Scale(f32),
574    /// Scale non-uniformly by (x, y).
575    ScaleXY(f32, f32),
576    /// A 2D transformation matrix.
577    Matrix([f32; 6]),
578}