use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::events::EventHandler;
static ELEMENT_ID_COUNTER: AtomicU32 = AtomicU32::new(1);
fn next_id() -> u32 {
ELEMENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
}
pub struct Screen {
pub root: Element,
pub stylesheet: Option<String>,
}
impl Screen {
pub fn new(root: Element) -> Self {
Self { root, stylesheet: None }
}
pub fn with_stylesheet(mut self, path: impl Into<String>) -> Self {
self.stylesheet = Some(path.into());
self
}
pub fn to_json(&self) -> String {
self.root.to_json()
}
}
#[derive(Debug)]
pub struct Element {
pub id: u32,
pub tag: &'static str,
pub class: Option<String>,
pub attrs: HashMap<&'static str, String>,
pub text: Option<String>,
pub children: Vec<Element>,
pub handlers: Vec<EventHandler>,
}
impl Element {
pub fn new(tag: &'static str) -> Self {
Self {
id: next_id(),
tag,
class: None,
attrs: HashMap::new(),
text: None,
children: Vec::new(),
handlers: Vec::new(),
}
}
pub fn class(mut self, cls: impl Into<String>) -> Self {
self.class = Some(cls.into());
self
}
pub fn attr(mut self, key: &'static str, value: impl Into<String>) -> Self {
self.attrs.insert(key, value.into());
self
}
pub fn text(mut self, content: impl Into<String>) -> Self {
self.text = Some(content.into());
self
}
pub fn child(mut self, child: Element) -> Self {
self.children.push(child);
self
}
pub fn on(mut self, handler: EventHandler) -> Self {
self.handlers.push(handler);
self
}
pub fn h1() -> Self { Self::new("h1") }
pub fn h2() -> Self { Self::new("h2") }
pub fn h3() -> Self { Self::new("h3") }
pub fn p() -> Self { Self::new("p") }
pub fn button() -> Self { Self::new("button") }
pub fn img() -> Self { Self::new("img") }
pub fn input() -> Self { Self::new("input") }
pub fn div() -> Self { Self::new("div") }
pub fn span() -> Self { Self::new("span") }
pub fn a() -> Self { Self::new("a") }
pub fn to_json(&self) -> String {
serde_json::to_string(&self.to_value()).unwrap_or_default()
}
pub fn to_value(&self) -> serde_json::Value {
use serde_json::json;
let children: Vec<serde_json::Value> = self.children
.iter()
.map(|c| c.to_value())
.collect();
let handlers: Vec<&str> = self.handlers
.iter()
.map(|h| h.event)
.collect();
let attrs: serde_json::Map<String, serde_json::Value> = self.attrs
.iter()
.map(|(k, v)| (k.to_string(), json!(v)))
.collect();
json!({
"id": self.id,
"tag": self.tag,
"class": self.class,
"text": self.text,
"attrs": attrs,
"handlers": handlers,
"children": children,
})
}
pub fn collect_handlers(&self, out: &mut Vec<(u32, EventHandler)>) {
for handler in &self.handlers {
out.push((self.id, handler.clone()));
}
for child in &self.children {
child.collect_handlers(out);
}
}
pub fn debug_render(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
let class_str = self.class.as_ref()
.map(|c| format!(r#" class="{}""#, c))
.unwrap_or_default();
let handler_str: String = self.handlers.iter()
.map(|h| format!(" on{}=[fn]", h.event))
.collect();
if self.children.is_empty() && self.text.is_none() {
return format!("{}<{}#{}{}{} />\n", pad, self.tag, self.id, class_str, handler_str);
}
let mut out = format!("{}<{}#{}{}{}>", pad, self.tag, self.id, class_str, handler_str);
if let Some(t) = &self.text { out.push_str(t); }
if !self.children.is_empty() {
out.push('\n');
for child in &self.children {
out.push_str(&child.debug_render(indent + 1));
}
out.push_str(&pad);
}
out.push_str(&format!("</{}>\n", self.tag));
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn element_gets_unique_ids() {
let a = Element::div();
let b = Element::div();
assert_ne!(a.id, b.id);
}
#[test]
fn element_builder_chain() {
let el = Element::h1().class("title").text("Hello, Bubba!");
assert_eq!(el.tag, "h1");
assert_eq!(el.class.as_deref(), Some("title"));
assert_eq!(el.text.as_deref(), Some("Hello, Bubba!"));
}
#[test]
fn to_json_contains_expected_fields() {
let el = Element::button()
.class("primary-btn")
.text("Tap me");
let json = el.to_json();
assert!(json.contains("\"tag\":\"button\""));
assert!(json.contains("\"class\":\"primary-btn\""));
assert!(json.contains("\"text\":\"Tap me\""));
}
#[test]
fn collect_handlers_finds_nested() {
use crate::events::EventHandler;
let el = Element::div()
.child(
Element::button()
.on(EventHandler::onclick(|_| {}))
);
let mut handlers = Vec::new();
el.collect_handlers(&mut handlers);
assert_eq!(handlers.len(), 1);
}
}