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::{focus_dispatch, render_state::AppContextId};
14
15/// Focus direction for navigation.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum FocusDirection {
18    /// Enter focus from outside.
19    Enter,
20    /// Exit focus to outside.
21    Exit,
22    /// Move to next focusable.
23    Next,
24    /// Move to previous focusable.
25    Previous,
26    /// Move up (2D navigation).
27    Up,
28    /// Move down (2D navigation).
29    Down,
30    /// Move left (2D navigation).
31    Left,
32    /// Move right (2D navigation).
33    Right,
34}
35
36type FocusChangedCallback = Rc<dyn Fn(FocusState)>;
37
38struct FocusTargetShared {
39    focus_state: Cell<FocusState>,
40    on_focus_changed: RefCell<Option<FocusChangedCallback>>,
41}
42
43impl FocusTargetShared {
44    fn new(on_focus_changed: Option<FocusChangedCallback>) -> Self {
45        Self {
46            focus_state: Cell::new(FocusState::Inactive),
47            on_focus_changed: RefCell::new(on_focus_changed),
48        }
49    }
50
51    fn set_focus_state(&self, state: FocusState) {
52        let old_state = self.focus_state.get();
53        if old_state != state {
54            self.focus_state.set(state);
55            let callback = self.on_focus_changed.borrow().clone();
56            if let Some(callback) = callback {
57                callback(state);
58            }
59        }
60    }
61}
62
63impl focus_dispatch::FocusTargetHandle for FocusTargetShared {
64    fn set_focus_state(&self, state: FocusState) {
65        FocusTargetShared::set_focus_state(self, state);
66    }
67}
68
69pub struct FocusTargetNode {
70    state: NodeState,
71    shared: Rc<FocusTargetShared>,
72    handle: Rc<dyn focus_dispatch::FocusTargetHandle>,
73    registered_node_id: Cell<Option<NodeId>>,
74}
75
76impl FocusTargetNode {
77    pub fn new() -> Self {
78        Self::from_callback(None)
79    }
80
81    pub fn with_callback<F>(callback: F) -> Self
82    where
83        F: Fn(FocusState) + 'static,
84    {
85        Self::from_callback(Some(Rc::new(callback) as FocusChangedCallback))
86    }
87
88    fn from_callback(on_focus_changed: Option<FocusChangedCallback>) -> Self {
89        let shared = Rc::new(FocusTargetShared::new(on_focus_changed));
90        let handle: Rc<dyn focus_dispatch::FocusTargetHandle> = shared.clone();
91        Self {
92            state: NodeState::new(),
93            shared,
94            handle,
95            registered_node_id: Cell::new(None),
96        }
97    }
98
99    pub fn set_focus_state(&self, state: FocusState) {
100        self.shared.set_focus_state(state);
101    }
102
103    pub fn clear_focus(&self) {
104        self.set_focus_state(FocusState::Inactive);
105    }
106
107    fn set_callback(&self, callback: Option<FocusChangedCallback>) {
108        *self.shared.on_focus_changed.borrow_mut() = callback;
109    }
110}
111
112impl Default for FocusTargetNode {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl DelegatableNode for FocusTargetNode {
119    fn node_state(&self) -> &NodeState {
120        &self.state
121    }
122}
123
124impl ModifierNode for FocusTargetNode {
125    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
126        self.state.set_attached(true);
127        if let Some(node_id) = context.node_id() {
128            self.registered_node_id.set(Some(node_id));
129            focus_dispatch::register_focus_target(node_id, Rc::clone(&self.handle));
130        }
131    }
132
133    fn on_detach(&mut self) {
134        self.state.set_attached(false);
135        if let Some(node_id) = self.registered_node_id.take() {
136            focus_dispatch::unregister_focus_target(node_id, &self.handle);
137        }
138        self.clear_focus();
139    }
140
141    impl_focus_node!();
142}
143
144impl FocusNode for FocusTargetNode {
145    fn focus_state(&self) -> FocusState {
146        self.shared.focus_state.get()
147    }
148
149    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, state: FocusState) {
150        self.set_focus_state(state);
151    }
152}
153
154#[derive(Clone)]
155pub struct FocusTargetElement {
156    on_focus_changed: Option<FocusChangedCallback>,
157}
158
159impl FocusTargetElement {
160    pub fn new() -> Self {
161        Self {
162            on_focus_changed: None,
163        }
164    }
165
166    pub fn with_callback<F>(callback: F) -> Self
167    where
168        F: Fn(FocusState) + 'static,
169    {
170        Self {
171            on_focus_changed: Some(Rc::new(callback)),
172        }
173    }
174}
175
176impl Default for FocusTargetElement {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182impl std::fmt::Debug for FocusTargetElement {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        f.debug_struct("FocusTargetElement")
185            .field("has_callback", &self.on_focus_changed.is_some())
186            .finish()
187    }
188}
189
190impl PartialEq for FocusTargetElement {
191    fn eq(&self, other: &Self) -> bool {
192        self.on_focus_changed.is_some() == other.on_focus_changed.is_some()
193    }
194}
195
196impl Hash for FocusTargetElement {
197    fn hash<H: Hasher>(&self, state: &mut H) {
198        "focus_target".hash(state);
199        self.on_focus_changed.is_some().hash(state);
200    }
201}
202
203impl ModifierNodeElement for FocusTargetElement {
204    type Node = FocusTargetNode;
205
206    fn create(&self) -> Self::Node {
207        if let Some(callback) = &self.on_focus_changed {
208            FocusTargetNode::with_callback({
209                let callback = callback.clone();
210                move |state| callback(state)
211            })
212        } else {
213            FocusTargetNode::new()
214        }
215    }
216
217    fn update(&self, node: &mut Self::Node) {
218        node.set_callback(self.on_focus_changed.clone());
219    }
220
221    fn inspector_name(&self) -> &'static str {
222        "focusTarget"
223    }
224
225    fn capabilities(&self) -> NodeCapabilities {
226        NodeCapabilities::FOCUS
227    }
228
229    fn always_update(&self) -> bool {
230        true
231    }
232}
233
234/// Why [`FocusRequester::request_focus`] could not move focus.
235#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub enum FocusRequestError {
237    /// The requester has never been attached to a node via
238    /// [`Modifier::focus_requester`](super::Modifier::focus_requester), or the
239    /// node it was attached to has since left composition.
240    NotAttached,
241    /// The requester's node is attached, but nothing on it — no
242    /// [`Modifier::focus_target`](super::Modifier::focus_target), no
243    /// [`Modifier::on_focus_changed`](super::Modifier::on_focus_changed), no
244    /// text field — is registered to receive focus.
245    NoFocusTarget,
246}
247
248impl std::fmt::Display for FocusRequestError {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        match self {
251            FocusRequestError::NotAttached => {
252                write!(f, "FocusRequester is not attached to a node in composition")
253            }
254            FocusRequestError::NoFocusTarget => write!(
255                f,
256                "FocusRequester's node has no focus target to move focus onto"
257            ),
258        }
259    }
260}
261
262impl std::error::Error for FocusRequestError {}
263
264#[derive(Clone, Copy)]
265struct FocusRequesterBinding {
266    app_context: AppContextId,
267    node_id: NodeId,
268}
269
270/// A handle an app holds to move focus onto a node imperatively.
271///
272/// `remember` one, hang it on a node with
273/// [`Modifier::focus_requester`](super::Modifier::focus_requester) next to a
274/// [`Modifier::focus_target`](super::Modifier::focus_target) (or
275/// [`Modifier::on_focus_changed`](super::Modifier::on_focus_changed)), and
276/// call [`request_focus`](Self::request_focus) — from a click handler, an
277/// effect that runs once a screen appears, wherever the app decides focus
278/// should move.
279///
280/// ```ignore
281/// let requester = remember(FocusRequester::new).with(Clone::clone);
282/// // ... in the composition:
283/// Modifier::empty().focus_requester(&requester).focus_target()
284/// // ... later, imperatively:
285/// requester.request_focus().ok();
286/// ```
287#[derive(Clone, Default)]
288pub struct FocusRequester {
289    binding: Rc<Cell<Option<FocusRequesterBinding>>>,
290}
291
292impl FocusRequester {
293    pub fn new() -> Self {
294        Self::default()
295    }
296
297    /// Moves focus onto the node this requester is attached to.
298    ///
299    /// See [`FocusRequestError`] for the two predictable ways this can fail
300    /// instead of moving focus.
301    pub fn request_focus(&self) -> Result<(), FocusRequestError> {
302        let Some(binding) = self.binding.get() else {
303            return Err(FocusRequestError::NotAttached);
304        };
305        match focus_dispatch::request_focus_for(binding.app_context, binding.node_id) {
306            Some(true) => Ok(()),
307            Some(false) => Err(FocusRequestError::NoFocusTarget),
308            None => Err(FocusRequestError::NotAttached),
309        }
310    }
311
312    /// The node this requester is attached to, if attached.
313    pub fn node_id(&self) -> Option<NodeId> {
314        self.binding.get().map(|binding| binding.node_id)
315    }
316
317    fn bind(&self, binding: Option<FocusRequesterBinding>) {
318        self.binding.set(binding);
319    }
320
321    fn binding_here(node_id: Option<NodeId>) -> Option<FocusRequesterBinding> {
322        Some(FocusRequesterBinding {
323            app_context: crate::render_state::current_app_context_id_opt()?,
324            node_id: node_id?,
325        })
326    }
327}
328
329impl std::fmt::Debug for FocusRequester {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        f.debug_struct("FocusRequester")
332            .field("node_id", &self.node_id())
333            .finish()
334    }
335}
336
337pub struct FocusRequesterNode {
338    state: NodeState,
339    requester: FocusRequester,
340}
341
342impl FocusRequesterNode {
343    pub(crate) fn new(requester: FocusRequester) -> Self {
344        Self {
345            state: NodeState::new(),
346            requester,
347        }
348    }
349}
350
351impl DelegatableNode for FocusRequesterNode {
352    fn node_state(&self) -> &NodeState {
353        &self.state
354    }
355}
356
357impl ModifierNode for FocusRequesterNode {
358    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
359        self.state.set_attached(true);
360        self.requester
361            .bind(FocusRequester::binding_here(context.node_id()));
362    }
363
364    fn on_detach(&mut self) {
365        self.state.set_attached(false);
366        self.requester.bind(None);
367    }
368}
369
370/// Modifier element for [`FocusRequester`].
371#[derive(Clone)]
372pub struct FocusRequesterElement {
373    requester: FocusRequester,
374}
375
376impl FocusRequesterElement {
377    pub(crate) fn new(requester: FocusRequester) -> Self {
378        Self { requester }
379    }
380}
381
382impl std::fmt::Debug for FocusRequesterElement {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.write_str("FocusRequesterElement")
385    }
386}
387
388impl PartialEq for FocusRequesterElement {
389    fn eq(&self, other: &Self) -> bool {
390        Rc::ptr_eq(&self.requester.binding, &other.requester.binding)
391    }
392}
393
394impl Eq for FocusRequesterElement {}
395
396impl Hash for FocusRequesterElement {
397    fn hash<H: Hasher>(&self, state: &mut H) {
398        Rc::as_ptr(&self.requester.binding).hash(state);
399    }
400}
401
402impl ModifierNodeElement for FocusRequesterElement {
403    type Node = FocusRequesterNode;
404
405    fn create(&self) -> Self::Node {
406        FocusRequesterNode::new(self.requester.clone())
407    }
408
409    fn update(&self, node: &mut Self::Node) {
410        if !Rc::ptr_eq(&node.requester.binding, &self.requester.binding) {
411            let bound = node.requester.binding.get();
412            node.requester.bind(None);
413            node.requester = self.requester.clone();
414            node.requester.bind(bound);
415        }
416    }
417
418    fn inspector_name(&self) -> &'static str {
419        "focusRequester"
420    }
421
422    fn capabilities(&self) -> NodeCapabilities {
423        NodeCapabilities::NONE
424    }
425}
426
427#[cfg(test)]
428#[path = "tests/focus_tests.rs"]
429mod tests;