1use std::{
2 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 self.active_focus_target = node_id;
45 }
46
47 fn active_focus_target(&self) -> Option<NodeId> {
48 self.active_focus_target
49 }
50
51 fn register_focus_target(&mut self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
52 self.focus_targets.entry(node_id).or_default().push(handle);
53 }
54
55 fn unregister_focus_target(&mut self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
56 let Some(handles) = self.focus_targets.get_mut(&node_id) else {
57 return;
58 };
59 handles.retain(|existing| !Rc::ptr_eq(existing, handle));
60 if handles.is_empty() {
61 self.focus_targets.remove(&node_id);
62 if self.active_focus_target == Some(node_id) {
63 self.active_focus_target = None;
64 }
65 }
66 }
67
68 fn has_focus_target(&self, node_id: NodeId) -> bool {
69 self.focus_targets.contains_key(&node_id)
70 }
71
72 fn focus_target_handles(&self, node_id: NodeId) -> Vec<Rc<dyn FocusTargetHandle>> {
73 self.focus_targets
74 .get(&node_id)
75 .cloned()
76 .unwrap_or_default()
77 }
78
79 fn swap_active_focus_target(&mut self, node_id: NodeId) -> Option<NodeId> {
80 if self.active_focus_target == Some(node_id) {
81 return None;
82 }
83 self.active_focus_target.replace(node_id)
84 }
85
86 fn take_first_focus_request_if_idle(&mut self) -> Option<NodeId> {
87 if self.dispatching_focus {
88 return None;
89 }
90 let next = self.pending_focus_requests.pop_front()?;
91 self.dispatching_focus = true;
92 Some(next)
93 }
94
95 fn take_next_focus_request(&mut self) -> Option<NodeId> {
96 self.pending_focus_requests.pop_front()
97 }
98
99 fn finish_focus_dispatch(&mut self) {
100 self.dispatching_focus = false;
101 }
102
103 fn take_pending_for_processing(&mut self) -> Option<Vec<NodeId>> {
104 if self.is_processing {
105 return None;
106 }
107
108 self.is_processing = true;
109 Some(self.dirty_nodes.drain().collect())
110 }
111
112 fn finish_processing<I>(&mut self, remaining: I)
113 where
114 I: IntoIterator<Item = NodeId>,
115 {
116 self.dirty_nodes.extend(remaining);
117 self.is_processing = false;
118 }
119
120 fn clear(&mut self) {
121 self.dirty_nodes.clear();
122 }
123}
124
125pub(crate) struct FocusInvalidationState {
126 manager: RefCell<FocusInvalidationManager>,
127}
128
129impl FocusInvalidationState {
130 pub(crate) fn new() -> Self {
131 Self {
132 manager: RefCell::new(FocusInvalidationManager::new()),
133 }
134 }
135
136 fn schedule_invalidation(&self, node_id: NodeId) {
137 self.manager.borrow_mut().schedule_invalidation(node_id);
138 }
139
140 fn has_pending_invalidation(&self) -> bool {
141 self.manager.borrow().has_pending_invalidation()
142 }
143
144 fn set_active_focus_target(&self, node_id: Option<NodeId>) {
145 self.manager.borrow_mut().set_active_focus_target(node_id);
146 }
147
148 fn active_focus_target(&self) -> Option<NodeId> {
149 self.manager.borrow().active_focus_target()
150 }
151
152 fn register_focus_target(&self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
153 self.manager
154 .borrow_mut()
155 .register_focus_target(node_id, handle);
156 }
157
158 fn unregister_focus_target(&self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
159 self.manager
160 .borrow_mut()
161 .unregister_focus_target(node_id, handle);
162 }
163
164 pub(crate) fn has_focus_target(&self, node_id: NodeId) -> bool {
165 self.manager.borrow().has_focus_target(node_id)
166 }
167
168 pub(crate) fn clear_active_focus(&self) -> bool {
169 let previous = {
170 let mut manager = self.manager.borrow_mut();
171 let previous = manager.active_focus_target();
172 manager.set_active_focus_target(None);
173 previous
174 };
175 let Some(previous) = previous else {
176 return false;
177 };
178 let handles = self.manager.borrow().focus_target_handles(previous);
179 for handle in handles {
180 handle.set_focus_state(FocusState::Inactive);
181 }
182 true
183 }
184
185 pub(crate) fn request_focus(&self, node_id: NodeId) -> bool {
186 let accepted = {
187 let mut manager = self.manager.borrow_mut();
188 if !manager.has_focus_target(node_id) {
189 false
190 } else {
191 manager.pending_focus_requests.push_back(node_id);
192 true
193 }
194 };
195 if accepted {
196 self.drain_focus_requests();
197 }
198 accepted
199 }
200
201 fn drain_focus_requests(&self) {
202 let Some(first) = self.manager.borrow_mut().take_first_focus_request_if_idle() else {
203 return;
204 };
205
206 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
207 let mut current = first;
208 loop {
209 self.apply_focus_change(current);
210 match self.manager.borrow_mut().take_next_focus_request() {
211 Some(next) => current = next,
212 None => break,
213 }
214 }
215 }));
216
217 self.manager.borrow_mut().finish_focus_dispatch();
218
219 if let Err(payload) = result {
220 std::panic::resume_unwind(payload);
221 }
222 }
223
224 fn apply_focus_change(&self, node_id: NodeId) {
225 let previous = self.manager.borrow_mut().swap_active_focus_target(node_id);
226
227 if let Some(previous) = previous {
228 let losing_handles = self.manager.borrow().focus_target_handles(previous);
229 for handle in losing_handles {
230 handle.set_focus_state(FocusState::Inactive);
231 }
232 }
233
234 let gaining_handles = self.manager.borrow().focus_target_handles(node_id);
235 for handle in gaining_handles {
236 handle.set_focus_state(FocusState::Active);
237 }
238 }
239
240 fn process_invalidations<F>(&self, processor: F)
241 where
242 F: FnMut(NodeId),
243 {
244 let Some(nodes) = self.manager.borrow_mut().take_pending_for_processing() else {
245 return;
246 };
247
248 self.process_pending_nodes(nodes, processor);
249 }
250
251 fn clear(&self) {
252 self.manager.borrow_mut().clear();
253 }
254
255 fn process_pending_nodes<F>(&self, nodes: Vec<NodeId>, mut processor: F)
256 where
257 F: FnMut(NodeId),
258 {
259 let mut remaining = nodes.into_iter();
260 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
261 for node_id in remaining.by_ref() {
262 processor(node_id);
263 }
264 }));
265
266 self.manager.borrow_mut().finish_processing(remaining);
267
268 if let Err(payload) = result {
269 std::panic::resume_unwind(payload);
270 }
271 }
272}
273
274pub fn schedule_focus_invalidation(node_id: NodeId) {
279 crate::render_state::with_focus_dispatch(|state| state.schedule_invalidation(node_id));
280}
281
282pub fn has_pending_focus_invalidations() -> bool {
284 crate::render_state::with_focus_dispatch(|state| state.has_pending_invalidation())
285}
286
287pub fn set_active_focus_target(node_id: Option<NodeId>) {
292 crate::render_state::with_focus_dispatch(|state| state.set_active_focus_target(node_id));
293}
294
295pub fn active_focus_target() -> Option<NodeId> {
297 crate::render_state::with_focus_dispatch(|state| state.active_focus_target())
298}
299
300pub(crate) fn register_focus_target(node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
301 crate::render_state::with_focus_dispatch(|state| state.register_focus_target(node_id, handle));
302}
303
304pub(crate) fn unregister_focus_target(node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
305 crate::render_state::with_focus_dispatch(|state| {
306 state.unregister_focus_target(node_id, handle)
307 });
308}
309
310#[cfg(test)]
311pub(crate) fn request_focus(node_id: NodeId) -> bool {
312 crate::render_state::with_focus_dispatch(|state| state.request_focus(node_id))
313}
314
315pub(crate) fn has_focus_target(node_id: NodeId) -> bool {
317 crate::render_state::with_focus_dispatch(|state| state.has_focus_target(node_id))
318}
319
320pub(crate) fn request_focus_in_context(node_id: NodeId) -> bool {
321 let Some(app_context) = crate::render_state::current_app_context_id_opt() else {
322 return false;
323 };
324 request_focus_for(app_context, node_id).unwrap_or(false)
325}
326
327pub(crate) fn clear_active_focus() -> bool {
329 crate::render_state::with_focus_dispatch(|state| state.clear_active_focus())
330}
331
332pub(crate) fn request_focus_for(
333 app_context: crate::render_state::AppContextId,
334 node_id: NodeId,
335) -> Option<bool> {
336 crate::render_state::with_focus_dispatch_by_app_context(app_context, |state| {
337 state.request_focus(node_id)
338 })
339}
340
341pub fn process_focus_invalidations<F>(processor: F)
347where
348 F: FnMut(NodeId),
349{
350 crate::render_state::with_focus_dispatch(|state| state.process_invalidations(processor));
351}
352
353pub fn clear_focus_invalidations() {
355 crate::render_state::with_focus_dispatch(|state| state.clear());
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn schedule_and_process_invalidations() {
364 let _app_context = crate::render_state::app_context_test_scope();
365 clear_focus_invalidations();
366
367 let node1: NodeId = 1;
368 let node2: NodeId = 2;
369
370 schedule_focus_invalidation(node1);
371 schedule_focus_invalidation(node2);
372
373 assert!(has_pending_focus_invalidations());
374
375 let mut processed = Vec::new();
376 process_focus_invalidations(|node_id| {
377 processed.push(node_id);
378 });
379
380 assert_eq!(processed.len(), 2);
381 assert!(processed.contains(&node1));
382 assert!(processed.contains(&node2));
383 assert!(!has_pending_focus_invalidations());
384 }
385
386 #[test]
387 fn active_focus_target_tracking() {
388 let _app_context = crate::render_state::app_context_test_scope();
389 set_active_focus_target(None);
390 assert_eq!(active_focus_target(), None);
391
392 let node: NodeId = 42;
393 set_active_focus_target(Some(node));
394 assert_eq!(active_focus_target(), Some(node));
395
396 set_active_focus_target(None);
397 assert_eq!(active_focus_target(), None);
398 }
399
400 #[test]
401 fn duplicate_invalidations_deduplicated() {
402 let _app_context = crate::render_state::app_context_test_scope();
403 clear_focus_invalidations();
404
405 let node: NodeId = 42;
406 schedule_focus_invalidation(node);
407 schedule_focus_invalidation(node);
408 schedule_focus_invalidation(node);
409
410 let mut count = 0;
411 process_focus_invalidations(|_| {
412 count += 1;
413 });
414
415 assert_eq!(count, 1);
416 }
417
418 #[test]
419 fn process_invalidations_recovers_after_processor_panic() {
420 let _app_context = crate::render_state::app_context_test_scope();
421 clear_focus_invalidations();
422
423 schedule_focus_invalidation(1);
424 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
425 process_focus_invalidations(|_| panic!("focus processor panic"));
426 }));
427 assert!(result.is_err());
428
429 schedule_focus_invalidation(2);
430 let mut processed = Vec::new();
431 process_focus_invalidations(|node_id| processed.push(node_id));
432
433 assert!(
434 processed.contains(&2),
435 "focus invalidation processing must not stay stuck after a processor panic"
436 );
437 assert!(!has_pending_focus_invalidations());
438 }
439
440 #[test]
441 fn process_invalidations_allows_processor_to_schedule_more_work() {
442 let _app_context = crate::render_state::app_context_test_scope();
443 clear_focus_invalidations();
444
445 schedule_focus_invalidation(1);
446 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
447 process_focus_invalidations(|_| schedule_focus_invalidation(2));
448 }));
449 assert!(
450 result.is_ok(),
451 "focus processors must be able to enqueue follow-up invalidations"
452 );
453 assert!(has_pending_focus_invalidations());
454
455 let mut processed = Vec::new();
456 process_focus_invalidations(|node_id| processed.push(node_id));
457
458 assert_eq!(processed, vec![2]);
459 assert!(!has_pending_focus_invalidations());
460 }
461
462 #[test]
463 fn focus_state_is_scoped_by_app_context() {
464 let _app_context = crate::render_state::app_context_test_scope();
465 let first = crate::render_state::AppContext::new_with_density(1.0);
466 let second = crate::render_state::AppContext::new_with_density(1.0);
467
468 first.enter(|| {
469 clear_focus_invalidations();
470 schedule_focus_invalidation(7);
471 set_active_focus_target(Some(17));
472 assert!(has_pending_focus_invalidations());
473 assert_eq!(active_focus_target(), Some(17));
474 });
475
476 second.enter(|| {
477 clear_focus_invalidations();
478 assert!(!has_pending_focus_invalidations());
479 assert_eq!(active_focus_target(), None);
480 schedule_focus_invalidation(9);
481 set_active_focus_target(Some(19));
482 });
483
484 first.enter(|| {
485 let mut processed = Vec::new();
486 process_focus_invalidations(|node_id| processed.push(node_id));
487 assert_eq!(processed, vec![7]);
488 assert_eq!(active_focus_target(), Some(17));
489 });
490
491 second.enter(|| {
492 let mut processed = Vec::new();
493 process_focus_invalidations(|node_id| processed.push(node_id));
494 assert_eq!(processed, vec![9]);
495 assert_eq!(active_focus_target(), Some(19));
496 });
497 }
498
499 struct RecordingTarget {
500 states: RefCell<Vec<FocusState>>,
501 on_active: RefCell<Option<Box<dyn Fn()>>>,
502 }
503
504 impl RecordingTarget {
505 fn new() -> Rc<Self> {
506 Rc::new(Self {
507 states: RefCell::new(Vec::new()),
508 on_active: RefCell::new(None),
509 })
510 }
511
512 fn states(&self) -> Vec<FocusState> {
513 self.states.borrow().clone()
514 }
515 }
516
517 impl FocusTargetHandle for RecordingTarget {
518 fn set_focus_state(&self, state: FocusState) {
519 self.states.borrow_mut().push(state);
520 if state == FocusState::Active
521 && let Some(callback) = self.on_active.borrow_mut().take()
522 {
523 callback();
524 }
525 }
526 }
527
528 fn as_handle(target: &Rc<RecordingTarget>) -> Rc<dyn FocusTargetHandle> {
529 Rc::clone(target) as Rc<dyn FocusTargetHandle>
530 }
531
532 #[test]
533 fn request_focus_moves_focus_between_two_registered_targets() {
534 let _app_context = crate::render_state::app_context_test_scope();
535 clear_focus_invalidations();
536 set_active_focus_target(None);
537
538 let a = RecordingTarget::new();
539 let b = RecordingTarget::new();
540 register_focus_target(1, as_handle(&a));
541 register_focus_target(2, as_handle(&b));
542
543 assert!(request_focus(1));
544 assert_eq!(a.states(), vec![FocusState::Active]);
545 assert_eq!(active_focus_target(), Some(1));
546
547 assert!(request_focus(2));
548 assert_eq!(a.states(), vec![FocusState::Active, FocusState::Inactive]);
549 assert_eq!(b.states(), vec![FocusState::Active]);
550 assert_eq!(active_focus_target(), Some(2));
551 }
552
553 #[test]
554 fn request_focus_on_an_unregistered_node_fails_without_changing_anything() {
555 let _app_context = crate::render_state::app_context_test_scope();
556 clear_focus_invalidations();
557 set_active_focus_target(None);
558
559 assert!(!request_focus(99));
560 assert_eq!(active_focus_target(), None);
561 }
562
563 #[test]
564 fn unregistering_the_active_targets_last_handle_clears_active_focus() {
565 let _app_context = crate::render_state::app_context_test_scope();
566 clear_focus_invalidations();
567 set_active_focus_target(None);
568
569 let a = RecordingTarget::new();
570 let handle = as_handle(&a);
571 register_focus_target(1, Rc::clone(&handle));
572 assert!(request_focus(1));
573 assert_eq!(active_focus_target(), Some(1));
574
575 unregister_focus_target(1, &handle);
576 assert_eq!(active_focus_target(), None);
577 assert!(!request_focus(1));
578 }
579
580 #[test]
581 fn request_focus_from_inside_a_callback_does_not_double_borrow_or_recurse() {
582 let _app_context = crate::render_state::app_context_test_scope();
583 clear_focus_invalidations();
584 set_active_focus_target(None);
585
586 let a = RecordingTarget::new();
587 let b = RecordingTarget::new();
588 register_focus_target(1, as_handle(&a));
589 register_focus_target(2, as_handle(&b));
590
591 *a.on_active.borrow_mut() = Some(Box::new({
592 let bounced_back = RefCell::new(false);
593 move || {
594 if !*bounced_back.borrow() {
595 *bounced_back.borrow_mut() = true;
596 assert!(request_focus(2));
597 }
598 }
599 }));
600
601 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
602 assert!(request_focus(1));
603 }));
604 assert!(
605 result.is_ok(),
606 "a request_focus call from inside a callback must not panic or overflow the stack"
607 );
608
609 assert_eq!(a.states(), vec![FocusState::Active, FocusState::Inactive]);
610 assert_eq!(b.states(), vec![FocusState::Active]);
611 assert_eq!(active_focus_target(), Some(2));
612 }
613
614 #[test]
615 fn a_panicking_callback_still_releases_the_dispatch_lock() {
616 let _app_context = crate::render_state::app_context_test_scope();
617 clear_focus_invalidations();
618 set_active_focus_target(None);
619
620 struct PanicsOnActivate;
621 impl FocusTargetHandle for PanicsOnActivate {
622 fn set_focus_state(&self, state: FocusState) {
623 if state == FocusState::Active {
624 panic!("focus target panicked while activating");
625 }
626 }
627 }
628
629 register_focus_target(1, Rc::new(PanicsOnActivate) as Rc<dyn FocusTargetHandle>);
630 let b = RecordingTarget::new();
631 register_focus_target(2, as_handle(&b));
632
633 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
634 request_focus(1);
635 }));
636 assert!(result.is_err());
637
638 assert!(request_focus(2));
639 assert_eq!(
640 b.states(),
641 vec![FocusState::Active],
642 "the dispatch lock must be released after a callback panic so later requests proceed"
643 );
644 }
645}