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: Router, S: Storage> ExecutorConfig<R, S> {
210 pub fn new(router: R, storage: S) -> Self {
212 Self {
213 router: Arc::new(router),
214 storage: Arc::new(storage),
215 }
216 }
217}
218
219pub struct CommandExecutor<R, S> {
223 config: ExecutorConfig<R, S>,
224}
225
226impl<R: Router, S: Storage> CommandExecutor<R, S> {
227 pub const fn new(config: ExecutorConfig<R, S>) -> Self {
229 Self { config }
230 }
231
232 pub fn execute<M: Send>(&self, command: Command<M>) -> ExecutionResult<M> {
237 match command {
238 Command::None => ExecutionResult::None,
239 Command::Batch(commands) => {
240 let mut messages = Vec::new();
241 for cmd in commands {
242 match self.execute(cmd) {
243 ExecutionResult::None | ExecutionResult::Pending => {}
244 ExecutionResult::Message(m) => messages.push(m),
245 ExecutionResult::Messages(ms) => messages.extend(ms),
246 }
247 }
248 if messages.is_empty() {
249 ExecutionResult::None
250 } else {
251 ExecutionResult::Messages(messages)
252 }
253 }
254 Command::Task(_) => {
255 ExecutionResult::Pending
257 }
258 Command::Navigate { route } => {
259 self.config.router.navigate(&route);
260 ExecutionResult::None
261 }
262 Command::SaveState { key } => {
263 let _ = key;
268 ExecutionResult::None
269 }
270 Command::LoadState { key, on_load } => {
271 let data = self.config.storage.load(&key);
272 let message = on_load(data);
273 ExecutionResult::Message(message)
274 }
275 }
276 }
277
278 pub fn router(&self) -> &R {
280 &self.config.router
281 }
282
283 pub fn storage(&self) -> &S {
285 &self.config.storage
286 }
287}
288
289#[must_use]
291pub fn default_executor() -> CommandExecutor<MemoryRouter, MemoryStorage> {
292 CommandExecutor::new(ExecutorConfig::new(
293 MemoryRouter::new(),
294 MemoryStorage::new(),
295 ))
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum FocusDirection {
305 Forward,
307 Backward,
309 Up,
311 Down,
313 Left,
315 Right,
317}
318
319#[derive(Debug, Default)]
321pub struct FocusManager {
322 focused: Option<u64>,
324 focus_ring: Vec<u64>,
326 traps: Vec<FocusTrap>,
328}
329
330#[derive(Debug)]
332pub struct FocusTrap {
333 pub widget_ids: Vec<u64>,
335 pub initial_focus: Option<u64>,
337}
338
339impl FocusManager {
340 #[must_use]
342 pub fn new() -> Self {
343 Self::default()
344 }
345
346 pub fn set_focus_ring(&mut self, widget_ids: Vec<u64>) {
348 self.focus_ring = widget_ids;
349 }
350
351 #[must_use]
353 pub const fn focused(&self) -> Option<u64> {
354 self.focused
355 }
356
357 pub fn focus(&mut self, widget_id: u64) -> bool {
359 let available = self.available_focus_ring();
360 if available.contains(&widget_id) {
361 self.focused = Some(widget_id);
362 true
363 } else {
364 false
365 }
366 }
367
368 pub fn blur(&mut self) {
370 self.focused = None;
371 }
372
373 pub fn move_focus(&mut self, direction: FocusDirection) -> Option<u64> {
375 let ring = self.available_focus_ring();
376 if ring.is_empty() {
377 return None;
378 }
379
380 let current_idx = self
381 .focused
382 .and_then(|f| ring.iter().position(|&id| id == f));
383
384 let next_idx = match direction {
385 FocusDirection::Forward | FocusDirection::Down | FocusDirection::Right => {
386 match current_idx {
387 Some(idx) => (idx + 1) % ring.len(),
388 None => 0,
389 }
390 }
391 FocusDirection::Backward | FocusDirection::Up | FocusDirection::Left => {
392 match current_idx {
393 Some(0) | None => ring.len() - 1,
394 Some(idx) => idx - 1,
395 }
396 }
397 };
398
399 let next_id = ring[next_idx];
400 self.focused = Some(next_id);
401 Some(next_id)
402 }
403
404 pub fn push_trap(&mut self, widget_ids: Vec<u64>) {
406 let initial = self.focused;
407 self.traps.push(FocusTrap {
408 widget_ids,
409 initial_focus: initial,
410 });
411 if let Some(first) = self.available_focus_ring().first().copied() {
413 self.focused = Some(first);
414 }
415 }
416
417 pub fn pop_trap(&mut self) -> Option<FocusTrap> {
419 let trap = self.traps.pop();
420 if let Some(ref t) = trap {
422 self.focused = t.initial_focus;
423 }
424 trap
425 }
426
427 #[must_use]
429 pub fn is_trapped(&self) -> bool {
430 !self.traps.is_empty()
431 }
432
433 fn available_focus_ring(&self) -> Vec<u64> {
435 if let Some(trap) = self.traps.last() {
436 trap.widget_ids.clone()
437 } else {
438 self.focus_ring.clone()
439 }
440 }
441
442 #[must_use]
444 pub fn is_focusable(&self, widget_id: u64) -> bool {
445 self.available_focus_ring().contains(&widget_id)
446 }
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
455pub enum EasingFunction {
456 #[default]
458 Linear,
459 EaseInQuad,
461 EaseOutQuad,
463 EaseInOutQuad,
465 EaseInCubic,
467 EaseOutCubic,
469 EaseInOutCubic,
471 EaseOutElastic,
473 EaseOutBounce,
475}
476
477impl EasingFunction {
478 #[must_use]
480 #[allow(clippy::suboptimal_flops)]
481 pub fn apply(self, t: f32) -> f32 {
482 let t = t.clamp(0.0, 1.0);
483 match self {
484 Self::Linear => t,
485 Self::EaseInQuad => t * t,
486 Self::EaseOutQuad => 1.0 - (1.0 - t) * (1.0 - t),
487 Self::EaseInOutQuad => {
488 if t < 0.5 {
489 2.0 * t * t
490 } else {
491 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
492 }
493 }
494 Self::EaseInCubic => t * t * t,
495 Self::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
496 Self::EaseInOutCubic => {
497 if t < 0.5 {
498 4.0 * t * t * t
499 } else {
500 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
501 }
502 }
503 Self::EaseOutElastic => {
504 if t == 0.0 || t == 1.0 {
505 t
506 } else {
507 let c4 = (2.0 * std::f32::consts::PI) / 3.0;
508 2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
509 }
510 }
511 Self::EaseOutBounce => {
512 let n1 = 7.5625;
513 let d1 = 2.75;
514 if t < 1.0 / d1 {
515 n1 * t * t
516 } else if t < 2.0 / d1 {
517 let t = t - 1.5 / d1;
518 n1 * t * t + 0.75
519 } else if t < 2.5 / d1 {
520 let t = t - 2.25 / d1;
521 n1 * t * t + 0.9375
522 } else {
523 let t = t - 2.625 / d1;
524 n1 * t * t + 0.984_375
525 }
526 }
527 }
528 }
529}
530
531#[derive(Debug, Clone)]
533pub struct Tween<T> {
534 pub from: T,
536 pub to: T,
538 pub duration_ms: u32,
540 pub easing: EasingFunction,
542 elapsed_ms: u32,
544}
545
546impl<T: Clone> Tween<T> {
547 pub fn new(from: T, to: T, duration_ms: u32) -> Self {
549 Self {
550 from,
551 to,
552 duration_ms,
553 easing: EasingFunction::default(),
554 elapsed_ms: 0,
555 }
556 }
557
558 #[must_use]
560 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
561 self.easing = easing;
562 self
563 }
564
565 #[must_use]
567 pub fn progress(&self) -> f32 {
568 if self.duration_ms == 0 {
569 1.0
570 } else {
571 (self.elapsed_ms as f32 / self.duration_ms as f32).min(1.0)
572 }
573 }
574
575 #[must_use]
577 pub fn eased_progress(&self) -> f32 {
578 self.easing.apply(self.progress())
579 }
580
581 #[must_use]
583 pub const fn is_complete(&self) -> bool {
584 self.elapsed_ms >= self.duration_ms
585 }
586
587 pub fn advance(&mut self, delta_ms: u32) {
589 self.elapsed_ms = self
590 .elapsed_ms
591 .saturating_add(delta_ms)
592 .min(self.duration_ms);
593 }
594
595 pub fn reset(&mut self) {
597 self.elapsed_ms = 0;
598 }
599}
600
601impl Tween<f32> {
602 #[must_use]
604 #[allow(clippy::suboptimal_flops)]
605 pub fn value(&self) -> f32 {
606 let t = self.eased_progress();
607 self.from + (self.to - self.from) * t
608 }
609}
610
611impl Tween<f64> {
612 #[must_use]
614 #[allow(clippy::suboptimal_flops)]
615 pub fn value(&self) -> f64 {
616 let t = f64::from(self.eased_progress());
617 self.from + (self.to - self.from) * t
618 }
619}
620
621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623pub enum AnimationState {
624 Idle,
626 Running,
628 Paused,
630 Completed,
632}
633
634pub type AnimationId = u64;
636
637#[derive(Debug)]
639pub struct AnimationInstance {
640 pub id: AnimationId,
642 pub tween: Tween<f32>,
644 pub state: AnimationState,
646 pub loop_count: u32,
648 pub current_loop: u32,
650 pub alternate: bool,
652 forward: bool,
654}
655
656impl AnimationInstance {
657 pub fn new(id: AnimationId, from: f32, to: f32, duration_ms: u32) -> Self {
659 Self {
660 id,
661 tween: Tween::new(from, to, duration_ms),
662 state: AnimationState::Idle,
663 loop_count: 1,
664 current_loop: 0,
665 alternate: false,
666 forward: true,
667 }
668 }
669
670 #[must_use]
672 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
673 self.tween = self.tween.with_easing(easing);
674 self
675 }
676
677 #[must_use]
679 pub const fn with_loop_count(mut self, count: u32) -> Self {
680 self.loop_count = count;
681 self
682 }
683
684 #[must_use]
686 pub const fn with_alternate(mut self, alternate: bool) -> Self {
687 self.alternate = alternate;
688 self
689 }
690
691 pub fn start(&mut self) {
693 self.state = AnimationState::Running;
694 self.current_loop = 0;
695 self.forward = true;
696 self.tween.reset();
697 }
698
699 pub fn pause(&mut self) {
701 if self.state == AnimationState::Running {
702 self.state = AnimationState::Paused;
703 }
704 }
705
706 pub fn resume(&mut self) {
708 if self.state == AnimationState::Paused {
709 self.state = AnimationState::Running;
710 }
711 }
712
713 pub fn stop(&mut self) {
715 self.state = AnimationState::Idle;
716 self.tween.reset();
717 }
718
719 #[must_use]
721 #[allow(clippy::suboptimal_flops)]
722 pub fn value(&self) -> f32 {
723 if self.forward {
724 self.tween.value()
725 } else {
726 self.tween.from
727 + (self.tween.to - self.tween.from) * (1.0 - self.tween.eased_progress())
728 }
729 }
730
731 pub fn advance(&mut self, delta_ms: u32) {
733 if self.state != AnimationState::Running {
734 return;
735 }
736
737 self.tween.advance(delta_ms);
738
739 if self.tween.is_complete() {
740 if self.loop_count == 0 || self.current_loop + 1 < self.loop_count {
742 self.current_loop += 1;
743 self.tween.reset();
744
745 if self.alternate {
746 self.forward = !self.forward;
747 }
748 } else {
749 self.state = AnimationState::Completed;
750 }
751 }
752 }
753}
754
755#[derive(Debug, Default)]
757pub struct Animator {
758 animations: Vec<AnimationInstance>,
759 next_id: AnimationId,
760}
761
762impl Animator {
763 #[must_use]
765 pub fn new() -> Self {
766 Self::default()
767 }
768
769 pub fn create(&mut self, from: f32, to: f32, duration_ms: u32) -> AnimationId {
771 let id = self.next_id;
772 self.next_id += 1;
773 self.animations
774 .push(AnimationInstance::new(id, from, to, duration_ms));
775 id
776 }
777
778 #[must_use]
780 pub fn get(&self, id: AnimationId) -> Option<&AnimationInstance> {
781 self.animations.iter().find(|a| a.id == id)
782 }
783
784 pub fn get_mut(&mut self, id: AnimationId) -> Option<&mut AnimationInstance> {
786 self.animations.iter_mut().find(|a| a.id == id)
787 }
788
789 pub fn start(&mut self, id: AnimationId) {
791 if let Some(anim) = self.get_mut(id) {
792 anim.start();
793 }
794 }
795
796 pub fn pause(&mut self, id: AnimationId) {
798 if let Some(anim) = self.get_mut(id) {
799 anim.pause();
800 }
801 }
802
803 pub fn resume(&mut self, id: AnimationId) {
805 if let Some(anim) = self.get_mut(id) {
806 anim.resume();
807 }
808 }
809
810 pub fn stop(&mut self, id: AnimationId) {
812 if let Some(anim) = self.get_mut(id) {
813 anim.stop();
814 }
815 }
816
817 pub fn remove(&mut self, id: AnimationId) {
819 self.animations.retain(|a| a.id != id);
820 }
821
822 pub fn advance(&mut self, delta_ms: u32) {
824 for anim in &mut self.animations {
825 anim.advance(delta_ms);
826 }
827 }
828
829 #[must_use]
831 pub fn value(&self, id: AnimationId) -> Option<f32> {
832 self.get(id).map(AnimationInstance::value)
833 }
834
835 #[must_use]
837 pub fn len(&self) -> usize {
838 self.animations.len()
839 }
840
841 #[must_use]
843 pub fn is_empty(&self) -> bool {
844 self.animations.is_empty()
845 }
846
847 pub fn cleanup_completed(&mut self) {
849 self.animations
850 .retain(|a| a.state != AnimationState::Completed);
851 }
852
853 #[must_use]
855 pub fn has_running(&self) -> bool {
856 self.animations
857 .iter()
858 .any(|a| a.state == AnimationState::Running)
859 }
860}
861
862#[derive(Debug)]
864pub struct Timer {
865 pub interval_ms: u32,
867 elapsed_ms: u32,
869 running: bool,
871 tick_count: u64,
873 max_ticks: u64,
875}
876
877impl Timer {
878 #[must_use]
880 pub const fn new(interval_ms: u32) -> Self {
881 Self {
882 interval_ms,
883 elapsed_ms: 0,
884 running: false,
885 tick_count: 0,
886 max_ticks: 0,
887 }
888 }
889
890 #[must_use]
892 pub const fn with_max_ticks(mut self, max: u64) -> Self {
893 self.max_ticks = max;
894 self
895 }
896
897 pub fn start(&mut self) {
899 self.running = true;
900 }
901
902 pub fn stop(&mut self) {
904 self.running = false;
905 }
906
907 pub fn reset(&mut self) {
909 self.elapsed_ms = 0;
910 self.tick_count = 0;
911 }
912
913 #[must_use]
915 pub const fn is_running(&self) -> bool {
916 self.running
917 }
918
919 #[must_use]
921 pub const fn tick_count(&self) -> u64 {
922 self.tick_count
923 }
924
925 pub fn advance(&mut self, delta_ms: u32) -> u32 {
927 if !self.running || self.interval_ms == 0 {
928 return 0;
929 }
930
931 self.elapsed_ms += delta_ms;
932 let ticks = self.elapsed_ms / self.interval_ms;
933 self.elapsed_ms %= self.interval_ms;
934
935 let mut actual_ticks = 0;
937 for _ in 0..ticks {
938 if self.max_ticks > 0 && self.tick_count >= self.max_ticks {
939 self.running = false;
940 break;
941 }
942 self.tick_count += 1;
943 actual_ticks += 1;
944 }
945
946 actual_ticks
947 }
948
949 #[must_use]
951 pub fn progress(&self) -> f32 {
952 if self.interval_ms == 0 {
953 0.0
954 } else {
955 self.elapsed_ms as f32 / self.interval_ms as f32
956 }
957 }
958}
959
960#[derive(Debug)]
962pub struct FrameTimer {
963 target_frame_us: u64,
965 last_frame_us: Option<u64>,
967 frame_times: [u64; 60],
969 frame_index: usize,
971 delta_count: usize,
973 total_frames: u64,
975}
976
977impl Default for FrameTimer {
978 fn default() -> Self {
979 Self::new(60)
980 }
981}
982
983impl FrameTimer {
984 #[must_use]
986 pub fn new(target_fps: u32) -> Self {
987 let target_frame_us = if target_fps > 0 {
988 1_000_000 / u64::from(target_fps)
989 } else {
990 16667
991 };
992 Self {
993 target_frame_us,
994 last_frame_us: None,
995 frame_times: [0; 60],
996 frame_index: 0,
997 delta_count: 0,
998 total_frames: 0,
999 }
1000 }
1001
1002 pub fn frame(&mut self, now_us: u64) {
1004 if let Some(last) = self.last_frame_us {
1005 let delta = now_us.saturating_sub(last);
1006 self.frame_times[self.frame_index] = delta;
1007 self.frame_index = (self.frame_index + 1) % 60;
1008 self.delta_count = (self.delta_count + 1).min(60);
1009 }
1010 self.last_frame_us = Some(now_us);
1011 self.total_frames += 1;
1012 }
1013
1014 #[must_use]
1016 pub fn average_frame_time_us(&self) -> u64 {
1017 if self.delta_count == 0 {
1018 return self.target_frame_us;
1019 }
1020 let sum: u64 = self.frame_times[..self.delta_count].iter().sum();
1021 sum / self.delta_count as u64
1022 }
1023
1024 #[must_use]
1026 pub fn fps(&self) -> f32 {
1027 let avg = self.average_frame_time_us();
1028 if avg == 0 {
1029 0.0
1030 } else {
1031 1_000_000.0 / avg as f32
1032 }
1033 }
1034
1035 #[must_use]
1037 pub fn is_on_target(&self) -> bool {
1038 let avg = self.average_frame_time_us();
1039 let target = self.target_frame_us;
1040 avg <= target + target / 10
1042 }
1043
1044 #[must_use]
1046 pub fn target_frame_ms(&self) -> f32 {
1047 self.target_frame_us as f32 / 1000.0
1048 }
1049
1050 #[must_use]
1052 pub const fn total_frames(&self) -> u64 {
1053 self.total_frames
1054 }
1055}
1056
1057#[derive(Debug)]
1063pub struct DataRefreshManager {
1064 tasks: Vec<RefreshTask>,
1066 current_time_ms: u64,
1068}
1069
1070#[derive(Debug, Clone)]
1072pub struct RefreshTask {
1073 pub key: String,
1075 pub interval_ms: u64,
1077 pub last_refresh_ms: u64,
1079 pub active: bool,
1081}
1082
1083impl DataRefreshManager {
1084 #[must_use]
1086 pub const fn new() -> Self {
1087 Self {
1088 tasks: Vec::new(),
1089 current_time_ms: 0,
1090 }
1091 }
1092
1093 pub fn register(&mut self, key: impl Into<String>, interval_ms: u64) {
1100 let key = key.into();
1101
1102 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1104 task.interval_ms = interval_ms;
1105 task.active = true;
1106 return;
1107 }
1108
1109 self.tasks.push(RefreshTask {
1110 key,
1111 interval_ms,
1112 last_refresh_ms: 0,
1113 active: true,
1114 });
1115 }
1116
1117 pub fn unregister(&mut self, key: &str) {
1119 self.tasks.retain(|t| t.key != key);
1120 }
1121
1122 pub fn pause(&mut self, key: &str) {
1124 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1125 task.active = false;
1126 }
1127 }
1128
1129 pub fn resume(&mut self, key: &str) {
1131 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1132 task.active = true;
1133 }
1134 }
1135
1136 pub fn update(&mut self, current_time_ms: u64) -> Vec<String> {
1140 self.current_time_ms = current_time_ms;
1141
1142 let mut to_refresh = Vec::new();
1143
1144 for task in &mut self.tasks {
1145 if !task.active {
1146 continue;
1147 }
1148
1149 let elapsed = current_time_ms.saturating_sub(task.last_refresh_ms);
1150 if elapsed >= task.interval_ms {
1151 to_refresh.push(task.key.clone());
1152 task.last_refresh_ms = current_time_ms;
1153 }
1154 }
1155
1156 to_refresh
1157 }
1158
1159 pub fn force_refresh(&mut self, key: &str) -> bool {
1161 if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1162 task.last_refresh_ms = 0;
1163 true
1164 } else {
1165 false
1166 }
1167 }
1168
1169 #[must_use]
1171 pub fn tasks(&self) -> &[RefreshTask] {
1172 &self.tasks
1173 }
1174
1175 #[must_use]
1177 pub fn get_task(&self, key: &str) -> Option<&RefreshTask> {
1178 self.tasks.iter().find(|t| t.key == key)
1179 }
1180
1181 #[must_use]
1183 pub fn is_due(&self, key: &str) -> bool {
1184 if let Some(task) = self.tasks.iter().find(|t| t.key == key) {
1185 if !task.active {
1186 return false;
1187 }
1188 let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1189 elapsed >= task.interval_ms
1190 } else {
1191 false
1192 }
1193 }
1194
1195 #[must_use]
1197 pub fn time_until_refresh(&self, key: &str) -> Option<u64> {
1198 self.tasks.iter().find(|t| t.key == key).map(|task| {
1199 if !task.active {
1200 return u64::MAX;
1201 }
1202 let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1203 task.interval_ms.saturating_sub(elapsed)
1204 })
1205 }
1206}
1207
1208impl Default for DataRefreshManager {
1209 fn default() -> Self {
1210 Self::new()
1211 }
1212}
1213
1214#[derive(Debug, Clone)]
1220pub struct TransitionConfig {
1221 pub duration_ms: u32,
1223 pub easing: EasingFunction,
1225 pub delay_ms: u32,
1227}
1228
1229impl Default for TransitionConfig {
1230 fn default() -> Self {
1231 Self {
1232 duration_ms: 300,
1233 easing: EasingFunction::EaseInOutCubic,
1234 delay_ms: 0,
1235 }
1236 }
1237}
1238
1239impl TransitionConfig {
1240 #[must_use]
1242 pub const fn new(duration_ms: u32) -> Self {
1243 Self {
1244 duration_ms,
1245 easing: EasingFunction::EaseInOutCubic,
1246 delay_ms: 0,
1247 }
1248 }
1249
1250 #[must_use]
1252 pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
1253 self.easing = easing;
1254 self
1255 }
1256
1257 #[must_use]
1259 pub const fn with_delay(mut self, delay_ms: u32) -> Self {
1260 self.delay_ms = delay_ms;
1261 self
1262 }
1263
1264 #[must_use]
1266 pub const fn quick() -> Self {
1267 Self::new(150)
1268 }
1269
1270 #[must_use]
1272 pub const fn normal() -> Self {
1273 Self::new(300)
1274 }
1275
1276 #[must_use]
1278 pub const fn slow() -> Self {
1279 Self::new(500)
1280 }
1281}
1282
1283#[derive(Debug, Clone)]
1287pub struct AnimatedProperty<T> {
1288 current: T,
1290 target: T,
1292 start: T,
1294 config: TransitionConfig,
1296 elapsed_ms: u32,
1298 animating: bool,
1300}
1301
1302impl<T: Clone + Default> Default for AnimatedProperty<T> {
1303 fn default() -> Self {
1304 Self::new(T::default())
1305 }
1306}
1307
1308impl<T: Clone> AnimatedProperty<T> {
1309 pub fn new(value: T) -> Self {
1311 Self {
1312 current: value.clone(),
1313 target: value.clone(),
1314 start: value,
1315 config: TransitionConfig::default(),
1316 elapsed_ms: 0,
1317 animating: false,
1318 }
1319 }
1320
1321 pub fn with_config(value: T, config: TransitionConfig) -> Self {
1323 Self {
1324 current: value.clone(),
1325 target: value.clone(),
1326 start: value,
1327 config,
1328 elapsed_ms: 0,
1329 animating: false,
1330 }
1331 }
1332
1333 pub const fn get(&self) -> &T {
1335 &self.current
1336 }
1337
1338 pub const fn target(&self) -> &T {
1340 &self.target
1341 }
1342
1343 #[must_use]
1345 pub const fn is_animating(&self) -> bool {
1346 self.animating
1347 }
1348
1349 pub fn set(&mut self, value: T) {
1351 self.start = self.current.clone();
1352 self.target = value;
1353 self.elapsed_ms = 0;
1354 self.animating = true;
1355 }
1356
1357 pub fn set_immediate(&mut self, value: T) {
1359 self.current = value.clone();
1360 self.target = value.clone();
1361 self.start = value;
1362 self.animating = false;
1363 self.elapsed_ms = 0;
1364 }
1365
1366 #[must_use]
1368 pub fn progress(&self) -> f32 {
1369 if !self.animating {
1370 return 1.0;
1371 }
1372
1373 let total = self.config.duration_ms + self.config.delay_ms;
1374 if total == 0 {
1375 return 1.0;
1376 }
1377
1378 if self.elapsed_ms < self.config.delay_ms {
1379 return 0.0;
1380 }
1381
1382 let elapsed_after_delay = self.elapsed_ms - self.config.delay_ms;
1383 (elapsed_after_delay as f32 / self.config.duration_ms as f32).min(1.0)
1384 }
1385
1386 #[must_use]
1388 pub fn eased_progress(&self) -> f32 {
1389 self.config.easing.apply(self.progress())
1390 }
1391}
1392
1393impl AnimatedProperty<f32> {
1394 pub fn advance(&mut self, delta_ms: u32) {
1396 if !self.animating {
1397 return;
1398 }
1399
1400 self.elapsed_ms += delta_ms;
1401
1402 let t = self.eased_progress();
1403 self.current = (self.target - self.start).mul_add(t, self.start);
1404
1405 if self.progress() >= 1.0 {
1406 self.current = self.target;
1407 self.animating = false;
1408 }
1409 }
1410}
1411
1412impl AnimatedProperty<f64> {
1413 pub fn advance(&mut self, delta_ms: u32) {
1415 if !self.animating {
1416 return;
1417 }
1418
1419 self.elapsed_ms += delta_ms;
1420
1421 let t = f64::from(self.eased_progress());
1422 self.current = (self.target - self.start).mul_add(t, self.start);
1423
1424 if self.progress() >= 1.0 {
1425 self.current = self.target;
1426 self.animating = false;
1427 }
1428 }
1429}
1430
1431impl AnimatedProperty<crate::Color> {
1432 pub fn advance(&mut self, delta_ms: u32) {
1434 if !self.animating {
1435 return;
1436 }
1437
1438 self.elapsed_ms += delta_ms;
1439
1440 let t = self.eased_progress();
1441 self.current = crate::Color {
1442 r: (self.target.r - self.start.r).mul_add(t, self.start.r),
1443 g: (self.target.g - self.start.g).mul_add(t, self.start.g),
1444 b: (self.target.b - self.start.b).mul_add(t, self.start.b),
1445 a: (self.target.a - self.start.a).mul_add(t, self.start.a),
1446 };
1447
1448 if self.progress() >= 1.0 {
1449 self.current = self.target;
1450 self.animating = false;
1451 }
1452 }
1453}
1454
1455impl AnimatedProperty<crate::Point> {
1456 pub fn advance(&mut self, delta_ms: u32) {
1458 if !self.animating {
1459 return;
1460 }
1461
1462 self.elapsed_ms += delta_ms;
1463
1464 let t = self.eased_progress();
1465 self.current = crate::Point {
1466 x: (self.target.x - self.start.x).mul_add(t, self.start.x),
1467 y: (self.target.y - self.start.y).mul_add(t, self.start.y),
1468 };
1469
1470 if self.progress() >= 1.0 {
1471 self.current = self.target;
1472 self.animating = false;
1473 }
1474 }
1475}
1476
1477impl AnimatedProperty<crate::Size> {
1478 pub fn advance(&mut self, delta_ms: u32) {
1480 if !self.animating {
1481 return;
1482 }
1483
1484 self.elapsed_ms += delta_ms;
1485
1486 let t = self.eased_progress();
1487 self.current = crate::Size {
1488 width: (self.target.width - self.start.width).mul_add(t, self.start.width),
1489 height: (self.target.height - self.start.height).mul_add(t, self.start.height),
1490 };
1491
1492 if self.progress() >= 1.0 {
1493 self.current = self.target;
1494 self.animating = false;
1495 }
1496 }
1497}
1498
1499#[derive(Debug, Clone, Copy)]
1501pub struct SpringConfig {
1502 pub stiffness: f32,
1504 pub damping: f32,
1506 pub mass: f32,
1508}
1509
1510impl Default for SpringConfig {
1511 fn default() -> Self {
1512 Self {
1513 stiffness: 100.0,
1514 damping: 10.0,
1515 mass: 1.0,
1516 }
1517 }
1518}
1519
1520impl SpringConfig {
1521 #[must_use]
1523 pub const fn new(stiffness: f32, damping: f32, mass: f32) -> Self {
1524 Self {
1525 stiffness,
1526 damping,
1527 mass,
1528 }
1529 }
1530
1531 #[must_use]
1533 pub const fn gentle() -> Self {
1534 Self::new(100.0, 15.0, 1.0)
1535 }
1536
1537 #[must_use]
1539 pub const fn bouncy() -> Self {
1540 Self::new(300.0, 10.0, 1.0)
1541 }
1542
1543 #[must_use]
1545 pub const fn stiff() -> Self {
1546 Self::new(500.0, 30.0, 1.0)
1547 }
1548}
1549
1550#[derive(Debug, Clone)]
1552pub struct SpringAnimation {
1553 position: f32,
1555 velocity: f32,
1557 target: f32,
1559 config: SpringConfig,
1561 velocity_threshold: f32,
1563 position_threshold: f32,
1565}
1566
1567impl SpringAnimation {
1568 #[must_use]
1570 pub fn new(initial: f32) -> Self {
1571 Self {
1572 position: initial,
1573 velocity: 0.0,
1574 target: initial,
1575 config: SpringConfig::default(),
1576 velocity_threshold: 0.01,
1577 position_threshold: 0.001,
1578 }
1579 }
1580
1581 #[must_use]
1583 pub const fn with_config(initial: f32, config: SpringConfig) -> Self {
1584 Self {
1585 position: initial,
1586 velocity: 0.0,
1587 target: initial,
1588 config,
1589 velocity_threshold: 0.01,
1590 position_threshold: 0.001,
1591 }
1592 }
1593
1594 #[must_use]
1596 pub const fn position(&self) -> f32 {
1597 self.position
1598 }
1599
1600 #[must_use]
1602 pub const fn velocity(&self) -> f32 {
1603 self.velocity
1604 }
1605
1606 #[must_use]
1608 pub const fn target(&self) -> f32 {
1609 self.target
1610 }
1611
1612 pub fn set_target(&mut self, target: f32) {
1614 self.target = target;
1615 }
1616
1617 pub fn set_immediate(&mut self, position: f32) {
1619 self.position = position;
1620 self.target = position;
1621 self.velocity = 0.0;
1622 }
1623
1624 #[must_use]
1626 pub fn is_at_rest(&self) -> bool {
1627 let position_diff = (self.position - self.target).abs();
1628 let velocity_abs = self.velocity.abs();
1629 position_diff < self.position_threshold && velocity_abs < self.velocity_threshold
1630 }
1631
1632 pub fn advance(&mut self, delta_s: f32) {
1634 if self.is_at_rest() {
1635 self.position = self.target;
1636 self.velocity = 0.0;
1637 return;
1638 }
1639
1640 let displacement = self.position - self.target;
1643 let spring_force = -self.config.stiffness * displacement;
1644 let damping_force = -self.config.damping * self.velocity;
1645 let acceleration = (spring_force + damping_force) / self.config.mass;
1646
1647 self.velocity += acceleration * delta_s;
1649 self.position += self.velocity * delta_s;
1650 }
1651
1652 pub fn advance_ms(&mut self, delta_ms: u32) {
1654 self.advance(delta_ms as f32 / 1000.0);
1655 }
1656}