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