1use std::collections::HashMap;
10use std::sync::atomic::{AtomicU32, Ordering};
11use crate::events::EventHandler;
12
13static ELEMENT_ID_COUNTER: AtomicU32 = AtomicU32::new(1);
15
16fn next_id() -> u32 {
17 ELEMENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
18}
19
20pub struct Screen {
30 pub root: Element,
32 pub stylesheet: Option<String>,
34}
35
36impl Screen {
37 pub fn new(root: Element) -> Self {
39 Self { root, stylesheet: None }
40 }
41
42 pub fn with_stylesheet(mut self, path: impl Into<String>) -> Self {
44 self.stylesheet = Some(path.into());
45 self
46 }
47
48 pub fn to_json(&self) -> String {
51 self.root.to_json()
52 }
53}
54
55#[derive(Debug)]
57pub struct Element {
58 pub id: u32,
60 pub tag: &'static str,
62 pub class: Option<String>,
64 pub attrs: HashMap<&'static str, String>,
66 pub text: Option<String>,
68 pub children: Vec<Element>,
70 pub handlers: Vec<EventHandler>,
72}
73
74impl Element {
75 pub fn new(tag: &'static str) -> Self {
77 Self {
78 id: next_id(),
79 tag,
80 class: None,
81 attrs: HashMap::new(),
82 text: None,
83 children: Vec::new(),
84 handlers: Vec::new(),
85 }
86 }
87
88 pub fn class(mut self, cls: impl Into<String>) -> Self {
90 self.class = Some(cls.into());
91 self
92 }
93
94 pub fn attr(mut self, key: &'static str, value: impl Into<String>) -> Self {
96 self.attrs.insert(key, value.into());
97 self
98 }
99
100 pub fn text(mut self, content: impl Into<String>) -> Self {
102 self.text = Some(content.into());
103 self
104 }
105
106 pub fn child(mut self, child: Element) -> Self {
108 self.children.push(child);
109 self
110 }
111
112 pub fn on(mut self, handler: EventHandler) -> Self {
114 self.handlers.push(handler);
115 self
116 }
117
118 pub fn h1() -> Self { Self::new("h1") }
122 pub fn h2() -> Self { Self::new("h2") }
124 pub fn h3() -> Self { Self::new("h3") }
126 pub fn p() -> Self { Self::new("p") }
128 pub fn button() -> Self { Self::new("button") }
130 pub fn img() -> Self { Self::new("img") }
132 pub fn input() -> Self { Self::new("input") }
134 pub fn div() -> Self { Self::new("div") }
136 pub fn span() -> Self { Self::new("span") }
138 pub fn a() -> Self { Self::new("a") }
140
141 pub fn to_json(&self) -> String {
156 serde_json::to_string(&self.to_value()).unwrap_or_default()
157 }
158
159 pub fn to_value(&self) -> serde_json::Value {
161 use serde_json::json;
162
163 let children: Vec<serde_json::Value> = self.children
164 .iter()
165 .map(|c| c.to_value())
166 .collect();
167
168 let handlers: Vec<&str> = self.handlers
170 .iter()
171 .map(|h| h.event)
172 .collect();
173
174 let attrs: serde_json::Map<String, serde_json::Value> = self.attrs
175 .iter()
176 .map(|(k, v)| (k.to_string(), json!(v)))
177 .collect();
178
179 json!({
180 "id": self.id,
181 "tag": self.tag,
182 "class": self.class,
183 "text": self.text,
184 "attrs": attrs,
185 "handlers": handlers,
186 "children": children,
187 })
188 }
189
190 pub fn collect_handlers(&self, out: &mut Vec<(u32, EventHandler)>) {
193 for handler in &self.handlers {
194 out.push((self.id, handler.clone()));
195 }
196 for child in &self.children {
197 child.collect_handlers(out);
198 }
199 }
200
201 pub fn debug_render(&self, indent: usize) -> String {
203 let pad = " ".repeat(indent);
204 let class_str = self.class.as_ref()
205 .map(|c| format!(r#" class="{}""#, c))
206 .unwrap_or_default();
207 let handler_str: String = self.handlers.iter()
208 .map(|h| format!(" on{}=[fn]", h.event))
209 .collect();
210
211 if self.children.is_empty() && self.text.is_none() {
212 return format!("{}<{}#{}{}{} />\n", pad, self.tag, self.id, class_str, handler_str);
213 }
214
215 let mut out = format!("{}<{}#{}{}{}>", pad, self.tag, self.id, class_str, handler_str);
216 if let Some(t) = &self.text { out.push_str(t); }
217 if !self.children.is_empty() {
218 out.push('\n');
219 for child in &self.children {
220 out.push_str(&child.debug_render(indent + 1));
221 }
222 out.push_str(&pad);
223 }
224 out.push_str(&format!("</{}>\n", self.tag));
225 out
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn element_gets_unique_ids() {
235 let a = Element::div();
236 let b = Element::div();
237 assert_ne!(a.id, b.id);
238 }
239
240 #[test]
241 fn element_builder_chain() {
242 let el = Element::h1().class("title").text("Hello, Bubba!");
243 assert_eq!(el.tag, "h1");
244 assert_eq!(el.class.as_deref(), Some("title"));
245 assert_eq!(el.text.as_deref(), Some("Hello, Bubba!"));
246 }
247
248 #[test]
249 fn to_json_contains_expected_fields() {
250 let el = Element::button()
251 .class("primary-btn")
252 .text("Tap me");
253 let json = el.to_json();
254 assert!(json.contains("\"tag\":\"button\""));
255 assert!(json.contains("\"class\":\"primary-btn\""));
256 assert!(json.contains("\"text\":\"Tap me\""));
257 }
258
259 #[test]
260 fn collect_handlers_finds_nested() {
261 use crate::events::EventHandler;
262 let el = Element::div()
263 .child(
264 Element::button()
265 .on(EventHandler::onclick(|_| {}))
266 );
267 let mut handlers = Vec::new();
268 el.collect_handlers(&mut handlers);
269 assert_eq!(handlers.len(), 1);
270 }
271}