use gpui::{
AnyElement, App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement,
LayoutId, Pixels, Window, point, px,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum DialogEntrance {
#[default]
SlideDown,
Fade,
FadeSlide,
None,
}
pub(super) struct EntranceSurface {
child: AnyElement,
travel: Pixels,
top_limit: Pixels,
pub(super) progress: f32,
}
impl EntranceSurface {
pub(super) fn new(child: AnyElement, travel: Pixels, top_limit: Pixels) -> Self {
Self {
child,
travel,
top_limit,
progress: 0.,
}
}
}
impl IntoElement for EntranceSurface {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for EntranceSurface {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
(self.child.request_layout(window, cx), ())
}
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut (),
window: &mut Window,
cx: &mut App,
) {
let available = (bounds.top() - self.top_limit).max(px(0.));
let travel = self.travel.max(px(0.)).min(available);
let offset = point(px(0.), -travel * (1. - self.progress.clamp(0., 1.)));
window.with_element_offset(offset, |window| self.child.prepaint(window, cx));
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut (),
_: &mut (),
window: &mut Window,
cx: &mut App,
) {
self.child.paint(window, cx);
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{
Anchor, Context, InteractiveElement as _, ParentElement as _, Render,
StatefulInteractiveElement as _, Styled as _, TestAppContext, div, size,
};
struct SurfaceProbe {
progress: f32,
requested_y: Pixels,
clicks: usize,
}
impl Render for SurfaceProbe {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let mut surface = EntranceSurface::new(
div()
.id("surface")
.w(px(200.))
.h(px(300.))
.debug_selector(|| "entrance-surface".into())
.on_click(cx.listener(|probe, _, _, _| probe.clicks += 1))
.into_any_element(),
px(8.),
px(16.),
);
surface.progress = self.progress;
div().size_full().child(
gpui_base::Positioner::corner(Anchor::TopLeft, point(px(100.), self.requested_y))
.margin(px(16.))
.child(surface),
)
}
}
#[gpui::test]
fn fade_slide_uses_resolved_bounds_and_keeps_the_surface_inside_the_window(
cx: &mut TestAppContext,
) {
let (view, cx) = cx.add_window_view(|_, _| SurfaceProbe {
progress: 0.,
requested_y: px(200.),
clicks: 0,
});
cx.simulate_resize(size(px(400.), px(400.)));
for (requested_y, positions) in [
(px(200.), [px(76.), px(80.), px(84.)]),
(px(20.), [px(16.), px(18.), px(20.)]),
(px(0.), [px(16.), px(16.), px(16.)]),
] {
for (progress, expected_y) in [0., 0.5, 1.].into_iter().zip(positions) {
cx.update(|window, cx| {
view.update(cx, |probe, cx| {
probe.progress = progress;
probe.requested_y = requested_y;
cx.notify();
});
window.draw(cx).clear(cx);
});
let bounds = cx.debug_bounds("entrance-surface").unwrap();
assert_eq!(bounds.top(), expected_y);
assert!(bounds.top() >= px(16.) && bounds.bottom() <= px(384.));
cx.simulate_click(bounds.origin + point(px(10.), px(2.)), Default::default());
}
}
cx.update(|_, cx| assert_eq!(view.read(cx).clicks, 9));
}
}