1use std::cell::{Cell, RefCell};
9use std::collections::HashMap;
10
11use day_spec::{AppProgress, StatusItemSpec};
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub enum KeepRunning {
17 #[default]
20 Automatic,
21 Always,
25 Never,
27}
28
29day_reactive::tls_slots! {
30 status;
31 static POLICY: Cell<KeepRunning> = const { Cell::new(KeepRunning::Automatic) };
32 static ITEMS: RefCell<Vec<(StatusItemSpec, Vec<u64>)>> = const { RefCell::new(Vec::new()) };
35 static TOLD_KEEP: Cell<Option<bool>> = const { Cell::new(None) };
37 static TRACKING: RefCell<HashMap<String, Tracking>> = RefCell::new(HashMap::new());
38 static NEXT_TRACKING: Cell<u64> = const { Cell::new(1) };
39}
40
41struct Tracking {
42 token: u64,
43 pending: Option<StatusItemSpec>,
44}
45
46pub fn begin_menu_tracking(id: &str) -> u64 {
50 let token = NEXT_TRACKING.with(|next| {
51 let token = next.get();
52 next.set(token.wrapping_add(1).max(1));
53 token
54 });
55 TRACKING.with(|tracking| {
56 let mut tracking = tracking.borrow_mut();
57 let pending = tracking.remove(id).and_then(|old| old.pending);
58 tracking.insert(id.to_owned(), Tracking { token, pending });
59 });
60 token
61}
62
63pub fn end_menu_tracking(id: &str, token: u64) {
67 let pending = TRACKING.with(|tracking| {
68 let mut tracking = tracking.borrow_mut();
69 if tracking.get(id).is_some_and(|t| t.token == token) {
70 tracking.remove(id).and_then(|t| t.pending)
71 } else {
72 None
73 }
74 });
75 if let Some(spec) = pending {
76 set_status_item(spec);
77 }
78}
79
80pub fn set_keep_running(policy: KeepRunning) {
82 POLICY.with(|p| p.set(policy));
83 sync_keep_running();
84}
85
86pub fn keep_running_policy() -> KeepRunning {
88 POLICY.with(|p| p.get())
89}
90
91pub(crate) fn keeps_running() -> bool {
93 match keep_running_policy() {
94 KeepRunning::Always => true,
95 KeepRunning::Never => false,
96 KeepRunning::Automatic => ITEMS.with(|i| !i.borrow().is_empty()),
97 }
98}
99
100fn sync_keep_running() {
102 let keep = keeps_running();
103 if TOLD_KEEP.with(|t| t.replace(Some(keep))) != Some(keep) && crate::tree::has_tree() {
104 crate::with_tree(|t| t.set_keep_running(keep));
105 }
106}
107
108pub(crate) fn boot_sync() {
111 TOLD_KEEP.with(|t| t.set(None));
112 if keeps_running() {
113 sync_keep_running();
114 }
115}
116
117fn action_ids(spec: &StatusItemSpec) -> Vec<u64> {
118 let mut ids = crate::menu::collect_ids(&spec.menu);
119 if spec.activate != 0 {
120 ids.push(spec.activate);
121 }
122 ids
123}
124
125pub fn set_status_item(spec: StatusItemSpec) {
129 let mut spec = Some(spec);
130 let superseded = TRACKING.with(|tracking| {
131 let mut tracking = tracking.borrow_mut();
132 let active = tracking.get_mut(&spec.as_ref().unwrap().id)?;
133 active.pending.replace(spec.take().unwrap())
134 });
135 if spec.is_none() {
136 if let Some(old) = superseded {
137 let mut keep = ITEMS.with(|items| {
140 items
141 .borrow()
142 .iter()
143 .find(|(s, _)| s.id == old.id)
144 .map(|(_, ids)| ids.clone())
145 .unwrap_or_default()
146 });
147 TRACKING.with(|tracking| {
148 if let Some(next) = tracking
149 .borrow()
150 .get(&old.id)
151 .and_then(|t| t.pending.as_ref())
152 {
153 keep.extend(action_ids(next));
154 }
155 });
156 let stale: Vec<_> = action_ids(&old)
157 .into_iter()
158 .filter(|id| !keep.contains(id))
159 .collect();
160 crate::menu::forget_actions(&stale);
161 }
162 return;
163 }
164 let spec = spec.unwrap();
165 let ids = action_ids(&spec);
166 let stale = ITEMS.with(|items| {
167 let mut items = items.borrow_mut();
168 match items.iter_mut().find(|(s, _)| s.id == spec.id) {
169 Some((s, old)) => {
170 let stale: Vec<u64> = old.iter().copied().filter(|i| !ids.contains(i)).collect();
171 *s = spec;
172 *old = ids;
173 stale
174 }
175 None => {
176 items.push((spec, ids));
177 Vec::new()
178 }
179 }
180 });
181 crate::menu::forget_actions(&stale);
182 send_items();
183}
184
185pub fn remove_status_item(id: &str) {
187 let pending =
188 TRACKING.with(|tracking| tracking.borrow_mut().remove(id).and_then(|t| t.pending));
189 if let Some(pending) = pending {
190 crate::menu::forget_actions(&action_ids(&pending));
191 }
192 let stale = ITEMS.with(|items| {
193 let mut items = items.borrow_mut();
194 let i = items.iter().position(|(s, _)| s.id == id)?;
195 Some(items.remove(i).1)
196 });
197 if let Some(stale) = stale {
198 crate::menu::forget_actions(&stale);
199 send_items();
200 }
201}
202
203pub fn status_items() -> Vec<StatusItemSpec> {
205 ITEMS.with(|i| i.borrow().iter().map(|(s, _)| s.clone()).collect())
206}
207
208fn send_items() {
209 let items = status_items();
210 crate::with_tree(|t| t.set_status_items(&items));
211 sync_keep_running();
212}
213
214pub fn set_app_progress(progress: AppProgress) {
216 crate::with_tree(|t| t.set_app_progress(progress));
217}
218
219pub fn set_dock_visible(visible: bool) {
223 crate::with_tree(|t| t.set_dock_visible(visible));
224}
225
226pub fn reset_status() {
228 TRACKING.with(|tracking| tracking.borrow_mut().clear());
229 ITEMS.with(|i| i.borrow_mut().clear());
230 POLICY.with(|p| p.set(KeepRunning::Automatic));
231 TOLD_KEEP.with(|t| t.set(None));
232}