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 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(|state| state.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(|state| state.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(|state| state.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(|state| state.clear());
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn schedule_and_process_invalidations() {
383 let _app_context = crate::render_state::app_context_test_scope();
384 clear_focus_invalidations();
385
386 let node1: NodeId = 1;
387 let node2: NodeId = 2;
388
389 schedule_focus_invalidation(node1);
390 schedule_focus_invalidation(node2);
391
392 assert!(has_pending_focus_invalidations());
393
394 let mut processed = Vec::new();
395 process_focus_invalidations(|node_id| {
396 processed.push(node_id);
397 });
398
399 assert_eq!(processed.len(), 2);
400 assert!(processed.contains(&node1));
401 assert!(processed.contains(&node2));
402 assert!(!has_pending_focus_invalidations());
403 }
404
405 #[test]
406 fn active_focus_target_tracking() {
407 let _app_context = crate::render_state::app_context_test_scope();
408 set_active_focus_target(None);
409 assert_eq!(active_focus_target(), None);
410
411 let node: NodeId = 42;
412 set_active_focus_target(Some(node));
413 assert_eq!(active_focus_target(), Some(node));
414
415 set_active_focus_target(None);
416 assert_eq!(active_focus_target(), None);
417 }
418
419 #[test]
420 fn duplicate_invalidations_deduplicated() {
421 let _app_context = crate::render_state::app_context_test_scope();
422 clear_focus_invalidations();
423
424 let node: NodeId = 42;
425 schedule_focus_invalidation(node);
426 schedule_focus_invalidation(node);
427 schedule_focus_invalidation(node);
428
429 let mut count = 0;
430 process_focus_invalidations(|_| {
431 count += 1;
432 });
433
434 assert_eq!(count, 1);
435 }
436
437 #[test]
438 fn process_invalidations_recovers_after_processor_panic() {
439 let _app_context = crate::render_state::app_context_test_scope();
440 clear_focus_invalidations();
441
442 schedule_focus_invalidation(1);
443 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
444 process_focus_invalidations(|_| panic!("focus processor panic"));
445 }));
446 assert!(result.is_err());
447
448 schedule_focus_invalidation(2);
449 let mut processed = Vec::new();
450 process_focus_invalidations(|node_id| processed.push(node_id));
451
452 assert!(
453 processed.contains(&2),
454 "focus invalidation processing must not stay stuck after a processor panic"
455 );
456 assert!(!has_pending_focus_invalidations());
457 }
458
459 #[test]
460 fn process_invalidations_allows_processor_to_schedule_more_work() {
461 let _app_context = crate::render_state::app_context_test_scope();
462 clear_focus_invalidations();
463
464 schedule_focus_invalidation(1);
465 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
466 process_focus_invalidations(|_| schedule_focus_invalidation(2));
467 }));
468 assert!(
469 result.is_ok(),
470 "focus processors must be able to enqueue follow-up invalidations"
471 );
472 assert!(has_pending_focus_invalidations());
473
474 let mut processed = Vec::new();
475 process_focus_invalidations(|node_id| processed.push(node_id));
476
477 assert_eq!(processed, vec![2]);
478 assert!(!has_pending_focus_invalidations());
479 }
480
481 #[test]
482 fn focus_state_is_scoped_by_app_context() {
483 let _app_context = crate::render_state::app_context_test_scope();
484 let first = crate::render_state::AppContext::new_with_density(1.0);
485 let second = crate::render_state::AppContext::new_with_density(1.0);
486
487 first.enter(|| {
488 clear_focus_invalidations();
489 schedule_focus_invalidation(7);
490 set_active_focus_target(Some(17));
491 assert!(has_pending_focus_invalidations());
492 assert_eq!(active_focus_target(), Some(17));
493 });
494
495 second.enter(|| {
496 clear_focus_invalidations();
497 assert!(!has_pending_focus_invalidations());
498 assert_eq!(active_focus_target(), None);
499 schedule_focus_invalidation(9);
500 set_active_focus_target(Some(19));
501 });
502
503 first.enter(|| {
504 let mut processed = Vec::new();
505 process_focus_invalidations(|node_id| processed.push(node_id));
506 assert_eq!(processed, vec![7]);
507 assert_eq!(active_focus_target(), Some(17));
508 });
509
510 second.enter(|| {
511 let mut processed = Vec::new();
512 process_focus_invalidations(|node_id| processed.push(node_id));
513 assert_eq!(processed, vec![9]);
514 assert_eq!(active_focus_target(), Some(19));
515 });
516 }
517
518 struct RecordingTarget {
519 states: RefCell<Vec<FocusState>>,
520 on_active: RefCell<Option<Box<dyn Fn()>>>,
521 }
522
523 impl RecordingTarget {
524 fn new() -> Rc<Self> {
525 Rc::new(Self {
526 states: RefCell::new(Vec::new()),
527 on_active: RefCell::new(None),
528 })
529 }
530
531 fn states(&self) -> Vec<FocusState> {
532 self.states.borrow().clone()
533 }
534 }
535
536 impl FocusTargetHandle for RecordingTarget {
537 fn set_focus_state(&self, state: FocusState) {
538 self.states.borrow_mut().push(state);
539 if state == FocusState::Active
540 && let Some(callback) = self.on_active.borrow_mut().take()
541 {
542 callback();
543 }
544 }
545 }
546
547 fn as_handle(target: &Rc<RecordingTarget>) -> Rc<dyn FocusTargetHandle> {
548 Rc::clone(target) as Rc<dyn FocusTargetHandle>
549 }
550
551 #[test]
552 fn request_focus_moves_focus_between_two_registered_targets() {
553 let _app_context = crate::render_state::app_context_test_scope();
554 clear_focus_invalidations();
555 set_active_focus_target(None);
556
557 let a = RecordingTarget::new();
558 let b = RecordingTarget::new();
559 register_focus_target(1, as_handle(&a));
560 register_focus_target(2, as_handle(&b));
561
562 assert!(request_focus(1));
563 assert_eq!(a.states(), vec![FocusState::Active]);
564 assert_eq!(active_focus_target(), Some(1));
565
566 assert!(request_focus(2));
567 assert_eq!(a.states(), vec![FocusState::Active, FocusState::Inactive]);
568 assert_eq!(b.states(), vec![FocusState::Active]);
569 assert_eq!(active_focus_target(), Some(2));
570 }
571
572 #[test]
573 fn request_focus_on_an_unregistered_node_fails_without_changing_anything() {
574 let _app_context = crate::render_state::app_context_test_scope();
575 clear_focus_invalidations();
576 set_active_focus_target(None);
577
578 assert!(!request_focus(99));
579 assert_eq!(active_focus_target(), None);
580 }
581
582 #[test]
583 fn unregistering_the_active_targets_last_handle_clears_active_focus() {
584 let _app_context = crate::render_state::app_context_test_scope();
585 clear_focus_invalidations();
586 set_active_focus_target(None);
587
588 let a = RecordingTarget::new();
589 let handle = as_handle(&a);
590 register_focus_target(1, Rc::clone(&handle));
591 assert!(request_focus(1));
592 assert_eq!(active_focus_target(), Some(1));
593
594 unregister_focus_target(1, &handle);
595 assert_eq!(active_focus_target(), None);
596 assert!(!request_focus(1));
597 }
598
599 #[test]
600 fn request_focus_from_inside_a_callback_does_not_double_borrow_or_recurse() {
601 let _app_context = crate::render_state::app_context_test_scope();
602 clear_focus_invalidations();
603 set_active_focus_target(None);
604
605 let a = RecordingTarget::new();
606 let b = RecordingTarget::new();
607 register_focus_target(1, as_handle(&a));
608 register_focus_target(2, as_handle(&b));
609
610 *a.on_active.borrow_mut() = Some(Box::new({
611 let bounced_back = RefCell::new(false);
612 move || {
613 if !*bounced_back.borrow() {
614 *bounced_back.borrow_mut() = true;
615 assert!(request_focus(2));
616 }
617 }
618 }));
619
620 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
621 assert!(request_focus(1));
622 }));
623 assert!(
624 result.is_ok(),
625 "a request_focus call from inside a callback must not panic or overflow the stack"
626 );
627
628 assert_eq!(a.states(), vec![FocusState::Active, FocusState::Inactive]);
629 assert_eq!(b.states(), vec![FocusState::Active]);
630 assert_eq!(active_focus_target(), Some(2));
631 }
632
633 #[test]
634 fn a_panicking_callback_still_releases_the_dispatch_lock() {
635 let _app_context = crate::render_state::app_context_test_scope();
636 clear_focus_invalidations();
637 set_active_focus_target(None);
638
639 struct PanicsOnActivate;
640 impl FocusTargetHandle for PanicsOnActivate {
641 fn set_focus_state(&self, state: FocusState) {
642 if state == FocusState::Active {
643 panic!("focus target panicked while activating");
644 }
645 }
646 }
647
648 register_focus_target(1, Rc::new(PanicsOnActivate) as Rc<dyn FocusTargetHandle>);
649 let b = RecordingTarget::new();
650 register_focus_target(2, as_handle(&b));
651
652 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
653 request_focus(1);
654 }));
655 assert!(result.is_err());
656
657 assert!(request_focus(2));
658 assert_eq!(
659 b.states(),
660 vec![FocusState::Active],
661 "the dispatch lock must be released after a callback panic so later requests proceed"
662 );
663 }
664}
665
666thread_local! {
667 static KEYBOARD_FOCUS_VISIBLE: Cell<bool> = const { Cell::new(false) };
668}
669
670pub fn set_keyboard_focus_visible(visible: bool) -> bool {
675 KEYBOARD_FOCUS_VISIBLE.with(|cell| cell.replace(visible) != visible)
676}
677
678pub fn keyboard_focus_visible() -> bool {
680 KEYBOARD_FOCUS_VISIBLE.with(|cell| cell.get())
681}