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