animation.rs
1use crate::{ContentGroup, prelude::*};
2use gpui::{AnimationElement, AnimationExt, Styled};
3use std::time::Duration;
4
5use gpui::ease_out_quint;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum AnimationDuration {
9 Instant = 50,
10 Fast = 150,
11 Slow = 300,
12}
13
14impl AnimationDuration {
15 pub fn duration(&self) -> Duration {
16 Duration::from_millis(*self as u64)
17 }
18}
19
20impl Into<std::time::Duration> for AnimationDuration {
21 fn into(self) -> Duration {
22 self.duration()
23 }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum AnimationDirection {
28 FromBottom,
29 FromLeft,
30 FromRight,
31 FromTop,
32}
33
34pub trait DefaultAnimations: Styled + Sized {
35 fn animate_in(
36 self,
37 animation_type: AnimationDirection,
38 fade_in: bool,
39 ) -> AnimationElement<Self> {
40 let animation_name = match animation_type {
41 AnimationDirection::FromBottom => "animate_from_bottom",
42 AnimationDirection::FromLeft => "animate_from_left",
43 AnimationDirection::FromRight => "animate_from_right",
44 AnimationDirection::FromTop => "animate_from_top",
45 };
46
47 self.with_animation(
48 animation_name,
49 gpui::Animation::new(AnimationDuration::Fast.into()).with_easing(ease_out_quint()),
50 move |mut this, delta| {
51 let start_opacity = 0.4;
52 let start_pos = 0.0;
53 let end_pos = 40.0;
54
55 if fade_in {
56 this = this.opacity(start_opacity + delta * (1.0 - start_opacity));
57 }
58
59 match animation_type {
60 AnimationDirection::FromBottom => {
61 this.bottom(px(start_pos + delta * (end_pos - start_pos)))
62 }
63 AnimationDirection::FromLeft => {
64 this.left(px(start_pos + delta * (end_pos - start_pos)))
65 }
66 AnimationDirection::FromRight => {
67 this.right(px(start_pos + delta * (end_pos - start_pos)))
68 }
69 AnimationDirection::FromTop => {
70 this.top(px(start_pos + delta * (end_pos - start_pos)))
71 }
72 }
73 },
74 )
75 }
76
77 fn animate_in_from_bottom(self, fade: bool) -> AnimationElement<Self> {
78 self.animate_in(AnimationDirection::FromBottom, fade)
79 }
80
81 fn animate_in_from_left(self, fade: bool) -> AnimationElement<Self> {
82 self.animate_in(AnimationDirection::FromLeft, fade)
83 }
84
85 fn animate_in_from_right(self, fade: bool) -> AnimationElement<Self> {
86 self.animate_in(AnimationDirection::FromRight, fade)
87 }
88
89 fn animate_in_from_top(self, fade: bool) -> AnimationElement<Self> {
90 self.animate_in(AnimationDirection::FromTop, fade)
91 }
92}
93
94impl<E: Styled> DefaultAnimations for E {}
95
96// Don't use this directly, it only exists to show animation previews
97#[derive(RegisterComponent)]
98struct Animation {}
99
100impl Component for Animation {
101 fn scope() -> ComponentScope {
102 ComponentScope::Utilities
103 }
104
105 fn description() -> Option<&'static str> {
106 Some("Demonstrates various animation patterns and transitions available in the UI system.")
107 }
108
109 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
110 let container_size = 128.0;
111 let element_size = 32.0;
112 let offset = container_size / 2.0 - element_size / 2.0;
113 Some(
114 v_flex()
115 .gap_6()
116 .children(vec![
117 example_group_with_title(
118 "Animate In",
119 vec![
120 single_example(
121 "From Bottom",
122 ContentGroup::new()
123 .relative()
124 .items_center()
125 .justify_center()
126 .size(px(container_size))
127 .child(
128 div()
129 .id("animate-in-from-bottom")
130 .absolute()
131 .size(px(element_size))
132 .left(px(offset))
133 .rounded_md()
134 .bg(gpui::red())
135 .animate_in(AnimationDirection::FromBottom, false),
136 )
137 .into_any_element(),
138 ),
139 single_example(
140 "From Top",
141 ContentGroup::new()
142 .relative()
143 .items_center()
144 .justify_center()
145 .size(px(container_size))
146 .child(
147 div()
148 .id("animate-in-from-top")
149 .absolute()
150 .size(px(element_size))
151 .left(px(offset))
152 .rounded_md()
153 .bg(gpui::blue())
154 .animate_in(AnimationDirection::FromTop, false),
155 )
156 .into_any_element(),
157 ),
158 single_example(
159 "From Left",
160 ContentGroup::new()
161 .relative()
162 .items_center()
163 .justify_center()
164 .size(px(container_size))
165 .child(
166 div()
167 .id("animate-in-from-left")
168 .absolute()
169 .size(px(element_size))
170 .top(px(offset))
171 .rounded_md()
172 .bg(gpui::green())
173 .animate_in(AnimationDirection::FromLeft, false),
174 )
175 .into_any_element(),
176 ),
177 single_example(
178 "From Right",
179 ContentGroup::new()
180 .relative()
181 .items_center()
182 .justify_center()
183 .size(px(container_size))
184 .child(
185 div()
186 .id("animate-in-from-right")
187 .absolute()
188 .size(px(element_size))
189 .top(px(offset))
190 .rounded_md()
191 .bg(gpui::yellow())
192 .animate_in(AnimationDirection::FromRight, false),
193 )
194 .into_any_element(),
195 ),
196 ],
197 )
198 .grow(),
199 example_group_with_title(
200 "Fade and Animate In",
201 vec![
202 single_example(
203 "From Bottom",
204 ContentGroup::new()
205 .relative()
206 .items_center()
207 .justify_center()
208 .size(px(container_size))
209 .child(
210 div()
211 .id("fade-animate-in-from-bottom")
212 .absolute()
213 .size(px(element_size))
214 .left(px(offset))
215 .rounded_md()
216 .bg(gpui::red())
217 .animate_in(AnimationDirection::FromBottom, true),
218 )
219 .into_any_element(),
220 ),
221 single_example(
222 "From Top",
223 ContentGroup::new()
224 .relative()
225 .items_center()
226 .justify_center()
227 .size(px(container_size))
228 .child(
229 div()
230 .id("fade-animate-in-from-top")
231 .absolute()
232 .size(px(element_size))
233 .left(px(offset))
234 .rounded_md()
235 .bg(gpui::blue())
236 .animate_in(AnimationDirection::FromTop, true),
237 )
238 .into_any_element(),
239 ),
240 single_example(
241 "From Left",
242 ContentGroup::new()
243 .relative()
244 .items_center()
245 .justify_center()
246 .size(px(container_size))
247 .child(
248 div()
249 .id("fade-animate-in-from-left")
250 .absolute()
251 .size(px(element_size))
252 .top(px(offset))
253 .rounded_md()
254 .bg(gpui::green())
255 .animate_in(AnimationDirection::FromLeft, true),
256 )
257 .into_any_element(),
258 ),
259 single_example(
260 "From Right",
261 ContentGroup::new()
262 .relative()
263 .items_center()
264 .justify_center()
265 .size(px(container_size))
266 .child(
267 div()
268 .id("fade-animate-in-from-right")
269 .absolute()
270 .size(px(element_size))
271 .top(px(offset))
272 .rounded_md()
273 .bg(gpui::yellow())
274 .animate_in(AnimationDirection::FromRight, true),
275 )
276 .into_any_element(),
277 ),
278 ],
279 )
280 .grow(),
281 ])
282 .into_any_element(),
283 )
284 }
285}