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::{
|
use gpui::{
|
||||||
Action, AnyElement, AnyView, App, AppContext, Bounds, Context, Div, Entity, EventEmitter,
|
Action, AnyElement, AnyView, App, AppContext, Bounds, Context, Div, Entity, EventEmitter,
|
||||||
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels,
|
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels,
|
||||||
Render, RenderOnce, SharedString, Size, StatefulInteractiveElement, StyleRefinement, Styled,
|
Render, RenderOnce, SharedString, Size, StyleRefinement, Styled, Window, WindowBounds,
|
||||||
Window, WindowBounds, WindowKind, WindowOptions, actions, div, prelude::FluentBuilder as _, px,
|
WindowKind, WindowOptions, actions, div, prelude::FluentBuilder as _, px, rems, size,
|
||||||
rems, size,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use accordion_story::AccordionStory;
|
pub use accordion_story::AccordionStory;
|
||||||
|
|
@ -121,7 +120,7 @@ use gpui_component::{
|
||||||
h_flex,
|
h_flex,
|
||||||
menu::PopupMenu,
|
menu::PopupMenu,
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
scroll::ScrollbarShow,
|
scroll::{ScrollableElement as _, ScrollbarShow},
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
||||||
|
|
@ -773,22 +772,14 @@ impl Focusable for StoryContainer {
|
||||||
}
|
}
|
||||||
impl Render for StoryContainer {
|
impl Render for StoryContainer {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
div()
|
||||||
.id("story-container")
|
.id("story-container")
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scrollbar()
|
||||||
|
.p(self.paddings)
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_action(cx.listener(Self::on_action_panel_info))
|
.on_action(cx.listener(Self::on_action_panel_info))
|
||||||
.on_action(cx.listener(Self::on_action_toggle_search))
|
.on_action(cx.listener(Self::on_action_toggle_search))
|
||||||
.when_some(self.story.clone(), |this, story| {
|
.when_some(self.story.clone(), |this, story| this.child(story))
|
||||||
this.child(
|
|
||||||
v_flex()
|
|
||||||
.id("story-children")
|
|
||||||
.w_full()
|
|
||||||
.flex_1()
|
|
||||||
.p(self.paddings)
|
|
||||||
.child(story),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,6 @@ impl Render for ListStory {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.flex_wrap()
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("scroll-top")
|
Button::new("scroll-top")
|
||||||
.outline()
|
.outline()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Axis, Context, Entity, FocusHandle, Focusable, InteractiveElement,
|
App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Pixels,
|
||||||
IntoElement, ParentElement, Pixels, Render, Size, Styled, Window, div, px, size,
|
Render, Size, Styled, Window, div, px, size,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
ActiveTheme as _, Selectable, StyledExt as _,
|
ActiveTheme as _, Selectable,
|
||||||
button::{Button, ButtonGroup},
|
button::{Button, ButtonGroup},
|
||||||
h_flex, v_flex,
|
h_flex,
|
||||||
|
scroll::ScrollableElement,
|
||||||
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ScrollableStory {
|
pub struct ScrollableStory {
|
||||||
|
|
@ -152,13 +154,12 @@ impl Render for ScrollableStory {
|
||||||
.min_h(px(200.))
|
.min_h(px(200.))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_3()
|
|
||||||
.w(test_width)
|
.w(test_width)
|
||||||
.id("test-1")
|
.p_3()
|
||||||
.scrollable(Axis::Vertical)
|
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.overflow_y_scrollbar()
|
||||||
.child("Scrollable Example")
|
.child("Scrollable Example")
|
||||||
.children(self.items.iter().take(500).map(|item| {
|
.children(self.items.iter().map(|item| {
|
||||||
div()
|
div()
|
||||||
.h(ITEM_HEIGHT)
|
.h(ITEM_HEIGHT)
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use gpui_component::{
|
||||||
button::{Button, ButtonGroup},
|
button::{Button, ButtonGroup},
|
||||||
divider::Divider,
|
divider::Divider,
|
||||||
h_flex,
|
h_flex,
|
||||||
scroll::{Scrollbar, ScrollbarAxis},
|
scroll::{ScrollableElement, ScrollbarAxis},
|
||||||
v_flex, v_virtual_list,
|
v_flex, v_virtual_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -282,15 +282,7 @@ impl Render for VirtualListStory {
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.gap_1(),
|
.gap_1(),
|
||||||
)
|
)
|
||||||
.child({
|
.scrollbar(&self.scroll_handle, self.axis),
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.right_0()
|
|
||||||
.bottom_0()
|
|
||||||
.child(Scrollbar::new(&self.scroll_handle).axis(self.axis))
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
use std::{rc::Rc, time::Duration};
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _,
|
Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, ClickEvent, Div, Edges,
|
||||||
AnyElement, App, Axis, Bounds, BoxShadow, ClickEvent, Div, Edges, FocusHandle, Hsla,
|
FocusHandle, Hsla, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement,
|
||||||
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled, Window, anchored, div, hsla,
|
||||||
RenderOnce, SharedString, StyleRefinement, Styled, Window,
|
point, prelude::FluentBuilder, px, relative,
|
||||||
};
|
};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
ActiveTheme as _, IconName, Root, Sizable as _, StyledExt, WindowExt as _,
|
||||||
actions::{Cancel, Confirm},
|
actions::{Cancel, Confirm},
|
||||||
animation::cubic_bezier,
|
animation::cubic_bezier,
|
||||||
button::{Button, ButtonVariant, ButtonVariants as _},
|
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";
|
const CONTEXT: &str = "Dialog";
|
||||||
|
|
@ -496,9 +499,10 @@ impl RenderOnce for Dialog {
|
||||||
.child(
|
.child(
|
||||||
div().w_full().flex_1().overflow_hidden().child(
|
div().w_full().flex_1().overflow_hidden().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.id("contents")
|
||||||
.pl(paddings.left)
|
.pl(paddings.left)
|
||||||
.pr(paddings.right)
|
.pr(paddings.right)
|
||||||
.scrollable(Axis::Vertical)
|
.overflow_scrollbar()
|
||||||
.child(self.content),
|
.child(self.content),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, InteractiveElement as _,
|
AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, InteractiveElement as _,
|
||||||
IntoElement, IsZero, MouseButton, ParentElement as _, Pixels, Rems, RenderOnce,
|
IntoElement, IsZero, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement,
|
||||||
StyleRefinement, Styled, Window, div, px, relative,
|
Styled, Window, div, px, relative,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::button::{Button, ButtonVariants as _};
|
use crate::button::{Button, ButtonVariants as _};
|
||||||
|
|
@ -193,8 +193,6 @@ impl Input {
|
||||||
.unwrap_or(px(0.)),
|
.unwrap_or(px(0.)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const MIN_SCROLL_PADDING: Pixels = px(2.0);
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.children(state.search_panel.clone())
|
.children(state.search_panel.clone())
|
||||||
|
|
@ -221,10 +219,10 @@ impl Input {
|
||||||
this.relative().child(
|
this.relative().child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.top(-paddings.top + MIN_SCROLL_PADDING)
|
.top(-paddings.top)
|
||||||
.left(left)
|
.left(left)
|
||||||
.right(-paddings.right + MIN_SCROLL_PADDING)
|
.right(-paddings.right)
|
||||||
.bottom(-paddings.bottom + MIN_SCROLL_PADDING)
|
.bottom(-paddings.bottom)
|
||||||
.child(scrollbar.scroll_size(scroll_size)),
|
.child(scrollbar.scroll_size(scroll_size)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
|
use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
|
||||||
use crate::actions::{SelectLeft, SelectRight};
|
use crate::actions::{SelectLeft, SelectRight};
|
||||||
use crate::menu::menu_item::MenuItemElement;
|
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::{ActiveTheme, Icon, IconName, Sizable as _, h_flex, v_flex};
|
||||||
use crate::{Side, Size, StyledExt, kbd::Kbd};
|
use crate::{Side, Size, StyledExt, kbd::Kbd};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
|
@ -1309,15 +1309,7 @@ impl Render for PopupMenu {
|
||||||
)
|
)
|
||||||
.when(self.scrollable, |this| {
|
.when(self.scrollable, |this| {
|
||||||
// TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.
|
// TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.
|
||||||
this.child(
|
this.vertical_scrollbar(&self.scroll_handle)
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.right_0()
|
|
||||||
.bottom_0()
|
|
||||||
.child(Scrollbar::vertical(&self.scroll_handle)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,222 +1,199 @@
|
||||||
|
use std::{panic::Location, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{StyledExt, scroll::ScrollbarHandle};
|
||||||
|
|
||||||
use super::{Scrollbar, ScrollbarAxis};
|
use super::{Scrollbar, ScrollbarAxis};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Bounds, Div, Element, ElementId, GlobalElementId, InspectorElementId,
|
App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
InteractiveElement, Interactivity, IntoElement, LayoutId, ParentElement, Pixels, Position,
|
ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
|
||||||
ScrollHandle, SharedString, Stateful, StatefulInteractiveElement, Style, StyleRefinement,
|
prelude::FluentBuilder,
|
||||||
Styled, Window, div, relative,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A scroll view is a container that allows the user to scroll through a large amount of content.
|
/// A trait for elements that can be made scrollable with scrollbars.
|
||||||
pub struct Scrollable<E> {
|
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,
|
id: ElementId,
|
||||||
element: Option<E>,
|
element: E,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
/// This is a fake element to handle Styled, InteractiveElement, not used.
|
|
||||||
_element: Stateful<Div>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> Scrollable<E>
|
impl<E> Scrollable<E>
|
||||||
where
|
where
|
||||||
E: Element,
|
E: InteractiveElement + Styled + ParentElement + Element,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(axis: impl Into<ScrollbarAxis>, element: E) -> Self {
|
#[track_caller]
|
||||||
let id = ElementId::Name(SharedString::from(
|
fn new(element: E, axis: impl Into<ScrollbarAxis>) -> Self {
|
||||||
format!("scrollable-{:?}", element.id(),),
|
let caller = Location::caller();
|
||||||
));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
element: Some(element),
|
id: ElementId::CodeLocation(*caller),
|
||||||
_element: div().id("fake"),
|
element,
|
||||||
id,
|
|
||||||
axis: axis.into(),
|
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>
|
impl<E> Styled for Scrollable<E>
|
||||||
where
|
where
|
||||||
E: Element + Styled,
|
E: InteractiveElement + Styled + ParentElement + Element,
|
||||||
{
|
{
|
||||||
fn style(&mut self) -> &mut StyleRefinement {
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
if let Some(element) = &mut self.element {
|
self.element.style()
|
||||||
element.style()
|
|
||||||
} else {
|
|
||||||
self._element.style()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> InteractiveElement for Scrollable<E>
|
impl<E> ParentElement for Scrollable<E>
|
||||||
where
|
where
|
||||||
E: Element + InteractiveElement,
|
E: InteractiveElement + Styled + ParentElement + Element,
|
||||||
{
|
{
|
||||||
fn interactivity(&mut self) -> &mut Interactivity {
|
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
||||||
if let Some(element) = &mut self.element {
|
self.element.extend(elements)
|
||||||
element.interactivity()
|
|
||||||
} else {
|
|
||||||
self._element.interactivity()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
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 {
|
let style = self.element.style().clone();
|
||||||
self
|
*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<E> Element for Scrollable<E>
|
impl ScrollableElement for Div {}
|
||||||
|
impl<E> ScrollableElement for Stateful<E>
|
||||||
where
|
where
|
||||||
E: Element,
|
E: ParentElement + Styled + Element,
|
||||||
|
Self: InteractiveElement,
|
||||||
{
|
{
|
||||||
type RequestLayoutState = AnyElement;
|
}
|
||||||
type PrepaintState = ScrollViewState;
|
|
||||||
|
|
||||||
fn id(&self) -> Option<ElementId> {
|
#[derive(IntoElement)]
|
||||||
Some(self.id.clone())
|
struct ScrollbarLayer<H: ScrollbarHandle + Clone> {
|
||||||
}
|
id: ElementId,
|
||||||
|
axis: ScrollbarAxis,
|
||||||
|
scroll_handle: Rc<H>,
|
||||||
|
}
|
||||||
|
|
||||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
impl<H> RenderOnce for ScrollbarLayer<H>
|
||||||
None
|
where
|
||||||
}
|
H: ScrollbarHandle + Clone + 'static,
|
||||||
|
{
|
||||||
fn request_layout(
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
&mut self,
|
render_scrollbar(self.id, self.scroll_handle.as_ref(), self.axis, window, cx)
|
||||||
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<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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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(scroll_handle).id(id).axis(axis))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,16 @@ use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// The width of the scrollbar (THUMB_ACTIVE_INSET * 2 + THUMB_ACTIVE_WIDTH)
|
/// 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 MIN_THUMB_SIZE: f32 = 48.;
|
||||||
|
|
||||||
const THUMB_WIDTH: Pixels = px(6.);
|
const THUMB_WIDTH: Pixels = px(6.);
|
||||||
const THUMB_RADIUS: Pixels = px(6. / 2.);
|
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_WIDTH: Pixels = px(8.);
|
||||||
const THUMB_ACTIVE_RADIUS: Pixels = px(8. / 2.);
|
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_DURATION: f32 = 3.0;
|
||||||
const FADE_OUT_DELAY: f32 = 2.0;
|
const FADE_OUT_DELAY: f32 = 2.0;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
h_flex,
|
h_flex,
|
||||||
label::Label,
|
label::Label,
|
||||||
scroll::Scrollbar,
|
scroll::ScrollableElement,
|
||||||
setting::{RenderOptions, SettingGroup, settings::SettingsState},
|
setting::{RenderOptions, SettingGroup, settings::SettingsState},
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
@ -177,15 +177,7 @@ impl SettingPage {
|
||||||
})
|
})
|
||||||
.size_full(),
|
.size_full(),
|
||||||
)
|
)
|
||||||
.child(
|
.vertical_scrollbar(&list_state),
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.right_0()
|
|
||||||
.bottom_0()
|
|
||||||
.child(Scrollbar::vertical(&list_state)),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
use std::{rc::Rc, time::Duration};
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _,
|
Animation, AnimationExt as _, AnyElement, App,ClickEvent, DefiniteLength, DismissEvent, Div,
|
||||||
AnyElement, App, Axis, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter,
|
EventEmitter, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton,
|
||||||
FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement,
|
ParentElement, Pixels, RenderOnce, Styled, Window, anchored, div, point,
|
||||||
Pixels, RenderOnce, Styled, Window,
|
prelude::FluentBuilder as _, px,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
ActiveTheme, IconName, Placement, Sizable, StyledExt as _, WindowExt as _,
|
||||||
actions::Cancel,
|
actions::Cancel,
|
||||||
button::{Button, ButtonVariants as _},
|
button::{Button, ButtonVariants as _},
|
||||||
dialog::overlay_color,
|
dialog::overlay_color,
|
||||||
h_flex,
|
h_flex,
|
||||||
|
scroll::ScrollableElement as _,
|
||||||
title_bar::TITLE_BAR_HEIGHT,
|
title_bar::TITLE_BAR_HEIGHT,
|
||||||
v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, WindowExt as _,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTEXT: &str = "Sheet";
|
const CONTEXT: &str = "Sheet";
|
||||||
|
|
@ -217,10 +219,7 @@ impl RenderOnce for Sheet {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
// Body
|
// Body
|
||||||
div()
|
div().flex_1().overflow_scrollbar().child(self.content),
|
||||||
.flex_1()
|
|
||||||
.overflow_hidden()
|
|
||||||
.child(v_flex().scrollable(Axis::Vertical).child(self.content)),
|
|
||||||
)
|
)
|
||||||
.when_some(self.footer, |this, footer| {
|
.when_some(self.footer, |this, footer| {
|
||||||
// Footer
|
// Footer
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
h_flex,
|
h_flex,
|
||||||
scroll::ScrollbarAxis,
|
scroll::ScrollableElement,
|
||||||
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
v_flex,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, EdgesRefinement,
|
AnyElement, App, ClickEvent, EdgesRefinement, InteractiveElement as _, IntoElement,
|
||||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, StyleRefinement,
|
ParentElement, Pixels, RenderOnce, StyleRefinement, Styled, Window, div,
|
||||||
Styled, Window,
|
prelude::FluentBuilder, px,
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -219,6 +220,7 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
||||||
.child(
|
.child(
|
||||||
v_flex().id("content").flex_1().min_h_0().child(
|
v_flex().id("content").flex_1().min_h_0().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.id("inner")
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.p_3()
|
.p_3()
|
||||||
.when(self.collapsed, |this| this.p_2())
|
.when(self.collapsed, |this| this.p_2())
|
||||||
|
|
@ -228,7 +230,7 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
|
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
|
||||||
)
|
)
|
||||||
.scrollable(ScrollbarAxis::Vertical),
|
.overflow_y_scrollbar(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when_some(self.footer.take(), |this, footer| {
|
.when_some(self.footer.take(), |this, footer| {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{
|
use crate::{ActiveTheme, PixelsExt as _};
|
||||||
ActiveTheme, PixelsExt as _,
|
|
||||||
scroll::{Scrollable, ScrollbarAxis},
|
|
||||||
};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, BoxShadow, Corners, DefiniteLength, Div, Edges, Element, FocusHandle, Hsla, ParentElement,
|
App, BoxShadow, Corners, DefiniteLength, Div, Edges, FocusHandle, Hsla, ParentElement, Pixels,
|
||||||
Pixels, Refineable, StyleRefinement, Styled, Window, div, point, px,
|
Refineable, StyleRefinement, Styled, Window, div, point, px,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -165,17 +162,6 @@ pub trait StyledExt: Styled + Sized {
|
||||||
self.border_1().border_color(cx.theme().ring)
|
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_thin, THIN);
|
||||||
font_weight!(font_extralight, EXTRA_LIGHT);
|
font_weight!(font_extralight, EXTRA_LIGHT);
|
||||||
font_weight!(font_light, LIGHT);
|
font_weight!(font_light, LIGHT);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use gpui::{
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
|
|
||||||
use crate::highlighter::HighlightTheme;
|
use crate::highlighter::HighlightTheme;
|
||||||
use crate::scroll::Scrollbar;
|
use crate::scroll::ScrollableElement;
|
||||||
use crate::{ActiveTheme, StyledExt, v_flex};
|
use crate::{ActiveTheme, StyledExt, v_flex};
|
||||||
use crate::{
|
use crate::{
|
||||||
global_state::GlobalState,
|
global_state::GlobalState,
|
||||||
|
|
@ -626,17 +626,7 @@ impl Element for TextView {
|
||||||
state: self.state.clone(),
|
state: self.state.clone(),
|
||||||
})
|
})
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
.when(self.scrollable, |this| {
|
.vertical_scrollbar(list_state)
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.w(Scrollbar::width())
|
|
||||||
.top_0()
|
|
||||||
.right_0()
|
|
||||||
.bottom_0()
|
|
||||||
.child(Scrollbar::vertical(list_state)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
let layout_id = el.request_layout(window, cx);
|
let layout_id = el.request_layout(window, cx);
|
||||||
(layout_id, el)
|
(layout_id, el)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
StyledExt,
|
StyledExt,
|
||||||
actions::{Confirm, SelectDown, SelectLeft, SelectRight, SelectUp},
|
actions::{Confirm, SelectDown, SelectLeft, SelectRight, SelectUp},
|
||||||
list::ListItem,
|
list::ListItem,
|
||||||
scroll::Scrollbar,
|
scroll::ScrollableElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTEXT: &str = "Tree";
|
const CONTEXT: &str = "Tree";
|
||||||
|
|
@ -347,54 +347,41 @@ impl Render for TreeState {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let render_item = self.render_item.clone();
|
let render_item = self.render_item.clone();
|
||||||
|
|
||||||
div()
|
div().id("tree-state").size_full().relative().child(
|
||||||
.id("tree-state")
|
uniform_list("entries", self.entries.len(), {
|
||||||
.size_full()
|
cx.processor(move |state, visible_range: Range<usize>, window, cx| {
|
||||||
.relative()
|
let mut items = Vec::with_capacity(visible_range.len());
|
||||||
.child(
|
for ix in visible_range {
|
||||||
uniform_list("entries", self.entries.len(), {
|
let entry = &state.entries[ix];
|
||||||
cx.processor(move |state, visible_range: Range<usize>, window, cx| {
|
let selected = Some(ix) == state.selected_ix;
|
||||||
let mut items = Vec::with_capacity(visible_range.len());
|
let item = (render_item)(ix, entry, selected, window, cx);
|
||||||
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()
|
let el = div()
|
||||||
.id(ix)
|
.id(ix)
|
||||||
.child(item.disabled(entry.item().is_disabled()).selected(selected))
|
.child(item.disabled(entry.item().is_disabled()).selected(selected))
|
||||||
.when(!entry.item().is_disabled(), |this| {
|
.when(!entry.item().is_disabled(), |this| {
|
||||||
this.on_mouse_down(
|
this.on_mouse_down(
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
cx.listener({
|
cx.listener({
|
||||||
move |this, _, window, cx| {
|
move |this, _, window, cx| {
|
||||||
this.on_entry_click(ix, window, cx);
|
this.on_entry_click(ix, window, cx);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
items.push(el)
|
items.push(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.flex_grow()
|
})
|
||||||
.size_full()
|
.flex_grow()
|
||||||
.track_scroll(self.scroll_handle.clone())
|
.size_full()
|
||||||
.with_sizing_behavior(ListSizingBehavior::Auto)
|
.track_scroll(self.scroll_handle.clone())
|
||||||
.into_any_element(),
|
.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 {
|
impl RenderOnce for Tree {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let focus_handle = self.state.read(cx).focus_handle.clone();
|
let focus_handle = self.state.read(cx).focus_handle.clone();
|
||||||
|
let scroll_handle = self.state.read(cx).scroll_handle.clone();
|
||||||
|
|
||||||
self.state
|
self.state
|
||||||
.update(cx, |state, _| state.render_item = self.render_item);
|
.update(cx, |state, _| state.render_item = self.render_item);
|
||||||
|
|
@ -448,6 +436,7 @@ impl RenderOnce for Tree {
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(self.state)
|
.child(self.state)
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
|
.vertical_scrollbar(&scroll_handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ A comprehensive scrollable container component that provides custom scrollbars,
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
scroll::{Scrollable, ScrollbarAxis, ScrollbarShow},
|
scroll::{ScrollableElement, ScrollbarAxis, ScrollbarShow},
|
||||||
StyledExt as _,
|
StyledExt as _,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
@ -20,21 +20,31 @@ use gpui_component::{
|
||||||
|
|
||||||
### Basic Scrollable Container
|
### 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
|
```rust
|
||||||
use gpui::{div, Axis};
|
use gpui::{div, Axis};
|
||||||
|
use gpui_component::ScrollableElement;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.id("scrollable-container")
|
||||||
.size_full()
|
.size_full()
|
||||||
.child("Your content here")
|
.child("Your content here")
|
||||||
.scrollable(Axis::Vertical)
|
.overflow_scrollbar()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scrollable with Content
|
### Vertical Scrolling
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.id("scrollable-container")
|
||||||
|
.overflow_y_scrollbar()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_4()
|
.p_4()
|
||||||
.child("Scrollable Content")
|
.child("Scrollable Content")
|
||||||
|
|
@ -45,13 +55,14 @@ v_flex()
|
||||||
.bg(cx.theme().secondary)
|
.bg(cx.theme().secondary)
|
||||||
.child(format!("Item {}", i))
|
.child(format!("Item {}", i))
|
||||||
}))
|
}))
|
||||||
.scrollable(Axis::Vertical)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Horizontal Scrolling
|
### Horizontal Scrolling
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.id("scrollable-container")
|
||||||
|
.overflow_x_scrollbar()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_4()
|
.p_4()
|
||||||
.children((0..50).map(|i| {
|
.children((0..50).map(|i| {
|
||||||
|
|
@ -61,14 +72,15 @@ h_flex()
|
||||||
.bg(cx.theme().accent)
|
.bg(cx.theme().accent)
|
||||||
.child(format!("Card {}", i))
|
.child(format!("Card {}", i))
|
||||||
}))
|
}))
|
||||||
.scrollable(Axis::Horizontal)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Both Directions
|
### Both Directions
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
div()
|
div()
|
||||||
|
.id("scrollable-container")
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.overflow_scrollbar()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.w(px(2000.)) // Wide content
|
.w(px(2000.)) // Wide content
|
||||||
|
|
@ -76,7 +88,6 @@ div()
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
.child("Large content area")
|
.child("Large content area")
|
||||||
)
|
)
|
||||||
.scrollable(ScrollbarAxis::Both)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom Scrollbars
|
## Custom Scrollbars
|
||||||
|
|
@ -86,7 +97,7 @@ div()
|
||||||
For more control, you can create scrollbars manually:
|
For more control, you can create scrollbars manually:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use gpui_component::scroll::{Scrollbar};
|
use gpui_component::scroll::{ScrollableElement};
|
||||||
|
|
||||||
pub struct ScrollableView {
|
pub struct ScrollableView {
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: ScrollHandle,
|
||||||
|
|
@ -105,21 +116,11 @@ impl Render for ScrollableView {
|
||||||
.size_full()
|
.size_full()
|
||||||
.child("Your scrollable content")
|
.child("Your scrollable content")
|
||||||
)
|
)
|
||||||
.child(
|
.vertical_scrollbar(&self.scroll_handle)
|
||||||
Scrollbar::vertical(&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
|
## Virtualization
|
||||||
|
|
||||||
### VirtualList for Large Datasets
|
### VirtualList for Large Datasets
|
||||||
|
|
@ -253,6 +254,7 @@ impl Render for FileBrowser {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.p_2()
|
.p_2()
|
||||||
|
.overflow_y_scrollbar()
|
||||||
.children(self.files.iter().map(|file| {
|
.children(self.files.iter().map(|file| {
|
||||||
div()
|
div()
|
||||||
.h(px(32.))
|
.h(px(32.))
|
||||||
|
|
@ -263,7 +265,6 @@ impl Render for FileBrowser {
|
||||||
.hover(|style| style.bg(cx.theme().secondary_hover))
|
.hover(|style| style.bg(cx.theme().secondary_hover))
|
||||||
.child(file.clone())
|
.child(file.clone())
|
||||||
}))
|
}))
|
||||||
.scrollable(Axis::Vertical)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue