Skip to main content

cranpose_ui/modifier/
focus.rs

1use std::{
2    cell::Cell,
3    hash::{Hash, Hasher},
4    rc::Rc,
5};
6
7use cranpose_foundation::{
8    DelegatableNode, FocusNode, FocusState, ModifierNode, ModifierNodeContext, ModifierNodeElement,
9    NodeCapabilities, NodeState, impl_focus_node,
10};
11
12/// Focus direction for navigation.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum FocusDirection {
15    /// Enter focus from outside.
16    Enter,
17    /// Exit focus to outside.
18    Exit,
19    /// Move to next focusable.
20    Next,
21    /// Move to previous focusable.
22    Previous,
23    /// Move up (2D navigation).
24    Up,
25    /// Move down (2D navigation).
26    Down,
27    /// Move left (2D navigation).
28    Left,
29    /// Move right (2D navigation).
30    Right,
31}
32
33pub struct FocusTargetNode {
34    state: NodeState,
35    focus_state: Cell<FocusState>,
36    on_focus_changed: Option<Rc<dyn Fn(FocusState)>>,
37}
38
39impl FocusTargetNode {
40    pub fn new() -> Self {
41        Self {
42            state: NodeState::new(),
43            focus_state: Cell::new(FocusState::Inactive),
44            on_focus_changed: None,
45        }
46    }
47
48    pub fn with_callback<F>(callback: F) -> Self
49    where
50        F: Fn(FocusState) + 'static,
51    {
52        Self {
53            state: NodeState::new(),
54            focus_state: Cell::new(FocusState::Inactive),
55            on_focus_changed: Some(Rc::new(callback)),
56        }
57    }
58
59    pub fn set_focus_state(&self, state: FocusState) {
60        let old_state = self.focus_state.get();
61        if old_state != state {
62            self.focus_state.set(state);
63            if let Some(callback) = &self.on_focus_changed {
64                callback(state);
65            }
66        }
67    }
68
69    pub fn clear_focus(&self) {
70        self.set_focus_state(FocusState::Inactive);
71    }
72}
73
74impl Default for FocusTargetNode {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl DelegatableNode for FocusTargetNode {
81    fn node_state(&self) -> &NodeState {
82        &self.state
83    }
84}
85
86impl ModifierNode for FocusTargetNode {
87    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
88        self.state.set_attached(true);
89    }
90
91    fn on_detach(&mut self) {
92        self.state.set_attached(false);
93        self.clear_focus();
94    }
95
96    impl_focus_node!();
97}
98
99impl FocusNode for FocusTargetNode {
100    fn focus_state(&self) -> FocusState {
101        self.focus_state.get()
102    }
103
104    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, state: FocusState) {
105        self.set_focus_state(state);
106    }
107}
108
109#[derive(Clone)]
110pub struct FocusTargetElement {
111    on_focus_changed: Option<Rc<dyn Fn(FocusState)>>,
112}
113
114impl FocusTargetElement {
115    pub fn new() -> Self {
116        Self {
117            on_focus_changed: None,
118        }
119    }
120
121    pub fn with_callback<F>(callback: F) -> Self
122    where
123        F: Fn(FocusState) + 'static,
124    {
125        Self {
126            on_focus_changed: Some(Rc::new(callback)),
127        }
128    }
129}
130
131impl Default for FocusTargetElement {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl std::fmt::Debug for FocusTargetElement {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        f.debug_struct("FocusTargetElement")
140            .field("has_callback", &self.on_focus_changed.is_some())
141            .finish()
142    }
143}
144
145impl PartialEq for FocusTargetElement {
146    fn eq(&self, other: &Self) -> bool {
147        self.on_focus_changed.is_some() == other.on_focus_changed.is_some()
148    }
149}
150
151impl Hash for FocusTargetElement {
152    fn hash<H: Hasher>(&self, state: &mut H) {
153        "focus_target".hash(state);
154        self.on_focus_changed.is_some().hash(state);
155    }
156}
157
158impl ModifierNodeElement for FocusTargetElement {
159    type Node = FocusTargetNode;
160
161    fn create(&self) -> Self::Node {
162        if let Some(callback) = &self.on_focus_changed {
163            FocusTargetNode::with_callback({
164                let callback = callback.clone();
165                move |state| callback(state)
166            })
167        } else {
168            FocusTargetNode::new()
169        }
170    }
171
172    fn update(&self, node: &mut Self::Node) {
173        node.on_focus_changed = self.on_focus_changed.clone();
174    }
175
176    fn inspector_name(&self) -> &'static str {
177        "focusTarget"
178    }
179
180    fn capabilities(&self) -> NodeCapabilities {
181        NodeCapabilities::FOCUS
182    }
183
184    fn always_update(&self) -> bool {
185        true
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use cranpose_foundation::{BasicModifierNodeContext, ModifierNodeChain};
192
193    use super::*;
194
195    #[test]
196    fn focus_target_node_lifecycle() {
197        let mut node = FocusTargetNode::new();
198        let mut context = BasicModifierNodeContext::new();
199
200        assert_eq!(node.focus_state(), FocusState::Inactive);
201        assert!(!node.node_state().is_attached());
202
203        node.on_attach(&mut context);
204        assert!(node.node_state().is_attached());
205
206        node.set_focus_state(FocusState::Active);
207        assert_eq!(node.focus_state(), FocusState::Active);
208        assert!(node.focus_state().is_focused());
209
210        node.on_detach();
211        assert!(!node.node_state().is_attached());
212        assert_eq!(node.focus_state(), FocusState::Inactive);
213    }
214
215    #[test]
216    fn focus_target_callback_invoked() {
217        use std::cell::RefCell;
218        let states = Rc::new(RefCell::new(Vec::new()));
219        let states_clone = states.clone();
220
221        let node = FocusTargetNode::with_callback(move |state| {
222            states_clone.borrow_mut().push(state);
223        });
224
225        node.set_focus_state(FocusState::Active);
226        node.set_focus_state(FocusState::ActiveParent);
227        node.set_focus_state(FocusState::Inactive);
228
229        let recorded = states.borrow();
230        assert_eq!(recorded.len(), 3);
231        assert_eq!(recorded[0], FocusState::Active);
232        assert_eq!(recorded[1], FocusState::ActiveParent);
233        assert_eq!(recorded[2], FocusState::Inactive);
234    }
235
236    #[test]
237    fn focus_element_creates_node() {
238        let element = FocusTargetElement::new();
239        let node = element.create();
240        assert_eq!(node.focus_state(), FocusState::Inactive);
241    }
242
243    #[test]
244    fn focus_chain_integration() {
245        let element = FocusTargetElement::new();
246        let dyn_element = cranpose_foundation::modifier_element(element);
247
248        let mut chain = ModifierNodeChain::new();
249        let mut context = BasicModifierNodeContext::new();
250
251        chain.update(vec![dyn_element], &mut context);
252
253        assert_eq!(chain.len(), 1);
254        assert!(chain.has_capability(NodeCapabilities::FOCUS));
255    }
256
257    #[test]
258    fn focus_state_predicates() {
259        assert!(FocusState::Active.is_focused());
260        assert!(FocusState::Captured.is_focused());
261        assert!(!FocusState::Inactive.is_focused());
262        assert!(!FocusState::ActiveParent.is_focused());
263
264        assert!(FocusState::Active.has_focus());
265        assert!(FocusState::ActiveParent.has_focus());
266        assert!(FocusState::Captured.has_focus());
267        assert!(!FocusState::Inactive.has_focus());
268
269        assert!(FocusState::Captured.is_captured());
270        assert!(!FocusState::Active.is_captured());
271    }
272}