rustyclaw_core/canvas/
a2ui.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub enum A2UIMessage {
13 BeginRendering { surface_id: String, root: String },
15
16 SurfaceUpdate {
18 surface_id: String,
19 components: Vec<A2UIComponentDef>,
20 },
21
22 DataModelUpdate {
24 surface_id: String,
25 data: serde_json::Value,
26 },
27
28 DeleteSurface { surface_id: String },
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct A2UIComponentDef {
35 pub id: String,
37
38 pub component: A2UIComponent,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(rename_all = "PascalCase")]
45pub enum A2UIComponent {
46 Column {
48 children: A2UIChildren,
49 #[serde(default)]
50 spacing: Option<String>,
51 },
52
53 Row {
55 children: A2UIChildren,
56 #[serde(default)]
57 spacing: Option<String>,
58 },
59
60 Text {
62 text: A2UITextValue,
63 #[serde(default)]
64 usage_hint: Option<String>,
65 },
66
67 Button {
69 label: A2UITextValue,
70 #[serde(default)]
71 action: Option<String>,
72 },
73
74 Input {
76 #[serde(default)]
77 placeholder: Option<String>,
78 #[serde(default)]
79 value: Option<A2UITextValue>,
80 #[serde(default)]
81 on_change: Option<String>,
82 },
83
84 Image {
86 src: String,
87 #[serde(default)]
88 alt: Option<String>,
89 },
90
91 Markdown { content: A2UITextValue },
93
94 Code {
96 content: A2UITextValue,
97 #[serde(default)]
98 language: Option<String>,
99 },
100
101 Progress {
103 value: f64,
104 #[serde(default)]
105 max: Option<f64>,
106 },
107
108 Spacer {
110 #[serde(default)]
111 size: Option<String>,
112 },
113
114 Divider,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub enum A2UIChildren {
122 ExplicitList(Vec<String>),
124
125 DataBinding { source: String, template: String },
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub enum A2UITextValue {
133 LiteralString(String),
135
136 DataBinding(String),
138}
139
140impl A2UITextValue {
141 pub fn literal(s: impl Into<String>) -> Self {
143 Self::LiteralString(s.into())
144 }
145
146 pub fn binding(expr: impl Into<String>) -> Self {
148 Self::DataBinding(expr.into())
149 }
150}
151
152#[derive(Debug, Clone, Default)]
154pub struct A2UISurface {
155 pub id: String,
157
158 pub root: Option<String>,
160
161 pub components: HashMap<String, A2UIComponent>,
163
164 pub data: serde_json::Value,
166}
167
168impl A2UISurface {
169 pub fn new(id: impl Into<String>) -> Self {
171 Self {
172 id: id.into(),
173 root: None,
174 components: HashMap::new(),
175 data: serde_json::Value::Null,
176 }
177 }
178
179 pub fn apply(&mut self, msg: &A2UIMessage) {
181 match msg {
182 A2UIMessage::BeginRendering { surface_id, root } => {
183 if surface_id == &self.id {
184 self.root = Some(root.clone());
185 }
186 }
187 A2UIMessage::SurfaceUpdate {
188 surface_id,
189 components,
190 } => {
191 if surface_id == &self.id {
192 for comp_def in components {
193 self.components
194 .insert(comp_def.id.clone(), comp_def.component.clone());
195 }
196 }
197 }
198 A2UIMessage::DataModelUpdate { surface_id, data } => {
199 if surface_id == &self.id {
200 self.data = data.clone();
201 }
202 }
203 A2UIMessage::DeleteSurface { .. } => {
204 }
206 }
207 }
208}