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

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use gpui::{ use gpui::{
App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, 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::{ use gpui_component::{
ActiveTheme as _, Selectable, ActiveTheme as _, Selectable,
@ -12,17 +12,18 @@ use gpui_component::{
v_flex, v_flex,
}; };
pub struct ScrollableStory { pub struct ScrollbarStory {
focus_handle: FocusHandle, focus_handle: FocusHandle,
items: Vec<String>, items: Vec<String>,
item_sizes: Rc<Vec<Size<Pixels>>>, item_sizes: Rc<Vec<Size<Pixels>>>,
test_width: Pixels, test_width: Pixels,
size_mode: usize, 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 { fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
let items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>(); let items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
let test_width = px(3000.); let test_width = px(3000.);
@ -37,6 +38,7 @@ impl ScrollableStory {
item_sizes: Rc::new(item_sizes), item_sizes: Rc::new(item_sizes),
test_width, test_width,
size_mode: 0, 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 { fn title() -> &'static str {
"Scrollable" "Scrollbar"
} }
fn description() -> &'static str { 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> { 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 { fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for ScrollableStory { impl Render for ScrollbarStory {
fn render( fn render(
&mut self, &mut self,
_: &mut gpui::Window, _: &mut gpui::Window,
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
let test_width = self.test_width;
v_flex() v_flex()
.size_full() .size_full()
.gap_4() .gap_4()
@ -149,26 +150,38 @@ impl Render for ScrollableStory {
.relative() .relative()
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.w_full() .flex_1()
.max_h(px(400.))
.min_h(px(200.))
.child( .child(
v_flex() uniform_list("list", self.items.len(), {
.w(test_width) let items = self.items.clone();
.p_3() move |visible_range, _, cx| {
.gap_1() let mut elements = Vec::with_capacity(visible_range.len());
.overflow_y_scrollbar() for ix in visible_range {
.child("Scrollable Example") let item = &items[ix];
.children(self.items.iter().map(|item| { elements.push(
div() div()
.h(ITEM_HEIGHT) .h(ITEM_HEIGHT)
.bg(cx.theme().background) .pt_1()
.items_center() .items_center()
.justify_center() .justify_center()
.text_sm() .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>, columns: Vec<Column>,
size: Size, size: Size,
loading: bool, loading: bool,
lazy_load: bool,
full_loading: bool, full_loading: bool,
eof: bool, eof: bool,
visible_rows: Range<usize>, visible_rows: Range<usize>,
@ -200,6 +201,7 @@ impl StockTableDelegate {
Self { Self {
size: Size::default(), size: Size::default(),
stocks: random_stocks(size), stocks: random_stocks(size),
lazy_load: false,
columns: vec![ columns: vec![
Column::new("id", "ID") Column::new("id", "ID")
.width(60.) .width(60.)
@ -543,6 +545,10 @@ impl TableDelegate for StockTableDelegate {
} }
fn is_eof(&self, _: &App) -> bool { fn is_eof(&self, _: &App) -> bool {
if !self.lazy_load {
return true;
}
return !self.loading && !self.eof; 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>>) { fn load_more(&mut self, _: &mut Window, cx: &mut Context<TableState<Self>>) {
if !self.lazy_load {
return;
}
self.loading = true; self.loading = true;
self._load_task = cx.spawn(async move |view, cx| { self._load_task = cx.spawn(async move |view, cx| {
@ -959,7 +969,18 @@ impl Render for TableStory {
this.child( this.child(
h_flex().gap_1().child(Spinner::new()).child("Loading..."), 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( .child(
h_flex() h_flex()

View file

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