gpui_component/scroll/
scrollable.rs

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