1use std::time::Duration;
2#[cfg(feature = "gpui")]
3use std::time::Instant;
4
5use crate::{MotionValue, Transition};
6
7const CHANNELS: usize = 6;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct Target {
17 layout: u8,
18 values: [f64; CHANNELS],
19}
20
21impl Default for Target {
22 fn default() -> Self {
23 Self {
24 layout: 0,
25 values: [0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
26 }
27 }
28}
29
30impl Target {
31 pub fn x(mut self, value: f32) -> Self {
33 assert!(value.is_finite(), "x must be finite");
34 self.values[0] = value.into();
35 self.layout |= 1;
36
37 self
38 }
39
40 pub fn y(mut self, value: f32) -> Self {
42 assert!(value.is_finite(), "y must be finite");
43 self.values[1] = value.into();
44 self.layout |= 2;
45
46 self
47 }
48
49 pub fn opacity(mut self, value: f32) -> Self {
51 assert!((0.0..=1.0).contains(&value), "opacity must be in 0..=1");
52 self.values[2] = value.into();
53 self.layout |= 4;
54
55 self
56 }
57
58 pub fn width(self, value: f32) -> Self {
62 self.with_layout(3, value)
63 }
64
65 pub fn height(self, value: f32) -> Self {
67 self.with_layout(4, value)
68 }
69
70 pub fn border_radius(self, value: f32) -> Self {
73 self.with_layout(5, value)
74 }
75
76 pub fn width_px(self) -> Option<f32> {
78 self.layout_value(3)
79 }
80
81 pub fn height_px(self) -> Option<f32> {
83 self.layout_value(4)
84 }
85
86 pub fn border_radius_px(self) -> Option<f32> {
88 self.layout_value(5)
89 }
90
91 fn with_layout(mut self, ix: usize, value: f32) -> Self {
92 assert!(
93 value.is_finite() && value >= 0.0,
94 "layout targets must be finite and non-negative"
95 );
96 self.values[ix] = value.into();
97 self.layout |= 1 << ix;
98
99 self
100 }
101
102 fn layout_value(self, ix: usize) -> Option<f32> {
103 (self.layout & (1 << ix) != 0)
104 .then(|| self.values[ix].clamp(0.0, f64::from(f32::MAX)) as f32)
105 }
106
107 fn with_layout_fallback(mut self, fallback: Self) -> Self {
108 for ix in 3..CHANNELS {
109 let bit = 1 << ix;
110
111 if self.layout & bit == 0 && fallback.layout & bit != 0 {
112 self.values[ix] = fallback.values[ix];
113 self.layout |= bit;
114 }
115 }
116
117 self
118 }
119
120 pub fn overlay(mut self, patch: Self) -> Self {
123 for ix in 0..CHANNELS {
124 if patch.layout & (1 << ix) != 0 {
125 self.values[ix] = patch.values[ix];
126 }
127 }
128
129 self.layout |= patch.layout;
130
131 self
132 }
133
134 pub(crate) fn values(self) -> [f64; CHANNELS] {
135 self.values
136 }
137
138 pub(crate) fn from_values(values: [f64; CHANNELS], layout: u8) -> Self {
139 Self { values, layout }
140 }
141
142 pub(crate) fn mask(self) -> u8 {
143 self.layout
144 }
145
146 pub(crate) fn inherit(self, base: Self) -> Self {
147 self.with_layout_fallback(base)
148 }
149
150 pub fn x_px(self) -> f32 {
152 self.values[0].clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
153 }
154
155 pub fn y_px(self) -> f32 {
157 self.values[1].clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
158 }
159
160 pub fn alpha(self) -> f32 {
162 self.values[2].clamp(0.0, 1.0) as f32
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq)]
168pub struct Motion {
169 target: Target,
170 initial: Option<Target>,
171 exit: Target,
172 transition: Transition,
173 present: bool,
174 delay: Duration,
175 overrides: [Option<(Transition, Duration)>; CHANNELS],
176}
177
178impl Motion {
179 pub fn new(target: Target) -> Self {
182 Self {
183 target,
184 initial: None,
185 exit: target.opacity(0.0),
186 transition: Transition::default(),
187 present: true,
188 delay: Duration::ZERO,
189 overrides: [None; CHANNELS],
190 }
191 }
192
193 pub fn target(self) -> Target {
195 self.target
196 }
197
198 pub fn with_target(mut self, target: Target) -> Self {
200 self.target = target;
201
202 self
203 }
204
205 pub fn initial(mut self, value: Target) -> Self {
207 self.initial = Some(value);
208
209 self
210 }
211
212 pub fn exit(mut self, value: Target) -> Self {
214 self.exit = value;
215
216 self
217 }
218
219 pub fn transition(mut self, value: Transition) -> Self {
221 self.transition = value;
222
223 self
224 }
225
226 pub fn delay(mut self, delay: Duration) -> Self {
228 self.delay = delay;
229
230 self
231 }
232
233 pub fn transition_for(
235 mut self,
236 property: Property,
237 transition: Transition,
238 delay: Duration,
239 ) -> Self {
240 self.overrides[property as usize] = Some((transition, delay));
241
242 self
243 }
244
245 pub fn is_present(self) -> bool {
247 self.present
248 }
249
250 pub fn present(mut self, value: bool) -> Self {
252 self.present = value;
253
254 self
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
260#[non_exhaustive]
261pub enum Property {
262 X,
264 Y,
266 Opacity,
268 Width,
270 Height,
272 BorderRadius,
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq)]
278#[non_exhaustive]
279pub enum Presence {
280 Entering,
282 Present,
284 Exiting,
286 Absent,
288}
289
290#[derive(Clone, Copy, Debug)]
292pub struct Frame {
293 target: Target,
294 velocity: [f64; CHANNELS],
295 presence: Presence,
296 animating: bool,
297 completed: bool,
298}
299
300impl Frame {
301 pub fn at(target: Target) -> Self {
303 Self {
304 target,
305 velocity: [0.0; CHANNELS],
306 presence: Presence::Present,
307 animating: false,
308 completed: false,
309 }
310 }
311
312 pub(crate) fn animated(
313 target: Target,
314 velocity: [f64; CHANNELS],
315 active: bool,
316 completed: bool,
317 ) -> Self {
318 Self {
319 target,
320 velocity,
321 presence: Presence::Present,
322 animating: active,
323 completed,
324 }
325 }
326
327 pub fn just_completed(self) -> bool {
329 self.completed
330 }
331
332 pub fn target(self) -> Target {
334 self.target
335 }
336
337 pub fn velocity(self) -> [f64; 3] {
339 [self.velocity[0], self.velocity[1], self.velocity[2]]
340 }
341
342 pub fn layout_velocity(self) -> [Option<f64>; 3] {
345 std::array::from_fn(|ix| {
346 (self.target.layout & (1 << (ix + 3)) != 0).then_some(self.velocity[ix + 3])
347 })
348 }
349
350 pub fn presence(self) -> Presence {
352 self.presence
353 }
354
355 pub fn is_animating(self) -> bool {
357 self.animating
358 }
359
360 pub fn is_mounted(self) -> bool {
362 self.presence != Presence::Absent
363 }
364
365 pub fn is_present(self) -> bool {
367 matches!(self.presence, Presence::Entering | Presence::Present)
368 }
369}
370
371#[derive(Debug)]
377pub struct MotionState {
378 layout: u8,
379 channels: [MotionValue; CHANNELS],
380 presence: Presence,
381 initialized: bool,
382 #[cfg(feature = "gpui")]
383 pub(crate) epoch: Instant,
384}
385
386impl Default for MotionState {
387 fn default() -> Self {
388 Self {
389 layout: 0,
390 channels: std::array::from_fn(|ix| MotionValue::new(if ix == 2 { 1.0 } else { 0.0 })),
391 presence: Presence::Absent,
392 initialized: false,
393 #[cfg(feature = "gpui")]
394 epoch: Instant::now(),
395 }
396 }
397}
398
399impl MotionState {
400 pub fn sample(&mut self, motion: Motion, now: Duration, reduced_motion: bool) -> Frame {
405 if !self.initialized {
406 self.initialized = true;
407 let target = motion
408 .target
409 .with_layout_fallback(motion.initial.unwrap_or_default());
410
411 let initial = if motion.present {
412 motion
413 .initial
414 .unwrap_or(target)
415 .with_layout_fallback(target)
416 } else {
417 motion.exit.with_layout_fallback(target)
418 };
419
420 self.layout = initial.layout;
421
422 for ix in 0..CHANNELS {
423 self.channels[ix].jump(initial.values[ix], now);
424 }
425
426 self.presence = if motion.present {
427 Presence::Entering
428 } else {
429 Presence::Absent
430 };
431 }
432
433 let previous_presence = self.presence;
434 let was_present = matches!(self.presence, Presence::Present | Presence::Entering);
435
436 let requested = if motion.present {
437 motion.target
438 } else {
439 motion.exit
440 }
441 .with_layout_fallback(Target {
442 values: std::array::from_fn(|ix| self.channels[ix].target()),
443 layout: self.layout,
444 });
445
446 let added = requested.layout & !self.layout;
447
448 for ix in 3..CHANNELS {
449 if added & (1 << ix) != 0 {
450 self.channels[ix].jump(requested.values[ix], now);
451 }
452 }
453
454 self.layout = requested.layout;
455
456 if was_present != motion.present {
457 self.presence = if motion.present {
458 Presence::Entering
459 } else {
460 Presence::Exiting
461 };
462 }
463
464 let mut active = false;
465 let mut completed = false;
466 let mut position = [0.0; CHANNELS];
467 let mut velocity = [0.0; CHANNELS];
468
469 for ix in 0..CHANNELS {
470 let (transition, delay) =
471 motion.overrides[ix].unwrap_or((motion.transition, motion.delay));
472
473 self.channels[ix].animate_delayed(requested.values[ix], transition, delay, now);
474 let value = self.channels[ix].sample(now, reduced_motion);
475
476 position[ix] = value.value();
477 velocity[ix] = value.velocity();
478 active |= value.is_animating();
479 completed |= value.just_completed();
480 }
481
482 if !active {
483 self.presence = if motion.present {
484 Presence::Present
485 } else {
486 Presence::Absent
487 };
488 }
489
490 completed &= !active;
491 completed |= self.presence == Presence::Absent && previous_presence != Presence::Absent;
492
493 Frame {
494 target: Target {
495 values: position,
496 layout: self.layout | 7,
497 },
498 velocity,
499 presence: self.presence,
500 animating: active,
501 completed,
502 }
503 }
504}