Skip to main content

pepl_ui/
surface.rs

1use crate::prop_value::PropValue;
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5/// The complete abstract UI tree produced by evaluating a PEPL `view` function.
6///
7/// A `Surface` is the top-level container that wraps the root [`SurfaceNode`].
8/// The host serializes this to JSON and renders it via its View Layer.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct Surface {
11    /// The root node of the UI tree.
12    pub root: SurfaceNode,
13}
14
15/// A single node in the abstract UI tree.
16///
17/// Matches the JSON schema from `host-integration.md`:
18/// ```json
19/// {
20///   "type": "Column",
21///   "props": { "spacing": 8 },
22///   "children": [ ... ]
23/// }
24/// ```
25///
26/// Props use [`BTreeMap`] for deterministic serialization order.
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct SurfaceNode {
29    /// Component type name (e.g., "Column", "Text", "Button").
30    #[serde(rename = "type")]
31    pub component_type: String,
32
33    /// Component properties. Uses `BTreeMap` for deterministic key ordering.
34    pub props: BTreeMap<String, PropValue>,
35
36    /// Child nodes (empty for leaf components like Text, Button).
37    pub children: Vec<SurfaceNode>,
38}
39
40// ── Constructors ──────────────────────────────────────────────────────────────
41
42impl Surface {
43    /// Create a new Surface wrapping a root node.
44    pub fn new(root: SurfaceNode) -> Self {
45        Self { root }
46    }
47
48    /// Serialize this Surface to JSON (deterministic output).
49    pub fn to_json(&self) -> String {
50        serde_json::to_string(self).expect("Surface serialization should never fail")
51    }
52
53    /// Serialize this Surface to pretty-printed JSON.
54    pub fn to_json_pretty(&self) -> String {
55        serde_json::to_string_pretty(self).expect("Surface serialization should never fail")
56    }
57}
58
59impl SurfaceNode {
60    /// Create a new node with the given component type and no props or children.
61    pub fn new(component_type: impl Into<String>) -> Self {
62        Self {
63            component_type: component_type.into(),
64            props: BTreeMap::new(),
65            children: Vec::new(),
66        }
67    }
68
69    /// Builder: add a prop.
70    pub fn with_prop(mut self, key: impl Into<String>, value: PropValue) -> Self {
71        self.props.insert(key.into(), value);
72        self
73    }
74
75    /// Builder: add a child node.
76    pub fn with_child(mut self, child: SurfaceNode) -> Self {
77        self.children.push(child);
78        self
79    }
80
81    /// Builder: set children.
82    pub fn with_children(mut self, children: Vec<SurfaceNode>) -> Self {
83        self.children = children;
84        self
85    }
86
87    /// Add a prop (mutable).
88    pub fn set_prop(&mut self, key: impl Into<String>, value: PropValue) {
89        self.props.insert(key.into(), value);
90    }
91
92    /// Add a child (mutable).
93    pub fn add_child(&mut self, child: SurfaceNode) {
94        self.children.push(child);
95    }
96}