Skip to main content

cranpose_ui/
focus_dispatch.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::{HashMap, HashSet, VecDeque},
4    rc::Rc,
5};
6
7use cranpose_core::NodeId;
8use cranpose_foundation::FocusState;
9
10pub(crate) trait FocusTargetHandle {
11    fn set_focus_state(&self, state: FocusState);
12}
13
14struct FocusInvalidationManager {
15    dirty_nodes: HashSet<NodeId>,
16    is_processing: bool,
17    active_focus_target: Option<NodeId>,
18    focus_targets: HashMap<NodeId, Vec<Rc<dyn FocusTargetHandle>>>,
19    pending_focus_requests: VecDeque<NodeId>,
20    dispatching_focus: bool,
21}
22
23impl FocusInvalidationManager {
24    fn new() -> Self {
25        Self {
26            dirty_nodes: HashSet::new(),
27            is_processing: false,
28            active_focus_target: None,
29            focus_targets: HashMap::new(),
30            pending_focus_requests: VecDeque::new(),
31            dispatching_focus: false,
32        }
33    }
34
35    fn schedule_invalidation(&mut self, node_id: NodeId) {
36        self.dirty_nodes.insert(node_id);
37    }
38
39    fn has_pending_invalidation(&self) -> bool {
40        !self.dirty_nodes.is_empty()
41    }
42
43    fn set_active_focus_target(&mut self, node_id: Option<NodeId>) {
44        if self.active_focus_target == node_id {
45            return;
46        }
47        for changed in self.active_focus_target.into_iter().chain(node_id) {
48            crate::semantics_dispatch::schedule_semantics_invalidation(changed);
49        }
50        self.active_focus_target = node_id;
51        crate::request_render_invalidation();
52    }
53
54    fn active_focus_target(&self) -> Option<NodeId> {
55        self.active_focus_target
56    }
57
58    /// Registers a target on `node_id`; a node's first target makes it
59    /// focusable, which its semantics report.
60    fn register_focus_target(&mut self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
61        let handles = self.focus_targets.entry(node_id).or_default();
62        if handles.is_empty() {
63            crate::semantics_dispatch::schedule_semantics_invalidation(node_id);
64        }
65        handles.push(handle);
66    }
67
68    fn unregister_focus_target(&mut self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
69        let Some(handles) = self.focus_targets.get_mut(&node_id) else {
70            return;
71        };
72        handles.retain(|existing| !Rc::ptr_eq(existing, handle));
73        if handles.is_empty() {
74            self.focus_targets.remove(&node_id);
75            crate::semantics_dispatch::schedule_semantics_invalidation(node_id);
76            if self.active_focus_target == Some(node_id) {
77                self.set_active_focus_target(None);
78            }
79        }
80    }
81
82    fn has_focus_target(&self, node_id: NodeId) -> bool {
83        self.focus_targets.contains_key(&node_id)
84    }
85
86    fn focus_target_handles(&self, node_id: NodeId) -> Vec<Rc<dyn FocusTargetHandle>> {
87        self.focus_targets
88            .get(&node_id)
89            .cloned()
90            .unwrap_or_default()
91    }
92
93    fn swap_active_focus_target(&mut self, node_id: NodeId) -> Option<NodeId> {
94        if self.active_focus_target == Some(node_id) {
95            return None;
96        }
97        let previous = self.active_focus_target;
98        self.set_active_focus_target(Some(node_id));
99        previous
100    }
101
102    fn take_first_focus_request_if_idle(&mut self) -> Option<NodeId> {
103        if self.dispatching_focus {
104            return None;
105        }
106        let next = self.pending_focus_requests.pop_front()?;
107        self.dispatching_focus = true;
108        Some(next)
109    }
110
111    fn take_next_focus_request(&mut self) -> Option<NodeId> {
112        self.pending_focus_requests.pop_front()
113    }
114
115    fn finish_focus_dispatch(&mut self) {
116        self.dispatching_focus = false;
117    }
118
119    fn take_pending_for_processing(&mut self) -> Option<Vec<NodeId>> {
120        if self.is_processing {
121            return None;
122        }
123
124        self.is_processing = true;
125        Some(self.dirty_nodes.drain().collect())
126    }
127
128    fn finish_processing<I>(&mut self, remaining: I)
129    where
130        I: IntoIterator<Item = NodeId>,
131    {
132        self.dirty_nodes.extend(remaining);
133        self.is_processing = false;
134    }
135
136    fn clear(&mut self) {
137        self.dirty_nodes.clear();
138    }
139}
140
141pub(crate) struct FocusInvalidationState {
142    manager: RefCell<FocusInvalidationManager>,
143    order: RefCell<Vec<crate::FocusEntry>>,
144}
145
146impl FocusInvalidationState {
147    pub(crate) fn new() -> Self {
148        Self {
149            manager: RefCell::new(FocusInvalidationManager::new()),
150            order: RefCell::new(Vec::new()),
151        }
152    }
153
154    pub(crate) fn set_focus_order(&self, entries: Vec<crate::FocusEntry>) {
155        *self.order.borrow_mut() = entries;
156    }
157
158    pub(crate) fn with_focus_order<T>(&self, reader: impl FnOnce(&[crate::FocusEntry]) -> T) -> T {
159        reader(&self.order.borrow())
160    }
161
162    fn schedule_invalidation(&self, node_id: NodeId) {
163        self.manager.borrow_mut().schedule_invalidation(node_id);
164    }
165
166    fn has_pending_invalidation(&self) -> bool {
167        self.manager.borrow().has_pending_invalidation()
168    }
169
170    fn set_active_focus_target(&self, node_id: Option<NodeId>) {
171        self.manager.borrow_mut().set_active_focus_target(node_id);
172    }
173
174    fn active_focus_target(&self) -> Option<NodeId> {
175        self.manager.borrow().active_focus_target()
176    }
177
178    fn register_focus_target(&self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
179        self.manager
180            .borrow_mut()
181            .register_focus_target(node_id, handle);
182    }
183
184    fn unregister_focus_target(&self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
185        self.manager
186            .borrow_mut()
187            .unregister_focus_target(node_id, handle);
188    }
189
190    pub(crate) fn has_focus_target(&self, node_id: NodeId) -> bool {
191        self.manager.borrow().has_focus_target(node_id)
192    }
193
194    pub(crate) fn clear_active_focus(&self) -> bool {
195        let previous = {
196            let mut manager = self.manager.borrow_mut();
197            let previous = manager.active_focus_target();
198            manager.set_active_focus_target(None);
199            previous
200        };
201        let Some(previous) = previous else {
202            return false;
203        };
204        let handles = self.manager.borrow().focus_target_handles(previous);
205        for handle in handles {
206            handle.set_focus_state(FocusState::Inactive);
207        }
208        true
209    }
210
211    pub(crate) fn request_focus(&self, node_id: NodeId) -> bool {
212        let accepted = {
213            let mut manager = self.manager.borrow_mut();
214            if !manager.has_focus_target(node_id) {
215                false
216            } else {
217                manager.pending_focus_requests.push_back(node_id);
218                true
219            }
220        };
221        if accepted {
222            self.drain_focus_requests();
223        }
224        accepted
225    }
226
227    fn drain_focus_requests(&self) {
228        let Some(first) = self.manager.borrow_mut().take_first_focus_request_if_idle() else {
229            return;
230        };
231
232        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
233            let mut current = first;
234            loop {
235                self.apply_focus_change(current);
236                match self.manager.borrow_mut().take_next_focus_request() {
237                    Some(next) => current = next,
238                    None => break,
239                }
240            }
241        }));
242
243        self.manager.borrow_mut().finish_focus_dispatch();
244
245        if let Err(payload) = result {
246            std::panic::resume_unwind(payload);
247        }
248    }
249
250    fn apply_focus_change(&self, node_id: NodeId) {
251        let previous = self.manager.borrow_mut().swap_active_focus_target(node_id);
252
253        if let Some(previous) = previous {
254            let losing_handles = self.manager.borrow().focus_target_handles(previous);
255            for handle in losing_handles {
256                handle.set_focus_state(FocusState::Inactive);
257            }
258        }
259
260        let gaining_handles = self.manager.borrow().focus_target_handles(node_id);
261        for handle in gaining_handles {
262            handle.set_focus_state(FocusState::Active);
263        }
264    }
265
266    fn process_invalidations<F>(&self, processor: F)
267    where
268        F: FnMut(NodeId),
269    {
270        let Some(nodes) = self.manager.borrow_mut().take_pending_for_processing() else {
271            return;
272        };
273
274        self.process_pending_nodes(nodes, processor);
275    }
276
277    fn clear(&self) {
278        self.manager.borrow_mut().clear();
279    }
280
281    fn process_pending_nodes<F>(&self, nodes: Vec<NodeId>, mut processor: F)
282    where
283        F: FnMut(NodeId),
284    {
285        let mut remaining = nodes.into_iter();
286        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
287            for node_id in remaining.by_ref() {
288                processor(node_id);
289            }
290        }));
291
292        self.manager.borrow_mut().finish_processing(remaining);
293
294        if let Err(payload) = result {
295            std::panic::resume_unwind(payload);
296        }
297    }
298}
299
300/// Schedules a focus invalidation for the specified node.
301///
302/// This is called automatically when focus modifiers invalidate
303/// and mirrors Kotlin's `FocusInvalidationManager.scheduleInvalidation`.
304pub fn schedule_focus_invalidation(node_id: NodeId) {
305    crate::render_state::with_focus_dispatch(|state| state.schedule_invalidation(node_id));
306}
307
308/// Returns true if any focus invalidations are pending.
309pub fn has_pending_focus_invalidations() -> bool {
310    crate::render_state::with_focus_dispatch(FocusInvalidationState::has_pending_invalidation)
311}
312
313/// Sets the currently active focus target.
314///
315/// This mirrors Kotlin's `FocusOwner.activeFocusTargetNode` and allows
316/// the focus system to track which node currently has focus.
317pub fn set_active_focus_target(node_id: Option<NodeId>) {
318    crate::render_state::with_focus_dispatch(|state| state.set_active_focus_target(node_id));
319}
320
321/// Returns the currently active focus target, if any.
322pub fn active_focus_target() -> Option<NodeId> {
323    crate::render_state::with_focus_dispatch(FocusInvalidationState::active_focus_target)
324}
325
326pub(crate) fn register_focus_target(node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
327    crate::render_state::with_focus_dispatch(|state| state.register_focus_target(node_id, handle));
328}
329
330pub(crate) fn unregister_focus_target(node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
331    crate::render_state::with_focus_dispatch(|state| {
332        state.unregister_focus_target(node_id, handle);
333    });
334}
335
336#[cfg(test)]
337pub(crate) fn request_focus(node_id: NodeId) -> bool {
338    crate::render_state::with_focus_dispatch(|state| state.request_focus(node_id))
339}
340
341/// Whether `node_id` registered a focus target in this app context.
342pub(crate) fn has_focus_target(node_id: NodeId) -> bool {
343    crate::render_state::with_focus_dispatch(|state| state.has_focus_target(node_id))
344}
345
346pub(crate) fn request_focus_in_context(node_id: NodeId) -> bool {
347    let Some(app_context) = crate::render_state::current_app_context_id_opt() else {
348        return false;
349    };
350    request_focus_for(app_context, node_id).unwrap_or(false)
351}
352
353/// Drops focus from the active target and answers whether one held it.
354pub(crate) fn clear_active_focus() -> bool {
355    crate::render_state::with_focus_dispatch(FocusInvalidationState::clear_active_focus)
356}
357
358pub(crate) fn request_focus_for(
359    app_context: crate::render_state::AppContextId,
360    node_id: NodeId,
361) -> Option<bool> {
362    crate::render_state::with_focus_dispatch_by_app_context(app_context, |state| {
363        state.request_focus(node_id)
364    })
365}
366
367/// Processes all pending focus invalidations.
368///
369/// The host (e.g., app shell or layout engine) should call this after
370/// composition/layout to service focus invalidations without forcing
371/// measure/layout passes.
372pub fn process_focus_invalidations<F>(processor: F)
373where
374    F: FnMut(NodeId),
375{
376    crate::render_state::with_focus_dispatch(|state| state.process_invalidations(processor));
377}
378
379/// Clears all pending focus invalidations without processing them.
380pub fn clear_focus_invalidations() {
381    crate::render_state::with_focus_dispatch(FocusInvalidationState::clear);
382}
383
384#[cfg(test)]
385#[path = "tests/focus_dispatch_tests.rs"]
386mod tests;
387
388thread_local! {
389    static KEYBOARD_FOCUS_VISIBLE: Cell<bool> = const { Cell::new(false) };
390}
391
392/// Records how focus last moved: true after Tab or an arrow key, false after
393/// a pointer press. A [`Modifier::focusable`](crate::Modifier::focusable)
394/// draws its ring only while this is true. Returns whether the value changed,
395/// so the caller can ask for a redraw.
396pub fn set_keyboard_focus_visible(visible: bool) -> bool {
397    KEYBOARD_FOCUS_VISIBLE.with(|cell| cell.replace(visible) != visible)
398}
399
400/// Whether the keyboard, and not a pointer, made the last focus move.
401pub fn keyboard_focus_visible() -> bool {
402    KEYBOARD_FOCUS_VISIBLE.with(Cell::get)
403}