virtual_list: Add element state to cache item size calculate. (#1048)

This commit is contained in:
Jason Lee 2025-07-07 11:47:35 +08:00 committed by GitHub
parent 4156c831c6
commit 40a2467374
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 68 deletions

View file

@ -143,6 +143,7 @@ pub fn measure_if(name: impl Into<SharedString>, if_: bool, f: impl FnOnce()) {
/// Measures the execution time.
#[inline]
#[track_caller]
pub fn measure(name: impl Into<SharedString>, f: impl FnOnce()) {
measure_if(name, true, f);
}
@ -153,6 +154,7 @@ pub struct Measure {
}
impl Measure {
#[track_caller]
pub fn new(name: impl Into<SharedString>) -> Self {
Self {
name: name.into(),
@ -160,6 +162,7 @@ impl Measure {
}
}
#[track_caller]
pub fn end(self) {
let duration = self.start.elapsed();
tracing::trace!("{} in {:?}", self.name, duration);

View file

@ -13,18 +13,20 @@
use std::{cmp, ops::Range, rc::Rc};
use gpui::{
div, point, px, size, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask, Context, Div,
Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement, IntoElement,
IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
StyleRefinement, Styled, Window,
div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
Context, Div, Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement,
IntoElement, IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
use smallvec::SmallVec;
/// Create a virtual list in Vertical direction.
/// Create a [`VirtualList`] in vertical direction.
///
/// This is like `uniform_list` in GPUI, but support two axis.
///
/// The `item_sizes` is the size of each column.
///
/// See also [`h_virtual_list`]
#[inline]
pub fn v_virtual_list<R, V>(
view: Entity<V>,
@ -39,7 +41,9 @@ where
virtual_list(view, id, Axis::Vertical, item_sizes, f)
}
/// Create a virtual list in Horizontal direction.
/// Create a [`VirtualList`] in horizontal direction.
///
/// See also [`v_virtual_list`]
#[inline]
pub fn h_virtual_list<R, V>(
view: Entity<V>,
@ -91,7 +95,7 @@ where
}
}
/// VirtualItem component for rendering a large number of differently sized columns.
/// VirtualList component for rendering a large number of differently sized items.
pub struct VirtualList {
id: ElementId,
axis: Axis,
@ -153,8 +157,15 @@ impl VirtualList {
pub struct VirtualListFrameState {
/// Visible items to be painted.
items: SmallVec<[AnyElement; 32]>,
item_sizes: Vec<Pixels>,
item_origins: Vec<Pixels>,
size_layout: ItemSizeLayout,
}
#[derive(Default, Clone)]
pub struct ItemSizeLayout {
items_sizes: Rc<Vec<Size<Pixels>>>,
container_size: Size<Pixels>,
sizes: Vec<Pixels>,
origins: Vec<Pixels>,
}
impl IntoElement for VirtualList {
@ -197,68 +208,68 @@ impl Element for VirtualList {
}
.to_pixels(font_size.into(), window.rem_size());
// TODO: To cache the item_sizes, item_origins
// If there have 500,000 items, this method will speed about 500~600µs
// let start = std::time::Instant::now();
// Prepare each item's size by axis
let item_sizes = match self.axis {
Axis::Horizontal => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.width
} else {
size.width + gap
}
})
.collect::<Vec<_>>(),
Axis::Vertical => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.height
} else {
size.height + gap
}
})
.collect::<Vec<_>>(),
};
let (layout_id, size_layout) = window.with_element_state(
global_id.unwrap(),
|state: Option<ItemSizeLayout>, window| {
let mut state = state.unwrap_or(ItemSizeLayout::default());
// Prepare each item's origin by axis
let item_origins = match self.axis {
Axis::Horizontal => item_sizes
.iter()
.scan(px(0.), |cumulative_x, size| {
let x = *cumulative_x;
*cumulative_x += *size;
Some(x)
})
.collect::<Vec<_>>(),
Axis::Vertical => item_sizes
.iter()
.scan(px(0.), |cumulative_y, size| {
let y = *cumulative_y;
*cumulative_y += *size;
Some(y)
})
.collect::<Vec<_>>(),
};
// println!("layout: {} {:?}", item_sizes.len(), start.elapsed());
if state.items_sizes != self.item_sizes {
state.items_sizes = self.item_sizes.clone();
// Prepare each item's size by axis
state.sizes = self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
let size = size.along(self.axis);
if i + 1 == self.items_count {
size
} else {
size + gap
}
})
.collect::<Vec<_>>();
let (layout_id, _) = self
.base
.request_layout(global_id, inspector_id, window, cx);
// Prepare each item's origin by axis
state.origins = state
.sizes
.iter()
.scan(px(0.), |cumulative, size| match self.axis {
Axis::Horizontal => {
let x = *cumulative;
*cumulative += *size;
Some(x)
}
Axis::Vertical => {
let y = *cumulative;
*cumulative += *size;
Some(y)
}
})
.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>()),
};
}
let (layout_id, _) = self
.base
.request_layout(global_id, inspector_id, window, cx);
((layout_id, state.clone()), state)
},
);
(
layout_id,
VirtualListFrameState {
items: SmallVec::new(),
item_sizes,
item_origins,
size_layout,
},
)
}
@ -295,17 +306,17 @@ impl Element for VirtualList {
Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom,
};
let item_sizes = &layout.item_sizes;
let item_origins = &layout.item_origins;
let item_sizes = &layout.size_layout.sizes;
let item_origins = &layout.size_layout.origins;
let content_size = match self.axis {
Axis::Horizontal => Size {
width: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size,
width: layout.size_layout.container_size.width + padding_size,
height: (first_item_size.height + padding_size).max(padded_bounds.size.height),
},
Axis::Vertical => Size {
width: (first_item_size.width + padding_size).max(padded_bounds.size.width),
height: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size,
height: layout.size_layout.container_size.height + padding_size,
},
};