ori_core/style/
transition.rs1use 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 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}