fret_ui_kit/primitives/
avatar.rs1use std::time::Duration;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum AvatarImageLoadingStatus {
18 #[default]
19 Idle,
20 Loading,
21 Loaded,
22 Error,
23}
24
25#[derive(Debug, Default, Clone, Copy)]
31pub struct AvatarFallbackDelay {
32 start_frame_id: Option<u64>,
33 last_frame_id: Option<u64>,
34 elapsed: Duration,
35}
36
37impl AvatarFallbackDelay {
38 pub fn drive(
45 &mut self,
46 frame_id: u64,
47 dt: Duration,
48 delay: Option<Duration>,
49 want_render: bool,
50 ) -> bool {
51 let Some(delay) = delay else {
52 *self = Self::default();
53 return want_render;
54 };
55
56 if !want_render {
57 *self = Self::default();
58 return false;
59 }
60
61 if delay == Duration::ZERO {
62 return true;
63 }
64
65 let _ = self.start_frame_id.get_or_insert(frame_id);
66 match self.last_frame_id {
67 None => {
68 self.last_frame_id = Some(frame_id);
69 }
70 Some(prev) if prev != frame_id => {
71 self.last_frame_id = Some(frame_id);
72 self.elapsed = self.elapsed.saturating_add(dt);
73 }
74 Some(_) => {}
75 }
76
77 self.elapsed >= delay
78 }
79}
80
81pub fn fallback_visible(status: AvatarImageLoadingStatus, delay_ready: bool) -> bool {
84 delay_ready && status != AvatarImageLoadingStatus::Loaded
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 const DT_16MS: Duration = Duration::from_millis(16);
92
93 #[test]
94 fn fallback_delay_gate_renders_immediately_without_delay() {
95 let mut gate = AvatarFallbackDelay::default();
96 assert!(!gate.drive(1, DT_16MS, None, false));
97 assert!(gate.drive(1, DT_16MS, None, true));
98 }
99
100 #[test]
101 fn fallback_delay_gate_waits_until_delay_elapses() {
102 let mut gate = AvatarFallbackDelay::default();
103 let delay = Some(Duration::from_millis(32));
104 assert!(!gate.drive(10, DT_16MS, delay, true));
105 assert!(!gate.drive(11, DT_16MS, delay, true));
106 assert!(gate.drive(12, DT_16MS, delay, true));
107 }
108
109 #[test]
110 fn fallback_delay_gate_resets_when_not_wanted() {
111 let mut gate = AvatarFallbackDelay::default();
112 let delay = Some(Duration::from_millis(32));
113 assert!(!gate.drive(10, DT_16MS, delay, true));
114 assert!(!gate.drive(11, DT_16MS, delay, false));
115 assert!(!gate.drive(12, DT_16MS, delay, true));
116 assert!(!gate.drive(13, DT_16MS, delay, true));
117 assert!(gate.drive(14, DT_16MS, delay, true));
118 }
119
120 #[test]
121 fn fallback_visible_hides_when_loaded() {
122 assert!(!fallback_visible(AvatarImageLoadingStatus::Loaded, true));
123 assert!(fallback_visible(AvatarImageLoadingStatus::Loading, true));
124 assert!(!fallback_visible(AvatarImageLoadingStatus::Loading, false));
125 }
126}