Skip to main content

rgpui_component/
virtual_list.rs

1//! 虚拟列表,用于渲染大量不同尺寸的行/列。
2//!
3//! > 注意:必须确保每列宽度或每行高度已知。
4//!
5//! 出于性能考虑,仅渲染可见范围内的元素。
6//!
7//! 灵感来源于 `gpui::uniform_list`。
8//! https://github.com/zed-industries/zed/blob/0ae1603610ab6b265bdfbee7b8dbc23c5ab06edc/crates/gpui/src/elements/uniform_list.rs
9//!
10//! 与 `uniform_list` 不同,每个项可以具有不同的尺寸。
11//!
12//! 这对于更复杂的布局非常有用,例如具有不同行高的表格。
13use std::{
14    cell::RefCell,
15    cmp,
16    ops::{Deref, Range},
17    rc::Rc,
18};
19
20use rgpui::{
21    Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask, Context,
22    DeferredScrollToItem, Div, Element, ElementId, Entity, GlobalElementId, Half, Hitbox,
23    InteractiveElement, IntoElement, IsZero as _, ListSizingBehavior, Pixels, Point, Render,
24    ScrollHandle, ScrollStrategy, Size, Stateful, StatefulInteractiveElement, StyleRefinement,
25    Styled, Window, div, point, px, size,
26};
27use smallvec::SmallVec;
28
29use crate::{AxisExt, scroll::ScrollbarHandle};
30
31/// 虚拟列表滚动句柄的内部状态
32struct VirtualListScrollHandleState {
33    /// 布局方向
34    axis: Axis,
35    /// 项总数
36    items_count: usize,
37    /// 延迟滚动到项的信息
38    pub deferred_scroll_to_item: Option<DeferredScrollToItem>,
39}
40
41/// [`VirtualList`] 的滚动句柄
42///
43/// 参见 [`ScrollHandle`]。
44#[derive(Clone)]
45pub struct VirtualListScrollHandle {
46    /// 内部状态
47    state: Rc<RefCell<VirtualListScrollHandleState>>,
48    /// 基础滚动句柄
49    base_handle: ScrollHandle,
50}
51
52impl From<ScrollHandle> for VirtualListScrollHandle {
53    fn from(handle: ScrollHandle) -> Self {
54        let mut this = VirtualListScrollHandle::new();
55        this.base_handle = handle;
56        this
57    }
58}
59
60impl AsRef<ScrollHandle> for VirtualListScrollHandle {
61    fn as_ref(&self) -> &ScrollHandle {
62        &self.base_handle
63    }
64}
65
66impl ScrollbarHandle for VirtualListScrollHandle {
67    fn offset(&self) -> Point<Pixels> {
68        self.base_handle.offset()
69    }
70
71    fn set_offset(&self, offset: Point<Pixels>) {
72        self.base_handle.set_offset(offset);
73    }
74
75    fn content_size(&self) -> Size<Pixels> {
76        self.base_handle.content_size()
77    }
78}
79
80impl Deref for VirtualListScrollHandle {
81    type Target = ScrollHandle;
82
83    fn deref(&self) -> &Self::Target {
84        &self.base_handle
85    }
86}
87
88impl VirtualListScrollHandle {
89    /// 创建新的虚拟列表滚动句柄
90    pub fn new() -> Self {
91        VirtualListScrollHandle {
92            state: Rc::new(RefCell::new(VirtualListScrollHandleState {
93                axis: Axis::Vertical,
94                items_count: 0,
95                deferred_scroll_to_item: None,
96            })),
97            base_handle: ScrollHandle::default(),
98        }
99    }
100
101    /// 获取基础滚动句柄
102    pub fn base_handle(&self) -> &ScrollHandle {
103        &self.base_handle
104    }
105
106    /// 滚动到指定索引的项
107    pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
108        self.scroll_to_item_with_offset(ix, strategy, 0);
109    }
110
111    /// 滚动到指定索引的项,带额外偏移
112    fn scroll_to_item_with_offset(&self, ix: usize, strategy: ScrollStrategy, offset: usize) {
113        let mut state = self.state.borrow_mut();
114        state.deferred_scroll_to_item = Some(DeferredScrollToItem {
115            item_index: ix,
116            strategy,
117            offset,
118            scroll_strict: false,
119        });
120    }
121
122    /// 滚动到列表底部
123    pub fn scroll_to_bottom(&self) {
124        let items_count = self.state.borrow().items_count;
125        self.scroll_to_item(items_count.saturating_sub(1), ScrollStrategy::Top);
126    }
127}
128
129/// 创建垂直方向的 [`VirtualList`]
130///
131/// 类似于 GPUI 中的 `uniform_list`,但支持两个方向。
132///
133/// `item_sizes` 是每个项的尺寸,
134/// 仅使用 `height`,`width` 将被忽略,虚拟列表会测量第一个项的宽度。
135///
136/// 参见 [`h_virtual_list`]
137#[inline]
138pub fn v_virtual_list<R, V>(
139    view: Entity<V>,
140    id: impl Into<ElementId>,
141    item_sizes: Rc<Vec<Size<Pixels>>>,
142    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
143) -> VirtualList
144where
145    R: IntoElement,
146    V: Render,
147{
148    virtual_list(view, id, Axis::Vertical, item_sizes, f)
149}
150
151/// 创建水平方向的 [`VirtualList`]
152///
153/// `item_sizes` 是每个项的尺寸,
154/// 仅使用 `width`,`height` 将被忽略,虚拟列表会测量第一个项的高度。
155///
156/// 参见 [`v_virtual_list`]
157#[inline]
158pub fn h_virtual_list<R, V>(
159    view: Entity<V>,
160    id: impl Into<ElementId>,
161    item_sizes: Rc<Vec<Size<Pixels>>>,
162    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
163) -> VirtualList
164where
165    R: IntoElement,
166    V: Render,
167{
168    virtual_list(view, id, Axis::Horizontal, item_sizes, f)
169}
170
171/// 内部虚拟列表创建函数
172pub(crate) fn virtual_list<R, V>(
173    view: Entity<V>,
174    id: impl Into<ElementId>,
175    axis: Axis,
176    item_sizes: Rc<Vec<Size<Pixels>>>,
177    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
178) -> VirtualList
179where
180    R: IntoElement,
181    V: Render,
182{
183    let id: ElementId = id.into();
184    let scroll_handle = VirtualListScrollHandle::new();
185    let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
186        view.update(cx, |this, cx| {
187            f(this, visible_range, window, cx)
188                .into_iter()
189                .map(|component| component.into_any_element())
190                .collect()
191        })
192    };
193
194    VirtualList {
195        id: id.clone(),
196        axis,
197        base: div()
198            .id(id)
199            .size_full()
200            .overflow_scroll()
201            .track_scroll(&scroll_handle),
202        scroll_handle,
203        items_count: item_sizes.len(),
204        item_sizes,
205        render_items: Box::new(render_range),
206        sizing_behavior: ListSizingBehavior::default(),
207    }
208}
209
210/// 虚拟列表组件,用于渲染大量不同尺寸的项
211pub struct VirtualList {
212    /// 元素 ID
213    id: ElementId,
214    /// 布局方向
215    axis: Axis,
216    /// 基础 Div 元素
217    base: Stateful<Div>,
218    /// 滚动句柄
219    scroll_handle: VirtualListScrollHandle,
220    /// 项总数
221    items_count: usize,
222    /// 每个项的尺寸
223    item_sizes: Rc<Vec<Size<Pixels>>>,
224    /// 渲染项的闭包
225    render_items: Box<
226        dyn for<'a> Fn(Range<usize>, &'a mut Window, &'a mut App) -> SmallVec<[AnyElement; 64]>,
227    >,
228    /// 列表尺寸行为
229    sizing_behavior: ListSizingBehavior,
230}
231
232impl Styled for VirtualList {
233    fn style(&mut self) -> &mut StyleRefinement {
234        self.base.style()
235    }
236}
237
238impl VirtualList {
239    /// 绑定滚动句柄
240    pub fn track_scroll(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
241        self.base = self.base.track_scroll(&scroll_handle);
242        self.scroll_handle = scroll_handle.clone();
243        self
244    }
245
246    /// 设置列表的尺寸行为
247    pub fn with_sizing_behavior(mut self, behavior: ListSizingBehavior) -> Self {
248        self.sizing_behavior = behavior;
249        self
250    }
251
252    /// 专为表格设计
253    ///
254    /// 表格比较特殊,因为其 `scroll_handle` 基于表头(那不是虚拟列表)。
255    pub(crate) fn with_scroll_handle(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
256        self.base = div().id(self.id.clone()).size_full();
257        self.scroll_handle = scroll_handle.clone();
258        self
259    }
260
261    /// 处理延迟滚动到指定项
262    fn scroll_to_deferred_item(
263        &self,
264        scroll_offset: Point<Pixels>,
265        items_bounds: &[Bounds<Pixels>],
266        content_bounds: &Bounds<Pixels>,
267        scroll_to_item: DeferredScrollToItem,
268    ) -> Point<Pixels> {
269        let Some(bounds) = items_bounds
270            .get(scroll_to_item.item_index + scroll_to_item.offset)
271            .cloned()
272        else {
273            return scroll_offset;
274        };
275
276        let mut scroll_offset = scroll_offset;
277        match scroll_to_item.strategy {
278            ScrollStrategy::Center => {
279                if self.axis.is_vertical() {
280                    scroll_offset.y = content_bounds.top() + content_bounds.size.height.half()
281                        - bounds.top()
282                        - bounds.size.height.half()
283                } else {
284                    scroll_offset.x = content_bounds.left() + content_bounds.size.width.half()
285                        - bounds.left()
286                        - bounds.size.width.half()
287                }
288            }
289            _ => {
290                // 参考:https://github.com/zed-industries/zed/blob/0d145289e0867a8d5d63e5e1397a5ca69c9d49c3/crates/gpui/src/elements/div.rs#L3026
291                if self.axis.is_vertical() {
292                    if bounds.top() + scroll_offset.y < content_bounds.top() {
293                        scroll_offset.y = content_bounds.top() - bounds.top()
294                    } else if bounds.bottom() + scroll_offset.y > content_bounds.bottom() {
295                        scroll_offset.y = content_bounds.bottom() - bounds.bottom();
296                    }
297                } else {
298                    if bounds.left() + scroll_offset.x < content_bounds.left() {
299                        scroll_offset.x = content_bounds.left() - bounds.left();
300                    } else if bounds.right() + scroll_offset.x > content_bounds.right() {
301                        scroll_offset.x = content_bounds.right() - bounds.right();
302                    }
303                }
304            }
305        }
306        self.scroll_handle.set_offset(scroll_offset);
307        scroll_offset
308    }
309
310    /// 测量单个项的尺寸
311    /// 参考:https://github.com/zed-industries/zed/blob/83f9f9d9e3f5914392cab9a09e3472711a1d7b38/crates/gpui/src/elements/uniform_list.rs#L660
312    fn measure_item(
313        &self,
314        list_width: Option<Pixels>,
315        window: &mut Window,
316        cx: &mut App,
317    ) -> Size<Pixels> {
318        if self.items_count == 0 {
319            return Size::default();
320        }
321
322        let item_ix = 0;
323        let mut items = (self.render_items)(item_ix..item_ix + 1, window, cx);
324        let Some(mut item_to_measure) = items.pop() else {
325            return Size::default();
326        };
327        let available_space = size(
328            list_width.map_or(AvailableSpace::MinContent, |width| {
329                AvailableSpace::Definite(width)
330            }),
331            AvailableSpace::MinContent,
332        );
333        item_to_measure.layout_as_root(available_space, window, cx)
334    }
335}
336
337/// [VirtualItem] 使用的帧状态
338pub struct VirtualListFrameState {
339    /// 待绘制的可见项
340    items: SmallVec<[AnyElement; 32]>,
341    /// 项尺寸布局信息
342    size_layout: ItemSizeLayout,
343}
344
345/// 项尺寸布局数据
346#[derive(Default, Clone)]
347pub struct ItemSizeLayout {
348    /// 所有项的尺寸
349    items_sizes: Rc<Vec<Size<Pixels>>>,
350    /// 内容总尺寸
351    content_size: Size<Pixels>,
352    /// 沿主轴的每个项尺寸
353    sizes: Vec<Pixels>,
354    /// 沿主轴的每个项起始位置
355    origins: Vec<Pixels>,
356    /// 上次布局的边界
357    last_layout_bounds: Bounds<Pixels>,
358}
359
360impl IntoElement for VirtualList {
361    type Element = Self;
362
363    fn into_element(self) -> Self::Element {
364        self
365    }
366}
367
368impl Element for VirtualList {
369    type RequestLayoutState = VirtualListFrameState;
370    type PrepaintState = Option<Hitbox>;
371
372    fn id(&self) -> Option<ElementId> {
373        Some(self.id.clone())
374    }
375
376    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
377        None
378    }
379
380    fn request_layout(
381        &mut self,
382        global_id: Option<&GlobalElementId>,
383        inspector_id: Option<&rgpui::InspectorElementId>,
384        window: &mut Window,
385        cx: &mut App,
386    ) -> (rgpui::LayoutId, Self::RequestLayoutState) {
387        let rem_size = window.rem_size();
388        let font_size = window.text_style().font_size.to_pixels(rem_size);
389        let mut size_layout = ItemSizeLayout::default();
390        let longest_item_size = self.measure_item(None, window, cx);
391
392        let layout_id = self.base.interactivity().request_layout(
393            global_id,
394            inspector_id,
395            window,
396            cx,
397            |style, window, cx| {
398                size_layout = window.with_element_state(
399                    global_id.unwrap(),
400                    |state: Option<ItemSizeLayout>, _window| {
401                        let mut state = state.unwrap_or(ItemSizeLayout::default());
402
403                        // 包含项之间的间距以计算项尺寸
404                        let gap = style
405                            .gap
406                            .along(self.axis)
407                            .to_pixels(font_size.into(), rem_size);
408
409                        if state.items_sizes != self.item_sizes {
410                            state.items_sizes = self.item_sizes.clone();
411                            // 准备每个项沿主轴的尺寸
412                            state.sizes = self
413                                .item_sizes
414                                .iter()
415                                .enumerate()
416                                .map(|(i, size)| {
417                                    let size = size.along(self.axis);
418                                    if i + 1 == self.items_count {
419                                        size
420                                    } else {
421                                        size + gap
422                                    }
423                                })
424                                .collect::<Vec<_>>();
425
426                            // 准备每个项沿主轴的起始位置
427                            state.origins = state
428                                .sizes
429                                .iter()
430                                .scan(px(0.), |cumulative, size| match self.axis {
431                                    Axis::Horizontal => {
432                                        let x = *cumulative;
433                                        *cumulative += *size;
434                                        Some(x)
435                                    }
436                                    Axis::Vertical => {
437                                        let y = *cumulative;
438                                        *cumulative += *size;
439                                        Some(y)
440                                    }
441                                })
442                                .collect::<Vec<_>>();
443
444                            state.content_size = if self.axis.is_horizontal() {
445                                Size {
446                                    width: px(state
447                                        .sizes
448                                        .iter()
449                                        .map(|size| size.as_f32())
450                                        .sum::<f32>()),
451                                    height: longest_item_size.height,
452                                }
453                            } else {
454                                Size {
455                                    width: longest_item_size.width,
456                                    height: px(state
457                                        .sizes
458                                        .iter()
459                                        .map(|size| size.as_f32())
460                                        .sum::<f32>()),
461                                }
462                            };
463                        }
464
465                        (state.clone(), state)
466                    },
467                );
468
469                let axis = self.axis;
470                let layout_id =
471                    match self.sizing_behavior {
472                        ListSizingBehavior::Infer => {
473                            window.with_text_style(style.text_style().cloned(), |window| {
474                                let size_layout = size_layout.clone();
475
476                                window.request_measured_layout(style, {
477                                    move |known_dimensions, available_space, _, _| {
478                                        let mut size = Size::default();
479                                        if axis.is_horizontal() {
480                                            size.width = known_dimensions.width.unwrap_or(
481                                                match available_space.width {
482                                                    AvailableSpace::Definite(x) => x,
483                                                    AvailableSpace::MinContent
484                                                    | AvailableSpace::MaxContent => {
485                                                        size_layout.content_size.width
486                                                    }
487                                                },
488                                            );
489                                            size.height = known_dimensions.width.unwrap_or(
490                                                match available_space.height {
491                                                    AvailableSpace::Definite(x) => x,
492                                                    AvailableSpace::MinContent
493                                                    | AvailableSpace::MaxContent => {
494                                                        size_layout.content_size.height
495                                                    }
496                                                },
497                                            );
498                                        } else {
499                                            size.width = known_dimensions.width.unwrap_or(
500                                                match available_space.width {
501                                                    AvailableSpace::Definite(x) => x,
502                                                    AvailableSpace::MinContent
503                                                    | AvailableSpace::MaxContent => {
504                                                        size_layout.content_size.width
505                                                    }
506                                                },
507                                            );
508                                            size.height = known_dimensions.height.unwrap_or(
509                                                match available_space.height {
510                                                    AvailableSpace::Definite(x) => x,
511                                                    AvailableSpace::MinContent
512                                                    | AvailableSpace::MaxContent => {
513                                                        size_layout.content_size.height
514                                                    }
515                                                },
516                                            );
517                                        }
518
519                                        size
520                                    }
521                                })
522                            })
523                        }
524                        ListSizingBehavior::Auto => window
525                            .with_text_style(style.text_style().cloned(), |window| {
526                                window.request_layout(style, None, cx)
527                            }),
528                    };
529
530                layout_id
531            },
532        );
533
534        (
535            layout_id,
536            VirtualListFrameState {
537                items: SmallVec::new(),
538                size_layout,
539            },
540        )
541    }
542
543    fn prepaint(
544        &mut self,
545        global_id: Option<&GlobalElementId>,
546        inspector_id: Option<&rgpui::InspectorElementId>,
547        bounds: Bounds<Pixels>,
548        layout: &mut Self::RequestLayoutState,
549        window: &mut Window,
550        cx: &mut App,
551    ) -> Self::PrepaintState {
552        layout.size_layout.last_layout_bounds = bounds;
553
554        let style = self
555            .base
556            .interactivity()
557            .compute_style(global_id, None, window, cx);
558        let border_widths = style.border_widths.to_pixels(window.rem_size());
559        let paddings = style
560            .padding
561            .to_pixels(bounds.size.into(), window.rem_size());
562
563        let item_sizes = &layout.size_layout.sizes;
564        let item_origins = &layout.size_layout.origins;
565
566        let content_bounds = Bounds::from_corners(
567            bounds.origin
568                + point(
569                    border_widths.left + paddings.left,
570                    border_widths.top + paddings.top,
571                ),
572            bounds.bottom_right()
573                - point(
574                    border_widths.right + paddings.right,
575                    border_widths.bottom + paddings.bottom,
576                ),
577        );
578
579        // 使用项边界更新滚动句柄
580        let items_bounds = item_origins
581            .iter()
582            .enumerate()
583            .map(|(i, &origin)| {
584                let item_size = item_sizes[i];
585
586                Bounds {
587                    origin: match self.axis {
588                        Axis::Horizontal => point(content_bounds.left() + origin, px(0.)),
589                        Axis::Vertical => point(px(0.), content_bounds.top() + origin),
590                    },
591                    size: match self.axis {
592                        Axis::Horizontal => size(item_size, content_bounds.size.height),
593                        Axis::Vertical => size(content_bounds.size.width, item_size),
594                    },
595                }
596            })
597            .collect::<Vec<_>>();
598
599        let axis = self.axis;
600
601        let mut scroll_state = self.scroll_handle.state.borrow_mut();
602        scroll_state.axis = axis;
603        scroll_state.items_count = self.items_count;
604
605        let mut scroll_offset = self.scroll_handle.offset();
606        if let Some(scroll_to_item) = scroll_state.deferred_scroll_to_item.take() {
607            scroll_offset = self.scroll_to_deferred_item(
608                scroll_offset,
609                &items_bounds,
610                &content_bounds,
611                scroll_to_item,
612            );
613        }
614
615        scroll_offset = scroll_offset
616            .max(&point(
617                content_bounds.size.width - layout.size_layout.content_size.width,
618                content_bounds.size.height - layout.size_layout.content_size.height,
619            ))
620            .min(&point(px(0.), px(0.)));
621        if scroll_offset != self.scroll_handle.offset() {
622            self.scroll_handle.set_offset(scroll_offset);
623        }
624
625        self.base.interactivity().prepaint(
626            global_id,
627            inspector_id,
628            bounds,
629            layout.size_layout.content_size,
630            window,
631            cx,
632            |_style, _, hitbox, window, cx| {
633                if self.items_count > 0 {
634                    let min_scroll_offset = content_bounds.size.along(self.axis)
635                        - layout.size_layout.content_size.along(self.axis);
636
637                    let is_scrolled = !scroll_offset.along(self.axis).is_zero();
638                    if is_scrolled {
639                        match self.axis {
640                            Axis::Horizontal if scroll_offset.x < min_scroll_offset => {
641                                scroll_offset.x = min_scroll_offset;
642                                self.scroll_handle.set_offset(scroll_offset);
643                            }
644                            Axis::Vertical if scroll_offset.y < min_scroll_offset => {
645                                scroll_offset.y = min_scroll_offset;
646                                self.scroll_handle.set_offset(scroll_offset);
647                            }
648                            _ => {}
649                        }
650                    }
651
652                    let (first_visible_element_ix, last_visible_element_ix) = match self.axis {
653                        Axis::Horizontal => {
654                            let mut cumulative_size = px(0.);
655                            let mut first_visible_element_ix = 0;
656                            for (i, &size) in item_sizes.iter().enumerate() {
657                                cumulative_size += size;
658                                if cumulative_size > -(scroll_offset.x + paddings.left) {
659                                    first_visible_element_ix = i;
660                                    break;
661                                }
662                            }
663
664                            cumulative_size = px(0.);
665                            let mut last_visible_element_ix = 0;
666                            for (i, &size) in item_sizes.iter().enumerate() {
667                                cumulative_size += size;
668                                if cumulative_size > (-scroll_offset.x + content_bounds.size.width)
669                                {
670                                    last_visible_element_ix = i + 1;
671                                    break;
672                                }
673                            }
674                            if last_visible_element_ix == 0 {
675                                last_visible_element_ix = self.items_count;
676                            } else {
677                                last_visible_element_ix += 1;
678                            }
679                            (first_visible_element_ix, last_visible_element_ix)
680                        }
681                        Axis::Vertical => {
682                            let mut cumulative_size = px(0.);
683                            let mut first_visible_element_ix = 0;
684                            for (i, &size) in item_sizes.iter().enumerate() {
685                                cumulative_size += size;
686                                if cumulative_size > -(scroll_offset.y + paddings.top) {
687                                    first_visible_element_ix = i;
688                                    break;
689                                }
690                            }
691
692                            cumulative_size = px(0.);
693                            let mut last_visible_element_ix = 0;
694                            for (i, &size) in item_sizes.iter().enumerate() {
695                                cumulative_size += size;
696                                if cumulative_size > (-scroll_offset.y + content_bounds.size.height)
697                                {
698                                    last_visible_element_ix = i + 1;
699                                    break;
700                                }
701                            }
702                            if last_visible_element_ix == 0 {
703                                last_visible_element_ix = self.items_count;
704                            } else {
705                                last_visible_element_ix += 1;
706                            }
707                            (first_visible_element_ix, last_visible_element_ix)
708                        }
709                    };
710
711                    let visible_range = first_visible_element_ix
712                        ..cmp::min(last_visible_element_ix, self.items_count);
713
714                    let items = (self.render_items)(visible_range.clone(), window, cx);
715
716                    let content_mask = ContentMask { bounds };
717                    window.with_content_mask(Some(content_mask), |window| {
718                        for (mut item, ix) in items.into_iter().zip(visible_range.clone()) {
719                            let item_origin = match self.axis {
720                                Axis::Horizontal => {
721                                    content_bounds.origin
722                                        + point(item_origins[ix] + scroll_offset.x, scroll_offset.y)
723                                }
724                                Axis::Vertical => {
725                                    content_bounds.origin
726                                        + point(scroll_offset.x, item_origins[ix] + scroll_offset.y)
727                                }
728                            };
729
730                            let available_space = match self.axis {
731                                Axis::Horizontal => size(
732                                    AvailableSpace::Definite(item_sizes[ix]),
733                                    AvailableSpace::Definite(content_bounds.size.height),
734                                ),
735                                Axis::Vertical => size(
736                                    AvailableSpace::Definite(content_bounds.size.width),
737                                    AvailableSpace::Definite(item_sizes[ix]),
738                                ),
739                            };
740
741                            item.layout_as_root(available_space, window, cx);
742                            item.prepaint_at(item_origin, window, cx);
743                            layout.items.push(item);
744                        }
745                    });
746                }
747
748                hitbox
749            },
750        )
751    }
752
753    fn paint(
754        &mut self,
755        global_id: Option<&GlobalElementId>,
756        inspector_id: Option<&rgpui::InspectorElementId>,
757        bounds: Bounds<Pixels>,
758        layout: &mut Self::RequestLayoutState,
759        hitbox: &mut Self::PrepaintState,
760        window: &mut Window,
761        cx: &mut App,
762    ) {
763        self.base.interactivity().paint(
764            global_id,
765            inspector_id,
766            bounds,
767            hitbox.as_ref(),
768            window,
769            cx,
770            |_, window, cx| {
771                for item in &mut layout.items {
772                    item.paint(window, cx);
773                }
774            },
775        )
776    }
777}