1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT
3
4use slint::{FilterModel, Model, SortModel};
5use std::rc::Rc;
6
7#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::prelude::*;
9
10slint::include_modules!();
11
12#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
13pub fn main() {
14 // This provides better error messages in debug mode.
15 // It's disabled in release mode so it doesn't bloat up the file size.
16 #[cfg(all(debug_assertions, target_arch = "wasm32"))]
17 console_error_panic_hook::set_once();
18
19 let todo_model = Rc::new(slint::VecModel::<TodoItem>::from(vec![
20 TodoItem { checked: true, title: "Implement the .slint file".into() },
21 TodoItem { checked: true, title: "Do the Rust part".into() },
22 TodoItem { checked: false, title: "Make the C++ code".into() },
23 TodoItem { checked: false, title: "Write some JavaScript code".into() },
24 TodoItem { checked: false, title: "Test the application".into() },
25 TodoItem { checked: false, title: "Ship to customer".into() },
26 TodoItem { checked: false, title: "???".into() },
27 TodoItem { checked: false, title: "Profit".into() },
28 ]));
29
30 let main_window = MainWindow::new().unwrap();
31
32 #[cfg(target_os = "android")]
33 STATE.with(|ui| {
34 *ui.borrow_mut() =
35 Some(State { main_window: main_window.clone_strong(), todo_model: todo_model.clone() })
36 });
37
38 main_window.on_todo_added({
39 let todo_model = todo_model.clone();
40 move |text| todo_model.push(TodoItem { checked: false, title: text })
41 });
42 main_window.on_remove_done({
43 let todo_model = todo_model.clone();
44 move || {
45 let mut offset = 0;
46 for i in 0..todo_model.row_count() {
47 if todo_model.row_data(i - offset).unwrap().checked {
48 todo_model.remove(i - offset);
49 offset += 1;
50 }
51 }
52 }
53 });
54
55 let weak_window = main_window.as_weak();
56 main_window.on_popup_confirmed(move || {
57 let window = weak_window.unwrap();
58 window.hide().unwrap();
59 });
60
61 {
62 let weak_window = main_window.as_weak();
63 let todo_model = todo_model.clone();
64 main_window.window().on_close_requested(move || {
65 let window = weak_window.unwrap();
66
67 if todo_model.iter().any(|t| !t.checked) {
68 window.invoke_show_confirm_popup();
69 slint::CloseRequestResponse::KeepWindowShown
70 } else {
71 slint::CloseRequestResponse::HideWindow
72 }
73 });
74 }
75
76 main_window.on_apply_sorting_and_filtering({
77 let weak_window = main_window.as_weak();
78 let todo_model = todo_model.clone();
79
80 move || {
81 let window = weak_window.unwrap();
82 window.set_todo_model(todo_model.clone().into());
83
84 if window.get_hide_done_items() {
85 window.set_todo_model(
86 Rc::new(FilterModel::new(window.get_todo_model(), |e| !e.checked)).into(),
87 );
88 }
89
90 if window.get_is_sort_by_name() {
91 window.set_todo_model(
92 Rc::new(SortModel::new(window.get_todo_model(), |lhs, rhs| {
93 lhs.title.to_lowercase().cmp(&rhs.title.to_lowercase())
94 }))
95 .into(),
96 );
97 }
98 }
99 });
100
101 main_window.set_show_header(true);
102 main_window.set_todo_model(todo_model.into());
103
104 main_window.run().unwrap();
105}
106
107#[cfg(target_os = "android")]
108#[no_mangle]
109fn android_main(app: slint::android::AndroidApp) {
110 use slint::android::android_activity::{MainEvent, PollEvent};
111 slint::android::init_with_event_listener(app, |event| {
112 match event {
113 PollEvent::Main(MainEvent::SaveState { saver, .. }) => {
114 STATE.with(|state| -> Option<()> {
115 let todo_state = SerializedState::save(state.borrow().as_ref()?);
116 saver.store(&serde_json::to_vec(&todo_state).ok()?);
117 Some(())
118 });
119 }
120 PollEvent::Main(MainEvent::Resume { loader, .. }) => {
121 STATE.with(|state| -> Option<()> {
122 let bytes: Vec<u8> = loader.load()?;
123 let todo_state: SerializedState = serde_json::from_slice(&bytes).ok()?;
124 todo_state.restore(state.borrow().as_ref()?);
125 Some(())
126 });
127 }
128 _ => {}
129 };
130 })
131 .unwrap();
132 main();
133}
134
135#[cfg(target_os = "android")]
136struct State {
137 main_window: MainWindow,
138 todo_model: Rc<slint::VecModel<TodoItem>>,
139}
140
141#[cfg(target_os = "android")]
142thread_local! {
143 static STATE : core::cell::RefCell<Option<State>> = Default::default();
144}
145
146#[cfg(target_os = "android")]
147#[derive(serde::Serialize, serde::Deserialize)]
148struct SerializedState {
149 items: Vec<TodoItem>,
150 sort: bool,
151 hide_done: bool,
152}
153
154#[cfg(target_os = "android")]
155impl SerializedState {
156 fn restore(self, state: &State) {
157 state.todo_model.set_vec(self.items);
158 state.main_window.set_hide_done_items(self.hide_done);
159 state.main_window.set_is_sort_by_name(self.sort);
160 state.main_window.invoke_apply_sorting_and_filtering();
161 }
162 fn save(state: &State) -> Self {
163 Self {
164 items: state.todo_model.iter().collect(),
165 sort: state.main_window.get_is_sort_by_name(),
166 hide_done: state.main_window.get_hide_done_items(),
167 }
168 }
169}
170