Skip to main content

pepl_ui/
prop_value.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4/// A property value in the Surface tree.
5///
6/// Matches the JSON representation used in the host WASM contract.
7/// Uses `BTreeMap` for record props to guarantee deterministic serialization.
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(untagged)]
10pub enum PropValue {
11    /// String value (e.g., `label: "Click me"`).
12    String(String),
13
14    /// Numeric value (e.g., `spacing: 8`).
15    Number(f64),
16
17    /// Boolean value (e.g., `disabled: true`).
18    Bool(bool),
19
20    /// Null / absent value.
21    Nil,
22
23    /// RGBA color as `{ r, g, b, a }` — each 0.0–1.0.
24    Color { r: f64, g: f64, b: f64, a: f64 },
25
26    /// Action reference (e.g., `on_tap: "increment"`).
27    /// Serialized as `{ "__action": "action_name" }` or
28    /// `{ "__action": "action_name", "__args": [...] }`.
29    ActionRef {
30        #[serde(rename = "__action")]
31        action: String,
32        #[serde(rename = "__args", skip_serializing_if = "Option::is_none")]
33        args: Option<Vec<PropValue>>,
34    },
35
36    /// Lambda / callback reference (e.g., `on_change: (s) -> set value = s`).
37    /// Serialized as `{ "__lambda": id }`. The host resolves the lambda at dispatch time.
38    Lambda {
39        #[serde(rename = "__lambda")]
40        lambda_id: u32,
41    },
42
43    /// Ordered list of values.
44    List(Vec<PropValue>),
45
46    /// Named fields. Uses `BTreeMap` for deterministic ordering.
47    Record(BTreeMap<String, PropValue>),
48}
49
50// ── Constructors ──────────────────────────────────────────────────────────────
51
52impl PropValue {
53    /// Create an action reference without arguments.
54    pub fn action(name: impl Into<String>) -> Self {
55        PropValue::ActionRef {
56            action: name.into(),
57            args: None,
58        }
59    }
60
61    /// Create an action reference with arguments.
62    pub fn action_with_args(name: impl Into<String>, args: Vec<PropValue>) -> Self {
63        PropValue::ActionRef {
64            action: name.into(),
65            args: Some(args),
66        }
67    }
68
69    /// Create a lambda reference.
70    pub fn lambda(id: u32) -> Self {
71        PropValue::Lambda { lambda_id: id }
72    }
73
74    /// Create a color value.
75    pub fn color(r: f64, g: f64, b: f64, a: f64) -> Self {
76        PropValue::Color { r, g, b, a }
77    }
78
79    /// Returns the type name for error messages.
80    pub fn type_name(&self) -> &'static str {
81        match self {
82            PropValue::String(_) => "string",
83            PropValue::Number(_) => "number",
84            PropValue::Bool(_) => "bool",
85            PropValue::Nil => "nil",
86            PropValue::Color { .. } => "color",
87            PropValue::ActionRef { .. } => "action",
88            PropValue::Lambda { .. } => "lambda",
89            PropValue::List(_) => "list",
90            PropValue::Record(_) => "record",
91        }
92    }
93}
94
95// ── From impls ────────────────────────────────────────────────────────────────
96
97impl From<&str> for PropValue {
98    fn from(s: &str) -> Self {
99        PropValue::String(s.to_string())
100    }
101}
102
103impl From<String> for PropValue {
104    fn from(s: String) -> Self {
105        PropValue::String(s)
106    }
107}
108
109impl From<f64> for PropValue {
110    fn from(n: f64) -> Self {
111        PropValue::Number(n)
112    }
113}
114
115impl From<i64> for PropValue {
116    fn from(n: i64) -> Self {
117        PropValue::Number(n as f64)
118    }
119}
120
121impl From<bool> for PropValue {
122    fn from(b: bool) -> Self {
123        PropValue::Bool(b)
124    }
125}