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