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(IntoComponent)]
98struct Animation {}
99
100// View this component preview using `workspace: open component-preview`
101impl ComponentPreview for Animation {
102 fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
103 let container_size = 128.0;
104 let element_size = 32.0;
105 let left_offset = element_size - container_size / 2.0;
106 v_flex()
107 .gap_6()
108 .children(vec![
109 example_group_with_title(
110 "Animate In",
111 vec![
112 single_example(
113 "From Bottom",
114 ContentGroup::new()
115 .relative()
116 .items_center()
117 .justify_center()
118 .size(px(container_size))
119 .child(
120 div()
121 .id("animate-in-from-bottom")
122 .absolute()
123 .size(px(element_size))
124 .left(px(left_offset))
125 .rounded_md()
126 .bg(gpui::red())
127 .animate_in(AnimationDirection::FromBottom, false),
128 )
129 .into_any_element(),
130 ),
131 single_example(
132 "From Top",
133 ContentGroup::new()
134 .relative()
135 .items_center()
136 .justify_center()
137 .size(px(container_size))
138 .child(
139 div()
140 .id("animate-in-from-top")
141 .absolute()
142 .size(px(element_size))
143 .left(px(left_offset))
144 .rounded_md()
145 .bg(gpui::blue())
146 .animate_in(AnimationDirection::FromTop, false),
147 )
148 .into_any_element(),
149 ),
150 single_example(
151 "From Left",
152 ContentGroup::new()
153 .relative()
154 .items_center()
155 .justify_center()
156 .size(px(container_size))
157 .child(
158 div()
159 .id("animate-in-from-left")
160 .absolute()
161 .size(px(element_size))
162 .left(px(left_offset))
163 .rounded_md()
164 .bg(gpui::green())
165 .animate_in(AnimationDirection::FromLeft, false),
166 )
167 .into_any_element(),
168 ),
169 single_example(
170 "From Right",
171 ContentGroup::new()
172 .relative()
173 .items_center()
174 .justify_center()
175 .size(px(container_size))
176 .child(
177 div()
178 .id("animate-in-from-right")
179 .absolute()
180 .size(px(element_size))
181 .left(px(left_offset))
182 .rounded_md()
183 .bg(gpui::yellow())
184 .animate_in(AnimationDirection::FromRight, false),
185 )
186 .into_any_element(),
187 ),
188 ],
189 )
190 .grow(),
191 example_group_with_title(
192 "Fade and Animate In",
193 vec![
194 single_example(
195 "From Bottom",
196 ContentGroup::new()
197 .relative()
198 .items_center()
199 .justify_center()
200 .size(px(container_size))
201 .child(
202 div()
203 .id("fade-animate-in-from-bottom")
204 .absolute()
205 .size(px(element_size))
206 .left(px(left_offset))
207 .rounded_md()
208 .bg(gpui::red())
209 .animate_in(AnimationDirection::FromBottom, true),
210 )
211 .into_any_element(),
212 ),
213 single_example(
214 "From Top",
215 ContentGroup::new()
216 .relative()
217 .items_center()
218 .justify_center()
219 .size(px(container_size))
220 .child(
221 div()
222 .id("fade-animate-in-from-top")
223 .absolute()
224 .size(px(element_size))
225 .left(px(left_offset))
226 .rounded_md()
227 .bg(gpui::blue())
228 .animate_in(AnimationDirection::FromTop, true),
229 )
230 .into_any_element(),
231 ),
232 single_example(
233 "From Left",
234 ContentGroup::new()
235 .relative()
236 .items_center()
237 .justify_center()
238 .size(px(container_size))
239 .child(
240 div()
241 .id("fade-animate-in-from-left")
242 .absolute()
243 .size(px(element_size))
244 .left(px(left_offset))
245 .rounded_md()
246 .bg(gpui::green())
247 .animate_in(AnimationDirection::FromLeft, true),
248 )
249 .into_any_element(),
250 ),
251 single_example(
252 "From Right",
253 ContentGroup::new()
254 .relative()
255 .items_center()
256 .justify_center()
257 .size(px(container_size))
258 .child(
259 div()
260 .id("fade-animate-in-from-right")
261 .absolute()
262 .size(px(element_size))
263 .left(px(left_offset))
264 .rounded_md()
265 .bg(gpui::yellow())
266 .animate_in(AnimationDirection::FromRight, true),
267 )
268 .into_any_element(),
269 ),
270 ],
271 )
272 .grow(),
273 ])
274 .into_any_element()
275 }
276}