diff --git a/crates/ui/src/virtual_list.rs b/crates/ui/src/virtual_list.rs index 66170fe6..d66698ec 100644 --- a/crates/ui/src/virtual_list.rs +++ b/crates/ui/src/virtual_list.rs @@ -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( @@ -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, + window: &mut Window, + cx: &mut App, + ) -> Size { + 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::()), - 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); } _ => {} }