gpui_component/scroll/
scrollable.rs

1use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
2use gpui::{
3    div, relative, AnyElement, App, Bounds, Div, Element, ElementId, GlobalElementId,
4    InspectorElementId, InteractiveElement, Interactivity, IntoElement, LayoutId, ParentElement,
5    Pixels, Position, ScrollHandle, SharedString, Stateful, StatefulInteractiveElement, Style,
6    StyleRefinement, Styled, Window,
7};
8
9/// A scroll view is a container that allows the user to scroll through a large amount of content.
10pub struct Scrollable<E> {
11    id: ElementId,
12    element: Option<E>,
13    axis: ScrollbarAxis,
14    /// This is a fake element to handle Styled, InteractiveElement, not used.
15    _element: Stateful<Div>,
16}
17
18impl<E> Scrollable<E>
19where
20    E: Element,
21{
22    pub(crate) fn new(axis: impl Into<ScrollbarAxis>, element: E) -> Self {
23        let id = ElementId::Name(SharedString::from(
24            format!("scrollable-{:?}", element.id(),),
25        ));
26
27        Self {
28            element: Some(element),
29            _element: div().id("fake"),
30            id,
31            axis: axis.into(),
32        }
33    }
34
35    /// Set only a vertical scrollbar.
36    pub fn vertical(mut self) -> Self {
37        self.set_axis(ScrollbarAxis::Vertical);
38        self
39    }
40
41    /// Set only a horizontal scrollbar.
42    /// In current implementation, this is not supported yet.
43    pub fn horizontal(mut self) -> Self {
44        self.set_axis(ScrollbarAxis::Horizontal);
45        self
46    }
47
48    /// Set the axis of the scroll view.
49    pub fn set_axis(&mut self, axis: impl Into<ScrollbarAxis>) {
50        self.axis = axis.into();
51    }
52
53    fn with_element_state<R>(
54        &mut self,
55        id: &GlobalElementId,
56        window: &mut Window,
57        cx: &mut App,
58        f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut Window, &mut App) -> R,
59    ) -> R {
60        window.with_optional_element_state::<ScrollViewState, _>(
61            Some(id),
62            |element_state, window| {
63                let mut element_state = element_state.unwrap().unwrap_or_default();
64                let result = f(self, &mut element_state, window, cx);
65                (result, Some(element_state))
66            },
67        )
68    }
69}
70
71pub struct ScrollViewState {
72    state: ScrollbarState,
73    handle: ScrollHandle,
74}
75
76impl Default for ScrollViewState {
77    fn default() -> Self {
78        Self {
79            handle: ScrollHandle::new(),
80            state: ScrollbarState::default(),
81        }
82    }
83}
84
85impl<E> ParentElement for Scrollable<E>
86where
87    E: Element + ParentElement,
88{
89    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
90        if let Some(element) = &mut self.element {
91            element.extend(elements);
92        }
93    }
94}
95
96impl<E> Styled for Scrollable<E>
97where
98    E: Element + Styled,
99{
100    fn style(&mut self) -> &mut StyleRefinement {
101        if let Some(element) = &mut self.element {
102            element.style()
103        } else {
104            self._element.style()
105        }
106    }
107}
108
109impl<E> InteractiveElement for Scrollable<E>
110where
111    E: Element + InteractiveElement,
112{
113    fn interactivity(&mut self) -> &mut Interactivity {
114        if let Some(element) = &mut self.element {
115            element.interactivity()
116        } else {
117            self._element.interactivity()
118        }
119    }
120}
121impl<E> StatefulInteractiveElement for Scrollable<E> where E: Element + StatefulInteractiveElement {}
122
123impl<E> IntoElement for Scrollable<E>
124where
125    E: Element,
126{
127    type Element = Self;
128
129    fn into_element(self) -> Self::Element {
130        self
131    }
132}
133
134impl<E> Element for Scrollable<E>
135where
136    E: Element,
137{
138    type RequestLayoutState = AnyElement;
139    type PrepaintState = ScrollViewState;
140
141    fn id(&self) -> Option<ElementId> {
142        Some(self.id.clone())
143    }
144
145    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
146        None
147    }
148
149    fn request_layout(
150        &mut self,
151        id: Option<&GlobalElementId>,
152        _: Option<&InspectorElementId>,
153        window: &mut Window,
154        cx: &mut App,
155    ) -> (LayoutId, Self::RequestLayoutState) {
156        let mut style = Style::default();
157        style.flex_grow = 1.0;
158        style.position = Position::Relative;
159        style.size.width = relative(1.0).into();
160        style.size.height = relative(1.0).into();
161
162        let axis = self.axis;
163        let scroll_id = self.id.clone();
164        let content = self.element.take().map(|c| c.into_any_element());
165
166        self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| {
167            let mut element = div()
168                .relative()
169                .size_full()
170                .overflow_hidden()
171                .child(
172                    div()
173                        .id(scroll_id)
174                        .track_scroll(&element_state.handle)
175                        .overflow_scroll()
176                        .relative()
177                        .size_full()
178                        .child(div().children(content)),
179                )
180                .child(
181                    div()
182                        .absolute()
183                        .top_0()
184                        .left_0()
185                        .right_0()
186                        .bottom_0()
187                        .child(
188                            Scrollbar::both(&element_state.state, &element_state.handle).axis(axis),
189                        ),
190                )
191                .into_any_element();
192
193            let element_id = element.request_layout(window, cx);
194            let layout_id = window.request_layout(style, vec![element_id], cx);
195
196            (layout_id, element)
197        })
198    }
199
200    fn prepaint(
201        &mut self,
202        _: Option<&GlobalElementId>,
203        _: Option<&InspectorElementId>,
204        _: Bounds<Pixels>,
205        element: &mut Self::RequestLayoutState,
206        window: &mut Window,
207        cx: &mut App,
208    ) -> Self::PrepaintState {
209        element.prepaint(window, cx);
210        // do nothing
211        ScrollViewState::default()
212    }
213
214    fn paint(
215        &mut self,
216        _: Option<&GlobalElementId>,
217        _: Option<&InspectorElementId>,
218        _: Bounds<Pixels>,
219        element: &mut Self::RequestLayoutState,
220        _: &mut Self::PrepaintState,
221        window: &mut Window,
222        cx: &mut App,
223    ) {
224        element.paint(window, cx)
225    }
226}