gpui-component/crates/story/src/scrollable_story.rs
Jason Lee 579c7a3cbb
virtual_list: Fix scrollable size calculate in VirtualList. (#1087)
The `virtual_list` scroll range issue because recently GPUI changed the
`ScrollHandle` API, now we only get the `max_offset`.

So #1086 have fixed this to implementation a `content_size` method to
calculate `max_offset` and `bounds` of the `ScrollHandle`.

But I found that the `Interactivity` prepaint (This is used in
VirtualList) was not to update the `bounds` of the `ScrollHandle`.

Wait https://github.com/zed-industries/zed/pull/35013 to merge.

| Before | After |
| -- | --- |
| <img width="620" alt="image"
src="https://github.com/user-attachments/assets/6ffb8f30-93b4-42ec-a837-90f80ecb2bf5"
/> | <img width="620" alt="image"
src="https://github.com/user-attachments/assets/21466332-030c-495f-8c84-e3a2c6eeb403"
/> |
- Split `Scrollbale` and `VirtualList` story.

## Break Changes

- The `content_size` argument has been removed from `virtual_list`
callback.
2025-07-24 08:30:42 +00:00

177 lines
5.8 KiB
Rust

use std::rc::Rc;
use gpui::{
div, px, size, App, AppContext, Axis, Context, Entity, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Pixels, Render, Size, Styled, Window,
};
use gpui_component::{
button::{Button, ButtonGroup},
h_flex,
scroll::ScrollbarState,
v_flex, ActiveTheme as _, Selectable, StyledExt as _,
};
pub struct ScrollableStory {
focus_handle: FocusHandle,
scroll_state: ScrollbarState,
items: Vec<String>,
item_sizes: Rc<Vec<Size<Pixels>>>,
test_width: Pixels,
size_mode: usize,
}
const ITEM_HEIGHT: Pixels = px(30.);
impl ScrollableStory {
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.);
let item_sizes = items
.iter()
.map(|_| size(test_width, ITEM_HEIGHT))
.collect::<Vec<_>>();
Self {
focus_handle: cx.focus_handle(),
scroll_state: ScrollbarState::default(),
items,
item_sizes: Rc::new(item_sizes),
test_width,
size_mode: 0,
}
}
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
pub fn change_test_cases(&mut self, n: usize, cx: &mut Context<Self>) {
self.size_mode = n;
if n == 0 {
self.items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
self.test_width = px(3000.);
} else if n == 1 {
self.items = (0..100).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
self.test_width = px(10000.);
} else if n == 2 {
self.items = (0..500000)
.map(|i| format!("Item {}", i))
.collect::<Vec<_>>();
self.test_width = px(10000.);
} else {
self.items = (0..5).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
self.test_width = px(10000.);
}
self.item_sizes = self
.items
.iter()
.map(|_| size(self.test_width, ITEM_HEIGHT))
.collect::<Vec<_>>()
.into();
self.scroll_state = ScrollbarState::default();
cx.notify();
}
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
h_flex().gap_2().justify_between().child(
h_flex().gap_2().child(
ButtonGroup::new("test-cases")
.outline()
.compact()
.child(
Button::new("test-0")
.label("Size 0")
.selected(self.size_mode == 0),
)
.child(
Button::new("test-1")
.label("Size 1")
.selected(self.size_mode == 1),
)
.child(
Button::new("test-2")
.label("Size 2")
.selected(self.size_mode == 2),
)
.child(
Button::new("test-3")
.label("Size 3")
.selected(self.size_mode == 3),
)
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
if clicks.contains(&0) {
view.change_test_cases(0, cx)
} else if clicks.contains(&1) {
view.change_test_cases(1, cx)
} else if clicks.contains(&2) {
view.change_test_cases(2, cx)
} else if clicks.contains(&3) {
view.change_test_cases(3, cx)
}
})),
),
)
}
}
impl super::Story for ScrollableStory {
fn title() -> &'static str {
"Scrollable"
}
fn description() -> &'static str {
"A scrollable container."
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
Self::view(window, cx)
}
}
impl Focusable for ScrollableStory {
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ScrollableStory {
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()
.child(self.render_buttons(cx))
.child({
div()
.relative()
.border_1()
.border_color(cx.theme().border)
.w_full()
.max_h(px(400.))
.min_h(px(200.))
.child(
v_flex()
.p_3()
.w(test_width)
.id("test-1")
.scrollable(Axis::Vertical)
.gap_1()
.child("Scrollable Example")
.children(self.items.iter().take(500).map(|item| {
div()
.h(ITEM_HEIGHT)
.bg(cx.theme().background)
.items_center()
.justify_center()
.text_sm()
.child(item.to_string())
})),
)
})
}
}