From 1752d3e2b44bc00312fcfd4d1dad47fd671f9126 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 18 Jun 2025 16:00:35 +0800 Subject: [PATCH] chore: Improve Scrollbar and Scrollable to support `Axis` and `ScrollbarAxis`. (#983) --- crates/story/src/scrollable_story.rs | 12 +++--- crates/ui/src/drawer.rs | 9 ++-- crates/ui/src/modal.rs | 9 ++-- crates/ui/src/scroll/scrollable.rs | 40 +++++++++--------- crates/ui/src/scroll/scrollbar.rs | 62 +++++++++++++++++----------- crates/ui/src/styled.rs | 2 +- 6 files changed, 72 insertions(+), 62 deletions(-) diff --git a/crates/story/src/scrollable_story.rs b/crates/story/src/scrollable_story.rs index 57441ec2..68456941 100644 --- a/crates/story/src/scrollable_story.rs +++ b/crates/story/src/scrollable_story.rs @@ -1,8 +1,8 @@ use std::rc::Rc; use gpui::{ - div, px, size, App, AppContext, Context, Entity, Focusable, InteractiveElement, IntoElement, - ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window, + div, px, size, App, AppContext, Axis, Context, Entity, Focusable, InteractiveElement, + IntoElement, ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window, }; use gpui_component::{ button::{Button, ButtonGroup}, @@ -144,17 +144,17 @@ impl ScrollableStory { .child( Button::new("test-axis-both") .label("Both Scrollbar") - .selected(self.axis == ScrollbarAxis::Both), + .selected(self.axis.is_both()), ) .child( Button::new("test-axis-vertical") .label("Vertical") - .selected(self.axis == ScrollbarAxis::Vertical), + .selected(self.axis.is_vertical()), ) .child( Button::new("test-axis-horizontal") .label("Horizontal") - .selected(self.axis == ScrollbarAxis::Horizontal), + .selected(self.axis.is_horizontal()), ) .on_click(cx.listener(|view, clicks: &Vec, _, cx| { if clicks.contains(&0) { @@ -298,7 +298,7 @@ impl Render for ScrollableStory { .p_3() .w(self.test_width) .id("test-1") - .scrollable(cx.entity().entity_id(), ScrollbarAxis::Vertical) + .scrollable(cx.entity().entity_id(), Axis::Vertical) .gap_1() .child("Scrollable Example") .children(self.items.iter().take(500).map(|item| { diff --git a/crates/ui/src/drawer.rs b/crates/ui/src/drawer.rs index ed42aaee..9bde6d79 100644 --- a/crates/ui/src/drawer.rs +++ b/crates/ui/src/drawer.rs @@ -2,9 +2,9 @@ use std::{rc::Rc, time::Duration}; use gpui::{ anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, - AnyElement, App, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, FocusHandle, - InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, - RenderOnce, Styled, Window, + AnyElement, App, Axis, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, + FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, + Pixels, RenderOnce, Styled, Window, }; use crate::{ @@ -13,7 +13,6 @@ use crate::{ h_flex, modal::overlay_color, root::ContextModal as _, - scroll::ScrollbarAxis, title_bar::TITLE_BAR_HEIGHT, v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, }; @@ -205,7 +204,7 @@ impl RenderOnce for Drawer { // Body div().flex_1().overflow_hidden().child( v_flex() - .scrollable(window.current_view(), ScrollbarAxis::Vertical) + .scrollable(window.current_view(), Axis::Vertical) .child(self.content), ), ) diff --git a/crates/ui/src/modal.rs b/crates/ui/src/modal.rs index 14211c1e..b51bba48 100644 --- a/crates/ui/src/modal.rs +++ b/crates/ui/src/modal.rs @@ -2,8 +2,8 @@ use std::{rc::Rc, time::Duration}; use gpui::{ anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _, - AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement, IntoElement, - KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, + AnyElement, App, Axis, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement, + IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled, Window, }; use rust_i18n::t; @@ -475,10 +475,7 @@ impl RenderOnce for Modal { v_flex() .pl(padding_left) .pr(padding_right) - .scrollable( - window.current_view(), - crate::scroll::ScrollbarAxis::Vertical, - ) + .scrollable(window.current_view(), Axis::Vertical) .child(self.content), ), ) diff --git a/crates/ui/src/scroll/scrollable.rs b/crates/ui/src/scroll/scrollable.rs index d6fa1eaa..6c54f1ca 100644 --- a/crates/ui/src/scroll/scrollable.rs +++ b/crates/ui/src/scroll/scrollable.rs @@ -2,9 +2,10 @@ use std::{cell::Cell, rc::Rc}; use super::{Scrollbar, ScrollbarAxis, ScrollbarState}; use gpui::{ - canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId, - InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString, - Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window, + canvas, div, relative, AnyElement, App, Bounds, Div, Element, ElementId, EntityId, + GlobalElementId, InspectorElementId, InteractiveElement, Interactivity, IntoElement, LayoutId, + ParentElement, Pixels, Position, ScrollHandle, SharedString, Size, Stateful, + StatefulInteractiveElement, Style, StyleRefinement, Styled, Window, }; /// A scroll view is a container that allows the user to scroll through a large amount of content. @@ -21,9 +22,9 @@ impl Scrollable where E: Element, { - pub(crate) fn new(view_id: EntityId, element: E, axis: ScrollbarAxis) -> Self { + pub(crate) fn new(view_id: EntityId, element: E, axis: impl Into) -> Self { let id = ElementId::Name(SharedString::from(format!( - "ScrollView:{}-{:?}", + "scrollable-{}-{:?}", view_id, element.id(), ))); @@ -33,7 +34,7 @@ where _element: div().id("fake"), id, view_id, - axis, + axis: axis.into(), } } @@ -51,8 +52,8 @@ where } /// Set the axis of the scroll view. - pub fn set_axis(&mut self, axis: ScrollbarAxis) { - self.axis = axis; + pub fn set_axis(&mut self, axis: impl Into) { + self.axis = axis.into(); } fn with_element_state( @@ -117,7 +118,7 @@ impl InteractiveElement for Scrollable where E: Element + InteractiveElement, { - fn interactivity(&mut self) -> &mut gpui::Interactivity { + fn interactivity(&mut self) -> &mut Interactivity { if let Some(element) = &mut self.element { element.interactivity() } else { @@ -145,7 +146,7 @@ where type RequestLayoutState = AnyElement; type PrepaintState = ScrollViewState; - fn id(&self) -> Option { + fn id(&self) -> Option { Some(self.id.clone()) } @@ -155,11 +156,11 @@ where fn request_layout( &mut self, - id: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, + id: Option<&GlobalElementId>, + _: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { + ) -> (LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); style.flex_grow = 1.0; style.position = Position::Relative; @@ -168,7 +169,6 @@ where let axis = self.axis; let view_id = self.view_id; - let scroll_id = self.id.clone(); let content = self.element.take().map(|c| c.into_any_element()); @@ -208,8 +208,8 @@ where ), ) .into_any_element(); - let element_id = element.request_layout(window, cx); + let element_id = element.request_layout(window, cx); let layout_id = window.request_layout(style, vec![element_id], cx); (layout_id, element) @@ -218,8 +218,8 @@ where fn prepaint( &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, + _: Option<&GlobalElementId>, + _: Option<&InspectorElementId>, _: gpui::Bounds, element: &mut Self::RequestLayoutState, window: &mut Window, @@ -232,9 +232,9 @@ where fn paint( &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: gpui::Bounds, + _: Option<&GlobalElementId>, + _: Option<&InspectorElementId>, + _: Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, window: &mut Window, diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 37cf892e..7b9b8bd2 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -5,12 +5,12 @@ use std::{ time::{Duration, Instant}, }; -use crate::ActiveTheme; +use crate::{ActiveTheme, AxisExt}; use gpui::{ - fill, point, px, relative, size, App, BorderStyle, Bounds, ContentMask, Corner, CursorStyle, - Edges, Element, EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, Style, - UniformListScrollHandle, Window, + fill, point, px, relative, size, App, Axis, BorderStyle, Bounds, ContentMask, Corner, + CursorStyle, Edges, Element, EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, + ScrollWheelEvent, Style, UniformListScrollHandle, Window, }; use serde::{Deserialize, Serialize}; @@ -85,9 +85,9 @@ pub struct ScrollbarState(Rc>); #[derive(Debug, Clone, Copy)] pub struct ScrollbarStateInner { - hovered_axis: Option, - hovered_on_thumb: Option, - dragged_axis: Option, + hovered_axis: Option, + hovered_on_thumb: Option, + dragged_axis: Option, drag_pos: Point, last_scroll_offset: Point, last_scroll_time: Option, @@ -118,7 +118,7 @@ impl Deref for ScrollbarState { } impl ScrollbarStateInner { - fn with_drag_pos(&self, axis: ScrollbarAxis, pos: Point) -> Self { + fn with_drag_pos(&self, axis: Axis, pos: Point) -> Self { let mut state = *self; if axis.is_vertical() { state.drag_pos.y = pos.y; @@ -136,7 +136,7 @@ impl ScrollbarStateInner { state } - fn with_hovered(&self, axis: Option) -> Self { + fn with_hovered(&self, axis: Option) -> Self { let mut state = *self; state.hovered_axis = axis; if axis.is_some() { @@ -145,7 +145,7 @@ impl ScrollbarStateInner { state } - fn with_hovered_on_thumb(&self, axis: Option) -> Self { + fn with_hovered_on_thumb(&self, axis: Option) -> Self { let mut state = *self; state.hovered_on_thumb = axis; if self.is_scrollbar_visible() { @@ -201,14 +201,28 @@ pub enum ScrollbarAxis { Both, } +impl From for ScrollbarAxis { + fn from(axis: Axis) -> Self { + match axis { + Axis::Vertical => Self::Vertical, + Axis::Horizontal => Self::Horizontal, + } + } +} + impl ScrollbarAxis { - #[inline] - fn is_vertical(&self) -> bool { + /// Return true if the scrollbar axis is vertical. + pub fn is_vertical(&self) -> bool { matches!(self, Self::Vertical) } - #[inline] - fn is_both(&self) -> bool { + /// Return true if the scrollbar axis is horizontal. + pub fn is_horizontal(&self) -> bool { + matches!(self, Self::Horizontal) + } + + /// Return true if the scrollbar axis is both vertical and horizontal. + pub fn is_both(&self) -> bool { matches!(self, Self::Both) } @@ -223,13 +237,13 @@ impl ScrollbarAxis { } #[inline] - fn all(&self) -> Vec { + fn all(&self) -> Vec { match self { - Self::Vertical => vec![Self::Vertical], - Self::Horizontal => vec![Self::Horizontal], + Self::Vertical => vec![Axis::Vertical], + Self::Horizontal => vec![Axis::Horizontal], // This should keep Horizontal first, Vertical is the primary axis // if Vertical not need display, then Horizontal will not keep right margin. - Self::Both => vec![Self::Horizontal, Self::Vertical], + Self::Both => vec![Axis::Horizontal, Axis::Vertical], } } } @@ -252,14 +266,14 @@ impl Scrollbar { fn new( view_id: EntityId, state: ScrollbarState, - axis: ScrollbarAxis, + axis: impl Into, scroll_handle: impl ScrollHandleOffsetable + 'static, scroll_size: gpui::Size, ) -> Self { Self { view_id, state, - axis, + axis: axis.into(), scroll_size, scroll_handle: Rc::new(Box::new(scroll_handle)), max_fps: 120, @@ -337,8 +351,8 @@ impl Scrollbar { } /// Set scrollbar axis. - pub fn axis(mut self, axis: ScrollbarAxis) -> Self { - self.axis = axis; + pub fn axis(mut self, axis: impl Into) -> Self { + self.axis = axis.into(); self } @@ -416,7 +430,7 @@ pub struct PrepaintState { } pub struct AxisPrepaintState { - axis: ScrollbarAxis, + axis: Axis, bar_hitbox: Hitbox, bounds: Bounds, radius: Pixels, diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index 05be1c96..f1d223a8 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -144,7 +144,7 @@ pub trait StyledExt: Styled + Sized { /// /// Current this is only have a vertical scrollbar. #[inline] - fn scrollable(self, view_id: EntityId, axis: ScrollbarAxis) -> Scrollable + fn scrollable(self, view_id: EntityId, axis: impl Into) -> Scrollable where Self: Element, {