1use gpui::prelude::*;
11use gpui::{div, px, AnimationExt, AnyElement, App, ElementId, IntoElement, Window};
12
13use crate::anim::Easing;
14use crate::devtools::ProbedAny;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum TransitionKind {
19 Fade,
20 SlideUp,
21 SlideDown,
22 SlideLeft,
23 SlideRight,
24}
25
26#[derive(IntoElement)]
28pub struct Transition {
29 id: ElementId,
30 kind: TransitionKind,
31 easing: Easing,
32 duration: u64,
33 child: Option<AnyElement>,
34}
35
36impl Transition {
37 pub fn new(id: impl Into<ElementId>) -> Self {
38 Transition {
39 id: id.into(),
40 kind: TransitionKind::Fade,
41 easing: Easing::default(),
42 duration: 200,
43 child: None,
44 }
45 }
46
47 pub fn kind(mut self, kind: TransitionKind) -> Self {
48 self.kind = kind;
49 self
50 }
51
52 pub fn easing(mut self, easing: Easing) -> Self {
54 self.easing = easing;
55 self
56 }
57
58 pub fn duration_ms(mut self, duration: u64) -> Self {
59 self.duration = duration;
60 self
61 }
62
63 pub fn child(mut self, child: impl IntoElement) -> Self {
64 self.child = Some(child.into_any_element());
65 self
66 }
67}
68
69impl RenderOnce for Transition {
70 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
71 let child = self.child.unwrap_or_else(|| div().into_any_element());
72 let kind = self.kind;
73 let easing = self.easing;
78 div()
79 .child(child)
80 .with_animation(self.id, easing.clock(self.duration), move |el, t| {
81 let delta = easing.apply(t);
82 let opacity = delta.clamp(0.0, 1.0);
83 let shift = (1.0 - delta) * 8.0;
84 match kind {
85 TransitionKind::Fade => el.opacity(opacity),
86 TransitionKind::SlideUp => el.opacity(opacity).mt(px(shift)),
87 TransitionKind::SlideDown => el.opacity(opacity).mt(px(-shift)),
88 TransitionKind::SlideLeft => el.opacity(opacity).ml(px(shift)),
89 TransitionKind::SlideRight => el.opacity(opacity).ml(px(-shift)),
90 }
91 })
92 .probe_any("Transition")
93 .into_any_element()
94 }
95}
96
97#[derive(IntoElement)]
101pub struct Collapse {
102 id: ElementId,
103 open: bool,
104 height: Option<f32>,
105 easing: Easing,
106 duration: u64,
107 child: Option<AnyElement>,
108}
109
110impl Collapse {
111 pub fn new(id: impl Into<ElementId>) -> Self {
112 Collapse {
113 id: id.into(),
114 open: false,
115 height: None,
116 easing: Easing::default(),
117 duration: 180,
118 child: None,
119 }
120 }
121
122 pub fn open(mut self, open: bool) -> Self {
123 self.open = open;
124 self
125 }
126
127 pub fn height(mut self, height: f32) -> Self {
131 self.height = Some(height.max(0.0));
132 self
133 }
134
135 pub fn easing(mut self, easing: Easing) -> Self {
136 self.easing = easing;
137 self
138 }
139
140 pub fn duration_ms(mut self, duration: u64) -> Self {
141 self.duration = duration;
142 self
143 }
144
145 pub fn child(mut self, child: impl IntoElement) -> Self {
146 self.child = Some(child.into_any_element());
147 self
148 }
149}
150
151impl RenderOnce for Collapse {
152 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
153 let easing = self.easing;
155 let animation = easing.clock(self.duration);
156
157 let Some(height) = self.height else {
158 if !self.open {
160 return div().into_any_element();
161 }
162 let child = self.child.unwrap_or_else(|| div().into_any_element());
163 return div()
164 .child(child)
165 .with_animation(self.id, animation, move |el, t| {
166 el.opacity(easing.apply(t).clamp(0.0, 1.0))
167 })
168 .into_any_element();
169 };
170
171 let child = self.child.unwrap_or_else(|| div().into_any_element());
172 let open = self.open;
173 let direction = if open {
176 "guise-collapse-open"
177 } else {
178 "guise-collapse-close"
179 };
180 div()
181 .id(self.id)
182 .overflow_hidden()
183 .child(child)
184 .with_animation(direction, animation, move |el, t| {
185 let d = if open {
186 easing.apply(t)
187 } else {
188 1.0 - easing.apply(t)
189 };
190 el.h(px(height * d.max(0.0))).opacity(d.clamp(0.0, 1.0))
193 })
194 .into_any_element()
195 .probe_any("Collapse")
196 .into_any_element()
197 }
198}