gpui_component/scroll/
scrollable_mask.rs1use gpui::{
2 px, relative, App, Axis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element, ElementId,
3 GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,
4 Position, ScrollHandle, ScrollWheelEvent, Style, Window,
5};
6
7use crate::AxisExt;
8
9pub struct ScrollableMask {
15 axis: Axis,
16 scroll_handle: ScrollHandle,
17 debug: Option<Hsla>,
18}
19
20impl ScrollableMask {
21 pub fn new(axis: Axis, scroll_handle: &ScrollHandle) -> Self {
23 Self {
24 scroll_handle: scroll_handle.clone(),
25 axis,
26 debug: None,
27 }
28 }
29
30 #[allow(dead_code)]
32 pub fn debug(mut self) -> Self {
33 self.debug = Some(gpui::yellow());
34 self
35 }
36}
37
38impl IntoElement for ScrollableMask {
39 type Element = Self;
40
41 fn into_element(self) -> Self::Element {
42 self
43 }
44}
45
46impl Element for ScrollableMask {
47 type RequestLayoutState = ();
48 type PrepaintState = Hitbox;
49
50 fn id(&self) -> Option<ElementId> {
51 None
52 }
53
54 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
55 None
56 }
57
58 fn request_layout(
59 &mut self,
60 _: Option<&GlobalElementId>,
61 _: Option<&gpui::InspectorElementId>,
62 window: &mut Window,
63 cx: &mut App,
64 ) -> (LayoutId, Self::RequestLayoutState) {
65 let mut style = Style::default();
66 style.position = Position::Absolute;
68 style.flex_grow = 1.0;
69 style.flex_shrink = 1.0;
70 style.size.width = relative(1.).into();
71 style.size.height = relative(1.).into();
72
73 (window.request_layout(style, None, cx), ())
74 }
75
76 fn prepaint(
77 &mut self,
78 _: Option<&GlobalElementId>,
79 _: Option<&gpui::InspectorElementId>,
80 bounds: Bounds<Pixels>,
81 _: &mut Self::RequestLayoutState,
82 window: &mut Window,
83 _: &mut App,
84 ) -> Self::PrepaintState {
85 let cover_bounds = Bounds {
87 origin: Point {
88 x: bounds.origin.x,
89 y: bounds.origin.y - bounds.size.height,
90 },
91 size: bounds.size,
92 };
93
94 window.insert_hitbox(cover_bounds, gpui::HitboxBehavior::Normal)
95 }
96
97 fn paint(
98 &mut self,
99 _: Option<&GlobalElementId>,
100 _: Option<&gpui::InspectorElementId>,
101 _: Bounds<Pixels>,
102 _: &mut Self::RequestLayoutState,
103 hitbox: &mut Self::PrepaintState,
104 window: &mut Window,
105 _: &mut App,
106 ) {
107 let is_horizontal = self.axis.is_horizontal();
108 let line_height = window.line_height();
109 let bounds = hitbox.bounds;
110
111 window.with_content_mask(Some(ContentMask { bounds }), |window| {
112 if let Some(color) = self.debug {
113 window.paint_quad(PaintQuad {
114 bounds,
115 border_widths: Edges::all(px(1.0)),
116 border_color: color,
117 background: gpui::transparent_white().into(),
118 corner_radii: Corners::all(px(0.)),
119 border_style: BorderStyle::default(),
120 });
121 }
122
123 window.on_mouse_event({
124 let view_id = window.current_view();
125 let scroll_handle = self.scroll_handle.clone();
126
127 move |event: &ScrollWheelEvent, phase, _, cx| {
128 if !(bounds.contains(&event.position) && phase.bubble()) {
129 return;
130 }
131
132 let mut offset = scroll_handle.offset();
133 let mut delta = event.delta.pixel_delta(line_height);
134
135 if !delta.x.is_zero() && !delta.y.is_zero() {
139 if delta.x.abs() > delta.y.abs() {
140 delta.y = px(0.);
141 } else {
142 delta.x = px(0.);
143 }
144 }
145
146 if is_horizontal {
147 offset.x += delta.x;
148 } else {
149 offset.y += delta.y;
150 }
151
152 if offset != scroll_handle.offset() {
153 scroll_handle.set_offset(offset);
154 cx.notify(view_id);
155 cx.stop_propagation();
156 }
157 }
158 });
159 });
160 }
161}