gpui_component/scroll/
scrollable.rs1use std::{panic::Location, rc::Rc};
2
3use crate::{StyledExt, scroll::ScrollbarHandle};
4
5use super::{Scrollbar, ScrollbarAxis};
6use gpui::{
7 App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
8 ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
9 prelude::FluentBuilder,
10};
11
12pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element {
14 #[track_caller]
16 fn scrollbar<H: ScrollbarHandle + Clone>(
17 self,
18 scroll_handle: &H,
19 axis: impl Into<ScrollbarAxis>,
20 ) -> Self {
21 self.child(ScrollbarLayer {
22 id: "scrollbar_layer".into(),
23 axis: axis.into(),
24 scroll_handle: Rc::new(scroll_handle.clone()),
25 })
26 }
27
28 #[track_caller]
30 fn vertical_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
31 self.scrollbar(scroll_handle, ScrollbarAxis::Vertical)
32 }
33 #[track_caller]
35 fn horizontal_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
36 self.scrollbar(scroll_handle, ScrollbarAxis::Horizontal)
37 }
38
39 #[track_caller]
41 fn overflow_scrollbar(self) -> Scrollable<Self> {
42 Scrollable::new(self, ScrollbarAxis::Both)
43 }
44
45 #[track_caller]
47 fn overflow_x_scrollbar(self) -> Scrollable<Self> {
48 Scrollable::new(self, ScrollbarAxis::Horizontal)
49 }
50
51 #[track_caller]
53 fn overflow_y_scrollbar(self) -> Scrollable<Self> {
54 Scrollable::new(self, ScrollbarAxis::Vertical)
55 }
56}
57
58#[derive(IntoElement)]
60pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
61 id: ElementId,
62 element: E,
63 axis: ScrollbarAxis,
64}
65
66impl<E> Scrollable<E>
67where
68 E: InteractiveElement + Styled + ParentElement + Element,
69{
70 #[track_caller]
71 fn new(element: E, axis: impl Into<ScrollbarAxis>) -> Self {
72 let caller = Location::caller();
73 Self {
74 id: ElementId::CodeLocation(*caller),
75 element,
76 axis: axis.into(),
77 }
78 }
79}
80
81impl<E> Styled for Scrollable<E>
82where
83 E: InteractiveElement + Styled + ParentElement + Element,
84{
85 fn style(&mut self) -> &mut StyleRefinement {
86 self.element.style()
87 }
88}
89
90impl<E> ParentElement for Scrollable<E>
91where
92 E: InteractiveElement + Styled + ParentElement + Element,
93{
94 fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
95 self.element.extend(elements)
96 }
97}
98
99impl InteractiveElement for Scrollable<Div> {
100 fn interactivity(&mut self) -> &mut gpui::Interactivity {
101 self.element.interactivity()
102 }
103}
104
105impl InteractiveElement for Scrollable<Stateful<Div>> {
106 fn interactivity(&mut self) -> &mut gpui::Interactivity {
107 self.element.interactivity()
108 }
109}
110
111impl<E> RenderOnce for Scrollable<E>
112where
113 E: InteractiveElement + Styled + ParentElement + Element + 'static,
114{
115 fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
116 let scroll_handle = window
117 .use_keyed_state(self.id.clone(), cx, |_, _| ScrollHandle::default())
118 .read(cx)
119 .clone();
120
121 let style = self.element.style().clone();
122 *self.element.style() = StyleRefinement::default();
123
124 div()
125 .id(self.id)
126 .size_full()
127 .refine_style(&style)
128 .relative()
129 .child(
130 div()
131 .id("scroll-area")
132 .flex()
133 .size_full()
134 .track_scroll(&scroll_handle)
135 .map(|this| match self.axis {
136 ScrollbarAxis::Vertical => this.flex_col().overflow_y_scroll(),
137 ScrollbarAxis::Horizontal => this.flex_row().overflow_x_scroll(),
138 ScrollbarAxis::Both => this.overflow_scroll(),
139 })
140 .child(self.element.flex_1()),
141 )
142 .child(render_scrollbar(
143 "scrollbar",
144 &scroll_handle,
145 self.axis,
146 window,
147 cx,
148 ))
149 }
150}
151
152impl ScrollableElement for Div {}
153impl<E> ScrollableElement for Stateful<E>
154where
155 E: ParentElement + Styled + Element,
156 Self: InteractiveElement,
157{
158}
159
160#[derive(IntoElement)]
161struct ScrollbarLayer<H: ScrollbarHandle + Clone> {
162 id: ElementId,
163 axis: ScrollbarAxis,
164 scroll_handle: Rc<H>,
165}
166
167impl<H> RenderOnce for ScrollbarLayer<H>
168where
169 H: ScrollbarHandle + Clone + 'static,
170{
171 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
172 render_scrollbar(self.id, self.scroll_handle.as_ref(), self.axis, window, cx)
173 }
174}
175
176#[inline]
177#[track_caller]
178fn render_scrollbar<H: ScrollbarHandle + Clone>(
179 id: impl Into<ElementId>,
180 scroll_handle: &H,
181 axis: ScrollbarAxis,
182 window: &mut Window,
183 cx: &mut App,
184) -> Div {
185 let is_inspector_picking = window.is_inspector_picking(cx);
188 if is_inspector_picking {
189 return div();
190 }
191
192 div()
193 .absolute()
194 .top_0()
195 .left_0()
196 .right_0()
197 .bottom_0()
198 .child(Scrollbar::new(scroll_handle).id(id).axis(axis))
199}