use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use day_spec::{AppProgress, StatusItemSpec};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum KeepRunning {
#[default]
Automatic,
Always,
Never,
}
day_reactive::tls_slots! {
status;
static POLICY: Cell<KeepRunning> = const { Cell::new(KeepRunning::Automatic) };
static ITEMS: RefCell<Vec<(StatusItemSpec, Vec<u64>)>> = const { RefCell::new(Vec::new()) };
static TOLD_KEEP: Cell<Option<bool>> = const { Cell::new(None) };
static TRACKING: RefCell<HashMap<String, Tracking>> = RefCell::new(HashMap::new());
static NEXT_TRACKING: Cell<u64> = const { Cell::new(1) };
}
struct Tracking {
token: u64,
pending: Option<StatusItemSpec>,
}
pub fn begin_menu_tracking(id: &str) -> u64 {
let token = NEXT_TRACKING.with(|next| {
let token = next.get();
next.set(token.wrapping_add(1).max(1));
token
});
TRACKING.with(|tracking| {
let mut tracking = tracking.borrow_mut();
let pending = tracking.remove(id).and_then(|old| old.pending);
tracking.insert(id.to_owned(), Tracking { token, pending });
});
token
}
pub fn end_menu_tracking(id: &str, token: u64) {
let pending = TRACKING.with(|tracking| {
let mut tracking = tracking.borrow_mut();
if tracking.get(id).is_some_and(|t| t.token == token) {
tracking.remove(id).and_then(|t| t.pending)
} else {
None
}
});
if let Some(spec) = pending {
set_status_item(spec);
}
}
pub fn set_keep_running(policy: KeepRunning) {
POLICY.with(|p| p.set(policy));
sync_keep_running();
}
pub fn keep_running_policy() -> KeepRunning {
POLICY.with(|p| p.get())
}
pub(crate) fn keeps_running() -> bool {
match keep_running_policy() {
KeepRunning::Always => true,
KeepRunning::Never => false,
KeepRunning::Automatic => ITEMS.with(|i| !i.borrow().is_empty()),
}
}
fn sync_keep_running() {
let keep = keeps_running();
if TOLD_KEEP.with(|t| t.replace(Some(keep))) != Some(keep) && crate::tree::has_tree() {
crate::with_tree(|t| t.set_keep_running(keep));
}
}
pub(crate) fn boot_sync() {
TOLD_KEEP.with(|t| t.set(None));
if keeps_running() {
sync_keep_running();
}
}
fn action_ids(spec: &StatusItemSpec) -> Vec<u64> {
let mut ids = crate::menu::collect_ids(&spec.menu);
if spec.activate != 0 {
ids.push(spec.activate);
}
ids
}
pub fn set_status_item(spec: StatusItemSpec) {
let mut spec = Some(spec);
let superseded = TRACKING.with(|tracking| {
let mut tracking = tracking.borrow_mut();
let active = tracking.get_mut(&spec.as_ref().unwrap().id)?;
active.pending.replace(spec.take().unwrap())
});
if spec.is_none() {
if let Some(old) = superseded {
let mut keep = ITEMS.with(|items| {
items
.borrow()
.iter()
.find(|(s, _)| s.id == old.id)
.map(|(_, ids)| ids.clone())
.unwrap_or_default()
});
TRACKING.with(|tracking| {
if let Some(next) = tracking
.borrow()
.get(&old.id)
.and_then(|t| t.pending.as_ref())
{
keep.extend(action_ids(next));
}
});
let stale: Vec<_> = action_ids(&old)
.into_iter()
.filter(|id| !keep.contains(id))
.collect();
crate::menu::forget_actions(&stale);
}
return;
}
let spec = spec.unwrap();
let ids = action_ids(&spec);
let stale = ITEMS.with(|items| {
let mut items = items.borrow_mut();
match items.iter_mut().find(|(s, _)| s.id == spec.id) {
Some((s, old)) => {
let stale: Vec<u64> = old.iter().copied().filter(|i| !ids.contains(i)).collect();
*s = spec;
*old = ids;
stale
}
None => {
items.push((spec, ids));
Vec::new()
}
}
});
crate::menu::forget_actions(&stale);
send_items();
}
pub fn remove_status_item(id: &str) {
let pending =
TRACKING.with(|tracking| tracking.borrow_mut().remove(id).and_then(|t| t.pending));
if let Some(pending) = pending {
crate::menu::forget_actions(&action_ids(&pending));
}
let stale = ITEMS.with(|items| {
let mut items = items.borrow_mut();
let i = items.iter().position(|(s, _)| s.id == id)?;
Some(items.remove(i).1)
});
if let Some(stale) = stale {
crate::menu::forget_actions(&stale);
send_items();
}
}
pub fn status_items() -> Vec<StatusItemSpec> {
ITEMS.with(|i| i.borrow().iter().map(|(s, _)| s.clone()).collect())
}
fn send_items() {
let items = status_items();
crate::with_tree(|t| t.set_status_items(&items));
sync_keep_running();
}
pub fn set_app_progress(progress: AppProgress) {
crate::with_tree(|t| t.set_app_progress(progress));
}
pub fn set_dock_visible(visible: bool) {
crate::with_tree(|t| t.set_dock_visible(visible));
}
pub fn reset_status() {
TRACKING.with(|tracking| tracking.borrow_mut().clear());
ITEMS.with(|i| i.borrow_mut().clear());
POLICY.with(|p| p.set(KeepRunning::Automatic));
TOLD_KEEP.with(|t| t.set(None));
}