1use std::{
2 cell::{Cell, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5};
6
7use cranpose_core::NodeId;
8use cranpose_foundation::{
9 DelegatableNode, FocusNode, FocusState, ModifierNode, ModifierNodeContext, ModifierNodeElement,
10 NodeCapabilities, NodeState, impl_focus_node,
11};
12
13use crate::{focus_dispatch, render_state::AppContextId};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum FocusDirection {
18 Enter,
20 Exit,
22 Next,
24 Previous,
26 Up,
28 Down,
30 Left,
32 Right,
34}
35
36type FocusChangedCallback = Rc<dyn Fn(FocusState)>;
37
38struct FocusTargetShared {
39 focus_state: Cell<FocusState>,
40 on_focus_changed: RefCell<Option<FocusChangedCallback>>,
41}
42
43impl FocusTargetShared {
44 fn new(on_focus_changed: Option<FocusChangedCallback>) -> Self {
45 Self {
46 focus_state: Cell::new(FocusState::Inactive),
47 on_focus_changed: RefCell::new(on_focus_changed),
48 }
49 }
50
51 fn set_focus_state(&self, state: FocusState) {
52 let old_state = self.focus_state.get();
53 if old_state != state {
54 self.focus_state.set(state);
55 let callback = self.on_focus_changed.borrow().clone();
56 if let Some(callback) = callback {
57 callback(state);
58 }
59 }
60 }
61}
62
63impl focus_dispatch::FocusTargetHandle for FocusTargetShared {
64 fn set_focus_state(&self, state: FocusState) {
65 FocusTargetShared::set_focus_state(self, state);
66 }
67}
68
69pub struct FocusTargetNode {
70 state: NodeState,
71 shared: Rc<FocusTargetShared>,
72 handle: Rc<dyn focus_dispatch::FocusTargetHandle>,
73 registered_node_id: Cell<Option<NodeId>>,
74}
75
76impl FocusTargetNode {
77 pub fn new() -> Self {
78 Self::from_callback(None)
79 }
80
81 pub fn with_callback<F>(callback: F) -> Self
82 where
83 F: Fn(FocusState) + 'static,
84 {
85 Self::from_callback(Some(Rc::new(callback) as FocusChangedCallback))
86 }
87
88 fn from_callback(on_focus_changed: Option<FocusChangedCallback>) -> Self {
89 let shared = Rc::new(FocusTargetShared::new(on_focus_changed));
90 let handle: Rc<dyn focus_dispatch::FocusTargetHandle> = shared.clone();
91 Self {
92 state: NodeState::new(),
93 shared,
94 handle,
95 registered_node_id: Cell::new(None),
96 }
97 }
98
99 pub fn set_focus_state(&self, state: FocusState) {
100 self.shared.set_focus_state(state);
101 }
102
103 pub fn clear_focus(&self) {
104 self.set_focus_state(FocusState::Inactive);
105 }
106
107 fn set_callback(&self, callback: Option<FocusChangedCallback>) {
108 *self.shared.on_focus_changed.borrow_mut() = callback;
109 }
110}
111
112impl Default for FocusTargetNode {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118impl DelegatableNode for FocusTargetNode {
119 fn node_state(&self) -> &NodeState {
120 &self.state
121 }
122}
123
124impl ModifierNode for FocusTargetNode {
125 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
126 self.state.set_attached(true);
127 if let Some(node_id) = context.node_id() {
128 self.registered_node_id.set(Some(node_id));
129 focus_dispatch::register_focus_target(node_id, Rc::clone(&self.handle));
130 }
131 }
132
133 fn on_detach(&mut self) {
134 self.state.set_attached(false);
135 if let Some(node_id) = self.registered_node_id.take() {
136 focus_dispatch::unregister_focus_target(node_id, &self.handle);
137 }
138 self.clear_focus();
139 }
140
141 impl_focus_node!();
142}
143
144impl FocusNode for FocusTargetNode {
145 fn focus_state(&self) -> FocusState {
146 self.shared.focus_state.get()
147 }
148
149 fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, state: FocusState) {
150 self.set_focus_state(state);
151 }
152}
153
154#[derive(Clone)]
155pub struct FocusTargetElement {
156 on_focus_changed: Option<FocusChangedCallback>,
157}
158
159impl FocusTargetElement {
160 pub fn new() -> Self {
161 Self {
162 on_focus_changed: None,
163 }
164 }
165
166 pub fn with_callback<F>(callback: F) -> Self
167 where
168 F: Fn(FocusState) + 'static,
169 {
170 Self {
171 on_focus_changed: Some(Rc::new(callback)),
172 }
173 }
174}
175
176impl Default for FocusTargetElement {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl std::fmt::Debug for FocusTargetElement {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 f.debug_struct("FocusTargetElement")
185 .field("has_callback", &self.on_focus_changed.is_some())
186 .finish()
187 }
188}
189
190impl PartialEq for FocusTargetElement {
191 fn eq(&self, other: &Self) -> bool {
192 self.on_focus_changed.is_some() == other.on_focus_changed.is_some()
193 }
194}
195
196impl Hash for FocusTargetElement {
197 fn hash<H: Hasher>(&self, state: &mut H) {
198 "focus_target".hash(state);
199 self.on_focus_changed.is_some().hash(state);
200 }
201}
202
203impl ModifierNodeElement for FocusTargetElement {
204 type Node = FocusTargetNode;
205
206 fn create(&self) -> Self::Node {
207 if let Some(callback) = &self.on_focus_changed {
208 FocusTargetNode::with_callback({
209 let callback = callback.clone();
210 move |state| callback(state)
211 })
212 } else {
213 FocusTargetNode::new()
214 }
215 }
216
217 fn update(&self, node: &mut Self::Node) {
218 node.set_callback(self.on_focus_changed.clone());
219 }
220
221 fn inspector_name(&self) -> &'static str {
222 "focusTarget"
223 }
224
225 fn capabilities(&self) -> NodeCapabilities {
226 NodeCapabilities::FOCUS
227 }
228
229 fn always_update(&self) -> bool {
230 true
231 }
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub enum FocusRequestError {
237 NotAttached,
241 NoFocusTarget,
246}
247
248impl std::fmt::Display for FocusRequestError {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 match self {
251 FocusRequestError::NotAttached => {
252 write!(f, "FocusRequester is not attached to a node in composition")
253 }
254 FocusRequestError::NoFocusTarget => write!(
255 f,
256 "FocusRequester's node has no focus target to move focus onto"
257 ),
258 }
259 }
260}
261
262impl std::error::Error for FocusRequestError {}
263
264#[derive(Clone, Copy)]
265struct FocusRequesterBinding {
266 app_context: AppContextId,
267 node_id: NodeId,
268}
269
270#[derive(Clone, Default)]
288pub struct FocusRequester {
289 binding: Rc<Cell<Option<FocusRequesterBinding>>>,
290}
291
292impl FocusRequester {
293 pub fn new() -> Self {
294 Self::default()
295 }
296
297 pub fn request_focus(&self) -> Result<(), FocusRequestError> {
302 let Some(binding) = self.binding.get() else {
303 return Err(FocusRequestError::NotAttached);
304 };
305 match focus_dispatch::request_focus_for(binding.app_context, binding.node_id) {
306 Some(true) => Ok(()),
307 Some(false) => Err(FocusRequestError::NoFocusTarget),
308 None => Err(FocusRequestError::NotAttached),
309 }
310 }
311
312 pub fn node_id(&self) -> Option<NodeId> {
314 self.binding.get().map(|binding| binding.node_id)
315 }
316
317 fn bind(&self, binding: Option<FocusRequesterBinding>) {
318 self.binding.set(binding);
319 }
320
321 fn binding_here(node_id: Option<NodeId>) -> Option<FocusRequesterBinding> {
322 Some(FocusRequesterBinding {
323 app_context: crate::render_state::current_app_context_id_opt()?,
324 node_id: node_id?,
325 })
326 }
327}
328
329impl std::fmt::Debug for FocusRequester {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 f.debug_struct("FocusRequester")
332 .field("node_id", &self.node_id())
333 .finish()
334 }
335}
336
337pub struct FocusRequesterNode {
338 state: NodeState,
339 requester: FocusRequester,
340}
341
342impl FocusRequesterNode {
343 pub(crate) fn new(requester: FocusRequester) -> Self {
344 Self {
345 state: NodeState::new(),
346 requester,
347 }
348 }
349}
350
351impl DelegatableNode for FocusRequesterNode {
352 fn node_state(&self) -> &NodeState {
353 &self.state
354 }
355}
356
357impl ModifierNode for FocusRequesterNode {
358 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
359 self.state.set_attached(true);
360 self.requester
361 .bind(FocusRequester::binding_here(context.node_id()));
362 }
363
364 fn on_detach(&mut self) {
365 self.state.set_attached(false);
366 self.requester.bind(None);
367 }
368}
369
370#[derive(Clone)]
372pub struct FocusRequesterElement {
373 requester: FocusRequester,
374}
375
376impl FocusRequesterElement {
377 pub(crate) fn new(requester: FocusRequester) -> Self {
378 Self { requester }
379 }
380}
381
382impl std::fmt::Debug for FocusRequesterElement {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 f.write_str("FocusRequesterElement")
385 }
386}
387
388impl PartialEq for FocusRequesterElement {
389 fn eq(&self, other: &Self) -> bool {
390 Rc::ptr_eq(&self.requester.binding, &other.requester.binding)
391 }
392}
393
394impl Eq for FocusRequesterElement {}
395
396impl Hash for FocusRequesterElement {
397 fn hash<H: Hasher>(&self, state: &mut H) {
398 Rc::as_ptr(&self.requester.binding).hash(state);
399 }
400}
401
402impl ModifierNodeElement for FocusRequesterElement {
403 type Node = FocusRequesterNode;
404
405 fn create(&self) -> Self::Node {
406 FocusRequesterNode::new(self.requester.clone())
407 }
408
409 fn update(&self, node: &mut Self::Node) {
410 if !Rc::ptr_eq(&node.requester.binding, &self.requester.binding) {
411 let bound = node.requester.binding.get();
412 node.requester.bind(None);
413 node.requester = self.requester.clone();
414 node.requester.bind(bound);
415 }
416 }
417
418 fn inspector_name(&self) -> &'static str {
419 "focusRequester"
420 }
421
422 fn capabilities(&self) -> NodeCapabilities {
423 NodeCapabilities::NONE
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use cranpose_foundation::{BasicModifierNodeContext, ModifierNodeChain};
430
431 use super::*;
432
433 #[test]
434 fn focus_target_node_lifecycle() {
435 let mut node = FocusTargetNode::new();
436 let mut context = BasicModifierNodeContext::new();
437
438 assert_eq!(node.focus_state(), FocusState::Inactive);
439 assert!(!node.node_state().is_attached());
440
441 node.on_attach(&mut context);
442 assert!(node.node_state().is_attached());
443
444 node.set_focus_state(FocusState::Active);
445 assert_eq!(node.focus_state(), FocusState::Active);
446 assert!(node.focus_state().is_focused());
447
448 node.on_detach();
449 assert!(!node.node_state().is_attached());
450 assert_eq!(node.focus_state(), FocusState::Inactive);
451 }
452
453 #[test]
454 fn focus_target_callback_invoked() {
455 use std::cell::RefCell;
456 let states = Rc::new(RefCell::new(Vec::new()));
457 let states_clone = states.clone();
458
459 let node = FocusTargetNode::with_callback(move |state| {
460 states_clone.borrow_mut().push(state);
461 });
462
463 node.set_focus_state(FocusState::Active);
464 node.set_focus_state(FocusState::ActiveParent);
465 node.set_focus_state(FocusState::Inactive);
466
467 let recorded = states.borrow();
468 assert_eq!(recorded.len(), 3);
469 assert_eq!(recorded[0], FocusState::Active);
470 assert_eq!(recorded[1], FocusState::ActiveParent);
471 assert_eq!(recorded[2], FocusState::Inactive);
472 }
473
474 #[test]
475 fn focus_element_creates_node() {
476 let element = FocusTargetElement::new();
477 let node = element.create();
478 assert_eq!(node.focus_state(), FocusState::Inactive);
479 }
480
481 #[test]
482 fn focus_chain_integration() {
483 let element = FocusTargetElement::new();
484 let dyn_element = cranpose_foundation::modifier_element(element);
485
486 let mut chain = ModifierNodeChain::new();
487 let mut context = BasicModifierNodeContext::new();
488
489 chain.update(vec![dyn_element], &mut context);
490
491 assert_eq!(chain.len(), 1);
492 assert!(chain.has_capability(NodeCapabilities::FOCUS));
493 }
494
495 #[test]
496 fn focus_state_predicates() {
497 assert!(FocusState::Active.is_focused());
498 assert!(FocusState::Captured.is_focused());
499 assert!(!FocusState::Inactive.is_focused());
500 assert!(!FocusState::ActiveParent.is_focused());
501
502 assert!(FocusState::Active.has_focus());
503 assert!(FocusState::ActiveParent.has_focus());
504 assert!(FocusState::Captured.has_focus());
505 assert!(!FocusState::Inactive.has_focus());
506
507 assert!(FocusState::Captured.is_captured());
508 assert!(!FocusState::Active.is_captured());
509 }
510
511 fn attach_at(
512 node_id: NodeId,
513 elements: Vec<cranpose_foundation::DynModifierElement>,
514 ) -> ModifierNodeChain {
515 let mut context = BasicModifierNodeContext::new();
516 context.set_node_id(Some(node_id));
517 let mut chain = ModifierNodeChain::new();
518 chain.update(elements, &mut context);
519 chain
520 }
521
522 #[test]
523 fn request_focus_on_a_never_attached_requester_fails_predictably() {
524 let requester = FocusRequester::new();
525 assert_eq!(
526 requester.request_focus(),
527 Err(FocusRequestError::NotAttached)
528 );
529 }
530
531 #[test]
532 fn request_focus_on_a_requester_with_no_focus_target_fails_predictably() {
533 let _app_context = crate::render_state::app_context_test_scope();
534 let requester = FocusRequester::new();
535
536 let _chain = attach_at(
537 1,
538 vec![cranpose_foundation::modifier_element(
539 FocusRequesterElement::new(requester.clone()),
540 )],
541 );
542
543 assert_eq!(requester.node_id(), Some(1));
544 assert_eq!(
545 requester.request_focus(),
546 Err(FocusRequestError::NoFocusTarget)
547 );
548 }
549
550 #[test]
551 fn a_focus_requester_moves_focus_onto_its_paired_focus_target() {
552 let _app_context = crate::render_state::app_context_test_scope();
553 let requester = FocusRequester::new();
554
555 let chain = attach_at(
556 2,
557 vec![
558 cranpose_foundation::modifier_element(FocusRequesterElement::new(
559 requester.clone(),
560 )),
561 cranpose_foundation::modifier_element(FocusTargetElement::new()),
562 ],
563 );
564
565 assert_eq!(requester.request_focus(), Ok(()));
566
567 let target = chain.node::<FocusTargetNode>(1).expect("focus target node");
568 assert_eq!(target.focus_state(), FocusState::Active);
569 assert_eq!(focus_dispatch::active_focus_target(), Some(2));
570 }
571
572 #[test]
573 fn request_focus_after_the_node_leaves_composition_fails_predictably() {
574 let _app_context = crate::render_state::app_context_test_scope();
575 let requester = FocusRequester::new();
576
577 let mut context = BasicModifierNodeContext::new();
578 context.set_node_id(Some(3));
579 let mut chain = ModifierNodeChain::new();
580 chain.update(
581 vec![
582 cranpose_foundation::modifier_element(FocusRequesterElement::new(
583 requester.clone(),
584 )),
585 cranpose_foundation::modifier_element(FocusTargetElement::new()),
586 ],
587 &mut context,
588 );
589 assert!(requester.request_focus().is_ok());
590
591 chain.update(Vec::new(), &mut context);
592
593 assert_eq!(
594 requester.request_focus(),
595 Err(FocusRequestError::NotAttached)
596 );
597 }
598
599 #[test]
600 fn focus_survives_the_requested_node_being_recomposed() {
601 let _app_context = crate::render_state::app_context_test_scope();
602 let requester = FocusRequester::new();
603
604 let mut context = BasicModifierNodeContext::new();
605 context.set_node_id(Some(4));
606 let mut chain = ModifierNodeChain::new();
607 let make_elements = || {
608 vec![
609 cranpose_foundation::modifier_element(FocusRequesterElement::new(
610 requester.clone(),
611 )),
612 cranpose_foundation::modifier_element(FocusTargetElement::new()),
613 ]
614 };
615 chain.update(make_elements(), &mut context);
616 assert_eq!(requester.request_focus(), Ok(()));
617
618 let node_ptr_before = {
619 let node = chain.node::<FocusTargetNode>(1).unwrap();
620 &*node as *const FocusTargetNode
621 };
622
623 chain.update(make_elements(), &mut context);
624
625 let target = chain.node::<FocusTargetNode>(1).unwrap();
626 let node_ptr_after = &*target as *const FocusTargetNode;
627 assert_eq!(
628 node_ptr_before, node_ptr_after,
629 "recomposition with a structurally equal modifier must reuse the node"
630 );
631 assert_eq!(
632 target.focus_state(),
633 FocusState::Active,
634 "recomposing the focused node must not reset its focus state"
635 );
636 assert_eq!(focus_dispatch::active_focus_target(), Some(4));
637 }
638
639 #[test]
640 fn requesting_focus_from_inside_on_focus_changed_does_not_double_borrow_or_recurse() {
641 let _app_context = crate::render_state::app_context_test_scope();
642
643 let requester_a = FocusRequester::new();
644 let requester_b = FocusRequester::new();
645 let requester_b_for_callback = requester_b.clone();
646 let bounced = Rc::new(Cell::new(false));
647 let bounced_for_callback = bounced.clone();
648
649 let chain_a = attach_at(
650 10,
651 vec![
652 cranpose_foundation::modifier_element(FocusRequesterElement::new(
653 requester_a.clone(),
654 )),
655 cranpose_foundation::modifier_element(FocusTargetElement::with_callback(
656 move |state| {
657 if state == FocusState::Active && !bounced_for_callback.get() {
658 bounced_for_callback.set(true);
659 requester_b_for_callback.request_focus().expect(
660 "a reentrant request_focus must succeed, not double-borrow",
661 );
662 }
663 },
664 )),
665 ],
666 );
667 let chain_b = attach_at(
668 20,
669 vec![
670 cranpose_foundation::modifier_element(FocusRequesterElement::new(
671 requester_b.clone(),
672 )),
673 cranpose_foundation::modifier_element(FocusTargetElement::new()),
674 ],
675 );
676
677 let result =
678 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| requester_a.request_focus()));
679 assert!(result.is_ok(), "a reentrant focus request must not panic");
680 assert_eq!(result.unwrap(), Ok(()));
681 assert!(bounced.get(), "the reentrant callback never ran");
682
683 let target_a = chain_a.node::<FocusTargetNode>(1).unwrap();
684 let target_b = chain_b.node::<FocusTargetNode>(1).unwrap();
685 assert_eq!(target_a.focus_state(), FocusState::Inactive);
686 assert_eq!(target_b.focus_state(), FocusState::Active);
687 assert_eq!(focus_dispatch::active_focus_target(), Some(20));
688 }
689}