1use std::time::Duration;
2
3use gpui::{
4 div, prelude::*, px, AnimationExt, AnyElement, App, ElementId, IntoElement, ParentElement,
5 RenderOnce, SharedString, StyleRefinement, Styled, Window,
6};
7
8use super::slot::StyledSlot;
9use super::style::{apply_style, MotionStyle, ResolvedStyle};
10use super::transition::{Ease, Transition};
11use super::values::MotionValue;
12use super::variants::Variants;
13
14#[derive(IntoElement)]
16pub struct Motion {
17 id: ElementId,
18 style: StyleRefinement,
19 children: Vec<AnyElement>,
20 initial: MotionStyle,
21 animate: MotionStyle,
22 exit: Option<MotionStyle>,
23 transition: Transition,
24 while_hover: Option<MotionStyle>,
25 while_tap: Option<MotionStyle>,
26 variants: Option<Variants>,
27 layout: bool,
28 opacity_value: Option<MotionValue>,
30 layout_from: Option<(gpui::Pixels, gpui::Pixels)>,
32 layout_to: Option<(gpui::Pixels, gpui::Pixels)>,
33 id_label: SharedString,
34}
35
36impl Default for Motion {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl Motion {
43 pub fn new() -> Self {
44 Self {
45 id: ElementId::Name("motion".into()),
46 id_label: "motion".into(),
47 style: StyleRefinement::default(),
48 children: Vec::new(),
49 initial: MotionStyle::new(),
50 animate: MotionStyle::new(),
51 exit: None,
52 transition: Transition::default(),
53 while_hover: None,
54 while_tap: None,
55 variants: None,
56 layout: false,
57 opacity_value: None,
58 layout_from: None,
59 layout_to: None,
60 }
61 }
62
63 pub fn id(mut self, id: impl Into<SharedString>) -> Self {
64 let label: SharedString = id.into();
65 self.id_label = label.clone();
66 self.id = ElementId::Name(label);
67 self
68 }
69
70 pub fn initial(mut self, style: MotionStyle) -> Self {
71 self.initial = style;
72 self
73 }
74
75 pub fn animate(mut self, style: MotionStyle) -> Self {
76 self.animate = style;
77 self
78 }
79
80 pub fn exit(mut self, style: MotionStyle) -> Self {
82 self.exit = Some(style);
83 self
84 }
85
86 pub fn exit_style(&self) -> Option<MotionStyle> {
87 self.exit
88 }
89
90 pub fn transition(mut self, transition: Transition) -> Self {
91 self.transition = transition;
92 self
93 }
94
95 pub fn while_hover(mut self, style: MotionStyle) -> Self {
97 self.while_hover = Some(style);
98 self
99 }
100
101 pub fn while_tap(mut self, style: MotionStyle) -> Self {
103 self.while_tap = Some(style);
104 self
105 }
106
107 pub fn variants(mut self, variants: Variants) -> Self {
108 self.variants = Some(variants);
109 self
110 }
111
112 pub fn initial_variant(mut self, name: impl AsRef<str>) -> Self {
113 if let Some(style) = self.variants.as_ref().and_then(|v| v.get(name.as_ref())) {
114 self.initial = style;
115 }
116 self
117 }
118
119 pub fn animate_variant(mut self, name: impl AsRef<str>) -> Self {
120 if let Some(style) = self.variants.as_ref().and_then(|v| v.get(name.as_ref())) {
121 self.animate = style;
122 }
123 self
124 }
125
126 pub fn exit_variant(mut self, name: impl AsRef<str>) -> Self {
127 if let Some(style) = self.variants.as_ref().and_then(|v| v.get(name.as_ref())) {
128 self.exit = Some(style);
129 }
130 self
131 }
132
133 pub fn layout(
135 mut self,
136 from: (impl Into<gpui::Pixels>, impl Into<gpui::Pixels>),
137 to: (impl Into<gpui::Pixels>, impl Into<gpui::Pixels>),
138 ) -> Self {
139 self.layout = true;
140 self.layout_from = Some((from.0.into(), from.1.into()));
141 self.layout_to = Some((to.0.into(), to.1.into()));
142 self
143 }
144
145 pub fn opacity_from(mut self, value: MotionValue) -> Self {
147 self.opacity_value = Some(value);
148 self
149 }
150
151 pub fn fade_up(self) -> Self {
152 self.initial(MotionStyle::new().opacity(0.).y(px(14.)))
153 .animate(MotionStyle::new().opacity(1.).y(px(0.)))
154 .exit(MotionStyle::new().opacity(0.).y(px(-10.)))
155 .transition(Transition::spring().stiffness(280.).damping(26.))
156 }
157
158 pub fn fade_in(self) -> Self {
159 self.initial(MotionStyle::new().opacity(0.))
160 .animate(MotionStyle::new().opacity(1.))
161 .exit(MotionStyle::new().opacity(0.))
162 .transition(Transition::tween(Duration::from_millis(280)).ease(Ease::EaseOut))
163 }
164
165 pub fn selection_in(self) -> Self {
167 self.initial(MotionStyle::new().opacity(0.))
168 .animate(MotionStyle::new().opacity(1.))
169 .transition(Transition::tween(Duration::from_millis(140)).ease(Ease::EaseOut))
170 }
171
172 pub fn surface_in(self) -> Self {
174 self.initial(MotionStyle::new().opacity(0.))
175 .animate(MotionStyle::new().opacity(1.))
176 .transition(Transition::tween(Duration::from_millis(180)).ease(Ease::EaseOut))
177 }
178
179 pub fn pop_in(self) -> Self {
180 self.initial(MotionStyle::new().opacity(0.).scale(0.92))
181 .animate(MotionStyle::new().opacity(1.).scale(1.))
182 .exit(MotionStyle::new().opacity(0.).scale(0.96))
183 .transition(
184 Transition::spring()
185 .bounce(0.2)
186 .duration(Duration::from_millis(420)),
187 )
188 }
189}
190
191impl Styled for Motion {
192 fn style(&mut self) -> &mut StyleRefinement {
193 &mut self.style
194 }
195}
196
197impl ParentElement for Motion {
198 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
199 self.children.extend(elements);
200 }
201}
202
203impl RenderOnce for Motion {
204 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
205 let mut from = self.initial.resolve(ResolvedStyle::default());
206 let mut to = self.animate.resolve(from);
207
208 if self.layout {
209 if let (Some((fw, fh)), Some((tw, th))) = (self.layout_from, self.layout_to) {
210 from.width = Some(fw);
211 from.height = Some(fh);
212 to.width = Some(tw);
213 to.height = Some(th);
214 }
215 }
216
217 let hover = self.while_hover.map(|h| h.resolve(to));
218 let tap = self.while_tap.map(|t| t.resolve(to));
219 let transition = self.transition;
220 let base_style = self.style;
221 let children = self.children;
222 let anim_id = self.id;
223 let interact_id = ElementId::Name(format!("{}__hit", self.id_label).into());
224 let opacity_value = self.opacity_value;
225
226 let mut el = div()
227 .id(interact_id)
228 .refine_style(&base_style)
229 .children(children);
230
231 if let Some(hover) = hover {
232 el = el.hover(move |s| {
233 let mut s = s.opacity(hover.opacity).ml(hover.x).mt(hover.y);
234 if let Some(w) = hover.width {
235 s = s.w(w);
236 }
237 if let Some(h) = hover.height {
238 s = s.h(h);
239 }
240 s
241 });
242 }
243
244 if let Some(tap) = tap {
245 el = el.active(move |s| {
246 let mut s = s.opacity(tap.opacity).ml(tap.x).mt(tap.y);
247 if let Some(w) = tap.width {
248 s = s.w(w);
249 }
250 if let Some(h) = tap.height {
251 s = s.h(h);
252 }
253 s
254 });
255 }
256
257 el.with_animation(anim_id, transition.into_animation(), move |this, delta| {
258 let mut frame = from.lerp(to, delta);
259 if let Some(value) = opacity_value.as_ref() {
260 frame.opacity = (frame.opacity * value.get().clamp(0.0, 1.0)).clamp(0.0, 1.0);
261 }
262 apply_style(this, frame)
263 })
264 }
265}
266
267#[allow(dead_code)]
269pub fn variant_pair(
270 hidden: MotionStyle,
271 visible: MotionStyle,
272) -> (Variants, SharedString, SharedString) {
273 let variants = Variants::new()
274 .set("hidden", hidden)
275 .set("visible", visible);
276 (variants, "hidden".into(), "visible".into())
277}