Skip to main content

cranpose_ui/modifier/
key_input.rs

1use std::{
2    fmt,
3    hash::{Hash, Hasher},
4    rc::Rc,
5};
6
7use cranpose_core::{Applier, MemoryApplier, NodeId};
8use cranpose_foundation::{
9    DelegatableNode, ModifierChainNodeRef, ModifierNode, ModifierNodeElement, NodeCapabilities,
10    NodeState,
11};
12use smallvec::SmallVec;
13
14use super::{Modifier, ModifierChainHandle};
15use crate::{
16    KeyEvent, SubcomposeLayoutNode, active_focus_target, widgets::nodes::layout_node::LayoutNode,
17};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20enum KeyPass {
21    Preview,
22    Bubble,
23}
24
25type KeyHandler = Rc<dyn Fn(&KeyEvent) -> bool>;
26
27impl Modifier {
28    /// Intercepts hardware keys before the focused component handles them.
29    ///
30    /// Ancestors run from the root toward the focused component, with modifiers
31    /// on each component running in declaration order. Return `true` to consume
32    /// the event and prevent editing, control activation and further handlers.
33    /// Only the focused component and its ancestors receive the event.
34    pub fn on_preview_key_event(self, handler: impl Fn(&KeyEvent) -> bool + 'static) -> Self {
35        self.then(Self::with_element(KeyInputElement {
36            pass: KeyPass::Preview,
37            handler: Rc::new(handler),
38        }))
39    }
40
41    /// Handles hardware keys the focused component did not consume.
42    ///
43    /// Events bubble from the focused component toward the root, with modifiers
44    /// on each component running in reverse declaration order. Return `true` to
45    /// consume the event and stop propagation to outer handlers.
46    pub fn on_key_event(self, handler: impl Fn(&KeyEvent) -> bool + 'static) -> Self {
47        self.then(Self::with_element(KeyInputElement {
48            pass: KeyPass::Bubble,
49            handler: Rc::new(handler),
50        }))
51    }
52}
53
54#[derive(Clone)]
55struct KeyInputElement {
56    pass: KeyPass,
57    handler: KeyHandler,
58}
59
60impl fmt::Debug for KeyInputElement {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("KeyInputElement")
63            .field("pass", &self.pass)
64            .finish_non_exhaustive()
65    }
66}
67
68impl PartialEq for KeyInputElement {
69    fn eq(&self, other: &Self) -> bool {
70        self.pass == other.pass
71    }
72}
73
74impl Hash for KeyInputElement {
75    fn hash<H: Hasher>(&self, state: &mut H) {
76        self.pass.hash(state);
77    }
78}
79
80impl ModifierNodeElement for KeyInputElement {
81    type Node = KeyInputNode;
82
83    fn create(&self) -> Self::Node {
84        KeyInputNode {
85            state: NodeState::new(),
86            pass: self.pass,
87            handler: self.handler.clone(),
88        }
89    }
90
91    fn update(&self, node: &mut Self::Node) {
92        node.pass = self.pass;
93        node.handler = self.handler.clone();
94    }
95
96    fn always_update(&self) -> bool {
97        true
98    }
99
100    fn capabilities(&self) -> NodeCapabilities {
101        NodeCapabilities::KEY_INPUT
102    }
103}
104
105struct KeyInputNode {
106    state: NodeState,
107    pass: KeyPass,
108    handler: KeyHandler,
109}
110
111impl DelegatableNode for KeyInputNode {
112    fn node_state(&self) -> &NodeState {
113        &self.state
114    }
115}
116
117impl ModifierNode for KeyInputNode {}
118
119/// The focused component's keyboard route within a receiving surface or modal.
120///
121/// Platform shells create one route per hardware event, dispatch preview, run
122/// their existing focused-control/editor handling, then dispatch unconsumed
123/// events in the bubble phase. The route follows live parent links and stops at
124/// `root`; a focused node belonging to another window is excluded.
125pub struct KeyEventRoute {
126    focused: Option<NodeId>,
127    handlers: SmallVec<[NodeId; 8]>,
128    in_scope: bool,
129}
130
131impl KeyEventRoute {
132    /// Captures the ancestor route for the currently focused component.
133    pub fn new(applier: &mut MemoryApplier, root: Option<NodeId>) -> Self {
134        let mut route = Self {
135            focused: active_focus_target(),
136            handlers: SmallVec::new(),
137            in_scope: false,
138        };
139        route.in_scope = visit_focus_ancestors(applier, route.focused, root, |id, chain| {
140            if chain.capabilities().contains(NodeCapabilities::KEY_INPUT) {
141                route.handlers.push(id);
142            }
143        });
144        if !route.in_scope {
145            route.handlers.clear();
146        }
147        route
148    }
149
150    /// Whether the focused component belongs to the receiving surface or modal.
151    ///
152    /// Platform clipboard and IME entry points use this before accessing a
153    /// focused editor. This check does not create a keyboard handler route.
154    pub fn contains_focus(applier: &mut MemoryApplier, root: Option<NodeId>) -> bool {
155        visit_focus_ancestors(applier, active_focus_target(), root, |_, _| {})
156    }
157
158    /// Whether focused-control dispatch still belongs to this event's target.
159    ///
160    /// A preview callback can move or clear focus. The event must not then edit
161    /// the newly focused field or activate a control on another surface.
162    pub fn focus_is_current(&self) -> bool {
163        self.focused == active_focus_target() && (self.focused.is_none() || self.in_scope)
164    }
165
166    /// Offers the event to ancestors before the focused component.
167    pub fn dispatch_preview(&self, applier: &mut MemoryApplier, event: &KeyEvent) -> bool {
168        self.handlers
169            .iter()
170            .rev()
171            .any(|&node| dispatch_node(applier, node, event, KeyPass::Preview))
172    }
173
174    /// Offers an unconsumed event from the focused component to its ancestors.
175    pub fn dispatch_bubble(&self, applier: &mut MemoryApplier, event: &KeyEvent) -> bool {
176        self.handlers
177            .iter()
178            .any(|&node| dispatch_node(applier, node, event, KeyPass::Bubble))
179    }
180}
181
182/// Visits the modifier chains of `focused` and its ancestors up to `root`,
183/// layout and subcompose nodes alike; false when the walk does not reach
184/// `root`.
185fn visit_focus_ancestors(
186    applier: &mut MemoryApplier,
187    focused: Option<NodeId>,
188    root: Option<NodeId>,
189    mut visit: impl FnMut(NodeId, &ModifierChainHandle),
190) -> bool {
191    let (Some(mut current), Some(root)) = (focused, root) else {
192        return false;
193    };
194    for _ in 0..100_000 {
195        let Ok(node) = applier.get_mut(current) else {
196            return false;
197        };
198        let parent = node.parent();
199        let node = node.as_any_mut();
200        let window_root = if let Some(layout) = node.downcast_mut::<LayoutNode>() {
201            visit(current, layout.modifier_chain());
202            layout.is_window_root()
203        } else {
204            if let Some(subcompose) = node.downcast_mut::<SubcomposeLayoutNode>() {
205                subcompose.with_modifier_chain(|chain| visit(current, chain));
206            }
207            false
208        };
209        if current == root {
210            return true;
211        }
212        if window_root {
213            return false;
214        }
215        let Some(parent) = parent else {
216            return false;
217        };
218        current = parent;
219    }
220    false
221}
222
223fn dispatch_node(
224    applier: &mut MemoryApplier,
225    node: NodeId,
226    event: &KeyEvent,
227    pass: KeyPass,
228) -> bool {
229    if let Ok(handled) = applier.with_node::<LayoutNode, _>(node, |node| {
230        dispatch_chain(node.modifier_chain(), event, pass)
231    }) {
232        return handled;
233    }
234    applier
235        .with_node::<SubcomposeLayoutNode, _>(node, |node| {
236            node.with_modifier_chain(|chain| dispatch_chain(chain, event, pass))
237        })
238        .unwrap_or(false)
239}
240
241/// Offers `event` to the key handlers of `pass` in a node's chain: head to
242/// tail before the focused node handles it, tail to head after.
243fn dispatch_chain(chain: &ModifierChainHandle, event: &KeyEvent, pass: KeyPass) -> bool {
244    let chain = chain.chain();
245    let visit = |entry: ModifierChainNodeRef<'_>| {
246        entry.kind_set().contains(NodeCapabilities::KEY_INPUT)
247            && entry
248                .with_node(|node| {
249                    let node: &dyn std::any::Any = node;
250                    node.downcast_ref::<KeyInputNode>()
251                        .is_some_and(|node| node.pass == pass && (node.handler)(event))
252                })
253                .unwrap_or(false)
254    };
255    match pass {
256        KeyPass::Preview => chain.head_to_tail().any(visit),
257        KeyPass::Bubble => chain.tail_to_head().any(visit),
258    }
259}