Skip to main content

cranpose_ui/modifier/
focus.rs

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/// Focus direction for navigation.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum FocusDirection {
22    /// Enter focus from outside.
23    Enter,
24    /// Exit focus to outside.
25    Exit,
26    /// Move to next focusable.
27    Next,
28    /// Move to previous focusable.
29    Previous,
30    /// Move up (2D navigation).
31    Up,
32    /// Move down (2D navigation).
33    Down,
34    /// Move left (2D navigation).
35    Left,
36    /// Move right (2D navigation).
37    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    /// The focus observers composed around this target.
46    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    /// Moves a focused target's focus from the old observers to the new.
79    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/// Why [`FocusRequester::request_focus`] could not move focus.
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum FocusRequestError {
260    /// The requester has never been attached to a node via
261    /// [`Modifier::focus_requester`](super::Modifier::focus_requester), or the
262    /// node it was attached to has since left composition.
263    NotAttached,
264    /// The requester's node is attached, but nothing on it — no
265    /// [`Modifier::focus_target`](super::Modifier::focus_target), no
266    /// [`Modifier::on_focus_changed`](super::Modifier::on_focus_changed), no
267    /// text field — is registered to receive focus.
268    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/// A handle an app holds to move focus onto a node imperatively.
294///
295/// `remember` one, hang it on a node with
296/// [`Modifier::focus_requester`](super::Modifier::focus_requester) next to a
297/// [`Modifier::focus_target`](super::Modifier::focus_target) (or
298/// [`Modifier::on_focus_changed`](super::Modifier::on_focus_changed)), and
299/// call [`request_focus`](Self::request_focus) — from a click handler, an
300/// effect that runs once a screen appears, wherever the app decides focus
301/// should move.
302///
303/// ```ignore
304/// let requester = remember(FocusRequester::new).with(Clone::clone);
305/// // ... in the composition:
306/// Modifier::empty().focus_requester(&requester).focus_target()
307/// // ... later, imperatively:
308/// requester.request_focus().ok();
309/// ```
310#[derive(Clone, Default)]
311pub struct FocusRequester {
312    binding: Rc<Cell<Option<FocusRequesterBinding>>>,
313}
314
315impl FocusRequester {
316    /// Creates a requester that no node uses yet. [`Self::request_focus`] fails with
317    /// `FocusRequestError::NotAttached` until a `focus_requester` modifier attaches it.
318    pub fn new() -> Self {
319        Self::default()
320    }
321
322    /// Moves focus onto the node this requester is attached to.
323    ///
324    /// See [`FocusRequestError`] for the two predictable ways this can fail
325    /// instead of moving focus.
326    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    /// The node this requester is attached to, if attached.
338    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/// Modifier element for [`FocusRequester`].
396#[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;