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,
|
kind: ScrollbarAxis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
enum ThumbState {
|
||||||
|
#[default]
|
||||||
|
Inactive,
|
||||||
|
Hover,
|
||||||
|
Dragging(Pixels),
|
||||||
|
}
|
||||||
|
|
||||||
impl ScrollableHandle for UniformListScrollHandle {
|
impl ScrollableHandle for UniformListScrollHandle {
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.0.borrow().base_handle.content_size()
|
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.
|
/// A scrollbar state that should be persisted across frames.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ScrollbarState {
|
pub struct ScrollbarState {
|
||||||
// If Some(), there's an active drag, offset by percentage from the origin of a thumb.
|
thumb_state: Rc<Cell<ThumbState>>,
|
||||||
drag: Rc<Cell<Option<Pixels>>>,
|
|
||||||
parent_id: Option<EntityId>,
|
parent_id: Option<EntityId>,
|
||||||
scroll_handle: Arc<dyn ScrollableHandle>,
|
scroll_handle: Arc<dyn ScrollableHandle>,
|
||||||
}
|
}
|
||||||
|
@ -97,7 +104,7 @@ pub struct ScrollbarState {
|
||||||
impl ScrollbarState {
|
impl ScrollbarState {
|
||||||
pub fn new(scroll: impl ScrollableHandle) -> Self {
|
pub fn new(scroll: impl ScrollableHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
drag: Default::default(),
|
thumb_state: Default::default(),
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
scroll_handle: Arc::new(scroll),
|
scroll_handle: Arc::new(scroll),
|
||||||
}
|
}
|
||||||
|
@ -114,7 +121,24 @@ impl ScrollbarState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dragging(&self) -> bool {
|
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>> {
|
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| {
|
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||||
let axis = self.kind;
|
let axis = self.kind;
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
let thumb_background = colors
|
let thumb_base_color = match self.state.thumb_state.get() {
|
||||||
.surface_background
|
ThumbState::Dragging(_) => colors.scrollbar_thumb_active_background,
|
||||||
.blend(colors.scrollbar_thumb_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(
|
let padded_bounds = Bounds::from_corners(
|
||||||
bounds
|
bounds
|
||||||
|
@ -302,11 +330,9 @@ impl Element for Scrollbar {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll.drag_started();
|
|
||||||
|
|
||||||
if thumb_bounds.contains(&event.position) {
|
if thumb_bounds.contains(&event.position) {
|
||||||
let offset = event.position.along(axis) - thumb_bounds.origin.along(axis);
|
let offset = event.position.along(axis) - thumb_bounds.origin.along(axis);
|
||||||
state.drag.set(Some(offset));
|
state.set_dragging(offset);
|
||||||
} else {
|
} else {
|
||||||
let click_offset = compute_click_offset(
|
let click_offset = compute_click_offset(
|
||||||
event.position,
|
event.position,
|
||||||
|
@ -332,26 +358,29 @@ impl Element for Scrollbar {
|
||||||
|
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
window.on_mouse_event(move |event: &MouseMoveEvent, _, window, cx| {
|
window.on_mouse_event(move |event: &MouseMoveEvent, _, window, cx| {
|
||||||
if let Some(drag_state) = state.drag.get().filter(|_| event.dragging()) {
|
match state.thumb_state.get() {
|
||||||
let drag_offset = compute_click_offset(
|
ThumbState::Dragging(drag_state) if event.dragging() => {
|
||||||
event.position,
|
let drag_offset = compute_click_offset(
|
||||||
scroll.content_size(),
|
event.position,
|
||||||
ScrollbarMouseEvent::ThumbDrag(drag_state),
|
scroll.content_size(),
|
||||||
);
|
ScrollbarMouseEvent::ThumbDrag(drag_state),
|
||||||
scroll.set_offset(scroll.offset().apply_along(axis, |_| drag_offset));
|
);
|
||||||
window.refresh();
|
scroll.set_offset(scroll.offset().apply_along(axis, |_| drag_offset));
|
||||||
if let Some(id) = state.parent_id {
|
window.refresh();
|
||||||
cx.notify(id);
|
if let Some(id) = state.parent_id {
|
||||||
|
cx.notify(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
_ => state.set_thumb_hovered(thumb_bounds.contains(&event.position)),
|
||||||
state.drag.set(None);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let scroll = self.state.scroll_handle.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() {
|
if phase.bubble() {
|
||||||
state.drag.take();
|
if state.is_dragging() {
|
||||||
|
state.set_thumb_hovered(thumb_bounds.contains(&event.position));
|
||||||
|
}
|
||||||
scroll.drag_ended();
|
scroll.drag_ended();
|
||||||
if let Some(id) = state.parent_id {
|
if let Some(id) = state.parent_id {
|
||||||
cx.notify(id);
|
cx.notify(id);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue