ui: Implement hover color for scrollbar component (#25525)
This PR implements color changing for the scrollbar component based upon user mouse interaction. https://github.com/user-attachments/assets/2fd14e2d-cc5c-4272-906e-bd39bfb007e4 This PR also already adds the state for a scrollbar being actively dragged. However, as themes currently do not provide a color for this scenario, this implementation re-uses the hover color as a placeholder instead. If this feature is at all wanted, I can quickly open up a follow-up PR which adds support for that property to themes as well as this component. Release Notes: - Added hover state to scrollbars outside of the editor.
This commit is contained in:
parent
0145e2c101
commit
233b73b385
1 changed files with 53 additions and 24 deletions
|
@ -14,6 +14,14 @@ pub struct Scrollbar {
|
|||
kind: ScrollbarAxis,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
enum ThumbState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging(Pixels),
|
||||
}
|
||||
|
||||
impl ScrollableHandle for UniformListScrollHandle {
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.0.borrow().base_handle.content_size()
|
||||
|
@ -88,8 +96,7 @@ pub trait ScrollableHandle: Any + Debug {
|
|||
/// A scrollbar state that should be persisted across frames.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScrollbarState {
|
||||
// If Some(), there's an active drag, offset by percentage from the origin of a thumb.
|
||||
drag: Rc<Cell<Option<Pixels>>>,
|
||||
thumb_state: Rc<Cell<ThumbState>>,
|
||||
parent_id: Option<EntityId>,
|
||||
scroll_handle: Arc<dyn ScrollableHandle>,
|
||||
}
|
||||
|
@ -97,7 +104,7 @@ pub struct ScrollbarState {
|
|||
impl ScrollbarState {
|
||||
pub fn new(scroll: impl ScrollableHandle) -> Self {
|
||||
Self {
|
||||
drag: Default::default(),
|
||||
thumb_state: Default::default(),
|
||||
parent_id: None,
|
||||
scroll_handle: Arc::new(scroll),
|
||||
}
|
||||
|
@ -114,7 +121,24 @@ impl ScrollbarState {
|
|||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.drag.get().is_some()
|
||||
matches!(self.thumb_state.get(), ThumbState::Dragging(_))
|
||||
}
|
||||
|
||||
fn set_dragging(&self, drag_offset: Pixels) {
|
||||
self.set_thumb_state(ThumbState::Dragging(drag_offset));
|
||||
self.scroll_handle.drag_started();
|
||||
}
|
||||
|
||||
fn set_thumb_hovered(&self, hovered: bool) {
|
||||
self.set_thumb_state(if hovered {
|
||||
ThumbState::Hover
|
||||
} else {
|
||||
ThumbState::Inactive
|
||||
});
|
||||
}
|
||||
|
||||
fn set_thumb_state(&self, state: ThumbState) {
|
||||
self.thumb_state.set(state);
|
||||
}
|
||||
|
||||
fn thumb_range(&self, axis: ScrollbarAxis) -> Option<Range<f32>> {
|
||||
|
@ -222,9 +246,13 @@ impl Element for Scrollbar {
|
|||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
let axis = self.kind;
|
||||
let colors = cx.theme().colors();
|
||||
let thumb_background = colors
|
||||
.surface_background
|
||||
.blend(colors.scrollbar_thumb_background);
|
||||
let thumb_base_color = match self.state.thumb_state.get() {
|
||||
ThumbState::Dragging(_) => colors.scrollbar_thumb_active_background,
|
||||
ThumbState::Hover => colors.scrollbar_thumb_hover_background,
|
||||
ThumbState::Inactive => colors.scrollbar_thumb_background,
|
||||
};
|
||||
|
||||
let thumb_background = colors.surface_background.blend(thumb_base_color);
|
||||
|
||||
let padded_bounds = Bounds::from_corners(
|
||||
bounds
|
||||
|
@ -302,11 +330,9 @@ impl Element for Scrollbar {
|
|||
return;
|
||||
}
|
||||
|
||||
scroll.drag_started();
|
||||
|
||||
if thumb_bounds.contains(&event.position) {
|
||||
let offset = event.position.along(axis) - thumb_bounds.origin.along(axis);
|
||||
state.drag.set(Some(offset));
|
||||
state.set_dragging(offset);
|
||||
} else {
|
||||
let click_offset = compute_click_offset(
|
||||
event.position,
|
||||
|
@ -332,26 +358,29 @@ impl Element for Scrollbar {
|
|||
|
||||
let state = self.state.clone();
|
||||
window.on_mouse_event(move |event: &MouseMoveEvent, _, window, cx| {
|
||||
if let Some(drag_state) = state.drag.get().filter(|_| event.dragging()) {
|
||||
let drag_offset = compute_click_offset(
|
||||
event.position,
|
||||
scroll.content_size(),
|
||||
ScrollbarMouseEvent::ThumbDrag(drag_state),
|
||||
);
|
||||
scroll.set_offset(scroll.offset().apply_along(axis, |_| drag_offset));
|
||||
window.refresh();
|
||||
if let Some(id) = state.parent_id {
|
||||
cx.notify(id);
|
||||
match state.thumb_state.get() {
|
||||
ThumbState::Dragging(drag_state) if event.dragging() => {
|
||||
let drag_offset = compute_click_offset(
|
||||
event.position,
|
||||
scroll.content_size(),
|
||||
ScrollbarMouseEvent::ThumbDrag(drag_state),
|
||||
);
|
||||
scroll.set_offset(scroll.offset().apply_along(axis, |_| drag_offset));
|
||||
window.refresh();
|
||||
if let Some(id) = state.parent_id {
|
||||
cx.notify(id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.drag.set(None);
|
||||
_ => state.set_thumb_hovered(thumb_bounds.contains(&event.position)),
|
||||
}
|
||||
});
|
||||
let state = self.state.clone();
|
||||
let scroll = self.state.scroll_handle.clone();
|
||||
window.on_mouse_event(move |_event: &MouseUpEvent, phase, _, cx| {
|
||||
window.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
|
||||
if phase.bubble() {
|
||||
state.drag.take();
|
||||
if state.is_dragging() {
|
||||
state.set_thumb_hovered(thumb_bounds.contains(&event.position));
|
||||
}
|
||||
scroll.drag_ended();
|
||||
if let Some(id) = state.parent_id {
|
||||
cx.notify(id);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue