use std::{collections::HashMap, rc::Rc, sync::Arc, time::Duration};
use gpui::{prelude::FluentBuilder, *};
use crate::{
interpolate::State,
transition::{IntoArcTransition, Transition, TransitionRegistry, general::Linear},
};
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Event {
NONE,
HOVER,
CLICK,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum AnimationPriority {
Lowest = 0,
Low = 25,
Medium = 50,
High = 75,
Realtime = 100,
}
#[derive(IntoElement)]
pub struct AnimatedWrapper<E>
where
E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static,
{
style: StyleRefinement,
children: Vec<AnyElement>,
id: ElementId,
child: E,
transitions: HashMap<Event, (Duration, Arc<dyn Transition>)>,
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
hover_modifier: Option<Rc<dyn Fn(&bool, State<StyleRefinement>) -> State<StyleRefinement>>>,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
click_modifier:
Option<Rc<dyn Fn(&ClickEvent, State<StyleRefinement>) -> State<StyleRefinement>>>,
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
AnimatedWrapper<E>
{
pub fn transition_on_hover<T, I>(
mut self,
duration: Duration,
transition: I,
modifier: impl Fn(&bool, State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transitions
.insert(Event::HOVER, (duration, transition.into_arc()));
self.hover_modifier = Some(Rc::new(modifier));
self
}
pub fn transition_on_click<T, I>(
mut self,
duration: Duration,
transition: I,
modifier: impl Fn(&ClickEvent, State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transitions
.insert(Event::CLICK, (duration, transition.into_arc()));
self.click_modifier = Some(Rc::new(modifier));
self
}
pub fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_hover = Some(Rc::new(listener));
self
}
pub fn on_click(
mut self,
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Rc::new(listener));
self
}
pub fn transition_when<T, I>(
self,
condition: bool,
duration: Duration,
transition: I,
then: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transition_when_with_priority(
condition,
duration,
transition,
AnimationPriority::Lowest,
then,
)
}
pub fn transition_when_with_priority<T, I>(
self,
condition: bool,
duration: Duration,
transition: I,
priority: AnimationPriority,
then: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
if condition {
Self::animated_handle_without_event(
self.id.clone(),
then,
(duration, transition.into_arc()),
priority,
);
}
self
}
pub fn transition_when_else<T, I>(
self,
condition: bool,
duration: Duration,
transition: I,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
else_fn: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transition_when_else_with_priority(
condition,
duration,
transition,
AnimationPriority::Lowest,
then,
else_fn,
)
}
pub fn transition_when_else_with_priority<T, I>(
self,
condition: bool,
duration: Duration,
transition: I,
priority: AnimationPriority,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
else_fn: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
if condition {
Self::animated_handle_without_event(
self.id.clone(),
then,
(duration, transition.into_arc()),
priority,
);
} else {
Self::animated_handle_without_event(
self.id.clone(),
else_fn,
(duration, transition.into_arc()),
priority,
);
}
self
}
pub fn transition_when_some<T, I, O>(
self,
option: Option<O>,
duration: Duration,
transition: I,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transition_when_some_with_priority(
option,
duration,
transition,
AnimationPriority::Lowest,
then,
)
}
pub fn transition_when_some_with_priority<T, I, O>(
self,
option: Option<O>,
duration: Duration,
transition: I,
priority: AnimationPriority,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
if option.is_some() {
Self::animated_handle_without_event(
self.id.clone(),
then,
(duration, transition.into_arc()),
priority,
);
}
self
}
pub fn transition_when_none<T, I, O>(
self,
option: &Option<O>,
duration: Duration,
transition: I,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
self.transition_when_none_with_priority(
option,
duration,
transition,
AnimationPriority::Lowest,
then,
)
}
pub fn transition_when_none_with_priority<T, I, O>(
self,
option: &Option<O>,
duration: Duration,
transition: I,
priority: AnimationPriority,
then: impl Fn(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
) -> Self
where
T: Transition + 'static,
I: IntoArcTransition<T>,
{
if option.is_none() {
Self::animated_handle_without_event(
self.id.clone(),
then,
(duration, transition.into_arc()),
priority,
);
}
self
}
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
AnimatedWrapper<E>
{
fn with_transition(mut child: E, id: impl Into<ElementId>) -> Self {
Self {
style: child.style().clone(),
children: Vec::new(),
id: id.into(),
child,
transitions: HashMap::new(),
on_click: None,
on_hover: None,
hover_modifier: None,
click_modifier: None,
}
}
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
Styled for AnimatedWrapper<E>
{
fn style(&mut self) -> &mut gpui::StyleRefinement {
&mut self.style
}
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
ParentElement for AnimatedWrapper<E>
{
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
RenderOnce for AnimatedWrapper<E>
{
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
TransitionRegistry::init(window, cx);
let mut root = self.child;
TransitionRegistry::with_state_default(self.id.clone(), &self.style, |state| {
root.style().refine(&state.cur);
});
let id_for_hover = self.id.clone();
let on_hover_cb = self.on_hover;
let hover_mod = self.hover_modifier;
let hover_transition = self
.transitions
.get(&Event::HOVER)
.cloned()
.unwrap_or_else(|| (Duration::default(), Arc::new(Linear)));
let id_for_click = self.id.clone();
let on_click_cb = self.on_click;
let click_mod = self.click_modifier;
let click_transition = self
.transitions
.get(&Event::CLICK)
.cloned()
.unwrap_or_else(|| (Duration::default(), Arc::new(Linear)));
root.on_hover(move |hovered, window, app| {
if let Some(cb) = &on_hover_cb {
cb(hovered, window, app);
}
if *hovered {
Self::animated_handle_persistent(
hovered,
id_for_hover.clone(),
Event::HOVER,
hover_mod.clone(),
hover_transition.clone(),
AnimationPriority::Medium,
);
} else {
TransitionRegistry::remove_persistent_context(&id_for_hover, Event::HOVER);
Self::animated_handle(
hovered,
id_for_hover.clone(),
Event::HOVER,
hover_mod.clone(),
hover_transition.clone(),
AnimationPriority::High,
false,
);
}
})
.on_click(move |event, window, app| {
if let Some(cb) = &on_click_cb {
cb(event, window, app);
}
Self::animated_handle(
event,
id_for_click.clone(),
Event::CLICK,
click_mod.clone(),
click_transition.clone(),
AnimationPriority::High,
true,
);
})
.children(self.children)
}
}
impl<E: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
AnimatedWrapper<E>
{
fn animated_handle<T>(
data: &T,
id: ElementId,
event: Event,
modifier: Option<Rc<dyn Fn(&T, State<StyleRefinement>) -> State<StyleRefinement>>>,
transition: (Duration, Arc<dyn Transition>),
priority: AnimationPriority,
save_persistent: bool,
) {
let mut should_start_task = None;
{
if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
let state_snapshot = state.clone();
if state.priority <= priority
&& let Some(modifier) = modifier
{
if save_persistent {
TransitionRegistry::save_persistent_context(
&id,
&state.to,
transition.0,
transition.1.clone(),
state.priority,
);
}
state.priority = priority;
*state = modifier(data, state.clone());
if state_snapshot.ne(&*state) {
let (ver, dt) = state.pre_animated(transition.0);
should_start_task = Some((ver, dt));
} else {
state.priority = AnimationPriority::Lowest;
}
}
} else {
should_start_task = None;
}
}
if let Some((ver, dt)) = should_start_task {
TransitionRegistry::background_animated_task(
id,
event,
dt,
transition.0,
transition.1,
ver,
false,
);
}
}
fn animated_handle_persistent<T>(
data: &T,
id: ElementId,
event: Event,
modifier: Option<Rc<dyn Fn(&T, State<StyleRefinement>) -> State<StyleRefinement>>>,
transition: (Duration, Arc<dyn Transition>),
priority: AnimationPriority,
) {
let mut should_start_task = None;
{
if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
let state_snapshot = state.clone();
if state.priority <= priority
&& let Some(modifier) = modifier
{
state.priority = priority;
*state = modifier(data, state.clone());
if state_snapshot.ne(&*state) {
let (ver, dt) = state.pre_animated(transition.0);
should_start_task = Some((ver, dt));
} else {
state.priority = AnimationPriority::Lowest;
}
}
} else {
should_start_task = None;
}
}
if let Some((ver, dt)) = should_start_task {
TransitionRegistry::background_animated_task(
id,
event,
dt,
transition.0,
transition.1,
ver,
true,
);
}
}
fn animated_handle_without_event(
id: ElementId,
modifier: impl FnOnce(State<StyleRefinement>) -> State<StyleRefinement> + 'static,
transition: (Duration, Arc<dyn Transition>),
priority: AnimationPriority,
) {
let mut should_start_task = None;
{
if let Some(mut state) = TransitionRegistry::state_mut(id.clone()) {
let state_snapshot = state.clone();
if state.priority <= priority {
state.priority = priority;
*state = modifier(state.clone());
if state_snapshot.ne(&*state) {
let (ver, dt) = state.pre_animated(transition.0);
should_start_task = Some((ver, dt));
} else {
state.priority = AnimationPriority::Lowest;
}
}
} else {
should_start_task = None;
}
}
if let Some((ver, dt)) = should_start_task {
TransitionRegistry::background_animated_task(
id,
Event::NONE,
dt,
transition.0,
transition.1,
ver,
false,
);
}
}
}
pub trait TransitionExt:
IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static
{
fn with_transition(self, id: impl Into<ElementId>) -> AnimatedWrapper<Self> {
AnimatedWrapper::with_transition(self, id)
}
}
impl<T: IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled + 'static>
TransitionExt for T
{
}