#![allow(dead_code)]
use std::sync::{Mutex, OnceLock};
use swiftui_native::prelude::*;
#[path = "common/spec.rs"]
mod spec;
#[cfg(target_os = "macos")]
#[path = "common/simulator.rs"]
mod simulator;
fn current() -> &'static Mutex<String> {
static CURRENT: OnceLock<Mutex<String>> = OnceLock::new();
CURRENT.get_or_init(|| Mutex::new(DEMOS[0].1.to_string()))
}
pub fn set_spec(text: &str) {
*current().lock().expect("spec") = text.to_string();
}
pub fn body() -> View {
let text_src = current().lock().expect("spec").clone();
match spec::parse(&text_src).and_then(|nodes| spec::build(&nodes)) {
Ok(v) => v,
Err(e) => vstack_aligned_spacing(
&[
text("Could not build that screen")
.text_style(TextStyle::Headline)
.fg_system(SystemColor::Red),
text(&e).font_size(12.0).fg_system(SystemColor::Secondary),
],
HorizontalAlignment::Leading,
Some(8.0),
)
.padding(20.0),
}
}
fn content_view() -> swiftui_native::ViewHandle {
swiftui_native::state::root_view(|_| body().into_handle())
}
const DEMOS: &[(&str, &str)] = &[
(
"readout",
r#"
// A status readout. Nothing here is a Rust type.
vstack spacing=14 pad=20 align=leading {
text "Deployment" style=large_title bold
text "3 of 4 regions healthy" color=secondary size=13
divider
hstack spacing=12 {
circle color=green frame=14x14
text "us-east" size=14
spacer
text "ok" color=green size=13
}
hstack spacing=12 {
circle color=green frame=14x14
text "us-west" size=14
spacer
text "ok" color=green size=13
}
hstack spacing=12 {
circle color=orange frame=14x14
text "eu-central" size=14
spacer
text "degraded" color=orange size=13
}
hstack spacing=12 {
circle color=gray frame=14x14
text "ap-south" size=14
spacer
text "draining" color=gray size=13
}
divider
text "Rollout" size=12 color=secondary
progress 0.62
spacer
button "Promote"
}
"#,
),
(
"card",
r#"
// A card, to show layering and a hex colour.
zstack align=center frame=320x200 {
color #1d2b53 frame=300x180 corner=16
vstack spacing=10 pad=24 align=leading {
text "Boarding pass" style=title2 bold color=white
text "LHR to SFO" color=white size=14 opacity=0.8
spacer
hstack spacing=16 {
vstack spacing=2 align=leading {
text "GATE" size=10 color=white opacity=0.6
text "B14" style=title3 bold color=white
}
vstack spacing=2 align=leading {
text "SEAT" size=10 color=white opacity=0.6
text "22A" style=title3 bold color=white
}
spacer
image "airplane"
}
}
}
"#,
),
(
"gauges",
r#"
// Three gauges and a grouped block, from data alone.
vstack spacing=16 pad=20 align=leading {
text "Capacity" style=title bold
gauge 0.34 label="CPU"
gauge 0.71 label="Memory"
gauge 0.12 label="Disk"
divider
group_box {
vstack spacing=6 align=leading {
text "Notes" style=headline
text "Every view on this screen was named by a string." size=12 color=secondary
text "The binary has no VStack<...> type baked in for it." size=12 color=secondary
}
}
spacer
}
"#,
),
];
fn demo(name: &str) -> Option<&'static str> {
DEMOS.iter().find(|(n, _)| *n == name).map(|(_, s)| *s)
}
fn load(arg: &str) -> Result<String, String> {
if arg == "-" {
use std::io::Read;
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).map_err(|e| e.to_string())?;
return Ok(s);
}
if let Some(d) = demo(arg) {
return Ok(d.to_string());
}
std::fs::read_to_string(arg).map_err(|e| format!("{arg}: {e}"))
}
fn repl() {
use std::io::{BufRead, Write};
eprintln!("Type a screen. Blank line renders it; Ctrl-D quits.");
eprintln!("Names: {}", spec::NAMES.join(" "));
let stdin = std::io::stdin();
let mut buf = String::new();
let mut n = 0usize;
loop {
eprint!("> ");
let _ = std::io::stderr().flush();
let mut line = String::new();
match stdin.lock().read_line(&mut line) {
Ok(0) => break,
Ok(_) => {}
Err(e) => {
eprintln!("{e}");
break;
}
}
if line.trim().is_empty() {
if buf.trim().is_empty() {
continue;
}
match spec::parse(&buf).and_then(|nodes| spec::build(&nodes)) {
Ok(v) => {
n += 1;
let out = format!(
"{}/../../target/dynamic_ui_repl_{n}.png",
env!("CARGO_MANIFEST_DIR")
);
let handle = v.into_handle();
eprintln!(" {}", swiftui_native::diag::type_name(
swiftui_native::views::meta_of(&handle)
));
if swiftui_native::render::render(&handle, 420, 420).write_png(&out) {
eprintln!(" wrote {out}");
}
}
Err(e) => eprintln!(" {e}"),
}
buf.clear();
} else {
buf.push_str(&line);
}
}
}
fn print_type(arg: &str) -> i32 {
match load(arg).and_then(|s| {
spec::parse(&s).and_then(|nodes| spec::build(&nodes)).map(|v| v.into_handle())
}) {
Ok(h) => {
println!(
"{}",
swiftui_native::diag::type_name(swiftui_native::views::meta_of(&h))
);
0
}
Err(e) => {
eprintln!("{e}");
1
}
}
}
#[cfg(target_os = "macos")]
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let flag = |f: &str| args.iter().any(|a| a == f);
if flag("--names") {
println!("{}", spec::NAMES.join("\n"));
return;
}
if flag("--repl") {
return repl();
}
if let Some(i) = args.iter().position(|a| a == "--type") {
let Some(arg) = args.get(i + 1) else {
eprintln!("--type wants a spec: a path, `-`, or a demo name");
std::process::exit(2);
};
std::process::exit(print_type(arg));
}
if flag("--render") {
for (name, src) in DEMOS {
set_spec(src);
let built = body().into_handle();
let out = format!("{}/../../target/dynamic_ui_{name}.png", env!("CARGO_MANIFEST_DIR"));
let ok = swiftui_native::render::render(&content_view(), 380, 420).write_png(&out);
println!(
"{name:10} {}\n {}",
swiftui_native::diag::type_name(swiftui_native::views::meta_of(&built)),
if ok { out } else { "render failed".to_string() }
);
}
return;
}
if flag("--sim") {
return simulator::main(&simulator::Bundle {
example: "dynamic_ui",
bundle_id: "dev.rswift.DynamicUI",
executable: "DynamicUI",
display_name: "Dynamic UI",
launch_args: &[],
});
}
if let Some(arg) = args.iter().find(|a| !a.starts_with("--")) {
match load(arg) {
Ok(s) => set_spec(&s),
Err(e) => {
eprintln!("{e}");
std::process::exit(1);
}
}
}
show_window(&content_view(), "Dynamic UI", 380.0, 460.0);
}
#[cfg(not(target_os = "macos"))]
fn main() {
swiftui_native::app::run(content_view)
}