scrollbar: Introduce overflow_scrollbar to adds Scrollbars to elements. (#1694)
## Description 1. Add `overflow_scrollbar`, `overflow_y_scrollbar`, `overflow_x_scroll` to GPUI elements to let them has scrollbars. It is almost like the `overscroll` but adds the Scrollbar. 2. And adjust the display scrollbar of margining 4px of the container. 3. Add `scrollbar`, `vertical_scrollbar`, `horizontal_scrollbar` with your own scroll handle. <img width="1181" height="911" alt="image" src="https://github.com/user-attachments/assets/8b02cd54-d527-4f22-b9be-f762ede22122" /> ## Break Changes - There `Scrollable` trait and it `scrollable` method has been removed. ```diff - div().id("contents").scrollable(Axis::Vertical) + div().id("contents").overflow_y_scrollbar() ```
This commit is contained in:
parent
0b1fab5b08
commit
118a0f500e
16 changed files with 274 additions and 361 deletions
|
|
@ -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<Self>) -> 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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,7 +458,6 @@ impl Render for ListStory {
|
|||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
Button::new("scroll-top")
|
||||
.outline()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<E> {
|
||||
/// 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<H: ScrollbarHandle + Clone>(
|
||||
self,
|
||||
scroll_handle: &H,
|
||||
axis: impl Into<ScrollbarAxis>,
|
||||
) -> 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<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
|
||||
self.scrollbar(scroll_handle, ScrollbarAxis::Vertical)
|
||||
}
|
||||
/// Adds a horizontal scrollbar to the element.
|
||||
#[track_caller]
|
||||
fn horizontal_scrollbar<H: ScrollbarHandle + Clone>(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<Self> {
|
||||
Scrollable::new(self, ScrollbarAxis::Both)
|
||||
}
|
||||
|
||||
/// Almost equivalent to [`StatefulInteractiveElement::overflow_x_scroll`], but adds Horizontal scrollbar.
|
||||
#[track_caller]
|
||||
fn overflow_x_scrollbar(self) -> Scrollable<Self> {
|
||||
Scrollable::new(self, ScrollbarAxis::Horizontal)
|
||||
}
|
||||
|
||||
/// Almost equivalent to [`StatefulInteractiveElement::overflow_y_scroll`], but adds Vertical scrollbar.
|
||||
#[track_caller]
|
||||
fn overflow_y_scrollbar(self) -> Scrollable<Self> {
|
||||
Scrollable::new(self, ScrollbarAxis::Vertical)
|
||||
}
|
||||
}
|
||||
|
||||
/// A scrollable element wrapper that adds scrollbars to an interactive element.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
|
||||
id: ElementId,
|
||||
element: Option<E>,
|
||||
element: E,
|
||||
axis: ScrollbarAxis,
|
||||
/// This is a fake element to handle Styled, InteractiveElement, not used.
|
||||
_element: Stateful<Div>,
|
||||
}
|
||||
|
||||
impl<E> Scrollable<E>
|
||||
where
|
||||
E: Element,
|
||||
E: InteractiveElement + Styled + ParentElement + Element,
|
||||
{
|
||||
pub(crate) fn new(axis: impl Into<ScrollbarAxis>, element: E) -> Self {
|
||||
let id = ElementId::Name(SharedString::from(
|
||||
format!("scrollable-{:?}", element.id(),),
|
||||
));
|
||||
|
||||
#[track_caller]
|
||||
fn new(element: E, axis: impl Into<ScrollbarAxis>) -> 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<ScrollbarAxis>) -> Self {
|
||||
self.axis = axis.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn with_element_state<R>(
|
||||
&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::<ScrollViewState, _>(
|
||||
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<E> ParentElement for Scrollable<E>
|
||||
where
|
||||
E: Element + ParentElement,
|
||||
{
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
if let Some(element) = &mut self.element {
|
||||
element.extend(elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Styled for Scrollable<E>
|
||||
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<E> InteractiveElement for Scrollable<E>
|
||||
impl<E> ParentElement for Scrollable<E>
|
||||
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<Item = gpui::AnyElement>) {
|
||||
self.element.extend(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<E> StatefulInteractiveElement for Scrollable<E> where E: Element + StatefulInteractiveElement {}
|
||||
|
||||
impl<E> IntoElement for Scrollable<E>
|
||||
impl InteractiveElement for Scrollable<Div> {
|
||||
fn interactivity(&mut self) -> &mut gpui::Interactivity {
|
||||
self.element.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for Scrollable<Stateful<Div>> {
|
||||
fn interactivity(&mut self) -> &mut gpui::Interactivity {
|
||||
self.element.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> RenderOnce for Scrollable<E>
|
||||
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();
|
||||
|
||||
impl<E> Element for Scrollable<E>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
type RequestLayoutState = AnyElement;
|
||||
type PrepaintState = ScrollViewState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
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()
|
||||
div()
|
||||
.id(self.id)
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.refine_style(&style)
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
.id(scroll_id)
|
||||
.track_scroll(&element_state.handle)
|
||||
.overflow_scroll()
|
||||
.relative()
|
||||
.id("scroll-area")
|
||||
.flex()
|
||||
.size_full()
|
||||
.child(div().children(content)),
|
||||
.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(
|
||||
.child(render_scrollbar(
|
||||
"scrollbar",
|
||||
&scroll_handle,
|
||||
self.axis,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableElement for Div {}
|
||||
impl<E> ScrollableElement for Stateful<E>
|
||||
where
|
||||
E: ParentElement + Styled + Element,
|
||||
Self: InteractiveElement,
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct ScrollbarLayer<H: ScrollbarHandle + Clone> {
|
||||
id: ElementId,
|
||||
axis: ScrollbarAxis,
|
||||
scroll_handle: Rc<H>,
|
||||
}
|
||||
|
||||
impl<H> RenderOnce for ScrollbarLayer<H>
|
||||
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<H: ScrollbarHandle + Clone>(
|
||||
id: impl Into<ElementId>,
|
||||
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(&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<Pixels>,
|
||||
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<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.paint(window, cx)
|
||||
}
|
||||
.child(Scrollbar::new(scroll_handle).id(id).axis(axis))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
|||
.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<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
|||
.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| {
|
||||
|
|
|
|||
|
|
@ -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<ScrollbarAxis>) -> Scrollable<Self>
|
||||
where
|
||||
Self: Element,
|
||||
{
|
||||
Scrollable::new(axis, self)
|
||||
}
|
||||
|
||||
font_weight!(font_thin, THIN);
|
||||
font_weight!(font_extralight, EXTRA_LIGHT);
|
||||
font_weight!(font_light, LIGHT);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
StyledExt,
|
||||
actions::{Confirm, SelectDown, SelectLeft, SelectRight, SelectUp},
|
||||
list::ListItem,
|
||||
scroll::Scrollbar,
|
||||
scroll::ScrollableElement,
|
||||
};
|
||||
|
||||
const CONTEXT: &str = "Tree";
|
||||
|
|
@ -347,11 +347,7 @@ impl Render for TreeState {
|
|||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let render_item = self.render_item.clone();
|
||||
|
||||
div()
|
||||
.id("tree-state")
|
||||
.size_full()
|
||||
.relative()
|
||||
.child(
|
||||
div().id("tree-state").size_full().relative().child(
|
||||
uniform_list("entries", self.entries.len(), {
|
||||
cx.processor(move |state, visible_range: Range<usize>, window, cx| {
|
||||
let mut items = Vec::with_capacity(visible_range.len());
|
||||
|
|
@ -386,15 +382,6 @@ impl Render for TreeState {
|
|||
.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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue