Skip to main content

fui/
event.rs

1#![allow(clippy::too_many_arguments)]
2
3use crate::context_menu_manager;
4use crate::drag_drop;
5use crate::external_drop;
6use crate::ffi::{CursorStyle, KeyEventType, PointerEventType, SemanticRole};
7use crate::focus_visibility;
8use crate::keyboard_scroll::handle_keyboard_scroll_fallback;
9use crate::keyboard_scroll_tracker::{
10    register_keyboard_scroll_node, reset_keyboard_scroll_tracking,
11    track_keyboard_scroll_pointer_up, unregister_keyboard_scroll_node,
12};
13use crate::mobile_text_selection_toolbar;
14use crate::node::{NodeHandle, NodeRef, WeakNodeRef};
15use crate::selection_handle_adorner;
16use crate::tool_tip_manager;
17use std::cell::{Cell, RefCell};
18use std::collections::HashMap;
19use std::rc::Rc;
20
21static mut KEY_BUFFER: [u8; 256] = [0; 256];
22static mut TEXT_BUFFER: [u8; 16 * 1024] = [0; 16 * 1024];
23
24pub const DEFAULT_LONG_PRESS_MINIMUM_DURATION_MS: i32 = 500;
25pub const DEFAULT_LONG_PRESS_MOVEMENT_TOLERANCE: f32 = 10.0;
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum PointerType {
29    Unknown = 0,
30    Mouse = 1,
31    Touch = 2,
32    Pen = 3,
33}
34
35impl PointerType {
36    pub fn from_raw(value: u32) -> Self {
37        match value {
38            1 => Self::Mouse,
39            2 => Self::Touch,
40            3 => Self::Pen,
41            _ => Self::Unknown,
42        }
43    }
44}
45
46#[repr(i32)]
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum PointerButton {
49    None = -1,
50    Primary = 0,
51    Auxiliary = 1,
52    Secondary = 2,
53    Back = 3,
54    Forward = 4,
55}
56
57impl PointerButton {
58    pub fn from_raw(value: i32) -> Self {
59        match value {
60            0 => Self::Primary,
61            1 => Self::Auxiliary,
62            2 => Self::Secondary,
63            3 => Self::Back,
64            4 => Self::Forward,
65            _ => Self::None,
66        }
67    }
68}
69
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
71pub struct PointerButtons(u32);
72
73impl PointerButtons {
74    pub const NONE: Self = Self(0);
75    pub const PRIMARY: Self = Self(1 << 0);
76    pub const SECONDARY: Self = Self(1 << 1);
77    pub const AUXILIARY: Self = Self(1 << 2);
78    pub const BACK: Self = Self(1 << 3);
79    pub const FORWARD: Self = Self(1 << 4);
80
81    pub const fn from_raw(value: u32) -> Self {
82        Self(value)
83    }
84
85    pub const fn bits(self) -> u32 {
86        self.0
87    }
88
89    pub const fn is_empty(self) -> bool {
90        self.0 == 0
91    }
92
93    pub const fn contains(self, buttons: Self) -> bool {
94        self.0 & buttons.0 == buttons.0
95    }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub enum GestureIntent {
100    None = 0,
101    Pan = 1,
102    Pinch = 2,
103    PanAndPinch = 3,
104}
105
106impl GestureIntent {
107    pub fn from_callbacks(has_pan: bool, has_pinch: bool) -> Self {
108        match (has_pan, has_pinch) {
109            (false, false) => Self::None,
110            (true, false) => Self::Pan,
111            (false, true) => Self::Pinch,
112            (true, true) => Self::PanAndPinch,
113        }
114    }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub enum GestureEventPhase {
119    Begin = 1,
120    Update = 2,
121    End = 3,
122    Cancel = 4,
123}
124
125impl GestureEventPhase {
126    pub fn from_raw(value: u32) -> Self {
127        match value {
128            1 => Self::Begin,
129            2 => Self::Update,
130            3 => Self::End,
131            _ => Self::Cancel,
132        }
133    }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum GestureEventKind {
138    None = 0,
139    Pan = 1,
140    Pinch = 2,
141}
142
143impl GestureEventKind {
144    pub fn from_raw(value: u32) -> Self {
145        match value {
146            1 => Self::Pan,
147            2 => Self::Pinch,
148            _ => Self::None,
149        }
150    }
151}
152
153#[derive(Clone, Debug)]
154pub struct PointerEventArgs {
155    target_handle: NodeHandle,
156    pub event_type: PointerEventType,
157    pub scene_x: f32,
158    pub scene_y: f32,
159    pub x: f32,
160    pub y: f32,
161    pub modifiers: u32,
162    pub pointer_id: i32,
163    pub pointer_type: PointerType,
164    pub button: PointerButton,
165    pub buttons: PointerButtons,
166    pub pressure: f32,
167    pub width: f32,
168    pub height: f32,
169    pub click_count: i32,
170    pub is_primary: bool,
171    pub tangential_pressure: f32,
172    pub tilt_x: f32,
173    pub tilt_y: f32,
174    pub twist: f32,
175    pub handled: bool,
176}
177
178#[derive(Clone, Copy, Debug, PartialEq)]
179pub(crate) struct PointerExtendedMetadata {
180    pub is_primary: bool,
181    pub tangential_pressure: f32,
182    pub tilt_x: f32,
183    pub tilt_y: f32,
184    pub twist: f32,
185}
186
187impl Default for PointerExtendedMetadata {
188    fn default() -> Self {
189        Self {
190            is_primary: true,
191            tangential_pressure: 0.0,
192            tilt_x: 0.0,
193            tilt_y: 0.0,
194            twist: 0.0,
195        }
196    }
197}
198
199impl PointerEventArgs {
200    #[allow(clippy::too_many_arguments)]
201    pub fn new(
202        target_handle: NodeHandle,
203        event_type: PointerEventType,
204        scene_x: f32,
205        scene_y: f32,
206        modifiers: u32,
207        pointer_id: i32,
208        pointer_type: PointerType,
209        button: i32,
210        buttons: u32,
211        pressure: f32,
212        width: f32,
213        height: f32,
214        click_count: i32,
215    ) -> Self {
216        Self::new_with_metadata(
217            target_handle,
218            event_type,
219            scene_x,
220            scene_y,
221            modifiers,
222            pointer_id,
223            pointer_type,
224            button,
225            buttons,
226            pressure,
227            width,
228            height,
229            click_count,
230            PointerExtendedMetadata::default(),
231        )
232    }
233
234    #[allow(clippy::too_many_arguments)]
235    pub(crate) fn new_with_metadata(
236        target_handle: NodeHandle,
237        event_type: PointerEventType,
238        scene_x: f32,
239        scene_y: f32,
240        modifiers: u32,
241        pointer_id: i32,
242        pointer_type: PointerType,
243        button: i32,
244        buttons: u32,
245        pressure: f32,
246        width: f32,
247        height: f32,
248        click_count: i32,
249        extended: PointerExtendedMetadata,
250    ) -> Self {
251        Self {
252            target_handle,
253            event_type,
254            scene_x,
255            scene_y,
256            x: scene_x,
257            y: scene_y,
258            modifiers,
259            pointer_id,
260            pointer_type,
261            button: PointerButton::from_raw(button),
262            buttons: PointerButtons::from_raw(buttons),
263            pressure,
264            width,
265            height,
266            click_count,
267            is_primary: extended.is_primary,
268            tangential_pressure: extended.tangential_pressure,
269            tilt_x: extended.tilt_x,
270            tilt_y: extended.tilt_y,
271            twist: extended.twist,
272            handled: false,
273        }
274    }
275
276    pub fn capture_pointer(&self) {
277        crate::event::capture_pointer(self.target_handle);
278        unsafe { crate::ffi::fui_set_pointer_capture(self.target_handle.raw()) };
279    }
280
281    pub fn release_pointer_capture(&self) {
282        crate::event::release_pointer(self.target_handle);
283        unsafe { crate::ffi::fui_release_pointer_capture() };
284    }
285
286    pub(crate) fn target_handle(&self) -> NodeHandle {
287        self.target_handle
288    }
289}
290
291#[derive(Clone, Debug)]
292pub struct WheelEventArgs {
293    pub scene_x: f32,
294    pub scene_y: f32,
295    pub x: f32,
296    pub y: f32,
297    pub delta_x: f32,
298    pub delta_y: f32,
299    pub delta_mode: u32,
300    pub modifiers: u32,
301    pub handled: bool,
302}
303
304impl WheelEventArgs {
305    pub fn new(
306        scene_x: f32,
307        scene_y: f32,
308        delta_x: f32,
309        delta_y: f32,
310        delta_mode: u32,
311        modifiers: u32,
312    ) -> Self {
313        Self {
314            scene_x,
315            scene_y,
316            x: scene_x,
317            y: scene_y,
318            delta_x,
319            delta_y,
320            delta_mode,
321            modifiers,
322            handled: false,
323        }
324    }
325}
326
327#[derive(Clone, Debug)]
328pub struct GestureEventArgs {
329    pub phase: GestureEventPhase,
330    pub kind: GestureEventKind,
331    pub scene_x: f32,
332    pub scene_y: f32,
333    pub delta_x: f32,
334    pub delta_y: f32,
335    pub scale: f32,
336    pub pointer_count: i32,
337    pub x: f32,
338    pub y: f32,
339    pub handled: bool,
340}
341
342impl GestureEventArgs {
343    #[allow(clippy::too_many_arguments)]
344    pub fn new(
345        phase: GestureEventPhase,
346        kind: GestureEventKind,
347        scene_x: f32,
348        scene_y: f32,
349        delta_x: f32,
350        delta_y: f32,
351        scale: f32,
352        pointer_count: i32,
353    ) -> Self {
354        Self {
355            phase,
356            kind,
357            scene_x,
358            scene_y,
359            delta_x,
360            delta_y,
361            scale,
362            pointer_count,
363            x: scene_x,
364            y: scene_y,
365            handled: false,
366        }
367    }
368}
369
370#[derive(Clone, Debug)]
371pub struct KeyEventArgs {
372    pub event_type: KeyEventType,
373    pub key: String,
374    pub modifiers: u32,
375    pub handled: bool,
376}
377
378impl KeyEventArgs {
379    pub fn new(event_type: KeyEventType, key: String, modifiers: u32) -> Self {
380        Self {
381            event_type,
382            key,
383            modifiers,
384            handled: false,
385        }
386    }
387}
388
389type GlobalKeyFilter = Rc<dyn Fn(KeyEventType, &str, u32) -> bool>;
390type ScrollHook = Rc<dyn Fn()>;
391
392struct GlobalKeyFilterEntry {
393    token: u32,
394    callback: GlobalKeyFilter,
395}
396
397#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
398pub struct FocusChangedEventArgs {
399    pub focused: bool,
400}
401
402#[derive(Clone, Debug, Default, PartialEq, Eq)]
403pub struct TextChangedEventArgs {
404    pub text: String,
405}
406
407#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
408pub struct SelectionChangedEventArgs {
409    pub start: u32,
410    pub end: u32,
411}
412
413#[derive(Clone, Debug)]
414pub struct LongPressEventArgs {
415    pub scene_x: f32,
416    pub scene_y: f32,
417    pub pointer_id: i32,
418    pub pointer_type: PointerType,
419    pub modifiers: u32,
420    pub duration_ms: i32,
421    pub x: f32,
422    pub y: f32,
423    pub handled: bool,
424}
425
426impl LongPressEventArgs {
427    pub fn new(
428        scene_x: f32,
429        scene_y: f32,
430        pointer_id: i32,
431        pointer_type: PointerType,
432        modifiers: u32,
433        duration_ms: i32,
434    ) -> Self {
435        Self {
436            scene_x,
437            scene_y,
438            pointer_id,
439            pointer_type,
440            modifiers,
441            duration_ms,
442            x: scene_x,
443            y: scene_y,
444            handled: false,
445        }
446    }
447}
448
449pub(crate) struct EventRouter {
450    nodes: RefCell<HashMap<NodeHandle, WeakNodeRef>>,
451    focused: RefCell<Option<NodeHandle>>,
452    captured_pointer: RefCell<Option<NodeHandle>>,
453    selection_cursor_owner: RefCell<Option<NodeHandle>>,
454    hover_stack: RefCell<Vec<NodeHandle>>,
455    current_cursor_style: Cell<CursorStyle>,
456    key_filters: RefCell<Vec<GlobalKeyFilterEntry>>,
457    scroll_hooks: RefCell<Vec<ScrollHook>>,
458    next_key_filter_token: Cell<u32>,
459}
460
461impl EventRouter {
462    pub(crate) fn new() -> Self {
463        Self {
464            nodes: RefCell::new(HashMap::new()),
465            focused: RefCell::new(None),
466            captured_pointer: RefCell::new(None),
467            selection_cursor_owner: RefCell::new(None),
468            hover_stack: RefCell::new(Vec::new()),
469            current_cursor_style: Cell::new(CursorStyle::Default),
470            key_filters: RefCell::new(Vec::new()),
471            scroll_hooks: RefCell::new(Vec::new()),
472            next_key_filter_token: Cell::new(1),
473        }
474    }
475
476    fn register(&self, node: &NodeRef) {
477        self.nodes
478            .borrow_mut()
479            .insert(node.handle(), node.downgrade());
480        register_keyboard_scroll_node(node);
481    }
482
483    fn reset(&self) {
484        self.nodes.borrow_mut().clear();
485        self.focused.replace(None);
486        self.captured_pointer.replace(None);
487        self.selection_cursor_owner.replace(None);
488        self.hover_stack.borrow_mut().clear();
489        self.key_filters.borrow_mut().clear();
490        // Scroll hooks are process-wide manager hooks in FUI-AS. Keep that
491        // lifetime here as well; route/test resets should clear per-node state
492        // but must not silently unregister static control-manager hooks.
493        self.next_key_filter_token.set(1);
494        drag_drop::reset();
495        external_drop::reset();
496        self.apply_cursor(CursorStyle::Default);
497        reset_keyboard_scroll_tracking();
498    }
499
500    fn unregister(&self, handle: NodeHandle) {
501        if let Some(node) = self.resolve_node(handle) {
502            drag_drop::handle_node_destroyed(node.clone());
503            external_drop::handle_node_destroyed(node);
504        }
505        self.nodes.borrow_mut().remove(&handle);
506        self.pop_hover(handle);
507        if self.focused.borrow().as_ref() == Some(&handle) {
508            self.focused.replace(None);
509        }
510        if self.captured_pointer.borrow().as_ref() == Some(&handle) {
511            self.captured_pointer.replace(None);
512        }
513        if self.selection_cursor_owner.borrow().as_ref() == Some(&handle) {
514            self.selection_cursor_owner.replace(None);
515        }
516        self.apply_current_cursor();
517        unregister_keyboard_scroll_node(handle);
518    }
519
520    fn push_key_filter(&self, callback: GlobalKeyFilter) -> u32 {
521        let token = self.next_key_filter_token.get();
522        self.next_key_filter_token.set(token + 1);
523        self.key_filters
524            .borrow_mut()
525            .push(GlobalKeyFilterEntry { token, callback });
526        token
527    }
528
529    fn remove_key_filter(&self, token: u32) {
530        self.key_filters
531            .borrow_mut()
532            .retain(|entry| entry.token != token);
533    }
534
535    fn register_scroll_hook(&self, callback: ScrollHook) {
536        self.scroll_hooks.borrow_mut().push(callback);
537    }
538
539    fn focused_node_is_enabled_button(&self) -> bool {
540        let Some(handle) = *self.focused.borrow() else {
541            return false;
542        };
543        let Some(node) = self.resolve_node(handle) else {
544            return false;
545        };
546        node.semantic_role_for_routing() == Some(SemanticRole::Button)
547            && node.is_enabled_for_routing()
548    }
549
550    pub(crate) fn dispatch_pointer_event(
551        &self,
552        handle: NodeHandle,
553        event_type: PointerEventType,
554        scene_x: f32,
555        scene_y: f32,
556        modifiers: u32,
557        pointer_id: i32,
558        pointer_type: PointerType,
559        button: i32,
560        buttons: u32,
561        pressure: f32,
562        width: f32,
563        height: f32,
564        click_count: i32,
565    ) -> bool {
566        self.dispatch_pointer_event_with_metadata(
567            handle,
568            event_type,
569            scene_x,
570            scene_y,
571            modifiers,
572            pointer_id,
573            pointer_type,
574            button,
575            buttons,
576            pressure,
577            width,
578            height,
579            click_count,
580            PointerExtendedMetadata::default(),
581        )
582    }
583
584    #[allow(clippy::too_many_arguments)]
585    pub(crate) fn dispatch_pointer_event_with_metadata(
586        &self,
587        handle: NodeHandle,
588        event_type: PointerEventType,
589        scene_x: f32,
590        scene_y: f32,
591        modifiers: u32,
592        pointer_id: i32,
593        pointer_type: PointerType,
594        button: i32,
595        buttons: u32,
596        pressure: f32,
597        width: f32,
598        height: f32,
599        click_count: i32,
600        extended: PointerExtendedMetadata,
601    ) -> bool {
602        context_menu_manager::track_pointer_event(event_type, handle.raw());
603        selection_handle_adorner::record_pointer_event(event_type, pointer_type);
604        let pointed_node = self.resolve_node(handle);
605        if event_type == PointerEventType::Down
606            && (button == PointerButton::Primary as i32
607                || matches!(pointer_type, PointerType::Touch | PointerType::Pen))
608            && crate::controls::ContextMenu::dismiss_for_outside_pointer_down(scene_x, scene_y)
609        {
610            drag_drop::handle_pointer_event(None, event_type, scene_x, scene_y, modifiers);
611            self.apply_current_cursor();
612            return true;
613        }
614        if event_type == PointerEventType::Down
615            && !preserves_selection_on_pointer_down_for_routing(pointed_node.as_ref())
616            && mobile_text_selection_toolbar::dismiss_for_outside_pointer_down(scene_x, scene_y)
617        {
618            drag_drop::handle_pointer_event(None, event_type, scene_x, scene_y, modifiers);
619            self.apply_current_cursor();
620            return true;
621        }
622        if event_type == PointerEventType::Down
623            && button == PointerButton::Primary as i32
624            && pointer_type != PointerType::Touch
625        {
626            self.selection_cursor_owner.replace(
627                pointed_node
628                    .as_ref()
629                    .and_then(selection_cursor_target)
630                    .map(|node| node.handle()),
631            );
632        } else if matches!(event_type, PointerEventType::Up | PointerEventType::Cancel) {
633            self.selection_cursor_owner.replace(None);
634        }
635        if event_type == PointerEventType::Up {
636            track_keyboard_scroll_pointer_up(pointed_node.clone(), scene_x, scene_y);
637        }
638        if matches!(
639            event_type,
640            PointerEventType::Move | PointerEventType::Up | PointerEventType::Cancel
641        ) {
642            let captured_handle = *self.captured_pointer.borrow();
643            if let Some(captured_handle) = captured_handle {
644                if let Some(captured_node) = self.resolve_node(captured_handle) {
645                    let captured_at_dispatch = captured_handle;
646                    let handled = self.dispatch_pointer_to_node(
647                        captured_node,
648                        captured_handle,
649                        event_type,
650                        scene_x,
651                        scene_y,
652                        modifiers,
653                        pointer_id,
654                        pointer_type,
655                        button,
656                        buttons,
657                        pressure,
658                        width,
659                        height,
660                        click_count,
661                        extended,
662                    );
663                    let captured_after_dispatch = *self.captured_pointer.borrow();
664                    if matches!(event_type, PointerEventType::Up | PointerEventType::Cancel)
665                        && captured_after_dispatch == Some(captured_at_dispatch)
666                    {
667                        self.captured_pointer.replace(None);
668                    }
669                    drag_drop::handle_pointer_event(
670                        pointed_node,
671                        event_type,
672                        scene_x,
673                        scene_y,
674                        modifiers,
675                    );
676                    self.apply_current_cursor();
677                    return handled;
678                }
679                self.captured_pointer.replace(None);
680                self.apply_current_cursor();
681            }
682        }
683        if matches!(
684            event_type,
685            PointerEventType::Move | PointerEventType::Up | PointerEventType::Cancel
686        ) {
687            let mut selection_event = PointerEventArgs::new_with_metadata(
688                handle,
689                event_type,
690                scene_x,
691                scene_y,
692                modifiers,
693                pointer_id,
694                pointer_type,
695                button,
696                buttons,
697                pressure,
698                width,
699                height,
700                click_count,
701                extended,
702            );
703            if selection_handle_adorner::route_active_handle_drag_event(&mut selection_event) {
704                drag_drop::handle_pointer_event(
705                    pointed_node,
706                    event_type,
707                    scene_x,
708                    scene_y,
709                    modifiers,
710                );
711                self.apply_current_cursor();
712                return selection_event.handled;
713            }
714        }
715
716        if handle == NodeHandle::INVALID {
717            if event_type == PointerEventType::Leave {
718                self.clear_hover_stack();
719            }
720            drag_drop::handle_pointer_event(None, event_type, scene_x, scene_y, modifiers);
721            self.apply_current_cursor();
722            return false;
723        }
724
725        let Some(node) = pointed_node.clone() else {
726            if event_type == PointerEventType::Leave {
727                self.clear_hover_stack();
728            }
729            drag_drop::handle_pointer_event(None, event_type, scene_x, scene_y, modifiers);
730            self.apply_current_cursor();
731            return false;
732        };
733        if event_type == PointerEventType::Enter {
734            self.push_hover(handle);
735        } else if event_type == PointerEventType::Leave {
736            self.pop_hover(handle);
737        }
738        let handled = self.dispatch_pointer_to_node(
739            node,
740            handle,
741            event_type,
742            scene_x,
743            scene_y,
744            modifiers,
745            pointer_id,
746            pointer_type,
747            button,
748            buttons,
749            pressure,
750            width,
751            height,
752            click_count,
753            extended,
754        );
755        drag_drop::handle_pointer_event(pointed_node, event_type, scene_x, scene_y, modifiers);
756        self.apply_current_cursor();
757        handled
758    }
759
760    #[allow(clippy::too_many_arguments)]
761    fn dispatch_pointer_to_node(
762        &self,
763        node: NodeRef,
764        target_handle: NodeHandle,
765        event_type: PointerEventType,
766        scene_x: f32,
767        scene_y: f32,
768        modifiers: u32,
769        pointer_id: i32,
770        pointer_type: PointerType,
771        button: i32,
772        buttons: u32,
773        pressure: f32,
774        width: f32,
775        height: f32,
776        click_count: i32,
777        extended: PointerExtendedMetadata,
778    ) -> bool {
779        let mut event = PointerEventArgs::new_with_metadata(
780            target_handle,
781            event_type,
782            scene_x,
783            scene_y,
784            modifiers,
785            pointer_id,
786            pointer_type,
787            button,
788            buttons,
789            pressure,
790            width,
791            height,
792            click_count,
793            extended,
794        );
795        node.handle_pointer_event(&mut event);
796        let mut parent = node.parent();
797        while let Some(current) = parent {
798            if event.handled {
799                break;
800            }
801            current.handle_bubbled_pointer_event(&mut event);
802            parent = current.parent();
803        }
804        event.handled
805    }
806
807    fn capture_pointer(&self, handle: NodeHandle) {
808        if self.resolve_node(handle).is_some() {
809            self.captured_pointer.replace(Some(handle));
810            self.apply_current_cursor();
811        }
812    }
813
814    fn release_pointer(&self, handle: NodeHandle) {
815        let captured = *self.captured_pointer.borrow();
816        if captured == Some(handle) {
817            self.captured_pointer.replace(None);
818            self.apply_current_cursor();
819        }
820    }
821
822    pub(crate) fn dispatch_wheel_event(
823        &self,
824        handle: NodeHandle,
825        scene_x: f32,
826        scene_y: f32,
827        delta_x: f32,
828        delta_y: f32,
829        delta_mode: u32,
830        modifiers: u32,
831    ) -> bool {
832        let Some(mut node) = self.resolve_node(handle) else {
833            return false;
834        };
835        let mut event =
836            WheelEventArgs::new(scene_x, scene_y, delta_x, delta_y, delta_mode, modifiers);
837        loop {
838            node.handle_wheel_event(&mut event);
839            if event.handled {
840                return true;
841            }
842            let Some(parent) = node.parent() else {
843                return false;
844            };
845            node = parent;
846        }
847    }
848
849    pub(crate) fn dispatch_key_event(
850        &self,
851        event_type: KeyEventType,
852        key: String,
853        modifiers: u32,
854    ) -> bool {
855        let key_filters = self
856            .key_filters
857            .borrow()
858            .iter()
859            .map(|entry| entry.callback.clone())
860            .collect::<Vec<_>>();
861        for filter in key_filters.iter().rev() {
862            if filter(event_type, key.as_str(), modifiers) {
863                return true;
864            }
865        }
866        let Some(handle) = *self.focused.borrow() else {
867            return event_type == KeyEventType::Down
868                && handle_keyboard_scroll_fallback(key.as_str(), modifiers);
869        };
870        let Some(mut node) = self.resolve_node(handle) else {
871            return event_type == KeyEventType::Down
872                && handle_keyboard_scroll_fallback(key.as_str(), modifiers);
873        };
874        let fallback_key = key.clone();
875        let mut event = KeyEventArgs::new(event_type, key, modifiers);
876        loop {
877            node.handle_key_event(&mut event);
878            if event.handled {
879                return true;
880            }
881            let Some(parent) = node.parent() else {
882                return event_type == KeyEventType::Down
883                    && handle_keyboard_scroll_fallback(fallback_key.as_str(), modifiers);
884            };
885            node = parent;
886        }
887    }
888
889    pub(crate) fn dispatch_focus_changed(&self, handle: NodeHandle, focused: bool) {
890        if focused {
891            self.focused.replace(Some(handle));
892        } else if self.focused.borrow().as_ref() == Some(&handle) {
893            self.focused.replace(None);
894        }
895
896        if let Some(node) = self.resolve_node(handle) {
897            node.handle_focus_changed(FocusChangedEventArgs { focused });
898        }
899    }
900
901    pub(crate) fn dispatch_scroll(
902        &self,
903        handle: NodeHandle,
904        offset_x: f32,
905        offset_y: f32,
906        content_width: f32,
907        content_height: f32,
908        viewport_width: f32,
909        viewport_height: f32,
910    ) {
911        tool_tip_manager::ToolTipManager::handle_scroll();
912        selection_handle_adorner::refresh_active_geometry();
913        mobile_text_selection_toolbar::refresh_active_geometry(
914            selection_handle_adorner::is_visible(),
915        );
916        let hooks = self.scroll_hooks.borrow().clone();
917        for hook in hooks {
918            hook();
919        }
920        if let Some(node) = self.resolve_node(handle) {
921            node.handle_scroll_changed(
922                offset_x,
923                offset_y,
924                content_width,
925                content_height,
926                viewport_width,
927                viewport_height,
928            );
929        }
930    }
931
932    pub(crate) fn dispatch_selection_changed(&self, handle: NodeHandle, start: u32, end: u32) {
933        let Some(node) = self.resolve_node(handle) else {
934            selection_handle_adorner::clear();
935            mobile_text_selection_toolbar::clear();
936            return;
937        };
938        let (chrome_handle, chrome_node) = selection_chrome_target(handle, &node);
939        selection_handle_adorner::handle_selection_changed(chrome_handle, start, end);
940        mobile_text_selection_toolbar::handle_selection_changed(
941            chrome_handle,
942            &chrome_node,
943            start,
944            end,
945            selection_handle_adorner::is_visible(),
946        );
947        node.handle_selection_changed(start, end);
948    }
949
950    pub(crate) fn dispatch_text_changed(&self, handle: NodeHandle, text: String) {
951        if let Some(node) = self.resolve_node(handle) {
952            node.handle_text_changed(text);
953        }
954    }
955
956    pub(crate) fn dispatch_text_replaced(
957        &self,
958        handle: NodeHandle,
959        start: u32,
960        end: u32,
961        text: String,
962    ) {
963        if let Some(node) = self.resolve_node(handle) {
964            node.handle_text_replaced(start, end, text);
965        }
966    }
967
968    pub(crate) fn dispatch_cross_selection_changed(&self, handle: NodeHandle, text: String) {
969        crate::context_menu_manager::handle_selection_changed(&text);
970        let Some(node) = self.resolve_node(handle) else {
971            selection_handle_adorner::clear();
972            mobile_text_selection_toolbar::clear();
973            return;
974        };
975        selection_handle_adorner::handle_cross_selection_changed(handle, &text);
976        mobile_text_selection_toolbar::handle_cross_selection_changed(
977            handle,
978            &node,
979            &text,
980            selection_handle_adorner::is_visible(),
981        );
982        node.handle_cross_selection_changed(text);
983    }
984
985    pub(crate) fn resolve_gesture_owner(&self, handle: NodeHandle) -> NodeHandle {
986        let mut node = self.resolve_node(handle);
987        while let Some(current) = node {
988            if current.is_effectively_enabled_for_routing()
989                && current.is_effectively_visible_for_routing()
990                && current.gesture_intent_for_routing() != GestureIntent::None
991            {
992                return current.handle();
993            }
994            node = current.parent();
995        }
996        NodeHandle::INVALID
997    }
998
999    pub(crate) fn get_gesture_intent(&self, handle: NodeHandle) -> GestureIntent {
1000        self.resolve_node(handle)
1001            .map(|node| node.gesture_intent_for_routing())
1002            .unwrap_or(GestureIntent::None)
1003    }
1004
1005    #[allow(clippy::too_many_arguments)]
1006    pub(crate) fn dispatch_gesture_event(
1007        &self,
1008        handle: NodeHandle,
1009        phase: GestureEventPhase,
1010        kind: GestureEventKind,
1011        scene_x: f32,
1012        scene_y: f32,
1013        delta_x: f32,
1014        delta_y: f32,
1015        scale: f32,
1016        pointer_count: i32,
1017    ) -> bool {
1018        let Some(node) = self.resolve_node(handle) else {
1019            return false;
1020        };
1021        let mut event = GestureEventArgs::new(
1022            phase,
1023            kind,
1024            scene_x,
1025            scene_y,
1026            delta_x,
1027            delta_y,
1028            scale,
1029            pointer_count,
1030        );
1031        node.handle_gesture_event(&mut event);
1032        let mut parent = node.parent();
1033        while let Some(current) = parent {
1034            if event.handled {
1035                break;
1036            }
1037            current.handle_bubbled_gesture_event(&mut event);
1038            parent = current.parent();
1039        }
1040        event.handled
1041    }
1042
1043    pub(crate) fn resolve_long_press_owner(&self, handle: NodeHandle) -> NodeHandle {
1044        let mut node = self.resolve_node(handle);
1045        while let Some(current) = node {
1046            if current.is_effectively_enabled_for_routing()
1047                && current.is_effectively_visible_for_routing()
1048                && current.has_long_press_for_routing()
1049            {
1050                return current.handle();
1051            }
1052            node = current.parent();
1053        }
1054        let mut node = self.resolve_node(handle);
1055        while let Some(current) = node {
1056            if current.is_effectively_enabled_for_routing()
1057                && current.is_effectively_visible_for_routing()
1058                && (current.link_url_for_routing().is_some()
1059                    || current.is_image_or_svg_for_routing()
1060                    || self.has_image_context_menu_target_descendant(&current))
1061            {
1062                return current.handle();
1063            }
1064            if current.is_effectively_enabled_for_routing()
1065                && current.is_effectively_visible_for_routing()
1066                && (current.is_selectable_text_for_routing()
1067                    || current.is_editable_text_for_routing())
1068            {
1069                return current.handle();
1070            }
1071            node = current.parent();
1072        }
1073        NodeHandle::INVALID
1074    }
1075
1076    fn has_image_context_menu_target_descendant(&self, node: &NodeRef) -> bool {
1077        for child in node.children() {
1078            if child.is_image_or_svg_for_routing()
1079                || self.has_image_context_menu_target_descendant(&child)
1080            {
1081                return true;
1082            }
1083        }
1084        false
1085    }
1086
1087    pub(crate) fn get_long_press_minimum_duration_ms(&self, handle: NodeHandle) -> i32 {
1088        self.resolve_node(handle)
1089            .map(|node| node.long_press_minimum_duration_ms_for_routing())
1090            .unwrap_or(DEFAULT_LONG_PRESS_MINIMUM_DURATION_MS)
1091    }
1092
1093    pub(crate) fn get_long_press_movement_tolerance(&self, handle: NodeHandle) -> f32 {
1094        self.resolve_node(handle)
1095            .map(|node| node.long_press_movement_tolerance_for_routing())
1096            .unwrap_or(DEFAULT_LONG_PRESS_MOVEMENT_TOLERANCE)
1097    }
1098
1099    pub(crate) fn dispatch_long_press_event(
1100        &self,
1101        handle: NodeHandle,
1102        scene_x: f32,
1103        scene_y: f32,
1104        pointer_id: i32,
1105        pointer_type: PointerType,
1106        modifiers: u32,
1107        duration_ms: i32,
1108    ) -> bool {
1109        let Some(node) = self.resolve_node(handle) else {
1110            return false;
1111        };
1112        let mut event = LongPressEventArgs::new(
1113            scene_x,
1114            scene_y,
1115            pointer_id,
1116            pointer_type,
1117            modifiers,
1118            duration_ms,
1119        );
1120        node.handle_long_press_event(&mut event);
1121        let mut parent = node.parent();
1122        while let Some(current) = parent {
1123            if event.handled {
1124                break;
1125            }
1126            current.handle_bubbled_long_press_event(&mut event);
1127            parent = current.parent();
1128        }
1129        if !event.handled
1130            && matches!(pointer_type, PointerType::Touch | PointerType::Pen)
1131            && (node.is_selectable_text_for_routing() || node.is_editable_text_for_routing())
1132        {
1133            mobile_text_selection_toolbar::set_pending_cross_selection_text_handle(handle);
1134            event.handled = crate::bindings::ui::select_word_at(handle.raw(), scene_x, scene_y);
1135            if !event.handled {
1136                mobile_text_selection_toolbar::set_pending_cross_selection_text_handle(
1137                    NodeHandle::INVALID,
1138                );
1139            }
1140        }
1141        if !event.handled {
1142            event.handled =
1143                context_menu_manager::show_for_long_press(handle.raw(), scene_x, scene_y);
1144        }
1145        event.handled
1146    }
1147
1148    pub(crate) fn dispatch_external_drop_event(
1149        &self,
1150        handle: NodeHandle,
1151        event_type: external_drop::ExternalDragEventType,
1152        x: f32,
1153        y: f32,
1154        modifiers: u32,
1155        items: Vec<external_drop::ExternalDropItemInfo>,
1156    ) -> crate::drag_drop::DragDropEffects {
1157        let pointed_node = if handle == NodeHandle::INVALID {
1158            None
1159        } else {
1160            self.resolve_node(handle)
1161        };
1162        external_drop::handle_event(pointed_node, event_type, x, y, modifiers, items)
1163    }
1164
1165    pub(crate) fn resolve_node(&self, handle: NodeHandle) -> Option<NodeRef> {
1166        self.nodes
1167            .borrow()
1168            .get(&handle)
1169            .and_then(WeakNodeRef::upgrade)
1170    }
1171
1172    fn handle_cursor_style_changed(&self, handle: NodeHandle) {
1173        let captured = *self.captured_pointer.borrow();
1174        if captured == Some(handle) {
1175            self.apply_current_cursor();
1176            return;
1177        }
1178        if captured.is_some() {
1179            return;
1180        }
1181        if self.hover_stack.borrow().last().copied() == Some(handle) {
1182            self.apply_current_cursor();
1183        }
1184    }
1185
1186    fn push_hover(&self, handle: NodeHandle) {
1187        let mut hover_stack = self.hover_stack.borrow_mut();
1188        if let Some(index) = hover_stack.iter().position(|hovered| *hovered == handle) {
1189            hover_stack.remove(index);
1190        }
1191        hover_stack.push(handle);
1192    }
1193
1194    fn pop_hover(&self, handle: NodeHandle) {
1195        let mut hover_stack = self.hover_stack.borrow_mut();
1196        if let Some(index) = hover_stack.iter().position(|hovered| *hovered == handle) {
1197            hover_stack.remove(index);
1198        }
1199    }
1200
1201    fn clear_hover_stack(&self) {
1202        self.hover_stack.borrow_mut().clear();
1203    }
1204
1205    fn apply_current_cursor(&self) {
1206        let drag_cursor = drag_drop::cursor_override_style();
1207        let style = if drag_cursor != CursorStyle::Default {
1208            drag_cursor
1209        } else if let Some(handle) = *self.captured_pointer.borrow() {
1210            self.resolve_node(handle)
1211                .map(|node| node.cursor_style_for_routing())
1212                .unwrap_or(CursorStyle::Default)
1213        } else if let Some(handle) = *self.selection_cursor_owner.borrow() {
1214            self.resolve_node(handle)
1215                .map(|node| node.cursor_style_for_routing())
1216                .unwrap_or(CursorStyle::Default)
1217        } else if let Some(handle) = self.hover_stack.borrow().last().copied() {
1218            self.resolve_node(handle)
1219                .map(|node| node.cursor_style_for_routing())
1220                .unwrap_or(CursorStyle::Default)
1221        } else {
1222            CursorStyle::Default
1223        };
1224        self.apply_cursor(style);
1225    }
1226
1227    fn apply_cursor(&self, style: CursorStyle) {
1228        if self.current_cursor_style.get() == style {
1229            return;
1230        }
1231        self.current_cursor_style.set(style);
1232        unsafe { crate::ffi::fui_set_cursor(style as u32) };
1233    }
1234}
1235
1236fn selection_cursor_target(node: &NodeRef) -> Option<NodeRef> {
1237    if !node.is_effectively_enabled_for_routing() || !node.is_effectively_visible_for_routing() {
1238        return None;
1239    }
1240    if node.is_selectable_text_for_routing() || node.is_editable_text_for_routing() {
1241        return Some(node.clone());
1242    }
1243    find_editable_text_descendant(node)
1244}
1245
1246fn selection_chrome_target(handle: NodeHandle, node: &NodeRef) -> (NodeHandle, NodeRef) {
1247    if node.is_selectable_text_for_routing() || node.is_editable_text_for_routing() {
1248        return (handle, node.clone());
1249    }
1250    if let Some(editor) = find_editable_text_descendant(node) {
1251        return (editor.handle(), editor);
1252    }
1253    (handle, node.clone())
1254}
1255
1256fn find_editable_text_descendant(node: &NodeRef) -> Option<NodeRef> {
1257    for child in node.children() {
1258        if child.is_editable_text_for_routing() {
1259            return Some(child);
1260        }
1261        if let Some(descendant) = find_editable_text_descendant(&child) {
1262            return Some(descendant);
1263        }
1264    }
1265    None
1266}
1267
1268fn preserves_selection_on_pointer_down_for_routing(node: Option<&NodeRef>) -> bool {
1269    let mut current = node.cloned();
1270    while let Some(value) = current {
1271        if value.preserves_selection_on_pointer_down_for_routing() {
1272            return true;
1273        }
1274        current = value.parent();
1275    }
1276    false
1277}
1278
1279thread_local! {
1280    static EVENT_ROUTER: EventRouter = EventRouter::new();
1281}
1282
1283fn with_event_router<T>(callback: impl FnOnce(&EventRouter) -> T) -> T {
1284    EVENT_ROUTER.with(callback)
1285}
1286
1287pub(crate) fn register_node(node: &NodeRef) {
1288    with_event_router(|router| router.register(node));
1289}
1290
1291pub(crate) fn unregister_node(handle: NodeHandle) {
1292    with_event_router(|router| router.unregister(handle));
1293}
1294
1295pub(crate) fn reset() {
1296    with_event_router(|router| router.reset());
1297}
1298
1299pub(crate) fn resolve_node(handle: NodeHandle) -> Option<NodeRef> {
1300    with_event_router(|router| router.resolve_node(handle))
1301}
1302
1303pub(crate) fn focused_node() -> Option<NodeRef> {
1304    with_event_router(|router| {
1305        let handle = *router.focused.borrow();
1306        handle.and_then(|handle| router.resolve_node(handle))
1307    })
1308}
1309
1310pub(crate) fn capture_pointer(handle: NodeHandle) {
1311    with_event_router(|router| router.capture_pointer(handle));
1312}
1313
1314pub(crate) fn release_pointer(handle: NodeHandle) {
1315    with_event_router(|router| router.release_pointer(handle));
1316}
1317
1318pub(crate) fn deactivate_subtree(root: &NodeRef) {
1319    fn belongs_to_subtree(node: &NodeRef, root: &NodeRef) -> bool {
1320        let mut current = Some(node.clone());
1321        while let Some(value) = current {
1322            if value.ptr_eq(root) {
1323                return true;
1324            }
1325            current = value.parent();
1326        }
1327        false
1328    }
1329    with_event_router(|router| {
1330        if router
1331            .captured_pointer
1332            .borrow()
1333            .and_then(|handle| router.resolve_node(handle))
1334            .is_some_and(|node| belongs_to_subtree(&node, root))
1335        {
1336            router.captured_pointer.replace(None);
1337        }
1338        if router
1339            .focused
1340            .borrow()
1341            .and_then(|handle| router.resolve_node(handle))
1342            .is_some_and(|node| belongs_to_subtree(&node, root))
1343        {
1344            router.focused.replace(None);
1345            crate::bindings::ui::request_focus(NodeHandle::INVALID.raw());
1346        }
1347        router.hover_stack.borrow_mut().retain(|handle| {
1348            router
1349                .resolve_node(*handle)
1350                .is_none_or(|node| !belongs_to_subtree(&node, root))
1351        });
1352        router.selection_cursor_owner.replace(None);
1353        drag_drop::reset();
1354        external_drop::reset();
1355        router.apply_current_cursor();
1356    });
1357    crate::context_menu_manager::hide_active_menu();
1358    crate::tool_tip_manager::ToolTipManager::clear();
1359    crate::selection_handle_adorner::clear();
1360    crate::mobile_text_selection_toolbar::clear();
1361}
1362
1363pub(crate) fn handle_cursor_style_changed(handle: NodeHandle) {
1364    with_event_router(|router| router.handle_cursor_style_changed(handle));
1365}
1366
1367pub(crate) fn begin_drag_session(source: NodeRef) -> bool {
1368    drag_drop::begin_session(source)
1369}
1370
1371pub(crate) fn cancel_drag_session(session: crate::drag_drop::DragSession) {
1372    drag_drop::cancel_session(session);
1373}
1374
1375pub(crate) fn cancel_drag_session_for_source(source: &NodeRef) {
1376    drag_drop::cancel_session_for_source(source);
1377}
1378
1379pub(crate) fn dispatch_pointer_event(
1380    handle: NodeHandle,
1381    event_type: PointerEventType,
1382    scene_x: f32,
1383    scene_y: f32,
1384    modifiers: u32,
1385    pointer_id: i32,
1386    pointer_type: PointerType,
1387    button: i32,
1388    buttons: u32,
1389    pressure: f32,
1390    width: f32,
1391    height: f32,
1392    click_count: i32,
1393) -> bool {
1394    focus_visibility::show_keyboard_focus_for_pointer_event(event_type);
1395    with_event_router(|router| {
1396        router.dispatch_pointer_event(
1397            handle,
1398            event_type,
1399            scene_x,
1400            scene_y,
1401            modifiers,
1402            pointer_id,
1403            pointer_type,
1404            button,
1405            buttons,
1406            pressure,
1407            width,
1408            height,
1409            click_count,
1410        )
1411    })
1412}
1413
1414#[allow(clippy::too_many_arguments)]
1415pub(crate) fn dispatch_pointer_event_with_metadata(
1416    handle: NodeHandle,
1417    event_type: PointerEventType,
1418    scene_x: f32,
1419    scene_y: f32,
1420    modifiers: u32,
1421    pointer_id: i32,
1422    pointer_type: PointerType,
1423    button: i32,
1424    buttons: u32,
1425    pressure: f32,
1426    width: f32,
1427    height: f32,
1428    click_count: i32,
1429    extended: PointerExtendedMetadata,
1430) -> bool {
1431    focus_visibility::show_keyboard_focus_for_pointer_event(event_type);
1432    with_event_router(|router| {
1433        router.dispatch_pointer_event_with_metadata(
1434            handle,
1435            event_type,
1436            scene_x,
1437            scene_y,
1438            modifiers,
1439            pointer_id,
1440            pointer_type,
1441            button,
1442            buttons,
1443            pressure,
1444            width,
1445            height,
1446            click_count,
1447            extended,
1448        )
1449    })
1450}
1451
1452pub(crate) fn dispatch_wheel_event(
1453    handle: NodeHandle,
1454    scene_x: f32,
1455    scene_y: f32,
1456    delta_x: f32,
1457    delta_y: f32,
1458    delta_mode: u32,
1459    modifiers: u32,
1460) -> bool {
1461    with_event_router(|router| {
1462        router.dispatch_wheel_event(
1463            handle, scene_x, scene_y, delta_x, delta_y, delta_mode, modifiers,
1464        )
1465    })
1466}
1467
1468pub(crate) fn dispatch_key_event(event_type: KeyEventType, key: String, modifiers: u32) -> bool {
1469    let handled =
1470        with_event_router(|router| router.dispatch_key_event(event_type, key.clone(), modifiers));
1471    if !handled || key.as_str() != "Tab" || modifiers != 0 {
1472        focus_visibility::show_keyboard_focus_for_key_event(event_type, key.as_str(), modifiers);
1473    }
1474    handled
1475}
1476
1477pub(crate) fn dispatch_focus_changed(handle: NodeHandle, focused: bool) {
1478    with_event_router(|router| router.dispatch_focus_changed(handle, focused));
1479}
1480
1481pub(crate) fn push_key_filter(callback: impl Fn(KeyEventType, &str, u32) -> bool + 'static) -> u32 {
1482    with_event_router(|router| router.push_key_filter(Rc::new(callback)))
1483}
1484
1485pub(crate) fn remove_key_filter(token: u32) {
1486    with_event_router(|router| router.remove_key_filter(token));
1487}
1488
1489pub(crate) fn register_scroll_hook(callback: impl Fn() + 'static) {
1490    with_event_router(|router| router.register_scroll_hook(Rc::new(callback)));
1491}
1492
1493pub(crate) fn focused_node_is_enabled_button() -> bool {
1494    with_event_router(|router| router.focused_node_is_enabled_button())
1495}
1496
1497pub(crate) fn dispatch_scroll(
1498    handle: NodeHandle,
1499    offset_x: f32,
1500    offset_y: f32,
1501    content_width: f32,
1502    content_height: f32,
1503    viewport_width: f32,
1504    viewport_height: f32,
1505) {
1506    with_event_router(|router| {
1507        router.dispatch_scroll(
1508            handle,
1509            offset_x,
1510            offset_y,
1511            content_width,
1512            content_height,
1513            viewport_width,
1514            viewport_height,
1515        )
1516    });
1517}
1518
1519pub(crate) fn dispatch_selection_changed(handle: NodeHandle, start: u32, end: u32) {
1520    with_event_router(|router| router.dispatch_selection_changed(handle, start, end));
1521}
1522
1523pub(crate) fn dispatch_text_changed(handle: NodeHandle, text: String) {
1524    with_event_router(|router| router.dispatch_text_changed(handle, text));
1525}
1526
1527pub(crate) fn dispatch_text_replaced(handle: NodeHandle, start: u32, end: u32, text: String) {
1528    with_event_router(|router| router.dispatch_text_replaced(handle, start, end, text));
1529}
1530
1531pub(crate) fn dispatch_cross_selection_changed(handle: NodeHandle, text: String) {
1532    with_event_router(|router| router.dispatch_cross_selection_changed(handle, text));
1533}
1534
1535pub(crate) fn resolve_gesture_owner(handle: NodeHandle) -> NodeHandle {
1536    with_event_router(|router| router.resolve_gesture_owner(handle))
1537}
1538
1539pub(crate) fn get_gesture_intent(handle: NodeHandle) -> GestureIntent {
1540    with_event_router(|router| router.get_gesture_intent(handle))
1541}
1542
1543#[allow(clippy::too_many_arguments)]
1544pub(crate) fn dispatch_gesture_event(
1545    handle: NodeHandle,
1546    phase: GestureEventPhase,
1547    kind: GestureEventKind,
1548    scene_x: f32,
1549    scene_y: f32,
1550    delta_x: f32,
1551    delta_y: f32,
1552    scale: f32,
1553    pointer_count: i32,
1554) -> bool {
1555    with_event_router(|router| {
1556        router.dispatch_gesture_event(
1557            handle,
1558            phase,
1559            kind,
1560            scene_x,
1561            scene_y,
1562            delta_x,
1563            delta_y,
1564            scale,
1565            pointer_count,
1566        )
1567    })
1568}
1569
1570pub(crate) fn resolve_long_press_owner(handle: NodeHandle) -> NodeHandle {
1571    with_event_router(|router| router.resolve_long_press_owner(handle))
1572}
1573
1574pub(crate) fn registered_node(handle: u64) -> Option<NodeRef> {
1575    with_event_router(|router| router.resolve_node(NodeHandle::from_raw(handle)))
1576}
1577
1578pub(crate) fn get_long_press_minimum_duration_ms(handle: NodeHandle) -> i32 {
1579    with_event_router(|router| router.get_long_press_minimum_duration_ms(handle))
1580}
1581
1582pub(crate) fn get_long_press_movement_tolerance(handle: NodeHandle) -> f32 {
1583    with_event_router(|router| router.get_long_press_movement_tolerance(handle))
1584}
1585
1586pub(crate) fn dispatch_long_press_event(
1587    handle: NodeHandle,
1588    scene_x: f32,
1589    scene_y: f32,
1590    pointer_id: i32,
1591    pointer_type: PointerType,
1592    modifiers: u32,
1593    duration_ms: i32,
1594) -> bool {
1595    with_event_router(|router| {
1596        router.dispatch_long_press_event(
1597            handle,
1598            scene_x,
1599            scene_y,
1600            pointer_id,
1601            pointer_type,
1602            modifiers,
1603            duration_ms,
1604        )
1605    })
1606}
1607
1608pub(crate) fn dispatch_external_drop_event(
1609    handle: NodeHandle,
1610    event_type: external_drop::ExternalDragEventType,
1611    x: f32,
1612    y: f32,
1613    modifiers: u32,
1614    items: Vec<external_drop::ExternalDropItemInfo>,
1615) -> crate::drag_drop::DragDropEffects {
1616    with_event_router(|router| {
1617        router.dispatch_external_drop_event(handle, event_type, x, y, modifiers, items)
1618    })
1619}
1620
1621#[cfg_attr(
1622    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1623    no_mangle
1624)]
1625pub extern "C" fn __fui_key_buffer() -> *const u8 {
1626    std::ptr::addr_of_mut!(KEY_BUFFER).cast::<u8>()
1627}
1628
1629#[cfg_attr(
1630    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1631    no_mangle
1632)]
1633pub extern "C" fn __fui_key_buffer_size() -> u32 {
1634    256
1635}
1636
1637#[cfg_attr(
1638    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1639    no_mangle
1640)]
1641pub extern "C" fn __fui_text_buffer() -> *const u8 {
1642    std::ptr::addr_of_mut!(TEXT_BUFFER).cast::<u8>()
1643}
1644
1645#[cfg_attr(
1646    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1647    no_mangle
1648)]
1649pub extern "C" fn __fui_text_buffer_size() -> u32 {
1650    (16 * 1024) as u32
1651}
1652
1653#[cfg_attr(
1654    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1655    no_mangle
1656)]
1657pub extern "C" fn __fui_on_pointer_event_with_metadata(
1658    event_type: u32,
1659    handle: u64,
1660    x: f32,
1661    y: f32,
1662    modifiers: u32,
1663    pointer_id: i32,
1664    pointer_type: u32,
1665    button: i32,
1666    buttons: u32,
1667    pressure: f32,
1668    width: f32,
1669    height: f32,
1670    click_count: i32,
1671    is_primary: bool,
1672    tangential_pressure: f32,
1673    tilt_x: f32,
1674    tilt_y: f32,
1675    twist: f32,
1676) -> bool {
1677    let routed_event_type = match event_type {
1678        1 => PointerEventType::Down,
1679        2 => PointerEventType::Up,
1680        3 => PointerEventType::Move,
1681        4 => PointerEventType::Enter,
1682        5 => PointerEventType::Leave,
1683        _ => PointerEventType::Cancel,
1684    };
1685    crate::context_menu_manager::handle_pointer_selection_event(
1686        routed_event_type == PointerEventType::Down,
1687        handle,
1688    );
1689    dispatch_pointer_event_with_metadata(
1690        NodeHandle::from_raw(handle),
1691        routed_event_type,
1692        x,
1693        y,
1694        modifiers,
1695        pointer_id,
1696        PointerType::from_raw(pointer_type),
1697        button,
1698        buttons,
1699        pressure,
1700        width,
1701        height,
1702        click_count,
1703        PointerExtendedMetadata {
1704            is_primary,
1705            tangential_pressure,
1706            tilt_x,
1707            tilt_y,
1708            twist,
1709        },
1710    )
1711}
1712
1713#[cfg_attr(
1714    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1715    no_mangle
1716)]
1717pub extern "C" fn __fui_on_wheel_event(
1718    handle: u64,
1719    x: f32,
1720    y: f32,
1721    delta_x: f32,
1722    delta_y: f32,
1723    delta_mode: u32,
1724    modifiers: u32,
1725) -> bool {
1726    dispatch_wheel_event(
1727        NodeHandle::from_raw(handle),
1728        x,
1729        y,
1730        delta_x,
1731        delta_y,
1732        delta_mode,
1733        modifiers,
1734    )
1735}
1736
1737#[cfg_attr(
1738    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1739    no_mangle
1740)]
1741/// # Safety
1742/// `key_ptr` must be null for an empty key or point to `key_len` readable bytes.
1743pub unsafe extern "C" fn __fui_on_key_event(
1744    event_type: u32,
1745    key_ptr: *const u8,
1746    key_len: u32,
1747    modifiers: u32,
1748) -> bool {
1749    let key = if key_ptr.is_null() || key_len == 0 {
1750        String::new()
1751    } else {
1752        let bytes = unsafe { std::slice::from_raw_parts(key_ptr, key_len as usize) };
1753        String::from_utf8_lossy(bytes).into_owned()
1754    };
1755    dispatch_key_event(
1756        match event_type {
1757            2 => KeyEventType::Up,
1758            _ => KeyEventType::Down,
1759        },
1760        key,
1761        modifiers,
1762    )
1763}
1764
1765#[cfg_attr(
1766    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1767    no_mangle
1768)]
1769pub extern "C" fn __fui_on_focus_changed(handle: u64, focused: bool) {
1770    dispatch_focus_changed(NodeHandle::from_raw(handle), focused);
1771}
1772
1773#[cfg_attr(
1774    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1775    no_mangle
1776)]
1777/// # Safety
1778/// `text_ptr` must be null for empty text or point to `text_len` readable bytes.
1779pub unsafe extern "C" fn __fui_on_text_changed(handle: u64, text_ptr: *const u8, text_len: u32) {
1780    let text = if text_ptr.is_null() || text_len == 0 {
1781        String::new()
1782    } else {
1783        let bytes = unsafe { std::slice::from_raw_parts(text_ptr, text_len as usize) };
1784        String::from_utf8_lossy(bytes).into_owned()
1785    };
1786    dispatch_text_changed(NodeHandle::from_raw(handle), text);
1787}
1788
1789#[cfg_attr(
1790    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1791    no_mangle
1792)]
1793/// # Safety
1794/// `text_ptr` must be null for empty replacement text or point to `text_len` readable bytes.
1795pub unsafe extern "C" fn __fui_on_text_replaced(
1796    handle: u64,
1797    start: u32,
1798    end: u32,
1799    text_ptr: *const u8,
1800    text_len: u32,
1801) {
1802    let text = if text_ptr.is_null() || text_len == 0 {
1803        String::new()
1804    } else {
1805        let bytes = unsafe { std::slice::from_raw_parts(text_ptr, text_len as usize) };
1806        String::from_utf8_lossy(bytes).into_owned()
1807    };
1808    dispatch_text_replaced(NodeHandle::from_raw(handle), start, end, text);
1809}
1810
1811#[cfg_attr(
1812    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1813    no_mangle
1814)]
1815pub extern "C" fn __fui_on_selection_changed(handle: u64, start: u32, end: u32) {
1816    dispatch_selection_changed(NodeHandle::from_raw(handle), start, end);
1817}
1818
1819#[cfg_attr(
1820    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1821    no_mangle
1822)]
1823/// # Safety
1824/// `text_ptr` must be null for empty text or point to `text_len` readable bytes.
1825pub unsafe extern "C" fn __fui_on_cross_selection_changed(
1826    handle: u64,
1827    text_ptr: *const u8,
1828    text_len: u32,
1829) {
1830    let text = if text_ptr.is_null() || text_len == 0 {
1831        String::new()
1832    } else {
1833        let bytes = unsafe { std::slice::from_raw_parts(text_ptr, text_len as usize) };
1834        String::from_utf8_lossy(bytes).into_owned()
1835    };
1836    dispatch_cross_selection_changed(NodeHandle::from_raw(handle), text);
1837}
1838
1839#[cfg_attr(
1840    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1841    no_mangle
1842)]
1843pub extern "C" fn __fui_resolve_gesture_owner(handle: u64) -> u64 {
1844    resolve_gesture_owner(NodeHandle::from_raw(handle)).raw()
1845}
1846
1847#[cfg_attr(
1848    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1849    no_mangle
1850)]
1851pub extern "C" fn __fui_get_gesture_intent(handle: u64) -> u32 {
1852    get_gesture_intent(NodeHandle::from_raw(handle)) as u32
1853}
1854
1855#[cfg_attr(
1856    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1857    no_mangle
1858)]
1859pub extern "C" fn __fui_on_gesture_event(
1860    handle: u64,
1861    phase: u32,
1862    kind: u32,
1863    scene_x: f32,
1864    scene_y: f32,
1865    delta_x: f32,
1866    delta_y: f32,
1867    scale: f32,
1868    pointer_count: i32,
1869) -> bool {
1870    dispatch_gesture_event(
1871        NodeHandle::from_raw(handle),
1872        GestureEventPhase::from_raw(phase),
1873        GestureEventKind::from_raw(kind),
1874        scene_x,
1875        scene_y,
1876        delta_x,
1877        delta_y,
1878        scale,
1879        pointer_count,
1880    )
1881}
1882
1883#[cfg_attr(
1884    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1885    no_mangle
1886)]
1887pub extern "C" fn __fui_resolve_long_press_owner(handle: u64) -> u64 {
1888    resolve_long_press_owner(NodeHandle::from_raw(handle)).raw()
1889}
1890
1891#[cfg_attr(
1892    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1893    no_mangle
1894)]
1895pub extern "C" fn __fui_get_long_press_minimum_duration_ms(handle: u64) -> i32 {
1896    get_long_press_minimum_duration_ms(NodeHandle::from_raw(handle))
1897}
1898
1899#[cfg_attr(
1900    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1901    no_mangle
1902)]
1903pub extern "C" fn __fui_get_long_press_movement_tolerance(handle: u64) -> f32 {
1904    get_long_press_movement_tolerance(NodeHandle::from_raw(handle))
1905}
1906
1907#[cfg_attr(
1908    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1909    no_mangle
1910)]
1911pub extern "C" fn __fui_long_press_continues_pointer_events(handle: u64) -> bool {
1912    registered_node(handle).is_some_and(|node| node.has_drag_source())
1913}
1914
1915#[cfg_attr(
1916    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1917    no_mangle
1918)]
1919pub extern "C" fn __fui_on_long_press_event(
1920    handle: u64,
1921    scene_x: f32,
1922    scene_y: f32,
1923    pointer_id: i32,
1924    pointer_type: u32,
1925    modifiers: u32,
1926    duration_ms: i32,
1927) -> bool {
1928    dispatch_long_press_event(
1929        NodeHandle::from_raw(handle),
1930        scene_x,
1931        scene_y,
1932        pointer_id,
1933        PointerType::from_raw(pointer_type),
1934        modifiers,
1935        duration_ms,
1936    )
1937}
1938
1939#[cfg_attr(
1940    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1941    no_mangle
1942)]
1943/// # Safety
1944/// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1945pub unsafe extern "C" fn __fui_on_external_drag_event(
1946    event_type: u32,
1947    handle: u64,
1948    x: f32,
1949    y: f32,
1950    modifiers: u32,
1951    payload_ptr: *const u8,
1952    payload_len: u32,
1953) -> u32 {
1954    let items = external_drop::decode_payload(payload_ptr, payload_len);
1955    let effect = dispatch_external_drop_event(
1956        NodeHandle::from_raw(handle),
1957        external_drop::ExternalDragEventType::from_raw(event_type),
1958        x,
1959        y,
1960        modifiers,
1961        items.clone(),
1962    );
1963    if !payload_ptr.is_null() && payload_len > 0 && items.is_empty() {
1964        crate::logger::error(
1965            "ExternalDrop",
1966            &format!(
1967                "Dropped malformed external payload for handle {}.",
1968                NodeHandle::from_raw(handle).raw()
1969            ),
1970        );
1971    }
1972    effect as u32
1973}
1974
1975#[cfg_attr(
1976    any(not(feature = "worker-runtime"), feature = "native-runtime"),
1977    no_mangle
1978)]
1979pub extern "C" fn fui_dispatch_custom_draw(handle: u64, canvas_ptr: usize) {
1980    if let Some(node) = resolve_node(NodeHandle::from_raw(handle)) {
1981        node.handle_custom_draw(canvas_ptr);
1982    }
1983}