@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,625 @@
1
+ use crate::frame_scheduler::mark_needs_commit;
2
+ use std::cell::RefCell;
3
+ use std::rc::Rc;
4
+
5
+ const MAX_FRAME_DELTA_MS: f64 = 100.0;
6
+
7
+ fn clamp_unit(value: f32) -> f32 {
8
+ value.clamp(0.0, 1.0)
9
+ }
10
+
11
+ fn clamp_frame_delta(delta_ms: f64) -> f64 {
12
+ delta_ms.clamp(0.0, MAX_FRAME_DELTA_MS)
13
+ }
14
+
15
+ fn mix_float(from: f32, to: f32, amount: f32) -> f32 {
16
+ from + ((to - from) * clamp_unit(amount))
17
+ }
18
+
19
+ fn mix_color_channel(from: u32, to: u32, amount: f32) -> u32 {
20
+ let mixed = (from as f32) + (((to as f32) - (from as f32)) * clamp_unit(amount));
21
+ mixed.round().clamp(0.0, 255.0) as u32
22
+ }
23
+
24
+ fn mix_color(from: u32, to: u32, amount: f32) -> u32 {
25
+ let fr = (from >> 24) & 0xFF;
26
+ let fg = (from >> 16) & 0xFF;
27
+ let fb = (from >> 8) & 0xFF;
28
+ let fa = from & 0xFF;
29
+ let tr = (to >> 24) & 0xFF;
30
+ let tg = (to >> 16) & 0xFF;
31
+ let tb = (to >> 8) & 0xFF;
32
+ let ta = to & 0xFF;
33
+ (mix_color_channel(fr, tr, amount) << 24)
34
+ | (mix_color_channel(fg, tg, amount) << 16)
35
+ | (mix_color_channel(fb, tb, amount) << 8)
36
+ | mix_color_channel(fa, ta, amount)
37
+ }
38
+
39
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
40
+ pub enum Easing {
41
+ Linear,
42
+ CubicIn,
43
+ CubicOut,
44
+ CubicInOut,
45
+ QuadOut,
46
+ }
47
+
48
+ impl Easing {
49
+ pub fn sample(self, progress: f32) -> f32 {
50
+ let t = clamp_unit(progress);
51
+ match self {
52
+ Self::Linear => t,
53
+ Self::CubicIn => t * t * t,
54
+ Self::CubicOut => {
55
+ let offset = t - 1.0;
56
+ (offset * offset * offset) + 1.0
57
+ }
58
+ Self::CubicInOut => {
59
+ if t < 0.5 {
60
+ 4.0 * t * t * t
61
+ } else {
62
+ let offset = (-2.0 * t) + 2.0;
63
+ 1.0 - ((offset * offset * offset) * 0.5)
64
+ }
65
+ }
66
+ Self::QuadOut => {
67
+ let offset = 1.0 - t;
68
+ 1.0 - (offset * offset)
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ pub struct Easings;
75
+
76
+ impl Easings {
77
+ pub fn linear() -> Easing {
78
+ Easing::Linear
79
+ }
80
+
81
+ pub fn cubic_in() -> Easing {
82
+ Easing::CubicIn
83
+ }
84
+
85
+ pub fn cubic_out() -> Easing {
86
+ Easing::CubicOut
87
+ }
88
+
89
+ pub fn cubic_in_out() -> Easing {
90
+ Easing::CubicInOut
91
+ }
92
+
93
+ pub fn quad_out() -> Easing {
94
+ Easing::QuadOut
95
+ }
96
+ }
97
+
98
+ #[derive(Clone, Copy, Debug, PartialEq)]
99
+ pub struct AnimationTiming {
100
+ pub duration_ms: f64,
101
+ pub easing: Easing,
102
+ }
103
+
104
+ impl AnimationTiming {
105
+ pub fn new(duration_ms: f64) -> Self {
106
+ Self {
107
+ duration_ms: duration_ms.max(0.0),
108
+ easing: Easing::Linear,
109
+ }
110
+ }
111
+
112
+ pub fn with_easing(duration_ms: f64, easing: Easing) -> Self {
113
+ Self {
114
+ duration_ms: duration_ms.max(0.0),
115
+ easing,
116
+ }
117
+ }
118
+ }
119
+
120
+ trait AnimationDriver {
121
+ fn on_start(&mut self, _timestamp_ms: f64) {}
122
+ fn on_sample(&mut self, eased_progress: f32, linear_progress: f32);
123
+ fn on_stop(&mut self, _finished: bool) {}
124
+ }
125
+
126
+ struct AnimationState {
127
+ timing: AnimationTiming,
128
+ running: bool,
129
+ started: bool,
130
+ last_timestamp_ms: f64,
131
+ elapsed_ms: f64,
132
+ driver: Box<dyn AnimationDriver>,
133
+ }
134
+
135
+ #[derive(Clone)]
136
+ pub struct Animation {
137
+ inner: Rc<RefCell<AnimationState>>,
138
+ }
139
+
140
+ impl Animation {
141
+ fn new(timing: AnimationTiming, driver: Box<dyn AnimationDriver>) -> Self {
142
+ Self {
143
+ inner: Rc::new(RefCell::new(AnimationState {
144
+ timing,
145
+ running: false,
146
+ started: false,
147
+ last_timestamp_ms: 0.0,
148
+ elapsed_ms: 0.0,
149
+ driver,
150
+ })),
151
+ }
152
+ }
153
+
154
+ pub fn is_running(&self) -> bool {
155
+ self.inner.borrow().running
156
+ }
157
+
158
+ pub fn cancel(&self) {
159
+ get_animation_manager().cancel(self);
160
+ }
161
+
162
+ pub fn finish(&self) {
163
+ get_animation_manager().finish(self);
164
+ }
165
+
166
+ pub fn dispose(&self) {
167
+ self.cancel();
168
+ }
169
+
170
+ fn attach(&self, known_timestamp_ms: f64, has_known_timestamp: bool) {
171
+ {
172
+ let mut state = self.inner.borrow_mut();
173
+ state.running = true;
174
+ state.started = false;
175
+ state.last_timestamp_ms = 0.0;
176
+ state.elapsed_ms = 0.0;
177
+ }
178
+ if has_known_timestamp {
179
+ self.start_internal(known_timestamp_ms);
180
+ }
181
+ }
182
+
183
+ fn tick(&self, timestamp_ms: f64) {
184
+ if !self.is_running() {
185
+ return;
186
+ }
187
+ if !self.inner.borrow().started {
188
+ self.start_internal(timestamp_ms);
189
+ return;
190
+ }
191
+ let (duration_ms, easing, last_timestamp_ms) = {
192
+ let state = self.inner.borrow();
193
+ (
194
+ state.timing.duration_ms,
195
+ state.timing.easing,
196
+ state.last_timestamp_ms,
197
+ )
198
+ };
199
+ let delta_ms = clamp_frame_delta(timestamp_ms - last_timestamp_ms);
200
+ let should_finish = {
201
+ let mut state = self.inner.borrow_mut();
202
+ state.last_timestamp_ms = timestamp_ms;
203
+ state.elapsed_ms += delta_ms;
204
+ let progress = if duration_ms <= 0.0 {
205
+ 1.0
206
+ } else {
207
+ clamp_unit((state.elapsed_ms / duration_ms) as f32)
208
+ };
209
+ state.driver.on_sample(easing.sample(progress), progress);
210
+ progress >= 1.0
211
+ };
212
+ if should_finish {
213
+ self.stop(true);
214
+ }
215
+ }
216
+
217
+ fn cancel_internal(&self) {
218
+ if self.is_running() {
219
+ self.stop(false);
220
+ }
221
+ }
222
+
223
+ fn finish_internal(&self) {
224
+ if !self.is_running() {
225
+ return;
226
+ }
227
+ if !self.inner.borrow().started {
228
+ let mut state = self.inner.borrow_mut();
229
+ state.started = true;
230
+ state.last_timestamp_ms = 0.0;
231
+ state.elapsed_ms = state.timing.duration_ms;
232
+ state.driver.on_start(0.0);
233
+ }
234
+ self.inner.borrow_mut().driver.on_sample(1.0, 1.0);
235
+ self.stop(true);
236
+ }
237
+
238
+ fn start_internal(&self, timestamp_ms: f64) {
239
+ let duration_ms = {
240
+ let mut state = self.inner.borrow_mut();
241
+ state.started = true;
242
+ state.last_timestamp_ms = timestamp_ms;
243
+ state.elapsed_ms = 0.0;
244
+ state.driver.on_start(timestamp_ms);
245
+ state.timing.duration_ms
246
+ };
247
+ if duration_ms <= 0.0 {
248
+ self.inner.borrow_mut().driver.on_sample(1.0, 1.0);
249
+ self.stop(true);
250
+ return;
251
+ }
252
+ let easing = self.inner.borrow().timing.easing;
253
+ self.inner
254
+ .borrow_mut()
255
+ .driver
256
+ .on_sample(easing.sample(0.0), 0.0);
257
+ }
258
+
259
+ fn stop(&self, finished: bool) {
260
+ let mut state = self.inner.borrow_mut();
261
+ state.running = false;
262
+ state.driver.on_stop(finished);
263
+ }
264
+ }
265
+
266
+ #[derive(Default)]
267
+ struct AnimationManagerState {
268
+ active_animations: Vec<Animation>,
269
+ last_timestamp_ms: f64,
270
+ has_last_timestamp: bool,
271
+ }
272
+
273
+ thread_local! {
274
+ static ANIMATION_MANAGER: RefCell<AnimationManagerState> = const { RefCell::new(AnimationManagerState {
275
+ active_animations: Vec::new(),
276
+ last_timestamp_ms: 0.0,
277
+ has_last_timestamp: false,
278
+ }) };
279
+ }
280
+
281
+ #[derive(Clone, Copy, Debug, Default)]
282
+ pub struct AnimationManager;
283
+
284
+ impl AnimationManager {
285
+ pub fn start(&self, animation: Animation) -> Animation {
286
+ self.cancel(&animation);
287
+ ANIMATION_MANAGER.with(|slot| {
288
+ let mut state = slot.borrow_mut();
289
+ animation.attach(state.last_timestamp_ms, state.has_last_timestamp);
290
+ if animation.is_running() {
291
+ state.active_animations.push(animation.clone());
292
+ }
293
+ });
294
+ mark_needs_commit();
295
+ animation
296
+ }
297
+
298
+ pub fn cancel(&self, animation: &Animation) {
299
+ ANIMATION_MANAGER.with(|slot| {
300
+ let mut state = slot.borrow_mut();
301
+ if let Some(index) = state
302
+ .active_animations
303
+ .iter()
304
+ .position(|candidate| Rc::ptr_eq(&candidate.inner, &animation.inner))
305
+ {
306
+ state.active_animations.swap_remove(index);
307
+ animation.cancel_internal();
308
+ }
309
+ });
310
+ }
311
+
312
+ pub fn finish(&self, animation: &Animation) {
313
+ ANIMATION_MANAGER.with(|slot| {
314
+ let mut state = slot.borrow_mut();
315
+ if let Some(index) = state
316
+ .active_animations
317
+ .iter()
318
+ .position(|candidate| Rc::ptr_eq(&candidate.inner, &animation.inner))
319
+ {
320
+ state.active_animations.swap_remove(index);
321
+ animation.finish_internal();
322
+ }
323
+ });
324
+ }
325
+
326
+ pub fn tick(&self, timestamp_ms: f64) {
327
+ let active_animations = ANIMATION_MANAGER.with(|slot| {
328
+ let mut state = slot.borrow_mut();
329
+ state.last_timestamp_ms = timestamp_ms;
330
+ state.has_last_timestamp = true;
331
+ state.active_animations.clone()
332
+ });
333
+ let mut completed = Vec::new();
334
+ let mut has_active = false;
335
+ for animation in active_animations {
336
+ animation.tick(timestamp_ms);
337
+ if animation.is_running() {
338
+ has_active = true;
339
+ } else {
340
+ completed.push(animation);
341
+ }
342
+ }
343
+ if !completed.is_empty() {
344
+ ANIMATION_MANAGER.with(|slot| {
345
+ let mut state = slot.borrow_mut();
346
+ state.active_animations.retain(|candidate| {
347
+ !completed.iter().any(|completed_animation| {
348
+ Rc::ptr_eq(&candidate.inner, &completed_animation.inner)
349
+ })
350
+ });
351
+ });
352
+ }
353
+ if has_active {
354
+ mark_needs_commit();
355
+ }
356
+ }
357
+
358
+ pub fn has_active_animations(&self) -> bool {
359
+ ANIMATION_MANAGER.with(|slot| !slot.borrow().active_animations.is_empty())
360
+ }
361
+
362
+ pub fn reset(&self) {
363
+ ANIMATION_MANAGER.with(|slot| {
364
+ let mut state = slot.borrow_mut();
365
+ for animation in state.active_animations.drain(..) {
366
+ animation.cancel_internal();
367
+ }
368
+ state.last_timestamp_ms = 0.0;
369
+ state.has_last_timestamp = false;
370
+ });
371
+ }
372
+ }
373
+
374
+ pub fn get_animation_manager() -> AnimationManager {
375
+ AnimationManager
376
+ }
377
+
378
+ pub fn tick_animations(timestamp_ms: f64) {
379
+ get_animation_manager().tick(timestamp_ms);
380
+ }
381
+
382
+ pub fn reset_animations() {
383
+ get_animation_manager().reset();
384
+ }
385
+
386
+ struct FloatAnimationDriver {
387
+ from: f32,
388
+ to: f32,
389
+ handler: Box<dyn Fn(f32)>,
390
+ }
391
+
392
+ impl AnimationDriver for FloatAnimationDriver {
393
+ fn on_sample(&mut self, eased_progress: f32, _linear_progress: f32) {
394
+ (self.handler)(mix_float(self.from, self.to, eased_progress));
395
+ }
396
+ }
397
+
398
+ struct ColorAnimationDriver {
399
+ from: u32,
400
+ to: u32,
401
+ handler: Box<dyn Fn(u32)>,
402
+ }
403
+
404
+ impl AnimationDriver for ColorAnimationDriver {
405
+ fn on_sample(&mut self, eased_progress: f32, _linear_progress: f32) {
406
+ (self.handler)(mix_color(self.from, self.to, eased_progress));
407
+ }
408
+ }
409
+
410
+ pub fn animate_float(
411
+ from_value: f32,
412
+ to_value: f32,
413
+ timing: AnimationTiming,
414
+ handler: impl Fn(f32) + 'static,
415
+ ) -> Animation {
416
+ get_animation_manager().start(Animation::new(
417
+ timing,
418
+ Box::new(FloatAnimationDriver {
419
+ from: from_value,
420
+ to: to_value,
421
+ handler: Box::new(handler),
422
+ }),
423
+ ))
424
+ }
425
+
426
+ pub fn animate_float_with<Owner: 'static>(
427
+ owner: Owner,
428
+ from_value: f32,
429
+ to_value: f32,
430
+ timing: AnimationTiming,
431
+ handler: impl Fn(&Owner, f32) + 'static,
432
+ ) -> Animation {
433
+ animate_float(from_value, to_value, timing, move |value| {
434
+ handler(&owner, value);
435
+ })
436
+ }
437
+
438
+ pub fn animate_color(
439
+ from_value: u32,
440
+ to_value: u32,
441
+ timing: AnimationTiming,
442
+ handler: impl Fn(u32) + 'static,
443
+ ) -> Animation {
444
+ get_animation_manager().start(Animation::new(
445
+ timing,
446
+ Box::new(ColorAnimationDriver {
447
+ from: from_value,
448
+ to: to_value,
449
+ handler: Box::new(handler),
450
+ }),
451
+ ))
452
+ }
453
+
454
+ pub fn animate_color_with<Owner: 'static>(
455
+ owner: Owner,
456
+ from_value: u32,
457
+ to_value: u32,
458
+ timing: AnimationTiming,
459
+ handler: impl Fn(&Owner, u32) + 'static,
460
+ ) -> Animation {
461
+ animate_color(from_value, to_value, timing, move |value| {
462
+ handler(&owner, value);
463
+ })
464
+ }
465
+
466
+ #[cfg(test)]
467
+ mod tests {
468
+ use super::*;
469
+ use crate::bridge_callbacks::__fui_on_frame;
470
+ use crate::ffi::{self, Call};
471
+ use crate::frame_scheduler::reset_commit_state;
472
+ use std::cell::Cell;
473
+ use std::rc::Rc;
474
+
475
+ #[test]
476
+ fn advances_float_animations_from_frame_timestamps() {
477
+ reset_animations();
478
+ reset_commit_state();
479
+ let value = Rc::new(Cell::new(-1.0));
480
+ let value_for_handler = value.clone();
481
+ let animation = animate_float(10.0, 30.0, AnimationTiming::new(200.0), move |next| {
482
+ value_for_handler.set(next);
483
+ });
484
+ let manager = get_animation_manager();
485
+
486
+ manager.tick(1000.0);
487
+ assert_eq!(value.get(), 10.0);
488
+ assert!(animation.is_running());
489
+
490
+ manager.tick(1050.0);
491
+ assert_eq!(value.get(), 15.0);
492
+
493
+ manager.tick(1150.0);
494
+ assert_eq!(value.get(), 25.0);
495
+
496
+ manager.tick(1250.0);
497
+ assert_eq!(value.get(), 30.0);
498
+ assert!(!animation.is_running());
499
+ assert!(!manager.has_active_animations());
500
+ }
501
+
502
+ #[test]
503
+ fn clamps_stale_frame_gaps_instead_of_jumping_to_completion() {
504
+ reset_animations();
505
+ reset_commit_state();
506
+ let value = Rc::new(Cell::new(-1.0));
507
+ let value_for_handler = value.clone();
508
+ let manager = get_animation_manager();
509
+ animate_float(0.0, 100.0, AnimationTiming::new(200.0), move |next| {
510
+ value_for_handler.set(next);
511
+ });
512
+
513
+ manager.tick(1000.0);
514
+ manager.tick(1800.0);
515
+
516
+ assert_eq!(value.get(), 50.0);
517
+ assert!(manager.has_active_animations());
518
+ }
519
+
520
+ #[test]
521
+ fn supports_owner_bound_float_and_color_helpers() {
522
+ reset_animations();
523
+ reset_commit_state();
524
+ let float_owner = Rc::new(Cell::new(-1.0));
525
+ let color_owner = Rc::new(Cell::new(0u32));
526
+ let manager = get_animation_manager();
527
+
528
+ animate_float_with(
529
+ float_owner.clone(),
530
+ 4.0,
531
+ 12.0,
532
+ AnimationTiming::new(80.0),
533
+ |owner, value| owner.set(value),
534
+ );
535
+ animate_color_with(
536
+ color_owner.clone(),
537
+ 0x000000FF,
538
+ 0xFF0000FF,
539
+ AnimationTiming::new(80.0),
540
+ |owner, value| owner.set(value),
541
+ );
542
+
543
+ manager.tick(500.0);
544
+ assert_eq!(float_owner.get(), 4.0);
545
+ assert_eq!(color_owner.get(), 0x000000FF);
546
+
547
+ manager.tick(540.0);
548
+ assert_eq!(float_owner.get(), 8.0);
549
+ assert_eq!(color_owner.get(), 0x800000FF);
550
+
551
+ manager.tick(580.0);
552
+ assert_eq!(float_owner.get(), 12.0);
553
+ assert_eq!(color_owner.get(), 0xFF0000FF);
554
+ }
555
+
556
+ #[test]
557
+ fn cancels_and_finishes_animations_through_the_animation_object() {
558
+ reset_animations();
559
+ reset_commit_state();
560
+ let value = Rc::new(Cell::new(-1.0));
561
+ let first_value = value.clone();
562
+ let animation = animate_float(0.0, 100.0, AnimationTiming::new(100.0), move |next| {
563
+ first_value.set(next);
564
+ });
565
+ let manager = get_animation_manager();
566
+
567
+ manager.tick(100.0);
568
+ manager.tick(140.0);
569
+ assert_eq!(value.get(), 40.0);
570
+
571
+ animation.cancel();
572
+ manager.tick(200.0);
573
+ assert_eq!(value.get(), 40.0);
574
+ assert!(!animation.is_running());
575
+
576
+ let second_value = value.clone();
577
+ let second = animate_float(0.0, 100.0, AnimationTiming::new(100.0), move |next| {
578
+ second_value.set(next);
579
+ });
580
+ manager.tick(300.0);
581
+ second.finish();
582
+ assert_eq!(value.get(), 100.0);
583
+ assert!(!second.is_running());
584
+ assert!(!manager.has_active_animations());
585
+ }
586
+
587
+ #[test]
588
+ fn uses_the_shared_frame_hook_to_tick_active_animations() {
589
+ reset_animations();
590
+ reset_commit_state();
591
+ let owner = Rc::new(Cell::new(-1.0));
592
+ animate_float_with(
593
+ owner.clone(),
594
+ 2.0,
595
+ 6.0,
596
+ AnimationTiming::new(100.0),
597
+ |slot, value| {
598
+ slot.set(value);
599
+ },
600
+ );
601
+
602
+ __fui_on_frame(1000.0);
603
+ assert_eq!(crate::frame_signal::frame_time_signal().value(), 1000.0);
604
+ assert_eq!(owner.get(), 2.0);
605
+
606
+ __fui_on_frame(1050.0);
607
+ assert_eq!(owner.get(), 4.0);
608
+ }
609
+
610
+ #[test]
611
+ fn requests_render_when_animation_starts() {
612
+ reset_animations();
613
+ reset_commit_state();
614
+ ffi::test::reset();
615
+ animate_float(0.0, 1.0, AnimationTiming::new(50.0), |_| {});
616
+ let calls = ffi::test::take_calls();
617
+ assert_eq!(
618
+ calls
619
+ .iter()
620
+ .filter(|call| matches!(call, Call::RequestRender))
621
+ .count(),
622
+ 1
623
+ );
624
+ }
625
+ }