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 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<usize>, _, 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| {

View file

@ -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),
),
)

View file

@ -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),
),
)

View file

@ -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<E> Scrollable<E>
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<ScrollbarAxis>) -> 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<ScrollbarAxis>) {
self.axis = axis.into();
}
fn with_element_state<R>(
@ -117,7 +118,7 @@ impl<E> InteractiveElement for Scrollable<E>
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<gpui::ElementId> {
fn id(&self) -> Option<ElementId> {
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<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@ -232,9 +232,9 @@ where
fn paint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
_: gpui::Bounds<Pixels>,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
window: &mut Window,

View file

@ -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<Cell<ScrollbarStateInner>>);
#[derive(Debug, Clone, Copy)]
pub struct ScrollbarStateInner {
hovered_axis: Option<ScrollbarAxis>,
hovered_on_thumb: Option<ScrollbarAxis>,
dragged_axis: Option<ScrollbarAxis>,
hovered_axis: Option<Axis>,
hovered_on_thumb: Option<Axis>,
dragged_axis: Option<Axis>,
drag_pos: Point<Pixels>,
last_scroll_offset: Point<Pixels>,
last_scroll_time: Option<Instant>,
@ -118,7 +118,7 @@ impl Deref for ScrollbarState {
}
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;
if axis.is_vertical() {
state.drag_pos.y = pos.y;
@ -136,7 +136,7 @@ impl ScrollbarStateInner {
state
}
fn with_hovered(&self, axis: Option<ScrollbarAxis>) -> Self {
fn with_hovered(&self, axis: Option<Axis>) -> 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<ScrollbarAxis>) -> Self {
fn with_hovered_on_thumb(&self, axis: Option<Axis>) -> 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<Axis> 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<ScrollbarAxis> {
fn all(&self) -> Vec<Axis> {
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<ScrollbarAxis>,
scroll_handle: impl ScrollHandleOffsetable + 'static,
scroll_size: gpui::Size<Pixels>,
) -> 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<ScrollbarAxis>) -> 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<Pixels>,
radius: Pixels,

View file

@ -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<Self>
fn scrollable(self, view_id: EntityId, axis: impl Into<ScrollbarAxis>) -> Scrollable<Self>
where
Self: Element,
{