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