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