virtual_list: Fix VirtualList by measure item to get correct content_size to avoid scroll overflow. (#1388)

- Fixed VirtualList example the horizontal scrollbar not appear.
- Fixed VirtualList example scroll slow, this because not call
`self.scroll_handle.set_offset` to update.
This commit is contained in:
Jason Lee 2025-10-16 15:50:09 +08:00 committed by GitHub
parent 2a9bc2a2f8
commit e0c1f91ed4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -119,7 +119,8 @@ impl VirtualListScrollHandle {
///
/// This is like `uniform_list` in GPUI, but support two axis.
///
/// The `item_sizes` is the size of each column.
/// The `item_sizes` is the size of each column,
/// only the `height` is used, `width` is ignored and VirtualList will measure the first item width.
///
/// See also [`h_virtual_list`]
#[inline]
@ -138,6 +139,9 @@ where
/// Create a [`VirtualList`] in horizontal direction.
///
/// The `item_sizes` is the size of each column,
/// only the `width` is used, `height` is ignored and VirtualList will measure the first item height.
///
/// See also [`v_virtual_list`]
#[inline]
pub fn h_virtual_list<R, V>(
@ -280,6 +284,31 @@ impl VirtualList {
self.scroll_handle.set_offset(scroll_offset);
scroll_offset
}
/// Ref from: https://github.com/zed-industries/zed/blob/83f9f9d9e3f5914392cab9a09e3472711a1d7b38/crates/gpui/src/elements/uniform_list.rs#L660
fn measure_item(
&self,
list_width: Option<Pixels>,
window: &mut Window,
cx: &mut App,
) -> Size<Pixels> {
if self.items_count == 0 {
return Size::default();
}
let item_ix = 0;
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();
};
let available_space = size(
list_width.map_or(AvailableSpace::MinContent, |width| {
AvailableSpace::Definite(width)
}),
AvailableSpace::MinContent,
);
item_to_measure.layout_as_root(available_space, window, cx)
}
}
/// Frame state used by the [VirtualItem].
@ -328,6 +357,7 @@ impl Element for VirtualList {
let rem_size = window.rem_size();
let font_size = window.text_style().font_size.to_pixels(rem_size);
let mut size_layout = ItemSizeLayout::default();
let longest_item_size = self.measure_item(None, window, cx);
let layout_id = self.base.interactivity().request_layout(
global_id,
@ -388,17 +418,11 @@ impl Element for VirtualList {
.iter()
.map(|size| size.as_f32())
.sum::<f32>()),
height: state
.items_sizes
.get(0)
.map_or(px(0.), |size| size.height),
height: longest_item_size.height,
}
} else {
Size {
width: state
.items_sizes
.get(0)
.map_or(px(0.), |size| size.width),
width: longest_item_size.width,
height: px(state
.sizes
.iter()
@ -557,7 +581,16 @@ impl Element for VirtualList {
scroll_to_item,
);
}
scroll_offset = scroll_offset.min(&point(px(0.), px(0.)));
scroll_offset = scroll_offset
.max(&point(
content_bounds.size.width - layout.size_layout.content_size.width,
content_bounds.size.height - layout.size_layout.content_size.height,
))
.min(&point(px(0.), px(0.)));
if scroll_offset != self.scroll_handle.offset() {
self.scroll_handle.set_offset(scroll_offset);
}
self.base.interactivity().prepaint(
global_id,
@ -571,20 +604,16 @@ impl Element for VirtualList {
let min_scroll_offset = content_bounds.size.along(self.axis)
- layout.size_layout.content_size.along(self.axis);
// Do not trigger scrolling if the content is smaller than the container.
if min_scroll_offset.as_f32() >= 0. {
scroll_offset.x = px(0.);
scroll_offset.y = px(0.);
}
let is_scrolled = !scroll_offset.along(self.axis).is_zero();
if is_scrolled {
match self.axis {
Axis::Horizontal if scroll_offset.x < min_scroll_offset => {
scroll_offset.x = min_scroll_offset;
self.scroll_handle.set_offset(scroll_offset);
}
Axis::Vertical if scroll_offset.y < min_scroll_offset => {
scroll_offset.y = min_scroll_offset;
self.scroll_handle.set_offset(scroll_offset);
}
_ => {}
}