Skip to main content

cranpose_ui/modifier/
focus.rs

1//! Focus modifier nodes for Cranpose.
2//!
3//! This module implements focus management that mirrors Jetpack Compose's
4//! focus system. Focus nodes participate in focus traversal, track focus state,
5//! and integrate with the modifier chain lifecycle.
6
7use std::cell::Cell;
8use std::hash::{Hash, Hasher};
9use std::rc::Rc;
10
11use cranpose_foundation::{
12    impl_focus_node, DelegatableNode, FocusNode, FocusState, ModifierNode, ModifierNodeContext,
13    ModifierNodeElement, NodeCapabilities, NodeState,
14};
15
16/// Focus direction for navigation.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum FocusDirection {
19    /// Enter focus from outside.
20    Enter,
21    /// Exit focus to outside.
22    Exit,
23    /// Move to next focusable.
24    Next,
25    /// Move to previous focusable.
26    Previous,
27    /// Move up (2D navigation).
28    Up,
29    /// Move down (2D navigation).
30    Down,
31    /// Move left (2D navigation).
32    Left,
33    /// Move right (2D navigation).
34    Right,
35}
36
37/// A focus target node that can receive focus.
38///
39/// This is the core building block for focusable components. Each focus target
40/// tracks its own focus state and participates in the focus traversal system.
41pub struct FocusTargetNode {
42    state: NodeState,
43    focus_state: Cell<FocusState>,
44    on_focus_changed: Option<Rc<dyn Fn(FocusState)>>,
45}
46
47impl FocusTargetNode {
48    pub fn new() -> Self {
49        Self {
50            state: NodeState::new(),
51            focus_state: Cell::new(FocusState::Inactive),
52            on_focus_changed: None,
53        }
54    }
55
56    pub fn with_callback<F>(callback: F) -> Self
57    where
58        F: Fn(FocusState) + 'static,
59    {
60        Self {
61            state: NodeState::new(),
62            focus_state: Cell::new(FocusState::Inactive),
63            on_focus_changed: Some(Rc::new(callback)),
64        }
65    }
66
67    /// Sets the focus state for this node.
68    pub fn set_focus_state(&self, state: FocusState) {
69        let old_state = self.focus_state.get();
70        if old_state != state {
71            self.focus_state.set(state);
72            if let Some(callback) = &self.on_focus_changed {
73                callback(state);
74            }
75        }
76    }
77
78    /// Clears focus from this node.
79    pub fn clear_focus(&self) {
80        self.set_focus_state(FocusState::Inactive);
81    }
82}
83
84impl Default for FocusTargetNode {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90impl DelegatableNode for FocusTargetNode {
91    fn node_state(&self) -> &NodeState {
92        &self.state
93    }
94}
95
96impl ModifierNode for FocusTargetNode {
97    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
98        self.state.set_attached(true);
99    }
100
101    fn on_detach(&mut self) {
102        self.state.set_attached(false);
103        self.clear_focus();
104    }
105
106    // Capability-driven implementation using helper macro
107    impl_focus_node!();
108}
109
110impl FocusNode for FocusTargetNode {
111    fn focus_state(&self) -> FocusState {
112        self.focus_state.get()
113    }
114
115    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, state: FocusState) {
116        self.set_focus_state(state);
117    }
118}
119
120/// Modifier element for focus targets.
121///
122/// Creates a focusable modifier that can receive and track focus.
123#[derive(Clone)]
124pub struct FocusTargetElement {
125    on_focus_changed: Option<Rc<dyn Fn(FocusState)>>,
126}
127
128impl FocusTargetElement {
129    pub fn new() -> Self {
130        Self {
131            on_focus_changed: None,
132        }
133    }
134
135    pub fn with_callback<F>(callback: F) -> Self
136    where
137        F: Fn(FocusState) + 'static,
138    {
139        Self {
140            on_focus_changed: Some(Rc::new(callback)),
141        }
142    }
143}
144
145impl Default for FocusTargetElement {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl std::fmt::Debug for FocusTargetElement {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        f.debug_struct("FocusTargetElement")
154            .field("has_callback", &self.on_focus_changed.is_some())
155            .finish()
156    }
157}
158
159impl PartialEq for FocusTargetElement {
160    fn eq(&self, other: &Self) -> bool {
161        // Type-based matching: compare only presence of callback, not pointer
162        // Nodes are updated via update() method, preserving behavior
163        self.on_focus_changed.is_some() == other.on_focus_changed.is_some()
164    }
165}
166
167impl Hash for FocusTargetElement {
168    fn hash<H: Hasher>(&self, state: &mut H) {
169        // Consistent hash based on callback presence only
170        "focus_target".hash(state);
171        self.on_focus_changed.is_some().hash(state);
172    }
173}
174
175impl ModifierNodeElement for FocusTargetElement {
176    type Node = FocusTargetNode;
177
178    fn create(&self) -> Self::Node {
179        if let Some(callback) = &self.on_focus_changed {
180            FocusTargetNode::with_callback({
181                let callback = callback.clone();
182                move |state| callback(state)
183            })
184        } else {
185            FocusTargetNode::new()
186        }
187    }
188
189    fn update(&self, node: &mut Self::Node) {
190        node.on_focus_changed = self.on_focus_changed.clone();
191    }
192
193    fn inspector_name(&self) -> &'static str {
194        "focusTarget"
195    }
196
197    fn capabilities(&self) -> NodeCapabilities {
198        NodeCapabilities::FOCUS
199    }
200
201    fn always_update(&self) -> bool {
202        // Always update to capture new closure if it changed
203        true
204    }
205}
206
207/// A focus requester node that can request focus for associated targets.
208///
209/// This node allows programmatic focus requests and is typically used to
210/// trigger focus on specific components in response to user actions or
211/// app logic.
212pub struct FocusRequesterNode {
213    state: NodeState,
214    requester: FocusRequesterToken,
215}
216
217impl FocusRequesterNode {
218    pub(crate) fn new(requester: FocusRequesterToken) -> Self {
219        Self {
220            state: NodeState::new(),
221            requester,
222        }
223    }
224}
225
226impl DelegatableNode for FocusRequesterNode {
227    fn node_state(&self) -> &NodeState {
228        &self.state
229    }
230}
231
232impl ModifierNode for FocusRequesterNode {
233    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
234        self.state.set_attached(true);
235    }
236
237    fn on_detach(&mut self) {
238        self.state.set_attached(false);
239    }
240}
241
242/// Modifier element for focus requesters.
243///
244/// Creates a modifier that can be used to programmatically request focus.
245#[derive(Debug, Clone, PartialEq, Eq, Hash)]
246pub struct FocusRequesterElement {
247    requester: FocusRequesterToken,
248}
249
250impl FocusRequesterElement {
251    pub(crate) fn new(requester: FocusRequesterToken) -> Self {
252        Self { requester }
253    }
254}
255
256impl ModifierNodeElement for FocusRequesterElement {
257    type Node = FocusRequesterNode;
258
259    fn create(&self) -> Self::Node {
260        FocusRequesterNode::new(self.requester.clone())
261    }
262
263    fn update(&self, node: &mut Self::Node) {
264        node.requester = self.requester.clone();
265    }
266
267    fn inspector_name(&self) -> &'static str {
268        "focusRequester"
269    }
270
271    fn capabilities(&self) -> NodeCapabilities {
272        NodeCapabilities::FOCUS
273    }
274}
275
276/// A handle for requesting focus programmatically.
277///
278/// This mirrors Jetpack Compose's FocusRequester class and provides
279/// an API for triggering focus changes from application code.
280#[derive(Clone, Debug)]
281pub struct FocusRequester {
282    token: FocusRequesterToken,
283}
284
285#[derive(Clone)]
286pub(crate) struct FocusRequesterToken(Rc<()>);
287
288impl FocusRequesterToken {
289    fn new() -> Self {
290        Self(Rc::new(()))
291    }
292
293    fn id(&self) -> usize {
294        Rc::as_ptr(&self.0) as usize
295    }
296}
297
298impl std::fmt::Debug for FocusRequesterToken {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        f.debug_tuple("FocusRequesterToken")
301            .field(&self.id())
302            .finish()
303    }
304}
305
306impl PartialEq for FocusRequesterToken {
307    fn eq(&self, other: &Self) -> bool {
308        Rc::ptr_eq(&self.0, &other.0)
309    }
310}
311
312impl Eq for FocusRequesterToken {}
313
314impl Hash for FocusRequesterToken {
315    fn hash<H: Hasher>(&self, state: &mut H) {
316        Rc::as_ptr(&self.0).hash(state);
317    }
318}
319
320impl Default for FocusRequester {
321    fn default() -> Self {
322        Self::new()
323    }
324}
325
326impl FocusRequester {
327    pub fn new() -> Self {
328        Self {
329            token: FocusRequesterToken::new(),
330        }
331    }
332
333    pub fn id(&self) -> usize {
334        self.token.id()
335    }
336
337    pub(crate) fn token(&self) -> FocusRequesterToken {
338        self.token.clone()
339    }
340
341    /// Requests focus for components associated with this requester.
342    pub fn request_focus(&self) -> bool {
343        // This will be wired up to the focus manager in the next phase
344        true
345    }
346
347    /// Captures focus, preventing other components from taking focus.
348    pub fn capture_focus(&self) -> bool {
349        // This will be wired up to the focus manager in the next phase
350        true
351    }
352
353    /// Releases captured focus.
354    pub fn free_focus(&self) -> bool {
355        // This will be wired up to the focus manager in the next phase
356        true
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363    use cranpose_foundation::{BasicModifierNodeContext, ModifierNodeChain};
364
365    #[test]
366    fn focus_target_node_lifecycle() {
367        let mut node = FocusTargetNode::new();
368        let mut context = BasicModifierNodeContext::new();
369
370        assert_eq!(node.focus_state(), FocusState::Inactive);
371        assert!(!node.node_state().is_attached());
372
373        node.on_attach(&mut context);
374        assert!(node.node_state().is_attached());
375
376        node.set_focus_state(FocusState::Active);
377        assert_eq!(node.focus_state(), FocusState::Active);
378        assert!(node.focus_state().is_focused());
379
380        node.on_detach();
381        assert!(!node.node_state().is_attached());
382        assert_eq!(node.focus_state(), FocusState::Inactive);
383    }
384
385    #[test]
386    fn focus_target_callback_invoked() {
387        use std::cell::RefCell;
388        let states = Rc::new(RefCell::new(Vec::new()));
389        let states_clone = states.clone();
390
391        let node = FocusTargetNode::with_callback(move |state| {
392            states_clone.borrow_mut().push(state);
393        });
394
395        node.set_focus_state(FocusState::Active);
396        node.set_focus_state(FocusState::ActiveParent);
397        node.set_focus_state(FocusState::Inactive);
398
399        let recorded = states.borrow();
400        assert_eq!(recorded.len(), 3);
401        assert_eq!(recorded[0], FocusState::Active);
402        assert_eq!(recorded[1], FocusState::ActiveParent);
403        assert_eq!(recorded[2], FocusState::Inactive);
404    }
405
406    #[test]
407    fn focus_element_creates_node() {
408        let element = FocusTargetElement::new();
409        let node = element.create();
410        assert_eq!(node.focus_state(), FocusState::Inactive);
411    }
412
413    #[test]
414    fn focus_chain_integration() {
415        let element = FocusTargetElement::new();
416        let dyn_element = cranpose_foundation::modifier_element(element);
417
418        let mut chain = ModifierNodeChain::new();
419        let mut context = BasicModifierNodeContext::new();
420
421        chain.update(vec![dyn_element], &mut context);
422
423        assert_eq!(chain.len(), 1);
424        assert!(chain.has_capability(NodeCapabilities::FOCUS));
425    }
426
427    #[test]
428    fn focus_requester_unique_ids() {
429        let req1 = FocusRequester::new();
430        let req2 = FocusRequester::new();
431        assert_ne!(req1.id(), req2.id());
432    }
433
434    #[test]
435    fn focus_requester_clone_keeps_retained_identity() {
436        let req = FocusRequester::new();
437        let clone = req.clone();
438
439        assert_eq!(req.id(), clone.id());
440    }
441
442    #[test]
443    fn focus_requester_ids_do_not_use_process_global_counter() {
444        let source = include_str!("focus.rs");
445        assert!(!source.contains(concat!("static ", "NEXT_ID")));
446        assert!(!source.contains(concat!("Atomic", "Usize")));
447    }
448
449    #[test]
450    fn focus_state_predicates() {
451        assert!(FocusState::Active.is_focused());
452        assert!(FocusState::Captured.is_focused());
453        assert!(!FocusState::Inactive.is_focused());
454        assert!(!FocusState::ActiveParent.is_focused());
455
456        assert!(FocusState::Active.has_focus());
457        assert!(FocusState::ActiveParent.has_focus());
458        assert!(FocusState::Captured.has_focus());
459        assert!(!FocusState::Inactive.has_focus());
460
461        assert!(FocusState::Captured.is_captured());
462        assert!(!FocusState::Active.is_captured());
463    }
464}