1use super::*;
2use std::collections::HashMap;
3
4use super::event_chain::pointer_cancel_event_for_capture_switch;
5
6impl<H: UiHost> UiTree<H> {
7 #[stacksafe::stacksafe]
8 pub fn dispatch_event(&mut self, app: &mut H, services: &mut dyn UiServices, event: &Event) {
9 let Some(base_root) = self
10 .base_layer
11 .and_then(|id| self.layers.get(id).map(|l| l.root))
12 else {
13 return;
14 };
15
16 let trace_enabled = tracing::enabled!(tracing::Level::TRACE);
17 let window = self.window;
18 let frame_id = app.frame_id();
19 let kind: &'static str = match event {
20 Event::Pointer(_) | Event::PointerCancel(_) => "pointer",
21 Event::Timer { .. } => "timer",
22 _ => "other",
23 };
24
25 let ((), elapsed) = fret_perf::measure_span(
26 self.debug_enabled,
27 trace_enabled,
28 || {
29 tracing::trace_span!(
30 "fret.ui.dispatch.event",
31 window = ?window,
32 frame_id = frame_id.0,
33 kind,
34 )
35 },
36 || self.dispatch_event_inner(app, services, event, base_root),
37 );
38 if self.debug_enabled {
39 self.debug_stats.dispatch_events = self.debug_stats.dispatch_events.saturating_add(1);
40 if let Some(elapsed) = elapsed {
41 self.debug_stats.dispatch_time += elapsed;
42 match kind {
43 "pointer" => {
44 self.debug_stats.dispatch_pointer_events =
45 self.debug_stats.dispatch_pointer_events.saturating_add(1);
46 self.debug_stats.dispatch_pointer_event_time += elapsed;
47 }
48 "timer" => {
49 self.debug_stats.dispatch_timer_events =
50 self.debug_stats.dispatch_timer_events.saturating_add(1);
51 self.debug_stats.dispatch_timer_event_time += elapsed;
52 }
53 _ => {
54 self.debug_stats.dispatch_other_events =
55 self.debug_stats.dispatch_other_events.saturating_add(1);
56 self.debug_stats.dispatch_other_event_time += elapsed;
57 }
58 }
59 }
60 }
61 }
62
63 #[stacksafe::stacksafe]
64 fn dispatch_event_inner(
65 &mut self,
66 app: &mut H,
67 services: &mut dyn UiServices,
68 event: &Event,
69 base_root: NodeId,
70 ) {
71 self.begin_debug_frame_if_needed(app.frame_id());
72 let trace_enabled = tracing::enabled!(tracing::Level::TRACE);
73 #[cfg(debug_assertions)]
74 let debug_focus_scope = std::env::var_os("FRET_TEST_DEBUG_FOCUS_SCOPE").is_some();
75 #[cfg(debug_assertions)]
76 let mut debug_focus_last = self.focus;
77 #[cfg(debug_assertions)]
78 let mut debug_focus_note = |label: &str, focus: Option<NodeId>| {
79 if !debug_focus_scope {
80 return;
81 }
82 if focus != debug_focus_last {
83 eprintln!(
84 "debug: dispatch {}: focus {:?} -> {:?}",
85 label, debug_focus_last, focus
86 );
87 debug_focus_last = focus;
88 }
89 };
90
91 if let Some(window) = self.window {
92 let frame_id = app.frame_id();
93 let now_monotonic = app
94 .global::<fret_core::WindowFrameClockService>()
95 .and_then(|svc| svc.snapshot(window))
96 .map(|s| s.now_monotonic);
97
98 match event {
99 Event::ClipboardReadText { token, .. } => {
100 app.with_global_mut_untracked(
101 fret_runtime::WindowClipboardDiagnosticsStore::default,
102 |svc, _host| {
103 svc.record_read_ok(window, frame_id, *token);
104 },
105 );
106 }
107 Event::ClipboardReadFailed { token, error } => {
108 app.with_global_mut_untracked(
109 fret_runtime::WindowClipboardDiagnosticsStore::default,
110 |svc, _host| {
111 svc.record_read_unavailable(
112 window,
113 frame_id,
114 *token,
115 error.message.clone(),
116 );
117 },
118 );
119 }
120 Event::ClipboardWriteCompleted { outcome, .. } => {
121 app.with_global_mut_untracked(
122 fret_runtime::WindowClipboardDiagnosticsStore::default,
123 |svc, _host| match outcome {
124 fret_core::ClipboardWriteOutcome::Succeeded => {
125 svc.record_write_ok(window, frame_id);
126 }
127 fret_core::ClipboardWriteOutcome::Failed { error } => {
128 svc.record_write_unavailable(
129 window,
130 frame_id,
131 error.message.clone(),
132 );
133 }
134 },
135 );
136 }
137 _ => {}
138 }
139
140 let update_pointer = |app: &mut H,
141 pointer_id: fret_core::PointerId,
142 position: Point| {
143 app.with_global_mut_untracked(
144 crate::pointer_motion::WindowPointerMotionService::default,
145 |svc, _host| {
146 svc.update_position(window, pointer_id, position, frame_id, now_monotonic);
147 },
148 );
149 };
150
151 match event {
152 Event::Pointer(pe) => match pe {
153 PointerEvent::Move {
154 pointer_id,
155 position,
156 ..
157 }
158 | PointerEvent::Down {
159 pointer_id,
160 position,
161 ..
162 }
163 | PointerEvent::Up {
164 pointer_id,
165 position,
166 ..
167 }
168 | PointerEvent::Wheel {
169 pointer_id,
170 position,
171 ..
172 }
173 | PointerEvent::PinchGesture {
174 pointer_id,
175 position,
176 ..
177 } => {
178 update_pointer(app, *pointer_id, *position);
179 }
180 },
181 Event::PointerCancel(e) => {
182 if let Some(position) = e.position {
183 update_pointer(app, e.pointer_id, position);
184 }
185 }
186 _ => {}
187 }
188 }
189
190 if matches!(event, Event::Pointer(_)) {
194 let (_, elapsed) = fret_perf::measure_span(
195 self.debug_enabled,
196 trace_enabled,
197 || tracing::trace_span!("fret.ui.dispatch.scroll_handle_invalidation"),
198 || {
199 self.invalidate_scroll_handle_bindings_for_changed_handles(
200 app,
201 crate::layout_pass::LayoutPassKind::Final,
202 false,
203 false,
204 );
205 },
206 );
207 if let Some(elapsed) = elapsed {
208 self.debug_stats.dispatch_scroll_handle_invalidation_time += elapsed;
209 }
210 }
211
212 let is_wheel = matches!(event, Event::Pointer(PointerEvent::Wheel { .. }));
213
214 let ((active_layers, barrier_root), active_layers_elapsed) = fret_perf::measure_span(
215 self.debug_enabled,
216 trace_enabled,
217 || tracing::trace_span!("fret.ui.dispatch.active_layers"),
218 || {
219 let (active_layers, barrier_root) = self.active_input_layers();
220 (active_layers, barrier_root)
221 },
222 );
223 #[cfg(debug_assertions)]
224 debug_focus_note("after active layers", self.focus);
225 if let Some(active_layers_elapsed) = active_layers_elapsed {
226 self.debug_stats.dispatch_active_layers_time += active_layers_elapsed;
227 }
228
229 let dispatch_cx = self.build_dispatch_cx(app.frame_id(), active_layers, barrier_root);
230 let active_layers: &[NodeId] = dispatch_cx.active_input_roots.as_slice();
231 let barrier_root = dispatch_cx.input_barrier_root;
232 let routing_barrier_root = dispatch_cx.barrier_root;
233
234 let hit_test_layer_roots: &[NodeId] = active_layers;
235 let pointer_chain_snapshot: &UiDispatchSnapshot = &dispatch_cx.input_snapshot;
236
237 let node_in_active_layers = |node: NodeId| dispatch_cx.node_in_active_input_layers(node);
238
239 if dispatch_cx.focus_barrier_root.is_some()
243 && self.focus.is_some()
244 && self
245 .focus
246 .is_some_and(|n| !dispatch_cx.node_in_active_focus_layers(n))
247 {
248 self.set_focus_unchecked(None, "dispatch/window: focus barrier scope");
249 }
250
251 let to_remove: Vec<fret_core::PointerId> = self
252 .captured
253 .iter()
254 .filter_map(|(p, n)| (!node_in_active_layers(*n)).then_some(*p))
255 .collect();
256 for p in to_remove {
257 self.captured.remove(&p);
258 }
259 if self.focus.is_some_and(|n| !self.node_exists(n)) {
260 self.set_focus_unchecked(None, "dispatch/window: missing focus node");
261 }
262 #[cfg(debug_assertions)]
263 debug_focus_note("after pre-dispatch cleanup", self.focus);
264
265 let focus_is_text_input = self.focus_is_text_input(app);
266 self.update_ime_composing_for_event(focus_is_text_input, event);
267 self.set_ime_allowed(app, focus_is_text_input);
268
269 let (input_ctx, input_ctx_elapsed) = fret_perf::measure_span(
270 self.debug_enabled,
271 trace_enabled,
272 || tracing::trace_span!("fret.ui.dispatch.input_context"),
273 || {
274 let is_pointer_move =
275 matches!(event, Event::Pointer(fret_core::PointerEvent::Move { .. }));
276 let input_ctx = self.current_window_input_context(
277 app,
278 barrier_root.is_some(),
279 focus_is_text_input,
280 );
281
282 if is_pointer_move {
283 self.publish_window_input_context_snapshot_untracked(app, &input_ctx, false);
286 } else {
287 self.publish_window_input_context_snapshot(app, &input_ctx);
288 let next_key_contexts =
289 self.shortcut_key_context_stack(app, routing_barrier_root);
290 self.publish_window_key_context_stack_snapshot(app, next_key_contexts);
291 }
292 input_ctx
293 },
294 );
295 if let Some(input_ctx_elapsed) = input_ctx_elapsed {
296 self.debug_stats.dispatch_input_context_time += input_ctx_elapsed;
297 }
298
299 let mut invalidation_visited = HashMap::<NodeId, u8>::new();
300 let mut needs_redraw = false;
301
302 let defer_keydown_shortcuts_until_after_dispatch =
306 self.pending_shortcut.keystrokes.is_empty()
307 && !self.replaying_pending_shortcut
308 && self.focus.is_some()
309 && match event {
310 Event::KeyDown { key, modifiers, .. } => {
311 Self::should_defer_keydown_shortcut_matching_to_text_input(
312 *key,
313 *modifiers,
314 focus_is_text_input,
315 )
316 }
317 _ => false,
318 };
319
320 if let Some(window) = self.window {
321 let pointer_type = match event {
322 Event::Pointer(fret_core::PointerEvent::Move { pointer_type, .. }) => {
323 Some(*pointer_type)
324 }
325 Event::Pointer(fret_core::PointerEvent::Down { pointer_type, .. }) => {
326 Some(*pointer_type)
327 }
328 Event::Pointer(fret_core::PointerEvent::Up { pointer_type, .. }) => {
329 Some(*pointer_type)
330 }
331 Event::PointerCancel(fret_core::PointerCancelEvent { pointer_type, .. }) => {
332 Some(*pointer_type)
333 }
334 _ => None,
335 };
336 if let Some(pointer_type) = pointer_type {
337 app.with_global_mut_untracked(crate::elements::ElementRuntime::new, |rt, _app| {
338 rt.set_window_primary_pointer_type(window, pointer_type);
339 });
340 }
341
342 let changed = crate::focus_visible::update_for_event(app, window, event);
343 if changed {
344 if let Some(focus) = self.focus {
345 self.mark_invalidation_dedup_with_detail(
346 focus,
347 Invalidation::Paint,
348 &mut invalidation_visited,
349 UiDebugInvalidationSource::Other,
350 UiDebugInvalidationDetail::FocusVisiblePolicy,
351 );
352 } else {
353 self.mark_invalidation_dedup_with_detail(
354 base_root,
355 Invalidation::Paint,
356 &mut invalidation_visited,
357 UiDebugInvalidationSource::Other,
358 UiDebugInvalidationDetail::FocusVisiblePolicy,
359 );
360 }
361 self.request_redraw_coalesced(app);
362 }
363
364 let changed = crate::input_modality::update_for_event(app, window, event);
365 if changed {
366 if let Some(focus) = self.focus {
367 self.mark_invalidation_dedup_with_detail(
368 focus,
369 Invalidation::Paint,
370 &mut invalidation_visited,
371 UiDebugInvalidationSource::Other,
372 UiDebugInvalidationDetail::InputModalityPolicy,
373 );
374 } else {
375 self.mark_invalidation_dedup_with_detail(
376 base_root,
377 Invalidation::Paint,
378 &mut invalidation_visited,
379 UiDebugInvalidationSource::Other,
380 UiDebugInvalidationDetail::InputModalityPolicy,
381 );
382 }
383 self.request_redraw_coalesced(app);
384 }
385 }
386
387 self.revalidate_pending_shortcut_for_current_routing_context(app, routing_barrier_root);
388
389 if let Event::Timer { token } = event
390 && !self.replaying_pending_shortcut
391 && !self.pending_shortcut.keystrokes.is_empty()
392 && self.pending_shortcut.timer == Some(*token)
393 {
394 let pending = std::mem::take(&mut self.pending_shortcut);
395 self.sync_pending_shortcut_overlay_state(app, None);
396 if let Some(command) = pending.fallback {
397 app.push_effect(Effect::Command {
398 window: self.window,
399 command,
400 });
401 } else {
402 self.replay_captured_keystrokes(app, services, &input_ctx, pending.keystrokes);
403 }
404 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
405 return;
406 }
407 if let Event::Timer { token } = event {
408 let window = self.window;
409 let frame_id = app.frame_id();
410 let token = *token;
411 let timer_dispatch_cx = {
416 let timer_layer_roots: Vec<NodeId> = self
417 .visible_layers_in_paint_order()
418 .filter_map(|layer_id| self.layers.get(layer_id).map(|l| l.root))
419 .collect();
420 self.build_dispatch_cx(frame_id, timer_layer_roots, None)
421 };
422 let mut timer_target: Option<NodeId> = None;
423 let mut broadcast_rebuild_visible_layers_elapsed: Option<Duration> = None;
424 let mut broadcast_loop_elapsed: Option<Duration> = None;
425 let mut broadcast_layers_visited: u32 = 0;
426 let mut stopped = false;
427 let mut broadcast_attempted = false;
428
429 let ((), timer_elapsed) =
430 fret_perf::measure_span(
431 self.debug_enabled,
432 trace_enabled,
433 || {
434 tracing::trace_span!(
435 "fret.ui.dispatch.timer",
436 window = ?window,
437 frame_id = frame_id.0,
438 token = token.0,
439 )
440 },
441 || {
442 if let Some(window) = window {
443 timer_target = crate::elements::timer_target(app, window, token)
444 .and_then(|target| match target {
445 crate::elements::TimerTarget::Element(element) => self
446 .resolve_live_attached_node_for_element(
447 app,
448 Some(window),
449 element,
450 ),
451 crate::elements::TimerTarget::Node(node) => Some(node),
452 });
453 }
454 if let Some(node) = timer_target {
455 let (targeted_stopped, _) = fret_perf::measure_span(
456 self.debug_enabled,
457 trace_enabled,
458 || {
459 tracing::trace_span!(
460 "fret.ui.dispatch.timer.targeted",
461 window = ?window,
462 frame_id = frame_id.0,
463 token = token.0,
464 node = ?node,
465 )
466 },
467 || {
468 self.dispatch_event_to_node_chain(
469 app,
470 services,
471 &timer_dispatch_cx,
472 &input_ctx,
473 node,
474 event,
475 &mut needs_redraw,
476 &mut invalidation_visited,
477 )
478 },
479 );
480 stopped = targeted_stopped;
481 }
482
483 if !stopped {
484 broadcast_attempted = true;
485 let (layers, rebuild_elapsed) = fret_perf::measure_span(
486 self.debug_enabled,
487 trace_enabled,
488 || {
489 tracing::trace_span!(
490 "fret.ui.dispatch.timer.broadcast.rebuild_visible_layers",
491 window = ?window,
492 frame_id = frame_id.0,
493 token = token.0,
494 )
495 },
496 || {
497 self.visible_layers_in_paint_order()
498 .collect::<Vec<UiLayerId>>()
499 },
500 );
501 broadcast_rebuild_visible_layers_elapsed = rebuild_elapsed;
502
503 let (broadcast_stopped, loop_elapsed) = fret_perf::measure_span(
504 self.debug_enabled,
505 trace_enabled,
506 || {
507 tracing::trace_span!(
508 "fret.ui.dispatch.timer.broadcast.loop",
509 window = ?window,
510 frame_id = frame_id.0,
511 token = token.0,
512 )
513 },
514 || {
515 for layer_id in layers.into_iter().rev() {
516 broadcast_layers_visited =
517 broadcast_layers_visited.saturating_add(1);
518 let Some(layer) = self.layers.get(layer_id) else {
519 continue;
520 };
521 if !layer.wants_timer_events || !layer.visible {
522 continue;
523 }
524 let stopped = self.dispatch_event_to_subtree_bubble(
525 app,
526 services,
527 &timer_dispatch_cx,
528 &input_ctx,
529 layer.root,
530 event,
531 &mut needs_redraw,
532 &mut invalidation_visited,
533 );
534 if stopped {
535 return true;
536 }
537 }
538 false
539 },
540 );
541 broadcast_loop_elapsed = loop_elapsed;
542 stopped = broadcast_stopped;
543 }
544 },
545 );
546
547 if self.debug_enabled {
548 let is_targeted = timer_target.is_some();
549 if is_targeted {
550 self.debug_stats.dispatch_timer_targeted_events = self
551 .debug_stats
552 .dispatch_timer_targeted_events
553 .saturating_add(1);
554 } else {
555 self.debug_stats.dispatch_timer_broadcast_events = self
556 .debug_stats
557 .dispatch_timer_broadcast_events
558 .saturating_add(1);
559 }
560
561 if let Some(timer_elapsed) = timer_elapsed {
562 if is_targeted {
563 self.debug_stats.dispatch_timer_targeted_time += timer_elapsed;
564 } else {
565 self.debug_stats.dispatch_timer_broadcast_time += timer_elapsed;
566 }
567
568 if timer_elapsed > self.debug_stats.dispatch_timer_slowest_event_time {
569 self.debug_stats.dispatch_timer_slowest_event_time = timer_elapsed;
570 self.debug_stats.dispatch_timer_slowest_token = Some(token);
571 self.debug_stats.dispatch_timer_slowest_was_broadcast = !is_targeted;
572 }
573 }
574
575 if broadcast_attempted && timer_target.is_none() {
576 self.debug_stats.dispatch_timer_broadcast_layers_visited = self
577 .debug_stats
578 .dispatch_timer_broadcast_layers_visited
579 .saturating_add(broadcast_layers_visited);
580
581 if let Some(rebuild_elapsed) = broadcast_rebuild_visible_layers_elapsed {
582 self.debug_stats
583 .dispatch_timer_broadcast_rebuild_visible_layers_time +=
584 rebuild_elapsed;
585 }
586 if let Some(loop_elapsed) = broadcast_loop_elapsed {
587 self.debug_stats.dispatch_timer_broadcast_loop_time += loop_elapsed;
588 }
589 }
590 }
591
592 if stopped {
593 if needs_redraw {
594 self.request_redraw_coalesced(app);
595 }
596 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
597 return;
598 }
599 }
600
601 if matches!(
602 event,
603 Event::ClipboardReadText { .. }
604 | Event::ClipboardReadFailed { .. }
605 | Event::ClipboardWriteCompleted { .. }
606 ) {
607 for layer_id in self
608 .visible_layers_in_paint_order()
609 .collect::<Vec<_>>()
610 .into_iter()
611 .rev()
612 {
613 let Some(layer_root) = self
614 .layers
615 .get(layer_id)
616 .and_then(|layer| layer.visible.then_some(layer.root))
617 else {
618 continue;
619 };
620
621 if self.dispatch_event_to_subtree_bubble(
622 app,
623 services,
624 &dispatch_cx,
625 &input_ctx,
626 layer_root,
627 event,
628 &mut needs_redraw,
629 &mut invalidation_visited,
630 ) {
631 break;
632 }
633 }
634 if needs_redraw {
635 self.request_redraw_coalesced(app);
636 }
637 return;
638 }
639
640 if let Event::TextInput(text) = event {
641 if !self.replaying_pending_shortcut
642 && self.pending_shortcut.capture_next_text_input_key.is_some()
643 {
644 self.pending_shortcut.capture_next_text_input_key = None;
645 if let Some(last) = self.pending_shortcut.keystrokes.last_mut() {
646 last.text = Some(text.clone());
647 }
648 self.suppress_text_input_until_key_up = None;
649 return;
650 }
651
652 if self.suppress_text_input_until_key_up.is_some() {
653 self.suppress_text_input_until_key_up = None;
654 return;
655 }
656 }
657
658 if let Event::KeyUp { key, .. } = event {
659 if self.suppress_text_input_until_key_up == Some(*key) {
660 self.suppress_text_input_until_key_up = None;
661 }
662 if self.pending_shortcut.capture_next_text_input_key == Some(*key) {
663 self.pending_shortcut.capture_next_text_input_key = None;
664 }
665 }
666
667 if let Some(window) = self.window
668 && self.handle_alt_menu_bar_activation(app, window, focus_is_text_input, event)
669 {
670 return;
671 }
672
673 let mut cursor_choice: Option<fret_core::CursorIcon> = None;
674 let mut cursor_choice_from_query = false;
675 let mut stop_propagation_requested = false;
676 let mut stop_propagation_requested_by: Option<NodeId> = None;
677 let mut pointer_down_outside = PointerDownOutsideOutcome::default();
678 let mut suppress_touch_up_outside_dispatch = false;
679 let mut suppress_pointer_dispatch = false;
680 let is_scroll_like = Self::event_is_scroll_like(event);
681 let mut wheel_stop_node: Option<NodeId> = None;
682 let mut synth_pointer_move_prev_target: Option<NodeId> = None;
683 let mut prevented_default_actions = fret_runtime::DefaultActionSet::default();
684 let event_window_position = event_position(event);
685 let event_window_wheel_delta = match event {
686 Event::Pointer(PointerEvent::Wheel { delta, .. }) => Some(*delta),
687 _ => None,
688 };
689 let mut focus_requested = false;
690 let mut defer_escape_overlay_dismiss = false;
691
692 if let Event::KeyDown {
693 key: fret_core::KeyCode::Escape,
694 repeat: false,
695 ..
696 } = event
697 && let Some(window) = self.window
698 && {
699 let dock_drag_affects_window = app.any_drag_session(|d| {
700 d.kind == fret_runtime::DRAG_KIND_DOCK_PANEL
701 && (d.source_window == window || d.current_window == window)
702 });
703 if dock_drag_affects_window {
704 let canceled = app.cancel_drag_sessions(|d| {
707 d.kind == fret_runtime::DRAG_KIND_DOCK_PANEL
708 && (d.source_window == window || d.current_window == window)
709 });
710 for pointer_id in canceled {
711 self.captured.remove(&pointer_id);
712 }
713 true
714 } else {
715 defer_escape_overlay_dismiss = true;
716 false
717 }
718 }
719 {
720 self.request_redraw_coalesced(app);
721 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
722 return;
723 }
724
725 if let Event::KeyDown {
726 key,
727 modifiers,
728 repeat,
729 } = event
730 && !defer_keydown_shortcuts_until_after_dispatch
731 && self.handle_keydown_shortcuts(
732 app,
733 services,
734 KeydownShortcutParams {
735 input_ctx: &input_ctx,
736 barrier_root: routing_barrier_root,
737 focus_is_text_input,
738 #[cfg(feature = "diagnostics")]
739 phase: fret_runtime::ShortcutRoutingPhase::PreDispatch,
740 #[cfg(feature = "diagnostics")]
741 deferred: false,
742 key: *key,
743 modifiers: *modifiers,
744 repeat: *repeat,
745 },
746 )
747 {
748 return;
749 }
750
751 let default_root = barrier_root.unwrap_or(base_root);
752
753 let event_pointer_id_for_capture: Option<fret_core::PointerId> = match event {
756 Event::Pointer(PointerEvent::Move { pointer_id, .. })
757 | Event::Pointer(PointerEvent::Down { pointer_id, .. })
758 | Event::Pointer(PointerEvent::Up { pointer_id, .. })
759 | Event::Pointer(PointerEvent::Wheel { pointer_id, .. })
760 | Event::Pointer(PointerEvent::PinchGesture { pointer_id, .. }) => Some(*pointer_id),
761 Event::PointerCancel(e) => Some(e.pointer_id),
762 _ => None,
763 };
764
765 let captured = event_pointer_id_for_capture.and_then(|p| self.captured.get(&p).copied());
766 if let Event::Pointer(PointerEvent::Move {
767 pointer_id,
768 position,
769 pointer_type: fret_core::PointerType::Touch,
770 ..
771 }) = event
772 {
773 self.update_touch_pointer_down_outside_move(*pointer_id, *position);
774 }
775 let (dock_drag_affects_window, dock_drag_capture_anchor) = self
776 .window
777 .map(|window| {
778 let affects = app.any_drag_session(|d| {
779 d.kind == fret_runtime::DRAG_KIND_DOCK_PANEL
780 && (d.source_window == window || d.current_window == window)
781 });
782 let anchor =
783 crate::internal_drag::route(&*app, window, fret_runtime::DRAG_KIND_DOCK_PANEL);
784 (affects, anchor)
785 })
786 .unwrap_or((false, None));
787
788 let internal_drag_target = (|| {
791 let Event::InternalDrag(e) = event else {
792 return None;
793 };
794 let window = self.window?;
795 let drag = app.drag(e.pointer_id)?;
796 if !drag.cross_window_hover {
797 return None;
798 }
799 let target = crate::internal_drag::route(app, window, drag.kind)?;
800 self.nodes.get(target).is_some().then_some(target)
807 })();
808 if std::env::var_os("FRET_INTERNAL_DRAG_ROUTE_TRACE").is_some_and(|v| !v.is_empty())
809 && let Some(window) = self.window
810 && let Event::InternalDrag(e) = event
811 && matches!(e.kind, fret_core::InternalDragKind::Drop)
812 {
813 let (drag_kind, cross_window_hover, route, route_in_active_layer) =
814 if let Some(drag) = app.drag(e.pointer_id) {
815 let route = crate::internal_drag::route(app, window, drag.kind);
816 let route_in_active_layer = route.is_some_and(&node_in_active_layers);
817 (
818 Some(drag.kind),
819 drag.cross_window_hover,
820 route,
821 route_in_active_layer,
822 )
823 } else {
824 (None, false, None, false)
825 };
826 tracing::info!(
827 window = ?window,
828 pointer_id = ?e.pointer_id,
829 kind = ?e.kind,
830 position = ?e.position,
831 modifiers = ?e.modifiers,
832 drag_kind = ?drag_kind,
833 cross_window_hover = cross_window_hover,
834 route = ?route,
835 route_in_active_layer = route_in_active_layer,
836 internal_drag_target = ?internal_drag_target,
837 last_internal_drag_target = ?self.last_internal_drag_target,
838 "internal drag route trace"
839 );
840 }
841
842 if let Some(window) = self.window
843 && matches!(event, Event::Pointer(_))
844 && let Some(pos) = event_position(event)
845 {
846 let hit = if matches!(event, Event::Pointer(PointerEvent::Move { .. })) {
853 self.hit_test_layers_cached(hit_test_layer_roots, pos)
854 } else {
855 self.hit_test_path_cache = None;
856 self.hit_test_layers_cached(hit_test_layer_roots, pos)
857 };
858
859 if let Event::Pointer(PointerEvent::Up {
860 pointer_id,
861 pointer_type: fret_core::PointerType::Touch,
862 ..
863 }) = event
864 && captured.is_none()
865 {
866 if dock_drag_affects_window {
867 self.touch_pointer_down_outside_candidates
868 .remove(pointer_id);
869 } else if let Some(candidate) = self
870 .touch_pointer_down_outside_candidates
871 .remove(pointer_id)
872 && let Some(layer) = self.layers.get(candidate.layer_id)
873 {
874 let foreign_capture_active = self.captured.iter().any(|(pid, node)| {
875 *pid != *pointer_id
876 && self
877 .node_layer(*node)
878 .is_some_and(|layer_id| layer_id != candidate.layer_id)
879 });
880
881 if !foreign_capture_active && !candidate.moved {
882 let active_pointer_down_outside_layers =
883 self.active_pointer_down_outside_layer_roots(barrier_root);
884 let snapshot = self.build_dispatch_snapshot_for_layer_roots(
885 app.frame_id(),
886 active_pointer_down_outside_layers.as_slice(),
887 barrier_root,
888 );
889
890 let hit_is_inside_layer = hit.is_some_and(|hit| {
891 if snapshot.pre.get(layer.root).is_some()
892 && snapshot.pre.get(hit).is_some()
893 {
894 snapshot.is_descendant(layer.root, hit)
895 } else {
896 self.is_reachable_from_root_via_children(layer.root, hit)
897 }
898 });
899 let hit_is_inside_branch = hit.is_some_and(|hit| {
900 layer
901 .pointer_down_outside_branches
902 .iter()
903 .copied()
904 .any(|branch| {
905 if snapshot.pre.get(branch).is_some()
906 && snapshot.pre.get(hit).is_some()
907 {
908 snapshot.is_descendant(branch, hit)
909 } else {
910 self.is_reachable_from_root_via_children(branch, hit)
911 }
912 })
913 });
914
915 if !hit_is_inside_layer && !hit_is_inside_branch {
916 let (window, root_element, tick_id) = if let Some(window) = self.window
917 && let Some(root_element) =
918 self.nodes.get(candidate.root).and_then(|n| n.element)
919 {
920 let tick_id = app.tick_id();
921 crate::elements::with_element_state(
922 app,
923 window,
924 root_element,
925 crate::action::DismissibleLastDismissRequest::default,
926 |st| {
927 st.tick_id = tick_id;
928 st.reason = None;
929 st.default_prevented = false;
930 },
931 );
932 (Some(window), Some(root_element), Some(tick_id))
933 } else {
934 (None, None, None)
935 };
936 self.dispatch_event_to_node_chain_observer(
937 app,
938 services,
939 &input_ctx,
940 candidate.root,
941 &candidate.down_event,
942 Some(&snapshot),
943 &mut invalidation_visited,
944 );
945 let mut clear_focus = true;
946 if let (Some(window), Some(root_element), Some(tick_id)) =
947 (window, root_element, tick_id)
948 {
949 let prevented = crate::elements::with_element_state(
950 app,
951 window,
952 root_element,
953 crate::action::DismissibleLastDismissRequest::default,
954 |st| {
955 st.tick_id == tick_id
956 && matches!(
957 st.reason,
958 Some(
959 crate::action::DismissReason::OutsidePress { .. }
960 )
961 )
962 && st.default_prevented
963 },
964 );
965 if prevented {
966 clear_focus = false;
967 }
968 }
969 if clear_focus {
970 self.set_focus(None);
971 }
972 needs_redraw = true;
973 suppress_touch_up_outside_dispatch = candidate.consume;
974 }
975 }
976 }
977 }
978
979 let mut hit_for_hover = hit;
986 let mut hit_for_hover_region = hit;
987 let mut hit_for_raw_below_barrier: Option<NodeId> = None;
988 if captured.is_none()
989 && let Some((occlusion_layer, occlusion)) =
990 self.topmost_pointer_occlusion_layer(barrier_root)
991 && occlusion != PointerOcclusion::None
992 {
993 let occlusion_z = self
994 .layer_order
995 .iter()
996 .position(|id| *id == occlusion_layer);
997 let hit_layer_z = hit
998 .and_then(|hit| self.node_layer(hit))
999 .and_then(|layer| self.layer_order.iter().position(|id| *id == layer));
1000
1001 let hit_is_below_occlusion = match (occlusion_z, hit_layer_z, hit) {
1002 (Some(oz), Some(hz), Some(_)) => hz < oz,
1003 (Some(_), None, Some(_)) => true,
1004 (Some(_), _, None) => true,
1005 _ => false,
1006 };
1007
1008 if hit_is_below_occlusion {
1009 hit_for_raw_below_barrier = hit;
1010 hit_for_hover = None;
1013 hit_for_hover_region = None;
1014
1015 let blocks_pointer_dispatch = match occlusion {
1016 PointerOcclusion::None => false,
1017 PointerOcclusion::BlockMouse => true,
1018 PointerOcclusion::BlockMouseExceptScroll => !is_scroll_like,
1019 };
1020 if blocks_pointer_dispatch {
1021 suppress_pointer_dispatch = true;
1022 }
1023 }
1024 }
1025
1026 if input_ctx.caps.ui.cursor_icons
1027 && cursor_choice.is_none()
1028 && matches!(event, Event::Pointer(PointerEvent::Move { .. }))
1029 {
1030 let (_, elapsed) = fret_perf::measure_span(
1031 self.debug_enabled,
1032 trace_enabled,
1033 || tracing::trace_span!("fret.ui.dispatch.cursor_query"),
1034 || {
1035 if let Some(start) = captured.or(hit_for_hover) {
1036 cursor_choice = self.cursor_icon_query_for_pointer_hit(
1037 start,
1038 &input_ctx,
1039 event,
1040 Some(pointer_chain_snapshot),
1041 );
1042 cursor_choice_from_query = cursor_choice.is_some();
1043 }
1044 },
1045 );
1046 if let Some(elapsed) = elapsed {
1047 self.debug_stats.dispatch_cursor_query_time += elapsed;
1048 }
1049 }
1050
1051 if matches!(event, Event::Pointer(PointerEvent::Down { .. })) && captured.is_none() {
1052 if dock_drag_affects_window {
1053 pointer_down_outside = PointerDownOutsideOutcome::default();
1064 } else {
1065 let active_pointer_down_outside_layers =
1066 self.active_pointer_down_outside_layer_roots(barrier_root);
1067 pointer_down_outside = self.dispatch_pointer_down_outside(
1068 app,
1069 services,
1070 PointerDownOutsideParams {
1071 input_ctx: &input_ctx,
1072 active_layer_roots: &active_pointer_down_outside_layers,
1073 barrier_root,
1074 base_root,
1075 hit,
1076 event,
1077 },
1078 &mut invalidation_visited,
1079 );
1080 if pointer_down_outside.dispatched {
1081 needs_redraw = true;
1082 }
1083 }
1084 }
1085
1086 let hover_capable = match event {
1087 Event::Pointer(PointerEvent::Move { pointer_type, .. })
1088 | Event::Pointer(PointerEvent::Down { pointer_type, .. })
1089 | Event::Pointer(PointerEvent::Up { pointer_type, .. })
1090 | Event::Pointer(PointerEvent::Wheel { pointer_type, .. })
1091 | Event::Pointer(PointerEvent::PinchGesture { pointer_type, .. }) => {
1092 pointer_type_supports_hover(*pointer_type)
1093 }
1094 _ => false,
1095 };
1096
1097 if hover_capable {
1098 let position = event_position(event);
1099 self.update_hover_state_from_hit(
1100 app,
1101 window,
1102 barrier_root,
1103 position,
1104 hit_for_hover,
1105 hit_for_hover_region,
1106 hit_for_raw_below_barrier,
1107 Some(pointer_chain_snapshot),
1108 &mut invalidation_visited,
1109 &mut needs_redraw,
1110 );
1111 }
1112 }
1113
1114 let mut pointer_hit: Option<NodeId> = None;
1115 let locked_touch_drag_target = match event {
1116 Event::Pointer(PointerEvent::Move {
1117 pointer_id,
1118 buttons,
1119 pointer_type: fret_core::PointerType::Touch,
1120 ..
1121 }) if buttons.left || buttons.right || buttons.middle => self
1122 .active_touch_drag_target
1123 .get(pointer_id)
1124 .copied()
1125 .and_then(|element| {
1126 self.resolve_live_attached_node_for_element(app, self.window, element)
1127 })
1128 .filter(|node| node_in_active_layers(*node)),
1129 _ => None,
1130 };
1131 let touch_drag_target_override = match (captured, locked_touch_drag_target, event) {
1132 (
1133 Some(captured_node),
1134 Some(locked_node),
1135 Event::Pointer(PointerEvent::Move {
1136 buttons,
1137 pointer_type: fret_core::PointerType::Touch,
1138 ..
1139 }),
1140 ) if buttons.left || buttons.right || buttons.middle => {
1141 let captured_is_pressable = self
1142 .window
1143 .and_then(|window| {
1144 crate::declarative::element_record_for_node(app, window, captured_node)
1145 })
1146 .is_some_and(|record| {
1147 matches!(
1148 record.instance,
1149 crate::declarative::ElementInstance::Pressable(_)
1150 )
1151 });
1152
1153 if captured_is_pressable && captured_node != locked_node {
1154 Some(locked_node)
1155 } else {
1156 None
1157 }
1158 }
1159 _ => None,
1160 };
1161 let touch_drag_reroute_from_pressable_capture = touch_drag_target_override.is_some();
1162 let target = if let Some(target) = touch_drag_target_override {
1163 Some(target)
1164 } else if let Some(captured) = captured {
1165 Some(captured)
1166 } else if let Some(target) = internal_drag_target {
1167 Some(target)
1168 } else if let Some(pos) = event_position(event) {
1169 let hit = if matches!(event, Event::Pointer(PointerEvent::Move { .. })) {
1171 self.hit_test_layers_cached(hit_test_layer_roots, pos)
1172 } else {
1173 self.hit_test_path_cache = None;
1174 self.hit_test_layers_cached(hit_test_layer_roots, pos)
1175 };
1176
1177 let hit = if matches!(event, Event::InternalDrag(_)) {
1178 (|| {
1179 let window = self.window?;
1180 crate::declarative::with_window_frame(app, window, |window_frame| {
1181 let window_frame = window_frame?;
1182 let mut node = hit?;
1183 loop {
1184 if let Some(record) = window_frame.instances.get(node)
1185 && matches!(
1186 record.instance,
1187 crate::declarative::ElementInstance::InternalDragRegion(p)
1188 if p.enabled
1189 )
1190 {
1191 return Some(node);
1192 }
1193 node = pointer_chain_snapshot.parent.get(node).copied().flatten()?;
1194 }
1195 })
1196 })()
1197 .or(hit)
1198 } else {
1199 hit
1200 };
1201 pointer_hit = hit;
1202
1203 if let Event::Pointer(PointerEvent::Down {
1204 pointer_id,
1205 pointer_type: fret_core::PointerType::Touch,
1206 ..
1207 }) = event
1208 {
1209 let element =
1210 hit.filter(|node| node_in_active_layers(*node))
1211 .and_then(|mut node| {
1212 let window = self.window?;
1213 loop {
1214 let record =
1215 crate::declarative::element_record_for_node(app, window, node);
1216 if let Some(record) = record
1217 && matches!(
1218 record.instance,
1219 crate::declarative::ElementInstance::Scroll(_)
1220 | crate::declarative::ElementInstance::VirtualList(_)
1221 )
1222 {
1223 return Some(record.element);
1224 }
1225 node = self.nodes.get(node).and_then(|n| n.parent)?;
1226 }
1227 });
1228 match element {
1229 Some(element) => {
1230 self.active_touch_drag_target.insert(*pointer_id, element);
1231 }
1232 None => {
1233 self.active_touch_drag_target.remove(pointer_id);
1234 }
1235 }
1236 }
1237
1238 if let Event::Pointer(PointerEvent::Move {
1239 buttons,
1240 pointer_id,
1241 ..
1242 }) = event
1243 && !buttons.left
1244 && !buttons.right
1245 && !buttons.middle
1246 {
1247 let mut last_pointer_move_hit = self
1251 .last_pointer_move_hit
1252 .get(pointer_id)
1253 .copied()
1254 .flatten();
1255 if barrier_root.is_some()
1256 && last_pointer_move_hit.is_some_and(|n| !node_in_active_layers(n))
1257 {
1258 self.last_pointer_move_hit.remove(pointer_id);
1259 last_pointer_move_hit = None;
1260 }
1261
1262 if hit != last_pointer_move_hit {
1263 synth_pointer_move_prev_target = last_pointer_move_hit;
1264 match hit {
1265 Some(hit) => {
1266 self.last_pointer_move_hit.insert(*pointer_id, Some(hit));
1267 }
1268 None => {
1269 self.last_pointer_move_hit.remove(pointer_id);
1270 }
1271 }
1272 }
1273 }
1274
1275 if matches!(event, Event::InternalDrag(_)) {
1276 if let Some(node) = hit {
1277 self.last_internal_drag_target = Some(node);
1278 } else if self
1279 .last_internal_drag_target
1280 .is_some_and(|n| !node_in_active_layers(n))
1281 {
1282 self.last_internal_drag_target = None;
1283 }
1284 }
1285
1286 locked_touch_drag_target
1287 .or(hit)
1288 .or_else(|| {
1289 matches!(event, Event::InternalDrag(_))
1290 .then_some(self.last_internal_drag_target)?
1291 })
1292 .or(barrier_root)
1293 .or(Some(default_root))
1294 } else {
1295 match event {
1296 Event::SetTextSelection { .. } => {
1297 let selection_node = self.window.and_then(|window| {
1298 crate::elements::with_window_state(app, window, |window_state| {
1299 window_state
1300 .active_text_selection()
1301 .map(|selection| selection.node)
1302 })
1303 });
1304 selection_node.or(self.focus).or(Some(default_root))
1305 }
1306 _ => self.focus.or(Some(default_root)),
1307 }
1308 };
1309
1310 let Some(mut node_id) = target else {
1311 return;
1312 };
1313
1314 match event {
1315 Event::Pointer(PointerEvent::Up {
1316 pointer_id,
1317 pointer_type: fret_core::PointerType::Touch,
1318 ..
1319 })
1320 | Event::PointerCancel(fret_core::PointerCancelEvent {
1321 pointer_id,
1322 pointer_type: fret_core::PointerType::Touch,
1323 ..
1324 }) => {
1325 self.active_touch_drag_target.remove(pointer_id);
1326 }
1327 _ => {}
1328 }
1329
1330 if matches!(event, Event::Pointer(PointerEvent::Down { .. }))
1331 && pointer_down_outside.suppress_hit_test_dispatch
1332 {
1333 if needs_redraw {
1334 self.request_redraw_coalesced(app);
1335 }
1336 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
1337 return;
1338 }
1339
1340 if matches!(event, Event::Pointer(PointerEvent::Up { .. }))
1341 && suppress_touch_up_outside_dispatch
1342 {
1343 if needs_redraw {
1344 self.request_redraw_coalesced(app);
1345 }
1346 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
1347 return;
1348 }
1349
1350 if suppress_pointer_dispatch && matches!(event, Event::Pointer(_)) {
1351 if matches!(event, Event::Pointer(PointerEvent::Move { .. })) {
1352 let (_, elapsed) = fret_perf::measure_span(
1353 self.debug_enabled,
1354 trace_enabled,
1355 || tracing::trace_span!("fret.ui.dispatch.pointer_move_layer_observers"),
1356 || {
1357 self.dispatch_pointer_move_layer_observers(
1358 app,
1359 services,
1360 &input_ctx,
1361 barrier_root,
1362 event,
1363 &mut needs_redraw,
1364 &mut invalidation_visited,
1365 );
1366 },
1367 );
1368 if let Some(elapsed) = elapsed {
1369 self.debug_stats.dispatch_pointer_move_layer_observers_time += elapsed;
1370 }
1371 }
1372 if needs_redraw {
1373 self.request_redraw_coalesced(app);
1374 }
1375 self.publish_post_dispatch_runtime_snapshots_for_event(app, event);
1376 return;
1377 }
1378
1379 if cursor_choice.is_none()
1380 && input_ctx.caps.ui.cursor_icons
1381 && matches!(event, Event::Pointer(_))
1382 && let Some(hit) = pointer_hit
1383 {
1384 cursor_choice = self.cursor_icon_query_for_pointer_hit(
1385 hit,
1386 &input_ctx,
1387 event,
1388 Some(pointer_chain_snapshot),
1389 );
1390 cursor_choice_from_query = cursor_choice.is_some();
1391 }
1392
1393 if !suppress_pointer_dispatch
1394 && matches!(
1395 event,
1396 Event::Pointer(_)
1397 | Event::PointerCancel(_)
1398 | Event::ExternalDrag(_)
1399 | Event::InternalDrag(_)
1400 )
1401 {
1402 let (chain, chain_elapsed) = fret_perf::measure_span(
1403 self.debug_enabled,
1404 trace_enabled,
1405 || tracing::trace_span!("fret.ui.dispatch.event_chain_build"),
1406 || {
1407 if event_position(event).is_some() {
1408 self.build_mapped_event_chain(node_id, event, Some(pointer_chain_snapshot))
1409 } else {
1410 self.build_unmapped_event_chain(
1411 node_id,
1412 event,
1413 Some(&dispatch_cx.focus_snapshot),
1414 )
1415 }
1416 },
1417 );
1418 if let Some(chain_elapsed) = chain_elapsed {
1419 self.debug_stats.dispatch_event_chain_build_time += chain_elapsed;
1420 }
1421 let pointer_hit_is_text_input =
1422 if matches!(event, Event::Pointer(PointerEvent::Down { .. }))
1423 && let Some(window) = self.window
1424 {
1425 chain.iter().any(|(node_id, _)| {
1426 crate::declarative::element_record_for_node(app, window, *node_id)
1427 .is_some_and(|record| {
1428 matches!(
1429 &record.instance,
1430 crate::declarative::ElementInstance::TextInput(_)
1431 | crate::declarative::ElementInstance::TextArea(_)
1432 | crate::declarative::ElementInstance::TextInputRegion(_)
1433 )
1434 })
1435 })
1436 } else {
1437 false
1438 };
1439 let pointer_hit_is_pressable =
1440 if matches!(event, Event::Pointer(PointerEvent::Down { .. }))
1441 && let Some(window) = self.window
1442 {
1443 chain.iter().any(|(node_id, _)| {
1444 crate::declarative::element_record_for_node(app, window, *node_id)
1445 .is_some_and(|record| {
1446 matches!(
1447 &record.instance,
1448 crate::declarative::ElementInstance::Pressable(_)
1449 )
1450 })
1451 })
1452 } else {
1453 false
1454 };
1455 let pointer_hit_pressable =
1456 if matches!(event, Event::Pointer(PointerEvent::Down { .. }))
1457 && let Some(window) = self.window
1458 {
1459 chain
1460 .iter()
1461 .enumerate()
1462 .find_map(|(chain_index, (node_id, _))| {
1463 crate::declarative::element_record_for_node(app, window, *node_id)
1464 .and_then(|record| {
1465 matches!(
1466 &record.instance,
1467 crate::declarative::ElementInstance::Pressable(_)
1468 )
1469 .then_some((record.element, chain_index))
1470 })
1471 })
1472 } else {
1473 None
1474 };
1475 let should_run_capture_phase = match event {
1476 Event::Pointer(PointerEvent::Down { .. })
1477 | Event::Pointer(PointerEvent::Up { .. })
1478 | Event::Pointer(PointerEvent::Wheel { .. })
1479 | Event::Pointer(PointerEvent::PinchGesture { .. })
1480 | Event::PointerCancel(..) => true,
1481 Event::Pointer(PointerEvent::Move { buttons, .. }) => {
1482 !touch_drag_reroute_from_pressable_capture
1483 && (captured.is_some() || buttons.left || buttons.right || buttons.middle)
1484 }
1485 _ => false,
1486 };
1487 let mut stopped_in_capture = false;
1488 if should_run_capture_phase {
1489 let mut capture_ctx = input_ctx.clone();
1490 capture_ctx.dispatch_phase = InputDispatchPhase::Capture;
1491
1492 let (_, capture_elapsed) = fret_perf::measure_span(
1493 self.debug_enabled,
1494 trace_enabled,
1495 || tracing::trace_span!("fret.ui.dispatch.widget_capture"),
1496 || {
1497 for (chain_index, (node_id, event_for_node)) in
1498 chain.iter().enumerate().rev()
1499 {
1500 let node_id = *node_id;
1501 let (
1502 pointer_hit_pressable_target,
1503 pointer_hit_pressable_target_in_descendant_subtree,
1504 ) = pointer_hit_pressable.map_or(
1505 (None, false),
1506 |(target, pressable_index)| {
1507 (Some(target), pressable_index < chain_index)
1508 },
1509 );
1510 let (
1511 invalidations,
1512 scroll_handle_invalidations,
1513 scroll_target_invalidations,
1514 requested_focus,
1515 requested_focus_target,
1516 requested_capture,
1517 requested_cursor,
1518 notify_requested,
1519 notify_requested_location,
1520 stop_propagation,
1521 ) = self.with_widget_mut(node_id, |widget, tree| {
1522 let (children, bounds) = tree
1523 .nodes
1524 .get(node_id)
1525 .map(|n| (n.children.as_slice(), n.bounds))
1526 .unwrap_or((&[][..], Rect::default()));
1527 let mut cx = EventCx {
1528 app,
1529 services: &mut *services,
1530 node: node_id,
1531 layer_root: tree.node_root(node_id),
1532 window: tree.window,
1533 pointer_id: event_pointer_id_for_capture,
1534 scale_factor: tree.last_layout_scale_factor.unwrap_or(1.0),
1535 event_window_position,
1536 event_window_wheel_delta,
1537 input_ctx: capture_ctx.clone(),
1538 pointer_hit_is_text_input,
1539 pointer_hit_is_pressable,
1540 pointer_hit_pressable_target,
1541 pointer_hit_pressable_target_in_descendant_subtree,
1542 prevented_default_actions: &mut prevented_default_actions,
1543 children,
1544 focus: tree.focus,
1545 captured: event_pointer_id_for_capture
1546 .and_then(|p| tree.captured.get(&p).copied()),
1547 bounds,
1548 invalidations: Vec::new(),
1549 scroll_handle_invalidations: Vec::new(),
1550 scroll_target_invalidations: Vec::new(),
1551 requested_focus: None,
1552 requested_focus_target: None,
1553 requested_capture: None,
1554 requested_cursor: None,
1555 notify_requested: false,
1556 notify_requested_location: None,
1557 stop_propagation: false,
1558 };
1559 widget.event_capture(&mut cx, event_for_node);
1560 (
1561 cx.invalidations,
1562 cx.scroll_handle_invalidations,
1563 cx.scroll_target_invalidations,
1564 cx.requested_focus,
1565 cx.requested_focus_target,
1566 cx.requested_capture,
1567 cx.requested_cursor,
1568 cx.notify_requested,
1569 cx.notify_requested_location,
1570 cx.stop_propagation,
1571 )
1572 });
1573
1574 if !invalidations.is_empty()
1575 || !scroll_handle_invalidations.is_empty()
1576 || !scroll_target_invalidations.is_empty()
1577 || requested_focus.is_some()
1578 || requested_focus_target.is_some()
1579 || requested_capture.is_some()
1580 || notify_requested
1581 {
1582 needs_redraw = true;
1583 }
1584
1585 for (id, inv) in invalidations {
1586 self.mark_invalidation(id, inv);
1587 }
1588 let mut resolved_scroll_handle_invalidations = Vec::new();
1589 self.extend_live_bound_scroll_handle_invalidations(
1590 app,
1591 &scroll_handle_invalidations,
1592 &mut resolved_scroll_handle_invalidations,
1593 );
1594 for (id, inv) in resolved_scroll_handle_invalidations {
1595 self.mark_invalidation(id, inv);
1596 }
1597 let mut resolved_scroll_target_invalidations = Vec::new();
1598 self.extend_live_scroll_target_invalidations(
1599 app,
1600 &scroll_target_invalidations,
1601 &mut resolved_scroll_target_invalidations,
1602 );
1603 for (id, inv) in resolved_scroll_target_invalidations {
1604 self.mark_invalidation(id, inv);
1605 }
1606 if notify_requested {
1607 self.debug_record_notify_request(
1608 app.frame_id(),
1609 node_id,
1610 notify_requested_location,
1611 );
1612 self.mark_invalidation_with_source(
1613 node_id,
1614 Invalidation::Paint,
1615 UiDebugInvalidationSource::Notify,
1616 );
1617 }
1618
1619 let focus_requested_now =
1620 requested_focus.is_some() || requested_focus_target.is_some();
1621 if let Some(focus) = self.resolve_requested_focus(
1622 app,
1623 requested_focus,
1624 requested_focus_target,
1625 ) && self.focus_request_is_allowed(
1626 app,
1627 self.window,
1628 dispatch_cx.active_focus_roots.as_slice(),
1629 focus,
1630 Some(&dispatch_cx.focus_snapshot),
1631 ) {
1632 focus_requested = true;
1633 if let Some(prev) = self.focus {
1634 self.mark_invalidation(prev, Invalidation::Paint);
1635 }
1636 self.focus = Some(focus);
1637 self.mark_invalidation(focus, Invalidation::Paint);
1638 if !matches!(event, Event::Pointer(_) | Event::PointerCancel(_)) {
1644 self.scroll_node_into_view(app, focus);
1645 }
1646 } else if focus_requested_now {
1647 focus_requested = true;
1648 }
1649
1650 if let Some(capture) = requested_capture
1651 && let Some(pointer_id) = event_pointer_id_for_capture
1652 {
1653 match capture {
1654 Some(node) => {
1655 let allow = !dock_drag_affects_window
1656 || dock_drag_capture_anchor == Some(node);
1657 if allow {
1658 if !matches!(event, Event::PointerCancel(_))
1659 && let Some(old_capture) =
1660 self.captured.get(&pointer_id).copied()
1661 && old_capture != node
1662 && node_in_active_layers(old_capture)
1663 {
1664 let mut cancel_ctx = input_ctx.clone();
1665 cancel_ctx.dispatch_phase =
1666 InputDispatchPhase::Bubble;
1667 let cancel_event =
1668 pointer_cancel_event_for_capture_switch(
1669 event, pointer_id,
1670 );
1671 let _ = self.dispatch_event_to_node_chain(
1672 app,
1673 services,
1674 &dispatch_cx,
1675 &cancel_ctx,
1676 old_capture,
1677 &cancel_event,
1678 &mut needs_redraw,
1679 &mut invalidation_visited,
1680 );
1681 }
1682 self.captured.insert(pointer_id, node);
1683 }
1684 }
1685 None => {
1686 self.captured.remove(&pointer_id);
1687 }
1688 }
1689 }
1690
1691 if let Some(requested_cursor) = requested_cursor
1692 && (cursor_choice.is_none() || cursor_choice_from_query)
1693 {
1694 cursor_choice = Some(requested_cursor);
1695 cursor_choice_from_query = false;
1696 }
1697
1698 if stop_propagation {
1699 stop_propagation_requested = true;
1700 if stop_propagation_requested_by.is_none() {
1701 stop_propagation_requested_by = Some(node_id);
1702 }
1703 if is_wheel && wheel_stop_node.is_none() {
1704 wheel_stop_node = Some(node_id);
1705 }
1706 stopped_in_capture = true;
1707 break;
1708 }
1709 }
1710 },
1711 );
1712 if let Some(capture_elapsed) = capture_elapsed {
1713 self.debug_stats.dispatch_widget_capture_time += capture_elapsed;
1714 }
1715 }
1716
1717 if !stopped_in_capture {
1718 let mut bubble_ctx = input_ctx.clone();
1719 bubble_ctx.dispatch_phase = InputDispatchPhase::Bubble;
1720
1721 let (_, bubble_elapsed) = fret_perf::measure_span(
1722 self.debug_enabled,
1723 trace_enabled,
1724 || tracing::trace_span!("fret.ui.dispatch.widget_bubble"),
1725 || {
1726 for (chain_index, (node_id, event_for_node)) in chain.iter().enumerate() {
1727 let node_id = *node_id;
1728 let (
1729 pointer_hit_pressable_target,
1730 pointer_hit_pressable_target_in_descendant_subtree,
1731 ) = pointer_hit_pressable.map_or(
1732 (None, false),
1733 |(target, pressable_index)| {
1734 (Some(target), pressable_index < chain_index)
1735 },
1736 );
1737 let (
1738 invalidations,
1739 scroll_handle_invalidations,
1740 scroll_target_invalidations,
1741 requested_focus,
1742 requested_focus_target,
1743 requested_capture,
1744 requested_cursor,
1745 notify_requested,
1746 notify_requested_location,
1747 stop_propagation,
1748 ) = self.with_widget_mut(node_id, |widget, tree| {
1749 let (children, bounds) = tree
1750 .nodes
1751 .get(node_id)
1752 .map(|n| (n.children.as_slice(), n.bounds))
1753 .unwrap_or((&[][..], Rect::default()));
1754 let mut cx = EventCx {
1755 app,
1756 services: &mut *services,
1757 node: node_id,
1758 layer_root: tree.node_root(node_id),
1759 window: tree.window,
1760 pointer_id: event_pointer_id_for_capture,
1761 scale_factor: tree.last_layout_scale_factor.unwrap_or(1.0),
1762 event_window_position,
1763 event_window_wheel_delta,
1764 input_ctx: bubble_ctx.clone(),
1765 pointer_hit_is_text_input,
1766 pointer_hit_is_pressable,
1767 pointer_hit_pressable_target,
1768 pointer_hit_pressable_target_in_descendant_subtree,
1769 prevented_default_actions: &mut prevented_default_actions,
1770 children,
1771 focus: tree.focus,
1772 captured: event_pointer_id_for_capture
1773 .and_then(|p| tree.captured.get(&p).copied()),
1774 bounds,
1775 invalidations: Vec::new(),
1776 scroll_handle_invalidations: Vec::new(),
1777 scroll_target_invalidations: Vec::new(),
1778 requested_focus: None,
1779 requested_focus_target: None,
1780 requested_capture: None,
1781 requested_cursor: None,
1782 notify_requested: false,
1783 notify_requested_location: None,
1784 stop_propagation: false,
1785 };
1786 widget.event(&mut cx, event_for_node);
1787 if cx.requested_cursor.is_none()
1788 && matches!(event_for_node, Event::Pointer(_))
1789 && cx.input_ctx.caps.ui.cursor_icons
1790 && let Some(position) = event_position(event_for_node)
1791 {
1792 cx.requested_cursor =
1793 widget.cursor_icon_at(bounds, position, &cx.input_ctx);
1794 }
1795 (
1796 cx.invalidations,
1797 cx.scroll_handle_invalidations,
1798 cx.scroll_target_invalidations,
1799 cx.requested_focus,
1800 cx.requested_focus_target,
1801 cx.requested_capture,
1802 cx.requested_cursor,
1803 cx.notify_requested,
1804 cx.notify_requested_location,
1805 cx.stop_propagation,
1806 )
1807 });
1808
1809 if !invalidations.is_empty()
1810 || !scroll_handle_invalidations.is_empty()
1811 || !scroll_target_invalidations.is_empty()
1812 || requested_focus.is_some()
1813 || requested_focus_target.is_some()
1814 || requested_capture.is_some()
1815 || notify_requested
1816 {
1817 needs_redraw = true;
1818 }
1819
1820 for (id, inv) in invalidations {
1821 self.mark_invalidation(id, inv);
1822 }
1823 let mut resolved_scroll_handle_invalidations = Vec::new();
1824 self.extend_live_bound_scroll_handle_invalidations(
1825 app,
1826 &scroll_handle_invalidations,
1827 &mut resolved_scroll_handle_invalidations,
1828 );
1829 for (id, inv) in resolved_scroll_handle_invalidations {
1830 self.mark_invalidation(id, inv);
1831 }
1832 let mut resolved_scroll_target_invalidations = Vec::new();
1833 self.extend_live_scroll_target_invalidations(
1834 app,
1835 &scroll_target_invalidations,
1836 &mut resolved_scroll_target_invalidations,
1837 );
1838 for (id, inv) in resolved_scroll_target_invalidations {
1839 self.mark_invalidation(id, inv);
1840 }
1841 if notify_requested {
1842 self.debug_record_notify_request(
1843 app.frame_id(),
1844 node_id,
1845 notify_requested_location,
1846 );
1847 self.mark_invalidation_with_source(
1848 node_id,
1849 Invalidation::Paint,
1850 UiDebugInvalidationSource::Notify,
1851 );
1852 }
1853
1854 let focus_requested_now =
1855 requested_focus.is_some() || requested_focus_target.is_some();
1856 if let Some(focus) = self.resolve_requested_focus(
1857 app,
1858 requested_focus,
1859 requested_focus_target,
1860 ) && self.focus_request_is_allowed(
1861 app,
1862 self.window,
1863 dispatch_cx.active_focus_roots.as_slice(),
1864 focus,
1865 Some(&dispatch_cx.focus_snapshot),
1866 ) {
1867 focus_requested = true;
1868 if let Some(prev) = self.focus {
1869 self.mark_invalidation(prev, Invalidation::Paint);
1870 }
1871 self.focus = Some(focus);
1872 self.mark_invalidation(focus, Invalidation::Paint);
1873 if !matches!(
1879 event_for_node,
1880 Event::Pointer(_) | Event::PointerCancel(_)
1881 ) {
1882 self.scroll_node_into_view(app, focus);
1883 }
1884 } else if focus_requested_now {
1885 focus_requested = true;
1886 }
1887
1888 if let Some(capture) = requested_capture
1889 && let Some(pointer_id) = event_pointer_id_for_capture
1890 {
1891 match capture {
1892 Some(node) => {
1893 let allow = !dock_drag_affects_window
1894 || dock_drag_capture_anchor == Some(node);
1895 if allow {
1896 if !matches!(event, Event::PointerCancel(_))
1897 && let Some(old_capture) =
1898 self.captured.get(&pointer_id).copied()
1899 && old_capture != node
1900 && node_in_active_layers(old_capture)
1901 {
1902 let mut cancel_ctx = input_ctx.clone();
1903 cancel_ctx.dispatch_phase =
1904 InputDispatchPhase::Bubble;
1905 let cancel_event =
1906 pointer_cancel_event_for_capture_switch(
1907 event, pointer_id,
1908 );
1909 let _ = self.dispatch_event_to_node_chain(
1910 app,
1911 services,
1912 &dispatch_cx,
1913 &cancel_ctx,
1914 old_capture,
1915 &cancel_event,
1916 &mut needs_redraw,
1917 &mut invalidation_visited,
1918 );
1919 }
1920 self.captured.insert(pointer_id, node);
1921 }
1922 }
1923 None => {
1924 self.captured.remove(&pointer_id);
1925 }
1926 }
1927 }
1928
1929 if let Some(requested_cursor) = requested_cursor
1930 && (cursor_choice.is_none() || cursor_choice_from_query)
1931 {
1932 cursor_choice = Some(requested_cursor);
1933 cursor_choice_from_query = false;
1934 }
1935
1936 if stop_propagation {
1937 stop_propagation_requested = true;
1938 if stop_propagation_requested_by.is_none() {
1939 stop_propagation_requested_by = Some(node_id);
1940 }
1941 if is_wheel && wheel_stop_node.is_none() {
1942 wheel_stop_node = Some(node_id);
1943 }
1944 }
1945
1946 let captured_now = event_pointer_id_for_capture
1947 .and_then(|p| self.captured.get(&p).copied());
1948 if (captured_now.is_some()
1949 && !touch_drag_reroute_from_pressable_capture)
1950 || stop_propagation
1951 {
1952 break;
1953 }
1954 }
1955 },
1956 );
1957 if let Some(bubble_elapsed) = bubble_elapsed {
1958 self.debug_stats.dispatch_widget_bubble_time += bubble_elapsed;
1959 }
1960 }
1961 } else if matches!(event, Event::KeyDown { .. } | Event::KeyUp { .. }) {
1962 let key_start = self
1966 .focus
1967 .filter(|&n| dispatch_cx.node_in_active_focus_layers(n))
1968 .or(dispatch_cx.barrier_root)
1969 .unwrap_or(node_id);
1970
1971 let mut chain: Vec<NodeId> = Vec::new();
1972 let mut cur = Some(key_start);
1973 while let Some(id) = cur {
1974 chain.push(id);
1975 if dispatch_cx.focus_snapshot.pre.get(id).is_none() {
1976 debug_assert!(
1977 false,
1978 "dispatch/window: key chain node missing from focus snapshot (node={id:?}, frame_id={:?}, window={:?})",
1979 dispatch_cx.focus_snapshot.frame_id, dispatch_cx.focus_snapshot.window
1980 );
1981 break;
1982 }
1983 cur = dispatch_cx.focus_snapshot.parent.get(id).copied().flatten();
1984 }
1985
1986 let mut stopped_in_capture = false;
1987 {
1988 let mut capture_ctx = input_ctx.clone();
1989 capture_ctx.dispatch_phase = InputDispatchPhase::Capture;
1990
1991 let (_, capture_elapsed) = fret_perf::measure_span(
1992 self.debug_enabled,
1993 trace_enabled,
1994 || tracing::trace_span!("fret.ui.dispatch.widget_capture", kind = "key"),
1995 || {
1996 for &node_id in chain.iter().rev() {
1997 let (
1998 invalidations,
1999 scroll_handle_invalidations,
2000 scroll_target_invalidations,
2001 requested_focus,
2002 requested_focus_target,
2003 requested_capture,
2004 requested_cursor,
2005 notify_requested,
2006 notify_requested_location,
2007 stop_propagation,
2008 ) = self.with_widget_mut(node_id, |widget, tree| {
2009 let (children, bounds) = tree
2010 .nodes
2011 .get(node_id)
2012 .map(|n| (n.children.as_slice(), n.bounds))
2013 .unwrap_or((&[][..], Rect::default()));
2014 let mut cx = EventCx {
2015 app,
2016 services: &mut *services,
2017 node: node_id,
2018 layer_root: tree.node_root(node_id),
2019 window: tree.window,
2020 pointer_id: event_pointer_id_for_capture,
2021 scale_factor: tree.last_layout_scale_factor.unwrap_or(1.0),
2022 event_window_position,
2023 event_window_wheel_delta,
2024 input_ctx: capture_ctx.clone(),
2025 pointer_hit_is_text_input: false,
2026 pointer_hit_is_pressable: false,
2027 pointer_hit_pressable_target: None,
2028 pointer_hit_pressable_target_in_descendant_subtree: false,
2029 prevented_default_actions: &mut prevented_default_actions,
2030 children,
2031 focus: tree.focus,
2032 captured: event_pointer_id_for_capture
2033 .and_then(|p| tree.captured.get(&p).copied()),
2034 bounds,
2035 invalidations: Vec::new(),
2036 scroll_handle_invalidations: Vec::new(),
2037 scroll_target_invalidations: Vec::new(),
2038 requested_focus: None,
2039 requested_focus_target: None,
2040 requested_capture: None,
2041 requested_cursor: None,
2042 notify_requested: false,
2043 notify_requested_location: None,
2044 stop_propagation: false,
2045 };
2046 widget.event_capture(&mut cx, event);
2047 (
2048 cx.invalidations,
2049 cx.scroll_handle_invalidations,
2050 cx.scroll_target_invalidations,
2051 cx.requested_focus,
2052 cx.requested_focus_target,
2053 cx.requested_capture,
2054 cx.requested_cursor,
2055 cx.notify_requested,
2056 cx.notify_requested_location,
2057 cx.stop_propagation,
2058 )
2059 });
2060
2061 if !invalidations.is_empty()
2062 || !scroll_handle_invalidations.is_empty()
2063 || !scroll_target_invalidations.is_empty()
2064 || requested_focus.is_some()
2065 || requested_focus_target.is_some()
2066 || requested_capture.is_some()
2067 || notify_requested
2068 {
2069 needs_redraw = true;
2070 }
2071
2072 for (id, inv) in invalidations {
2073 self.mark_invalidation(id, inv);
2074 }
2075 let mut resolved_scroll_handle_invalidations = Vec::new();
2076 self.extend_live_bound_scroll_handle_invalidations(
2077 app,
2078 &scroll_handle_invalidations,
2079 &mut resolved_scroll_handle_invalidations,
2080 );
2081 for (id, inv) in resolved_scroll_handle_invalidations {
2082 self.mark_invalidation(id, inv);
2083 }
2084 let mut resolved_scroll_target_invalidations = Vec::new();
2085 self.extend_live_scroll_target_invalidations(
2086 app,
2087 &scroll_target_invalidations,
2088 &mut resolved_scroll_target_invalidations,
2089 );
2090 for (id, inv) in resolved_scroll_target_invalidations {
2091 self.mark_invalidation(id, inv);
2092 }
2093 if notify_requested {
2094 self.debug_record_notify_request(
2095 app.frame_id(),
2096 node_id,
2097 notify_requested_location,
2098 );
2099 self.mark_invalidation_with_source(
2100 node_id,
2101 Invalidation::Paint,
2102 UiDebugInvalidationSource::Notify,
2103 );
2104 }
2105
2106 let focus_requested_now =
2107 requested_focus.is_some() || requested_focus_target.is_some();
2108 if let Some(focus) = self.resolve_requested_focus(
2109 app,
2110 requested_focus,
2111 requested_focus_target,
2112 ) && self.focus_request_is_allowed(
2113 app,
2114 self.window,
2115 dispatch_cx.active_focus_roots.as_slice(),
2116 focus,
2117 Some(&dispatch_cx.focus_snapshot),
2118 ) {
2119 focus_requested = true;
2120 if let Some(prev) = self.focus {
2121 self.mark_invalidation(prev, Invalidation::Paint);
2122 }
2123 self.focus = Some(focus);
2124 self.mark_invalidation(focus, Invalidation::Paint);
2125 self.scroll_node_into_view(app, focus);
2126 } else if focus_requested_now {
2127 focus_requested = true;
2128 }
2129
2130 if let Some(capture) = requested_capture
2131 && let Some(pointer_id) = event_pointer_id_for_capture
2132 {
2133 match capture {
2134 Some(node) => {
2135 let allow = !dock_drag_affects_window
2136 || dock_drag_capture_anchor == Some(node);
2137 if allow {
2138 self.captured.insert(pointer_id, node);
2139 }
2140 }
2141 None => {
2142 self.captured.remove(&pointer_id);
2143 }
2144 }
2145 }
2146
2147 if requested_cursor.is_some() && cursor_choice.is_none() {
2148 cursor_choice = requested_cursor;
2149 }
2150
2151 if stop_propagation {
2152 stop_propagation_requested = true;
2153 if stop_propagation_requested_by.is_none() {
2154 stop_propagation_requested_by = Some(node_id);
2155 }
2156 stopped_in_capture = true;
2157 break;
2158 }
2159 }
2160 },
2161 );
2162 if let Some(capture_elapsed) = capture_elapsed {
2163 self.debug_stats.dispatch_widget_capture_time += capture_elapsed;
2164 }
2165 }
2166 if !stopped_in_capture {
2167 let mut bubble_ctx = input_ctx.clone();
2168 bubble_ctx.dispatch_phase = InputDispatchPhase::Bubble;
2169
2170 let (_, bubble_elapsed) = fret_perf::measure_span(
2171 self.debug_enabled,
2172 trace_enabled,
2173 || tracing::trace_span!("fret.ui.dispatch.widget_bubble", kind = "key"),
2174 || {
2175 for node_id in chain {
2176 let (
2177 invalidations,
2178 scroll_handle_invalidations,
2179 scroll_target_invalidations,
2180 requested_focus,
2181 requested_focus_target,
2182 requested_capture,
2183 requested_cursor,
2184 notify_requested,
2185 notify_requested_location,
2186 stop_propagation,
2187 ) = self.with_widget_mut(node_id, |widget, tree| {
2188 let (children, bounds) = tree
2189 .nodes
2190 .get(node_id)
2191 .map(|n| (n.children.as_slice(), n.bounds))
2192 .unwrap_or((&[][..], Rect::default()));
2193 let mut cx = EventCx {
2194 app,
2195 services: &mut *services,
2196 node: node_id,
2197 layer_root: tree.node_root(node_id),
2198 window: tree.window,
2199 pointer_id: event_pointer_id_for_capture,
2200 scale_factor: tree.last_layout_scale_factor.unwrap_or(1.0),
2201 event_window_position,
2202 event_window_wheel_delta,
2203 input_ctx: bubble_ctx.clone(),
2204 pointer_hit_is_text_input: false,
2205 pointer_hit_is_pressable: false,
2206 pointer_hit_pressable_target: None,
2207 pointer_hit_pressable_target_in_descendant_subtree: false,
2208 prevented_default_actions: &mut prevented_default_actions,
2209 children,
2210 focus: tree.focus,
2211 captured: event_pointer_id_for_capture
2212 .and_then(|p| tree.captured.get(&p).copied()),
2213 bounds,
2214 invalidations: Vec::new(),
2215 scroll_handle_invalidations: Vec::new(),
2216 scroll_target_invalidations: Vec::new(),
2217 requested_focus: None,
2218 requested_focus_target: None,
2219 requested_capture: None,
2220 requested_cursor: None,
2221 notify_requested: false,
2222 notify_requested_location: None,
2223 stop_propagation: false,
2224 };
2225 widget.event(&mut cx, event);
2226 (
2227 cx.invalidations,
2228 cx.scroll_handle_invalidations,
2229 cx.scroll_target_invalidations,
2230 cx.requested_focus,
2231 cx.requested_focus_target,
2232 cx.requested_capture,
2233 cx.requested_cursor,
2234 cx.notify_requested,
2235 cx.notify_requested_location,
2236 cx.stop_propagation,
2237 )
2238 });
2239
2240 if !invalidations.is_empty()
2241 || !scroll_handle_invalidations.is_empty()
2242 || !scroll_target_invalidations.is_empty()
2243 || requested_focus.is_some()
2244 || requested_focus_target.is_some()
2245 || requested_capture.is_some()
2246 || notify_requested
2247 {
2248 needs_redraw = true;
2249 }
2250
2251 for (id, inv) in invalidations {
2252 self.mark_invalidation(id, inv);
2253 }
2254 let mut resolved_scroll_handle_invalidations = Vec::new();
2255 self.extend_live_bound_scroll_handle_invalidations(
2256 app,
2257 &scroll_handle_invalidations,
2258 &mut resolved_scroll_handle_invalidations,
2259 );
2260 for (id, inv) in resolved_scroll_handle_invalidations {
2261 self.mark_invalidation(id, inv);
2262 }
2263 let mut resolved_scroll_target_invalidations = Vec::new();
2264 self.extend_live_scroll_target_invalidations(
2265 app,
2266 &scroll_target_invalidations,
2267 &mut resolved_scroll_target_invalidations,
2268 );
2269 for (id, inv) in resolved_scroll_target_invalidations {
2270 self.mark_invalidation(id, inv);
2271 }
2272 if notify_requested {
2273 self.debug_record_notify_request(
2274 app.frame_id(),
2275 node_id,
2276 notify_requested_location,
2277 );
2278 self.mark_invalidation_with_source(
2279 node_id,
2280 Invalidation::Paint,
2281 UiDebugInvalidationSource::Notify,
2282 );
2283 }
2284
2285 let focus_requested_now =
2286 requested_focus.is_some() || requested_focus_target.is_some();
2287 if let Some(focus) = self.resolve_requested_focus(
2288 app,
2289 requested_focus,
2290 requested_focus_target,
2291 ) && self.focus_request_is_allowed(
2292 app,
2293 self.window,
2294 dispatch_cx.active_focus_roots.as_slice(),
2295 focus,
2296 Some(&dispatch_cx.focus_snapshot),
2297 ) {
2298 focus_requested = true;
2299 if let Some(prev) = self.focus {
2300 self.mark_invalidation(prev, Invalidation::Paint);
2301 }
2302 self.focus = Some(focus);
2303 self.mark_invalidation(focus, Invalidation::Paint);
2304 self.scroll_node_into_view(app, focus);
2305 } else if focus_requested_now {
2306 focus_requested = true;
2307 }
2308
2309 if let Some(capture) = requested_capture
2310 && let Some(pointer_id) = event_pointer_id_for_capture
2311 {
2312 match capture {
2313 Some(node) => {
2314 let allow = !dock_drag_affects_window
2315 || dock_drag_capture_anchor == Some(node);
2316 if allow {
2317 self.captured.insert(pointer_id, node);
2318 }
2319 }
2320 None => {
2321 self.captured.remove(&pointer_id);
2322 }
2323 }
2324 }
2325
2326 if requested_cursor.is_some() && cursor_choice.is_none() {
2327 cursor_choice = requested_cursor;
2328 }
2329
2330 if stop_propagation {
2331 stop_propagation_requested = true;
2332 if stop_propagation_requested_by.is_none() {
2333 stop_propagation_requested_by = Some(node_id);
2334 }
2335 break;
2336 }
2337 }
2338 },
2339 );
2340 if let Some(bubble_elapsed) = bubble_elapsed {
2341 self.debug_stats.dispatch_widget_bubble_time += bubble_elapsed;
2342 }
2343 }
2344
2345 let stopped_by_dismissible_root_hook = stop_propagation_requested
2346 && self.window.is_some_and(|window| {
2347 stop_propagation_requested_by
2348 .and_then(|node| self.nodes.get(node).and_then(|n| n.element))
2349 .and_then(|element| {
2350 crate::elements::with_element_state(
2351 app,
2352 window,
2353 element,
2354 crate::action::DismissibleActionHooks::default,
2355 |hooks| hooks.on_dismiss_request.clone(),
2356 )
2357 })
2358 .is_some()
2359 });
2360
2361 if defer_escape_overlay_dismiss
2362 && !stopped_by_dismissible_root_hook
2363 && (!stop_propagation_requested || !focus_requested)
2364 && let Event::KeyDown {
2365 key: fret_core::KeyCode::Escape,
2366 repeat: false,
2367 ..
2368 } = event
2369 && let Some(window) = self.window
2370 && self.dismiss_topmost_overlay_on_escape(app, window, base_root, barrier_root)
2371 {
2372 self.request_redraw_coalesced(app);
2373 return;
2374 }
2375 } else {
2376 loop {
2377 let (
2378 invalidations,
2379 scroll_handle_invalidations,
2380 scroll_target_invalidations,
2381 requested_focus,
2382 requested_focus_target,
2383 requested_capture,
2384 requested_cursor,
2385 notify_requested,
2386 notify_requested_location,
2387 stop_propagation,
2388 ) = self.with_widget_mut(node_id, |widget, tree| {
2389 let (children, bounds) = tree
2390 .nodes
2391 .get(node_id)
2392 .map(|n| (n.children.as_slice(), n.bounds))
2393 .unwrap_or((&[][..], Rect::default()));
2394 let mut cx = EventCx {
2395 app,
2396 services: &mut *services,
2397 node: node_id,
2398 layer_root: tree.node_root(node_id),
2399 window: tree.window,
2400 pointer_id: event_pointer_id_for_capture,
2401 scale_factor: tree.last_layout_scale_factor.unwrap_or(1.0),
2402 event_window_position,
2403 event_window_wheel_delta,
2404 input_ctx: input_ctx.clone(),
2405 pointer_hit_is_text_input: false,
2406 pointer_hit_is_pressable: false,
2407 pointer_hit_pressable_target: None,
2408 pointer_hit_pressable_target_in_descendant_subtree: false,
2409 prevented_default_actions: &mut prevented_default_actions,
2410 children,
2411 focus: tree.focus,
2412 captured: event_pointer_id_for_capture
2413 .and_then(|p| tree.captured.get(&p).copied()),
2414 bounds,
2415 invalidations: Vec::new(),
2416 scroll_handle_invalidations: Vec::new(),
2417 scroll_target_invalidations: Vec::new(),
2418 requested_focus: None,
2419 requested_focus_target: None,
2420 requested_capture: None,
2421 requested_cursor: None,
2422 notify_requested: false,
2423 notify_requested_location: None,
2424 stop_propagation: false,
2425 };
2426 widget.event(&mut cx, event);
2427 (
2428 cx.invalidations,
2429 cx.scroll_handle_invalidations,
2430 cx.scroll_target_invalidations,
2431 cx.requested_focus,
2432 cx.requested_focus_target,
2433 cx.requested_capture,
2434 cx.requested_cursor,
2435 cx.notify_requested,
2436 cx.notify_requested_location,
2437 cx.stop_propagation,
2438 )
2439 });
2440 if !invalidations.is_empty()
2441 || !scroll_handle_invalidations.is_empty()
2442 || !scroll_target_invalidations.is_empty()
2443 || requested_focus.is_some()
2444 || requested_focus_target.is_some()
2445 || requested_capture.is_some()
2446 || notify_requested
2447 {
2448 needs_redraw = true;
2449 }
2450
2451 for (id, inv) in invalidations {
2452 self.mark_invalidation(id, inv);
2453 }
2454 let mut resolved_scroll_handle_invalidations = Vec::new();
2455 self.extend_live_bound_scroll_handle_invalidations(
2456 app,
2457 &scroll_handle_invalidations,
2458 &mut resolved_scroll_handle_invalidations,
2459 );
2460 for (id, inv) in resolved_scroll_handle_invalidations {
2461 self.mark_invalidation(id, inv);
2462 }
2463 let mut resolved_scroll_target_invalidations = Vec::new();
2464 self.extend_live_scroll_target_invalidations(
2465 app,
2466 &scroll_target_invalidations,
2467 &mut resolved_scroll_target_invalidations,
2468 );
2469 for (id, inv) in resolved_scroll_target_invalidations {
2470 self.mark_invalidation(id, inv);
2471 }
2472 if notify_requested {
2473 self.debug_record_notify_request(
2474 app.frame_id(),
2475 node_id,
2476 notify_requested_location,
2477 );
2478 self.mark_invalidation_with_source(
2479 node_id,
2480 Invalidation::Paint,
2481 UiDebugInvalidationSource::Notify,
2482 );
2483 }
2484
2485 let focus_requested_now =
2486 requested_focus.is_some() || requested_focus_target.is_some();
2487 if let Some(focus) =
2488 self.resolve_requested_focus(app, requested_focus, requested_focus_target)
2489 && self.focus_request_is_allowed(
2490 app,
2491 self.window,
2492 dispatch_cx.active_focus_roots.as_slice(),
2493 focus,
2494 Some(&dispatch_cx.focus_snapshot),
2495 )
2496 {
2497 focus_requested = true;
2498 if let Some(prev) = self.focus {
2499 self.mark_invalidation(prev, Invalidation::Paint);
2500 }
2501 self.focus = Some(focus);
2502 self.mark_invalidation(focus, Invalidation::Paint);
2503 if !matches!(event, Event::Pointer(_) | Event::PointerCancel(_)) {
2509 self.scroll_node_into_view(app, focus);
2510 }
2511 } else if focus_requested_now {
2512 focus_requested = true;
2513 }
2514
2515 if let Some(capture) = requested_capture
2516 && let Some(pointer_id) = event_pointer_id_for_capture
2517 {
2518 match capture {
2519 Some(node) => {
2520 let allow =
2521 !dock_drag_affects_window || dock_drag_capture_anchor == Some(node);
2522 if allow {
2523 self.captured.insert(pointer_id, node);
2524 }
2525 }
2526 None => {
2527 self.captured.remove(&pointer_id);
2528 }
2529 }
2530 };
2531
2532 if requested_cursor.is_some() && cursor_choice.is_none() {
2533 cursor_choice = requested_cursor;
2534 }
2535
2536 if stop_propagation {
2537 stop_propagation_requested = true;
2538 if stop_propagation_requested_by.is_none() {
2539 stop_propagation_requested_by = Some(node_id);
2540 }
2541 if is_wheel && wheel_stop_node.is_none() {
2542 wheel_stop_node = Some(node_id);
2543 }
2544 }
2545
2546 let captured_now =
2547 event_pointer_id_for_capture.and_then(|p| self.captured.get(&p).copied());
2548 if captured_now.is_some() || stop_propagation {
2549 break;
2550 }
2551
2552 if dispatch_cx.focus_snapshot.pre.get(node_id).is_none() {
2553 tracing::warn!(
2554 node = ?node_id,
2555 frame_id = ?dispatch_cx.focus_snapshot.frame_id,
2556 window = ?dispatch_cx.focus_snapshot.window,
2557 "dispatch/window: bubble chain node missing from focus snapshot"
2558 );
2559 break;
2560 }
2561 node_id = match dispatch_cx
2562 .focus_snapshot
2563 .parent
2564 .get(node_id)
2565 .copied()
2566 .flatten()
2567 {
2568 Some(parent) => parent,
2569 None => break,
2570 };
2571 }
2572 }
2573
2574 if let Event::Pointer(PointerEvent::Down {
2575 button,
2576 pointer_type,
2577 ..
2578 }) = event
2579 && *button == fret_core::MouseButton::Left
2580 && !focus_requested
2581 && !prevented_default_actions.contains(fret_runtime::DefaultAction::FocusOnPointerDown)
2582 && captured.is_none()
2583 && internal_drag_target.is_none()
2584 && let Some(window) = self.window
2585 && let Some(hit) = pointer_hit
2586 {
2587 let candidate = self.first_focusable_ancestor_including_declarative(app, window, hit);
2588 if let Some(focus) = candidate
2589 && self.focus_request_is_allowed(
2590 app,
2591 self.window,
2592 dispatch_cx.active_focus_roots.as_slice(),
2593 focus,
2594 Some(&dispatch_cx.focus_snapshot),
2595 )
2596 {
2597 if let Some(prev) = self.focus {
2598 self.mark_invalidation(prev, Invalidation::Paint);
2599 }
2600 self.focus = Some(focus);
2601 self.mark_invalidation(focus, Invalidation::Paint);
2602
2603 if *pointer_type == fret_core::PointerType::Touch && self.focus_is_text_input(app) {
2607 app.push_effect(Effect::ImeRequestVirtualKeyboard {
2608 window,
2609 visible: true,
2610 });
2611 }
2612
2613 needs_redraw = true;
2617 }
2618 }
2619
2620 if is_wheel
2621 && let Some(scroll_target) = wheel_stop_node
2622 && let Some(window) = self.window
2623 {
2624 let is_scroll_target = declarative::with_window_frame(app, window, |window_frame| {
2625 let window_frame = window_frame?;
2626 let record = window_frame.instances.get(scroll_target)?;
2627 Some(matches!(
2628 record.instance,
2629 declarative::ElementInstance::Scroll(_)
2630 | declarative::ElementInstance::VirtualList(_)
2631 | declarative::ElementInstance::WheelRegion(_)
2632 | declarative::ElementInstance::Scrollbar(_)
2633 ))
2634 })
2635 .unwrap_or(false);
2636
2637 if is_scroll_target {
2638 struct ScrollDismissHookHost<'a, H: crate::UiHost> {
2639 app: &'a mut H,
2640 window: AppWindowId,
2641 element: crate::GlobalElementId,
2642 }
2643
2644 impl<H: crate::UiHost> crate::action::UiActionHost for ScrollDismissHookHost<'_, H> {
2645 fn models_mut(&mut self) -> &mut fret_runtime::ModelStore {
2646 self.app.models_mut()
2647 }
2648
2649 fn push_effect(&mut self, effect: Effect) {
2650 match effect {
2651 Effect::SetTimer {
2652 window: Some(window),
2653 token,
2654 ..
2655 } if window == self.window => {
2656 crate::elements::record_timer_target(
2657 &mut *self.app,
2658 window,
2659 token,
2660 self.element,
2661 );
2662 }
2663 Effect::CancelTimer { token } => {
2664 crate::elements::clear_timer_target(
2665 &mut *self.app,
2666 self.window,
2667 token,
2668 );
2669 }
2670 _ => {}
2671 }
2672 self.app.push_effect(effect);
2673 }
2674
2675 fn request_redraw(&mut self, window: AppWindowId) {
2676 self.app.request_redraw(window);
2677 }
2678
2679 fn next_timer_token(&mut self) -> fret_runtime::TimerToken {
2680 self.app.next_timer_token()
2681 }
2682
2683 fn next_clipboard_token(&mut self) -> fret_runtime::ClipboardToken {
2684 self.app.next_clipboard_token()
2685 }
2686
2687 fn next_share_sheet_token(&mut self) -> fret_runtime::ShareSheetToken {
2688 self.app.next_share_sheet_token()
2689 }
2690 }
2691
2692 let mut dismissed_any = false;
2693 for layer_id in self.visible_layers_in_paint_order() {
2694 let Some(layer) = self.layers.get(layer_id) else {
2695 continue;
2696 };
2697 if layer.scroll_dismiss_elements.is_empty() {
2698 continue;
2699 }
2700 let should_dismiss = layer
2701 .scroll_dismiss_elements
2702 .iter()
2703 .copied()
2704 .filter_map(|element| {
2705 self.resolve_live_attached_node_for_element(app, Some(window), element)
2706 })
2707 .any(|node| self.is_descendant(scroll_target, node));
2708 if !should_dismiss {
2709 continue;
2710 }
2711 let Some(root_element) = self.nodes.get(layer.root).and_then(|n| n.element)
2712 else {
2713 continue;
2714 };
2715 let hook = crate::elements::with_element_state(
2716 app,
2717 window,
2718 root_element,
2719 crate::action::DismissibleActionHooks::default,
2720 |hooks| hooks.on_dismiss_request.clone(),
2721 );
2722 let Some(hook) = hook else {
2723 continue;
2724 };
2725 let mut host = ScrollDismissHookHost {
2726 app,
2727 window,
2728 element: root_element,
2729 };
2730 let mut req =
2731 crate::action::DismissRequestCx::new(crate::action::DismissReason::Scroll);
2732 hook(
2733 &mut host,
2734 crate::action::ActionCx {
2735 window,
2736 target: root_element,
2737 },
2738 &mut req,
2739 );
2740 dismissed_any = true;
2741 }
2742
2743 if dismissed_any {
2744 needs_redraw = true;
2745 }
2746 }
2747 }
2748
2749 if matches!(event, Event::PointerCancel(_))
2750 && let Some(pointer_id) = event_pointer_id_for_capture
2751 {
2752 self.captured.remove(&pointer_id);
2753 }
2754
2755 if let Event::PointerCancel(e) = event
2756 && let Some(window) = self.window
2757 && pointer_type_supports_hover(e.pointer_type)
2758 {
2759 let (prev_element, prev_node, _next_element, _next_node) =
2760 crate::elements::update_hovered_pressable(app, window, None);
2761 if prev_node.is_some() {
2762 needs_redraw = true;
2763 self.debug_record_hover_edge_pressable();
2764 if let Some(node) = prev_node {
2765 self.mark_invalidation_dedup_with_source(
2766 node,
2767 Invalidation::Paint,
2768 &mut invalidation_visited,
2769 UiDebugInvalidationSource::Hover,
2770 );
2771 }
2772 }
2773
2774 if let Some(element) = prev_element
2775 && prev_node.is_some()
2776 {
2777 Self::run_pressable_hover_hook(app, window, element, false);
2778 }
2779
2780 let (_prev_element, prev_node, _next_element, _next_node) =
2781 crate::elements::update_hovered_hover_region(app, window, None);
2782 if prev_node.is_some() {
2783 needs_redraw = true;
2784 self.debug_record_hover_edge_hover_region();
2785 if let Some(node) = prev_node {
2786 self.mark_invalidation_dedup_with_source(
2787 node,
2788 Invalidation::Paint,
2789 &mut invalidation_visited,
2790 UiDebugInvalidationSource::Hover,
2791 );
2792 }
2793 }
2794 }
2795
2796 if let Event::PointerCancel(e) = event {
2797 self.touch_pointer_down_outside_candidates
2798 .remove(&e.pointer_id);
2799 }
2800
2801 #[cfg(feature = "diagnostics")]
2802 if defer_keydown_shortcuts_until_after_dispatch
2803 && stop_propagation_requested
2804 && let Some(window) = self.window
2805 && let Event::KeyDown {
2806 key,
2807 modifiers,
2808 repeat,
2809 } = event
2810 {
2811 let focus_is_text_input = self.focus_is_text_input(app);
2812 let key_contexts = if !self.pending_shortcut.keystrokes.is_empty() {
2813 self.pending_shortcut.key_contexts.clone()
2814 } else {
2815 self.shortcut_key_context_stack(app, routing_barrier_root)
2816 };
2817 app.with_global_mut_untracked(
2818 fret_runtime::WindowShortcutRoutingDiagnosticsStore::default,
2819 |store, app| {
2820 store.record(
2821 window,
2822 fret_runtime::ShortcutRoutingDecision {
2823 seq: 0,
2824 frame_id: app.frame_id(),
2825 phase: fret_runtime::ShortcutRoutingPhase::PostDispatch,
2826 key: *key,
2827 modifiers: *modifiers,
2828 repeat: *repeat,
2829 deferred: true,
2830 focus_is_text_input,
2831 ime_composing: self.ime_composing,
2832 pending_sequence_len: self
2833 .pending_shortcut
2834 .keystrokes
2835 .len()
2836 .min(u32::MAX as usize)
2837 as u32,
2838 outcome: fret_runtime::ShortcutRoutingOutcome::ConsumedByWidget,
2839 command: None,
2840 command_enabled: None,
2841 key_contexts,
2842 },
2843 );
2844 },
2845 );
2846 }
2847
2848 if defer_keydown_shortcuts_until_after_dispatch
2849 && !stop_propagation_requested
2850 && let Event::KeyDown {
2851 key,
2852 modifiers,
2853 repeat,
2854 } = event
2855 {
2856 let focus_is_text_input = self.focus_is_text_input(app);
2857 let input_ctx_for_shortcuts = InputContext {
2858 focus_is_text_input,
2859 ..input_ctx.clone()
2860 };
2861
2862 let ime_reserved = self.ime_composing
2863 && Self::should_defer_keydown_shortcut_matching_to_text_input(
2864 *key,
2865 *modifiers,
2866 focus_is_text_input,
2867 );
2868
2869 #[cfg(feature = "diagnostics")]
2870 if let Some(window) = self.window
2871 && ime_reserved
2872 {
2873 let key_contexts = if !self.pending_shortcut.keystrokes.is_empty() {
2874 self.pending_shortcut.key_contexts.clone()
2875 } else {
2876 self.shortcut_key_context_stack(app, routing_barrier_root)
2877 };
2878 app.with_global_mut_untracked(
2879 fret_runtime::WindowShortcutRoutingDiagnosticsStore::default,
2880 |store, app| {
2881 store.record(
2882 window,
2883 fret_runtime::ShortcutRoutingDecision {
2884 seq: 0,
2885 frame_id: app.frame_id(),
2886 phase: fret_runtime::ShortcutRoutingPhase::PostDispatch,
2887 key: *key,
2888 modifiers: *modifiers,
2889 repeat: *repeat,
2890 deferred: true,
2891 focus_is_text_input,
2892 ime_composing: self.ime_composing,
2893 pending_sequence_len: self
2894 .pending_shortcut
2895 .keystrokes
2896 .len()
2897 .min(u32::MAX as usize)
2898 as u32,
2899 outcome: fret_runtime::ShortcutRoutingOutcome::ReservedForIme,
2900 command: None,
2901 command_enabled: None,
2902 key_contexts,
2903 },
2904 );
2905 },
2906 );
2907 }
2908
2909 if !ime_reserved
2910 && self.handle_keydown_shortcuts(
2911 app,
2912 services,
2913 KeydownShortcutParams {
2914 input_ctx: &input_ctx_for_shortcuts,
2915 barrier_root: routing_barrier_root,
2916 focus_is_text_input,
2917 #[cfg(feature = "diagnostics")]
2918 phase: fret_runtime::ShortcutRoutingPhase::PostDispatch,
2919 #[cfg(feature = "diagnostics")]
2920 deferred: true,
2921 key: *key,
2922 modifiers: *modifiers,
2923 repeat: *repeat,
2924 },
2925 )
2926 {
2927 if needs_redraw {
2928 self.request_redraw_coalesced(app);
2929 }
2930 return;
2931 }
2932 }
2933
2934 if let Event::Pointer(PointerEvent::Move { .. }) = event
2935 && let Some(prev) = synth_pointer_move_prev_target
2936 && captured.is_none()
2937 && node_in_active_layers(prev)
2938 {
2939 let (_, elapsed) = fret_perf::measure_span(
2945 self.debug_enabled,
2946 trace_enabled,
2947 || tracing::trace_span!("fret.ui.dispatch.synth_hover_observer", node = ?prev),
2948 || {
2949 self.dispatch_event_to_node_chain_observer(
2950 app,
2951 services,
2952 &input_ctx,
2953 prev,
2954 event,
2955 Some(&dispatch_cx.input_snapshot),
2956 &mut invalidation_visited,
2957 );
2958 needs_redraw = true;
2959 },
2960 );
2961 if let Some(elapsed) = elapsed {
2962 self.debug_stats.dispatch_synth_hover_observer_time += elapsed;
2963 }
2964 }
2965
2966 if is_wheel
2967 && wheel_stop_node.is_some()
2968 && captured.is_none()
2969 && let Some(window) = self.window
2970 && let Event::Pointer(PointerEvent::Wheel {
2971 position,
2972 pointer_type,
2973 ..
2974 }) = event
2975 && pointer_type_supports_hover(*pointer_type)
2976 {
2977 self.invalidate_scroll_handle_bindings_for_changed_handles(
2980 app,
2981 crate::layout_pass::LayoutPassKind::Final,
2982 false,
2983 false,
2984 );
2985
2986 self.hit_test_path_cache = None;
2987 let hit = self.hit_test_layers_cached(hit_test_layer_roots, *position);
2988
2989 let mut hit_for_hover = hit;
2990 let mut hit_for_hover_region = hit;
2991 let mut hit_for_raw_below_barrier: Option<NodeId> = None;
2992 if let Some((occlusion_layer, occlusion)) =
2993 self.topmost_pointer_occlusion_layer(barrier_root)
2994 && occlusion != PointerOcclusion::None
2995 {
2996 let occlusion_z = self
2997 .layer_order
2998 .iter()
2999 .position(|id| *id == occlusion_layer);
3000 let hit_layer_z = hit
3001 .and_then(|hit| self.node_layer(hit))
3002 .and_then(|layer| self.layer_order.iter().position(|id| *id == layer));
3003 let hit_is_below_occlusion = match (occlusion_z, hit_layer_z, hit) {
3004 (Some(oz), Some(hz), Some(_)) => hz < oz,
3005 (Some(_), None, Some(_)) => true,
3006 (Some(_), _, None) => true,
3007 _ => false,
3008 };
3009 if hit_is_below_occlusion {
3010 hit_for_raw_below_barrier = hit;
3011 hit_for_hover = None;
3012 hit_for_hover_region = None;
3013 }
3014 }
3015
3016 let (_, elapsed) = fret_perf::measure_span(
3017 self.debug_enabled,
3018 trace_enabled,
3019 || tracing::trace_span!("fret.ui.dispatch.hover_update"),
3020 || {
3021 self.update_hover_state_from_hit(
3022 app,
3023 window,
3024 barrier_root,
3025 Some(*position),
3026 hit_for_hover,
3027 hit_for_hover_region,
3028 hit_for_raw_below_barrier,
3029 Some(pointer_chain_snapshot),
3030 &mut invalidation_visited,
3031 &mut needs_redraw,
3032 );
3033 },
3034 );
3035 if let Some(elapsed) = elapsed {
3036 self.debug_stats.dispatch_hover_update_time += elapsed;
3037 }
3038 }
3039
3040 if input_ctx.caps.ui.cursor_icons
3041 && let Some(window) = self.window
3042 && matches!(event, Event::Pointer(_))
3043 {
3044 let icon = cursor_choice.unwrap_or(fret_core::CursorIcon::Default);
3045 let (_, elapsed) = fret_perf::measure_span(
3046 self.debug_enabled,
3047 trace_enabled,
3048 || {
3049 tracing::trace_span!(
3050 "fret.ui.dispatch.cursor_effect",
3051 window = ?window,
3052 icon = ?icon
3053 )
3054 },
3055 || app.push_effect(Effect::CursorSetIcon { window, icon }),
3056 );
3057 if let Some(elapsed) = elapsed {
3058 self.debug_stats.dispatch_cursor_effect_time += elapsed;
3059 }
3060 }
3061
3062 if needs_redraw {
3063 self.request_redraw_coalesced(app);
3064 }
3065 let (_, elapsed) = fret_perf::measure_span(
3066 self.debug_enabled,
3067 trace_enabled,
3068 || tracing::trace_span!("fret.ui.dispatch.pointer_move_layer_observers"),
3069 || {
3070 self.dispatch_pointer_move_layer_observers(
3071 app,
3072 services,
3073 &input_ctx,
3074 barrier_root,
3075 event,
3076 &mut needs_redraw,
3077 &mut invalidation_visited,
3078 );
3079 },
3080 );
3081 if let Some(elapsed) = elapsed {
3082 self.debug_stats.dispatch_pointer_move_layer_observers_time += elapsed;
3083 }
3084 if needs_redraw {
3085 self.request_redraw_coalesced(app);
3086 }
3087
3088 let (_, elapsed) = fret_perf::measure_span(
3091 self.debug_enabled,
3092 trace_enabled,
3093 || tracing::trace_span!("fret.ui.dispatch.post_dispatch_snapshot"),
3094 || self.publish_post_dispatch_runtime_snapshots_for_event(app, event),
3095 );
3096 if let Some(elapsed) = elapsed {
3097 self.debug_stats.dispatch_post_dispatch_snapshot_time += elapsed;
3098 }
3099 }
3100}