1use std::{collections::HashMap, rc::Rc, sync::Arc, time::Duration};
2
3use gpui::{prelude::FluentBuilder, *};
4
5use crate::{
6 interpolate::State,
7 transition::{IntoArcTransition, Transition, TransitionRegistry, general::Linear},
8};
9
10#[derive(Debug, Clone, Hash, PartialEq, Eq)]
11pub enum Event {
12 NONE,
13 HOVER,
14 CLICK,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
18pub enum AnimationPriority {
19 Lowest = 0,
20 Low = 25,
21 Medium = 50,
22 High = 75,
23 Realtime = 100,
24}
25
26#[derive(IntoElement)]
27pub struct AnimatedWrapper<E>
28where
29 E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static,
30{
31 style: StyleRefinement,
32 children: Vec<AnyElement>,
33 id: ElementId,
34 child: E,
35 transitions: HashMap<Event, (Duration, Arc<dyn Transition>)>,
36 on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
37 hover_modifier: Option<Rc<dyn Fn(&bool, State<StyleRefinement>) -> State<StyleRefinement>>>,
38 on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
39 click_modifier:
40 Option<Rc<dyn Fn(&ClickEvent, State<StyleRefinement>) -> State<StyleRefinement>>>,
41}
42
43impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
44 AnimatedWrapper<E>
45{
46 pub fn transition_on_hover<T, I>(
47 mut self,
48 duration: Duration,
49 transition: I,
50 modifier: impl Fn(&bool, State<StyleRefinement>) -> State<StyleRefinement> + 'static,
51 ) -> Self
52 where
53 T: Transition + 'static,
54 I: IntoArcTransition<T>,
55 {
56 self.transitions
57 .insert(Event::HOVER, (duration, transition.into_arc()));
58 self.hover_modifier = Some(Rc::new(modifier));
59
60 self
61 }
62
63 pub fn transition_on_click<T, I>(
64 mut self,
65 duration: Duration,
66 transition: I,
67 modifier: impl Fn(&ClickEvent, State<StyleRefinement>) -> State<StyleRefinement> + 'static,
68 ) -> Self
69 where
70 T: Transition + 'static,
71 I: IntoArcTransition<T>,
72 {
73 self.transitions
74 .insert(Event::CLICK, (duration, transition.into_arc()));
75 self.click_modifier = Some(Rc::new(modifier));
76
77 self
78 }
79
80 pub fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
81 self.on_hover = Some(Rc::new(listener));
82
83 self
84 }
85
86 pub fn on_click(
87 mut self,
88 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
89 ) -> Self {
90 self.on_click = Some(Rc::new(listener));
91
92 self
93 }
94
95 pub fn transition_when<T, I>(
99 self,
100 condition: bool,
101 duration: Duration,
102 transition: I,
103 then: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
104 ) -> Self
105 where
106 T: Transition + 'static,
107 I: IntoArcTransition<T>,
108 {
109 self.transition_when_with_priority(
110 condition,
111 duration,
112 transition,
113 AnimationPriority::Lowest,
114 then,
115 )
116 }
117
118 pub fn transition_when_with_priority<T, I>(
122 self,
123 condition: bool,
124 duration: Duration,
125 transition: I,
126 priority: AnimationPriority,
127 then: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
128 ) -> Self
129 where
130 T: Transition + 'static,
131 I: IntoArcTransition<T>,
132 {
133 if condition {
134 Self::animated_handle_without_event(
135 self.id.clone(),
136 then,
137 (duration, transition.into_arc()),
138 priority,
139 );
140 }
141
142 self
143 }
144
145 pub fn transition_when_else<T, I>(
149 self,
150 condition: bool,
151 duration: Duration,
152 transition: I,
153 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
154 else_fn: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
155 ) -> Self
156 where
157 T: Transition + 'static,
158 I: IntoArcTransition<T>,
159 {
160 self.transition_when_else_with_priority(
161 condition,
162 duration,
163 transition,
164 AnimationPriority::Lowest,
165 then,
166 else_fn,
167 )
168 }
169
170 pub fn transition_when_else_with_priority<T, I>(
174 self,
175 condition: bool,
176 duration: Duration,
177 transition: I,
178 priority: AnimationPriority,
179 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
180 else_fn: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
181 ) -> Self
182 where
183 T: Transition + 'static,
184 I: IntoArcTransition<T>,
185 {
186 if condition {
187 Self::animated_handle_without_event(
188 self.id.clone(),
189 then,
190 (duration, transition.into_arc()),
191 priority,
192 );
193 } else {
194 Self::animated_handle_without_event(
195 self.id.clone(),
196 else_fn,
197 (duration, transition.into_arc()),
198 priority,
199 );
200 }
201
202 self
203 }
204
205 pub fn transition_when_some<T, I, O>(
209 self,
210 option: Option<O>,
211 duration: Duration,
212 transition: I,
213 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
214 ) -> Self
215 where
216 T: Transition + 'static,
217 I: IntoArcTransition<T>,
218 {
219 self.transition_when_some_with_priority(
220 option,
221 duration,
222 transition,
223 AnimationPriority::Lowest,
224 then,
225 )
226 }
227
228 pub fn transition_when_some_with_priority<T, I, O>(
232 self,
233 option: Option<O>,
234 duration: Duration,
235 transition: I,
236 priority: AnimationPriority,
237 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
238 ) -> Self
239 where
240 T: Transition + 'static,
241 I: IntoArcTransition<T>,
242 {
243 if option.is_some() {
244 Self::animated_handle_without_event(
245 self.id.clone(),
246 then,
247 (duration, transition.into_arc()),
248 priority,
249 );
250 }
251
252 self
253 }
254
255 pub fn transition_when_none<T, I, O>(
259 self,
260 option: &Option<O>,
261 duration: Duration,
262 transition: I,
263 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
264 ) -> Self
265 where
266 T: Transition + 'static,
267 I: IntoArcTransition<T>,
268 {
269 self.transition_when_none_with_priority(
270 option,
271 duration,
272 transition,
273 AnimationPriority::Lowest,
274 then,
275 )
276 }
277
278 pub fn transition_when_none_with_priority<T, I, O>(
282 self,
283 option: &Option<O>,
284 duration: Duration,
285 transition: I,
286 priority: AnimationPriority,
287 then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
288 ) -> Self
289 where
290 T: Transition + 'static,
291 I: IntoArcTransition<T>,
292 {
293 if option.is_none() {
294 Self::animated_handle_without_event(
295 self.id.clone(),
296 then,
297 (duration, transition.into_arc()),
298 priority,
299 );
300 }
301
302 self
303 }
304}
305
306impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
307 AnimatedWrapper<E>
308{
309 fn with_transition(mut child: E, id: impl Into<ElementId>) -> Self {
310 Self {
311 style: child.style().clone(),
312 children: Vec::new(),
313 id: id.into(),
314 child,
315 transitions: HashMap::new(),
316 on_click: None,
317 on_hover: None,
318 hover_modifier: None,
319 click_modifier: None,
320 }
321 }
322}
323
324impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
325 Styled for AnimatedWrapper<E>
326{
327 fn style(&mut self) -> &mut gpui::StyleRefinement {
328 &mut self.style
329 }
330}
331
332impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
333 ParentElement for AnimatedWrapper<E>
334{
335 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
336 self.children.extend(elements);
337 }
338}
339
340impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
341 RenderOnce for AnimatedWrapper<E>
342{
343 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
344 TransitionRegistry::init(cx);
345
346 let mut root = self.child;
347
348 TransitionRegistry::with_state_default(self.id.clone(), &self.style, |state| {
349 root.style().refine(&state.cur);
350 });
351
352 let id_for_hover = self.id.clone();
353 let on_hover_cb = self.on_hover;
354 let hover_mod = self.hover_modifier;
355 let hover_transition = self
356 .transitions
357 .get(&Event::HOVER)
358 .cloned()
359 .unwrap_or_else(|| (Duration::default(), Arc::new(Linear)));
360
361 let id_for_click = self.id.clone();
362 let on_click_cb = self.on_click;
363 let click_mod = self.click_modifier;
364 let click_transition = self
365 .transitions
366 .get(&Event::CLICK)
367 .cloned()
368 .unwrap_or_else(|| (Duration::default(), Arc::new(Linear)));
369
370 root.on_hover(move |hovered, window, app| {
371 if let Some(cb) = &on_hover_cb {
372 cb(hovered, window, app);
373 }
374
375 if *hovered {
376 Self::animated_handle_persistent(
377 hovered,
378 id_for_hover.clone(),
379 Event::HOVER,
380 hover_mod.clone(),
381 hover_transition.clone(),
382 AnimationPriority::Medium,
383 );
384 } else {
385 TransitionRegistry::remove_persistent_context(&id_for_hover, Event::HOVER);
386 Self::animated_handle(
387 hovered,
388 id_for_hover.clone(),
389 Event::HOVER,
390 hover_mod.clone(),
391 hover_transition.clone(),
392 AnimationPriority::High,
393 false,
394 );
395 }
396 })
397 .on_click(move |event, window, app| {
398 if let Some(cb) = &on_click_cb {
399 cb(event, window, app);
400 }
401
402 Self::animated_handle(
403 event,
404 id_for_click.clone(),
405 Event::CLICK,
406 click_mod.clone(),
407 click_transition.clone(),
408 AnimationPriority::High,
409 true,
410 );
411 })
412 .children(self.children)
413 }
414}
415
416impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
417 AnimatedWrapper<E>
418{
419 fn animated_handle<T>(
420 data: &T,
421 id: ElementId,
422 event: Event,
423 modifier: Option<Rc<dyn Fn(&T, State<StyleRefinement>) -> State<StyleRefinement>>>,
424 transition: (Duration, Arc<dyn Transition>),
425 priority: AnimationPriority,
426 save_persistent: bool,
427 ) {
428 let mut should_start_task = None;
429
430 {
431 if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
432 let state_snapshot = state.clone();
433
434 if state.priority <= priority
435 && let Some(modifier) = modifier
436 {
437 if save_persistent {
438 TransitionRegistry::save_persistent_context(
439 &id,
440 &state.to,
441 transition.0,
442 transition.1.clone(),
443 state.priority,
444 );
445 }
446
447 state.priority = priority;
449 *state = modifier(data, state.clone());
450
451 if state_snapshot.ne(&*state) {
452 let (ver, dt) = state.pre_animated(transition.0);
453 should_start_task = Some((ver, dt));
454 } else {
455 state.priority = AnimationPriority::Lowest;
456 }
457 }
458 } else {
459 should_start_task = None;
460 }
461 }
462
463 if let Some((ver, dt)) = should_start_task {
464 TransitionRegistry::background_animated_task(
465 id,
466 event,
467 dt,
468 transition.0,
469 transition.1,
470 ver,
471 false,
472 );
473 }
474 }
475
476 fn animated_handle_persistent<T>(
477 data: &T,
478 id: ElementId,
479 event: Event,
480 modifier: Option<Rc<dyn Fn(&T, State<StyleRefinement>) -> State<StyleRefinement>>>,
481 transition: (Duration, Arc<dyn Transition>),
482 priority: AnimationPriority,
483 ) {
484 let mut should_start_task = None;
485
486 {
487 if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
488 let state_snapshot = state.clone();
489
490 if state.priority <= priority
491 && let Some(modifier) = modifier
492 {
493 state.priority = priority;
495 *state = modifier(data, state.clone());
496
497 if state_snapshot.ne(&*state) {
498 let (ver, dt) = state.pre_animated(transition.0);
499 should_start_task = Some((ver, dt));
500 } else {
501 state.priority = AnimationPriority::Lowest;
502 }
503 }
504 } else {
505 should_start_task = None;
506 }
507 }
508
509 if let Some((ver, dt)) = should_start_task {
510 TransitionRegistry::background_animated_task(
511 id,
512 event,
513 dt,
514 transition.0,
515 transition.1,
516 ver,
517 true,
518 );
519 }
520 }
521 fn animated_handle_without_event(
522 id: ElementId,
523 modifier: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
524 transition: (Duration, Arc<dyn Transition>),
525 priority: AnimationPriority,
526 ) {
527 let mut should_start_task = None;
528
529 {
530 if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
531 let state_snapshot = state.clone();
532
533 if state.priority <= priority {
534 state.priority = priority;
535 *state = modifier(state.clone());
536
537 if state_snapshot.ne(&*state) {
538 let (ver, dt) = state.pre_animated(transition.0);
539 should_start_task = Some((ver, dt));
540 } else {
541 state.priority = AnimationPriority::Lowest;
542 }
543 }
544 } else {
545 should_start_task = None;
546 }
547 }
548
549 if let Some((ver, dt)) = should_start_task {
550 TransitionRegistry::background_animated_task(
551 id,
552 Event::NONE,
553 dt,
554 transition.0,
555 transition.1,
556 ver,
557 false,
558 );
559 }
560 }
561}
562
563pub trait TransitionExt:
564 IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static
565{
566 fn with_transition(self, id: impl Into<ElementId>) -> AnimatedWrapper<Self> {
567 AnimatedWrapper::with_transition(self, id)
568 }
569}
570
571impl<T: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
572 TransitionExt for T
573{
574}