Skip to main content

gpui_cn/theme/
motion.rs

1use std::time::Duration;
2
3use gpui_kit::base::{Easing, Transition};
4
5/// Timing gpui-cn components animate with.
6///
7/// Components sample motion through `gpui-base` (`transition`, `spring`,
8/// `Presence`, `Sequence`, ...), which snaps to the target when reduce
9/// motion is on. These tokens only say how long and with what curve.
10#[derive(Clone)]
11#[non_exhaustive]
12pub struct MotionTokens {
13    /// Hover, pressed, and focus-ring changes.
14    pub fast: Duration,
15    /// Tooltip and popover enter and exit.
16    pub normal: Duration,
17    /// Sheet, dialog, and measured reveal.
18    pub slow: Duration,
19    /// The curve for something appearing or settling.
20    pub enter: Easing,
21    /// The curve for something leaving.
22    pub exit: Easing,
23    /// The curve for something that moves between two resting places in
24    /// view: a sidebar sliding, a disclosure's rows folding. An
25    /// ease-in-out, since both ends are on screen and neither should
26    /// snap; the enter curve, which leaps and then settles, is for
27    /// something arriving.
28    pub fold: Easing,
29    /// Delay between items in a staggered list.
30    pub stagger: Duration,
31    /// A dock pane moving to its new slot when the layout changes: 460ms,
32    /// `vd.layoutMs` in the Trellis bundle.
33    pub layout: Duration,
34    /// A lifted dock pane shrinking into its floating card: 220ms,
35    /// `appearMs` in the Trellis bundle.
36    pub appear: Duration,
37    /// How long the pointer rests on a new drop target before the dock
38    /// previews it there: 150ms, `_v` in the Trellis bundle. A hysteresis,
39    /// not motion, so [`MotionTokens::none`] keeps it.
40    pub retarget: Duration,
41    /// The curve of dock layout motion: ease-out quint, `1 - (1 - t)^5`,
42    /// as Trellis moves its panels.
43    pub layout_curve: Easing,
44}
45
46impl Default for MotionTokens {
47    fn default() -> Self {
48        Self {
49            fast: Duration::from_millis(120),
50            normal: Duration::from_millis(180),
51            slow: Duration::from_millis(280),
52            // The same curves gpui-component uses, so motion feels the same
53            // across both layers in one application.
54            enter: Easing::cubic_bezier(0.16, 1.0, 0.3, 1.0).expect("valid enter curve"),
55            exit: Easing::cubic_bezier(0.4, 0.0, 1.0, 1.0).expect("valid exit curve"),
56            // The standard ease-in-out, the pace of a macOS outline's
57            // disclosure.
58            fold: Easing::cubic_bezier(0.4, 0.0, 0.2, 1.0).expect("valid fold curve"),
59            stagger: Duration::from_millis(40),
60            layout: Duration::from_millis(460),
61            appear: Duration::from_millis(220),
62            retarget: Duration::from_millis(150),
63            layout_curve: Easing::Custom(std::rc::Rc::new(ease_out_quint)),
64        }
65    }
66}
67
68/// Ease-out quint, `1 - (1 - t)^5`: fast off the mark and a long settle.
69fn ease_out_quint(t: f32) -> f32 {
70    1. - (1. - t).powi(5)
71}
72
73impl MotionTokens {
74    /// A `slow` transition with the fold curve, for a region that folds.
75    pub fn fold_transition(&self) -> Transition {
76        Transition::new(self.slow).easing(self.fold.clone())
77    }
78
79    /// A `fast` transition with the fold curve, for a thumb that slides
80    /// between the two ends of its track.
81    pub fn slide_transition(&self) -> Transition {
82        Transition::new(self.fast).easing(self.fold.clone())
83    }
84
85    /// A `normal` transition with the enter curve, for content that
86    /// glides to a new resting place: a tab strip paging or taking a wheel
87    /// step. The enter curve leaves fast and settles, the shape of a
88    /// scroll that decelerates, so a run of steps reads as one motion.
89    pub fn glide_transition(&self) -> Transition {
90        Transition::new(self.normal).easing(self.enter.clone())
91    }
92
93    /// A `fast` transition with the enter curve.
94    pub fn fast_transition(&self) -> Transition {
95        Transition::new(self.fast).easing(self.enter.clone())
96    }
97
98    /// A `normal` transition with the enter curve.
99    pub fn enter_transition(&self) -> Transition {
100        Transition::new(self.normal).easing(self.enter.clone())
101    }
102
103    /// A `normal` transition with the exit curve.
104    pub fn exit_transition(&self) -> Transition {
105        Transition::new(self.normal).easing(self.exit.clone())
106    }
107
108    /// A `slow` transition with the enter curve.
109    pub fn slow_transition(&self) -> Transition {
110        Transition::new(self.slow).easing(self.enter.clone())
111    }
112
113    /// Motion tokens with every duration at zero, for a product that wants
114    /// no animation regardless of the system preference.
115    pub fn none() -> Self {
116        Self {
117            fast: Duration::ZERO,
118            normal: Duration::ZERO,
119            slow: Duration::ZERO,
120            stagger: Duration::ZERO,
121            layout: Duration::ZERO,
122            appear: Duration::ZERO,
123            ..Self::default()
124        }
125    }
126}