From 5c8d0d7eb42a8635d2fa2c6a52610f3274231a7e Mon Sep 17 00:00:00 2001 From: xda <150917089+xda2023@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:53:53 +0900 Subject: [PATCH] scrollbar: Do not show scrollbar event mouse is in Element rect, only show when hovered on scrollbar area or scrolled (#333) Let the scrollbar only display when hovering and scrolling. The effect comparison is on the left GPUI and on the right mac. I guess they used the 10th power ease-in function, and the effect is similar. Fixes https://github.com/huacnlee/gpui-component/issues/260 ![2024-10-11 22 06 51](https://github.com/user-attachments/assets/4fa35150-2f93-4b79-bdbb-d8b7783e4b5a) --- crates/ui/src/scroll/scrollbar.rs | 148 +++++++++++++++--------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 1328260b..4096f8d2 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -1,10 +1,10 @@ -use std::{cell::Cell, rc::Rc}; +use std::{cell::Cell, rc::Rc, time::Instant}; use crate::theme::ActiveTheme; use gpui::{ fill, point, px, relative, Bounds, ContentMask, Edges, Element, EntityId, Hitbox, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, - Style, UniformListScrollHandle, + ScrollWheelEvent, Style, UniformListScrollHandle, }; const MIN_THUMB_SIZE: f32 = 80.; @@ -48,7 +48,8 @@ pub struct ScrollbarState { hovered_axis: Option, dragged_axis: Option, drag_pos: Point, - visible: bool, + last_scroll_offset: Point, + last_scroll_time: Option, } impl Default for ScrollbarState { @@ -57,7 +58,8 @@ impl Default for ScrollbarState { hovered_axis: None, dragged_axis: None, drag_pos: point(px(0.), px(0.)), - visible: false, + last_scroll_offset: point(px(0.), px(0.)), + last_scroll_time: None, } } } @@ -91,9 +93,14 @@ impl ScrollbarState { state } - fn with_visiable(&self, visiable: bool) -> Self { + fn with_last_scroll( + &self, + last_scroll_offset: Point, + last_scroll_time: Option, + ) -> Self { let mut state = *self; - state.visible = visiable; + state.last_scroll_offset = last_scroll_offset; + state.last_scroll_time = last_scroll_time; state } } @@ -365,34 +372,28 @@ impl Element for Scrollbar { }, }; - let thumb_bg = cx.theme().scrollbar_thumb; let state = self.state.clone(); - let (thumb_bg, bar_bg, bar_border, inset, radius) = - if state.get().dragged_axis == Some(axis) { - ( - thumb_bg, - cx.theme().scrollbar, - cx.theme().border, - THUMB_INSET - px(1.), - THUMB_RADIUS, - ) - } else if state.get().hovered_axis == Some(axis) { - ( - thumb_bg, - cx.theme().scrollbar, - cx.theme().border, - THUMB_INSET - px(1.), - THUMB_RADIUS, - ) - } else { - ( - thumb_bg.opacity(0.3), - cx.theme().transparent, - gpui::transparent_black(), - THUMB_INSET, - THUMB_RADIUS - px(1.), - ) - }; + let mut thumb_bg = cx.theme().transparent; + let mut bar_bg = cx.theme().transparent; + + if state.get().dragged_axis == Some(axis) + || state.get().hovered_axis == Some(axis) + { + thumb_bg = cx.theme().scrollbar_thumb; + bar_bg = cx.theme().scrollbar; + } else { + if let Some(last_time) = state.get().last_scroll_time { + let elapsed = Instant::now().duration_since(last_time).as_secs_f32(); + if elapsed < 1.0 { + thumb_bg = + cx.theme().scrollbar_thumb.opacity(1.0 - elapsed.powi(10)); + cx.request_animation_frame(); + } + } + } + let bar_border = cx.theme().border; + let inset = THUMB_INSET - px(1.); + let radius = THUMB_RADIUS; let border_width = px(0.); let thumb_bounds = if is_vertical { @@ -419,33 +420,49 @@ impl Element for Scrollbar { ) }; - 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: border_width, - right: px(0.), - bottom: px(0.), - left: px(0.), - } - }, - border_color: bar_border, - }); + 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: border_width, + right: px(0.), + bottom: px(0.), + left: px(0.), + } + }, + border_color: bar_border, + }); - cx.paint_quad(fill(thumb_bounds, thumb_bg).corner_radii(radius)); - } + cx.paint_quad(fill(thumb_bounds, thumb_bg).corner_radii(radius)); + + cx.on_mouse_event({ + let state = self.state.clone(); + let view_id = self.view_id; + let scroll_handle = self.scroll_handle.clone(); + + move |event: &ScrollWheelEvent, phase, cx| { + if phase.bubble() && hitbox_bounds.contains(&event.position) { + if scroll_handle.offset() != state.get().last_scroll_offset { + state.set(state.get().with_last_scroll( + scroll_handle.offset(), + Some(Instant::now()), + )); + cx.notify(view_id); + } + } + } + }); cx.on_mouse_event({ let state = self.state.clone(); @@ -514,21 +531,6 @@ impl Element for Scrollbar { } } - // If mouse out of the bounds, hide scrollbar - if hitbox_bounds.contains(&event.position) - || state.get().dragged_axis.is_some() - { - if !state.get().visible { - state.set(state.get().with_visiable(true)); - cx.notify(view_id); - } - } else { - if state.get().visible { - state.set(state.get().with_visiable(false)); - cx.notify(view_id); - } - } - // Move thumb position on dragging if state.get().dragged_axis == Some(axis) && event.dragging() { // drag_pos is the position of the mouse down event