From 40a246737406193358e0b49d09cd235dee236768 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 7 Jul 2025 11:47:35 +0800 Subject: [PATCH] virtual_list: Add element state to cache item size calculate. (#1048) --- crates/ui/src/lib.rs | 3 + crates/ui/src/virtual_list.rs | 147 ++++++++++++++++++---------------- 2 files changed, 82 insertions(+), 68 deletions(-) diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 84e2dcd6..6efc658f 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -143,6 +143,7 @@ pub fn measure_if(name: impl Into, if_: bool, f: impl FnOnce()) { /// Measures the execution time. #[inline] +#[track_caller] pub fn measure(name: impl Into, 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) -> 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); diff --git a/crates/ui/src/virtual_list.rs b/crates/ui/src/virtual_list.rs index 8958afe2..dfac0339 100644 --- a/crates/ui/src/virtual_list.rs +++ b/crates/ui/src/virtual_list.rs @@ -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( view: Entity, @@ -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( view: Entity, @@ -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, - item_origins: Vec, + size_layout: ItemSizeLayout, +} + +#[derive(Default, Clone)] +pub struct ItemSizeLayout { + items_sizes: Rc>>, + container_size: Size, + sizes: Vec, + origins: Vec, } 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::>(), - Axis::Vertical => self - .item_sizes - .iter() - .enumerate() - .map(|(i, size)| { - if i == self.items_count - 1 { - size.height - } else { - size.height + gap - } - }) - .collect::>(), - }; + let (layout_id, size_layout) = window.with_element_state( + global_id.unwrap(), + |state: Option, 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::>(), - Axis::Vertical => item_sizes - .iter() - .scan(px(0.), |cumulative_y, size| { - let y = *cumulative_y; - *cumulative_y += *size; - Some(y) - }) - .collect::>(), - }; - // 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::>(); - 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::>(); + state.container_size = Size { + width: px(self.item_sizes.iter().map(|size| size.width.0).sum::()), + height: px(self + .item_sizes + .iter() + .map(|size| size.height.0) + .sum::()), + }; + } + + 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::()) + 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::()) + padding_size, + height: layout.size_layout.container_size.height + padding_size, }, };