cranpose_ui/modifier/
focus.rs1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum FocusDirection {
19 Enter,
21 Exit,
23 Next,
25 Previous,
27 Up,
29 Down,
31 Left,
33 Right,
35}
36
37pub 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 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 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 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#[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 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 "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 true
204 }
205}
206
207pub 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#[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#[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 pub fn request_focus(&self) -> bool {
343 true
345 }
346
347 pub fn capture_focus(&self) -> bool {
349 true
351 }
352
353 pub fn free_focus(&self) -> bool {
355 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}