1use crate::CubicBezier;
5use crate::TIME_EPSILON;
6use crate::ease::Ease;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Dir {
11 Left,
13 Right,
15 Up,
17 Down,
19}
20
21impl Dir {
22 pub(crate) fn name(self) -> &'static str {
23 match self {
24 Self::Left => "left",
25 Self::Right => "right",
26 Self::Up => "up",
27 Self::Down => "down",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
37pub struct LayerPose {
38 pub opacity: f64,
40 pub dx: f64,
42 pub dy: f64,
44 pub scale: f64,
46 pub wipe: Option<(Dir, f64)>,
48}
49
50impl Default for LayerPose {
51 fn default() -> Self {
52 Self {
53 opacity: 1.0,
54 dx: 0.0,
55 dy: 0.0,
56 scale: 1.0,
57 wipe: None,
58 }
59 }
60}
61
62impl LayerPose {
63 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn opacity(mut self, opacity: f64) -> Self {
70 self.opacity = opacity;
71 self
72 }
73
74 pub fn offset(mut self, dx: f64, dy: f64) -> Self {
76 self.dx = dx;
77 self.dy = dy;
78 self
79 }
80
81 pub fn scale(mut self, scale: f64) -> Self {
83 self.scale = scale;
84 self
85 }
86
87 pub fn wipe(mut self, dir: Dir, amount: f64) -> Self {
89 self.wipe = Some((dir, amount));
90 self
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
99pub struct Blend {
100 pub from: LayerPose,
102 pub to: LayerPose,
104 pub incoming_on_top: bool,
106 pub mix: bool,
108}
109
110impl Default for Blend {
111 fn default() -> Self {
112 Self {
113 from: LayerPose::new(),
114 to: LayerPose::new(),
115 incoming_on_top: true,
116 mix: false,
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq)]
126#[must_use = "a shader transition does nothing until it is passed to `Transition::shader`"]
127pub struct ShaderTransition {
128 preset: ShaderPreset,
129 wave: f64,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133enum ShaderPreset {
134 WaterWipe,
135}
136
137impl ShaderTransition {
138 pub fn water_wipe() -> Self {
141 Self {
142 preset: ShaderPreset::WaterWipe,
143 wave: 0.04,
144 }
145 }
146
147 pub fn wave(mut self, height: f64) -> Self {
150 self.wave = if height.is_finite() {
151 height.max(0.0)
152 } else {
153 0.0
154 };
155 self
156 }
157
158 pub fn wave_height(self) -> f64 {
160 self.wave
161 }
162
163 pub fn name(self) -> &'static str {
165 match self.preset {
166 ShaderPreset::WaterWipe => "water_wipe",
167 }
168 }
169}
170
171#[derive(Clone, Copy)]
174pub enum Kind {
175 Fade,
177 Slide(Dir),
179 Wipe(Dir),
181 ZoomIn,
183 ZoomOut,
185 Custom(fn(f64) -> Blend),
187 Shader(ShaderTransition),
189}
190
191impl Kind {
192 pub(crate) fn name(self) -> &'static str {
193 match self {
194 Self::Fade => "fade",
195 Self::Slide(_) => "slide",
196 Self::Wipe(_) => "wipe",
197 Self::ZoomIn => "zoom_in",
198 Self::ZoomOut => "zoom_out",
199 Self::Custom(_) => "custom",
200 Self::Shader(s) => s.name(),
201 }
202 }
203
204 pub(crate) fn dir(self) -> Option<Dir> {
205 match self {
206 Self::Slide(d) | Self::Wipe(d) => Some(d),
207 _ => None,
208 }
209 }
210}
211
212#[derive(Clone, Copy)]
215pub struct Recorded {
216 pub(crate) kind: Kind,
217 pub(crate) seconds: f64,
218 pub(crate) ease: CubicBezier,
219 pub(crate) overlap: bool,
220 pub(crate) start: f64,
222}
223
224impl Recorded {
225 pub fn new(kind: Kind, seconds: f64, ease: CubicBezier, overlap: bool, start: f64) -> Self {
227 Self {
228 kind,
229 seconds,
230 ease,
231 overlap,
232 start,
233 }
234 }
235
236 pub fn kind_name(self) -> &'static str {
239 self.kind.name()
240 }
241
242 pub fn shader(self) -> Option<ShaderTransition> {
244 match self.kind {
245 Kind::Shader(s) => Some(s),
246 _ => None,
247 }
248 }
249
250 pub fn dir_name(self) -> Option<&'static str> {
252 self.kind.dir().map(Dir::name)
253 }
254
255 pub fn overlap(self) -> bool {
257 self.overlap
258 }
259
260 pub fn seconds(self) -> f64 {
262 self.seconds
263 }
264
265 pub fn start(self) -> f64 {
267 self.start
268 }
269
270 pub fn active(self, now: f64) -> bool {
272 self.seconds > 0.0
273 && now + TIME_EPSILON >= self.start
274 && now + TIME_EPSILON < self.start + self.seconds
275 }
276
277 pub fn progress(self, now: f64) -> f64 {
279 if self.seconds <= 0.0 {
280 return 1.0;
281 }
282 let t = ((now - self.start) / self.seconds).clamp(0.0, 1.0);
283 self.ease.ease(t).clamp(0.0, 1.0)
284 }
285
286 pub fn blend_at(self, now: f64) -> Blend {
288 blend(self.kind, self.progress(now))
289 }
290}
291
292pub(crate) fn blend(kind: Kind, t: f64) -> Blend {
294 let t = t.clamp(0.0, 1.0);
295 match kind {
296 Kind::Fade => Blend {
297 mix: true,
298 ..Blend::default()
299 },
300 Kind::Slide(dir) => {
301 let (fx, fy, tx, ty) = shift(dir, t);
302 Blend {
303 from: LayerPose::new().offset(fx, fy),
304 to: LayerPose::new().offset(tx, ty),
305 ..Blend::default()
306 }
307 }
308 Kind::Wipe(dir) => Blend {
309 to: LayerPose::new().wipe(dir, t),
310 ..Blend::default()
311 },
312 Kind::ZoomIn => Blend {
313 to: LayerPose::new().opacity(t).scale(0.6 + 0.4 * t),
314 ..Blend::default()
315 },
316 Kind::ZoomOut => Blend {
317 from: LayerPose::new().opacity(1.0 - t).scale(1.0 + 0.4 * t),
318 incoming_on_top: false,
319 ..Blend::default()
320 },
321 Kind::Custom(f) => f(t),
322 Kind::Shader(_) => Blend::default(),
324 }
325}
326
327fn shift(dir: Dir, t: f64) -> (f64, f64, f64, f64) {
328 match dir {
329 Dir::Left => (-t, 0.0, 1.0 - t, 0.0),
330 Dir::Right => (t, 0.0, t - 1.0, 0.0),
331 Dir::Up => (0.0, -t, 0.0, 1.0 - t),
332 Dir::Down => (0.0, t, 0.0, t - 1.0),
333 }
334}