@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
@@ -0,0 +1,838 @@
1
+ use crate::bindings::ui;
2
+ use crate::event::{PointerEventArgs, PointerType};
3
+ use crate::ffi::{PointerEventType, PositionType, Unit, Visibility};
4
+ use crate::node::{flex_box, portal, FlexBox, Node, NodeHandle};
5
+ use std::cell::RefCell;
6
+
7
+ const HANDLE_COLOR: u32 = 0x0A84FFFF;
8
+ const HIT_TARGET_SIZE: f32 = 90.0;
9
+ const HIT_TARGET_PADDING: f32 = 25.0;
10
+ const START_ANCHOR_WIDTH: f32 = 0.0;
11
+ const KNOB_SIZE: f32 = 18.0;
12
+ const SHOULDER_SIZE: f32 = 8.0;
13
+ const START_STEM_X: f32 = 72.0;
14
+ const END_STEM_X: f32 = 18.0;
15
+
16
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
17
+ enum SelectionHandleSide {
18
+ Start,
19
+ End,
20
+ }
21
+
22
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
23
+ enum SelectionHandleDragSide {
24
+ None,
25
+ Start,
26
+ End,
27
+ }
28
+
29
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
30
+ #[allow(dead_code)]
31
+ pub(crate) enum SelectionHandleMode {
32
+ Auto,
33
+ Always,
34
+ Disabled,
35
+ }
36
+
37
+ struct State {
38
+ host_root: Option<FlexBox>,
39
+ start_handle: Option<FlexBox>,
40
+ end_handle: Option<FlexBox>,
41
+ attached: bool,
42
+ active_handle: NodeHandle,
43
+ active_start: u32,
44
+ active_end: u32,
45
+ active_uses_range_geometry: bool,
46
+ active_uses_cross_geometry: bool,
47
+ mode_value: SelectionHandleMode,
48
+ last_pointer_type: PointerType,
49
+ dragging_side: SelectionHandleDragSide,
50
+ dragging_captured_visual_side: SelectionHandleDragSide,
51
+ start_anchor: Option<(f32, f32)>,
52
+ end_anchor: Option<(f32, f32)>,
53
+ stationary_anchor: Option<(f32, f32)>,
54
+ }
55
+
56
+ impl State {
57
+ fn new() -> Self {
58
+ Self {
59
+ host_root: None,
60
+ start_handle: None,
61
+ end_handle: None,
62
+ attached: false,
63
+ active_handle: NodeHandle::INVALID,
64
+ active_start: 0,
65
+ active_end: 0,
66
+ active_uses_range_geometry: false,
67
+ active_uses_cross_geometry: false,
68
+ mode_value: SelectionHandleMode::Auto,
69
+ last_pointer_type: PointerType::Unknown,
70
+ dragging_side: SelectionHandleDragSide::None,
71
+ dragging_captured_visual_side: SelectionHandleDragSide::None,
72
+ start_anchor: None,
73
+ end_anchor: None,
74
+ stationary_anchor: None,
75
+ }
76
+ }
77
+ }
78
+
79
+ thread_local! {
80
+ static STATE: RefCell<State> = RefCell::new(State::new());
81
+ }
82
+
83
+ fn handle_stem_x(side: SelectionHandleSide) -> f32 {
84
+ match side {
85
+ SelectionHandleSide::Start => START_STEM_X,
86
+ SelectionHandleSide::End => END_STEM_X,
87
+ }
88
+ }
89
+
90
+ fn handle_knob_x(side: SelectionHandleSide) -> f32 {
91
+ match side {
92
+ SelectionHandleSide::Start => handle_stem_x(side) - KNOB_SIZE + START_ANCHOR_WIDTH,
93
+ SelectionHandleSide::End => handle_stem_x(side),
94
+ }
95
+ }
96
+
97
+ fn handle_shoulder_x(side: SelectionHandleSide) -> f32 {
98
+ let knob_x = handle_knob_x(side);
99
+ match side {
100
+ SelectionHandleSide::Start => knob_x + KNOB_SIZE - SHOULDER_SIZE,
101
+ SelectionHandleSide::End => knob_x,
102
+ }
103
+ }
104
+
105
+ fn make_knob(side: SelectionHandleSide) -> FlexBox {
106
+ let knob = flex_box();
107
+ knob.position_type(PositionType::Absolute)
108
+ .position(handle_knob_x(side), HIT_TARGET_PADDING)
109
+ .width(KNOB_SIZE, Unit::Pixel)
110
+ .height(KNOB_SIZE, Unit::Pixel)
111
+ .corner_radius(KNOB_SIZE * 0.5)
112
+ .bg_color(HANDLE_COLOR)
113
+ .border(1.0, 0x00000022)
114
+ .preserve_selection_on_pointer_down(true);
115
+ knob
116
+ }
117
+
118
+ fn make_shoulder(side: SelectionHandleSide) -> FlexBox {
119
+ let shoulder = flex_box();
120
+ shoulder
121
+ .position_type(PositionType::Absolute)
122
+ .position(handle_shoulder_x(side), HIT_TARGET_PADDING)
123
+ .width(SHOULDER_SIZE, Unit::Pixel)
124
+ .height(SHOULDER_SIZE, Unit::Pixel)
125
+ .bg_color(HANDLE_COLOR)
126
+ .preserve_selection_on_pointer_down(true);
127
+ shoulder
128
+ }
129
+
130
+ fn make_handle(side: SelectionHandleSide) -> FlexBox {
131
+ let knob = make_knob(side);
132
+ let shoulder = make_shoulder(side);
133
+ match side {
134
+ SelectionHandleSide::Start => {
135
+ knob.on_pointer_down(handle_start_pointer_down);
136
+ shoulder.on_pointer_down(handle_start_pointer_down);
137
+ }
138
+ SelectionHandleSide::End => {
139
+ knob.on_pointer_down(handle_end_pointer_down);
140
+ shoulder.on_pointer_down(handle_end_pointer_down);
141
+ }
142
+ }
143
+ knob.on_pointer_move(handle_pointer_move)
144
+ .on_pointer_up(handle_pointer_up)
145
+ .on_pointer_cancel(handle_pointer_cancel);
146
+ shoulder
147
+ .on_pointer_move(handle_pointer_move)
148
+ .on_pointer_up(handle_pointer_up)
149
+ .on_pointer_cancel(handle_pointer_cancel);
150
+ let handle = flex_box();
151
+ handle
152
+ .position_type(PositionType::Absolute)
153
+ .width(HIT_TARGET_SIZE, Unit::Pixel)
154
+ .height(HIT_TARGET_SIZE, Unit::Pixel)
155
+ .bg_color(0x00000000)
156
+ .child(&knob)
157
+ .child(&shoulder)
158
+ .interactive(true)
159
+ .visibility(Visibility::Hidden)
160
+ .preserve_selection_on_pointer_down(true);
161
+ handle
162
+ }
163
+
164
+ pub(crate) fn create_default_host() -> FlexBox {
165
+ STATE.with(|slot| {
166
+ let mut state = slot.borrow_mut();
167
+ if let Some(host_root) = state.host_root.as_ref() {
168
+ return host_root.clone();
169
+ }
170
+ let start_handle = make_handle(SelectionHandleSide::Start);
171
+ let end_handle = make_handle(SelectionHandleSide::End);
172
+ start_handle
173
+ .on_pointer_down(handle_start_pointer_down)
174
+ .on_pointer_move(handle_pointer_move)
175
+ .on_pointer_up(handle_pointer_up)
176
+ .on_pointer_cancel(handle_pointer_cancel);
177
+ end_handle
178
+ .on_pointer_down(handle_end_pointer_down)
179
+ .on_pointer_move(handle_pointer_move)
180
+ .on_pointer_up(handle_pointer_up)
181
+ .on_pointer_cancel(handle_pointer_cancel);
182
+ let host_root = portal();
183
+ host_root
184
+ .position_type(PositionType::Absolute)
185
+ .position(0.0, 0.0)
186
+ .width(100.0, Unit::Percent)
187
+ .height(100.0, Unit::Percent)
188
+ .child(&start_handle)
189
+ .child(&end_handle);
190
+ state.host_root = Some(host_root.clone());
191
+ state.start_handle = Some(start_handle);
192
+ state.end_handle = Some(end_handle);
193
+ host_root
194
+ })
195
+ }
196
+
197
+ pub(crate) fn reset() {
198
+ STATE.with(|slot| {
199
+ let mut state = slot.borrow_mut();
200
+ if let Some(host_root) = state.host_root.take() {
201
+ host_root.dispose();
202
+ }
203
+ *state = State::new();
204
+ });
205
+ }
206
+
207
+ pub(crate) fn clear() {
208
+ let dragging = STATE.with(|slot| slot.borrow().dragging_side);
209
+ if dragging != SelectionHandleDragSide::None {
210
+ end_handle_drag();
211
+ }
212
+ STATE.with(|slot| {
213
+ let mut state = slot.borrow_mut();
214
+ state.active_handle = NodeHandle::INVALID;
215
+ state.active_start = 0;
216
+ state.active_end = 0;
217
+ state.active_uses_range_geometry = false;
218
+ state.active_uses_cross_geometry = false;
219
+ state.dragging_side = SelectionHandleDragSide::None;
220
+ state.dragging_captured_visual_side = SelectionHandleDragSide::None;
221
+ state.stationary_anchor = None;
222
+ });
223
+ hide_handles();
224
+ }
225
+
226
+ pub(crate) fn record_pointer_event(event_type: PointerEventType, pointer_type: PointerType) {
227
+ if event_type == PointerEventType::Down {
228
+ STATE.with(|slot| slot.borrow_mut().last_pointer_type = pointer_type);
229
+ }
230
+ }
231
+
232
+ pub(crate) fn handle_selection_changed(handle: NodeHandle, start: u32, end: u32) {
233
+ let dragging = STATE.with(|slot| slot.borrow().dragging_side);
234
+ if start == end || !should_show() {
235
+ if start == end
236
+ && dragging != SelectionHandleDragSide::None
237
+ && STATE.with(|slot| slot.borrow().active_handle == handle)
238
+ {
239
+ STATE.with(|slot| {
240
+ let mut state = slot.borrow_mut();
241
+ state.active_start = start;
242
+ state.active_end = end;
243
+ });
244
+ create_default_host();
245
+ position_range_handles();
246
+ show_non_dragged_handles();
247
+ return;
248
+ }
249
+ clear();
250
+ return;
251
+ }
252
+ STATE.with(|slot| {
253
+ let mut state = slot.borrow_mut();
254
+ state.active_handle = handle;
255
+ state.active_start = start;
256
+ state.active_end = end;
257
+ state.active_uses_range_geometry = true;
258
+ state.active_uses_cross_geometry = false;
259
+ });
260
+ create_default_host();
261
+ if !position_range_handles() {
262
+ clear();
263
+ return;
264
+ }
265
+ if dragging != SelectionHandleDragSide::None {
266
+ show_non_dragged_handles();
267
+ return;
268
+ }
269
+ show_handles();
270
+ }
271
+
272
+ pub(crate) fn handle_cross_selection_changed(handle: NodeHandle, text: &str) {
273
+ let dragging = STATE.with(|slot| slot.borrow().dragging_side);
274
+ if text.is_empty() || !should_show() {
275
+ if text.is_empty()
276
+ && dragging != SelectionHandleDragSide::None
277
+ && STATE.with(|slot| slot.borrow().active_handle == handle)
278
+ {
279
+ STATE.with(|slot| {
280
+ let mut state = slot.borrow_mut();
281
+ state.active_start = 0;
282
+ state.active_end = 0;
283
+ });
284
+ create_default_host();
285
+ show_non_dragged_handles();
286
+ return;
287
+ }
288
+ clear();
289
+ return;
290
+ }
291
+ STATE.with(|slot| {
292
+ let mut state = slot.borrow_mut();
293
+ state.active_handle = handle;
294
+ state.active_start = 0;
295
+ state.active_end = text.len() as u32;
296
+ state.active_uses_range_geometry = true;
297
+ state.active_uses_cross_geometry = true;
298
+ });
299
+ create_default_host();
300
+ if !position_cross_selection_handles() {
301
+ STATE.with(|slot| {
302
+ let mut state = slot.borrow_mut();
303
+ state.active_uses_range_geometry = false;
304
+ state.active_uses_cross_geometry = false;
305
+ });
306
+ position_placeholder_handles();
307
+ }
308
+ if dragging != SelectionHandleDragSide::None {
309
+ show_non_dragged_handles();
310
+ return;
311
+ }
312
+ show_handles();
313
+ }
314
+
315
+ pub(crate) fn refresh_active_geometry() {
316
+ let (active_handle, uses_range_geometry, uses_cross_geometry) = STATE.with(|slot| {
317
+ let state = slot.borrow();
318
+ (
319
+ state.active_handle,
320
+ state.active_uses_range_geometry,
321
+ state.active_uses_cross_geometry,
322
+ )
323
+ });
324
+ if active_handle == NodeHandle::INVALID || !uses_range_geometry {
325
+ return;
326
+ }
327
+ if uses_cross_geometry {
328
+ if !position_cross_selection_handles() {
329
+ clear();
330
+ }
331
+ return;
332
+ }
333
+ if !position_range_handles() {
334
+ clear();
335
+ }
336
+ }
337
+
338
+ pub(crate) fn is_visible() -> bool {
339
+ STATE.with(|slot| slot.borrow().active_handle != NodeHandle::INVALID)
340
+ }
341
+
342
+ pub(crate) fn route_active_handle_drag_event(event: &mut PointerEventArgs) -> bool {
343
+ let dragging_side = STATE.with(|slot| slot.borrow().dragging_side);
344
+ if dragging_side == SelectionHandleDragSide::None {
345
+ return false;
346
+ }
347
+ match event.event_type {
348
+ PointerEventType::Move => handle_pointer_move(event),
349
+ PointerEventType::Up => handle_pointer_up(event),
350
+ PointerEventType::Cancel => handle_pointer_cancel(event),
351
+ _ => {}
352
+ }
353
+ event.handled
354
+ }
355
+
356
+ fn should_show() -> bool {
357
+ STATE.with(|slot| {
358
+ let state = slot.borrow();
359
+ match state.mode_value {
360
+ SelectionHandleMode::Disabled => false,
361
+ SelectionHandleMode::Always => true,
362
+ SelectionHandleMode::Auto => match state.last_pointer_type {
363
+ PointerType::Touch => true,
364
+ PointerType::Unknown => {
365
+ crate::generated::framework_host_services::fui_is_coarse_pointer()
366
+ }
367
+ _ => false,
368
+ },
369
+ }
370
+ })
371
+ }
372
+
373
+ fn show_handles() {
374
+ ensure_attached();
375
+ STATE.with(|slot| {
376
+ let state = slot.borrow();
377
+ if let Some(start_handle) = state.start_handle.as_ref() {
378
+ start_handle.visibility(Visibility::Normal);
379
+ set_handle_chrome_visibility(start_handle, Visibility::Normal);
380
+ }
381
+ if let Some(end_handle) = state.end_handle.as_ref() {
382
+ end_handle.visibility(Visibility::Normal);
383
+ set_handle_chrome_visibility(end_handle, Visibility::Normal);
384
+ }
385
+ });
386
+ }
387
+
388
+ fn hide_handles() {
389
+ STATE.with(|slot| {
390
+ let state = slot.borrow();
391
+ if let Some(start_handle) = state.start_handle.as_ref() {
392
+ set_handle_chrome_visibility(start_handle, Visibility::Normal);
393
+ start_handle.visibility(Visibility::Hidden);
394
+ }
395
+ if let Some(end_handle) = state.end_handle.as_ref() {
396
+ set_handle_chrome_visibility(end_handle, Visibility::Normal);
397
+ end_handle.visibility(Visibility::Hidden);
398
+ }
399
+ });
400
+ detach_handles();
401
+ }
402
+
403
+ fn set_handle_chrome_visibility(handle: &FlexBox, visibility: Visibility) {
404
+ for child in handle.node_ref().children() {
405
+ if child.handle() != NodeHandle::INVALID {
406
+ ui::set_visibility(child.handle().raw(), visibility as u32);
407
+ }
408
+ }
409
+ }
410
+
411
+ fn hide_handle(side: SelectionHandleDragSide) {
412
+ if let Some(handle) = handle_for_side(side) {
413
+ handle.visibility(Visibility::Normal);
414
+ set_handle_chrome_visibility(&handle, Visibility::Hidden);
415
+ }
416
+ }
417
+
418
+ fn show_handle(side: SelectionHandleDragSide) {
419
+ if let Some(handle) = handle_for_side(side) {
420
+ handle.visibility(Visibility::Normal);
421
+ set_handle_chrome_visibility(&handle, Visibility::Normal);
422
+ }
423
+ }
424
+
425
+ fn show_non_dragged_handles() {
426
+ ensure_attached();
427
+ let hidden_visual_side = STATE.with(|slot| slot.borrow().dragging_captured_visual_side);
428
+ STATE.with(|slot| {
429
+ let state = slot.borrow();
430
+ if let Some(start_handle) = state.start_handle.as_ref() {
431
+ start_handle.visibility(Visibility::Normal);
432
+ set_handle_chrome_visibility(
433
+ start_handle,
434
+ if hidden_visual_side == SelectionHandleDragSide::Start {
435
+ Visibility::Hidden
436
+ } else {
437
+ Visibility::Normal
438
+ },
439
+ );
440
+ }
441
+ if let Some(end_handle) = state.end_handle.as_ref() {
442
+ end_handle.visibility(Visibility::Normal);
443
+ set_handle_chrome_visibility(
444
+ end_handle,
445
+ if hidden_visual_side == SelectionHandleDragSide::End {
446
+ Visibility::Hidden
447
+ } else {
448
+ Visibility::Normal
449
+ },
450
+ );
451
+ }
452
+ });
453
+ }
454
+
455
+ fn handle_start_pointer_down(event: &mut PointerEventArgs) {
456
+ event.handled = begin_handle_drag(
457
+ semantic_side_for_visual_side(SelectionHandleDragSide::Start),
458
+ SelectionHandleDragSide::Start,
459
+ event.pointer_type,
460
+ );
461
+ }
462
+
463
+ fn handle_end_pointer_down(event: &mut PointerEventArgs) {
464
+ event.handled = begin_handle_drag(
465
+ semantic_side_for_visual_side(SelectionHandleDragSide::End),
466
+ SelectionHandleDragSide::End,
467
+ event.pointer_type,
468
+ );
469
+ }
470
+
471
+ fn handle_pointer_move(event: &mut PointerEventArgs) {
472
+ if STATE.with(|slot| slot.borrow().dragging_side) == SelectionHandleDragSide::None {
473
+ return;
474
+ }
475
+ event.handled = true;
476
+ refresh_active_geometry();
477
+ show_non_dragged_handles();
478
+ }
479
+
480
+ fn handle_pointer_up(event: &mut PointerEventArgs) {
481
+ if STATE.with(|slot| slot.borrow().dragging_side) == SelectionHandleDragSide::None {
482
+ return;
483
+ }
484
+ event.handled = true;
485
+ end_handle_drag();
486
+ }
487
+
488
+ fn handle_pointer_cancel(event: &mut PointerEventArgs) {
489
+ if STATE.with(|slot| slot.borrow().dragging_side) == SelectionHandleDragSide::None {
490
+ return;
491
+ }
492
+ event.handled = true;
493
+ end_handle_drag();
494
+ }
495
+
496
+ fn begin_handle_drag(
497
+ side: SelectionHandleDragSide,
498
+ visual_side: SelectionHandleDragSide,
499
+ pointer_type: PointerType,
500
+ ) -> bool {
501
+ if !matches!(pointer_type, PointerType::Touch | PointerType::Pen) {
502
+ return false;
503
+ }
504
+ let active_handle = STATE.with(|slot| slot.borrow().active_handle);
505
+ if active_handle == NodeHandle::INVALID {
506
+ return false;
507
+ }
508
+ if !capture_stationary_anchor(visual_side) {
509
+ return false;
510
+ }
511
+ let Some(handle) = handle_for_side(visual_side) else {
512
+ STATE.with(|slot| slot.borrow_mut().stationary_anchor = None);
513
+ return false;
514
+ };
515
+ STATE.with(|slot| {
516
+ let mut state = slot.borrow_mut();
517
+ state.dragging_side = side;
518
+ state.dragging_captured_visual_side = visual_side;
519
+ });
520
+ if !ui::begin_selection_endpoint_drag(
521
+ active_handle.raw(),
522
+ match side {
523
+ SelectionHandleDragSide::Start => 0,
524
+ SelectionHandleDragSide::End => 1,
525
+ SelectionHandleDragSide::None => 0,
526
+ },
527
+ ) {
528
+ STATE.with(|slot| {
529
+ let mut state = slot.borrow_mut();
530
+ state.dragging_side = SelectionHandleDragSide::None;
531
+ state.dragging_captured_visual_side = SelectionHandleDragSide::None;
532
+ state.stationary_anchor = None;
533
+ });
534
+ return false;
535
+ }
536
+ crate::mobile_text_selection_toolbar::hide_for_handle_drag();
537
+ if handle.handle() != NodeHandle::INVALID {
538
+ crate::event::capture_pointer(handle.handle());
539
+ unsafe { crate::ffi::fui_set_pointer_capture(handle.handle().raw()) };
540
+ }
541
+ set_handle_hit_test_visible(visual_side, false);
542
+ hide_handle(visual_side);
543
+ show_handle(if visual_side == SelectionHandleDragSide::Start {
544
+ SelectionHandleDragSide::End
545
+ } else {
546
+ SelectionHandleDragSide::Start
547
+ });
548
+ true
549
+ }
550
+
551
+ fn semantic_side_for_visual_side(visual_side: SelectionHandleDragSide) -> SelectionHandleDragSide {
552
+ if visual_side == SelectionHandleDragSide::None {
553
+ return SelectionHandleDragSide::None;
554
+ }
555
+ let (active_start, active_end) = STATE.with(|slot| {
556
+ let state = slot.borrow();
557
+ (state.active_start, state.active_end)
558
+ });
559
+ let forward = active_start <= active_end;
560
+ match visual_side {
561
+ SelectionHandleDragSide::Start if forward => SelectionHandleDragSide::Start,
562
+ SelectionHandleDragSide::Start => SelectionHandleDragSide::End,
563
+ SelectionHandleDragSide::End if forward => SelectionHandleDragSide::End,
564
+ SelectionHandleDragSide::End => SelectionHandleDragSide::Start,
565
+ SelectionHandleDragSide::None => SelectionHandleDragSide::None,
566
+ }
567
+ }
568
+
569
+ fn end_handle_drag() {
570
+ let visual_side = STATE.with(|slot| slot.borrow().dragging_captured_visual_side);
571
+ if let Some(handle) = handle_for_side(visual_side) {
572
+ if handle.handle() != NodeHandle::INVALID {
573
+ crate::event::release_pointer(handle.handle());
574
+ unsafe { crate::ffi::fui_release_pointer_capture() };
575
+ }
576
+ }
577
+ set_handle_hit_test_visible(visual_side, true);
578
+ STATE.with(|slot| {
579
+ let mut state = slot.borrow_mut();
580
+ state.dragging_side = SelectionHandleDragSide::None;
581
+ state.dragging_captured_visual_side = SelectionHandleDragSide::None;
582
+ state.stationary_anchor = None;
583
+ });
584
+ if STATE.with(|slot| {
585
+ let state = slot.borrow();
586
+ state.active_start == state.active_end
587
+ }) {
588
+ clear();
589
+ return;
590
+ }
591
+ refresh_active_geometry();
592
+ show_handles();
593
+ crate::mobile_text_selection_toolbar::show_after_handle_drag(is_visible());
594
+ }
595
+
596
+ fn set_handle_hit_test_visible(side: SelectionHandleDragSide, visible: bool) {
597
+ let Some(handle) = handle_for_side(side) else {
598
+ return;
599
+ };
600
+ if handle.handle() == NodeHandle::INVALID {
601
+ return;
602
+ }
603
+ ui::set_interactive(handle.handle().raw(), visible);
604
+ }
605
+
606
+ fn position_placeholder_handles() {
607
+ ensure_attached();
608
+ STATE.with(|slot| {
609
+ let state = slot.borrow();
610
+ if let Some(start_handle) = state.start_handle.as_ref() {
611
+ start_handle.position(0.0, 0.0);
612
+ }
613
+ if let Some(end_handle) = state.end_handle.as_ref() {
614
+ end_handle.position(HIT_TARGET_SIZE + 8.0, 0.0);
615
+ }
616
+ });
617
+ }
618
+
619
+ fn position_range_handles() -> bool {
620
+ ensure_attached();
621
+ let (active_handle, active_start, active_end, dragging_side) = STATE.with(|slot| {
622
+ let state = slot.borrow();
623
+ (
624
+ state.active_handle,
625
+ state.active_start,
626
+ state.active_end,
627
+ state.dragging_side,
628
+ )
629
+ });
630
+ let (Some(start_handle), Some(end_handle)) = STATE.with(|slot| {
631
+ let state = slot.borrow();
632
+ (state.start_handle.clone(), state.end_handle.clone())
633
+ }) else {
634
+ return false;
635
+ };
636
+ if active_handle == NodeHandle::INVALID {
637
+ return false;
638
+ }
639
+ let (range_start, range_end) = if active_end < active_start {
640
+ (active_end, active_start)
641
+ } else {
642
+ (active_start, active_end)
643
+ };
644
+ let rects = ui::get_text_range_rects(active_handle.raw(), range_start, range_end);
645
+ if rects.is_empty() {
646
+ return false;
647
+ }
648
+ let first_rect = rects.first().copied().unwrap();
649
+ let last_rect = rects.last().copied().unwrap();
650
+ if dragging_side != SelectionHandleDragSide::None {
651
+ position_dragging_range_handles(
652
+ &start_handle,
653
+ &end_handle,
654
+ first_rect.x,
655
+ first_rect.y + first_rect.height,
656
+ last_rect.x + last_rect.width,
657
+ last_rect.y + last_rect.height,
658
+ );
659
+ return true;
660
+ }
661
+ position_start_handle(
662
+ &start_handle,
663
+ first_rect.x,
664
+ first_rect.y + first_rect.height,
665
+ );
666
+ position_end_handle(
667
+ &end_handle,
668
+ last_rect.x + last_rect.width,
669
+ last_rect.y + last_rect.height,
670
+ );
671
+ true
672
+ }
673
+
674
+ fn position_dragging_range_handles(
675
+ start_handle: &FlexBox,
676
+ end_handle: &FlexBox,
677
+ lower_x: f32,
678
+ lower_y: f32,
679
+ upper_x: f32,
680
+ upper_y: f32,
681
+ ) {
682
+ let (active_start, active_end, dragging_side, dragging_captured_visual_side, stationary_anchor) =
683
+ STATE.with(|slot| {
684
+ let state = slot.borrow();
685
+ (
686
+ state.active_start,
687
+ state.active_end,
688
+ state.dragging_side,
689
+ state.dragging_captured_visual_side,
690
+ state.stationary_anchor,
691
+ )
692
+ });
693
+ let forward = active_start <= active_end;
694
+ let dragging_start = dragging_side == SelectionHandleDragSide::Start;
695
+ let moving_is_lower = dragging_start == forward;
696
+ let (moving_x, moving_y) = if moving_is_lower {
697
+ (lower_x, lower_y)
698
+ } else {
699
+ (upper_x, upper_y)
700
+ };
701
+ let (stationary_x, stationary_y) = if moving_is_lower {
702
+ (upper_x, upper_y)
703
+ } else {
704
+ (lower_x, lower_y)
705
+ };
706
+ let ((moving_x, moving_y), (stationary_x, stationary_y)) =
707
+ if let Some((stationary_x, stationary_y)) = stationary_anchor {
708
+ let lower_distance = distance_squared(lower_x, lower_y, stationary_x, stationary_y);
709
+ let upper_distance = distance_squared(upper_x, upper_y, stationary_x, stationary_y);
710
+ let moving = if upper_distance >= lower_distance {
711
+ (upper_x, upper_y)
712
+ } else {
713
+ (lower_x, lower_y)
714
+ };
715
+ (moving, (stationary_x, stationary_y))
716
+ } else {
717
+ ((moving_x, moving_y), (stationary_x, stationary_y))
718
+ };
719
+ match dragging_captured_visual_side {
720
+ SelectionHandleDragSide::Start => {
721
+ position_end_handle(end_handle, stationary_x, stationary_y);
722
+ position_start_handle(start_handle, moving_x, moving_y);
723
+ }
724
+ SelectionHandleDragSide::End => {
725
+ position_start_handle(start_handle, stationary_x, stationary_y);
726
+ position_end_handle(end_handle, moving_x, moving_y);
727
+ }
728
+ SelectionHandleDragSide::None => {}
729
+ }
730
+ }
731
+
732
+ fn distance_squared(left_x: f32, left_y: f32, right_x: f32, right_y: f32) -> f32 {
733
+ let dx = left_x - right_x;
734
+ let dy = left_y - right_y;
735
+ (dx * dx) + (dy * dy)
736
+ }
737
+
738
+ fn position_cross_selection_handles() -> bool {
739
+ ensure_attached();
740
+ let active_handle = STATE.with(|slot| slot.borrow().active_handle);
741
+ let (Some(start_handle), Some(end_handle)) = STATE.with(|slot| {
742
+ let state = slot.borrow();
743
+ (state.start_handle.clone(), state.end_handle.clone())
744
+ }) else {
745
+ return false;
746
+ };
747
+ if active_handle == NodeHandle::INVALID {
748
+ return false;
749
+ }
750
+ let Some(rects) = ui::get_cross_selection_endpoint_rects(active_handle.raw()) else {
751
+ return false;
752
+ };
753
+ position_start_handle(
754
+ &start_handle,
755
+ rects.start.x,
756
+ rects.start.y + rects.start.height,
757
+ );
758
+ position_end_handle(
759
+ &end_handle,
760
+ rects.end.x + rects.end.width,
761
+ rects.end.y + rects.end.height,
762
+ );
763
+ true
764
+ }
765
+
766
+ fn position_start_handle(handle: &FlexBox, x: f32, y: f32) {
767
+ STATE.with(|slot| slot.borrow_mut().start_anchor = Some((x, y)));
768
+ handle.position(x - START_STEM_X, y - HIT_TARGET_PADDING);
769
+ }
770
+
771
+ fn position_end_handle(handle: &FlexBox, x: f32, y: f32) {
772
+ STATE.with(|slot| slot.borrow_mut().end_anchor = Some((x, y)));
773
+ handle.position(x - END_STEM_X, y - HIT_TARGET_PADDING);
774
+ }
775
+
776
+ fn capture_stationary_anchor(visual_side: SelectionHandleDragSide) -> bool {
777
+ STATE.with(|slot| {
778
+ let mut state = slot.borrow_mut();
779
+ state.stationary_anchor = match visual_side {
780
+ SelectionHandleDragSide::Start => state.end_anchor,
781
+ SelectionHandleDragSide::End => state.start_anchor,
782
+ SelectionHandleDragSide::None => None,
783
+ };
784
+ state.stationary_anchor.is_some()
785
+ })
786
+ }
787
+
788
+ fn handle_for_side(side: SelectionHandleDragSide) -> Option<FlexBox> {
789
+ STATE.with(|slot| {
790
+ let state = slot.borrow();
791
+ match side {
792
+ SelectionHandleDragSide::Start => state.start_handle.clone(),
793
+ SelectionHandleDragSide::End => state.end_handle.clone(),
794
+ SelectionHandleDragSide::None => None,
795
+ }
796
+ })
797
+ }
798
+
799
+ fn ensure_attached() {
800
+ STATE.with(|slot| {
801
+ let mut state = slot.borrow_mut();
802
+ if state.attached {
803
+ return;
804
+ }
805
+ let Some(host_root) = state.host_root.as_ref() else {
806
+ return;
807
+ };
808
+ let Some(start_handle) = state.start_handle.as_ref() else {
809
+ return;
810
+ };
811
+ let Some(end_handle) = state.end_handle.as_ref() else {
812
+ return;
813
+ };
814
+ host_root.child(start_handle).child(end_handle);
815
+ state.attached = true;
816
+ });
817
+ }
818
+
819
+ fn detach_handles() {
820
+ STATE.with(|slot| {
821
+ let mut state = slot.borrow_mut();
822
+ if !state.attached {
823
+ return;
824
+ }
825
+ let Some(host_root) = state.host_root.as_ref() else {
826
+ return;
827
+ };
828
+ let Some(start_handle) = state.start_handle.as_ref() else {
829
+ return;
830
+ };
831
+ let Some(end_handle) = state.end_handle.as_ref() else {
832
+ return;
833
+ };
834
+ let _ = host_root.remove_child(start_handle);
835
+ let _ = host_root.remove_child(end_handle);
836
+ state.attached = false;
837
+ });
838
+ }