cranpose_ui/
focus_dispatch.rs1use 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 fn register_focus_target(&mut self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
59 self.focus_targets.entry(node_id).or_default().push(handle);
60 }
61
62 fn unregister_focus_target(&mut self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
63 let Some(handles) = self.focus_targets.get_mut(&node_id) else {
64 return;
65 };
66 handles.retain(|existing| !Rc::ptr_eq(existing, handle));
67 if handles.is_empty() {
68 self.focus_targets.remove(&node_id);
69 if self.active_focus_target == Some(node_id) {
70 self.set_active_focus_target(None);
71 }
72 }
73 }
74
75 fn has_focus_target(&self, node_id: NodeId) -> bool {
76 self.focus_targets.contains_key(&node_id)
77 }
78
79 fn focus_target_handles(&self, node_id: NodeId) -> Vec<Rc<dyn FocusTargetHandle>> {
80 self.focus_targets
81 .get(&node_id)
82 .cloned()
83 .unwrap_or_default()
84 }
85
86 fn swap_active_focus_target(&mut self, node_id: NodeId) -> Option<NodeId> {
87 if self.active_focus_target == Some(node_id) {
88 return None;
89 }
90 let previous = self.active_focus_target;
91 self.set_active_focus_target(Some(node_id));
92 previous
93 }
94
95 fn take_first_focus_request_if_idle(&mut self) -> Option<NodeId> {
96 if self.dispatching_focus {
97 return None;
98 }
99 let next = self.pending_focus_requests.pop_front()?;
100 self.dispatching_focus = true;
101 Some(next)
102 }
103
104 fn take_next_focus_request(&mut self) -> Option<NodeId> {
105 self.pending_focus_requests.pop_front()
106 }
107
108 fn finish_focus_dispatch(&mut self) {
109 self.dispatching_focus = false;
110 }
111
112 fn take_pending_for_processing(&mut self) -> Option<Vec<NodeId>> {
113 if self.is_processing {
114 return None;
115 }
116
117 self.is_processing = true;
118 Some(self.dirty_nodes.drain().collect())
119 }
120
121 fn finish_processing<I>(&mut self, remaining: I)
122 where
123 I: IntoIterator<Item = NodeId>,
124 {
125 self.dirty_nodes.extend(remaining);
126 self.is_processing = false;
127 }
128
129 fn clear(&mut self) {
130 self.dirty_nodes.clear();
131 }
132}
133
134pub(crate) struct FocusInvalidationState {
135 manager: RefCell<FocusInvalidationManager>,
136 order: RefCell<Vec<crate::FocusEntry>>,
137}
138
139impl FocusInvalidationState {
140 pub(crate) fn new() -> Self {
141 Self {
142 manager: RefCell::new(FocusInvalidationManager::new()),
143 order: RefCell::new(Vec::new()),
144 }
145 }
146
147 pub(crate) fn set_focus_order(&self, entries: Vec<crate::FocusEntry>) {
148 *self.order.borrow_mut() = entries;
149 }
150
151 pub(crate) fn with_focus_order<T>(&self, reader: impl FnOnce(&[crate::FocusEntry]) -> T) -> T {
152 reader(&self.order.borrow())
153 }
154
155 fn schedule_invalidation(&self, node_id: NodeId) {
156 self.manager.borrow_mut().schedule_invalidation(node_id);
157 }
158
159 fn has_pending_invalidation(&self) -> bool {
160 self.manager.borrow().has_pending_invalidation()
161 }
162
163 fn set_active_focus_target(&self, node_id: Option<NodeId>) {
164 self.manager.borrow_mut().set_active_focus_target(node_id);
165 }
166
167 fn active_focus_target(&self) -> Option<NodeId> {
168 self.manager.borrow().active_focus_target()
169 }
170
171 fn register_focus_target(&self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
172 self.manager
173 .borrow_mut()
174 .register_focus_target(node_id, handle);
175 }
176
177 fn unregister_focus_target(&self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
178 self.manager
179 .borrow_mut()
180 .unregister_focus_target(node_id, handle);
181 }
182
183 pub(crate) fn has_focus_target(&self, node_id: NodeId) -> bool {
184 self.manager.borrow().has_focus_target(node_id)
185 }
186
187 pub(crate) fn clear_active_focus(&self) -> bool {
188 let previous = {
189 let mut manager = self.manager.borrow_mut();
190 let previous = manager.active_focus_target();
191 manager.set_active_focus_target(None);
192 previous
193 };
194 let Some(previous) = previous else {
195 return false;
196 };
197 let handles = self.manager.borrow().focus_target_handles(previous);
198 for handle in handles {
199 handle.set_focus_state(FocusState::Inactive);
200 }
201 true
202 }
203
204 pub(crate) fn request_focus(&self, node_id: NodeId) -> bool {
205 let accepted = {
206 let mut manager = self.manager.borrow_mut();
207 if !manager.has_focus_target(node_id) {
208 false
209 } else {
210 manager.pending_focus_requests.push_back(node_id);
211 true
212 }
213 };
214 if accepted {
215 self.drain_focus_requests();
216 }
217 accepted
218 }
219
220 fn drain_focus_requests(&self) {
221 let Some(first) = self.manager.borrow_mut().take_first_focus_request_if_idle() else {
222 return;
223 };
224
225 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
226 let mut current = first;
227 loop {
228 self.apply_focus_change(current);
229 match self.manager.borrow_mut().take_next_focus_request() {
230 Some(next) => current = next,
231 None => break,
232 }
233 }
234 }));
235
236 self.manager.borrow_mut().finish_focus_dispatch();
237
238 if let Err(payload) = result {
239 std::panic::resume_unwind(payload);
240 }
241 }
242
243 fn apply_focus_change(&self, node_id: NodeId) {
244 let previous = self.manager.borrow_mut().swap_active_focus_target(node_id);
245
246 if let Some(previous) = previous {
247 let losing_handles = self.manager.borrow().focus_target_handles(previous);
248 for handle in losing_handles {
249 handle.set_focus_state(FocusState::Inactive);
250 }
251 }
252
253 let gaining_handles = self.manager.borrow().focus_target_handles(node_id);
254 for handle in gaining_handles {
255 handle.set_focus_state(FocusState::Active);
256 }
257 }
258
259 fn process_invalidations<F>(&self, processor: F)
260 where
261 F: FnMut(NodeId),
262 {
263 let Some(nodes) = self.manager.borrow_mut().take_pending_for_processing() else {
264 return;
265 };
266
267 self.process_pending_nodes(nodes, processor);
268 }
269
270 fn clear(&self) {
271 self.manager.borrow_mut().clear();
272 }
273
274 fn process_pending_nodes<F>(&self, nodes: Vec<NodeId>, mut processor: F)
275 where
276 F: FnMut(NodeId),
277 {
278 let mut remaining = nodes.into_iter();
279 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
280 for node_id in remaining.by_ref() {
281 processor(node_id);
282 }
283 }));
284
285 self.manager.borrow_mut().finish_processing(remaining);
286
287 if let Err(payload) = result {
288 std::panic::resume_unwind(payload);
289 }
290 }
291}
292
293pub fn schedule_focus_invalidation(node_id: NodeId) {
298 crate::render_state::with_focus_dispatch(|state| state.schedule_invalidation(node_id));
299}
300
301pub fn has_pending_focus_invalidations() -> bool {
303 crate::render_state::with_focus_dispatch(FocusInvalidationState::has_pending_invalidation)
304}
305
306pub fn set_active_focus_target(node_id: Option<NodeId>) {
311 crate::render_state::with_focus_dispatch(|state| state.set_active_focus_target(node_id));
312}
313
314pub fn active_focus_target() -> Option<NodeId> {
316 crate::render_state::with_focus_dispatch(FocusInvalidationState::active_focus_target)
317}
318
319pub(crate) fn register_focus_target(node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
320 crate::render_state::with_focus_dispatch(|state| state.register_focus_target(node_id, handle));
321}
322
323pub(crate) fn unregister_focus_target(node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
324 crate::render_state::with_focus_dispatch(|state| {
325 state.unregister_focus_target(node_id, handle);
326 });
327}
328
329#[cfg(test)]
330pub(crate) fn request_focus(node_id: NodeId) -> bool {
331 crate::render_state::with_focus_dispatch(|state| state.request_focus(node_id))
332}
333
334pub(crate) fn has_focus_target(node_id: NodeId) -> bool {
336 crate::render_state::with_focus_dispatch(|state| state.has_focus_target(node_id))
337}
338
339pub(crate) fn request_focus_in_context(node_id: NodeId) -> bool {
340 let Some(app_context) = crate::render_state::current_app_context_id_opt() else {
341 return false;
342 };
343 request_focus_for(app_context, node_id).unwrap_or(false)
344}
345
346pub(crate) fn clear_active_focus() -> bool {
348 crate::render_state::with_focus_dispatch(FocusInvalidationState::clear_active_focus)
349}
350
351pub(crate) fn request_focus_for(
352 app_context: crate::render_state::AppContextId,
353 node_id: NodeId,
354) -> Option<bool> {
355 crate::render_state::with_focus_dispatch_by_app_context(app_context, |state| {
356 state.request_focus(node_id)
357 })
358}
359
360pub fn process_focus_invalidations<F>(processor: F)
366where
367 F: FnMut(NodeId),
368{
369 crate::render_state::with_focus_dispatch(|state| state.process_invalidations(processor));
370}
371
372pub fn clear_focus_invalidations() {
374 crate::render_state::with_focus_dispatch(FocusInvalidationState::clear);
375}
376
377#[cfg(test)]
378#[path = "tests/focus_dispatch_tests.rs"]
379mod tests;
380
381thread_local! {
382 static KEYBOARD_FOCUS_VISIBLE: Cell<bool> = const { Cell::new(false) };
383}
384
385pub fn set_keyboard_focus_visible(visible: bool) -> bool {
390 KEYBOARD_FOCUS_VISIBLE.with(|cell| cell.replace(visible) != visible)
391}
392
393pub fn keyboard_focus_visible() -> bool {
395 KEYBOARD_FOCUS_VISIBLE.with(Cell::get)
396}