cranpose_ui/modifier/
focus.rs1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum FocusDirection {
15 Enter,
17 Exit,
19 Next,
21 Previous,
23 Up,
25 Down,
27 Left,
29 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}