chore: Improve Scrollbar and Scrollable to support Axis and ScrollbarAxis. (#983)

This commit is contained in:
Jason Lee 2025-06-18 16:00:35 +08:00 committed by GitHub
parent c5fed17941
commit 1752d3e2b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 62 deletions

View file

@ -1,8 +1,8 @@
use std::rc::Rc; use std::rc::Rc;
use gpui::{ use gpui::{
div, px, size, App, AppContext, Context, Entity, Focusable, InteractiveElement, IntoElement, div, px, size, App, AppContext, Axis, Context, Entity, Focusable, InteractiveElement,
ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window, IntoElement, ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window,
}; };
use gpui_component::{ use gpui_component::{
button::{Button, ButtonGroup}, button::{Button, ButtonGroup},
@ -144,17 +144,17 @@ impl ScrollableStory {
.child( .child(
Button::new("test-axis-both") Button::new("test-axis-both")
.label("Both Scrollbar") .label("Both Scrollbar")
.selected(self.axis == ScrollbarAxis::Both), .selected(self.axis.is_both()),
) )
.child( .child(
Button::new("test-axis-vertical") Button::new("test-axis-vertical")
.label("Vertical") .label("Vertical")
.selected(self.axis == ScrollbarAxis::Vertical), .selected(self.axis.is_vertical()),
) )
.child( .child(
Button::new("test-axis-horizontal") Button::new("test-axis-horizontal")
.label("Horizontal") .label("Horizontal")
.selected(self.axis == ScrollbarAxis::Horizontal), .selected(self.axis.is_horizontal()),
) )
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| { .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
if clicks.contains(&0) { if clicks.contains(&0) {
@ -298,7 +298,7 @@ impl Render for ScrollableStory {
.p_3() .p_3()
.w(self.test_width) .w(self.test_width)
.id("test-1") .id("test-1")
.scrollable(cx.entity().entity_id(), ScrollbarAxis::Vertical) .scrollable(cx.entity().entity_id(), Axis::Vertical)
.gap_1() .gap_1()
.child("Scrollable Example") .child("Scrollable Example")
.children(self.items.iter().take(500).map(|item| { .children(self.items.iter().take(500).map(|item| {

View file

@ -2,9 +2,9 @@ use std::{rc::Rc, time::Duration};
use gpui::{ use gpui::{
anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _,
AnyElement, App, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, FocusHandle, AnyElement, App, Axis, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter,
InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement,
RenderOnce, Styled, Window, Pixels, RenderOnce, Styled, Window,
}; };
use crate::{ use crate::{
@ -13,7 +13,6 @@ use crate::{
h_flex, h_flex,
modal::overlay_color, modal::overlay_color,
root::ContextModal as _, root::ContextModal as _,
scroll::ScrollbarAxis,
title_bar::TITLE_BAR_HEIGHT, title_bar::TITLE_BAR_HEIGHT,
v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _,
}; };
@ -205,7 +204,7 @@ impl RenderOnce for Drawer {
// Body // Body
div().flex_1().overflow_hidden().child( div().flex_1().overflow_hidden().child(
v_flex() v_flex()
.scrollable(window.current_view(), ScrollbarAxis::Vertical) .scrollable(window.current_view(), Axis::Vertical)
.child(self.content), .child(self.content),
), ),
) )

View file

@ -2,8 +2,8 @@ use std::{rc::Rc, time::Duration};
use gpui::{ use gpui::{
anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _, anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _,
AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement, IntoElement, AnyElement, App, Axis, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement,
KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
StyleRefinement, Styled, Window, StyleRefinement, Styled, Window,
}; };
use rust_i18n::t; use rust_i18n::t;
@ -475,10 +475,7 @@ impl RenderOnce for Modal {
v_flex() v_flex()
.pl(padding_left) .pl(padding_left)
.pr(padding_right) .pr(padding_right)
.scrollable( .scrollable(window.current_view(), Axis::Vertical)
window.current_view(),
crate::scroll::ScrollbarAxis::Vertical,
)
.child(self.content), .child(self.content),
), ),
) )

View file

@ -2,9 +2,10 @@ use std::{cell::Cell, rc::Rc};
use super::{Scrollbar, ScrollbarAxis, ScrollbarState}; use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
use gpui::{ use gpui::{
canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId, canvas, div, relative, AnyElement, App, Bounds, Div, Element, ElementId, EntityId,
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString, GlobalElementId, InspectorElementId, InteractiveElement, Interactivity, IntoElement, LayoutId,
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window, 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. /// A scroll view is a container that allows the user to scroll through a large amount of content.
@ -21,9 +22,9 @@ impl<E> Scrollable<E>
where where
E: Element, 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<ScrollbarAxis>) -> Self {
let id = ElementId::Name(SharedString::from(format!( let id = ElementId::Name(SharedString::from(format!(
"ScrollView:{}-{:?}", "scrollable-{}-{:?}",
view_id, view_id,
element.id(), element.id(),
))); )));
@ -33,7 +34,7 @@ where
_element: div().id("fake"), _element: div().id("fake"),
id, id,
view_id, view_id,
axis, axis: axis.into(),
} }
} }
@ -51,8 +52,8 @@ where
} }
/// Set the axis of the scroll view. /// Set the axis of the scroll view.
pub fn set_axis(&mut self, axis: ScrollbarAxis) { pub fn set_axis(&mut self, axis: impl Into<ScrollbarAxis>) {
self.axis = axis; self.axis = axis.into();
} }
fn with_element_state<R>( fn with_element_state<R>(
@ -117,7 +118,7 @@ impl<E> InteractiveElement for Scrollable<E>
where where
E: Element + InteractiveElement, E: Element + InteractiveElement,
{ {
fn interactivity(&mut self) -> &mut gpui::Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
if let Some(element) = &mut self.element { if let Some(element) = &mut self.element {
element.interactivity() element.interactivity()
} else { } else {
@ -145,7 +146,7 @@ where
type RequestLayoutState = AnyElement; type RequestLayoutState = AnyElement;
type PrepaintState = ScrollViewState; type PrepaintState = ScrollViewState;
fn id(&self) -> Option<gpui::ElementId> { fn id(&self) -> Option<ElementId> {
Some(self.id.clone()) Some(self.id.clone())
} }
@ -155,11 +156,11 @@ where
fn request_layout( fn request_layout(
&mut self, &mut self,
id: Option<&gpui::GlobalElementId>, id: Option<&GlobalElementId>,
_: Option<&gpui::InspectorElementId>, _: Option<&InspectorElementId>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default(); let mut style = Style::default();
style.flex_grow = 1.0; style.flex_grow = 1.0;
style.position = Position::Relative; style.position = Position::Relative;
@ -168,7 +169,6 @@ where
let axis = self.axis; let axis = self.axis;
let view_id = self.view_id; let view_id = self.view_id;
let scroll_id = self.id.clone(); let scroll_id = self.id.clone();
let content = self.element.take().map(|c| c.into_any_element()); let content = self.element.take().map(|c| c.into_any_element());
@ -208,8 +208,8 @@ where
), ),
) )
.into_any_element(); .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); let layout_id = window.request_layout(style, vec![element_id], cx);
(layout_id, element) (layout_id, element)
@ -218,8 +218,8 @@ where
fn prepaint( fn prepaint(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&GlobalElementId>,
_: Option<&gpui::InspectorElementId>, _: Option<&InspectorElementId>,
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
window: &mut Window, window: &mut Window,
@ -232,9 +232,9 @@ where
fn paint( fn paint(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&GlobalElementId>,
_: Option<&gpui::InspectorElementId>, _: Option<&InspectorElementId>,
_: gpui::Bounds<Pixels>, _: Bounds<Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
window: &mut Window, window: &mut Window,

View file

@ -5,12 +5,12 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::ActiveTheme; use crate::{ActiveTheme, AxisExt};
use gpui::{ use gpui::{
fill, point, px, relative, size, App, BorderStyle, Bounds, ContentMask, Corner, CursorStyle, fill, point, px, relative, size, App, Axis, BorderStyle, Bounds, ContentMask, Corner,
Edges, Element, EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, CursorStyle, Edges, Element, EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent,
MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, Style, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle,
UniformListScrollHandle, Window, ScrollWheelEvent, Style, UniformListScrollHandle, Window,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -85,9 +85,9 @@ pub struct ScrollbarState(Rc<Cell<ScrollbarStateInner>>);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ScrollbarStateInner { pub struct ScrollbarStateInner {
hovered_axis: Option<ScrollbarAxis>, hovered_axis: Option<Axis>,
hovered_on_thumb: Option<ScrollbarAxis>, hovered_on_thumb: Option<Axis>,
dragged_axis: Option<ScrollbarAxis>, dragged_axis: Option<Axis>,
drag_pos: Point<Pixels>, drag_pos: Point<Pixels>,
last_scroll_offset: Point<Pixels>, last_scroll_offset: Point<Pixels>,
last_scroll_time: Option<Instant>, last_scroll_time: Option<Instant>,
@ -118,7 +118,7 @@ impl Deref for ScrollbarState {
} }
impl ScrollbarStateInner { impl ScrollbarStateInner {
fn with_drag_pos(&self, axis: ScrollbarAxis, pos: Point<Pixels>) -> Self { fn with_drag_pos(&self, axis: Axis, pos: Point<Pixels>) -> Self {
let mut state = *self; let mut state = *self;
if axis.is_vertical() { if axis.is_vertical() {
state.drag_pos.y = pos.y; state.drag_pos.y = pos.y;
@ -136,7 +136,7 @@ impl ScrollbarStateInner {
state state
} }
fn with_hovered(&self, axis: Option<ScrollbarAxis>) -> Self { fn with_hovered(&self, axis: Option<Axis>) -> Self {
let mut state = *self; let mut state = *self;
state.hovered_axis = axis; state.hovered_axis = axis;
if axis.is_some() { if axis.is_some() {
@ -145,7 +145,7 @@ impl ScrollbarStateInner {
state state
} }
fn with_hovered_on_thumb(&self, axis: Option<ScrollbarAxis>) -> Self { fn with_hovered_on_thumb(&self, axis: Option<Axis>) -> Self {
let mut state = *self; let mut state = *self;
state.hovered_on_thumb = axis; state.hovered_on_thumb = axis;
if self.is_scrollbar_visible() { if self.is_scrollbar_visible() {
@ -201,14 +201,28 @@ pub enum ScrollbarAxis {
Both, Both,
} }
impl From<Axis> for ScrollbarAxis {
fn from(axis: Axis) -> Self {
match axis {
Axis::Vertical => Self::Vertical,
Axis::Horizontal => Self::Horizontal,
}
}
}
impl ScrollbarAxis { impl ScrollbarAxis {
#[inline] /// Return true if the scrollbar axis is vertical.
fn is_vertical(&self) -> bool { pub fn is_vertical(&self) -> bool {
matches!(self, Self::Vertical) matches!(self, Self::Vertical)
} }
#[inline] /// Return true if the scrollbar axis is horizontal.
fn is_both(&self) -> bool { 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) matches!(self, Self::Both)
} }
@ -223,13 +237,13 @@ impl ScrollbarAxis {
} }
#[inline] #[inline]
fn all(&self) -> Vec<ScrollbarAxis> { fn all(&self) -> Vec<Axis> {
match self { match self {
Self::Vertical => vec![Self::Vertical], Self::Vertical => vec![Axis::Vertical],
Self::Horizontal => vec![Self::Horizontal], Self::Horizontal => vec![Axis::Horizontal],
// This should keep Horizontal first, Vertical is the primary axis // This should keep Horizontal first, Vertical is the primary axis
// if Vertical not need display, then Horizontal will not keep right margin. // 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( fn new(
view_id: EntityId, view_id: EntityId,
state: ScrollbarState, state: ScrollbarState,
axis: ScrollbarAxis, axis: impl Into<ScrollbarAxis>,
scroll_handle: impl ScrollHandleOffsetable + 'static, scroll_handle: impl ScrollHandleOffsetable + 'static,
scroll_size: gpui::Size<Pixels>, scroll_size: gpui::Size<Pixels>,
) -> Self { ) -> Self {
Self { Self {
view_id, view_id,
state, state,
axis, axis: axis.into(),
scroll_size, scroll_size,
scroll_handle: Rc::new(Box::new(scroll_handle)), scroll_handle: Rc::new(Box::new(scroll_handle)),
max_fps: 120, max_fps: 120,
@ -337,8 +351,8 @@ impl Scrollbar {
} }
/// Set scrollbar axis. /// Set scrollbar axis.
pub fn axis(mut self, axis: ScrollbarAxis) -> Self { pub fn axis(mut self, axis: impl Into<ScrollbarAxis>) -> Self {
self.axis = axis; self.axis = axis.into();
self self
} }
@ -416,7 +430,7 @@ pub struct PrepaintState {
} }
pub struct AxisPrepaintState { pub struct AxisPrepaintState {
axis: ScrollbarAxis, axis: Axis,
bar_hitbox: Hitbox, bar_hitbox: Hitbox,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
radius: Pixels, radius: Pixels,

View file

@ -144,7 +144,7 @@ pub trait StyledExt: Styled + Sized {
/// ///
/// Current this is only have a vertical scrollbar. /// Current this is only have a vertical scrollbar.
#[inline] #[inline]
fn scrollable(self, view_id: EntityId, axis: ScrollbarAxis) -> Scrollable<Self> fn scrollable(self, view_id: EntityId, axis: impl Into<ScrollbarAxis>) -> Scrollable<Self>
where where
Self: Element, Self: Element,
{ {