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}