Add to support click Scrollbar to scroll and fix thumb position when dragging. (#112)

- Add scrollbar border on hover and dragging.

https://github.com/user-attachments/assets/c7c3b9c1-d458-438d-bf78-3b5bc2ddcd5c
This commit is contained in:
Jason Lee 2024-08-06 15:09:42 +08:00 committed by GitHub
parent bc4e5732a0
commit 2e1a4f84fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 93 additions and 41 deletions

View file

@ -2,14 +2,14 @@ use std::{cell::Cell, rc::Rc};
use crate::theme::ActiveTheme; use crate::theme::ActiveTheme;
use gpui::{ use gpui::{
fill, point, px, relative, AnyView, Bounds, ContentMask, Element, Hitbox, IntoElement, fill, point, px, relative, size, AnyView, Bounds, ContentMask, Edges, Element, Hitbox,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Position, ScrollHandle, Style, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position,
UniformListScrollHandle, ScrollHandle, Style, UniformListScrollHandle,
}; };
const MIN_THUMB_SIZE: f32 = 80.; const MIN_THUMB_SIZE: f32 = 80.;
const THUMB_RADIUS: Pixels = Pixels(5.0); const THUMB_RADIUS: Pixels = Pixels(5.0);
const THUMB_INSET: Pixels = Pixels(2.5); const THUMB_INSET: Pixels = Pixels(2.);
pub trait ScrollHandleOffsetable { pub trait ScrollHandleOffsetable {
fn offset(&self) -> Point<Pixels>; fn offset(&self) -> Point<Pixels>;
@ -224,7 +224,7 @@ impl Scrollbar {
) -> Self { ) -> Self {
let last_item_height = scroll_handle.0.borrow().last_item_height.unwrap_or(px(10.)); let last_item_height = scroll_handle.0.borrow().last_item_height.unwrap_or(px(10.));
let max_height = items_count as f32 * last_item_height; let max_height = items_count as f32 * last_item_height;
let scroll_size = gpui::size(px(0.), max_height); let scroll_size = size(px(0.), max_height);
Self::new( Self::new(
view.into(), view.into(),
@ -365,33 +365,40 @@ impl Element for Scrollbar {
let thumb_bg = cx.theme().scrollbar_thumb; let thumb_bg = cx.theme().scrollbar_thumb;
let state = self.state.clone(); let state = self.state.clone();
let (thumb_bg, bar_bg, inset) = if state.get().dragged_axis == Some(axis) { let (thumb_bg, bar_bg, bar_border, inset) =
(thumb_bg, cx.theme().scrollbar, px(0.)) if state.get().dragged_axis == Some(axis) {
} else if state.get().hovered_axis == Some(axis) { (thumb_bg, cx.theme().scrollbar, cx.theme().border, px(1.))
(thumb_bg, cx.theme().scrollbar, px(0.)) } else if state.get().hovered_axis == Some(axis) {
} else { (thumb_bg, cx.theme().scrollbar, cx.theme().border, px(1.))
(thumb_bg, cx.theme().transparent, THUMB_INSET) } else {
}; (
thumb_bg,
cx.theme().transparent,
gpui::transparent_black(),
THUMB_INSET,
)
};
let border_width = px(1.);
let thumb_bounds = if is_vertical { let thumb_bounds = if is_vertical {
Bounds::from_corners( Bounds::from_corners(
point( point(
bounds.origin.x + inset, bounds.origin.x + inset + border_width,
bounds.origin.y + thumb_start + inset, bounds.origin.y + thumb_start,
), ),
point( point(
bounds.origin.x + self.width - inset, bounds.origin.x + self.width - inset,
bounds.origin.y + thumb_end - (inset * 2), bounds.origin.y + thumb_end,
), ),
) )
} else { } else {
Bounds::from_corners( Bounds::from_corners(
point( point(
bounds.origin.x + thumb_start + inset, bounds.origin.x + thumb_start,
bounds.origin.y + inset, bounds.origin.y + inset + border_width,
), ),
point( point(
bounds.origin.x + thumb_end - (inset * 2), bounds.origin.x + thumb_end,
bounds.origin.y + self.width - inset, bounds.origin.y + self.width - inset,
), ),
) )
@ -399,6 +406,29 @@ impl Element for Scrollbar {
if state.get().visible { if state.get().visible {
cx.paint_quad(fill(bounds, bar_bg)); cx.paint_quad(fill(bounds, bar_bg));
cx.paint_quad(PaintQuad {
bounds,
corner_radii: (0.).into(),
background: gpui::transparent_black(),
border_widths: if is_vertical {
Edges {
top: px(0.),
right: px(0.),
bottom: px(0.),
left: border_width,
}
} else {
Edges {
top: px(1.),
right: px(0.),
bottom: px(0.),
left: border_width,
}
},
border_color: bar_border,
});
cx.paint_quad( cx.paint_quad(
fill(thumb_bounds, thumb_bg).corner_radii(THUMB_RADIUS - inset), fill(thumb_bounds, thumb_bg).corner_radii(THUMB_RADIUS - inset),
); );
@ -407,24 +437,43 @@ impl Element for Scrollbar {
cx.on_mouse_event({ cx.on_mouse_event({
let state = self.state.clone(); let state = self.state.clone();
let view_id = self.view.entity_id(); let view_id = self.view.entity_id();
let scroll_handle = self.scroll_handle.clone();
move |event: &MouseDownEvent, phase, cx| { move |event: &MouseDownEvent, phase, cx| {
if phase.bubble() && thumb_bounds.contains(&event.position) { if phase.bubble() && bounds.contains(&event.position) {
let drag_pos = if is_vertical {
point(
state.get().drag_pos.x,
event.position.y - thumb_bounds.origin.y,
)
} else {
point(
event.position.x - thumb_bounds.origin.x,
state.get().drag_pos.y,
)
};
cx.stop_propagation(); cx.stop_propagation();
state.set(state.get().with_drag_pos(axis, drag_pos));
cx.notify(view_id); if thumb_bounds.contains(&event.position) {
// click on the thumb bar, set the drag position
let pos = event.position - thumb_bounds.origin;
state.set(state.get().with_drag_pos(axis, pos));
cx.notify(view_id);
} else {
// click on the scrollbar, jump to the position
// Set the thumb bar center to the click position
let offset = scroll_handle.offset();
let percentage = if is_vertical {
(event.position.y - thumb_length / 2. - bounds.origin.y)
/ (bounds.size.height - thumb_length)
} else {
(event.position.x - thumb_length / 2. - bounds.origin.x)
/ (bounds.size.width - thumb_length)
}
.min(1.);
if is_vertical {
scroll_handle.set_offset(point(
offset.x,
-scroll_area_size * percentage,
));
} else {
scroll_handle.set_offset(point(
-scroll_area_size * percentage,
offset.y,
));
}
}
} }
} }
}); });
@ -435,7 +484,7 @@ impl Element for Scrollbar {
let view_id = self.view.entity_id(); let view_id = self.view.entity_id();
move |event: &MouseMoveEvent, _, cx| { move |event: &MouseMoveEvent, _, cx| {
if thumb_bounds.contains(&event.position) { if bounds.contains(&event.position) {
if state.get().hovered_axis != Some(axis) { if state.get().hovered_axis != Some(axis) {
state.set(state.get().with_hovered(Some(axis))); state.set(state.get().with_hovered(Some(axis)));
cx.notify(view_id); cx.notify(view_id);
@ -466,20 +515,23 @@ impl Element for Scrollbar {
// Move thumb position on dragging // Move thumb position on dragging
if state.get().dragged_axis == Some(axis) && event.dragging() { if state.get().dragged_axis == Some(axis) && event.dragging() {
// drag_pos is the position of the mouse down event
// We need to keep the thumb bar still at the origin down position
let drag_pos = state.get().drag_pos; let drag_pos = state.get().drag_pos;
let percentage = if is_vertical { let percentage = if is_vertical {
(event.position.y - bounds.origin.y - drag_pos.y) (event.position.y - drag_pos.y - bounds.origin.y)
/ container_size / (bounds.size.height - thumb_length)
} else { } else {
(event.position.x - bounds.origin.x - drag_pos.x) (event.position.x - drag_pos.x - bounds.origin.x)
/ container_size / (bounds.size.width - thumb_length)
} }
.min(1.); .min(1.);
let offset = if is_vertical { let offset = if is_vertical {
point(scroll_handle.offset().x, -percentage * scroll_area_size) point(scroll_handle.offset().x, -scroll_area_size * percentage)
} else { } else {
point(-percentage * scroll_area_size, scroll_handle.offset().y) point(-scroll_area_size * percentage, scroll_handle.offset().y)
}; };
scroll_handle.set_offset(offset); scroll_handle.set_offset(offset);

View file

@ -195,7 +195,7 @@ impl Colors {
input: hsl(240.0, 5.9, 90.0), input: hsl(240.0, 5.9, 90.0),
ring: hsl(240.0, 5.9, 65.0), ring: hsl(240.0, 5.9, 65.0),
selection: hsl(211.0, 97.0, 85.0), selection: hsl(211.0, 97.0, 85.0),
scrollbar: hsl(0., 0., 98.), scrollbar: hsl(0., 0., 97.),
scrollbar_thumb: hsl(0., 0., 49.), scrollbar_thumb: hsl(0., 0., 49.),
panel: hsl(0.0, 0.0, 100.0), panel: hsl(0.0, 0.0, 100.0),
tab_bar: hsl(240.0, 4.8, 95.9), tab_bar: hsl(240.0, 4.8, 95.9),