1use std::rc::Rc;
10use std::time::Duration;
11
12use gpui::prelude::*;
13use gpui::{div, px, AnimationExt, AnyElement, App, Context, EventEmitter, IntoElement, Window};
14
15use super::Easing;
16use crate::devtools::ProbedAny;
17use crate::transition::TransitionKind;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PresenceEvent {
22 Shown,
23 Hidden,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27enum Phase {
28 Closed,
29 Open,
30 Closing,
31}
32
33type ContentBuilder = Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>;
34
35pub struct Presence {
38 phase: Phase,
39 kind: TransitionKind,
40 easing: Easing,
41 duration_ms: u64,
42 content: Option<ContentBuilder>,
43 epoch: usize,
46}
47
48impl EventEmitter<PresenceEvent> for Presence {}
49
50impl Presence {
51 pub fn new(_cx: &mut Context<Self>) -> Self {
52 Presence {
53 phase: Phase::Closed,
54 kind: TransitionKind::Fade,
55 easing: Easing::default(),
56 duration_ms: 180,
57 content: None,
58 epoch: 0,
59 }
60 }
61
62 pub fn content(
65 mut self,
66 builder: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
67 ) -> Self {
68 self.content = Some(Rc::new(builder));
69 self
70 }
71
72 pub fn kind(mut self, kind: TransitionKind) -> Self {
73 self.kind = kind;
74 self
75 }
76
77 pub fn easing(mut self, easing: Easing) -> Self {
78 self.easing = easing;
79 self
80 }
81
82 pub fn duration_ms(mut self, duration: u64) -> Self {
83 self.duration_ms = duration.max(1);
84 self
85 }
86
87 pub fn is_open(&self) -> bool {
89 self.phase != Phase::Closed
90 }
91
92 pub fn set_open(&mut self, open: bool, cx: &mut Context<Self>) {
93 match (open, self.phase) {
94 (true, Phase::Closed) | (true, Phase::Closing) => {
95 self.phase = Phase::Open;
96 self.epoch += 1;
97 cx.emit(PresenceEvent::Shown);
98 cx.notify();
99 }
100 (false, Phase::Open) => {
101 self.phase = Phase::Closing;
102 self.epoch += 1;
103 let epoch = self.epoch;
104 let wait = Duration::from_millis(self.duration_ms);
105 cx.spawn(async move |this, cx| {
106 cx.background_executor().timer(wait).await;
107 this
108 .update(cx, |presence, cx| {
109 if presence.epoch == epoch && presence.phase == Phase::Closing {
110 presence.phase = Phase::Closed;
111 cx.emit(PresenceEvent::Hidden);
112 cx.notify();
113 }
114 })
115 .ok();
116 })
117 .detach();
118 cx.notify();
119 }
120 _ => {}
121 }
122 }
123
124 pub fn toggle(&mut self, cx: &mut Context<Self>) {
125 self.set_open(self.phase == Phase::Closed, cx);
126 }
127}
128
129impl Render for Presence {
130 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
131 if self.phase == Phase::Closed {
132 return div().into_any_element();
133 }
134 let Some(builder) = self.content.clone() else {
135 return div().into_any_element();
136 };
137 let child = builder(window, cx);
138 let kind = self.kind;
139 let entering = self.phase == Phase::Open;
140 let easing = self.easing;
144 let animation = easing.clock(self.duration_ms);
145 let id = (
146 if entering {
147 "guise-presence-in"
148 } else {
149 "guise-presence-out"
150 },
151 self.epoch,
152 );
153
154 div()
155 .child(child)
156 .with_animation(id, animation, move |el, t| {
157 let delta = easing.apply(t);
159 let d = if entering { delta } else { 1.0 - delta };
160 let opacity = d.clamp(0.0, 1.0);
161 let shift = (1.0 - d) * 8.0;
162 match kind {
163 TransitionKind::Fade => el.opacity(opacity),
164 TransitionKind::SlideUp => el.opacity(opacity).mt(px(shift)),
165 TransitionKind::SlideDown => el.opacity(opacity).mt(px(-shift)),
166 TransitionKind::SlideLeft => el.opacity(opacity).ml(px(shift)),
167 TransitionKind::SlideRight => el.opacity(opacity).ml(px(-shift)),
168 }
169 })
170 .into_any_element()
171 .probe_any("Presence")
172 .into_any_element()
173 }
174}