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.
This commit is contained in:
Jason Lee 2025-07-24 16:30:42 +08:00 committed by GitHub
parent 56cf05b749
commit 579c7a3cbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 347 additions and 231 deletions

24
Cargo.lock generated
View file

@ -1432,7 +1432,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"indexmap 2.9.0",
"rustc-hash 2.1.0",
@ -1990,7 +1990,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"proc-macro2",
"quote",
@ -3142,7 +3142,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@ -3327,7 +3327,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -3597,7 +3597,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"bytes",
@ -3615,7 +3615,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"rustls",
"rustls-platform-verifier",
@ -4441,7 +4441,7 @@ dependencies = [
[[package]]
name = "media"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"bindgen 0.71.1",
@ -6390,7 +6390,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"derive_refineable",
"workspace-hack",
@ -6545,7 +6545,7 @@ dependencies = [
[[package]]
name = "reqwest_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"bytes",
@ -7121,7 +7121,7 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
[[package]]
name = "semantic_version"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"serde",
@ -7704,7 +7704,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"arrayvec",
"log",
@ -8931,7 +8931,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
dependencies = [
"anyhow",
"async-fs 2.1.2",

View file

@ -43,6 +43,7 @@ mod themes;
mod title_bar;
mod toggle_story;
mod tooltip_story;
mod virtual_list_story;
mod webview_story;
mod welcome_story;
@ -100,6 +101,7 @@ pub use title_bar::AppTitleBar;
pub use toggle_story::ToggleStory;
pub use tooltip_story::TooltipStory;
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
pub use virtual_list_story::VirtualListStory;
pub use webview_story::WebViewStory;
pub use welcome_story::WelcomeStory;

View file

@ -53,8 +53,8 @@ impl Gallery {
StoryContainer::panel::<DropdownStory>(window, cx),
StoryContainer::panel::<FormStory>(window, cx),
StoryContainer::panel::<IconStory>(window, cx),
StoryContainer::panel::<IndicatorStory>(window, cx),
StoryContainer::panel::<ImageStory>(window, cx),
StoryContainer::panel::<IndicatorStory>(window, cx),
StoryContainer::panel::<InputStory>(window, cx),
StoryContainer::panel::<KbdStory>(window, cx),
StoryContainer::panel::<LabelStory>(window, cx),
@ -78,6 +78,7 @@ impl Gallery {
StoryContainer::panel::<TagStory>(window, cx),
StoryContainer::panel::<TextareaStory>(window, cx),
StoryContainer::panel::<TooltipStory>(window, cx),
StoryContainer::panel::<VirtualListStory>(window, cx),
],
),
];

View file

@ -1,29 +1,23 @@
use std::rc::Rc;
use gpui::{
div, px, size, App, AppContext, Axis, Context, Entity, Focusable, InteractiveElement,
IntoElement, ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window,
div, px, size, App, AppContext, Axis, Context, Entity, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Pixels, Render, Size, Styled, Window,
};
use gpui_component::{
button::{Button, ButtonGroup},
divider::Divider,
h_flex,
label::Label,
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
v_flex, v_virtual_list, ActiveTheme as _, Selectable, StyledExt as _,
scroll::ScrollbarState,
v_flex, ActiveTheme as _, Selectable, StyledExt as _,
};
pub struct ScrollableStory {
focus_handle: gpui::FocusHandle,
scroll_handle: ScrollHandle,
scroll_size: gpui::Size<Pixels>,
focus_handle: FocusHandle,
scroll_state: ScrollbarState,
items: Vec<String>,
item_sizes: Rc<Vec<Size<Pixels>>>,
test_width: Pixels,
axis: ScrollbarAxis,
size_mode: usize,
message: SharedString,
}
const ITEM_HEIGHT: Pixels = px(30.);
@ -39,15 +33,11 @@ impl ScrollableStory {
Self {
focus_handle: cx.focus_handle(),
scroll_handle: ScrollHandle::new(),
scroll_state: ScrollbarState::default(),
scroll_size: gpui::Size::default(),
items,
item_sizes: Rc::new(item_sizes),
test_width,
axis: ScrollbarAxis::Both,
size_mode: 0,
message: SharedString::default(),
}
}
@ -83,91 +73,45 @@ impl ScrollableStory {
cx.notify();
}
pub fn change_axis(&mut self, axis: ScrollbarAxis, cx: &mut Context<Self>) {
self.axis = axis;
cx.notify();
}
fn set_message(&mut self, msg: &str, cx: &mut Context<Self>) {
self.message = SharedString::from(msg.to_string());
cx.notify();
}
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_2()
.justify_between()
.child(
h_flex()
.gap_2()
h_flex().gap_2().justify_between().child(
h_flex().gap_2().child(
ButtonGroup::new("test-cases")
.outline()
.compact()
.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)
}
})),
Button::new("test-0")
.label("Size 0")
.selected(self.size_mode == 0),
)
.child(Divider::vertical().px_2())
.child(
ButtonGroup::new("scrollbars")
.outline()
.compact()
.child(
Button::new("test-axis-both")
.label("Both Scrollbar")
.selected(self.axis.is_both()),
)
.child(
Button::new("test-axis-vertical")
.label("Vertical")
.selected(self.axis.is_vertical()),
)
.child(
Button::new("test-axis-horizontal")
.label("Horizontal")
.selected(self.axis.is_horizontal()),
)
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
if clicks.contains(&0) {
view.change_axis(ScrollbarAxis::Both, cx)
} else if clicks.contains(&1) {
view.change_axis(ScrollbarAxis::Vertical, cx)
} else if clicks.contains(&2) {
view.change_axis(ScrollbarAxis::Horizontal, cx)
}
})),
),
)
.child(Label::new(self.message.clone()))
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)
}
})),
),
)
}
}
@ -177,8 +121,7 @@ impl super::Story for ScrollableStory {
}
fn description() -> &'static str {
"Add vertical or horizontal, or both scrollbars to a container, \
and use `virtual_list` to render a large number of items."
"A scrollable container."
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
@ -198,79 +141,11 @@ impl Render for ScrollableStory {
_: &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().w_full().flex_1().min_h_64().child(
div().relative().size_full().child(
v_flex()
.id("test-0")
.relative()
.size_full()
.child(
v_virtual_list(
cx.entity().clone(),
"items",
self.item_sizes.clone(),
move |story, visible_range, content_size, _, cx| {
story.set_message(
&format!("visible_range: {:?}", visible_range),
cx,
);
story.scroll_size = content_size;
visible_range
.map(|ix| {
h_flex()
.h(ITEM_HEIGHT)
.gap_1()
.children(
(0..(story.test_width.0 as i32 / 100))
.map(|i| {
div()
.flex()
.h_full()
.items_center()
.justify_center()
.text_sm()
.w(px(100.))
.bg(cx.theme().secondary)
.child(if i == 0 {
format!("{}", ix)
} else {
format!("{}", i)
})
})
.collect::<Vec<_>>(),
)
.items_center()
})
.collect::<Vec<_>>()
},
)
.track_scroll(&self.scroll_handle)
.p_4()
.border_1()
.border_color(cx.theme().border)
.v_flex()
.gap_1(),
)
.child({
div()
.absolute()
.top_0()
.left_0()
.right_0()
.bottom_0()
.child(
Scrollbar::both(&self.scroll_state, &self.scroll_handle)
.axis(self.axis),
)
}),
),
),
)
.child({
div()
.relative()
@ -282,7 +157,7 @@ impl Render for ScrollableStory {
.child(
v_flex()
.p_3()
.w(self.test_width)
.w(test_width)
.id("test-1")
.scrollable(Axis::Vertical)
.gap_1()

View file

@ -0,0 +1,256 @@
use std::{ops::Range, rc::Rc};
use gpui::{
div, px, size, App, AppContext, Context, Div, Entity, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Pixels, Render, ScrollHandle, Size, Styled,
Window,
};
use gpui_component::{
button::{Button, ButtonGroup},
divider::Divider,
h_flex,
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
v_flex, v_virtual_list, ActiveTheme as _, Selectable,
};
pub struct VirtualListStory {
focus_handle: FocusHandle,
scroll_handle: ScrollHandle,
scroll_state: ScrollbarState,
items: Vec<String>,
item_sizes: Rc<Vec<Size<Pixels>>>,
columns_count: usize,
axis: ScrollbarAxis,
size_mode: usize,
visible_range: Range<usize>,
}
const ITEM_SIZE: Size<Pixels> = size(px(100.), px(30.));
impl VirtualListStory {
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
let items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
let item_sizes = items.iter().map(|_| ITEM_SIZE).collect::<Vec<_>>();
Self {
focus_handle: cx.focus_handle(),
scroll_handle: ScrollHandle::new(),
scroll_state: ScrollbarState::default(),
items,
item_sizes: Rc::new(item_sizes),
columns_count: 100,
axis: ScrollbarAxis::Both,
size_mode: 0,
visible_range: (0..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.columns_count = 30;
} else if n == 1 {
self.items = (0..100).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
self.columns_count = 100;
} else if n == 2 {
self.items = (0..500000)
.map(|i| format!("Item {}", i))
.collect::<Vec<_>>();
self.columns_count = 100;
} else {
self.items = (0..5).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
self.columns_count = 10;
}
self.item_sizes = Rc::new(self.items.iter().map(|_| ITEM_SIZE).collect());
self.scroll_state = ScrollbarState::default();
cx.notify();
}
pub fn change_axis(&mut self, axis: ScrollbarAxis, cx: &mut Context<Self>) {
self.axis = axis;
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)
}
})),
)
.child(Divider::vertical().px_2())
.child(
ButtonGroup::new("scrollbars")
.outline()
.compact()
.child(
Button::new("test-axis-both")
.label("Both Scrollbar")
.selected(self.axis.is_both()),
)
.child(
Button::new("test-axis-vertical")
.label("Vertical")
.selected(self.axis.is_vertical()),
)
.child(
Button::new("test-axis-horizontal")
.label("Horizontal")
.selected(self.axis.is_horizontal()),
)
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
if clicks.contains(&0) {
view.change_axis(ScrollbarAxis::Both, cx)
} else if clicks.contains(&1) {
view.change_axis(ScrollbarAxis::Vertical, cx)
} else if clicks.contains(&2) {
view.change_axis(ScrollbarAxis::Horizontal, cx)
}
})),
),
)
.child(format!("visible_range: {:?}", self.visible_range))
}
}
impl super::Story for VirtualListStory {
fn title() -> &'static str {
"VirtualList"
}
fn description() -> &'static str {
"Add vertical or horizontal, or both scrollbars to a container, \
and use `virtual_list` to render a large number of items."
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
Self::view(window, cx)
}
}
impl Focusable for VirtualListStory {
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for VirtualListStory {
fn render(
&mut self,
_: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
let columns_count = self.columns_count;
fn render_item(cx: &App) -> Div {
div()
.flex()
.h_full()
.items_center()
.justify_center()
.text_sm()
.w(ITEM_SIZE.width)
.h(ITEM_SIZE.height)
.bg(cx.theme().secondary)
}
v_flex()
.size_full()
.gap_4()
.child(self.render_buttons(cx))
.child(
div().w_full().flex_1().min_h_64().child(
div().relative().size_full().child(
v_flex()
.id("list")
.relative()
.size_full()
.child(
v_virtual_list(
cx.entity().clone(),
"items",
self.item_sizes.clone(),
move |story, visible_range, _, cx| {
story.visible_range = visible_range.clone();
visible_range
.map(|ix| {
h_flex().gap_1().items_center().children(
(0..columns_count).map(|i| {
render_item(cx).child(if i == 0 {
format!("row: {}", ix)
} else {
format!("{}", i)
})
}),
)
})
.collect()
},
)
.track_scroll(&self.scroll_handle)
.p_4()
.border_1()
.border_color(cx.theme().border)
.gap_1(),
)
.child({
div()
.absolute()
.top_0()
.left_0()
.right_0()
.bottom_0()
.child(
Scrollbar::both(&self.scroll_state, &self.scroll_handle)
.axis(self.axis),
)
}),
),
),
)
}
}

View file

@ -1093,7 +1093,7 @@ where
Axis::Horizontal,
col_sizes,
{
move |table, visible_range: Range<usize>, _, window, cx| {
move |table, visible_range: Range<usize>, window, cx| {
table.update_visible_range_if_need(
visible_range.clone(),
Axis::Horizontal,

View file

@ -32,7 +32,7 @@ pub fn v_virtual_list<R, V>(
view: Entity<V>,
id: impl Into<ElementId>,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
@ -49,7 +49,7 @@ pub fn h_virtual_list<R, V>(
view: Entity<V>,
id: impl Into<ElementId>,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
@ -63,7 +63,7 @@ pub(crate) fn virtual_list<R, V>(
id: impl Into<ElementId>,
axis: Axis,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
@ -71,9 +71,9 @@ where
{
let id: ElementId = id.into();
let scroll_handle = ScrollHandle::default();
let render_range = move |visible_range, content_size, window: &mut Window, cx: &mut App| {
let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
view.update(cx, |this, cx| {
f(this, visible_range, content_size, window, cx)
f(this, visible_range, window, cx)
.into_iter()
.map(|component| component.into_any_element())
.collect()
@ -101,16 +101,10 @@ pub struct VirtualList {
axis: Axis,
base: Stateful<Div>,
scroll_handle: ScrollHandle,
// scroll_handle: ScrollHandle,
items_count: usize,
item_sizes: Rc<Vec<Size<Pixels>>>,
render_items: Box<
dyn for<'a> Fn(
Range<usize>,
Size<Pixels>,
&'a mut Window,
&'a mut App,
) -> SmallVec<[AnyElement; 64]>,
dyn for<'a> Fn(Range<usize>, &'a mut Window, &'a mut App) -> SmallVec<[AnyElement; 64]>,
>,
}
@ -146,7 +140,7 @@ impl VirtualList {
// So we try to use the second item to measure, if there is no second item, use the first item.
let item_ix = if self.items_count > 1 { 1 } else { 0 };
let mut items = (self.render_items)(item_ix..item_ix + 1, Size::default(), window, cx);
let mut items = (self.render_items)(item_ix..item_ix + 1, window, cx);
let Some(mut item_to_measure) = items.pop() else {
return Size::default();
};
@ -165,7 +159,7 @@ pub struct VirtualListFrameState {
#[derive(Default, Clone)]
pub struct ItemSizeLayout {
items_sizes: Rc<Vec<Size<Pixels>>>,
container_size: Size<Pixels>,
content_size: Pixels,
sizes: Vec<Pixels>,
origins: Vec<Pixels>,
}
@ -197,18 +191,18 @@ impl Element for VirtualList {
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let rem_size = window.rem_size();
let style = self
.base
.interactivity()
.compute_style(global_id, None, window, cx);
let font_size = window.text_style().font_size.to_pixels(window.rem_size());
let font_size = window.text_style().font_size.to_pixels(rem_size);
// Including the gap between items for calculate the item size
let gap = match self.axis {
Axis::Horizontal => style.gap.width,
Axis::Vertical => style.gap.height,
}
.to_pixels(font_size.into(), window.rem_size());
let gap = style
.gap
.along(self.axis)
.to_pixels(font_size.into(), rem_size);
let (layout_id, size_layout) = window.with_element_state(
global_id.unwrap(),
@ -249,14 +243,7 @@ impl Element for VirtualList {
}
})
.collect::<Vec<_>>();
state.container_size = Size {
width: px(self.item_sizes.iter().map(|size| size.width.0).sum::<f32>()),
height: px(self
.item_sizes
.iter()
.map(|size| size.height.0)
.sum::<f32>()),
};
state.content_size = px(state.sizes.iter().map(|size| size.0).sum::<f32>());
}
let (layout_id, _) = self
@ -285,6 +272,8 @@ impl Element for VirtualList {
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState {
let first_item_size = self.measure_item(window, cx);
let style = self
.base
.interactivity()
@ -294,16 +283,14 @@ impl Element for VirtualList {
.padding
.to_pixels(bounds.size.into(), window.rem_size());
let first_item_size = self.measure_item(window, cx);
let padded_bounds = Bounds::from_corners(
let container_with_padding_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top + padding.top),
bounds.bottom_right()
- point(border.right + padding.right, border.bottom + padding.bottom),
);
// Get border + padding pixel size
let padding_size = match self.axis {
let padding_width = match self.axis {
Axis::Horizontal => border.left + padding.left + border.right + padding.right,
Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom,
};
@ -311,14 +298,16 @@ impl Element for VirtualList {
let item_sizes = &layout.size_layout.sizes;
let item_origins = &layout.size_layout.origins;
let content_size = match self.axis {
let scroll_size = match self.axis {
Axis::Horizontal => Size {
width: layout.size_layout.container_size.width + padding_size,
height: (first_item_size.height + padding_size).max(padded_bounds.size.height),
width: layout.size_layout.content_size + padding_width,
height: (first_item_size.height + padding_width)
.max(container_with_padding_bounds.size.height),
},
Axis::Vertical => Size {
width: (first_item_size.width + padding_size).max(padded_bounds.size.width),
height: layout.size_layout.container_size.height + padding_size,
width: (first_item_size.width + padding_width)
.max(container_with_padding_bounds.size.width),
height: layout.size_layout.content_size + padding_width,
},
};
@ -326,7 +315,7 @@ impl Element for VirtualList {
global_id,
inspector_id,
bounds,
content_size,
scroll_size,
window,
cx,
|style, _, hitbox, window, cx| {
@ -342,15 +331,9 @@ impl Element for VirtualList {
);
if self.items_count > 0 {
let is_scrolled = match self.axis {
Axis::Horizontal => !scroll_offset.x.is_zero(),
Axis::Vertical => !scroll_offset.y.is_zero(),
};
let min_scroll_offset = match self.axis {
Axis::Horizontal => padded_bounds.size.width - content_size.width,
Axis::Vertical => padded_bounds.size.height - content_size.height,
};
let is_scrolled = !scroll_offset.along(self.axis).is_zero();
let min_scroll_offset =
padded_bounds.size.along(self.axis) - scroll_size.along(self.axis);
if is_scrolled {
match self.axis {
@ -425,8 +408,7 @@ impl Element for VirtualList {
let visible_range = first_visible_element_ix
..cmp::min(last_visible_element_ix, self.items_count);
let items =
(self.render_items)(visible_range.clone(), content_size, window, cx);
let items = (self.render_items)(visible_range.clone(), window, cx);
let content_mask = ContentMask { bounds };
window.with_content_mask(Some(content_mask), |window| {