story: Use uniform_list for scroll example. (#1709)

Ref #1621

Update scrollable story to use `UniformList` to avoid performance issue
in story.

And also renamed it to `ScrollbarStory`.
This commit is contained in:
Jason Lee 2025-12-01 11:07:29 +08:00 committed by GitHub
parent a792719dbe
commit 4e0f9af789
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 38 deletions

View file

@ -11,7 +11,7 @@ use gpui_component_assets::Assets;
use gpui_component_story::{
AccordionStory, AppState, AppTitleBar, ButtonStory, CalendarStory, DialogStory, FormStory,
IconStory, ImageStory, InputStory, LabelStory, ListStory, NotificationStory, Open,
PopoverStory, ProgressStory, ResizableStory, ScrollableStory, SelectStory, SidebarStory,
PopoverStory, ProgressStory, ResizableStory, ScrollbarStory, SelectStory, SidebarStory,
StoryContainer, SwitchStory, TableStory, TooltipStory, WebViewStory,
};
use serde::Deserialize;
@ -269,7 +269,7 @@ impl StoryWorkspace {
),
DockItem::tabs(
vec![
Arc::new(StoryContainer::panel::<ScrollableStory>(window, cx)),
Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),
Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),
],
None,
@ -358,7 +358,7 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)),
Arc::new(StoryContainer::panel::<CalendarStory>(window, cx)),
Arc::new(StoryContainer::panel::<ResizableStory>(window, cx)),
Arc::new(StoryContainer::panel::<ScrollableStory>(window, cx)),
Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),
Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),
Arc::new(StoryContainer::panel::<SidebarStory>(window, cx)),
Arc::new(StoryContainer::panel::<FormStory>(window, cx)),
@ -447,7 +447,7 @@ impl StoryWorkspace {
12 => Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),
13 => Arc::new(StoryContainer::panel::<CalendarStory>(window, cx)),
14 => Arc::new(StoryContainer::panel::<ResizableStory>(window, cx)),
15 => Arc::new(StoryContainer::panel::<ScrollableStory>(window, cx)),
15 => Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),
16 => Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),
17 => Arc::new(StoryContainer::panel::<WebViewStory>(window, cx)),
_ => Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)),

View file

@ -30,7 +30,7 @@ mod popover_story;
mod progress_story;
mod radio_story;
mod resizable_story;
mod scrollable_story;
mod scrollbar_story;
mod select_story;
mod settings_story;
mod sheet_story;
@ -90,7 +90,7 @@ pub use popover_story::PopoverStory;
pub use progress_story::ProgressStory;
pub use radio_story::RadioStory;
pub use resizable_story::ResizableStory;
pub use scrollable_story::ScrollableStory;
pub use scrollbar_story::ScrollbarStory;
pub use select_story::SelectStory;
use serde::{Deserialize, Serialize};
pub use settings_story::SettingsStory;
@ -670,7 +670,7 @@ impl StoryState {
"PopoverStory" => story!(PopoverStory),
"ProgressStory" => story!(ProgressStory),
"ResizableStory" => story!(ResizableStory),
"ScrollableStory" => story!(ScrollableStory),
"ScrollbarStory" => story!(ScrollbarStory),
"SwitchStory" => story!(SwitchStory),
"TableStory" => story!(TableStory),
"LabelStory" => story!(LabelStory),

View file

@ -68,7 +68,7 @@ impl Gallery {
StoryContainer::panel::<ProgressStory>(window, cx),
StoryContainer::panel::<RadioStory>(window, cx),
StoryContainer::panel::<ResizableStory>(window, cx),
StoryContainer::panel::<ScrollableStory>(window, cx),
StoryContainer::panel::<ScrollbarStory>(window, cx),
StoryContainer::panel::<SelectStory>(window, cx),
StoryContainer::panel::<SettingsStory>(window, cx),
StoryContainer::panel::<SheetStory>(window, cx),

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use gpui::{
App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Pixels,
Render, Size, Styled, Window, div, px, size,
Render, Size, Styled, UniformListScrollHandle, Window, div, px, size, uniform_list,
};
use gpui_component::{
ActiveTheme as _, Selectable,
@ -12,17 +12,18 @@ use gpui_component::{
v_flex,
};
pub struct ScrollableStory {
pub struct ScrollbarStory {
focus_handle: FocusHandle,
items: Vec<String>,
item_sizes: Rc<Vec<Size<Pixels>>>,
test_width: Pixels,
size_mode: usize,
scroll_handle: UniformListScrollHandle,
}
const ITEM_HEIGHT: Pixels = px(30.);
const ITEM_HEIGHT: Pixels = px(50.);
impl ScrollableStory {
impl ScrollbarStory {
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
let items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
let test_width = px(3000.);
@ -37,6 +38,7 @@ impl ScrollableStory {
item_sizes: Rc::new(item_sizes),
test_width,
size_mode: 0,
scroll_handle: UniformListScrollHandle::new(),
}
}
@ -113,13 +115,13 @@ impl ScrollableStory {
}
}
impl super::Story for ScrollableStory {
impl super::Story for ScrollbarStory {
fn title() -> &'static str {
"Scrollable"
"Scrollbar"
}
fn description() -> &'static str {
"A scrollable container."
"Add scrollbar to a scrollable element."
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {
@ -127,19 +129,18 @@ impl super::Story for ScrollableStory {
}
}
impl Focusable for ScrollableStory {
impl Focusable for ScrollbarStory {
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ScrollableStory {
impl Render for ScrollbarStory {
fn render(
&mut self,
_: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
let test_width = self.test_width;
v_flex()
.size_full()
.gap_4()
@ -149,26 +150,38 @@ impl Render for ScrollableStory {
.relative()
.border_1()
.border_color(cx.theme().border)
.w_full()
.max_h(px(400.))
.min_h(px(200.))
.flex_1()
.child(
v_flex()
.w(test_width)
.p_3()
.gap_1()
.overflow_y_scrollbar()
.child("Scrollable Example")
.children(self.items.iter().map(|item| {
uniform_list("list", self.items.len(), {
let items = self.items.clone();
move |visible_range, _, cx| {
let mut elements = Vec::with_capacity(visible_range.len());
for ix in visible_range {
let item = &items[ix];
elements.push(
div()
.h(ITEM_HEIGHT)
.bg(cx.theme().background)
.pt_1()
.items_center()
.justify_center()
.text_sm()
.child(item.to_string())
})),
.child(
div()
.p_2()
.bg(cx.theme().secondary)
.child(item.to_string()),
),
);
}
elements
}
})
.py_1()
.px_3()
.size_full()
.track_scroll(self.scroll_handle.clone()),
)
.vertical_scrollbar(&self.scroll_handle)
})
}
}

View file

@ -187,6 +187,7 @@ struct StockTableDelegate {
columns: Vec<Column>,
size: Size,
loading: bool,
lazy_load: bool,
full_loading: bool,
eof: bool,
visible_rows: Range<usize>,
@ -200,6 +201,7 @@ impl StockTableDelegate {
Self {
size: Size::default(),
stocks: random_stocks(size),
lazy_load: false,
columns: vec![
Column::new("id", "ID")
.width(60.)
@ -543,6 +545,10 @@ impl TableDelegate for StockTableDelegate {
}
fn is_eof(&self, _: &App) -> bool {
if !self.lazy_load {
return true;
}
return !self.loading && !self.eof;
}
@ -551,6 +557,10 @@ impl TableDelegate for StockTableDelegate {
}
fn load_more(&mut self, _: &mut Window, cx: &mut Context<TableState<Self>>) {
if !self.lazy_load {
return;
}
self.loading = true;
self._load_task = cx.spawn(async move |view, cx| {
@ -959,7 +969,18 @@ impl Render for TableStory {
this.child(
h_flex().gap_1().child(Spinner::new()).child("Loading..."),
)
}),
})
.child(
Checkbox::new("lazy-load")
.label("Lazy Load")
.checked(delegate.lazy_load)
.on_click(cx.listener(|this, check: &bool, _, cx| {
this.table.update(cx, |table, cx| {
table.delegate_mut().lazy_load = *check;
cx.notify();
})
})),
),
)
.child(
h_flex()

View file

@ -136,7 +136,7 @@
"children": [],
"info": {
"panel": {
"story_klass": "ScrollableStory"
"story_klass": "ScrollbarStory"
}
}
}