use std::rc::Rc;
use std::time::Duration;
use gpui::prelude::*;
use gpui::{div, px, AnimationExt, AnyElement, App, Context, EventEmitter, IntoElement, Window};
use super::Easing;
use crate::transition::TransitionKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PresenceEvent {
Shown,
Hidden,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Closed,
Open,
Closing,
}
type ContentBuilder = Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>;
pub struct Presence {
phase: Phase,
kind: TransitionKind,
easing: Easing,
duration_ms: u64,
content: Option<ContentBuilder>,
epoch: usize,
}
impl EventEmitter<PresenceEvent> for Presence {}
impl Presence {
pub fn new(_cx: &mut Context<Self>) -> Self {
Presence {
phase: Phase::Closed,
kind: TransitionKind::Fade,
easing: Easing::default(),
duration_ms: 180,
content: None,
epoch: 0,
}
}
pub fn content(
mut self,
builder: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
) -> Self {
self.content = Some(Rc::new(builder));
self
}
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_ms = duration.max(1);
self
}
pub fn is_open(&self) -> bool {
self.phase != Phase::Closed
}
pub fn set_open(&mut self, open: bool, cx: &mut Context<Self>) {
match (open, self.phase) {
(true, Phase::Closed) | (true, Phase::Closing) => {
self.phase = Phase::Open;
self.epoch += 1;
cx.emit(PresenceEvent::Shown);
cx.notify();
}
(false, Phase::Open) => {
self.phase = Phase::Closing;
self.epoch += 1;
let epoch = self.epoch;
let wait = Duration::from_millis(self.duration_ms);
cx.spawn(async move |this, cx| {
cx.background_executor().timer(wait).await;
this.update(cx, |presence, cx| {
if presence.epoch == epoch && presence.phase == Phase::Closing {
presence.phase = Phase::Closed;
cx.emit(PresenceEvent::Hidden);
cx.notify();
}
})
.ok();
})
.detach();
cx.notify();
}
_ => {}
}
}
pub fn toggle(&mut self, cx: &mut Context<Self>) {
self.set_open(self.phase == Phase::Closed, cx);
}
}
impl Render for Presence {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.phase == Phase::Closed {
return div().into_any_element();
}
let Some(builder) = self.content.clone() else {
return div().into_any_element();
};
let child = builder(window, cx);
let kind = self.kind;
let entering = self.phase == Phase::Open;
let easing = self.easing;
let animation = easing.clock(self.duration_ms);
let id = (
if entering {
"guise-presence-in"
} else {
"guise-presence-out"
},
self.epoch,
);
div()
.child(child)
.with_animation(id, animation, move |el, t| {
let delta = easing.apply(t);
let d = if entering { delta } else { 1.0 - delta };
let opacity = d.clamp(0.0, 1.0);
let shift = (1.0 - d) * 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)),
}
})
.into_any_element()
}
}