diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index fdeac8b8..2530ad50 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -55,9 +55,8 @@ mod welcome_story; use gpui::{ Action, AnyElement, AnyView, App, AppContext, Bounds, Context, Div, Entity, EventEmitter, Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, - Render, RenderOnce, SharedString, Size, StatefulInteractiveElement, StyleRefinement, Styled, - Window, WindowBounds, WindowKind, WindowOptions, actions, div, prelude::FluentBuilder as _, px, - rems, size, + Render, RenderOnce, SharedString, Size, StyleRefinement, Styled, Window, WindowBounds, + WindowKind, WindowOptions, actions, div, prelude::FluentBuilder as _, px, rems, size, }; pub use accordion_story::AccordionStory; @@ -121,7 +120,7 @@ use gpui_component::{ h_flex, menu::PopupMenu, notification::Notification, - scroll::ScrollbarShow, + scroll::{ScrollableElement as _, ScrollbarShow}, v_flex, }; use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _}; @@ -773,22 +772,14 @@ impl Focusable for StoryContainer { } impl Render for StoryContainer { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - v_flex() + div() .id("story-container") .size_full() - .overflow_y_scroll() + .overflow_y_scrollbar() + .p(self.paddings) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_action_panel_info)) .on_action(cx.listener(Self::on_action_toggle_search)) - .when_some(self.story.clone(), |this, story| { - this.child( - v_flex() - .id("story-children") - .w_full() - .flex_1() - .p(self.paddings) - .child(story), - ) - }) + .when_some(self.story.clone(), |this, story| this.child(story)) } } diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index b0456f28..9563b0ba 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -458,7 +458,6 @@ impl Render for ListStory { .child( h_flex() .gap_2() - .flex_wrap() .child( Button::new("scroll-top") .outline() diff --git a/crates/story/src/scrollable_story.rs b/crates/story/src/scrollable_story.rs index 719a2547..36b5bfc0 100644 --- a/crates/story/src/scrollable_story.rs +++ b/crates/story/src/scrollable_story.rs @@ -1,13 +1,15 @@ use std::rc::Rc; use gpui::{ - App, AppContext, Axis, Context, Entity, FocusHandle, Focusable, InteractiveElement, - IntoElement, ParentElement, Pixels, Render, Size, Styled, Window, div, px, size, + App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, + Render, Size, Styled, Window, div, px, size, }; use gpui_component::{ - ActiveTheme as _, Selectable, StyledExt as _, + ActiveTheme as _, Selectable, button::{Button, ButtonGroup}, - h_flex, v_flex, + h_flex, + scroll::ScrollableElement, + v_flex, }; pub struct ScrollableStory { @@ -152,13 +154,12 @@ impl Render for ScrollableStory { .min_h(px(200.)) .child( v_flex() - .p_3() .w(test_width) - .id("test-1") - .scrollable(Axis::Vertical) + .p_3() .gap_1() + .overflow_y_scrollbar() .child("Scrollable Example") - .children(self.items.iter().take(500).map(|item| { + .children(self.items.iter().map(|item| { div() .h(ITEM_HEIGHT) .bg(cx.theme().background) diff --git a/crates/story/src/virtual_list_story.rs b/crates/story/src/virtual_list_story.rs index 497cc35e..a2c38201 100644 --- a/crates/story/src/virtual_list_story.rs +++ b/crates/story/src/virtual_list_story.rs @@ -9,7 +9,7 @@ use gpui_component::{ button::{Button, ButtonGroup}, divider::Divider, h_flex, - scroll::{Scrollbar, ScrollbarAxis}, + scroll::{ScrollableElement, ScrollbarAxis}, v_flex, v_virtual_list, }; @@ -282,15 +282,7 @@ impl Render for VirtualListStory { .border_color(cx.theme().border) .gap_1(), ) - .child({ - div() - .absolute() - .top_0() - .left_0() - .right_0() - .bottom_0() - .child(Scrollbar::new(&self.scroll_handle).axis(self.axis)) - }), + .scrollbar(&self.scroll_handle, self.axis), ), ), ) diff --git a/crates/ui/src/dialog.rs b/crates/ui/src/dialog.rs index 9a9ee03b..d215ccad 100644 --- a/crates/ui/src/dialog.rs +++ b/crates/ui/src/dialog.rs @@ -1,18 +1,21 @@ use std::{rc::Rc, time::Duration}; use gpui::{ - anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _, - AnyElement, App, Axis, Bounds, BoxShadow, ClickEvent, Div, Edges, FocusHandle, Hsla, - InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, - RenderOnce, SharedString, StyleRefinement, Styled, Window, + Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, ClickEvent, Div, Edges, + FocusHandle, Hsla, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, + Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled, Window, anchored, div, hsla, + point, prelude::FluentBuilder, px, relative, }; use rust_i18n::t; use crate::{ + ActiveTheme as _, IconName, Root, Sizable as _, StyledExt, WindowExt as _, actions::{Cancel, Confirm}, animation::cubic_bezier, button::{Button, ButtonVariant, ButtonVariants as _}, - h_flex, v_flex, ActiveTheme as _, IconName, Root, Sizable as _, StyledExt, WindowExt as _, + h_flex, + scroll::ScrollableElement as _, + v_flex, }; const CONTEXT: &str = "Dialog"; @@ -496,9 +499,10 @@ impl RenderOnce for Dialog { .child( div().w_full().flex_1().overflow_hidden().child( v_flex() + .id("contents") .pl(paddings.left) .pr(paddings.right) - .scrollable(Axis::Vertical) + .overflow_scrollbar() .child(self.content), ), ) diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index df3d3f54..21f3f782 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -1,8 +1,8 @@ use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, InteractiveElement as _, - IntoElement, IsZero, MouseButton, ParentElement as _, Pixels, Rems, RenderOnce, - StyleRefinement, Styled, Window, div, px, relative, + IntoElement, IsZero, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement, + Styled, Window, div, px, relative, }; use crate::button::{Button, ButtonVariants as _}; @@ -193,8 +193,6 @@ impl Input { .unwrap_or(px(0.)), }; - const MIN_SCROLL_PADDING: Pixels = px(2.0); - v_flex() .size_full() .children(state.search_panel.clone()) @@ -221,10 +219,10 @@ impl Input { this.relative().child( div() .absolute() - .top(-paddings.top + MIN_SCROLL_PADDING) + .top(-paddings.top) .left(left) - .right(-paddings.right + MIN_SCROLL_PADDING) - .bottom(-paddings.bottom + MIN_SCROLL_PADDING) + .right(-paddings.right) + .bottom(-paddings.bottom) .child(scrollbar.scroll_size(scroll_size)), ) } else { diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index fb2bdb51..e7703977 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -1,7 +1,7 @@ use crate::actions::{Cancel, Confirm, SelectDown, SelectUp}; use crate::actions::{SelectLeft, SelectRight}; use crate::menu::menu_item::MenuItemElement; -use crate::scroll::Scrollbar; +use crate::scroll::ScrollableElement; use crate::{ActiveTheme, Icon, IconName, Sizable as _, h_flex, v_flex}; use crate::{Side, Size, StyledExt, kbd::Kbd}; use gpui::{ @@ -1309,15 +1309,7 @@ impl Render for PopupMenu { ) .when(self.scrollable, |this| { // TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed. - this.child( - div() - .absolute() - .top_0() - .left_0() - .right_0() - .bottom_0() - .child(Scrollbar::vertical(&self.scroll_handle)), - ) + this.vertical_scrollbar(&self.scroll_handle) }) } } diff --git a/crates/ui/src/scroll/scrollable.rs b/crates/ui/src/scroll/scrollable.rs index 03f72f5f..efa360ca 100644 --- a/crates/ui/src/scroll/scrollable.rs +++ b/crates/ui/src/scroll/scrollable.rs @@ -1,222 +1,199 @@ +use std::{panic::Location, rc::Rc}; + +use crate::{StyledExt, scroll::ScrollbarHandle}; + use super::{Scrollbar, ScrollbarAxis}; use gpui::{ - AnyElement, App, Bounds, Div, Element, ElementId, GlobalElementId, InspectorElementId, - InteractiveElement, Interactivity, IntoElement, LayoutId, ParentElement, Pixels, Position, - ScrollHandle, SharedString, Stateful, StatefulInteractiveElement, Style, StyleRefinement, - Styled, Window, div, relative, + App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, + ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, + prelude::FluentBuilder, }; -/// A scroll view is a container that allows the user to scroll through a large amount of content. -pub struct Scrollable { +/// A trait for elements that can be made scrollable with scrollbars. +pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element { + /// Adds a scrollbar to the element. + #[track_caller] + fn scrollbar( + self, + scroll_handle: &H, + axis: impl Into, + ) -> Self { + self.child(ScrollbarLayer { + id: "scrollbar_layer".into(), + axis: axis.into(), + scroll_handle: Rc::new(scroll_handle.clone()), + }) + } + + /// Adds a vertical scrollbar to the element. + #[track_caller] + fn vertical_scrollbar(self, scroll_handle: &H) -> Self { + self.scrollbar(scroll_handle, ScrollbarAxis::Vertical) + } + /// Adds a horizontal scrollbar to the element. + #[track_caller] + fn horizontal_scrollbar(self, scroll_handle: &H) -> Self { + self.scrollbar(scroll_handle, ScrollbarAxis::Horizontal) + } + + /// Almost equivalent to [`StatefulInteractiveElement::overflow_scroll`], but adds scrollbars. + #[track_caller] + fn overflow_scrollbar(self) -> Scrollable { + Scrollable::new(self, ScrollbarAxis::Both) + } + + /// Almost equivalent to [`StatefulInteractiveElement::overflow_x_scroll`], but adds Horizontal scrollbar. + #[track_caller] + fn overflow_x_scrollbar(self) -> Scrollable { + Scrollable::new(self, ScrollbarAxis::Horizontal) + } + + /// Almost equivalent to [`StatefulInteractiveElement::overflow_y_scroll`], but adds Vertical scrollbar. + #[track_caller] + fn overflow_y_scrollbar(self) -> Scrollable { + Scrollable::new(self, ScrollbarAxis::Vertical) + } +} + +/// A scrollable element wrapper that adds scrollbars to an interactive element. +#[derive(IntoElement)] +pub struct Scrollable { id: ElementId, - element: Option, + element: E, axis: ScrollbarAxis, - /// This is a fake element to handle Styled, InteractiveElement, not used. - _element: Stateful
, } impl Scrollable where - E: Element, + E: InteractiveElement + Styled + ParentElement + Element, { - pub(crate) fn new(axis: impl Into, element: E) -> Self { - let id = ElementId::Name(SharedString::from( - format!("scrollable-{:?}", element.id(),), - )); - + #[track_caller] + fn new(element: E, axis: impl Into) -> Self { + let caller = Location::caller(); Self { - element: Some(element), - _element: div().id("fake"), - id, + id: ElementId::CodeLocation(*caller), + element, axis: axis.into(), } } - - /// Set only a vertical scrollbar. - pub fn vertical(self) -> Self { - self.axis(ScrollbarAxis::Vertical) - } - - /// Set only a horizontal scrollbar. - /// In current implementation, this is not supported yet. - pub fn horizontal(self) -> Self { - self.axis(ScrollbarAxis::Horizontal) - } - - /// Set the axis of the scroll view. - pub fn axis(mut self, axis: impl Into) -> Self { - self.axis = axis.into(); - self - } - - fn with_element_state( - &mut self, - id: &GlobalElementId, - window: &mut Window, - cx: &mut App, - f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut Window, &mut App) -> R, - ) -> R { - window.with_optional_element_state::( - Some(id), - |element_state, window| { - let mut element_state = element_state.unwrap().unwrap_or_default(); - let result = f(self, &mut element_state, window, cx); - (result, Some(element_state)) - }, - ) - } -} - -#[doc(hidden)] -pub struct ScrollViewState { - handle: ScrollHandle, -} - -impl Default for ScrollViewState { - fn default() -> Self { - Self { - handle: ScrollHandle::new(), - } - } -} - -impl ParentElement for Scrollable -where - E: Element + ParentElement, -{ - fn extend(&mut self, elements: impl IntoIterator) { - if let Some(element) = &mut self.element { - element.extend(elements); - } - } } impl Styled for Scrollable where - E: Element + Styled, + E: InteractiveElement + Styled + ParentElement + Element, { fn style(&mut self) -> &mut StyleRefinement { - if let Some(element) = &mut self.element { - element.style() - } else { - self._element.style() - } + self.element.style() } } -impl InteractiveElement for Scrollable +impl ParentElement for Scrollable where - E: Element + InteractiveElement, + E: InteractiveElement + Styled + ParentElement + Element, { - fn interactivity(&mut self) -> &mut Interactivity { - if let Some(element) = &mut self.element { - element.interactivity() - } else { - self._element.interactivity() - } + fn extend(&mut self, elements: impl IntoIterator) { + self.element.extend(elements) } } -impl StatefulInteractiveElement for Scrollable where E: Element + StatefulInteractiveElement {} -impl IntoElement for Scrollable +impl InteractiveElement for Scrollable
{ + fn interactivity(&mut self) -> &mut gpui::Interactivity { + self.element.interactivity() + } +} + +impl InteractiveElement for Scrollable> { + fn interactivity(&mut self) -> &mut gpui::Interactivity { + self.element.interactivity() + } +} + +impl RenderOnce for Scrollable where - E: Element, + E: InteractiveElement + Styled + ParentElement + Element + 'static, { - type Element = Self; + fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let scroll_handle = window + .use_keyed_state(self.id.clone(), cx, |_, _| ScrollHandle::default()) + .read(cx) + .clone(); - fn into_element(self) -> Self::Element { - self + let style = self.element.style().clone(); + *self.element.style() = StyleRefinement::default(); + + div() + .id(self.id) + .size_full() + .refine_style(&style) + .relative() + .child( + div() + .id("scroll-area") + .flex() + .size_full() + .track_scroll(&scroll_handle) + .map(|this| match self.axis { + ScrollbarAxis::Vertical => this.flex_col().overflow_y_scroll(), + ScrollbarAxis::Horizontal => this.flex_row().overflow_x_scroll(), + ScrollbarAxis::Both => this.overflow_scroll(), + }) + .child(self.element.flex_1()), + ) + .child(render_scrollbar( + "scrollbar", + &scroll_handle, + self.axis, + window, + cx, + )) } } -impl Element for Scrollable +impl ScrollableElement for Div {} +impl ScrollableElement for Stateful where - E: Element, + E: ParentElement + Styled + Element, + Self: InteractiveElement, { - type RequestLayoutState = AnyElement; - type PrepaintState = ScrollViewState; +} - fn id(&self) -> Option { - Some(self.id.clone()) - } +#[derive(IntoElement)] +struct ScrollbarLayer { + id: ElementId, + axis: ScrollbarAxis, + scroll_handle: Rc, +} - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - id: Option<&GlobalElementId>, - _: Option<&InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.flex_grow = 1.0; - style.position = Position::Relative; - style.size.width = relative(1.0).into(); - style.size.height = relative(1.0).into(); - - let axis = self.axis; - let scroll_id = self.id.clone(); - let content = self.element.take().map(|c| c.into_any_element()); - - self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| { - let mut element = div() - .relative() - .size_full() - .overflow_hidden() - .child( - div() - .id(scroll_id) - .track_scroll(&element_state.handle) - .overflow_scroll() - .relative() - .size_full() - .child(div().children(content)), - ) - .child( - div() - .absolute() - .top_0() - .left_0() - .right_0() - .bottom_0() - .child(Scrollbar::new(&element_state.handle).axis(axis)), - ) - .into_any_element(); - - let element_id = element.request_layout(window, cx); - let layout_id = window.request_layout(style, vec![element_id], cx); - - (layout_id, element) - }) - } - - fn prepaint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&InspectorElementId>, - _: Bounds, - element: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - element.prepaint(window, cx); - // do nothing - ScrollViewState::default() - } - - fn paint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&InspectorElementId>, - _: Bounds, - element: &mut Self::RequestLayoutState, - _: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - element.paint(window, cx) +impl RenderOnce for ScrollbarLayer +where + H: ScrollbarHandle + Clone + 'static, +{ + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + render_scrollbar(self.id, self.scroll_handle.as_ref(), self.axis, window, cx) } } + +#[inline] +#[track_caller] +fn render_scrollbar( + id: impl Into, + scroll_handle: &H, + axis: ScrollbarAxis, + window: &mut Window, + cx: &mut App, +) -> Div { + // Do not render scrollbar when inspector is picking elements, + // to allow us to pick the background elements. + let is_inspector_picking = window.is_inspector_picking(cx); + if is_inspector_picking { + return div(); + } + + div() + .absolute() + .top_0() + .left_0() + .right_0() + .bottom_0() + .child(Scrollbar::new(scroll_handle).id(id).axis(axis)) +} diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 21c3451d..20abb88c 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -18,16 +18,16 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// The width of the scrollbar (THUMB_ACTIVE_INSET * 2 + THUMB_ACTIVE_WIDTH) -const WIDTH: Pixels = px(2. * 2. + 8.); +const WIDTH: Pixels = px(4. * 2. + 8.); const MIN_THUMB_SIZE: f32 = 48.; const THUMB_WIDTH: Pixels = px(6.); const THUMB_RADIUS: Pixels = px(6. / 2.); -const THUMB_INSET: Pixels = px(2.); +const THUMB_INSET: Pixels = px(4.); const THUMB_ACTIVE_WIDTH: Pixels = px(8.); const THUMB_ACTIVE_RADIUS: Pixels = px(8. / 2.); -const THUMB_ACTIVE_INSET: Pixels = px(2.); +const THUMB_ACTIVE_INSET: Pixels = px(4.); const FADE_OUT_DURATION: f32 = 3.0; const FADE_OUT_DELAY: f32 = 2.0; diff --git a/crates/ui/src/setting/page.rs b/crates/ui/src/setting/page.rs index 593dee3e..34a10ddf 100644 --- a/crates/ui/src/setting/page.rs +++ b/crates/ui/src/setting/page.rs @@ -9,7 +9,7 @@ use crate::{ button::{Button, ButtonVariants}, h_flex, label::Label, - scroll::Scrollbar, + scroll::ScrollableElement, setting::{RenderOptions, SettingGroup, settings::SettingsState}, v_flex, }; @@ -177,15 +177,7 @@ impl SettingPage { }) .size_full(), ) - .child( - div() - .absolute() - .top_0() - .left_0() - .right_0() - .bottom_0() - .child(Scrollbar::vertical(&list_state)), - ), + .vertical_scrollbar(&list_state), ) } } diff --git a/crates/ui/src/sheet.rs b/crates/ui/src/sheet.rs index ed287618..2d3375d5 100644 --- a/crates/ui/src/sheet.rs +++ b/crates/ui/src/sheet.rs @@ -1,19 +1,21 @@ use std::{rc::Rc, time::Duration}; use gpui::{ - anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, - AnyElement, App, Axis, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, - FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, - Pixels, RenderOnce, Styled, Window, + Animation, AnimationExt as _, AnyElement, App,ClickEvent, DefiniteLength, DismissEvent, Div, + EventEmitter, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, + ParentElement, Pixels, RenderOnce, Styled, Window, anchored, div, point, + prelude::FluentBuilder as _, px, }; use crate::{ + ActiveTheme, IconName, Placement, Sizable, StyledExt as _, WindowExt as _, actions::Cancel, button::{Button, ButtonVariants as _}, dialog::overlay_color, h_flex, + scroll::ScrollableElement as _, title_bar::TITLE_BAR_HEIGHT, - v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, WindowExt as _, + v_flex, }; const CONTEXT: &str = "Sheet"; @@ -217,10 +219,7 @@ impl RenderOnce for Sheet { ) .child( // Body - div() - .flex_1() - .overflow_hidden() - .child(v_flex().scrollable(Axis::Vertical).child(self.content)), + div().flex_1().overflow_scrollbar().child(self.content), ) .when_some(self.footer, |this, footer| { // Footer diff --git a/crates/ui/src/sidebar/mod.rs b/crates/ui/src/sidebar/mod.rs index 34e56e71..74b0ee7f 100644 --- a/crates/ui/src/sidebar/mod.rs +++ b/crates/ui/src/sidebar/mod.rs @@ -1,13 +1,14 @@ use crate::{ + ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt, button::{Button, ButtonVariants}, h_flex, - scroll::ScrollbarAxis, - v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt, + scroll::ScrollableElement, + v_flex, }; use gpui::{ - div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, EdgesRefinement, - InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, StyleRefinement, - Styled, Window, + AnyElement, App, ClickEvent, EdgesRefinement, InteractiveElement as _, IntoElement, + ParentElement, Pixels, RenderOnce, StyleRefinement, Styled, Window, div, + prelude::FluentBuilder, px, }; use std::rc::Rc; @@ -219,6 +220,7 @@ impl RenderOnce for Sidebar { .child( v_flex().id("content").flex_1().min_h_0().child( v_flex() + .id("inner") .gap_3() .p_3() .when(self.collapsed, |this| this.p_2()) @@ -228,7 +230,7 @@ impl RenderOnce for Sidebar { .enumerate() .map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))), ) - .scrollable(ScrollbarAxis::Vertical), + .overflow_y_scrollbar(), ), ) .when_some(self.footer.take(), |this, footer| { diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index 0df5112f..94a869d2 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -1,10 +1,7 @@ -use crate::{ - ActiveTheme, PixelsExt as _, - scroll::{Scrollable, ScrollbarAxis}, -}; +use crate::{ActiveTheme, PixelsExt as _}; use gpui::{ - App, BoxShadow, Corners, DefiniteLength, Div, Edges, Element, FocusHandle, Hsla, ParentElement, - Pixels, Refineable, StyleRefinement, Styled, Window, div, point, px, + App, BoxShadow, Corners, DefiniteLength, Div, Edges, FocusHandle, Hsla, ParentElement, Pixels, + Refineable, StyleRefinement, Styled, Window, div, point, px, }; use serde::{Deserialize, Serialize}; @@ -165,17 +162,6 @@ pub trait StyledExt: Styled + Sized { self.border_1().border_color(cx.theme().ring) } - /// Wraps the element in a ScrollView. - /// - /// Current this is only have a vertical scrollbar. - #[inline] - fn scrollable(self, axis: impl Into) -> Scrollable - where - Self: Element, - { - Scrollable::new(axis, self) - } - font_weight!(font_thin, THIN); font_weight!(font_extralight, EXTRA_LIGHT); font_weight!(font_light, LIGHT); diff --git a/crates/ui/src/text/text_view.rs b/crates/ui/src/text/text_view.rs index b82ff30b..b35e7c98 100644 --- a/crates/ui/src/text/text_view.rs +++ b/crates/ui/src/text/text_view.rs @@ -14,7 +14,7 @@ use gpui::{ use smol::stream::StreamExt; use crate::highlighter::HighlightTheme; -use crate::scroll::Scrollbar; +use crate::scroll::ScrollableElement; use crate::{ActiveTheme, StyledExt, v_flex}; use crate::{ global_state::GlobalState, @@ -626,17 +626,7 @@ impl Element for TextView { state: self.state.clone(), }) .refine_style(&self.style) - .when(self.scrollable, |this| { - this.child( - div() - .absolute() - .w(Scrollbar::width()) - .top_0() - .right_0() - .bottom_0() - .child(Scrollbar::vertical(list_state)), - ) - }) + .vertical_scrollbar(list_state) .into_any_element(); let layout_id = el.request_layout(window, cx); (layout_id, el) diff --git a/crates/ui/src/tree.rs b/crates/ui/src/tree.rs index 9c9003c0..f9bab39e 100644 --- a/crates/ui/src/tree.rs +++ b/crates/ui/src/tree.rs @@ -11,7 +11,7 @@ use crate::{ StyledExt, actions::{Confirm, SelectDown, SelectLeft, SelectRight, SelectUp}, list::ListItem, - scroll::Scrollbar, + scroll::ScrollableElement, }; const CONTEXT: &str = "Tree"; @@ -347,54 +347,41 @@ impl Render for TreeState { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let render_item = self.render_item.clone(); - div() - .id("tree-state") - .size_full() - .relative() - .child( - uniform_list("entries", self.entries.len(), { - cx.processor(move |state, visible_range: Range, window, cx| { - let mut items = Vec::with_capacity(visible_range.len()); - for ix in visible_range { - let entry = &state.entries[ix]; - let selected = Some(ix) == state.selected_ix; - let item = (render_item)(ix, entry, selected, window, cx); + div().id("tree-state").size_full().relative().child( + uniform_list("entries", self.entries.len(), { + cx.processor(move |state, visible_range: Range, window, cx| { + let mut items = Vec::with_capacity(visible_range.len()); + for ix in visible_range { + let entry = &state.entries[ix]; + let selected = Some(ix) == state.selected_ix; + let item = (render_item)(ix, entry, selected, window, cx); - let el = div() - .id(ix) - .child(item.disabled(entry.item().is_disabled()).selected(selected)) - .when(!entry.item().is_disabled(), |this| { - this.on_mouse_down( - MouseButton::Left, - cx.listener({ - move |this, _, window, cx| { - this.on_entry_click(ix, window, cx); - } - }), - ) - }); + let el = div() + .id(ix) + .child(item.disabled(entry.item().is_disabled()).selected(selected)) + .when(!entry.item().is_disabled(), |this| { + this.on_mouse_down( + MouseButton::Left, + cx.listener({ + move |this, _, window, cx| { + this.on_entry_click(ix, window, cx); + } + }), + ) + }); - items.push(el) - } + items.push(el) + } - items - }) + items }) - .flex_grow() - .size_full() - .track_scroll(self.scroll_handle.clone()) - .with_sizing_behavior(ListSizingBehavior::Auto) - .into_any_element(), - ) - .child( - div() - .absolute() - .top_0() - .right_0() - .bottom_0() - .w(Scrollbar::width()) - .child(Scrollbar::vertical(&self.scroll_handle)), - ) + }) + .flex_grow() + .size_full() + .track_scroll(self.scroll_handle.clone()) + .with_sizing_behavior(ListSizingBehavior::Auto) + .into_any_element(), + ) } } @@ -432,6 +419,7 @@ impl Styled for Tree { impl RenderOnce for Tree { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let focus_handle = self.state.read(cx).focus_handle.clone(); + let scroll_handle = self.state.read(cx).scroll_handle.clone(); self.state .update(cx, |state, _| state.render_item = self.render_item); @@ -448,6 +436,7 @@ impl RenderOnce for Tree { .size_full() .child(self.state) .refine_style(&self.style) + .vertical_scrollbar(&scroll_handle) } } diff --git a/docs/docs/components/scrollable.md b/docs/docs/components/scrollable.md index 30e07739..e1caeb00 100644 --- a/docs/docs/components/scrollable.md +++ b/docs/docs/components/scrollable.md @@ -11,7 +11,7 @@ A comprehensive scrollable container component that provides custom scrollbars, ```rust use gpui_component::{ - scroll::{Scrollable, ScrollbarAxis, ScrollbarShow}, + scroll::{ScrollableElement, ScrollbarAxis, ScrollbarShow}, StyledExt as _, }; ``` @@ -20,21 +20,31 @@ use gpui_component::{ ### Basic Scrollable Container -The simplest way to make any element scrollable is using the `scrollable()` method from `StyledExt`: +The simplest way to make any element scrollable is using the `overflow_scrollbar()` method from `ScrollableElement` trait. + +This method is almost like the `overflow_scroll()` method, but it adds scrollbars. + +- `overflow_scrollbar()` - Adds scrollbars for both axes as needed. +- `overflow_x_scrollbar()` - Adds horizontal scrollbar as needed. +- `overflow_y_scrollbar()` - Adds vertical scrollbar as needed. ```rust use gpui::{div, Axis}; +use gpui_component::ScrollableElement; div() + .id("scrollable-container") .size_full() .child("Your content here") - .scrollable(Axis::Vertical) + .overflow_scrollbar() ``` -### Scrollable with Content +### Vertical Scrolling ```rust v_flex() + .id("scrollable-container") + .overflow_y_scrollbar() .gap_2() .p_4() .child("Scrollable Content") @@ -45,13 +55,14 @@ v_flex() .bg(cx.theme().secondary) .child(format!("Item {}", i)) })) - .scrollable(Axis::Vertical) ``` ### Horizontal Scrolling ```rust h_flex() + .id("scrollable-container") + .overflow_x_scrollbar() .gap_2() .p_4() .children((0..50).map(|i| { @@ -61,14 +72,15 @@ h_flex() .bg(cx.theme().accent) .child(format!("Card {}", i)) })) - .scrollable(Axis::Horizontal) ``` ### Both Directions ```rust div() + .id("scrollable-container") .size_full() + .overflow_scrollbar() .child( div() .w(px(2000.)) // Wide content @@ -76,7 +88,6 @@ div() .bg(cx.theme().background) .child("Large content area") ) - .scrollable(ScrollbarAxis::Both) ``` ## Custom Scrollbars @@ -86,7 +97,7 @@ div() For more control, you can create scrollbars manually: ```rust -use gpui_component::scroll::{Scrollbar}; +use gpui_component::scroll::{ScrollableElement}; pub struct ScrollableView { scroll_handle: ScrollHandle, @@ -105,21 +116,11 @@ impl Render for ScrollableView { .size_full() .child("Your scrollable content") ) - .child( - Scrollbar::vertical(&self.scroll_handle) - ) + .vertical_scrollbar(&self.scroll_handle) } } ``` -### Customizing Scrollbar Behavior - -```rust -Scrollbar::both(&scroll_handle) - .axis(ScrollbarAxis::Vertical) - .scroll_size(size(px(1000.), px(2000.))) // Custom content size -``` - ## Virtualization ### VirtualList for Large Datasets @@ -253,6 +254,7 @@ impl Render for FileBrowser { v_flex() .gap_1() .p_2() + .overflow_y_scrollbar() .children(self.files.iter().map(|file| { div() .h(px(32.)) @@ -263,7 +265,6 @@ impl Render for FileBrowser { .hover(|style| style.bg(cx.theme().secondary_hover)) .child(file.clone()) })) - .scrollable(Axis::Vertical) ) } }