1use super::*;
2
3impl<R> AppShell<R>
4where
5 R: Renderer,
6 R::Error: Debug,
7{
8 fn resolve_gesture_targets(
9 &self,
10 pointer: PointerId,
11 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
12 self.resolve_hit_path(pointer)
13 }
14
15 fn resolve_hit_path(
22 &self,
23 pointer: PointerId,
24 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
25 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
26 return Vec::new();
27 };
28
29 let scene = self.renderer.scene();
30 let targets: Vec<_> = node_ids
31 .iter()
32 .filter_map(|&id| scene.find_target(id))
33 .collect();
34 log::trace!(
35 target: "cranpose::input",
36 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
37 targets.len()
38 );
39 targets
40 }
41
42 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
43 where
44 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
45 {
46 for target in targets {
47 let node_id = target.node_id();
48 target.dispatch(event.clone());
49 log::trace!(
50 target: "cranpose::input",
51 "dispatch {:?} node={} consumed={} stop_on_consume={}",
52 event.kind,
53 node_id,
54 event.is_consumed(),
55 stop_on_consume,
56 );
57 if stop_on_consume && event.is_consumed() {
58 break;
59 }
60 }
61 }
62
63 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
64 enter_event_handler();
65 let result = run_in_mutable_snapshot(|| self.set_cursor_inner(x, y)).unwrap_or(false);
66 exit_event_handler();
67 if result {
68 self.mark_dirty();
69 }
70 log::trace!(
71 target: "cranpose::input",
72 "set_cursor ({x:.2},{y:.2}) -> {result}"
73 );
74 result
75 }
76
77 fn set_cursor_inner(&mut self, x: f32, y: f32) -> bool {
78 self.cursor = (x, y);
79
80 if self.buttons_pressed != PointerButtons::NONE {
84 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
85 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
86 if !targets.is_empty() {
87 let event =
88 PointerEvent::new(PointerEventKind::Move, Point { x, y }, Point { x, y })
89 .with_buttons(self.buttons_pressed);
90 self.dispatch_targets(targets, event, false);
91 return true;
92 }
93
94 return false;
95 }
96
97 return false;
100 }
101
102 let hits = self.renderer.scene().hit_test(x, y);
105 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
106
107 let pos = Point { x, y };
109 let previously_hovered = self.hovered_nodes.clone();
110 for old_id in previously_hovered {
111 if !new_ids.contains(&old_id) {
112 if let Some(target) = self.renderer.scene().find_target(old_id) {
113 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos)
114 .with_buttons(self.buttons_pressed);
115 self.dispatch_targets(std::iter::once(target), exit_event, false);
116 }
117 }
118 }
119
120 for hit in &hits {
122 if !self.hovered_nodes.contains(&hit.node_id()) {
123 let enter_event = PointerEvent::new(PointerEventKind::Enter, pos, pos)
124 .with_buttons(self.buttons_pressed);
125 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
126 }
127 }
128
129 self.hovered_nodes = new_ids;
130
131 if !hits.is_empty() {
132 let event = PointerEvent::new(PointerEventKind::Move, pos, pos)
133 .with_buttons(self.buttons_pressed);
134 self.dispatch_targets(hits, event, true);
135 true
136 } else {
137 false
138 }
139 }
140
141 pub fn pointer_pressed(&mut self) -> bool {
142 enter_event_handler();
143 let result = run_in_mutable_snapshot(|| self.pointer_pressed_inner()).unwrap_or(false);
144 exit_event_handler();
145 if result {
146 self.mark_dirty();
147 }
148 log::trace!(target: "cranpose::input", "pointer_pressed -> {result}");
149 result
150 }
151
152 fn pointer_pressed_inner(&mut self) -> bool {
153 self.buttons_pressed.insert(PointerButton::Primary);
155
156 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
164 if hits.is_empty() {
165 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
166 false
167 } else {
168 let event = PointerEvent::new(
169 PointerEventKind::Down,
170 Point {
171 x: self.cursor.0,
172 y: self.cursor.1,
173 },
174 Point {
175 x: self.cursor.0,
176 y: self.cursor.1,
177 },
178 )
179 .with_buttons(self.buttons_pressed);
180
181 let mut delivered_capture_paths = Vec::new();
182 for hit in hits {
183 let node_id = hit.node_id();
184 delivered_capture_paths.push(hit.capture_path());
185 hit.dispatch(event.clone());
186 log::trace!(
187 target: "cranpose::input",
188 "dispatch {:?} node={} consumed={} stop_on_consume=true",
189 event.kind,
190 node_id,
191 event.is_consumed(),
192 );
193 if event.is_consumed() {
194 break;
195 }
196 }
197
198 self.hit_path_tracker
199 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
200 log::trace!(
201 target: "cranpose::input",
202 "pointer_pressed_inner cached_hit_path={:?}",
203 self.hit_path_tracker.get_path(PointerId::PRIMARY),
204 );
205
206 true
207 }
208 }
209
210 pub fn pointer_released(&mut self) -> bool {
211 enter_event_handler();
212 let result = run_in_mutable_snapshot(|| self.pointer_released_inner()).unwrap_or(false);
213 exit_event_handler();
214 if result {
215 self.mark_dirty();
216 }
217 log::trace!(target: "cranpose::input", "pointer_released -> {result}");
218 result
219 }
220
221 fn pointer_released_inner(&mut self) -> bool {
222 self.buttons_pressed.remove(PointerButton::Primary);
225 let corrected_buttons = self.buttons_pressed;
226 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
227
228 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
230
231 if !targets.is_empty() {
232 let event = PointerEvent::new(
233 PointerEventKind::Up,
234 Point {
235 x: self.cursor.0,
236 y: self.cursor.1,
237 },
238 Point {
239 x: self.cursor.0,
240 y: self.cursor.1,
241 },
242 )
243 .with_buttons(corrected_buttons);
244
245 self.dispatch_targets(targets, event, false);
246 true
247 } else {
248 false
249 }
250 }
251
252 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
256 enter_event_handler();
257 let result = run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y))
258 .unwrap_or(false);
259 exit_event_handler();
260 if result {
261 self.mark_dirty();
262 }
263 log::trace!(
264 target: "cranpose::input",
265 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
266 );
267 result
268 }
269
270 fn pointer_scrolled_inner(&mut self, delta_x: f32, delta_y: f32) -> bool {
271 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
272 return false;
273 }
274
275 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
276 if hits.is_empty() {
277 return false;
278 }
279
280 let event = PointerEvent::new(
281 PointerEventKind::Scroll,
282 Point {
283 x: self.cursor.0,
284 y: self.cursor.1,
285 },
286 Point {
287 x: self.cursor.0,
288 y: self.cursor.1,
289 },
290 )
291 .with_buttons(self.buttons_pressed)
292 .with_scroll_delta(Point {
293 x: delta_x,
294 y: delta_y,
295 });
296
297 self.dispatch_targets(hits, event.clone(), true);
298
299 event.is_consumed()
300 }
301
302 pub fn cancel_gesture(&mut self) {
308 enter_event_handler();
309 let _ = run_in_mutable_snapshot(|| {
310 self.cancel_gesture_inner();
311 });
312 exit_event_handler();
313 }
314
315 fn cancel_gesture_inner(&mut self) {
316 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
317
318 self.hit_path_tracker.clear();
320 self.buttons_pressed = PointerButtons::NONE;
321
322 if !targets.is_empty() {
323 let event = PointerEvent::new(
324 PointerEventKind::Cancel,
325 Point {
326 x: self.cursor.0,
327 y: self.cursor.1,
328 },
329 Point {
330 x: self.cursor.0,
331 y: self.cursor.1,
332 },
333 );
334
335 self.dispatch_targets(targets, event, false);
336 }
337
338 let pos = Point {
340 x: self.cursor.0,
341 y: self.cursor.1,
342 };
343 let hovered_nodes = self.hovered_nodes.clone();
344 for node_id in hovered_nodes {
345 if let Some(target) = self.renderer.scene().find_target(node_id) {
346 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos);
347 self.dispatch_targets(std::iter::once(target), exit_event, false);
348 }
349 }
350 self.hovered_nodes.clear();
351 }
352
353 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
360 enter_event_handler();
361 let result = self.on_key_event_inner(event);
362 exit_event_handler();
363 result
364 }
365
366 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
368 use KeyEventType::KeyDown;
369
370 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
372 #[cfg(all(
375 not(target_arch = "wasm32"),
376 not(target_os = "android"),
377 not(target_os = "ios")
378 ))]
379 {
380 match event.key_code {
381 KeyCode::C => {
383 let text = self.on_copy();
385 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
386 let _ = clipboard.set_text(&text);
387 return true;
388 }
389 }
390 KeyCode::X => {
392 let text = self.on_cut();
394 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
395 let _ = clipboard.set_text(&text);
396 self.mark_dirty();
397 self.request_layout_pass();
398 return true;
399 }
400 }
401 KeyCode::V => {
403 let text = self.clipboard.as_mut().and_then(|cb| cb.get_text().ok());
405 if let Some(text) = text {
406 if self.on_paste(&text) {
407 return true;
408 }
409 }
410 }
411 _ => {}
412 }
413 }
414 }
415
416 if !cranpose_ui::text_field_focus::has_focused_field() {
418 return false;
419 }
420
421 let handled = run_in_mutable_snapshot(|| {
425 cranpose_ui::text_field_focus::dispatch_key_event(event)
428 })
429 .unwrap_or(false);
430
431 if handled {
432 self.mark_dirty();
434 self.request_layout_pass();
435 }
436
437 handled
438 }
439
440 pub fn on_paste(&mut self, text: &str) -> bool {
444 let handled =
448 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
449 .unwrap_or(false);
450
451 if handled {
452 self.mark_dirty();
453 self.request_layout_pass();
454 }
455
456 handled
457 }
458
459 pub fn on_copy(&mut self) -> Option<String> {
463 cranpose_ui::text_field_focus::dispatch_copy()
465 }
466
467 pub fn on_cut(&mut self) -> Option<String> {
471 let text = cranpose_ui::text_field_focus::dispatch_cut();
473
474 if text.is_some() {
475 self.mark_dirty();
476 self.request_layout_pass();
477 }
478
479 text
480 }
481
482 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
486 pub fn set_primary_selection(&mut self, text: &str) {
487 use arboard::{LinuxClipboardKind, SetExtLinux};
488 if let Some(ref mut clipboard) = self.clipboard {
489 let result = clipboard
490 .set()
491 .clipboard(LinuxClipboardKind::Primary)
492 .text(text.to_string());
493 if let Err(e) = result {
494 log::debug!("Primary selection set failed: {:?}", e);
496 }
497 }
498 }
499
500 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
503 pub fn get_primary_selection(&mut self) -> Option<String> {
504 use arboard::{GetExtLinux, LinuxClipboardKind};
505 if let Some(ref mut clipboard) = self.clipboard {
506 clipboard
507 .get()
508 .clipboard(LinuxClipboardKind::Primary)
509 .text()
510 .ok()
511 } else {
512 None
513 }
514 }
515
516 #[cfg(all(
517 not(target_os = "linux"),
518 not(target_arch = "wasm32"),
519 not(target_os = "ios")
520 ))]
521 pub fn get_primary_selection(&mut self) -> Option<String> {
522 None
523 }
524
525 pub fn sync_selection_to_primary(&mut self) {
528 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
529 {
530 if let Some(text) = self.on_copy() {
531 self.set_primary_selection(&text);
532 }
533 }
534 }
535
536 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
544 let handled = run_in_mutable_snapshot(|| {
546 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
547 })
548 .unwrap_or(false);
549
550 if handled {
551 self.mark_dirty();
552 self.request_layout_pass();
554 }
555
556 handled
557 }
558
559 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
562 let handled = run_in_mutable_snapshot(|| {
563 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
564 })
565 .unwrap_or(false);
566
567 if handled {
568 self.mark_dirty();
569 self.request_layout_pass();
570 }
571
572 handled
573 }
574}