@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,701 @@
1
+ use super::internal::slider_presenter::{
2
+ create_default_slider_presenter, SliderPresenter, SliderTemplate, SliderVisualState,
3
+ };
4
+ use super::*;
5
+ use crate::logger;
6
+ use crate::node::WeakFlexBox;
7
+ use crate::signal::SubscriptionGuard;
8
+ use crate::{focus_adorner, focus_visibility};
9
+
10
+ const DEFAULT_SLIDER_LENGTH: f32 = 180.0;
11
+
12
+ fn clamp(value: f32, min: f32, max: f32) -> f32 {
13
+ if value < min {
14
+ return min;
15
+ }
16
+ if value > max {
17
+ return max;
18
+ }
19
+ value
20
+ }
21
+
22
+ fn create_slider_presenter(
23
+ template: Option<Rc<dyn SliderTemplate>>,
24
+ sizing: Option<SliderSizing>,
25
+ ) -> Rc<dyn SliderPresenter> {
26
+ if let Some(template) = template {
27
+ return template.create(sizing);
28
+ }
29
+ if let Some(template_set) = get_control_templates() {
30
+ if let Some(template) = template_set.slider {
31
+ return template.create(sizing);
32
+ }
33
+ }
34
+ create_default_slider_presenter(sizing)
35
+ }
36
+
37
+ #[derive(Clone)]
38
+ pub struct Slider {
39
+ root: FlexBox,
40
+ slider_presenter: Rc<RefCell<Rc<dyn SliderPresenter>>>,
41
+ template_override: Rc<RefCell<Option<Rc<dyn SliderTemplate>>>>,
42
+ sizing_value: Rc<Cell<Option<SliderSizing>>>,
43
+ colors_value: Rc<Cell<Option<SliderColors>>>,
44
+ min: Rc<Cell<f32>>,
45
+ max: Rc<Cell<f32>>,
46
+ step: Rc<Cell<f32>>,
47
+ value: Rc<Cell<f32>>,
48
+ length: Rc<Cell<f32>>,
49
+ orientation: Rc<Cell<Orientation>>,
50
+ changed: Rc<RefCell<Option<SliderChangedCallback>>>,
51
+ hovered_state: Rc<Cell<bool>>,
52
+ dragging_state: Rc<Cell<bool>>,
53
+ focused_state: Rc<Cell<bool>>,
54
+ weak_root: Rc<WeakNodeRef>,
55
+ weak_flex_root: WeakFlexBox,
56
+ theme_guard: Rc<RefCell<Option<SubscriptionGuard>>>,
57
+ focus_visibility_guard: Rc<RefCell<Option<SubscriptionGuard>>>,
58
+ }
59
+
60
+ impl Default for Slider {
61
+ fn default() -> Self {
62
+ Self::new()
63
+ }
64
+ }
65
+
66
+ impl Slider {
67
+ pub fn new() -> Self {
68
+ let root = flex_box();
69
+ let slider_presenter = create_slider_presenter(None, None);
70
+ let presenter_root = slider_presenter.root();
71
+ presenter_root.position_type(PositionType::Absolute);
72
+
73
+ root.interactive(true)
74
+ .focusable(true, 0)
75
+ .semantic_role(SemanticRole::Slider)
76
+ .reflect_semantic_disabled_from_enabled()
77
+ .semantic_orientation(Orientation::Horizontal)
78
+ .semantic_value_range(0.0, 0.0, 100.0)
79
+ .default_semantic_label("Slider")
80
+ .cursor(CursorStyle::Pointer)
81
+ .child(&presenter_root);
82
+
83
+ let control = Self {
84
+ weak_root: Rc::new(root.node_ref().downgrade()),
85
+ weak_flex_root: root.downgrade(),
86
+ root,
87
+ slider_presenter: Rc::new(RefCell::new(slider_presenter)),
88
+ template_override: Rc::new(RefCell::new(None)),
89
+ sizing_value: Rc::new(Cell::new(None)),
90
+ colors_value: Rc::new(Cell::new(None)),
91
+ min: Rc::new(Cell::new(0.0)),
92
+ max: Rc::new(Cell::new(100.0)),
93
+ step: Rc::new(Cell::new(1.0)),
94
+ value: Rc::new(Cell::new(0.0)),
95
+ length: Rc::new(Cell::new(DEFAULT_SLIDER_LENGTH)),
96
+ orientation: Rc::new(Cell::new(Orientation::Horizontal)),
97
+ changed: Rc::new(RefCell::new(None)),
98
+ hovered_state: Rc::new(Cell::new(false)),
99
+ dragging_state: Rc::new(Cell::new(false)),
100
+ focused_state: Rc::new(Cell::new(false)),
101
+ theme_guard: Rc::new(RefCell::new(None)),
102
+ focus_visibility_guard: Rc::new(RefCell::new(None)),
103
+ };
104
+ control.install_visual_subscriptions();
105
+ control.bind_events();
106
+ let target = SliderEventTarget::from_slider(&control);
107
+ control.persist_state(crate::persisted::persisted_value_adapter(
108
+ "slider-value",
109
+ crate::persisted::PersistedFloat32Codec,
110
+ 1,
111
+ {
112
+ let value = control.value.clone();
113
+ move || Some(value.get())
114
+ },
115
+ move |value| {
116
+ target.apply_persisted_value(value);
117
+ },
118
+ ));
119
+ control.set_value_inner(0.0, false, false);
120
+ control.handle_theme_changed();
121
+ control
122
+ }
123
+
124
+ fn bind_events(&self) {
125
+ let target = SliderEventTarget::from_slider(self);
126
+ self.root.on_pointer_enter(move |_event| {
127
+ target.handle_pointer_enter();
128
+ });
129
+
130
+ let target = SliderEventTarget::from_slider(self);
131
+ self.root.on_pointer_leave(move |_event| {
132
+ target.handle_pointer_leave();
133
+ });
134
+
135
+ let target = SliderEventTarget::from_slider(self);
136
+ self.root.on_pointer_down(move |event| {
137
+ target.handle_pointer_down(event);
138
+ });
139
+
140
+ let target = SliderEventTarget::from_slider(self);
141
+ self.root.on_pointer_move(move |event| {
142
+ target.handle_pointer_move(event);
143
+ });
144
+
145
+ let target = SliderEventTarget::from_slider(self);
146
+ self.root.on_pointer_up(move |event| {
147
+ target.handle_pointer_up(event);
148
+ });
149
+
150
+ let target = SliderEventTarget::from_slider(self);
151
+ self.root.on_key_down(move |event| {
152
+ target.sync_focus_chrome();
153
+ target.key_value(event);
154
+ });
155
+
156
+ let target = SliderEventTarget::from_slider(self);
157
+ self.root.on_focus_changed(move |event| {
158
+ if target.focused_state.get() != event.focused {
159
+ target.focused_state.set(event.focused);
160
+ target.handle_theme_changed();
161
+ }
162
+ });
163
+ }
164
+
165
+ pub fn min(&self, value: f32) -> &Self {
166
+ self.min.set(value);
167
+ if self.max.get() < value {
168
+ self.max.set(value);
169
+ }
170
+ self.set_value_inner(self.value.get(), true, false);
171
+ self
172
+ }
173
+
174
+ pub fn max(&self, value: f32) -> &Self {
175
+ self.max.set(value);
176
+ if self.min.get() > value {
177
+ self.min.set(value);
178
+ }
179
+ self.set_value_inner(self.value.get(), true, false);
180
+ self
181
+ }
182
+
183
+ pub fn step(&self, value: f32) -> &Self {
184
+ if value <= 0.0 {
185
+ logger::warn(
186
+ "Layout",
187
+ &format!("Slider.step() received {value}; clamping to 1.0."),
188
+ );
189
+ }
190
+ self.step.set(if value > 0.0 { value } else { 1.0 });
191
+ self.set_value_inner(self.value.get(), true, false);
192
+ self
193
+ }
194
+
195
+ pub fn value(&self, value: f32) -> &Self {
196
+ self.set_value_inner(value, true, false);
197
+ self
198
+ }
199
+
200
+ pub fn length(&self, value: f32) -> &Self {
201
+ let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
202
+ if value <= thumb_size {
203
+ logger::warn(
204
+ "Layout",
205
+ &format!(
206
+ "Slider.length() received {value}; clamping to a value above the thumb size."
207
+ ),
208
+ );
209
+ }
210
+ self.length.set(if value > thumb_size {
211
+ value
212
+ } else {
213
+ thumb_size + 1.0
214
+ });
215
+ self.sync_presentation();
216
+ self
217
+ }
218
+
219
+ pub fn orientation(&self, orientation: Orientation) -> &Self {
220
+ let orientation = if orientation == Orientation::Vertical {
221
+ Orientation::Vertical
222
+ } else {
223
+ Orientation::Horizontal
224
+ };
225
+ self.orientation.set(orientation);
226
+ self.root.semantic_orientation(orientation);
227
+ self.sync_semantic_label();
228
+ self.sync_presentation();
229
+ self
230
+ }
231
+
232
+ pub fn sizing(&self, sizing: Option<SliderSizing>) -> &Self {
233
+ self.sizing_value.set(sizing);
234
+ self.replace_presenter(create_slider_presenter(
235
+ self.template_override.borrow().clone(),
236
+ self.sizing_value.get(),
237
+ ));
238
+ let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
239
+ if self.length.get() <= thumb_size {
240
+ self.length.set(thumb_size + 1.0);
241
+ }
242
+ self.sync_presentation();
243
+ self
244
+ }
245
+
246
+ pub fn colors(&self, colors: Option<SliderColors>) -> &Self {
247
+ self.colors_value.set(colors);
248
+ self.sync_presentation();
249
+ self
250
+ }
251
+
252
+ pub fn template(&self, template: Option<Rc<dyn SliderTemplate>>) -> &Self {
253
+ self.template_override.replace(template.clone());
254
+ self.replace_presenter(create_slider_presenter(template, self.sizing_value.get()));
255
+ let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
256
+ if self.length.get() <= thumb_size {
257
+ logger::warn(
258
+ "Layout",
259
+ "Slider.template() increased the thumb size beyond the current slider length; clamping length to stay interactive.",
260
+ );
261
+ self.length.set(thumb_size + 1.0);
262
+ }
263
+ self.sync_presentation();
264
+ self
265
+ }
266
+
267
+ pub fn enabled(&self, enabled: bool) -> &Self {
268
+ self.root.enabled(enabled);
269
+ if !enabled && self.dragging_state.replace(false) {
270
+ if let Some(handle) = upgraded_handle(&self.weak_root) {
271
+ crate::event::release_pointer(handle);
272
+ unsafe { crate::ffi::fui_release_pointer_capture() };
273
+ }
274
+ }
275
+ self.handle_theme_changed();
276
+ self
277
+ }
278
+
279
+ pub fn on_changed(&self, handler: impl Fn(SliderChangedEventArgs) + 'static) -> &Self {
280
+ *self.changed.borrow_mut() = Some(Rc::new(handler));
281
+ self
282
+ }
283
+
284
+ pub fn current_value(&self) -> f32 {
285
+ self.value.get()
286
+ }
287
+
288
+ fn install_visual_subscriptions(&self) {
289
+ let target = SliderEventTarget::from_slider(self);
290
+ *self.theme_guard.borrow_mut() = Some(subscribe(move |_theme| {
291
+ target.handle_theme_changed();
292
+ }));
293
+
294
+ let target = SliderEventTarget::from_slider(self);
295
+ *self.focus_visibility_guard.borrow_mut() =
296
+ Some(focus_visibility::subscribe(move |_visible| {
297
+ target.sync_focus_chrome();
298
+ }));
299
+ }
300
+
301
+ fn set_value_inner(&self, value: f32, emit: bool, announce: bool) {
302
+ SliderEventTarget::from_slider(self).set_value_inner(value, emit, announce);
303
+ }
304
+
305
+ fn create_visual_state(&self) -> SliderVisualState {
306
+ create_slider_visual_state(
307
+ self.value.get(),
308
+ self.min.get(),
309
+ self.max.get(),
310
+ self.orientation.get(),
311
+ self.hovered_state.get(),
312
+ self.dragging_state.get(),
313
+ self.focused_state.get(),
314
+ self.is_enabled(),
315
+ )
316
+ }
317
+
318
+ fn sync_presentation(&self) {
319
+ let state = self.create_visual_state();
320
+ sync_slider_geometry_with_state(
321
+ &self.root,
322
+ self.slider_presenter.borrow().as_ref(),
323
+ state,
324
+ self.length.get(),
325
+ );
326
+ sync_slider_visual_state_with_state(
327
+ &self.root,
328
+ self.slider_presenter.borrow().as_ref(),
329
+ state,
330
+ self.colors_value.get(),
331
+ );
332
+ }
333
+
334
+ fn sync_semantic_label(&self) {
335
+ self.root
336
+ .default_semantic_label(if self.orientation.get() == Orientation::Vertical {
337
+ "Vertical slider"
338
+ } else {
339
+ "Slider"
340
+ });
341
+ }
342
+
343
+ fn handle_theme_changed(&self) {
344
+ self.root.cursor(if self.is_enabled() {
345
+ CursorStyle::Pointer
346
+ } else {
347
+ CursorStyle::Default
348
+ });
349
+ self.sync_presentation();
350
+ self.sync_focus_chrome();
351
+ }
352
+
353
+ fn is_enabled(&self) -> bool {
354
+ self.root.retained_node_ref().is_enabled_for_routing()
355
+ }
356
+
357
+ fn sync_focus_chrome(&self) {
358
+ sync_slider_focus_chrome(&self.root, self.focused_state.get(), self.is_enabled());
359
+ }
360
+
361
+ fn replace_presenter(&self, next_presenter: Rc<dyn SliderPresenter>) {
362
+ let previous = self.slider_presenter.borrow().clone();
363
+ if Rc::ptr_eq(&previous, &next_presenter) {
364
+ return;
365
+ }
366
+ let previous_root = previous.root();
367
+ let next_root = next_presenter.root();
368
+ next_root.position_type(PositionType::Absolute);
369
+ self.root.remove_child(&previous_root);
370
+ self.root.child(&next_root);
371
+ previous_root.dispose();
372
+ self.slider_presenter.replace(next_presenter);
373
+ }
374
+ }
375
+
376
+ impl Node for Slider {
377
+ fn retained_node_ref(&self) -> NodeRef {
378
+ self.root.retained_node_ref()
379
+ }
380
+
381
+ fn build_self(&self) {
382
+ self.sync_presentation();
383
+ self.sync_focus_chrome();
384
+ self.root.build_self();
385
+ }
386
+ }
387
+
388
+ impl HasFlexBoxRoot for Slider {
389
+ fn flex_box_root(&self) -> &FlexBox {
390
+ &self.root
391
+ }
392
+ }
393
+
394
+ #[derive(Clone)]
395
+ struct SliderEventTarget {
396
+ weak_root: Rc<WeakNodeRef>,
397
+ weak_flex_root: WeakFlexBox,
398
+ slider_presenter: Rc<RefCell<Rc<dyn SliderPresenter>>>,
399
+ min: Rc<Cell<f32>>,
400
+ max: Rc<Cell<f32>>,
401
+ step: Rc<Cell<f32>>,
402
+ value: Rc<Cell<f32>>,
403
+ length: Rc<Cell<f32>>,
404
+ orientation: Rc<Cell<Orientation>>,
405
+ changed: Rc<RefCell<Option<SliderChangedCallback>>>,
406
+ focused_state: Rc<Cell<bool>>,
407
+ hovered_state: Rc<Cell<bool>>,
408
+ dragging_state: Rc<Cell<bool>>,
409
+ colors_value: Rc<Cell<Option<SliderColors>>>,
410
+ }
411
+
412
+ impl SliderEventTarget {
413
+ fn from_slider(slider: &Slider) -> Self {
414
+ Self {
415
+ weak_root: slider.weak_root.clone(),
416
+ weak_flex_root: slider.weak_flex_root.clone(),
417
+ slider_presenter: slider.slider_presenter.clone(),
418
+ min: slider.min.clone(),
419
+ max: slider.max.clone(),
420
+ step: slider.step.clone(),
421
+ value: slider.value.clone(),
422
+ length: slider.length.clone(),
423
+ orientation: slider.orientation.clone(),
424
+ changed: slider.changed.clone(),
425
+ focused_state: slider.focused_state.clone(),
426
+ hovered_state: slider.hovered_state.clone(),
427
+ dragging_state: slider.dragging_state.clone(),
428
+ colors_value: slider.colors_value.clone(),
429
+ }
430
+ }
431
+
432
+ fn is_enabled(&self) -> bool {
433
+ self.weak_flex_root
434
+ .upgrade()
435
+ .map(|root| root.retained_node_ref().is_enabled_for_routing())
436
+ .unwrap_or(false)
437
+ }
438
+
439
+ fn handle_pointer_enter(&self) {
440
+ if !self.is_enabled() {
441
+ return;
442
+ }
443
+ self.hovered_state.set(true);
444
+ self.sync_presentation();
445
+ }
446
+
447
+ fn handle_pointer_leave(&self) {
448
+ if !self.is_enabled() {
449
+ return;
450
+ }
451
+ self.hovered_state.set(false);
452
+ if !self.dragging_state.get() {
453
+ self.sync_presentation();
454
+ }
455
+ }
456
+
457
+ fn handle_pointer_down(&self, event: &mut PointerEventArgs) {
458
+ if !self.is_enabled() {
459
+ return;
460
+ }
461
+ self.hovered_state.set(true);
462
+ self.dragging_state.set(true);
463
+ self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
464
+ self.sync_presentation();
465
+ event.capture_pointer();
466
+ event.handled = true;
467
+ }
468
+
469
+ fn handle_pointer_move(&self, event: &mut PointerEventArgs) {
470
+ if !self.is_enabled() || !self.dragging_state.get() || event.buttons == 0 {
471
+ return;
472
+ }
473
+ self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
474
+ event.handled = true;
475
+ }
476
+
477
+ fn handle_pointer_up(&self, event: &mut PointerEventArgs) {
478
+ if !self.is_enabled() || !self.dragging_state.get() {
479
+ return;
480
+ }
481
+ self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
482
+ self.dragging_state.set(false);
483
+ self.sync_presentation();
484
+ event.release_pointer_capture();
485
+ event.handled = true;
486
+ }
487
+
488
+ fn key_value(&self, event: &mut KeyEventArgs) {
489
+ if !self.is_enabled() || event.modifiers != 0 {
490
+ return;
491
+ }
492
+ let mut next = self.value.get();
493
+ match event.key.as_str() {
494
+ "Home" => next = self.min.get(),
495
+ "End" => next = self.max.get(),
496
+ "ArrowUp" if self.orientation.get() == Orientation::Vertical => next += self.step.get(),
497
+ "ArrowDown" if self.orientation.get() == Orientation::Vertical => {
498
+ next -= self.step.get()
499
+ }
500
+ "ArrowRight" if self.orientation.get() == Orientation::Horizontal => {
501
+ next += self.step.get()
502
+ }
503
+ "ArrowLeft" if self.orientation.get() == Orientation::Horizontal => {
504
+ next -= self.step.get()
505
+ }
506
+ _ => return,
507
+ }
508
+ self.set_value_inner(next, true, true);
509
+ event.handled = true;
510
+ }
511
+
512
+ fn apply_persisted_value(&self, value: f32) {
513
+ self.set_value_inner(value, true, false);
514
+ }
515
+
516
+ fn set_value_inner(&self, next: f32, emit: bool, announce: bool) {
517
+ let normalized =
518
+ normalize_slider_value(next, self.min.get(), self.max.get(), self.step.get());
519
+ if (normalized - self.value.get()).abs() <= f32::EPSILON {
520
+ self.sync_presentation();
521
+ return;
522
+ }
523
+ self.value.set(normalized);
524
+ if let Some(root) = self.weak_flex_root.upgrade() {
525
+ root.semantic_value_range(normalized, self.min.get(), self.max.get());
526
+ if announce && root.has_built_handle() {
527
+ ui::request_semantic_announcement(root.handle().raw());
528
+ }
529
+ }
530
+ self.sync_presentation();
531
+ if emit {
532
+ if let Some(callback) = self.changed.borrow().clone() {
533
+ callback(SliderChangedEventArgs { value: normalized });
534
+ }
535
+ }
536
+ }
537
+
538
+ fn update_value_from_pointer(&self, x: f32, y: f32, emit: bool, announce: bool) {
539
+ let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
540
+ let available = self.length.get() - thumb_size;
541
+ if available <= 0.0 {
542
+ return;
543
+ }
544
+ let (mut local_x, mut local_y) = (x, y);
545
+ if let Some(handle) = upgraded_handle(&self.weak_root) {
546
+ if let Some(bounds) = ui::get_bounds(handle.raw()) {
547
+ local_x = x - bounds[0];
548
+ local_y = y - bounds[1];
549
+ }
550
+ }
551
+ let leading_inset = SLIDER_OUTER_INSET + (thumb_size * 0.5);
552
+ let offset = if self.orientation.get() == Orientation::Vertical {
553
+ available - clamp(local_y - leading_inset, 0.0, available)
554
+ } else {
555
+ clamp(local_x - leading_inset, 0.0, available)
556
+ };
557
+ let fraction = clamp(offset / available, 0.0, 1.0);
558
+ let next = self.min.get() + ((self.max.get() - self.min.get()) * fraction);
559
+ self.set_value_inner(next, emit, announce);
560
+ }
561
+
562
+ fn create_visual_state(&self) -> SliderVisualState {
563
+ create_slider_visual_state(
564
+ self.value.get(),
565
+ self.min.get(),
566
+ self.max.get(),
567
+ self.orientation.get(),
568
+ self.hovered_state.get(),
569
+ self.dragging_state.get(),
570
+ self.focused_state.get(),
571
+ self.is_enabled(),
572
+ )
573
+ }
574
+
575
+ fn sync_presentation(&self) {
576
+ let Some(root) = self.weak_flex_root.upgrade() else {
577
+ return;
578
+ };
579
+ let presenter = self.slider_presenter.borrow().clone();
580
+ let state = self.create_visual_state();
581
+ sync_slider_geometry_with_state(&root, presenter.as_ref(), state, self.length.get());
582
+ sync_slider_visual_state_with_state(
583
+ &root,
584
+ presenter.as_ref(),
585
+ state,
586
+ self.colors_value.get(),
587
+ );
588
+ }
589
+
590
+ fn handle_theme_changed(&self) {
591
+ let Some(root) = self.weak_flex_root.upgrade() else {
592
+ return;
593
+ };
594
+ root.cursor(if self.is_enabled() {
595
+ CursorStyle::Pointer
596
+ } else {
597
+ CursorStyle::Default
598
+ });
599
+ self.sync_presentation();
600
+ self.sync_focus_chrome();
601
+ }
602
+
603
+ fn sync_focus_chrome(&self) {
604
+ let Some(root) = self.weak_flex_root.upgrade() else {
605
+ return;
606
+ };
607
+ sync_slider_focus_chrome(
608
+ &root,
609
+ self.focused_state.get(),
610
+ root.retained_node_ref().is_enabled_for_routing(),
611
+ );
612
+ }
613
+ }
614
+
615
+ fn create_slider_visual_state(
616
+ value: f32,
617
+ min: f32,
618
+ max: f32,
619
+ orientation: Orientation,
620
+ hovered: bool,
621
+ dragging: bool,
622
+ focused: bool,
623
+ enabled: bool,
624
+ ) -> SliderVisualState {
625
+ let normalized_value = if max > min {
626
+ clamp((value - min) / (max - min), 0.0, 1.0)
627
+ } else {
628
+ 0.0
629
+ };
630
+ SliderVisualState::new(
631
+ value,
632
+ min,
633
+ max,
634
+ normalized_value,
635
+ orientation,
636
+ hovered,
637
+ dragging,
638
+ focused,
639
+ enabled,
640
+ )
641
+ }
642
+
643
+ fn sync_slider_geometry_with_state(
644
+ root: &FlexBox,
645
+ presenter: &dyn SliderPresenter,
646
+ state: SliderVisualState,
647
+ length: f32,
648
+ ) {
649
+ let metrics = presenter.metrics();
650
+ if state.orientation == Orientation::Vertical {
651
+ root.width(
652
+ metrics.thumb_size + (SLIDER_OUTER_INSET * 2.0) + metrics.cross_axis_extra,
653
+ Unit::Pixel,
654
+ )
655
+ .height(length + (SLIDER_OUTER_INSET * 2.0), Unit::Pixel);
656
+ } else {
657
+ root.width(length + (SLIDER_OUTER_INSET * 2.0), Unit::Pixel)
658
+ .height(
659
+ metrics.thumb_size + (SLIDER_OUTER_INSET * 2.0) + metrics.cross_axis_extra,
660
+ Unit::Pixel,
661
+ );
662
+ }
663
+ presenter
664
+ .root()
665
+ .position_type(PositionType::Absolute)
666
+ .position(SLIDER_CHILD_INSET, SLIDER_CHILD_INSET);
667
+ presenter.layout(state, length);
668
+ root.semantic_value_range(state.value, state.min, state.max);
669
+ root.default_semantic_label(if state.orientation == Orientation::Vertical {
670
+ "Vertical slider"
671
+ } else {
672
+ "Slider"
673
+ });
674
+ }
675
+
676
+ fn sync_slider_visual_state_with_state(
677
+ root: &FlexBox,
678
+ presenter: &dyn SliderPresenter,
679
+ state: SliderVisualState,
680
+ colors: Option<SliderColors>,
681
+ ) {
682
+ let theme = current_theme();
683
+ root.corner_radius(theme.spacing.sm)
684
+ .border(SLIDER_FOCUS_BORDER_WIDTH, theme.colors.background)
685
+ .padding(
686
+ SLIDER_PADDING,
687
+ SLIDER_PADDING,
688
+ SLIDER_PADDING,
689
+ SLIDER_PADDING,
690
+ )
691
+ .opacity(if state.enabled { 1.0 } else { 0.6 });
692
+ presenter.apply(theme, state, colors);
693
+ }
694
+
695
+ fn sync_slider_focus_chrome(root: &FlexBox, focused: bool, enabled: bool) {
696
+ if focused && enabled && focus_visibility::keyboard_focus_visible() {
697
+ focus_adorner::show_standard(root, current_theme().spacing.sm);
698
+ return;
699
+ }
700
+ focus_adorner::hide_owner(root);
701
+ }