1use sim_kernel::Expr;
22use sim_lib_view::SurfaceCaps;
23
24pub fn render_scene(scene: &Expr, caps: &SurfaceCaps) -> String {
31 let reduced = sim_lib_view::codec::reduce_for_caps(scene, caps);
32 render_node(&reduced).join("\n")
33}
34
35fn render_node(node: &Expr) -> Vec<String> {
37 let Some(kind) = sim_lib_scene::node_kind(node) else {
38 return vec![atom_text(node)];
40 };
41 match &*kind.name {
42 "text" => vec![text_content(node)],
43 "stack" | "box" | "overlay" => render_children(node),
44 "grid" | "table" => render_rows(node),
45 "field" => vec![render_field(node)],
46 "button" => vec![format!("[{}]", field_text(node, "label"))],
47 "badge" => vec![format!(
48 "<{}: {}>",
49 field_text(node, "status"),
50 field_text(node, "label")
51 )],
52 other => vec![format!("[{other}]")],
54 }
55}
56
57fn render_children(node: &Expr) -> Vec<String> {
59 let mut lines = Vec::new();
60 if let Some(Expr::List(items) | Expr::Vector(items)) =
61 sim_value::access::field(node, "children")
62 {
63 for child in items {
64 lines.extend(render_node(child));
65 }
66 }
67 lines
68}
69
70fn render_rows(node: &Expr) -> Vec<String> {
73 let mut lines = Vec::new();
74 if let Some(Expr::List(cols) | Expr::Vector(cols)) = sim_value::access::field(node, "columns") {
75 lines.push(join_cells(cols));
76 }
77 if let Some(Expr::List(rows) | Expr::Vector(rows)) = sim_value::access::field(node, "rows") {
78 for row in rows {
79 lines.push(render_row(row));
80 }
81 }
82 lines
83}
84
85fn render_row(row: &Expr) -> String {
88 match row {
89 Expr::List(cells) | Expr::Vector(cells) => join_cells(cells),
90 Expr::Map(entries) => entries
91 .iter()
92 .map(|(_, value)| atom_text(value))
93 .collect::<Vec<_>>()
94 .join(" | "),
95 atom => atom_text(atom),
96 }
97}
98
99fn join_cells(cells: &[Expr]) -> String {
101 cells.iter().map(atom_text).collect::<Vec<_>>().join(" | ")
102}
103
104fn render_field(node: &Expr) -> String {
107 let value = field_text(node, "value");
108 match sim_value::access::field_str(node, "label") {
109 Some(label) => format!("{label}: {value}"),
110 None => value,
111 }
112}
113
114fn text_content(node: &Expr) -> String {
117 for key in ["text", "content"] {
118 if let Some(value) = sim_value::access::field(node, key) {
119 return atom_text(value);
120 }
121 }
122 String::new()
123}
124
125fn field_text(node: &Expr, name: &str) -> String {
127 sim_value::access::field(node, name)
128 .map(atom_text)
129 .unwrap_or_default()
130}
131
132fn atom_text(value: &Expr) -> String {
134 match value {
135 Expr::Nil => "nil".to_owned(),
136 Expr::Bool(flag) => flag.to_string(),
137 Expr::Number(number) => number.canonical.clone(),
138 Expr::String(text) => text.clone(),
139 Expr::Symbol(symbol) | Expr::Local(symbol) => symbol.as_qualified_str(),
140 Expr::Bytes(bytes) => format!("#bytes({})", bytes.len()),
141 Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
142 items.iter().map(atom_text).collect::<Vec<_>>().join(" ")
143 }
144 Expr::Map(entries) => entries
145 .iter()
146 .map(|(key, value)| format!("{}={}", atom_text(key), atom_text(value)))
147 .collect::<Vec<_>>()
148 .join(" "),
149 other => format!("<{}>", sim_value::kind::expr_kind(other)),
150 }
151}