Skip to main content

cranpose_ui/
focus_dispatch.rs

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