cranpose_app_shell/shell_input/
key_activation.rs1use std::{fmt::Debug, rc::Rc};
2
3use cranpose_core::{NodeId, enter_event_handler_scope, run_in_mutable_snapshot};
4use cranpose_foundation::PointerEventKind;
5use cranpose_render_common::Renderer;
6use cranpose_ui::{Key, KeyEvent, KeyEventType, LayoutBox};
7use cranpose_ui_graphics::{Point, Rect};
8
9use crate::{PointerEventTime, SurfaceMut};
10
11pub(crate) struct KeyPress {
13 key: Key,
14 target: PressTarget,
15}
16
17enum PressTarget {
18 Pointer(Press),
19 Action(Action),
22}
23
24enum Action {
25 Direct(cranpose_foundation::SemanticsCustomAction),
26 Edit(NodeId),
27}
28
29struct Press {
32 position: Point,
33 local: Point,
34 handlers: Rc<cranpose_ui::ModifierNodeSlices>,
35}
36
37pub(super) enum ActivationTarget {
38 Direct(cranpose_foundation::SemanticsCustomAction),
39 Edit(NodeId),
40 Pointer(NodeId, Option<Rect>),
41}
42
43pub(super) fn activation_target(
44 node: &cranpose_ui::SemanticsNode,
45 node_id: NodeId,
46 canvas_key: Option<u64>,
47) -> Option<ActivationTarget> {
48 if node.hidden {
49 return None;
50 }
51 if node.node_id != node_id {
52 return node
53 .children
54 .iter()
55 .find_map(|child| activation_target(child, node_id, canvas_key));
56 }
57 if !node.enabled {
58 return None;
59 }
60 if let Some(key) = canvas_key {
61 let child = node
62 .details()
63 .canvas_children
64 .iter()
65 .find(|child| child.key == key)?;
66 return (child.enabled && child.clickable)
67 .then_some(ActivationTarget::Pointer(node_id, Some(child.bounds)));
68 }
69 if let Some(action) = &node.on_click {
70 return Some(ActivationTarget::Direct(action.clone()));
71 }
72 if node.details().editable_text && node.focusable {
73 return Some(ActivationTarget::Edit(node_id));
74 }
75 node.actions.first().map(|action| match action {
76 cranpose_ui::SemanticsAction::Click { handler } => {
77 ActivationTarget::Pointer(handler.node_id(), None)
78 }
79 })
80}
81
82pub(super) fn activation_input(
83 root: &LayoutBox,
84 node_id: NodeId,
85 canvas_bounds: Option<Rect>,
86) -> Option<(Point, Point, Rc<cranpose_ui::ModifierNodeSlices>)> {
87 if root.node_id != node_id {
88 return root
89 .children
90 .iter()
91 .find_map(|child| activation_input(child, node_id, canvas_bounds));
92 }
93 let bounds = canvas_bounds.map_or(root.rect, |bounds| Rect {
94 x: root.rect.x + bounds.x,
95 y: root.rect.y + bounds.y,
96 ..bounds
97 });
98 let position = Point {
99 x: bounds.x + bounds.width * 0.5,
100 y: bounds.y + bounds.height * 0.5,
101 };
102 let local = Point {
103 x: position.x - root.rect.x,
104 y: position.y - root.rect.y,
105 };
106 (bounds.width > 0.0 && bounds.height > 0.0 && position.x.is_finite() && position.y.is_finite())
107 .then(|| (position, local, Rc::clone(&root.node_data.modifier_slices)))
108}
109
110pub(super) fn plain_key_down(event: &KeyEvent) -> bool {
111 event.type_() == KeyEventType::KeyDown
112 && !event.modifiers().ctrl
113 && !event.modifiers().meta
114 && !event.modifiers().alt
115}
116
117#[expect(clippy::multiple_inherent_impl, reason = "one file per shell concern")]
118impl<R> SurfaceMut<'_, R>
119where
120 R: Renderer,
121 R::Error: Debug,
122{
123 pub fn accessibility_activate(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> bool {
129 let event_time = self.shell.app.realtime_pointer_event_time(None);
130 let _event_handler = enter_event_handler_scope();
131 let result = {
132 let _app_context = self.shell.app.app_context.enter_scope();
133 run_in_mutable_snapshot(|| self.activate_node(node_id, canvas_key, event_time))
134 .unwrap_or(false)
135 };
136 if result {
137 self.mark_dirty();
138 }
139 result
140 }
141
142 fn activate_node(
143 &mut self,
144 node_id: NodeId,
145 canvas_key: Option<u64>,
146 event_time: PointerEventTime,
147 ) -> bool {
148 match self.press_target(node_id, canvas_key) {
149 Some(PressTarget::Pointer(press)) => {
150 for kind in [PointerEventKind::Down, PointerEventKind::Up] {
151 self.send_press(&press, kind, event_time);
152 }
153 true
154 }
155 Some(PressTarget::Action(target)) => self.run_action(target),
156 None => false,
157 }
158 }
159
160 pub(super) fn press_by_key(&mut self, node_id: NodeId, key: Key) -> bool {
166 let event_time = self.shell.app.realtime_pointer_event_time(None);
167 let _event_handler = enter_event_handler_scope();
168 let _app_context = self.shell.app.app_context.enter_scope();
169 run_in_mutable_snapshot(|| {
170 self.end_key_press(PointerEventKind::Cancel, event_time);
171 let Some(target) = self.press_target(node_id, None) else {
172 return false;
173 };
174 if let PressTarget::Pointer(press) = &target {
175 self.send_press(press, PointerEventKind::Down, event_time);
176 }
177 self.surface_mut().key_press = Some(KeyPress { key, target });
178 self.mark_dirty();
179 true
180 })
181 .unwrap_or(false)
182 }
183
184 pub(super) fn release_by_key(&mut self, key: Key) -> bool {
187 if self
188 .surface()
189 .key_press
190 .as_ref()
191 .is_none_or(|press| press.key != key)
192 {
193 return false;
194 }
195 let event_time = self.shell.app.realtime_pointer_event_time(None);
196 let _event_handler = enter_event_handler_scope();
197 let _app_context = self.shell.app.app_context.enter_scope();
198 run_in_mutable_snapshot(|| self.end_key_press(PointerEventKind::Up, event_time))
199 .unwrap_or(false)
200 }
201
202 fn end_key_press(&mut self, kind: PointerEventKind, event_time: PointerEventTime) -> bool {
203 let Some(press) = self.surface_mut().key_press.take() else {
204 return false;
205 };
206 self.mark_dirty();
207 match press.target {
208 PressTarget::Pointer(press) => {
209 self.send_press(&press, kind, event_time);
210 true
211 }
212 PressTarget::Action(target) => kind == PointerEventKind::Up && self.run_action(target),
213 }
214 }
215
216 fn press_target(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> Option<PressTarget> {
217 let target = {
218 let (app, surface) = self.parts();
219 surface
220 .semantics_tree_for_input(app)
221 .and_then(|tree| activation_target(tree.root(), node_id, canvas_key))?
222 };
223 let (target_id, canvas_bounds) = match target {
224 ActivationTarget::Pointer(node_id, bounds) => (node_id, bounds),
225 ActivationTarget::Direct(action) => {
226 return Some(PressTarget::Action(Action::Direct(action)));
227 }
228 ActivationTarget::Edit(node_id) => {
229 return Some(PressTarget::Action(Action::Edit(node_id)));
230 }
231 };
232 let (position, local, handlers) =
233 self.with_layout_tree(|tree| activation_input(tree?.root(), target_id, canvas_bounds))?;
234 Some(PressTarget::Pointer(Press {
235 position,
236 local,
237 handlers,
238 }))
239 }
240
241 fn send_press(&self, press: &Press, kind: PointerEventKind, event_time: PointerEventTime) {
242 let event = self.pointer_event(kind, press.local, press.position, event_time);
243 press.handlers.dispatch_pointer_event(event.clone());
244 event.finish_post_dispatch();
245 }
246
247 fn run_action(&mut self, action: Action) -> bool {
248 match action {
249 Action::Direct(action) => {
250 action.invoke();
251 true
252 }
253 Action::Edit(node_id) => {
254 self.activate();
255 cranpose_ui::request_focus_from_platform(node_id)
256 }
257 }
258 }
259}