1#![allow(dead_code)]
33
34use std::sync::{Mutex, OnceLock};
35use swiftui_native::prelude::*;
36
37#[path = "common/spec.rs"]
38mod spec;
39
40#[cfg(target_os = "macos")]
41#[path = "common/simulator.rs"]
42mod simulator;
43
44fn current() -> &'static Mutex<String> {
51 static CURRENT: OnceLock<Mutex<String>> = OnceLock::new();
52 CURRENT.get_or_init(|| Mutex::new(DEMOS[0].1.to_string()))
53}
54
55pub fn set_spec(text: &str) {
56 *current().lock().expect("spec") = text.to_string();
57}
58
59pub fn body() -> View {
65 let text_src = current().lock().expect("spec").clone();
66 match spec::parse(&text_src).and_then(|nodes| spec::build(&nodes)) {
67 Ok(v) => v,
68 Err(e) => vstack_aligned_spacing(
69 &[
70 text("Could not build that screen")
71 .text_style(TextStyle::Headline)
72 .fg_system(SystemColor::Red),
73 text(&e).font_size(12.0).fg_system(SystemColor::Secondary),
74 ],
75 HorizontalAlignment::Leading,
76 Some(8.0),
77 )
78 .padding(20.0),
79 }
80}
81
82fn content_view() -> swiftui_native::ViewHandle {
83 swiftui_native::state::root_view(|_| body().into_handle())
84}
85
86const DEMOS: &[(&str, &str)] = &[
94 (
95 "readout",
96 r#"
97// A status readout. Nothing here is a Rust type.
98vstack spacing=14 pad=20 align=leading {
99 text "Deployment" style=large_title bold
100 text "3 of 4 regions healthy" color=secondary size=13
101
102 divider
103
104 hstack spacing=12 {
105 circle color=green frame=14x14
106 text "us-east" size=14
107 spacer
108 text "ok" color=green size=13
109 }
110 hstack spacing=12 {
111 circle color=green frame=14x14
112 text "us-west" size=14
113 spacer
114 text "ok" color=green size=13
115 }
116 hstack spacing=12 {
117 circle color=orange frame=14x14
118 text "eu-central" size=14
119 spacer
120 text "degraded" color=orange size=13
121 }
122 hstack spacing=12 {
123 circle color=gray frame=14x14
124 text "ap-south" size=14
125 spacer
126 text "draining" color=gray size=13
127 }
128
129 divider
130 text "Rollout" size=12 color=secondary
131 progress 0.62
132 spacer
133 button "Promote"
134}
135"#,
136 ),
137 (
138 "card",
139 r#"
140// A card, to show layering and a hex colour.
141zstack align=center frame=320x200 {
142 color #1d2b53 frame=300x180 corner=16
143 vstack spacing=10 pad=24 align=leading {
144 text "Boarding pass" style=title2 bold color=white
145 text "LHR to SFO" color=white size=14 opacity=0.8
146 spacer
147 hstack spacing=16 {
148 vstack spacing=2 align=leading {
149 text "GATE" size=10 color=white opacity=0.6
150 text "B14" style=title3 bold color=white
151 }
152 vstack spacing=2 align=leading {
153 text "SEAT" size=10 color=white opacity=0.6
154 text "22A" style=title3 bold color=white
155 }
156 spacer
157 image "airplane"
158 }
159 }
160}
161"#,
162 ),
163 (
164 "gauges",
165 r#"
166// Three gauges and a grouped block, from data alone.
167vstack spacing=16 pad=20 align=leading {
168 text "Capacity" style=title bold
169 gauge 0.34 label="CPU"
170 gauge 0.71 label="Memory"
171 gauge 0.12 label="Disk"
172 divider
173 group_box {
174 vstack spacing=6 align=leading {
175 text "Notes" style=headline
176 text "Every view on this screen was named by a string." size=12 color=secondary
177 text "The binary has no VStack<...> type baked in for it." size=12 color=secondary
178 }
179 }
180 spacer
181}
182"#,
183 ),
184];
185
186fn demo(name: &str) -> Option<&'static str> {
187 DEMOS.iter().find(|(n, _)| *n == name).map(|(_, s)| *s)
188}
189
190fn load(arg: &str) -> Result<String, String> {
192 if arg == "-" {
193 use std::io::Read;
194 let mut s = String::new();
195 std::io::stdin().read_to_string(&mut s).map_err(|e| e.to_string())?;
196 return Ok(s);
197 }
198 if let Some(d) = demo(arg) {
199 return Ok(d.to_string());
200 }
201 std::fs::read_to_string(arg).map_err(|e| format!("{arg}: {e}"))
202}
203
204fn repl() {
209 use std::io::{BufRead, Write};
210 eprintln!("Type a screen. Blank line renders it; Ctrl-D quits.");
211 eprintln!("Names: {}", spec::NAMES.join(" "));
212 let stdin = std::io::stdin();
213 let mut buf = String::new();
214 let mut n = 0usize;
215 loop {
216 eprint!("> ");
217 let _ = std::io::stderr().flush();
218 let mut line = String::new();
219 match stdin.lock().read_line(&mut line) {
220 Ok(0) => break,
221 Ok(_) => {}
222 Err(e) => {
223 eprintln!("{e}");
224 break;
225 }
226 }
227 if line.trim().is_empty() {
228 if buf.trim().is_empty() {
229 continue;
230 }
231 match spec::parse(&buf).and_then(|nodes| spec::build(&nodes)) {
232 Ok(v) => {
233 n += 1;
234 let out = format!(
235 "{}/../../target/dynamic_ui_repl_{n}.png",
236 env!("CARGO_MANIFEST_DIR")
237 );
238 let handle = v.into_handle();
239 eprintln!(" {}", swiftui_native::diag::type_name(
240 swiftui_native::views::meta_of(&handle)
241 ));
242 if swiftui_native::render::render(&handle, 420, 420).write_png(&out) {
243 eprintln!(" wrote {out}");
244 }
245 }
246 Err(e) => eprintln!(" {e}"),
247 }
248 buf.clear();
249 } else {
250 buf.push_str(&line);
251 }
252 }
253}
254
255fn print_type(arg: &str) -> i32 {
261 match load(arg).and_then(|s| {
262 spec::parse(&s).and_then(|nodes| spec::build(&nodes)).map(|v| v.into_handle())
263 }) {
264 Ok(h) => {
265 println!(
266 "{}",
267 swiftui_native::diag::type_name(swiftui_native::views::meta_of(&h))
268 );
269 0
270 }
271 Err(e) => {
272 eprintln!("{e}");
273 1
274 }
275 }
276}
277
278#[cfg(target_os = "macos")]
279fn main() {
280 let args: Vec<String> = std::env::args().skip(1).collect();
281 let flag = |f: &str| args.iter().any(|a| a == f);
282
283 if flag("--names") {
284 println!("{}", spec::NAMES.join("\n"));
285 return;
286 }
287 if flag("--repl") {
288 return repl();
289 }
290 if let Some(i) = args.iter().position(|a| a == "--type") {
291 let Some(arg) = args.get(i + 1) else {
292 eprintln!("--type wants a spec: a path, `-`, or a demo name");
293 std::process::exit(2);
294 };
295 std::process::exit(print_type(arg));
296 }
297 if flag("--render") {
298 for (name, src) in DEMOS {
301 set_spec(src);
302 let built = body().into_handle();
307 let out = format!("{}/../../target/dynamic_ui_{name}.png", env!("CARGO_MANIFEST_DIR"));
308 let ok = swiftui_native::render::render(&content_view(), 380, 420).write_png(&out);
309 println!(
310 "{name:10} {}\n {}",
311 swiftui_native::diag::type_name(swiftui_native::views::meta_of(&built)),
312 if ok { out } else { "render failed".to_string() }
313 );
314 }
315 return;
316 }
317 if flag("--sim") {
318 return simulator::main(&simulator::Bundle {
319 example: "dynamic_ui",
320 bundle_id: "dev.rswift.DynamicUI",
321 executable: "DynamicUI",
322 display_name: "Dynamic UI",
323 launch_args: &[],
324 });
325 }
326
327 if let Some(arg) = args.iter().find(|a| !a.starts_with("--")) {
329 match load(arg) {
330 Ok(s) => set_spec(&s),
331 Err(e) => {
332 eprintln!("{e}");
333 std::process::exit(1);
334 }
335 }
336 }
337 show_window(&content_view(), "Dynamic UI", 380.0, 460.0);
338}
339
340#[cfg(not(target_os = "macos"))]
341fn main() {
342 swiftui_native::app::run(content_view)
343}