1use std::collections::HashMap;
8use crate::events::EventHandler;
9
10pub struct Screen {
20 pub root: Element,
22 pub stylesheet: Option<String>,
24}
25
26impl Screen {
27 pub fn new(root: Element) -> Self {
29 Self { root, stylesheet: None }
30 }
31
32 pub fn with_stylesheet(mut self, path: impl Into<String>) -> Self {
34 self.stylesheet = Some(path.into());
35 self
36 }
37}
38
39#[derive(Debug)]
41pub struct Element {
42 pub tag: &'static str,
44 pub class: Option<String>,
46 pub attrs: HashMap<&'static str, String>,
48 pub text: Option<String>,
50 pub children: Vec<Element>,
52 pub handlers: Vec<EventHandler>,
54}
55
56impl Element {
57 pub fn new(tag: &'static str) -> Self {
59 Self {
60 tag,
61 class: None,
62 attrs: HashMap::new(),
63 text: None,
64 children: Vec::new(),
65 handlers: Vec::new(),
66 }
67 }
68
69 pub fn class(mut self, cls: impl Into<String>) -> Self {
71 self.class = Some(cls.into());
72 self
73 }
74
75 pub fn attr(mut self, key: &'static str, value: impl Into<String>) -> Self {
77 self.attrs.insert(key, value.into());
78 self
79 }
80
81 pub fn text(mut self, content: impl Into<String>) -> Self {
83 self.text = Some(content.into());
84 self
85 }
86
87 pub fn child(mut self, child: Element) -> Self {
89 self.children.push(child);
90 self
91 }
92
93 pub fn on(mut self, handler: EventHandler) -> Self {
95 self.handlers.push(handler);
96 self
97 }
98
99 pub fn h1() -> Self { Self::new("h1") }
103 pub fn h2() -> Self { Self::new("h2") }
105 pub fn h3() -> Self { Self::new("h3") }
107 pub fn p() -> Self { Self::new("p") }
109 pub fn button() -> Self { Self::new("button") }
111 pub fn img() -> Self { Self::new("img") }
113 pub fn input() -> Self { Self::new("input") }
115 pub fn div() -> Self { Self::new("div") }
117 pub fn span() -> Self { Self::new("span") }
119 pub fn a() -> Self { Self::new("a") }
121
122 pub fn debug_render(&self, indent: usize) -> String {
124 let pad = " ".repeat(indent * 2);
125 let class_str = self
126 .class
127 .as_ref()
128 .map(|c| format!(r#" class="{}""#, c))
129 .unwrap_or_default();
130
131 let attrs_str: String = self
132 .attrs
133 .iter()
134 .map(|(k, v)| format!(r#" {}="{}""#, k, v))
135 .collect();
136
137 let handlers_str: String = self
138 .handlers
139 .iter()
140 .map(|h| format!(" {}=[fn]", h.event))
141 .collect();
142
143 if self.children.is_empty() && self.text.is_none() {
144 return format!("{}<{}{}{}{} />\n", pad, self.tag, class_str, attrs_str, handlers_str);
145 }
146
147 let mut out = format!("{}<{}{}{}{}>", pad, self.tag, class_str, attrs_str, handlers_str);
148 if let Some(text) = &self.text {
149 out.push_str(&format!("{}", text));
150 }
151 if !self.children.is_empty() {
152 out.push('\n');
153 for child in &self.children {
154 out.push_str(&child.debug_render(indent + 1));
155 }
156 out.push_str(&pad);
157 }
158 out.push_str(&format!("</{}>\n", self.tag));
159 out
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn element_builder_chain() {
169 let el = Element::h1()
170 .class("title")
171 .text("Hello, Bubba!");
172 assert_eq!(el.tag, "h1");
173 assert_eq!(el.class.as_deref(), Some("title"));
174 assert_eq!(el.text.as_deref(), Some("Hello, Bubba!"));
175 }
176
177 #[test]
178 fn debug_render_self_closing() {
179 let el = Element::img().attr("src", "avatar.png").class("avatar");
180 let rendered = el.debug_render(0);
181 assert!(rendered.contains("<img"));
182 assert!(rendered.contains("/>"));
183 }
184}