1use std::sync::{Mutex, OnceLock};
13use swiftui_native::prelude::*;
14use swiftui_native::{state, views};
15
16#[derive(Clone)]
17struct Item {
18 title: String,
19 done: bool,
20}
21
22fn items() -> &'static Mutex<Vec<Item>> {
25 static ITEMS: OnceLock<Mutex<Vec<Item>>> = OnceLock::new();
26 ITEMS.get_or_init(|| {
27 Mutex::new(vec![
28 Item { title: "Read the field offsets from the metadata".into(), done: true },
29 Item { title: "Draw the ring as an arc".into(), done: false },
30 ])
31 })
32}
33
34fn draft() -> &'static views::BoundState<String> {
36 static DRAFT: OnceLock<views::BoundState<String>> = OnceLock::new();
37 DRAFT.get_or_init(|| views::BoundState::new(String::new()))
38}
39
40fn add() {
41 let text = draft().get();
42 if text.trim().is_empty() {
43 return;
44 }
45 items().lock().unwrap().push(Item { title: text, done: false });
46 draft().set(String::new());
47 state::refresh_animated(state::ease_in_out(0.25));
48}
49
50fn toggle(i: usize) {
51 let mut list = items().lock().unwrap();
52 if let Some(it) = list.get_mut(i) {
53 it.done = !it.done;
54 }
55 drop(list);
56 state::refresh();
57}
58
59fn row(i: usize, it: &Item) -> View {
60 hstack_spacing(
61 &[
62 button_labelled(
64 system_image(if it.done { "checkmark.circle.fill" } else { "circle" })
65 .symbol_rendering_mode(Some(views::SymbolRenderingMode::Hierarchical))
66 .view()
67 .fg_system(if it.done { SystemColor::Green } else { SystemColor::Gray }),
68 move || toggle(i),
69 )
70 .button_style(views::ButtonStyle::Bordered),
71 text(&it.title)
72 .strikethrough_styled(it.done, LinePattern::Solid, None)
73 .fg_system(if it.done { SystemColor::Gray } else { SystemColor::Primary }),
74 spacer(),
75 ],
76 10.0,
77 )
78 .padding_edges(EdgeSet::Vertical, Some(6.0))
79 .a11y_label(&it.title)
80 .a11y_value(if it.done { "Done" } else { "Not done" })
81}
82
83fn body() -> View {
84 let todos = items().lock().unwrap().clone();
85 let remaining = todos.iter().filter(|i| !i.done).count();
86
87 vstack_aligned_spacing(
88 &[
89 hstack![
90 text("Todo").text_style(TextStyle::LargeTitle).bold(),
91 spacer(),
92 text!("{remaining} left").fg_system(SystemColor::Gray),
93 ],
94 hstack_spacing(
95 &[
96 textfield_bound("What needs doing?", draft())
97 .text_field_style(views::TextFieldStyle::RoundedBorderTextFieldStyle)
98 .on_submit(SubmitTrigger::Text, add)
99 .submit_label(SubmitLabel::Done),
100 button("Add", add).button_style(views::ButtonStyle::BorderedProminent),
101 ],
102 8.0,
103 ),
104 view_if!(
107 todos.is_empty(),
108 vstack_spacing(
109 &[
110 system_image("checkmark.seal").view().font_size(40.0)
111 .fg_system(SystemColor::Green),
112 text("Nothing left to do").fg_system(SystemColor::Gray),
113 ],
114 8.0,
115 )
116 .frame_optional(None, Some(160.0), Alignment::Center)
117 .transition(Transition::ScaleWithOpacity { scale: 0.8, anchor: (0.5, 0.5) }),
118 list(for_each_items(
119 &todos.iter().cloned().enumerate().collect::<Vec<_>>(),
120 |(i, it)| row(*i, it),
121 ))
122 .list_style(views::ListStyle::PlainListStyle)
123 ),
124 ],
125 HorizontalAlignment::Leading,
126 Some(14.0),
127 )
128 .padding(20.0)
129}
130
131fn content_view() -> swiftui_native::ViewHandle {
132 state::root_view(|_| body().into_handle())
133}
134
135#[cfg(target_os = "macos")]
136#[path = "common/simulator.rs"]
137mod simulator;
138
139#[cfg(target_os = "macos")]
140fn main() {
141 if std::env::args().any(|a| a == "--sim" || a == "device") {
142 return simulator::main(&simulator::Bundle {
143 example: "todo",
144 bundle_id: "dev.rswift.Todo",
145 executable: "Todo",
146 display_name: "Todo",
147 launch_args: &[],
148 });
149 }
150 if std::env::args().any(|a| a == "--render") {
151 let out = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/todo.png");
152 swiftui_native::render::render(&content_view(), 420, 460).write_png(out);
153 eprintln!("wrote {out}");
154 return;
155 }
156 show_window(&content_view(), "Todo", 420.0, 460.0);
157}
158
159#[cfg(not(target_os = "macos"))]
160fn main() {
161 swiftui_native::app::run(content_view)
162}