1use std::ops::Range;
2
3use gpui::{
4 point, Bounds, ContentMask, Hitbox, MouseDownEvent, MouseMoveEvent, ScrollWheelEvent, Style,
5 UniformListScrollHandle,
6};
7use ui::{prelude::*, px, relative, IntoElement};
8
9pub(crate) struct ProjectPanelScrollbar {
10 thumb: Range<f32>,
11 scroll: UniformListScrollHandle,
12 item_count: usize,
13}
14
15impl ProjectPanelScrollbar {
16 pub(crate) fn new(
17 thumb: Range<f32>,
18 scroll: UniformListScrollHandle,
19 item_count: usize,
20 ) -> Self {
21 Self {
22 thumb,
23 scroll,
24 item_count,
25 }
26 }
27}
28
29impl gpui::Element for ProjectPanelScrollbar {
30 type RequestLayoutState = ();
31
32 type PrepaintState = Hitbox;
33
34 fn id(&self) -> Option<ui::ElementId> {
35 None
36 }
37
38 fn request_layout(
39 &mut self,
40 _id: Option<&gpui::GlobalElementId>,
41 cx: &mut ui::WindowContext,
42 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
43 let mut style = Style::default();
44 style.flex_grow = 1.;
45 style.flex_shrink = 1.;
46 style.size.width = px(12.).into();
47 style.size.height = relative(1.).into();
48 (cx.request_layout(style, None), ())
49 }
50
51 fn prepaint(
52 &mut self,
53 _id: Option<&gpui::GlobalElementId>,
54 bounds: Bounds<ui::Pixels>,
55 _request_layout: &mut Self::RequestLayoutState,
56 cx: &mut ui::WindowContext,
57 ) -> Self::PrepaintState {
58 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
59 cx.insert_hitbox(bounds, false)
60 })
61 }
62
63 fn paint(
64 &mut self,
65 _id: Option<&gpui::GlobalElementId>,
66 bounds: Bounds<ui::Pixels>,
67 _request_layout: &mut Self::RequestLayoutState,
68 _prepaint: &mut Self::PrepaintState,
69 cx: &mut ui::WindowContext,
70 ) {
71 let hitbox_id = _prepaint.id;
72 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
73 let colors = cx.theme().colors();
74 let scrollbar_background = colors.scrollbar_track_border;
75 let thumb_background = colors.scrollbar_thumb_background;
76 cx.paint_quad(gpui::fill(bounds, scrollbar_background));
77
78 let thumb_offset = self.thumb.start * bounds.size.height;
79 let thumb_end = self.thumb.end * bounds.size.height;
80 let thumb_upper_left = point(bounds.origin.x, bounds.origin.y + thumb_offset);
81 let thumb_lower_right = point(
82 bounds.origin.x + bounds.size.width,
83 bounds.origin.y + thumb_end,
84 );
85 let thumb_percentage_size = self.thumb.end - self.thumb.start;
86 cx.paint_quad(gpui::fill(
87 Bounds::from_corners(thumb_upper_left, thumb_lower_right),
88 thumb_background,
89 ));
90 let scroll = self.scroll.clone();
91 let item_count = self.item_count;
92 cx.on_mouse_event({
93 let scroll = self.scroll.clone();
94 move |event: &MouseDownEvent, phase, _cx| {
95 if phase.bubble() && bounds.contains(&event.position) {
96 let scroll = scroll.0.borrow();
97 if let Some(last_height) = scroll.last_item_height {
98 let max_offset = item_count as f32 * last_height;
99 let percentage =
100 (event.position.y - bounds.origin.y) / bounds.size.height;
101
102 let percentage = percentage.min(1. - thumb_percentage_size);
103 scroll
104 .base_handle
105 .set_offset(point(px(0.), -max_offset * percentage));
106 }
107 }
108 }
109 });
110 cx.on_mouse_event({
111 let scroll = self.scroll.clone();
112 move |event: &ScrollWheelEvent, phase, cx| {
113 if phase.bubble() && bounds.contains(&event.position) {
114 let scroll = scroll.0.borrow_mut();
115 let current_offset = scroll.base_handle.offset();
116 scroll
117 .base_handle
118 .set_offset(current_offset + event.delta.pixel_delta(cx.line_height()));
119 }
120 }
121 });
122
123 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
124 if phase.bubble() && bounds.contains(&event.position) && hitbox_id.is_hovered(cx) {
125 if event.dragging() {
126 let scroll = scroll.0.borrow();
127 if let Some(last_height) = scroll.last_item_height {
128 let max_offset = item_count as f32 * last_height;
129 let percentage =
130 (event.position.y - bounds.origin.y) / bounds.size.height;
131
132 let percentage = percentage.min(1. - thumb_percentage_size);
133 scroll
134 .base_handle
135 .set_offset(point(px(0.), -max_offset * percentage));
136 }
137 } else {
138 cx.stop_propagation();
139 }
140 }
141 });
142 })
143 }
144}
145
146impl IntoElement for ProjectPanelScrollbar {
147 type Element = Self;
148
149 fn into_element(self) -> Self::Element {
150 self
151 }
152}