1use crate::state::Command;
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9
10pub trait Router: Send + Sync {
12 fn navigate(&self, route: &str);
14
15 fn current_route(&self) -> String;
17}
18
19pub trait Storage: Send + Sync {
21 fn save(&self, key: &str, data: &[u8]);
23
24 fn load(&self, key: &str) -> Option<Vec<u8>>;
26
27 fn remove(&self, key: &str);
29
30 fn contains(&self, key: &str) -> bool;
32}
33
34#[derive(Debug, Default)]
36pub struct MemoryStorage {
37 data: Mutex<HashMap<String, Vec<u8>>>,
38}
39
40impl MemoryStorage {
41 #[must_use]
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 #[must_use]
49 pub fn len(&self) -> usize {
50 self.data
51 .lock()
52 .expect("MemoryStorage mutex poisoned")
53 .len()
54 }
55
56 #[must_use]
58 pub fn is_empty(&self) -> bool {
59 self.data
60 .lock()
61 .expect("MemoryStorage mutex poisoned")
62 .is_empty()
63 }
64
65 pub fn clear(&self) {
67 self.data
68 .lock()
69 .expect("MemoryStorage mutex poisoned")
70 .clear();
71 }
72}
73
74impl Storage for MemoryStorage {
75 fn save(&self, key: &str, data: &[u8]) {
76 self.data
77 .lock()
78 .expect("MemoryStorage mutex poisoned")
79 .insert(key.to_string(), data.to_vec());
80 }
81
82 fn load(&self, key: &str) -> Option<Vec<u8>> {
83 self.data
84 .lock()
85 .expect("MemoryStorage mutex poisoned")
86 .get(key)
87 .cloned()
88 }
89
90 fn remove(&self, key: &str) {
91 self.data
92 .lock()
93 .expect("MemoryStorage mutex poisoned")
94 .remove(key);
95 }
96
97 fn contains(&self, key: &str) -> bool {
98 self.data
99 .lock()
100 .expect("MemoryStorage mutex poisoned")
101 .contains_key(key)
102 }
103}
104
105#[derive(Debug)]
107pub struct MemoryRouter {
108 route: Mutex<String>,
109 history: Mutex<Vec<String>>,
110}
111
112impl Default for MemoryRouter {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118impl MemoryRouter {
119 #[must_use]
121 pub fn new() -> Self {
122 Self {
123 route: Mutex::new("/".to_string()),
124 history: Mutex::new(vec!["/".to_string()]),
125 }
126 }
127
128 #[must_use]
130 pub fn history(&self) -> Vec<String> {
131 self.history
132 .lock()
133 .expect("MemoryRouter mutex poisoned")
134 .clone()
135 }
136
137 #[must_use]
139 pub fn history_len(&self) -> usize {
140 self.history
141 .lock()
142 .expect("MemoryRouter mutex poisoned")
143 .len()
144 }
145}
146
147impl Router for MemoryRouter {
148 fn navigate(&self, route: &str) {
149 let mut current = self.route.lock().expect("MemoryRouter mutex poisoned");
150 *current = route.to_string();
151 self.history
152 .lock()
153 .expect("MemoryRouter mutex poisoned")
154 .push(route.to_string());
155 }
156
157 fn current_route(&self) -> String {
158 self.route
159 .lock()
160 .expect("MemoryRouter mutex poisoned")
161 .clone()
162 }
163}
164
165#[derive(Debug)]
167pub enum ExecutionResult<M> {
168 None,
170 Message(M),
172 Messages(Vec<M>),
174 Pending,
176}
177
178impl<M> ExecutionResult<M> {
179 #[must_use]
181 pub const fn is_none(&self) -> bool {
182 matches!(self, Self::None)
183 }
184
185 #[must_use]
187 pub const fn has_messages(&self) -> bool {
188 matches!(self, Self::Message(_) | Self::Messages(_))
189 }
190
191 pub fn into_messages(self) -> Vec<M> {
193 match self {
194 Self::None | Self::Pending => vec![],
195 Self::Message(m) => vec![m],
196 Self::Messages(ms) => ms,
197 }
198 }
199}
200
201pub struct ExecutorConfig<R, S> {
203 pub router: Arc<R>,
205 pub storage: Arc<S>,
207}
208
209impl<R, S> std::fmt::Debug for ExecutorConfig<R, S> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("ExecutorConfig")
212 .field("router", &std::any::type_name::<R>())
213 .field("storage", &std::any::type_name::<S>())
214 .finish()
215 }
216}
217
218impl<R, S> std::fmt::Debug for CommandExecutor<R, S> {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 f.debug_struct("CommandExecutor")
221 .field("config", &self.config)
222 .finish()
223 }
224}
225
226impl<R: Router, S: Storage> ExecutorConfig<R, S> {
227 pub fn new(router: R, storage: S) -> Self {
229 Self {
230 router: Arc::new(router),
231 storage: Arc::new(storage),
232 }
233 }
234}
235
236pub struct CommandExecutor<R, S> {
240 config: ExecutorConfig<R, S>,
241}
242
243impl<R: Router, S: Storage> CommandExecutor<R, S> {
244 pub const fn new(config: ExecutorConfig<R, S>) -> Self {
246 Self { config }
247 }
248
249 pub fn execute<M: Send>(&self, command: Command<M>) -> ExecutionResult<M> {
254 match command {
255 Command::None => ExecutionResult::None,
256 Command::Batch(commands) => {
257 let mut messages = Vec::new();
258 for cmd in commands {
259 match self.execute(cmd) {
260 ExecutionResult::None | ExecutionResult::Pending => {}
261 ExecutionResult::Message(m) => messages.push(m),
262 ExecutionResult::Messages(ms) => messages.extend(ms),
263 }
264 }
265 if messages.is_empty() {
266 ExecutionResult::None
267 } else {
268 ExecutionResult::Messages(messages)
269 }
270 }
271 Command::Task(_) => {
272 ExecutionResult::Pending
274 }
275 Command::Navigate { route } => {
276 self.config.router.navigate(&route);
277 ExecutionResult::None
278 }
279 Command::SaveState { key } => {
280 let _ = key;
285 ExecutionResult::None
286 }
287 Command::LoadState { key, on_load } => {
288 let data = self.config.storage.load(&key);
289 let message = on_load(data);
290 ExecutionResult::Message(message)
291 }
292 }
293 }
294
295 pub fn router(&self) -> &R {
297 &self.config.router
298 }
299
300 pub fn storage(&self) -> &S {
302 &self.config.storage
303 }
304}
305
306#[must_use]
308pub fn default_executor() -> CommandExecutor<MemoryRouter, MemoryStorage> {
309 CommandExecutor::new(ExecutorConfig::new(
310 MemoryRouter::new(),
311 MemoryStorage::new(),
312 ))
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub enum FocusDirection {
322 Forward,
324 Backward,
326 Up,
328 Down,
330 Left,
332 Right,
334}
335
336#[derive(Debug, Default)]
338pub struct FocusManager {
339 focused: Option<u64>,
341 focus_ring: Vec<u64>,
343 traps: Vec<FocusTrap>,
345}
346
347#[derive(Debug)]
349pub struct FocusTrap {
350 pub widget_ids: Vec<u64>,
352 pub initial_focus: Option<u64>,
354}
355
356impl FocusManager {
357 #[must_use]
359 pub fn new() -> Self {
360 Self::default()
361 }
362
363 pub fn set_focus_ring(&mut self, widget_ids: Vec<u64>) {
365 self.focus_ring = widget_ids;
366 }
367
368 #[must_use]
370 pub const fn focused(&self) -> Option<u64> {
371 self.focused
372 }
373
374 pub fn focus(&mut self, widget_id: u64) -> bool {
376 let available = self.available_focus_ring();
377 if available.contains(&widget_id) {
378 self.focused = Some(widget_id);
379 true
380 } else {
381 false
382 }
383 }
384
385 pub fn blur(&mut self) {
387 self.focused = None;
388 }
389
390 pub fn move_focus(&mut self, direction: FocusDirection) -> Option<u64> {
392 let ring = self.available_focus_ring();
393 if ring.is_empty() {
394 return None;
395 }
396
397 let current_idx = self
398 .focused
399 .and_then(|f| ring.iter().position(|&id| id == f));
400
401 let next_idx = match direction {
402 FocusDirection::Forward | FocusDirection::Down | FocusDirection::Right => {
403 match current_idx {
404 Some(idx) => (idx + 1) % ring.len(),
405 None => 0,
406 }
407 }
408 FocusDirection::Backward | FocusDirection::Up | FocusDirection::Left => {
409 match current_idx {
410 Some(0) | None => ring.len() - 1,
411 Some(idx) => idx - 1,
412 }
413 }
414 };
415
416 let next_id = ring[next_idx];
417 self.focused = Some(next_id);
418 Some(next_id)
419 }
420
421 pub fn push_trap(&mut self, widget_ids: Vec<u64>) {
423 let initial = self.focused;
424 self.traps.push(FocusTrap {
425 widget_ids,
426 initial_focus: initial,
427 });
428 if let Some(first) = self.available_focus_ring().first().copied() {
430 self.focused = Some(first);
431 }
432 }
433
434 pub fn pop_trap(&mut self) -> Option<FocusTrap> {
436 let trap = self.traps.pop();
437 if let Some(ref t) = trap {
439 self.focused = t.initial_focus;
440 }
441 trap
442 }
443
444 #[must_use]
446 pub fn is_trapped(&self) -> bool {
447 !self.traps.is_empty()
448 }
449
450 fn available_focus_ring(&self) -> Vec<u64> {
452 if let Some(trap) = self.traps.last() {
453 trap.widget_ids.clone()
454 } else {
455 self.focus_ring.clone()
456 }
457 }
458
459 #[must_use]
461 pub fn is_focusable(&self, widget_id: u64) -> bool {
462 self.available_focus_ring().contains(&widget_id)
463 }
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
472pub enum EasingFunction {
473 #[default]
475 Linear,
476 EaseInQuad,
478 EaseOutQuad,
480 EaseInOutQuad,
482 EaseInCubic,
484 EaseOutCubic,
486 EaseInOutCubic,
488 EaseOutElastic,
490 EaseOutBounce,
492}
493
494impl EasingFunction {
495 #[must_use]
497 #[allow(clippy::suboptimal_flops)]
498 pub fn apply(self, t: f32) -> f32 {
499 let t = t.clamp(0.0, 1.0);
500 match self {
501 Self::Linear => t,
502 Self::EaseInQuad => t * t,
503 Self::EaseOutQuad => 1.0 - (1.0 - t) * (1.0 - t),
504 Self::EaseInOutQuad => {
505 if t < 0.5 {
506 2.0 * t * t
507 } else {
508 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
509 }
510 }
511 Self::EaseInCubic => t * t * t,
512 Self::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
513 Self::EaseInOutCubic => {
514 if t < 0.5 {
515 4.0 * t * t * t
516 } else {
517 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
518 }
519 }
520 Self::EaseOutElastic => {
521 if t == 0.0 || t == 1.0 {
522 t
523 } else {
524 let c4 = (2.0 * std::f32::consts::PI) / 3.0;
525 2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
526 }
527 }
528 Self::EaseOutBounce => {
529 let n1 = 7.5625;
530 let d1 = 2.75;
531 if t < 1.0 / d1 {
532 n1 * t * t
533 } else if t < 2.0 / d1 {
534 let t = t - 1.5 / d1;
535 n1 * t * t + 0.75
536 } else if t < 2.5 / d1 {
537 let t = t - 2.25 / d1;
538 n1 * t * t + 0.9375
539 } else {
540 let t = t - 2.625 / d1;
541 n1 * t * t + 0.984_375
542 }
543 }
544 }
545 }
546}
547
548#[derive(Debug, Clone)]
550pub struct Tween<T> {
551 pub from: T,
553 pub to: T,
555 pub duration_ms: u32,
557 pub easing: EasingFunction,
559 elapsed_ms: u32,
561}
562
563impl<T: Clone> Tween<T> {
564 pub fn new(from: T, to: T, duration_ms: u32) -> Self {
566 Self {
567 from,
568 to,
569 duration_ms,
570 easing: EasingFunction::default(),
571 elapsed_ms: 0,
572 }
573 }
574
575 #[must_use]
577 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
578 self.easing = easing;
579 self
580 }
581
582 #[must_use]
584 pub fn progress(&self) -> f32 {
585 if self.duration_ms == 0 {
586 1.0
587 } else {
588 (self.elapsed_ms as f32 / self.duration_ms as f32).min(1.0)
589 }
590 }
591
592 #[must_use]
594 pub fn eased_progress(&self) -> f32 {
595 self.easing.apply(self.progress())
596 }
597
598 #[must_use]
600 pub const fn is_complete(&self) -> bool {
601 self.elapsed_ms >= self.duration_ms
602 }
603
604 pub fn advance(&mut self, delta_ms: u32) {
606 self.elapsed_ms = self
607 .elapsed_ms
608 .saturating_add(delta_ms)
609 .min(self.duration_ms);
610 }
611
612 pub fn reset(&mut self) {
614 self.elapsed_ms = 0;
615 }
616}
617
618impl Tween<f32> {
619 #[must_use]
621 #[allow(clippy::suboptimal_flops)]
622 pub fn value(&self) -> f32 {
623 let t = self.eased_progress();
624 self.from + (self.to - self.from) * t
625 }
626}
627
628impl Tween<f64> {
629 #[must_use]
631 #[allow(clippy::suboptimal_flops)]
632 pub fn value(&self) -> f64 {
633 let t = f64::from(self.eased_progress());
634 self.from + (self.to - self.from) * t
635 }
636}
637
638#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640pub enum AnimationState {
641 Idle,
643 Running,
645 Paused,
647 Completed,
649}
650
651pub type AnimationId = u64;
653
654#[derive(Debug)]
656pub struct AnimationInstance {
657 pub id: AnimationId,
659 pub tween: Tween<f32>,
661 pub state: AnimationState,
663 pub loop_count: u32,
665 pub current_loop: u32,
667 pub alternate: bool,
669 forward: bool,
671}
672
673impl AnimationInstance {
674 pub fn new(id: AnimationId, from: f32, to: f32, duration_ms: u32) -> Self {
676 Self {
677 id,
678 tween: Tween::new(from, to, duration_ms),
679 state: AnimationState::Idle,
680 loop_count: 1,
681 current_loop: 0,
682 alternate: false,
683 forward: true,
684 }
685 }
686
687 #[must_use]
689 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
690 self.tween = self.tween.with_easing(easing);
691 self
692 }
693
694 #[must_use]
696 pub const fn with_loop_count(mut self, count: u32) -> Self {
697 self.loop_count = count;
698 self
699 }
700
701 #[must_use]
703 pub const fn with_alternate(mut self, alternate: bool) -> Self {
704 self.alternate = alternate;
705 self
706 }
707
708 pub fn start(&mut self) {
710 self.state = AnimationState::Running;
711 self.current_loop = 0;
712 self.forward = true;
713 self.tween.reset();
714 }
715
716 pub fn pause(&mut self) {
718 if self.state == AnimationState::Running {
719 self.state = AnimationState::Paused;
720 }
721 }
722
723 pub fn resume(&mut self) {
725 if self.state == AnimationState::Paused {
726 self.state = AnimationState::Running;
727 }
728 }
729
730 pub fn stop(&mut self) {
732 self.state = AnimationState::Idle;
733 self.tween.reset();
734 }
735
736 #[must_use]
738 #[allow(clippy::suboptimal_flops)]
739 pub fn value(&self) -> f32 {
740 if self.forward {
741 self.tween.value()
742 } else {
743 self.tween.from
744 + (self.tween.to - self.tween.from) * (1.0 - self.tween.eased_progress())
745 }
746 }
747
748 pub fn advance(&mut self, delta_ms: u32) {
750 if self.state != AnimationState::Running {
751 return;
752 }
753
754 self.tween.advance(delta_ms);
755
756 if self.tween.is_complete() {
757 if self.loop_count == 0 || self.current_loop + 1 < self.loop_count {
759 self.current_loop += 1;
760 self.tween.reset();
761
762 if self.alternate {
763 self.forward = !self.forward;
764 }
765 } else {
766 self.state = AnimationState::Completed;
767 }
768 }
769 }
770}
771
772#[derive(Debug, Default)]
774pub struct Animator {
775 animations: Vec<AnimationInstance>,
776 next_id: AnimationId,
777}
778
779impl Animator {
780 #[must_use]
782 pub fn new() -> Self {
783 Self::default()
784 }
785
786 pub fn create(&mut self, from: f32, to: f32, duration_ms: u32) -> AnimationId {
788 let id = self.next_id;
789 self.next_id += 1;
790 self.animations
791 .push(AnimationInstance::new(id, from, to, duration_ms));
792 id
793 }
794
795 #[must_use]
797 pub fn get(&self, id: AnimationId) -> Option<&AnimationInstance> {
798 self.animations.iter().find(|a| a.id == id)
799 }
800
801 pub fn get_mut(&mut self, id: AnimationId) -> Option<&mut AnimationInstance> {
803 self.animations.iter_mut().find(|a| a.id == id)
804 }
805
806 pub fn start(&mut self, id: AnimationId) {
808 if let Some(anim) = self.get_mut(id) {
809 anim.start();
810 }
811 }
812
813 pub fn pause(&mut self, id: AnimationId) {
815 if let Some(anim) = self.get_mut(id) {
816 anim.pause();
817 }
818 }
819
820 pub fn resume(&mut self, id: AnimationId) {
822 if let Some(anim) = self.get_mut(id) {
823 anim.resume();
824 }
825 }
826
827 pub fn stop(&mut self, id: AnimationId) {
829 if let Some(anim) = self.get_mut(id) {
830 anim.stop();
831 }
832 }
833
834 pub fn remove(&mut self, id: AnimationId) {
836 self.animations.retain(|a| a.id != id);
837 }
838
839 pub fn advance(&mut self, delta_ms: u32) {
841 for anim in &mut self.animations {
842 anim.advance(delta_ms);
843 }
844 }
845
846 #[must_use]
848 pub fn value(&self, id: AnimationId) -> Option<f32> {
849 self.get(id).map(AnimationInstance::value)
850 }
851
852 #[must_use]
854 pub fn len(&self) -> usize {
855 self.animations.len()
856 }
857
858 #[must_use]
860 pub fn is_empty(&self) -> bool {
861 self.animations.is_empty()
862 }
863
864 pub fn cleanup_completed(&mut self) {
866 self.animations
867 .retain(|a| a.state != AnimationState::Completed);
868 }
869
870 #[must_use]
872 pub fn has_running(&self) -> bool {
873 self.animations
874 .iter()
875 .any(|a| a.state == AnimationState::Running)
876 }
877}
878
879#[derive(Debug)]
881pub struct Timer {
882 pub interval_ms: u32,
884 elapsed_ms: u32,
886 running: bool,
888 tick_count: u64,
890 max_ticks: u64,
892}
893
894impl Timer {
895 #[must_use]
897 pub const fn new(interval_ms: u32) -> Self {
898 Self {
899 interval_ms,
900 elapsed_ms: 0,
901 running: false,
902 tick_count: 0,
903 max_ticks: 0,
904 }
905 }
906
907 #[must_use]
909 pub const fn with_max_ticks(mut self, max: u64) -> Self {
910 self.max_ticks = max;
911 self
912 }
913
914 pub fn start(&mut self) {
916 self.running = true;
917 }
918
919 pub fn stop(&mut self) {
921 self.running = false;
922 }
923
924 pub fn reset(&mut self) {
926 self.elapsed_ms = 0;
927 self.tick_count = 0;
928 }
929
930 #[must_use]
932 pub const fn is_running(&self) -> bool {
933 self.running
934 }
935
936 #[must_use]
938 pub const fn tick_count(&self) -> u64 {
939 self.tick_count
940 }
941
942 pub fn advance(&mut self, delta_ms: u32) -> u32 {
944 if !self.running || self.interval_ms == 0 {
945 return 0;
946 }
947
948 self.elapsed_ms += delta_ms;
949 let ticks = self.elapsed_ms / self.interval_ms;
950 self.elapsed_ms %= self.interval_ms;
951
952 let mut actual_ticks = 0;
954 for _ in 0..ticks {
955 if self.max_ticks > 0 && self.tick_count >= self.max_ticks {
956 self.running = false;
957 break;
958 }
959 self.tick_count += 1;
960 actual_ticks += 1;
961 }
962
963 actual_ticks
964 }
965
966 #[must_use]
968 pub fn progress(&self) -> f32 {
969 if self.interval_ms == 0 {
970 0.0
971 } else {
972 self.elapsed_ms as f32 / self.interval_ms as f32
973 }
974 }
975}
976
977#[derive(Debug)]
979pub struct FrameTimer {
980 target_frame_us: u64,
982 last_frame_us: Option<u64>,
984 frame_times: [u64; 60],
986 frame_index: usize,
988 delta_count: usize,
990 total_frames: u64,
992}
993
994impl Default for FrameTimer {
995 fn default() -> Self {
996 Self::new(60)
997 }
998}
999
1000impl FrameTimer {
1001 #[must_use]
1003 pub fn new(target_fps: u32) -> Self {
1004 let target_frame_us = if target_fps > 0 {
1005 1_000_000 / u64::from(target_fps)
1006 } else {
1007 16667
1008 };
1009 Self {
1010 target_frame_us,
1011 last_frame_us: None,
1012 frame_times: [0; 60],
1013 frame_index: 0,
1014 delta_count: 0,
1015 total_frames: 0,
1016 }
1017 }
1018
1019 pub fn frame(&mut self, now_us: u64) {
1021 if let Some(last) = self.last_frame_us {
1022 let delta = now_us.saturating_sub(last);
1023 self.frame_times[self.frame_index] = delta;
1024 self.frame_index = (self.frame_index + 1) % 60;
1025 self.delta_count = (self.delta_count + 1).min(60);
1026 }
1027 self.last_frame_us = Some(now_us);
1028 self.total_frames += 1;
1029 }
1030
1031 #[must_use]
1033 pub fn average_frame_time_us(&self) -> u64 {
1034 if self.delta_count == 0 {
1035 return self.target_frame_us;
1036 }
1037 let sum: u64 = self.frame_times[..self.delta_count].iter().sum();
1038 sum / self.delta_count as u64
1039 }
1040
1041 #[must_use]
1043 pub fn fps(&self) -> f32 {
1044 let avg = self.average_frame_time_us();
1045 if avg == 0 {
1046 0.0
1047 } else {
1048 1_000_000.0 / avg as f32
1049 }
1050 }
1051
1052 #[must_use]
1054 pub fn is_on_target(&self) -> bool {
1055 let avg = self.average_frame_time_us();
1056 let target = self.target_frame_us;
1057 avg <= target + target / 10
1059 }
1060
1061 #[must_use]
1063 pub fn target_frame_ms(&self) -> f32 {
1064 self.target_frame_us as f32 / 1000.0
1065 }
1066
1067 #[must_use]
1069 pub const fn total_frames(&self) -> u64 {
1070 self.total_frames
1071 }
1072}
1073
1074#[derive(Debug)]
1080pub struct DataRefreshManager {
1081 tasks: Vec<RefreshTask>,
1083 current_time_ms: u64,
1085}
1086
1087#[derive(Debug, Clone)]
1089pub struct RefreshTask {
1090 pub key: String,
1092 pub interval_ms: u64,
1094 pub last_refresh_ms: u64,
1096 pub active: bool,
1098}
1099
1100impl DataRefreshManager {
1101 #[must_use]
1103 pub const fn new() -> Self {
1104 Self {
1105 tasks: Vec::new(),
1106 current_time_ms: 0,
1107 }
1108 }
1109
1110 pub fn register(&mut self, key: impl Into<String>, interval_ms: u64) {
1117 let key = key.into();
1118
1119 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1121 task.interval_ms = interval_ms;
1122 task.active = true;
1123 return;
1124 }
1125
1126 self.tasks.push(RefreshTask {
1127 key,
1128 interval_ms,
1129 last_refresh_ms: 0,
1130 active: true,
1131 });
1132 }
1133
1134 pub fn unregister(&mut self, key: &str) {
1136 self.tasks.retain(|t| t.key != key);
1137 }
1138
1139 pub fn pause(&mut self, key: &str) {
1141 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1142 task.active = false;
1143 }
1144 }
1145
1146 pub fn resume(&mut self, key: &str) {
1148 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1149 task.active = true;
1150 }
1151 }
1152
1153 pub fn update(&mut self, current_time_ms: u64) -> Vec<String> {
1157 self.current_time_ms = current_time_ms;
1158
1159 let mut to_refresh = Vec::new();
1160
1161 for task in &mut self.tasks {
1162 if !task.active {
1163 continue;
1164 }
1165
1166 let elapsed = current_time_ms.saturating_sub(task.last_refresh_ms);
1167 if elapsed >= task.interval_ms {
1168 to_refresh.push(task.key.clone());
1169 task.last_refresh_ms = current_time_ms;
1170 }
1171 }
1172
1173 to_refresh
1174 }
1175
1176 pub fn force_refresh(&mut self, key: &str) -> bool {
1178 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1179 task.last_refresh_ms = 0;
1180 true
1181 } else {
1182 false
1183 }
1184 }
1185
1186 #[must_use]
1188 pub fn tasks(&self) -> &[RefreshTask] {
1189 &self.tasks
1190 }
1191
1192 #[must_use]
1194 pub fn get_task(&self, key: &str) -> Option<&RefreshTask> {
1195 self.tasks.iter().find(|t| t.key == key)
1196 }
1197
1198 #[must_use]
1200 pub fn is_due(&self, key: &str) -> bool {
1201 if let Some(task) = self.tasks.iter().find(|t| t.key == key) {
1202 if !task.active {
1203 return false;
1204 }
1205 let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1206 elapsed >= task.interval_ms
1207 } else {
1208 false
1209 }
1210 }
1211
1212 #[must_use]
1214 pub fn time_until_refresh(&self, key: &str) -> Option<u64> {
1215 self.tasks.iter().find(|t| t.key == key).map(|task| {
1216 if !task.active {
1217 return u64::MAX;
1218 }
1219 let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1220 task.interval_ms.saturating_sub(elapsed)
1221 })
1222 }
1223}
1224
1225impl Default for DataRefreshManager {
1226 fn default() -> Self {
1227 Self::new()
1228 }
1229}
1230
1231#[derive(Debug, Clone)]
1237pub struct TransitionConfig {
1238 pub duration_ms: u32,
1240 pub easing: EasingFunction,
1242 pub delay_ms: u32,
1244}
1245
1246impl Default for TransitionConfig {
1247 fn default() -> Self {
1248 Self {
1249 duration_ms: 300,
1250 easing: EasingFunction::EaseInOutCubic,
1251 delay_ms: 0,
1252 }
1253 }
1254}
1255
1256impl TransitionConfig {
1257 #[must_use]
1259 pub const fn new(duration_ms: u32) -> Self {
1260 Self {
1261 duration_ms,
1262 easing: EasingFunction::EaseInOutCubic,
1263 delay_ms: 0,
1264 }
1265 }
1266
1267 #[must_use]
1269 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
1270 self.easing = easing;
1271 self
1272 }
1273
1274 #[must_use]
1276 pub const fn with_delay(mut self, delay_ms: u32) -> Self {
1277 self.delay_ms = delay_ms;
1278 self
1279 }
1280
1281 #[must_use]
1283 pub const fn quick() -> Self {
1284 Self::new(150)
1285 }
1286
1287 #[must_use]
1289 pub const fn normal() -> Self {
1290 Self::new(300)
1291 }
1292
1293 #[must_use]
1295 pub const fn slow() -> Self {
1296 Self::new(500)
1297 }
1298}
1299
1300#[derive(Debug, Clone)]
1304pub struct AnimatedProperty<T> {
1305 current: T,
1307 target: T,
1309 start: T,
1311 config: TransitionConfig,
1313 elapsed_ms: u32,
1315 animating: bool,
1317}
1318
1319impl<T: Clone + Default> Default for AnimatedProperty<T> {
1320 fn default() -> Self {
1321 Self::new(T::default())
1322 }
1323}
1324
1325impl<T: Clone> AnimatedProperty<T> {
1326 pub fn new(value: T) -> Self {
1328 Self {
1329 current: value.clone(),
1330 target: value.clone(),
1331 start: value,
1332 config: TransitionConfig::default(),
1333 elapsed_ms: 0,
1334 animating: false,
1335 }
1336 }
1337
1338 pub fn with_config(value: T, config: TransitionConfig) -> Self {
1340 Self {
1341 current: value.clone(),
1342 target: value.clone(),
1343 start: value,
1344 config,
1345 elapsed_ms: 0,
1346 animating: false,
1347 }
1348 }
1349
1350 pub const fn get(&self) -> &T {
1352 &self.current
1353 }
1354
1355 pub const fn target(&self) -> &T {
1357 &self.target
1358 }
1359
1360 #[must_use]
1362 pub const fn is_animating(&self) -> bool {
1363 self.animating
1364 }
1365
1366 pub fn set(&mut self, value: T) {
1368 self.start = self.current.clone();
1369 self.target = value;
1370 self.elapsed_ms = 0;
1371 self.animating = true;
1372 }
1373
1374 pub fn set_immediate(&mut self, value: T) {
1376 self.current = value.clone();
1377 self.target = value.clone();
1378 self.start = value;
1379 self.animating = false;
1380 self.elapsed_ms = 0;
1381 }
1382
1383 #[must_use]
1385 pub fn progress(&self) -> f32 {
1386 if !self.animating {
1387 return 1.0;
1388 }
1389
1390 let total = self.config.duration_ms + self.config.delay_ms;
1391 if total == 0 {
1392 return 1.0;
1393 }
1394
1395 if self.elapsed_ms < self.config.delay_ms {
1396 return 0.0;
1397 }
1398
1399 let elapsed_after_delay = self.elapsed_ms - self.config.delay_ms;
1400 (elapsed_after_delay as f32 / self.config.duration_ms as f32).min(1.0)
1401 }
1402
1403 #[must_use]
1405 pub fn eased_progress(&self) -> f32 {
1406 self.config.easing.apply(self.progress())
1407 }
1408}
1409
1410impl AnimatedProperty<f32> {
1411 pub fn advance(&mut self, delta_ms: u32) {
1413 if !self.animating {
1414 return;
1415 }
1416
1417 self.elapsed_ms += delta_ms;
1418
1419 let t = self.eased_progress();
1420 self.current = (self.target - self.start).mul_add(t, self.start);
1421
1422 if self.progress() >= 1.0 {
1423 self.current = self.target;
1424 self.animating = false;
1425 }
1426 }
1427}
1428
1429impl AnimatedProperty<f64> {
1430 pub fn advance(&mut self, delta_ms: u32) {
1432 if !self.animating {
1433 return;
1434 }
1435
1436 self.elapsed_ms += delta_ms;
1437
1438 let t = f64::from(self.eased_progress());
1439 self.current = (self.target - self.start).mul_add(t, self.start);
1440
1441 if self.progress() >= 1.0 {
1442 self.current = self.target;
1443 self.animating = false;
1444 }
1445 }
1446}
1447
1448impl AnimatedProperty<crate::Color> {
1449 pub fn advance(&mut self, delta_ms: u32) {
1451 if !self.animating {
1452 return;
1453 }
1454
1455 self.elapsed_ms += delta_ms;
1456
1457 let t = self.eased_progress();
1458 self.current = crate::Color {
1459 r: (self.target.r - self.start.r).mul_add(t, self.start.r),
1460 g: (self.target.g - self.start.g).mul_add(t, self.start.g),
1461 b: (self.target.b - self.start.b).mul_add(t, self.start.b),
1462 a: (self.target.a - self.start.a).mul_add(t, self.start.a),
1463 };
1464
1465 if self.progress() >= 1.0 {
1466 self.current = self.target;
1467 self.animating = false;
1468 }
1469 }
1470}
1471
1472impl AnimatedProperty<crate::Point> {
1473 pub fn advance(&mut self, delta_ms: u32) {
1475 if !self.animating {
1476 return;
1477 }
1478
1479 self.elapsed_ms += delta_ms;
1480
1481 let t = self.eased_progress();
1482 self.current = crate::Point {
1483 x: (self.target.x - self.start.x).mul_add(t, self.start.x),
1484 y: (self.target.y - self.start.y).mul_add(t, self.start.y),
1485 };
1486
1487 if self.progress() >= 1.0 {
1488 self.current = self.target;
1489 self.animating = false;
1490 }
1491 }
1492}
1493
1494impl AnimatedProperty<crate::Size> {
1495 pub fn advance(&mut self, delta_ms: u32) {
1497 if !self.animating {
1498 return;
1499 }
1500
1501 self.elapsed_ms += delta_ms;
1502
1503 let t = self.eased_progress();
1504 self.current = crate::Size {
1505 width: (self.target.width - self.start.width).mul_add(t, self.start.width),
1506 height: (self.target.height - self.start.height).mul_add(t, self.start.height),
1507 };
1508
1509 if self.progress() >= 1.0 {
1510 self.current = self.target;
1511 self.animating = false;
1512 }
1513 }
1514}
1515
1516#[derive(Debug, Clone, Copy)]
1518pub struct SpringConfig {
1519 pub stiffness: f32,
1521 pub damping: f32,
1523 pub mass: f32,
1525}
1526
1527impl Default for SpringConfig {
1528 fn default() -> Self {
1529 Self {
1530 stiffness: 100.0,
1531 damping: 10.0,
1532 mass: 1.0,
1533 }
1534 }
1535}
1536
1537impl SpringConfig {
1538 #[must_use]
1540 pub const fn new(stiffness: f32, damping: f32, mass: f32) -> Self {
1541 Self {
1542 stiffness,
1543 damping,
1544 mass,
1545 }
1546 }
1547
1548 #[must_use]
1550 pub const fn gentle() -> Self {
1551 Self::new(100.0, 15.0, 1.0)
1552 }
1553
1554 #[must_use]
1556 pub const fn bouncy() -> Self {
1557 Self::new(300.0, 10.0, 1.0)
1558 }
1559
1560 #[must_use]
1562 pub const fn stiff() -> Self {
1563 Self::new(500.0, 30.0, 1.0)
1564 }
1565}
1566
1567#[derive(Debug, Clone)]
1569pub struct SpringAnimation {
1570 position: f32,
1572 velocity: f32,
1574 target: f32,
1576 config: SpringConfig,
1578 velocity_threshold: f32,
1580 position_threshold: f32,
1582}
1583
1584impl SpringAnimation {
1585 #[must_use]
1587 pub fn new(initial: f32) -> Self {
1588 Self {
1589 position: initial,
1590 velocity: 0.0,
1591 target: initial,
1592 config: SpringConfig::default(),
1593 velocity_threshold: 0.01,
1594 position_threshold: 0.001,
1595 }
1596 }
1597
1598 #[must_use]
1600 pub const fn with_config(initial: f32, config: SpringConfig) -> Self {
1601 Self {
1602 position: initial,
1603 velocity: 0.0,
1604 target: initial,
1605 config,
1606 velocity_threshold: 0.01,
1607 position_threshold: 0.001,
1608 }
1609 }
1610
1611 #[must_use]
1613 pub const fn position(&self) -> f32 {
1614 self.position
1615 }
1616
1617 #[must_use]
1619 pub const fn velocity(&self) -> f32 {
1620 self.velocity
1621 }
1622
1623 #[must_use]
1625 pub const fn target(&self) -> f32 {
1626 self.target
1627 }
1628
1629 pub fn set_target(&mut self, target: f32) {
1631 self.target = target;
1632 }
1633
1634 pub fn set_immediate(&mut self, position: f32) {
1636 self.position = position;
1637 self.target = position;
1638 self.velocity = 0.0;
1639 }
1640
1641 #[must_use]
1643 pub fn is_at_rest(&self) -> bool {
1644 let position_diff = (self.position - self.target).abs();
1645 let velocity_abs = self.velocity.abs();
1646 position_diff < self.position_threshold && velocity_abs < self.velocity_threshold
1647 }
1648
1649 pub fn advance(&mut self, delta_s: f32) {
1651 if self.is_at_rest() {
1652 self.position = self.target;
1653 self.velocity = 0.0;
1654 return;
1655 }
1656
1657 let displacement = self.position - self.target;
1660 let spring_force = -self.config.stiffness * displacement;
1661 let damping_force = -self.config.damping * self.velocity;
1662 let acceleration = (spring_force + damping_force) / self.config.mass;
1663
1664 self.velocity += acceleration * delta_s;
1666 self.position += self.velocity * delta_s;
1667 }
1668
1669 pub fn advance_ms(&mut self, delta_ms: u32) {
1671 self.advance(delta_ms as f32 / 1000.0);
1672 }
1673}