Skip to main content

gpui_base/motion/
reveal.rs

1use gpui::{
2    AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
3    InspectorElementId, IntoElement, LayoutId, Pixels, Style, Window, px, relative, size,
4};
5
6/// A measured, clipped vertical reveal driven by normalized progress.
7pub struct MotionReveal {
8    id: ElementId,
9    progress: f32,
10    child: AnyElement,
11}
12
13#[derive(Clone, Copy, Default)]
14struct RevealState {
15    height: Option<Pixels>,
16}
17
18impl MotionReveal {
19    pub fn new(id: impl Into<ElementId>, progress: f32, child: AnyElement) -> Self {
20        Self {
21            id: id.into(),
22            progress: progress.clamp(0.0, 1.0),
23            child,
24        }
25    }
26}
27
28impl IntoElement for MotionReveal {
29    type Element = Self;
30
31    fn into_element(self) -> Self::Element {
32        self
33    }
34}
35
36impl Element for MotionReveal {
37    type RequestLayoutState = ();
38    type PrepaintState = ();
39
40    fn id(&self) -> Option<ElementId> {
41        Some(self.id.clone())
42    }
43
44    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
45        None
46    }
47
48    fn request_layout(
49        &mut self,
50        global_id: Option<&GlobalElementId>,
51        _: Option<&InspectorElementId>,
52        window: &mut Window,
53        cx: &mut App,
54    ) -> (LayoutId, Self::RequestLayoutState) {
55        let height = window.with_element_state(
56            global_id.expect("MotionReveal must have an id"),
57            |state: Option<RevealState>, _| {
58                let state = state.unwrap_or_default();
59                (state.height, state)
60            },
61        );
62        let mut style = Style::default();
63        style.size.width = relative(1.0).into();
64        match height {
65            None if self.progress > 0.0 => {}
66            None => style.size.height = px(0.0).into(),
67            Some(height) => style.size.height = (height * self.progress).into(),
68        }
69        (window.request_layout(style, None, cx), ())
70    }
71
72    fn prepaint(
73        &mut self,
74        global_id: Option<&GlobalElementId>,
75        _: Option<&InspectorElementId>,
76        bounds: Bounds<Pixels>,
77        _: &mut Self::RequestLayoutState,
78        window: &mut Window,
79        cx: &mut App,
80    ) -> Self::PrepaintState {
81        let measured = self.child.layout_as_root(
82            size(
83                AvailableSpace::Definite(bounds.size.width),
84                AvailableSpace::MinContent,
85            ),
86            window,
87            cx,
88        );
89        let changed = window.with_element_state(
90            global_id.expect("MotionReveal must have an id"),
91            |state: Option<RevealState>, _| {
92                let mut state = state.unwrap_or_default();
93                let changed = state.height != Some(measured.height);
94                state.height = Some(measured.height);
95                (changed, state)
96            },
97        );
98        if changed {
99            window.request_animation_frame();
100        }
101        window.with_content_mask(Some(ContentMask { bounds }), |window| {
102            self.child.prepaint_at(bounds.origin, window, cx);
103        });
104    }
105
106    fn paint(
107        &mut self,
108        _: Option<&GlobalElementId>,
109        _: Option<&InspectorElementId>,
110        bounds: Bounds<Pixels>,
111        _: &mut Self::RequestLayoutState,
112        _: &mut Self::PrepaintState,
113        window: &mut Window,
114        cx: &mut App,
115    ) {
116        window.with_content_mask(Some(ContentMask { bounds }), |window| {
117            self.child.paint(window, cx);
118        });
119    }
120}