@effindomv2/fui-rs 0.1.3

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