@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,514 @@
1
+ use super::*;
2
+ use crate::animation::AnimationTiming;
3
+ use crate::ffi::{Orientation, Unit};
4
+ use crate::persisted::{store_scroll_offset, try_load_scroll_offset};
5
+ use crate::signal::Subscription;
6
+ use crate::transitions::NodeTransitions;
7
+ use std::cell::{Cell, RefCell};
8
+ use std::rc::{Rc, Weak};
9
+
10
+ const DEFAULT_SCROLLBAR_GUTTER: f32 = 0.0;
11
+ const OVERFLOW_TOLERANCE: f32 = 0.5;
12
+
13
+ fn clamp(value: f32, min: f32, max: f32) -> f32 {
14
+ if value < min {
15
+ return min;
16
+ }
17
+ if value > max {
18
+ return max;
19
+ }
20
+ value
21
+ }
22
+
23
+ struct ScrollBoxInner {
24
+ root: FlexBox,
25
+ scroll_state: ScrollState,
26
+ viewport: ScrollView,
27
+ top_row: FlexBox,
28
+ bottom_row: FlexBox,
29
+ vertical_gutter: FlexBox,
30
+ corner: FlexBox,
31
+ vertical_scrollbar: ScrollBar,
32
+ horizontal_scrollbar: ScrollBar,
33
+ vertical_visibility: Cell<ScrollBarVisibility>,
34
+ horizontal_visibility: Cell<ScrollBarVisibility>,
35
+ vertical_scroll_enabled: Cell<bool>,
36
+ horizontal_scroll_enabled: Cell<bool>,
37
+ scrollbar_gutter: Cell<f32>,
38
+ persist_scroll: Cell<bool>,
39
+ persisted_restore_pending: Cell<bool>,
40
+ subscriptions: RefCell<Vec<Subscription>>,
41
+ }
42
+
43
+ #[derive(Clone)]
44
+ pub struct ScrollBox {
45
+ inner: Rc<ScrollBoxInner>,
46
+ }
47
+
48
+ impl ScrollBox {
49
+ pub fn new() -> Self {
50
+ Self::with_parts(ScrollState::new(), ScrollView::new())
51
+ }
52
+
53
+ pub fn with_parts(scroll_state: ScrollState, viewport: ScrollView) -> Self {
54
+ let root = column();
55
+ let viewport = viewport
56
+ .bind_scroll_state(scroll_state.clone())
57
+ .fill_size()
58
+ .clone();
59
+ let vertical_gutter = flex_box();
60
+ vertical_gutter
61
+ .width(DEFAULT_SCROLLBAR_GUTTER, Unit::Pixel)
62
+ .fill_height()
63
+ .on_pointer_down(|_event| {});
64
+ let vertical_scrollbar = ScrollBar::new(scroll_state.clone(), Orientation::Vertical);
65
+ let corner = flex_box();
66
+ corner
67
+ .width(0.0, Unit::Pixel)
68
+ .height(0.0, Unit::Pixel)
69
+ .on_pointer_down(|_event| {});
70
+ let horizontal_scrollbar = ScrollBar::new(scroll_state.clone(), Orientation::Horizontal);
71
+ let top_row = row();
72
+ top_row
73
+ .fill_size()
74
+ .on_pointer_down(|_event| {})
75
+ .child(&viewport)
76
+ .child(&vertical_gutter)
77
+ .child(&vertical_scrollbar.render());
78
+ let bottom_row = row();
79
+ bottom_row
80
+ .fill_width()
81
+ .on_pointer_down(|_event| {})
82
+ .child(&horizontal_scrollbar.render())
83
+ .child(&corner);
84
+ root.child(&top_row).child(&bottom_row);
85
+
86
+ let inner = Rc::new(ScrollBoxInner {
87
+ root,
88
+ scroll_state,
89
+ viewport,
90
+ top_row,
91
+ bottom_row,
92
+ vertical_gutter,
93
+ corner,
94
+ vertical_scrollbar,
95
+ horizontal_scrollbar,
96
+ vertical_visibility: Cell::new(ScrollBarVisibility::Auto),
97
+ horizontal_visibility: Cell::new(ScrollBarVisibility::Auto),
98
+ vertical_scroll_enabled: Cell::new(true),
99
+ horizontal_scroll_enabled: Cell::new(true),
100
+ scrollbar_gutter: Cell::new(DEFAULT_SCROLLBAR_GUTTER),
101
+ persist_scroll: Cell::new(true),
102
+ persisted_restore_pending: Cell::new(false),
103
+ subscriptions: RefCell::new(Vec::new()),
104
+ });
105
+ let scroll_box = Self { inner };
106
+ scroll_box.attach_listeners();
107
+ scroll_box.refresh_chrome();
108
+ scroll_box
109
+ }
110
+
111
+ pub fn scroll_state(&self) -> ScrollState {
112
+ self.inner.scroll_state.clone()
113
+ }
114
+
115
+ pub fn viewport(&self) -> ScrollView {
116
+ self.inner.viewport.clone()
117
+ }
118
+
119
+ pub fn vertical_scrollbar(&self) -> ScrollBar {
120
+ self.inner.vertical_scrollbar.clone()
121
+ }
122
+
123
+ pub fn horizontal_scrollbar(&self) -> ScrollBar {
124
+ self.inner.horizontal_scrollbar.clone()
125
+ }
126
+
127
+ pub fn child<T: Node>(&self, node: &T) -> &Self {
128
+ self.inner.viewport.child(node);
129
+ self.bind_content_scroll_proxy_targets();
130
+ self
131
+ }
132
+
133
+ pub fn children<I, C>(&self, nodes: I) -> &Self
134
+ where
135
+ I: IntoIterator<Item = C>,
136
+ C: Into<Child>,
137
+ {
138
+ for node in nodes {
139
+ self.inner
140
+ .viewport
141
+ .retained_node_ref()
142
+ .append_child_ref(&node.into().node_ref);
143
+ }
144
+ self.bind_content_scroll_proxy_targets();
145
+ self
146
+ }
147
+
148
+ pub fn scroll_enabled_x(&self, enabled: bool) -> &Self {
149
+ self.inner.horizontal_scroll_enabled.set(enabled);
150
+ self.inner.viewport.scroll_enabled_x(enabled);
151
+ self.refresh_chrome();
152
+ self
153
+ }
154
+
155
+ pub fn scroll_enabled_y(&self, enabled: bool) -> &Self {
156
+ self.inner.vertical_scroll_enabled.set(enabled);
157
+ self.inner.viewport.scroll_enabled_y(enabled);
158
+ self.refresh_chrome();
159
+ self
160
+ }
161
+
162
+ pub fn smooth_scrolling(&self, smooth_scrolling: bool) -> &Self {
163
+ self.inner.viewport.smooth_scrolling(smooth_scrolling);
164
+ self
165
+ }
166
+
167
+ pub fn persist_scroll(&self, persist: bool) -> &Self {
168
+ self.inner.persist_scroll.set(persist);
169
+ if !persist {
170
+ self.inner.persisted_restore_pending.set(false);
171
+ }
172
+ self
173
+ }
174
+
175
+ pub fn scroll_offset(&self, x: f32, y: f32) -> &Self {
176
+ self.inner.viewport.scroll_offset(x, y);
177
+ self
178
+ }
179
+
180
+ pub fn transitions(&self, transitions: Option<NodeTransitions>) -> &Self {
181
+ self.inner.viewport.transitions(transitions);
182
+ self
183
+ }
184
+
185
+ pub fn scroll_content_size(&self, width: f32, height: f32) -> &Self {
186
+ self.inner.viewport.scroll_content_size(width, height);
187
+ self
188
+ }
189
+
190
+ pub fn scroll_to(&self, x: f32, y: f32) -> &Self {
191
+ self.inner.viewport.scroll_to(x, y);
192
+ self
193
+ }
194
+
195
+ pub fn scroll_to_animated(&self, x: f32, y: f32, timing: AnimationTiming) -> &Self {
196
+ self.inner.viewport.scroll_to_animated(x, y, timing);
197
+ self
198
+ }
199
+
200
+ pub fn set_runtime_scroll_offset(&self, x: f32, y: f32) {
201
+ self.inner.viewport.set_runtime_scroll_offset(x, y);
202
+ }
203
+
204
+ pub fn vertical_scrollbar_visibility(&self, mode: ScrollBarVisibility) -> &Self {
205
+ self.inner.vertical_visibility.set(mode);
206
+ self.refresh_chrome();
207
+ self
208
+ }
209
+
210
+ pub fn horizontal_scrollbar_visibility(&self, mode: ScrollBarVisibility) -> &Self {
211
+ self.inner.horizontal_visibility.set(mode);
212
+ self.refresh_chrome();
213
+ self
214
+ }
215
+
216
+ pub fn scrollbar_gutter(&self, value: f32) -> &Self {
217
+ self.inner.scrollbar_gutter.set(value.max(0.0));
218
+ self.refresh_chrome();
219
+ self
220
+ }
221
+
222
+ fn attach_listeners(&self) {
223
+ let weak = Rc::downgrade(&self.inner);
224
+ self.inner
225
+ .vertical_scrollbar
226
+ .set_chrome_metric_changed_handler(move || {
227
+ if let Some(inner) = Weak::upgrade(&weak) {
228
+ ScrollBox { inner }.refresh_chrome();
229
+ }
230
+ });
231
+ let weak = Rc::downgrade(&self.inner);
232
+ self.inner
233
+ .horizontal_scrollbar
234
+ .set_chrome_metric_changed_handler(move || {
235
+ if let Some(inner) = Weak::upgrade(&weak) {
236
+ ScrollBox { inner }.refresh_chrome();
237
+ }
238
+ });
239
+
240
+ let weak = Rc::downgrade(&self.inner);
241
+ self.inner
242
+ .subscriptions
243
+ .borrow_mut()
244
+ .push(self.inner.scroll_state.subscribe_offset_x(move || {
245
+ if let Some(inner) = Weak::upgrade(&weak) {
246
+ let scroll_box = ScrollBox { inner };
247
+ scroll_box.store_persisted_scroll_offset_if_needed();
248
+ scroll_box.try_restore_persisted_scroll_offset();
249
+ }
250
+ }));
251
+ let weak = Rc::downgrade(&self.inner);
252
+ self.inner
253
+ .subscriptions
254
+ .borrow_mut()
255
+ .push(self.inner.scroll_state.subscribe_offset_y(move || {
256
+ if let Some(inner) = Weak::upgrade(&weak) {
257
+ let scroll_box = ScrollBox { inner };
258
+ scroll_box.store_persisted_scroll_offset_if_needed();
259
+ scroll_box.try_restore_persisted_scroll_offset();
260
+ }
261
+ }));
262
+ let weak = Rc::downgrade(&self.inner);
263
+ self.inner.subscriptions.borrow_mut().push(
264
+ self.inner.scroll_state.subscribe_content_width(move || {
265
+ if let Some(inner) = Weak::upgrade(&weak) {
266
+ let scroll_box = ScrollBox { inner };
267
+ scroll_box.refresh_chrome();
268
+ scroll_box.try_restore_persisted_scroll_offset();
269
+ }
270
+ }),
271
+ );
272
+ let weak = Rc::downgrade(&self.inner);
273
+ self.inner.subscriptions.borrow_mut().push(
274
+ self.inner.scroll_state.subscribe_content_height(move || {
275
+ if let Some(inner) = Weak::upgrade(&weak) {
276
+ let scroll_box = ScrollBox { inner };
277
+ scroll_box.refresh_chrome();
278
+ scroll_box.try_restore_persisted_scroll_offset();
279
+ }
280
+ }),
281
+ );
282
+ let weak = Rc::downgrade(&self.inner);
283
+ self.inner.subscriptions.borrow_mut().push(
284
+ self.inner.scroll_state.subscribe_viewport_width(move || {
285
+ if let Some(inner) = Weak::upgrade(&weak) {
286
+ let scroll_box = ScrollBox { inner };
287
+ scroll_box.refresh_chrome();
288
+ scroll_box.bind_scroll_chrome();
289
+ scroll_box.try_restore_persisted_scroll_offset();
290
+ }
291
+ }),
292
+ );
293
+ let weak = Rc::downgrade(&self.inner);
294
+ self.inner.subscriptions.borrow_mut().push(
295
+ self.inner.scroll_state.subscribe_viewport_height(move || {
296
+ if let Some(inner) = Weak::upgrade(&weak) {
297
+ let scroll_box = ScrollBox { inner };
298
+ scroll_box.refresh_chrome();
299
+ scroll_box.bind_scroll_chrome();
300
+ scroll_box.try_restore_persisted_scroll_offset();
301
+ }
302
+ }),
303
+ );
304
+ }
305
+
306
+ fn bind_scroll_chrome(&self) {
307
+ let viewport_handle = self.inner.viewport.handle().raw();
308
+ self.inner
309
+ .vertical_scrollbar
310
+ .bind_scroll_handle(viewport_handle);
311
+ self.inner
312
+ .horizontal_scrollbar
313
+ .bind_scroll_handle(viewport_handle);
314
+ self.inner
315
+ .root
316
+ .bind_scroll_proxy_target_handle(viewport_handle);
317
+ self.inner
318
+ .top_row
319
+ .bind_scroll_proxy_target_handle(viewport_handle);
320
+ self.inner
321
+ .bottom_row
322
+ .bind_scroll_proxy_target_handle(viewport_handle);
323
+ self.inner
324
+ .vertical_gutter
325
+ .bind_scroll_proxy_target_handle(viewport_handle);
326
+ self.inner
327
+ .corner
328
+ .bind_scroll_proxy_target_handle(viewport_handle);
329
+ self.bind_content_scroll_proxy_targets();
330
+ }
331
+
332
+ fn bind_content_scroll_proxy_targets(&self) {
333
+ let viewport_handle = self.inner.viewport.handle().raw();
334
+ let children = self.inner.viewport.retained_node_ref().children();
335
+ for child in children {
336
+ child.bind_scroll_proxy_target_handle(viewport_handle);
337
+ }
338
+ }
339
+
340
+ fn refresh_chrome(&self) {
341
+ let vertical_rail_thickness =
342
+ self.inner.scrollbar_gutter.get() + self.inner.vertical_scrollbar.thickness();
343
+ let horizontal_rail_thickness = self.inner.horizontal_scrollbar.thickness();
344
+ let show_vertical = self.should_show(
345
+ self.inner.vertical_visibility.get(),
346
+ self.inner.vertical_scroll_enabled.get(),
347
+ self.inner.scroll_state.content_height(),
348
+ self.inner.scroll_state.viewport_height(),
349
+ );
350
+ let show_horizontal = self.should_show(
351
+ self.inner.horizontal_visibility.get(),
352
+ self.inner.horizontal_scroll_enabled.get(),
353
+ self.inner.scroll_state.content_width(),
354
+ self.inner.scroll_state.viewport_width(),
355
+ );
356
+ self.inner.vertical_scrollbar.chrome_visible(show_vertical);
357
+ self.inner
358
+ .horizontal_scrollbar
359
+ .chrome_visible(show_horizontal);
360
+
361
+ self.inner.vertical_gutter.width(
362
+ if show_vertical {
363
+ self.inner.scrollbar_gutter.get()
364
+ } else {
365
+ 0.0
366
+ },
367
+ Unit::Pixel,
368
+ );
369
+ self.inner.vertical_scrollbar.render().width(
370
+ if show_vertical {
371
+ self.inner.vertical_scrollbar.thickness()
372
+ } else {
373
+ 0.0
374
+ },
375
+ Unit::Pixel,
376
+ );
377
+ let horizontal_rail_height = if show_horizontal {
378
+ horizontal_rail_thickness
379
+ } else {
380
+ 0.0
381
+ };
382
+ self.inner
383
+ .bottom_row
384
+ .height(horizontal_rail_height, Unit::Pixel);
385
+ self.inner.horizontal_scrollbar.render().height(
386
+ if show_horizontal {
387
+ self.inner.horizontal_scrollbar.thickness()
388
+ } else {
389
+ 0.0
390
+ },
391
+ Unit::Pixel,
392
+ );
393
+ let vertical_rail_width = if show_vertical {
394
+ vertical_rail_thickness
395
+ } else {
396
+ 0.0
397
+ };
398
+ self.inner.corner.width(vertical_rail_width, Unit::Pixel);
399
+ self.inner
400
+ .corner
401
+ .height(horizontal_rail_height, Unit::Pixel);
402
+ }
403
+
404
+ fn should_show(
405
+ &self,
406
+ mode: ScrollBarVisibility,
407
+ enabled: bool,
408
+ content_size: f32,
409
+ viewport_size: f32,
410
+ ) -> bool {
411
+ if !enabled || mode == ScrollBarVisibility::Never {
412
+ return false;
413
+ }
414
+ if mode == ScrollBarVisibility::Always {
415
+ return true;
416
+ }
417
+ if viewport_size <= 0.0 || content_size <= 0.0 {
418
+ return false;
419
+ }
420
+ content_size > viewport_size + OVERFLOW_TOLERANCE
421
+ }
422
+
423
+ fn store_persisted_scroll_offset_if_needed(&self) {
424
+ if !self.inner.persist_scroll.get() {
425
+ return;
426
+ }
427
+ let Some(node_id) = self.inner.root.retained_node_ref().node_id() else {
428
+ return;
429
+ };
430
+ if node_id.is_empty() {
431
+ return;
432
+ }
433
+ store_scroll_offset(
434
+ &node_id,
435
+ self.inner.scroll_state.offset_x(),
436
+ self.inner.scroll_state.offset_y(),
437
+ );
438
+ }
439
+
440
+ fn try_restore_persisted_scroll_offset(&self) -> bool {
441
+ if !self.inner.persisted_restore_pending.get() || !self.inner.persist_scroll.get() {
442
+ return false;
443
+ }
444
+ let Some(node_id) = self.inner.root.retained_node_ref().node_id() else {
445
+ self.inner.persisted_restore_pending.set(false);
446
+ return false;
447
+ };
448
+ if node_id.is_empty() {
449
+ self.inner.persisted_restore_pending.set(false);
450
+ return false;
451
+ }
452
+ let Some(restored) = try_load_scroll_offset(&node_id) else {
453
+ self.inner.persisted_restore_pending.set(false);
454
+ return false;
455
+ };
456
+ let max_x = (self.inner.scroll_state.content_width()
457
+ - self.inner.scroll_state.viewport_width())
458
+ .max(0.0);
459
+ let max_y = (self.inner.scroll_state.content_height()
460
+ - self.inner.scroll_state.viewport_height())
461
+ .max(0.0);
462
+ let can_restore_x = !self.inner.horizontal_scroll_enabled.get()
463
+ || self.inner.scroll_state.viewport_width() > 0.0;
464
+ let can_restore_y = !self.inner.vertical_scroll_enabled.get()
465
+ || self.inner.scroll_state.viewport_height() > 0.0;
466
+ if !can_restore_x || !can_restore_y {
467
+ return false;
468
+ }
469
+ self.inner.persisted_restore_pending.set(false);
470
+ self.set_runtime_scroll_offset(
471
+ clamp(restored.x, 0.0, max_x),
472
+ clamp(restored.y, 0.0, max_y),
473
+ );
474
+ true
475
+ }
476
+ }
477
+
478
+ impl Default for ScrollBox {
479
+ fn default() -> Self {
480
+ Self::new()
481
+ }
482
+ }
483
+
484
+ impl Node for ScrollBox {
485
+ fn retained_node_ref(&self) -> NodeRef {
486
+ let scroll_box = self.clone();
487
+ self.inner
488
+ .root
489
+ .retained_node_ref()
490
+ .with_build_callback(move || scroll_box.build())
491
+ }
492
+
493
+ fn build(&self) {
494
+ self.ensure_handle();
495
+ self.inner.root.build_self();
496
+ self.build_children();
497
+ self.inner
498
+ .persisted_restore_pending
499
+ .set(self.inner.persist_scroll.get());
500
+ self.bind_scroll_chrome();
501
+ self.refresh_chrome();
502
+ let _ = self.try_restore_persisted_scroll_offset();
503
+ }
504
+
505
+ fn build_self(&self) {
506
+ self.inner.root.build_self();
507
+ }
508
+ }
509
+
510
+ impl HasFlexBoxRoot for ScrollBox {
511
+ fn flex_box_root(&self) -> &FlexBox {
512
+ &self.inner.root
513
+ }
514
+ }
@@ -0,0 +1,121 @@
1
+ use crate::signal::{Callback, Signal, Subscription};
2
+ use std::cell::RefCell;
3
+ use std::rc::Rc;
4
+
5
+ fn set_signal(signal: &Rc<RefCell<Signal<f32>>>, next: f32) {
6
+ let callbacks = signal.borrow_mut().set(next);
7
+ if let Some(callbacks) = callbacks {
8
+ for callback in callbacks {
9
+ callback();
10
+ }
11
+ }
12
+ }
13
+
14
+ fn subscribe_signal(
15
+ signal: &Rc<RefCell<Signal<f32>>>,
16
+ handler: impl Fn() + 'static,
17
+ ) -> Subscription {
18
+ let callback: Callback = Rc::new(handler);
19
+ signal.borrow_mut().subscribe(callback)
20
+ }
21
+
22
+ #[derive(Clone)]
23
+ pub struct ScrollState {
24
+ offset_x: Rc<RefCell<Signal<f32>>>,
25
+ offset_y: Rc<RefCell<Signal<f32>>>,
26
+ content_width: Rc<RefCell<Signal<f32>>>,
27
+ content_height: Rc<RefCell<Signal<f32>>>,
28
+ viewport_width: Rc<RefCell<Signal<f32>>>,
29
+ viewport_height: Rc<RefCell<Signal<f32>>>,
30
+ }
31
+
32
+ impl Default for ScrollState {
33
+ fn default() -> Self {
34
+ Self::new()
35
+ }
36
+ }
37
+
38
+ impl ScrollState {
39
+ pub fn new() -> Self {
40
+ Self {
41
+ offset_x: Rc::new(RefCell::new(Signal::new(0.0))),
42
+ offset_y: Rc::new(RefCell::new(Signal::new(0.0))),
43
+ content_width: Rc::new(RefCell::new(Signal::new(0.0))),
44
+ content_height: Rc::new(RefCell::new(Signal::new(0.0))),
45
+ viewport_width: Rc::new(RefCell::new(Signal::new(0.0))),
46
+ viewport_height: Rc::new(RefCell::new(Signal::new(0.0))),
47
+ }
48
+ }
49
+
50
+ pub fn offset_x(&self) -> f32 {
51
+ self.offset_x.borrow().get()
52
+ }
53
+
54
+ pub fn offset_y(&self) -> f32 {
55
+ self.offset_y.borrow().get()
56
+ }
57
+
58
+ pub fn content_width(&self) -> f32 {
59
+ self.content_width.borrow().get()
60
+ }
61
+
62
+ pub fn content_height(&self) -> f32 {
63
+ self.content_height.borrow().get()
64
+ }
65
+
66
+ pub fn viewport_width(&self) -> f32 {
67
+ self.viewport_width.borrow().get()
68
+ }
69
+
70
+ pub fn viewport_height(&self) -> f32 {
71
+ self.viewport_height.borrow().get()
72
+ }
73
+
74
+ pub fn set_offset_x(&self, next: f32) {
75
+ set_signal(&self.offset_x, next);
76
+ }
77
+
78
+ pub fn set_offset_y(&self, next: f32) {
79
+ set_signal(&self.offset_y, next);
80
+ }
81
+
82
+ pub fn set_content_width(&self, next: f32) {
83
+ set_signal(&self.content_width, next);
84
+ }
85
+
86
+ pub fn set_content_height(&self, next: f32) {
87
+ set_signal(&self.content_height, next);
88
+ }
89
+
90
+ pub fn set_viewport_width(&self, next: f32) {
91
+ set_signal(&self.viewport_width, next);
92
+ }
93
+
94
+ pub fn set_viewport_height(&self, next: f32) {
95
+ set_signal(&self.viewport_height, next);
96
+ }
97
+
98
+ pub fn subscribe_offset_x(&self, handler: impl Fn() + 'static) -> Subscription {
99
+ subscribe_signal(&self.offset_x, handler)
100
+ }
101
+
102
+ pub fn subscribe_offset_y(&self, handler: impl Fn() + 'static) -> Subscription {
103
+ subscribe_signal(&self.offset_y, handler)
104
+ }
105
+
106
+ pub fn subscribe_content_width(&self, handler: impl Fn() + 'static) -> Subscription {
107
+ subscribe_signal(&self.content_width, handler)
108
+ }
109
+
110
+ pub fn subscribe_content_height(&self, handler: impl Fn() + 'static) -> Subscription {
111
+ subscribe_signal(&self.content_height, handler)
112
+ }
113
+
114
+ pub fn subscribe_viewport_width(&self, handler: impl Fn() + 'static) -> Subscription {
115
+ subscribe_signal(&self.viewport_width, handler)
116
+ }
117
+
118
+ pub fn subscribe_viewport_height(&self, handler: impl Fn() + 'static) -> Subscription {
119
+ subscribe_signal(&self.viewport_height, handler)
120
+ }
121
+ }