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