use std::sync::{Mutex, OnceLock};
use swiftui_native::prelude::*;
use swiftui_native::{state, views};
#[derive(Clone)]
struct Item {
title: String,
done: bool,
}
fn items() -> &'static Mutex<Vec<Item>> {
static ITEMS: OnceLock<Mutex<Vec<Item>>> = OnceLock::new();
ITEMS.get_or_init(|| {
Mutex::new(vec![
Item { title: "Read the field offsets from the metadata".into(), done: true },
Item { title: "Draw the ring as an arc".into(), done: false },
])
})
}
fn draft() -> &'static views::BoundState<String> {
static DRAFT: OnceLock<views::BoundState<String>> = OnceLock::new();
DRAFT.get_or_init(|| views::BoundState::new(String::new()))
}
fn add() {
let text = draft().get();
if text.trim().is_empty() {
return;
}
items().lock().unwrap().push(Item { title: text, done: false });
draft().set(String::new());
state::refresh_animated(state::ease_in_out(0.25));
}
fn toggle(i: usize) {
let mut list = items().lock().unwrap();
if let Some(it) = list.get_mut(i) {
it.done = !it.done;
}
drop(list);
state::refresh();
}
fn row(i: usize, it: &Item) -> View {
hstack_spacing(
&[
button_labelled(
system_image(if it.done { "checkmark.circle.fill" } else { "circle" })
.symbol_rendering_mode(Some(views::SymbolRenderingMode::Hierarchical))
.view()
.fg_system(if it.done { SystemColor::Green } else { SystemColor::Gray }),
move || toggle(i),
)
.button_style(views::ButtonStyle::Bordered),
text(&it.title)
.strikethrough_styled(it.done, LinePattern::Solid, None)
.fg_system(if it.done { SystemColor::Gray } else { SystemColor::Primary }),
spacer(),
],
10.0,
)
.padding_edges(EdgeSet::Vertical, Some(6.0))
.a11y_label(&it.title)
.a11y_value(if it.done { "Done" } else { "Not done" })
}
fn body() -> View {
let todos = items().lock().unwrap().clone();
let remaining = todos.iter().filter(|i| !i.done).count();
vstack_aligned_spacing(
&[
hstack![
text("Todo").text_style(TextStyle::LargeTitle).bold(),
spacer(),
text!("{remaining} left").fg_system(SystemColor::Gray),
],
hstack_spacing(
&[
textfield_bound("What needs doing?", draft())
.text_field_style(views::TextFieldStyle::RoundedBorderTextFieldStyle)
.on_submit(SubmitTrigger::Text, add)
.submit_label(SubmitLabel::Done),
button("Add", add).button_style(views::ButtonStyle::BorderedProminent),
],
8.0,
),
view_if!(
todos.is_empty(),
vstack_spacing(
&[
system_image("checkmark.seal").view().font_size(40.0)
.fg_system(SystemColor::Green),
text("Nothing left to do").fg_system(SystemColor::Gray),
],
8.0,
)
.frame_optional(None, Some(160.0), Alignment::Center)
.transition(Transition::ScaleWithOpacity { scale: 0.8, anchor: (0.5, 0.5) }),
list(for_each_items(
&todos.iter().cloned().enumerate().collect::<Vec<_>>(),
|(i, it)| row(*i, it),
))
.list_style(views::ListStyle::PlainListStyle)
),
],
HorizontalAlignment::Leading,
Some(14.0),
)
.padding(20.0)
}
fn content_view() -> swiftui_native::ViewHandle {
state::root_view(|_| body().into_handle())
}
#[cfg(target_os = "macos")]
#[path = "common/simulator.rs"]
mod simulator;
#[cfg(target_os = "macos")]
fn main() {
if std::env::args().any(|a| a == "--sim" || a == "device") {
return simulator::main(&simulator::Bundle {
example: "todo",
bundle_id: "dev.rswift.Todo",
executable: "Todo",
display_name: "Todo",
launch_args: &[],
});
}
if std::env::args().any(|a| a == "--render") {
let out = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/todo.png");
swiftui_native::render::render(&content_view(), 420, 460).write_png(out);
eprintln!("wrote {out}");
return;
}
show_window(&content_view(), "Todo", 420.0, 460.0);
}
#[cfg(not(target_os = "macos"))]
fn main() {
swiftui_native::app::run(content_view)
}