ori_core/style/
transition.rs

1use std::{
2    any::Any,
3    ops::{Add, Mul},
4};
5
6use ori_graphics::Color;
7use smallvec::SmallVec;
8use smol_str::SmolStr;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11pub struct StyleTransition {
12    pub duration: f32,
13}
14
15impl StyleTransition {
16    pub const fn new(duration: f32) -> Self {
17        Self { duration }
18    }
19
20    pub const fn instant() -> Self {
21        Self::new(0.0)
22    }
23}
24
25impl From<f32> for StyleTransition {
26    fn from(duration: f32) -> Self {
27        Self::new(duration)
28    }
29}
30
31pub trait Transitionable
32where
33    Self: Mul<f32, Output = Self> + Add<Output = Self> + PartialEq + Copy,
34{
35}
36
37impl<T: Mul<f32, Output = T> + Add<Output = T> + PartialEq + Copy> Transitionable for T {}
38
39#[derive(Clone, Copy, Debug)]
40pub struct TransitionState<T> {
41    pub from: Option<T>,
42    pub to: Option<T>,
43    pub prev_transition: Option<StyleTransition>,
44    pub transition: Option<StyleTransition>,
45    pub elapsed: f32,
46}
47
48impl<T> Default for TransitionState<T> {
49    fn default() -> Self {
50        Self {
51            from: None,
52            to: None,
53            prev_transition: None,
54            transition: None,
55            elapsed: 0.0,
56        }
57    }
58}
59
60impl<T: Transitionable> TransitionState<T> {
61    fn mix(from: T, to: T, progress: f32) -> T {
62        from * (1.0 - progress) + to * progress
63    }
64
65    fn transition(&self) -> Option<StyleTransition> {
66        if let Some(transition) = self.transition {
67            return Some(transition);
68        }
69
70        self.prev_transition
71    }
72
73    fn is_complete(&self) -> bool {
74        if let Some(transition) = self.transition() {
75            return self.elapsed >= transition.duration;
76        }
77
78        true
79    }
80
81    pub fn get(&mut self, to: T, transition: Option<StyleTransition>) -> T {
82        if self.from.is_none() {
83            self.from = Some(to);
84        }
85
86        if self.transition != transition || self.to != Some(to) {
87            if let (Some(prev), Some(new)) = (self.transition, transition) {
88                let progress = self.elapsed / prev.duration;
89
90                self.elapsed = new.duration - (new.duration * progress);
91            } else {
92                self.elapsed = 0.0;
93            }
94
95            self.prev_transition = self.transition;
96            self.transition = transition;
97            self.to = Some(to);
98        }
99
100        if self.is_complete() {
101            return to;
102        }
103
104        self.from.unwrap()
105    }
106
107    pub fn update(&mut self, delta: f32) -> bool {
108        if self.is_complete() {
109            return false;
110        }
111
112        let Some(transition) = self.transition() else {
113            return false;
114        };
115
116        let Some(to) = self.to else {
117            return false;
118        };
119
120        // TODO: this is some bs
121        if self.elapsed == 0.0 {
122            self.elapsed = f32::EPSILON;
123            return true;
124        }
125
126        let remaining = transition.duration - self.elapsed;
127        let delta = delta.min(remaining);
128        let progress = delta / remaining;
129
130        let value = Self::mix(self.from.unwrap(), to, progress);
131        self.from = Some(value);
132
133        self.elapsed += delta;
134
135        true
136    }
137}
138
139#[derive(Clone, Debug, Default)]
140pub struct TransitionStates {
141    units: SmallVec<[(SmolStr, TransitionState<f32>); 4]>,
142    colors: SmallVec<[(SmolStr, TransitionState<Color>); 4]>,
143}
144
145impl TransitionStates {
146    pub const fn new() -> Self {
147        Self {
148            units: SmallVec::new_const(),
149            colors: SmallVec::new_const(),
150        }
151    }
152
153    fn find_unit(&mut self, name: &str) -> Option<&mut TransitionState<f32>> {
154        for (key, value) in &mut self.units {
155            if key == name {
156                return Some(value);
157            }
158        }
159
160        None
161    }
162
163    fn find_color(&mut self, name: &str) -> Option<&mut TransitionState<Color>> {
164        for (key, value) in &mut self.colors {
165            if key == name {
166                return Some(value);
167            }
168        }
169
170        None
171    }
172
173    pub fn transition_unit(
174        &mut self,
175        name: &str,
176        value: f32,
177        transition: Option<StyleTransition>,
178    ) -> f32 {
179        if let Some(state) = self.find_unit(name) {
180            return state.get(value, transition);
181        }
182
183        let mut state = TransitionState::default();
184        let result = state.get(value, transition);
185
186        self.units.push((name.into(), state));
187
188        result
189    }
190
191    pub fn transition_color(
192        &mut self,
193        name: &str,
194        value: Color,
195        transition: Option<StyleTransition>,
196    ) -> Color {
197        if let Some(state) = self.find_color(name) {
198            return state.get(value, transition);
199        }
200
201        let mut state = TransitionState::default();
202        let result = state.get(value, transition);
203
204        self.colors.push((name.into(), state));
205
206        result
207    }
208
209    pub(crate) fn transition_any<T: Any>(
210        &mut self,
211        name: &str,
212        value: &mut T,
213        transition: Option<StyleTransition>,
214    ) {
215        if let Some(value) = <dyn Any>::downcast_mut::<f32>(value) {
216            *value = self.transition_unit(name, *value, transition);
217        }
218
219        if let Some(value) = <dyn Any>::downcast_mut::<Color>(value) {
220            *value = self.transition_color(name, *value, transition);
221        }
222    }
223
224    pub fn update(&mut self, delta: f32) -> bool {
225        let mut redraw = false;
226
227        for (_, state) in &mut self.units {
228            redraw |= state.update(delta);
229        }
230
231        for (_, state) in &mut self.colors {
232            redraw |= state.update(delta);
233        }
234
235        redraw
236    }
237}