Skip to main content

day_core/
shield.rs

1// Copyright © The Daybrite Project
2// SPDX-License-Identifier: MPL-2.0
3
4//! The system-gesture / interactive-dismiss / status-bar shield (docs/cover.md): the registries
5//! behind the `defers_system_gestures`, `interactive_dismiss_disabled` and `status_bar_hidden`
6//! modifiers. Each mounted modifier pushes an entry while its subtree is alive; the union of
7//! entries is the app's current request.
8//!
9//! Gesture deferral and the status bar flow straight to the backend
10//! (`Toolkit::defer_system_gestures`, `Toolkit::set_status_bar_hidden`) on every change. Dismiss-disabled is a *query*: the `cover` piece reads it (reactively, via the
11//! change [`day_reactive::Trigger`]) when deciding whether a native back may close it and when patching the
12//! presented surface's `isModalInPresentation`-style flag.
13
14use std::cell::{Cell, RefCell};
15
16use day_reactive::Signal;
17use day_spec::Edges;
18
19use crate::tree::{has_tree, with_tree};
20
21day_reactive::tls_slots! {
22    shield;
23    static DEFERRALS: RefCell<Vec<(u64, Edges)>> = const { RefCell::new(Vec::new()) };
24    static DISMISS_DISABLED: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
25    /// `(token, hidden)` per mounted `status_bar_hidden` modifier. A request can change its
26    /// answer while mounted (a reactive setting), so each entry carries its current value.
27    static STATUS_BAR: RefCell<Vec<(u64, bool)>> = const { RefCell::new(Vec::new()) };
28    static NEXT: Cell<u64> = const { Cell::new(1) };
29    /// Change counter for reactive readers ([`dismiss_disabled`]), created lazily by
30    /// [`changed_signal`]. Root-scoped so it outlives whatever page scope touched it first.
31    static CHANGED: Cell<Option<Signal<u64>>> = const { Cell::new(None) };
32}
33
34fn changed_signal() -> Signal<u64> {
35    CHANGED.with(|c| match c.get() {
36        Some(s) => s,
37        None => {
38            let s = Signal::global(0);
39            c.set(Some(s));
40            s
41        }
42    })
43}
44
45fn next_token() -> u64 {
46    NEXT.with(|c| {
47        let t = c.get();
48        c.set(t + 1);
49        t
50    })
51}
52
53fn notify_changed() {
54    changed_signal().update(|v| *v = v.wrapping_add(1));
55}
56
57fn apply_deferral() {
58    let union = deferred_edges();
59    if has_tree() {
60        with_tree(|t| t.defer_system_gestures(union));
61    }
62    notify_changed();
63}
64
65/// Register an edge-deferral request (a `defers_system_gestures` subtree mounted). The backend
66/// receives the new union immediately. Pair with [`pop_gesture_deferral`] on unmount.
67pub fn push_gesture_deferral(edges: Edges) -> u64 {
68    let token = next_token();
69    DEFERRALS.with(|d| d.borrow_mut().push((token, edges)));
70    apply_deferral();
71    token
72}
73
74pub fn pop_gesture_deferral(token: u64) {
75    DEFERRALS.with(|d| d.borrow_mut().retain(|(t, _)| *t != token));
76    apply_deferral();
77}
78
79/// The union of every live deferral request (`Edges::NONE` when there are none). Untracked.
80pub fn deferred_edges() -> Edges {
81    DEFERRALS.with(|d| {
82        d.borrow()
83            .iter()
84            .fold(Edges::NONE, |acc, (_, e)| acc.union(*e))
85    })
86}
87
88fn apply_status_bar() {
89    let hidden = status_bar_hidden();
90    if has_tree() {
91        with_tree(|t| t.set_status_bar_hidden(hidden));
92    }
93}
94
95/// Register a status-bar request (a `status_bar_hidden` subtree mounted) with its current
96/// answer. The backend receives the new union immediately. Update it with
97/// [`set_status_bar_request`] and pair it with [`pop_status_bar_request`] on unmount.
98pub fn push_status_bar_request(hidden: bool) -> u64 {
99    let token = next_token();
100    STATUS_BAR.with(|s| s.borrow_mut().push((token, hidden)));
101    apply_status_bar();
102    token
103}
104
105/// Change a live status-bar request's answer (its reactive source changed).
106pub fn set_status_bar_request(token: u64, hidden: bool) {
107    let changed = STATUS_BAR.with(|s| {
108        let mut s = s.borrow_mut();
109        match s.iter_mut().find(|(t, _)| *t == token) {
110            Some((_, h)) if *h != hidden => {
111                *h = hidden;
112                true
113            }
114            _ => false,
115        }
116    });
117    if changed {
118        apply_status_bar();
119    }
120}
121
122pub fn pop_status_bar_request(token: u64) {
123    STATUS_BAR.with(|s| s.borrow_mut().retain(|(t, _)| *t != token));
124    apply_status_bar();
125}
126
127/// Whether any live subtree asks for the status bar hidden. Untracked.
128pub fn status_bar_hidden() -> bool {
129    STATUS_BAR.with(|s| s.borrow().iter().any(|(_, hidden)| *hidden))
130}
131
132/// Register an interactive-dismiss-disabled request (an `interactive_dismiss_disabled` subtree
133/// mounted). Pair with [`pop_dismiss_disabled`] on unmount.
134pub fn push_dismiss_disabled() -> u64 {
135    let token = next_token();
136    DISMISS_DISABLED.with(|d| d.borrow_mut().push(token));
137    notify_changed();
138    token
139}
140
141pub fn pop_dismiss_disabled(token: u64) {
142    DISMISS_DISABLED.with(|d| d.borrow_mut().retain(|t| *t != token));
143    notify_changed();
144}
145
146/// Whether any live subtree asked to disable interactive dismissal. Reads track the change
147/// counter, so a binding re-runs as modifiers mount and unmount.
148pub fn dismiss_disabled() -> bool {
149    changed_signal().track();
150    DISMISS_DISABLED.with(|d| !d.borrow().is_empty())
151}