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:
parent
bc4e5732a0
commit
2e1a4f84fd
2 changed files with 93 additions and 41 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue