1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | use slint::{FilterModel, Model, SortModel}; |
5 | use std::rc::Rc; |
6 | |
7 | #[cfg (target_arch = "wasm32" )] |
8 | use wasm_bindgen::prelude::*; |
9 | |
10 | slint::include_modules!(); |
11 | |
12 | #[cfg_attr (target_arch = "wasm32" , wasm_bindgen(start))] |
13 | pub 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 ] |
109 | fn 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" )] |
136 | struct State { |
137 | main_window: MainWindow, |
138 | todo_model: Rc<slint::VecModel<TodoItem>>, |
139 | } |
140 | |
141 | #[cfg (target_os = "android" )] |
142 | thread_local! { |
143 | static STATE : core::cell::RefCell<Option<State>> = Default::default(); |
144 | } |
145 | |
146 | #[cfg (target_os = "android" )] |
147 | #[derive (serde::Serialize, serde::Deserialize)] |
148 | struct SerializedState { |
149 | items: Vec<TodoItem>, |
150 | sort: bool, |
151 | hide_done: bool, |
152 | } |
153 | |
154 | #[cfg (target_os = "android" )] |
155 | impl 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 | |