1use std::{
2 cell::{Cell, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5};
6
7use cranpose_core::NodeId;
8use cranpose_foundation::{
9 DelegatableNode, FocusNode, FocusState, ModifierNode, ModifierNodeContext, ModifierNodeElement,
10 NodeCapabilities, NodeState, impl_focus_node,
11};
12
13use crate::{
14 focus_dispatch,
15 focus_within::{FocusWithinChain, current_focus_within},
16 render_state::AppContextId,
17};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum FocusDirection {
22 Enter,
24 Exit,
26 Next,
28 Previous,
30 Up,
32 Down,
34 Left,
36 Right,
38}
39
40type FocusChangedCallback = Rc<dyn Fn(FocusState)>;
41
42struct FocusTargetShared {
43 focus_state: Cell<FocusState>,
44 on_focus_changed: RefCell<Option<FocusChangedCallback>>,
45 observers: RefCell<Option<FocusWithinChain>>,
47}
48
49impl FocusTargetShared {
50 fn new(on_focus_changed: Option<FocusChangedCallback>) -> Self {
51 Self {
52 focus_state: Cell::new(FocusState::Inactive),
53 on_focus_changed: RefCell::new(on_focus_changed),
54 observers: RefCell::new(None),
55 }
56 }
57
58 fn set_focus_state(&self, state: FocusState) {
59 let old_state = self.focus_state.get();
60 if old_state != state {
61 self.focus_state.set(state);
62 if old_state.is_focused() != state.is_focused()
63 && let Some(observers) = self.observers.borrow().clone()
64 {
65 if state.is_focused() {
66 observers.enter();
67 } else {
68 observers.leave();
69 }
70 }
71 let callback = self.on_focus_changed.borrow().clone();
72 if let Some(callback) = callback {
73 callback(state);
74 }
75 }
76 }
77
78 fn set_observers(&self, observers: Option<FocusWithinChain>) {
80 let previous = self.observers.replace(observers.clone());
81 if previous == observers || !self.focus_state.get().is_focused() {
82 return;
83 }
84 if let Some(previous) = previous {
85 previous.leave();
86 }
87 if let Some(observers) = observers {
88 observers.enter();
89 }
90 }
91}
92
93impl focus_dispatch::FocusTargetHandle for FocusTargetShared {
94 fn set_focus_state(&self, state: FocusState) {
95 FocusTargetShared::set_focus_state(self, state);
96 }
97}
98
99pub struct FocusTargetNode {
100 state: NodeState,
101 shared: Rc<FocusTargetShared>,
102 handle: Rc<dyn focus_dispatch::FocusTargetHandle>,
103 registered_node_id: Cell<Option<NodeId>>,
104}
105
106impl FocusTargetNode {
107 pub fn new() -> Self {
108 Self::from_callback(None)
109 }
110
111 fn from_callback(on_focus_changed: Option<FocusChangedCallback>) -> Self {
112 let shared = Rc::new(FocusTargetShared::new(on_focus_changed));
113 let handle: Rc<dyn focus_dispatch::FocusTargetHandle> = shared.clone();
114 Self {
115 state: NodeState::new(),
116 shared,
117 handle,
118 registered_node_id: Cell::new(None),
119 }
120 }
121
122 pub fn set_focus_state(&self, state: FocusState) {
123 self.shared.set_focus_state(state);
124 }
125
126 pub fn clear_focus(&self) {
127 self.set_focus_state(FocusState::Inactive);
128 }
129
130 fn set_callback(&self, callback: Option<FocusChangedCallback>) {
131 *self.shared.on_focus_changed.borrow_mut() = callback;
132 }
133}
134
135impl Default for FocusTargetNode {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl DelegatableNode for FocusTargetNode {
142 fn node_state(&self) -> &NodeState {
143 &self.state
144 }
145}
146
147impl ModifierNode for FocusTargetNode {
148 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
149 self.state.set_attached(true);
150 if let Some(node_id) = context.node_id() {
151 self.registered_node_id.set(Some(node_id));
152 focus_dispatch::register_focus_target(node_id, Rc::clone(&self.handle));
153 }
154 }
155
156 fn on_detach(&mut self) {
157 self.state.set_attached(false);
158 if let Some(node_id) = self.registered_node_id.take() {
159 focus_dispatch::unregister_focus_target(node_id, &self.handle);
160 }
161 self.clear_focus();
162 }
163
164 impl_focus_node!();
165}
166
167impl FocusNode for FocusTargetNode {
168 fn focus_state(&self) -> FocusState {
169 self.shared.focus_state.get()
170 }
171
172 fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, state: FocusState) {
173 self.set_focus_state(state);
174 }
175}
176
177#[derive(Clone)]
178pub struct FocusTargetElement {
179 on_focus_changed: Option<FocusChangedCallback>,
180 observers: Option<FocusWithinChain>,
181}
182
183impl FocusTargetElement {
184 pub fn new() -> Self {
185 Self {
186 on_focus_changed: None,
187 observers: current_focus_within(),
188 }
189 }
190
191 pub fn with_callback<F>(callback: F) -> Self
192 where
193 F: Fn(FocusState) + 'static,
194 {
195 Self {
196 on_focus_changed: Some(Rc::new(callback)),
197 observers: current_focus_within(),
198 }
199 }
200}
201
202impl Default for FocusTargetElement {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208impl std::fmt::Debug for FocusTargetElement {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 f.debug_struct("FocusTargetElement")
211 .field("has_callback", &self.on_focus_changed.is_some())
212 .finish()
213 }
214}
215
216impl PartialEq for FocusTargetElement {
217 fn eq(&self, other: &Self) -> bool {
218 self.on_focus_changed.is_some() == other.on_focus_changed.is_some()
219 && self.observers == other.observers
220 }
221}
222
223impl Hash for FocusTargetElement {
224 fn hash<H: Hasher>(&self, state: &mut H) {
225 "focus_target".hash(state);
226 self.on_focus_changed.is_some().hash(state);
227 }
228}
229
230impl ModifierNodeElement for FocusTargetElement {
231 type Node = FocusTargetNode;
232
233 fn create(&self) -> Self::Node {
234 let node = FocusTargetNode::from_callback(self.on_focus_changed.clone());
235 node.shared.set_observers(self.observers.clone());
236 node
237 }
238
239 fn update(&self, node: &mut Self::Node) {
240 node.set_callback(self.on_focus_changed.clone());
241 node.shared.set_observers(self.observers.clone());
242 }
243
244 fn inspector_name(&self) -> &'static str {
245 "focusTarget"
246 }
247
248 fn capabilities(&self) -> NodeCapabilities {
249 NodeCapabilities::FOCUS
250 }
251
252 fn always_update(&self) -> bool {
253 true
254 }
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum FocusRequestError {
260 NotAttached,
264 NoFocusTarget,
269}
270
271impl std::fmt::Display for FocusRequestError {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self {
274 FocusRequestError::NotAttached => {
275 write!(f, "FocusRequester is not attached to a node in composition")
276 }
277 FocusRequestError::NoFocusTarget => write!(
278 f,
279 "FocusRequester's node has no focus target to move focus onto"
280 ),
281 }
282 }
283}
284
285impl std::error::Error for FocusRequestError {}
286
287#[derive(Clone, Copy)]
288struct FocusRequesterBinding {
289 app_context: AppContextId,
290 node_id: NodeId,
291}
292
293#[derive(Clone, Default)]
311pub struct FocusRequester {
312 binding: Rc<Cell<Option<FocusRequesterBinding>>>,
313}
314
315impl FocusRequester {
316 pub fn new() -> Self {
319 Self::default()
320 }
321
322 pub fn request_focus(&self) -> Result<(), FocusRequestError> {
327 let Some(binding) = self.binding.get() else {
328 return Err(FocusRequestError::NotAttached);
329 };
330 match focus_dispatch::request_focus_for(binding.app_context, binding.node_id) {
331 Some(true) => Ok(()),
332 Some(false) => Err(FocusRequestError::NoFocusTarget),
333 None => Err(FocusRequestError::NotAttached),
334 }
335 }
336
337 pub fn node_id(&self) -> Option<NodeId> {
339 self.binding.get().map(|binding| binding.node_id)
340 }
341
342 fn bind(&self, binding: Option<FocusRequesterBinding>) {
343 self.binding.set(binding);
344 }
345
346 fn binding_here(node_id: Option<NodeId>) -> Option<FocusRequesterBinding> {
347 Some(FocusRequesterBinding {
348 app_context: crate::render_state::current_app_context_id_opt()?,
349 node_id: node_id?,
350 })
351 }
352}
353
354impl std::fmt::Debug for FocusRequester {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 f.debug_struct("FocusRequester")
357 .field("node_id", &self.node_id())
358 .finish()
359 }
360}
361
362pub struct FocusRequesterNode {
363 state: NodeState,
364 requester: FocusRequester,
365}
366
367impl FocusRequesterNode {
368 pub(crate) fn new(requester: FocusRequester) -> Self {
369 Self {
370 state: NodeState::new(),
371 requester,
372 }
373 }
374}
375
376impl DelegatableNode for FocusRequesterNode {
377 fn node_state(&self) -> &NodeState {
378 &self.state
379 }
380}
381
382impl ModifierNode for FocusRequesterNode {
383 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
384 self.state.set_attached(true);
385 self.requester
386 .bind(FocusRequester::binding_here(context.node_id()));
387 }
388
389 fn on_detach(&mut self) {
390 self.state.set_attached(false);
391 self.requester.bind(None);
392 }
393}
394
395#[derive(Clone)]
397pub struct FocusRequesterElement {
398 requester: FocusRequester,
399}
400
401impl FocusRequesterElement {
402 pub(crate) fn new(requester: FocusRequester) -> Self {
403 Self { requester }
404 }
405}
406
407impl std::fmt::Debug for FocusRequesterElement {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 f.write_str("FocusRequesterElement")
410 }
411}
412
413impl PartialEq for FocusRequesterElement {
414 fn eq(&self, other: &Self) -> bool {
415 Rc::ptr_eq(&self.requester.binding, &other.requester.binding)
416 }
417}
418
419impl Eq for FocusRequesterElement {}
420
421impl Hash for FocusRequesterElement {
422 fn hash<H: Hasher>(&self, state: &mut H) {
423 Rc::as_ptr(&self.requester.binding).hash(state);
424 }
425}
426
427impl ModifierNodeElement for FocusRequesterElement {
428 type Node = FocusRequesterNode;
429
430 fn create(&self) -> Self::Node {
431 FocusRequesterNode::new(self.requester.clone())
432 }
433
434 fn update(&self, node: &mut Self::Node) {
435 if !Rc::ptr_eq(&node.requester.binding, &self.requester.binding) {
436 let bound = node.requester.binding.get();
437 node.requester.bind(None);
438 node.requester = self.requester.clone();
439 node.requester.bind(bound);
440 }
441 }
442
443 fn inspector_name(&self) -> &'static str {
444 "focusRequester"
445 }
446
447 fn capabilities(&self) -> NodeCapabilities {
448 NodeCapabilities::NONE
449 }
450}
451
452#[cfg(test)]
453#[path = "tests/focus_tests.rs"]
454mod tests;