use gpui::prelude::*;
use gpui::{div, px, AnimationExt, AnyElement, App, ElementId, IntoElement, Window};
use crate::anim::Easing;
use crate::devtools::ProbedAny;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransitionKind {
Fade,
SlideUp,
SlideDown,
SlideLeft,
SlideRight,
}
#[derive(IntoElement)]
pub struct Transition {
id: ElementId,
kind: TransitionKind,
easing: Easing,
duration: u64,
child: Option<AnyElement>,
}
impl Transition {
pub fn new(id: impl Into<ElementId>) -> Self {
Transition {
id: id.into(),
kind: TransitionKind::Fade,
easing: Easing::default(),
duration: 200,
child: None,
}
}
pub fn kind(mut self, kind: TransitionKind) -> Self {
self.kind = kind;
self
}
pub fn easing(mut self, easing: Easing) -> Self {
self.easing = easing;
self
}
pub fn duration_ms(mut self, duration: u64) -> Self {
self.duration = duration;
self
}
pub fn child(mut self, child: impl IntoElement) -> Self {
self.child = Some(child.into_any_element());
self
}
}
impl RenderOnce for Transition {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let child = self.child.unwrap_or_else(|| div().into_any_element());
let kind = self.kind;
let easing = self.easing;
div()
.child(child)
.with_animation(self.id, easing.clock(self.duration), move |el, t| {
let delta = easing.apply(t);
let opacity = delta.clamp(0.0, 1.0);
let shift = (1.0 - delta) * 8.0;
match kind {
TransitionKind::Fade => el.opacity(opacity),
TransitionKind::SlideUp => el.opacity(opacity).mt(px(shift)),
TransitionKind::SlideDown => el.opacity(opacity).mt(px(-shift)),
TransitionKind::SlideLeft => el.opacity(opacity).ml(px(shift)),
TransitionKind::SlideRight => el.opacity(opacity).ml(px(-shift)),
}
})
.probe_any("Transition")
.into_any_element()
}
}
#[derive(IntoElement)]
pub struct Collapse {
id: ElementId,
open: bool,
height: Option<f32>,
easing: Easing,
duration: u64,
child: Option<AnyElement>,
}
impl Collapse {
pub fn new(id: impl Into<ElementId>) -> Self {
Collapse {
id: id.into(),
open: false,
height: None,
easing: Easing::default(),
duration: 180,
child: None,
}
}
pub fn open(mut self, open: bool) -> Self {
self.open = open;
self
}
pub fn height(mut self, height: f32) -> Self {
self.height = Some(height.max(0.0));
self
}
pub fn easing(mut self, easing: Easing) -> Self {
self.easing = easing;
self
}
pub fn duration_ms(mut self, duration: u64) -> Self {
self.duration = duration;
self
}
pub fn child(mut self, child: impl IntoElement) -> Self {
self.child = Some(child.into_any_element());
self
}
}
impl RenderOnce for Collapse {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let easing = self.easing;
let animation = easing.clock(self.duration);
let Some(height) = self.height else {
if !self.open {
return div().into_any_element();
}
let child = self.child.unwrap_or_else(|| div().into_any_element());
return div()
.child(child)
.with_animation(self.id, animation, move |el, t| {
el.opacity(easing.apply(t).clamp(0.0, 1.0))
})
.into_any_element();
};
let child = self.child.unwrap_or_else(|| div().into_any_element());
let open = self.open;
let direction = if open {
"guise-collapse-open"
} else {
"guise-collapse-close"
};
div()
.id(self.id)
.overflow_hidden()
.child(child)
.with_animation(direction, animation, move |el, t| {
let d = if open {
easing.apply(t)
} else {
1.0 - easing.apply(t)
};
el.h(px(height * d.max(0.0))).opacity(d.clamp(0.0, 1.0))
})
.into_any_element()
.probe_any("Collapse")
.into_any_element()
}
}