gpui_component/
virtual_list.rs

1//! Vistual List for render a large number of differently sized rows/columns.
2//!
3//! > NOTE: This must ensure each column width or row height.
4//!
5//! Only visible range are rendered for performance reasons.
6//!
7//! Inspired by `gpui::uniform_list`.
8//! https://github.com/zed-industries/zed/blob/0ae1603610ab6b265bdfbee7b8dbc23c5ab06edc/crates/gpui/src/elements/uniform_list.rs
9//!
10//! Unlike the `uniform_list`, the each item can have different size.
11//!
12//! This is useful for more complex layout, for example, a table with different row height.
13use std::{
14    cell::RefCell,
15    cmp,
16    ops::{Deref, Range},
17    rc::Rc,
18};
19
20use gpui::{
21    div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
22    Context, 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,
26};
27use smallvec::SmallVec;
28
29use crate::{scroll::ScrollHandleOffsetable, AxisExt, PixelsExt};
30
31struct VirtualListScrollHandleState {
32    axis: Axis,
33    items_count: usize,
34    pub deferred_scroll_to_item: Option<DeferredScrollToItem>,
35}
36
37#[derive(Clone)]
38pub struct VirtualListScrollHandle {
39    state: Rc<RefCell<VirtualListScrollHandleState>>,
40    base_handle: ScrollHandle,
41}
42
43impl From<ScrollHandle> for VirtualListScrollHandle {
44    fn from(handle: ScrollHandle) -> Self {
45        let mut this = VirtualListScrollHandle::new();
46        this.base_handle = handle;
47        this
48    }
49}
50
51impl AsRef<ScrollHandle> for VirtualListScrollHandle {
52    fn as_ref(&self) -> &ScrollHandle {
53        &self.base_handle
54    }
55}
56
57impl ScrollHandleOffsetable for VirtualListScrollHandle {
58    fn offset(&self) -> Point<Pixels> {
59        self.base_handle.offset()
60    }
61
62    fn set_offset(&self, offset: Point<Pixels>) {
63        self.base_handle.set_offset(offset);
64    }
65
66    fn content_size(&self) -> Size<Pixels> {
67        self.base_handle.content_size()
68    }
69}
70
71impl Deref for VirtualListScrollHandle {
72    type Target = ScrollHandle;
73
74    fn deref(&self) -> &Self::Target {
75        &self.base_handle
76    }
77}
78
79impl VirtualListScrollHandle {
80    pub fn new() -> Self {
81        VirtualListScrollHandle {
82            state: Rc::new(RefCell::new(VirtualListScrollHandleState {
83                axis: Axis::Vertical,
84                items_count: 0,
85                deferred_scroll_to_item: None,
86            })),
87            base_handle: ScrollHandle::default(),
88        }
89    }
90
91    pub fn base_handle(&self) -> &ScrollHandle {
92        &self.base_handle
93    }
94
95    /// Scroll to the item at the given index.
96    pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
97        self.scroll_to_item_with_offset(ix, strategy, 0);
98    }
99
100    /// Scroll to the item at the given index, with an additional offset items.
101    fn scroll_to_item_with_offset(&self, ix: usize, strategy: ScrollStrategy, offset: usize) {
102        let mut state = self.state.borrow_mut();
103        state.deferred_scroll_to_item = Some(DeferredScrollToItem {
104            item_index: ix,
105            strategy,
106            offset,
107            scroll_strict: false,
108        });
109    }
110
111    /// Scrolls to the bottom of the list.
112    pub fn scroll_to_bottom(&self) {
113        let items_count = self.state.borrow().items_count;
114        self.scroll_to_item(items_count.saturating_sub(1), ScrollStrategy::Top);
115    }
116}
117
118/// Create a [`VirtualList`] in vertical direction.
119///
120/// This is like `uniform_list` in GPUI, but support two axis.
121///
122/// The `item_sizes` is the size of each column,
123/// only the `height` is used, `width` is ignored and VirtualList will measure the first item width.
124///
125/// See also [`h_virtual_list`]
126#[inline]
127pub fn v_virtual_list<R, V>(
128    view: Entity<V>,
129    id: impl Into<ElementId>,
130    item_sizes: Rc<Vec<Size<Pixels>>>,
131    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
132) -> VirtualList
133where
134    R: IntoElement,
135    V: Render,
136{
137    virtual_list(view, id, Axis::Vertical, item_sizes, f)
138}
139
140/// Create a [`VirtualList`] in horizontal direction.
141///
142/// The `item_sizes` is the size of each column,
143/// only the `width` is used, `height` is ignored and VirtualList will measure the first item height.
144///
145/// See also [`v_virtual_list`]
146#[inline]
147pub fn h_virtual_list<R, V>(
148    view: Entity<V>,
149    id: impl Into<ElementId>,
150    item_sizes: Rc<Vec<Size<Pixels>>>,
151    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
152) -> VirtualList
153where
154    R: IntoElement,
155    V: Render,
156{
157    virtual_list(view, id, Axis::Horizontal, item_sizes, f)
158}
159
160pub(crate) fn virtual_list<R, V>(
161    view: Entity<V>,
162    id: impl Into<ElementId>,
163    axis: Axis,
164    item_sizes: Rc<Vec<Size<Pixels>>>,
165    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
166) -> VirtualList
167where
168    R: IntoElement,
169    V: Render,
170{
171    let id: ElementId = id.into();
172    let scroll_handle = VirtualListScrollHandle::new();
173    let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
174        view.update(cx, |this, cx| {
175            f(this, visible_range, window, cx)
176                .into_iter()
177                .map(|component| component.into_any_element())
178                .collect()
179        })
180    };
181
182    VirtualList {
183        id: id.clone(),
184        axis,
185        base: div()
186            .id(id)
187            .size_full()
188            .overflow_scroll()
189            .track_scroll(&scroll_handle),
190        scroll_handle,
191        items_count: item_sizes.len(),
192        item_sizes,
193        render_items: Box::new(render_range),
194        sizing_behavior: ListSizingBehavior::default(),
195    }
196}
197
198/// VirtualList component for rendering a large number of differently sized items.
199pub struct VirtualList {
200    id: ElementId,
201    axis: Axis,
202    base: Stateful<Div>,
203    scroll_handle: VirtualListScrollHandle,
204    items_count: usize,
205    item_sizes: Rc<Vec<Size<Pixels>>>,
206    render_items: Box<
207        dyn for<'a> Fn(Range<usize>, &'a mut Window, &'a mut App) -> SmallVec<[AnyElement; 64]>,
208    >,
209    sizing_behavior: ListSizingBehavior,
210}
211
212impl Styled for VirtualList {
213    fn style(&mut self) -> &mut StyleRefinement {
214        self.base.style()
215    }
216}
217
218impl VirtualList {
219    pub fn track_scroll(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
220        self.base = self.base.track_scroll(&scroll_handle);
221        self.scroll_handle = scroll_handle.clone();
222        self
223    }
224
225    /// Set the sizing behavior for the list.
226    pub fn with_sizing_behavior(mut self, behavior: ListSizingBehavior) -> Self {
227        self.sizing_behavior = behavior;
228        self
229    }
230
231    /// Specify for table.
232    ///
233    /// Table is special, because the `scroll_handle` is based on Table head (That is not a virtual list).
234    pub(crate) fn with_scroll_handle(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
235        self.base = div().id(self.id.clone()).size_full();
236        self.scroll_handle = scroll_handle.clone();
237        self
238    }
239
240    fn scroll_to_deferred_item(
241        &self,
242        scroll_offset: Point<Pixels>,
243        items_bounds: &[Bounds<Pixels>],
244        content_bounds: &Bounds<Pixels>,
245        scroll_to_item: DeferredScrollToItem,
246    ) -> Point<Pixels> {
247        let Some(bounds) = items_bounds
248            .get(scroll_to_item.item_index + scroll_to_item.offset)
249            .cloned()
250        else {
251            return scroll_offset;
252        };
253
254        let mut scroll_offset = scroll_offset;
255        match scroll_to_item.strategy {
256            ScrollStrategy::Center => {
257                if self.axis.is_vertical() {
258                    scroll_offset.y = content_bounds.top() + content_bounds.size.height.half()
259                        - bounds.top()
260                        - bounds.size.height.half()
261                } else {
262                    scroll_offset.x = content_bounds.left() + content_bounds.size.width.half()
263                        - bounds.left()
264                        - bounds.size.width.half()
265                }
266            }
267            _ => {
268                // Ref: https://github.com/zed-industries/zed/blob/0d145289e0867a8d5d63e5e1397a5ca69c9d49c3/crates/gpui/src/elements/div.rs#L3026
269                if self.axis.is_vertical() {
270                    if bounds.top() + scroll_offset.y < content_bounds.top() {
271                        scroll_offset.y = content_bounds.top() - bounds.top()
272                    } else if bounds.bottom() + scroll_offset.y > content_bounds.bottom() {
273                        scroll_offset.y = content_bounds.bottom() - bounds.bottom();
274                    }
275                } else {
276                    if bounds.left() + scroll_offset.x < content_bounds.left() {
277                        scroll_offset.x = content_bounds.left() - bounds.left();
278                    } else if bounds.right() + scroll_offset.x > content_bounds.right() {
279                        scroll_offset.x = content_bounds.right() - bounds.right();
280                    }
281                }
282            }
283        }
284        self.scroll_handle.set_offset(scroll_offset);
285        scroll_offset
286    }
287
288    /// Ref from: https://github.com/zed-industries/zed/blob/83f9f9d9e3f5914392cab9a09e3472711a1d7b38/crates/gpui/src/elements/uniform_list.rs#L660
289    fn measure_item(
290        &self,
291        list_width: Option<Pixels>,
292        window: &mut Window,
293        cx: &mut App,
294    ) -> Size<Pixels> {
295        if self.items_count == 0 {
296            return Size::default();
297        }
298
299        let item_ix = 0;
300        let mut items = (self.render_items)(item_ix..item_ix + 1, window, cx);
301        let Some(mut item_to_measure) = items.pop() else {
302            return Size::default();
303        };
304        let available_space = size(
305            list_width.map_or(AvailableSpace::MinContent, |width| {
306                AvailableSpace::Definite(width)
307            }),
308            AvailableSpace::MinContent,
309        );
310        item_to_measure.layout_as_root(available_space, window, cx)
311    }
312}
313
314/// Frame state used by the [VirtualItem].
315pub struct VirtualListFrameState {
316    /// Visible items to be painted.
317    items: SmallVec<[AnyElement; 32]>,
318    size_layout: ItemSizeLayout,
319}
320
321#[derive(Default, Clone)]
322pub struct ItemSizeLayout {
323    items_sizes: Rc<Vec<Size<Pixels>>>,
324    content_size: Size<Pixels>,
325    sizes: Vec<Pixels>,
326    origins: Vec<Pixels>,
327    last_layout_bounds: Bounds<Pixels>,
328}
329
330impl IntoElement for VirtualList {
331    type Element = Self;
332
333    fn into_element(self) -> Self::Element {
334        self
335    }
336}
337
338impl Element for VirtualList {
339    type RequestLayoutState = VirtualListFrameState;
340    type PrepaintState = Option<Hitbox>;
341
342    fn id(&self) -> Option<ElementId> {
343        Some(self.id.clone())
344    }
345
346    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
347        None
348    }
349
350    fn request_layout(
351        &mut self,
352        global_id: Option<&GlobalElementId>,
353        inspector_id: Option<&gpui::InspectorElementId>,
354        window: &mut Window,
355        cx: &mut App,
356    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
357        let rem_size = window.rem_size();
358        let font_size = window.text_style().font_size.to_pixels(rem_size);
359        let mut size_layout = ItemSizeLayout::default();
360        let longest_item_size = self.measure_item(None, window, cx);
361
362        let layout_id = self.base.interactivity().request_layout(
363            global_id,
364            inspector_id,
365            window,
366            cx,
367            |style, window, cx| {
368                size_layout = window.with_element_state(
369                    global_id.unwrap(),
370                    |state: Option<ItemSizeLayout>, _window| {
371                        let mut state = state.unwrap_or(ItemSizeLayout::default());
372
373                        // Including the gap between items for calculate the item size
374                        let gap = style
375                            .gap
376                            .along(self.axis)
377                            .to_pixels(font_size.into(), rem_size);
378
379                        if state.items_sizes != self.item_sizes {
380                            state.items_sizes = self.item_sizes.clone();
381                            // Prepare each item's size by axis
382                            state.sizes = self
383                                .item_sizes
384                                .iter()
385                                .enumerate()
386                                .map(|(i, size)| {
387                                    let size = size.along(self.axis);
388                                    if i + 1 == self.items_count {
389                                        size
390                                    } else {
391                                        size + gap
392                                    }
393                                })
394                                .collect::<Vec<_>>();
395
396                            // Prepare each item's origin by axis
397                            state.origins = state
398                                .sizes
399                                .iter()
400                                .scan(px(0.), |cumulative, size| match self.axis {
401                                    Axis::Horizontal => {
402                                        let x = *cumulative;
403                                        *cumulative += *size;
404                                        Some(x)
405                                    }
406                                    Axis::Vertical => {
407                                        let y = *cumulative;
408                                        *cumulative += *size;
409                                        Some(y)
410                                    }
411                                })
412                                .collect::<Vec<_>>();
413
414                            state.content_size = if self.axis.is_horizontal() {
415                                Size {
416                                    width: px(state
417                                        .sizes
418                                        .iter()
419                                        .map(|size| size.as_f32())
420                                        .sum::<f32>()),
421                                    height: longest_item_size.height,
422                                }
423                            } else {
424                                Size {
425                                    width: longest_item_size.width,
426                                    height: px(state
427                                        .sizes
428                                        .iter()
429                                        .map(|size| size.as_f32())
430                                        .sum::<f32>()),
431                                }
432                            };
433                        }
434
435                        (state.clone(), state)
436                    },
437                );
438
439                let axis = self.axis;
440                let layout_id =
441                    match self.sizing_behavior {
442                        ListSizingBehavior::Infer => {
443                            window.with_text_style(style.text_style().cloned(), |window| {
444                                let size_layout = size_layout.clone();
445
446                                window.request_measured_layout(style, {
447                                    move |known_dimensions, available_space, _, _| {
448                                        let mut size = Size::default();
449                                        if axis.is_horizontal() {
450                                            size.width = known_dimensions.width.unwrap_or(
451                                                match available_space.width {
452                                                    AvailableSpace::Definite(x) => x,
453                                                    AvailableSpace::MinContent
454                                                    | AvailableSpace::MaxContent => {
455                                                        size_layout.content_size.width
456                                                    }
457                                                },
458                                            );
459                                            size.height = known_dimensions.width.unwrap_or(
460                                                match available_space.height {
461                                                    AvailableSpace::Definite(x) => x,
462                                                    AvailableSpace::MinContent
463                                                    | AvailableSpace::MaxContent => {
464                                                        size_layout.content_size.height
465                                                    }
466                                                },
467                                            );
468                                        } else {
469                                            size.width = known_dimensions.width.unwrap_or(
470                                                match available_space.width {
471                                                    AvailableSpace::Definite(x) => x,
472                                                    AvailableSpace::MinContent
473                                                    | AvailableSpace::MaxContent => {
474                                                        size_layout.content_size.width
475                                                    }
476                                                },
477                                            );
478                                            size.height = known_dimensions.height.unwrap_or(
479                                                match available_space.height {
480                                                    AvailableSpace::Definite(x) => x,
481                                                    AvailableSpace::MinContent
482                                                    | AvailableSpace::MaxContent => {
483                                                        size_layout.content_size.height
484                                                    }
485                                                },
486                                            );
487                                        }
488
489                                        size
490                                    }
491                                })
492                            })
493                        }
494                        ListSizingBehavior::Auto => window
495                            .with_text_style(style.text_style().cloned(), |window| {
496                                window.request_layout(style, None, cx)
497                            }),
498                    };
499
500                layout_id
501            },
502        );
503
504        (
505            layout_id,
506            VirtualListFrameState {
507                items: SmallVec::new(),
508                size_layout,
509            },
510        )
511    }
512
513    fn prepaint(
514        &mut self,
515        global_id: Option<&GlobalElementId>,
516        inspector_id: Option<&gpui::InspectorElementId>,
517        bounds: Bounds<Pixels>,
518        layout: &mut Self::RequestLayoutState,
519        window: &mut Window,
520        cx: &mut App,
521    ) -> Self::PrepaintState {
522        layout.size_layout.last_layout_bounds = bounds;
523
524        let style = self
525            .base
526            .interactivity()
527            .compute_style(global_id, None, window, cx);
528        let border_widths = style.border_widths.to_pixels(window.rem_size());
529        let paddings = style
530            .padding
531            .to_pixels(bounds.size.into(), window.rem_size());
532
533        let item_sizes = &layout.size_layout.sizes;
534        let item_origins = &layout.size_layout.origins;
535
536        let content_bounds = Bounds::from_corners(
537            bounds.origin
538                + point(
539                    border_widths.left + paddings.left,
540                    border_widths.top + paddings.top,
541                ),
542            bounds.bottom_right()
543                - point(
544                    border_widths.right + paddings.right,
545                    border_widths.bottom + paddings.bottom,
546                ),
547        );
548
549        // Update scroll_handle with the item bounds
550        let items_bounds = item_origins
551            .iter()
552            .enumerate()
553            .map(|(i, &origin)| {
554                let item_size = item_sizes[i];
555
556                Bounds {
557                    origin: match self.axis {
558                        Axis::Horizontal => point(content_bounds.left() + origin, px(0.)),
559                        Axis::Vertical => point(px(0.), content_bounds.top() + origin),
560                    },
561                    size: match self.axis {
562                        Axis::Horizontal => size(item_size, content_bounds.size.height),
563                        Axis::Vertical => size(content_bounds.size.width, item_size),
564                    },
565                }
566            })
567            .collect::<Vec<_>>();
568
569        let axis = self.axis;
570
571        let mut scroll_state = self.scroll_handle.state.borrow_mut();
572        scroll_state.axis = axis;
573        scroll_state.items_count = self.items_count;
574
575        let mut scroll_offset = self.scroll_handle.offset();
576        if let Some(scroll_to_item) = scroll_state.deferred_scroll_to_item.take() {
577            scroll_offset = self.scroll_to_deferred_item(
578                scroll_offset,
579                &items_bounds,
580                &content_bounds,
581                scroll_to_item,
582            );
583        }
584
585        scroll_offset = scroll_offset
586            .max(&point(
587                content_bounds.size.width - layout.size_layout.content_size.width,
588                content_bounds.size.height - layout.size_layout.content_size.height,
589            ))
590            .min(&point(px(0.), px(0.)));
591        if scroll_offset != self.scroll_handle.offset() {
592            self.scroll_handle.set_offset(scroll_offset);
593        }
594
595        self.base.interactivity().prepaint(
596            global_id,
597            inspector_id,
598            bounds,
599            layout.size_layout.content_size,
600            window,
601            cx,
602            |_style, _, hitbox, window, cx| {
603                if self.items_count > 0 {
604                    let min_scroll_offset = content_bounds.size.along(self.axis)
605                        - layout.size_layout.content_size.along(self.axis);
606
607                    let is_scrolled = !scroll_offset.along(self.axis).is_zero();
608                    if is_scrolled {
609                        match self.axis {
610                            Axis::Horizontal if scroll_offset.x < min_scroll_offset => {
611                                scroll_offset.x = min_scroll_offset;
612                                self.scroll_handle.set_offset(scroll_offset);
613                            }
614                            Axis::Vertical if scroll_offset.y < min_scroll_offset => {
615                                scroll_offset.y = min_scroll_offset;
616                                self.scroll_handle.set_offset(scroll_offset);
617                            }
618                            _ => {}
619                        }
620                    }
621
622                    let (first_visible_element_ix, last_visible_element_ix) = match self.axis {
623                        Axis::Horizontal => {
624                            let mut cumulative_size = px(0.);
625                            let mut first_visible_element_ix = 0;
626                            for (i, &size) in item_sizes.iter().enumerate() {
627                                cumulative_size += size;
628                                if cumulative_size > -(scroll_offset.x + paddings.left) {
629                                    first_visible_element_ix = i;
630                                    break;
631                                }
632                            }
633
634                            cumulative_size = px(0.);
635                            let mut last_visible_element_ix = 0;
636                            for (i, &size) in item_sizes.iter().enumerate() {
637                                cumulative_size += size;
638                                if cumulative_size > (-scroll_offset.x + content_bounds.size.width)
639                                {
640                                    last_visible_element_ix = i + 1;
641                                    break;
642                                }
643                            }
644                            if last_visible_element_ix == 0 {
645                                last_visible_element_ix = self.items_count;
646                            } else {
647                                last_visible_element_ix += 1;
648                            }
649                            (first_visible_element_ix, last_visible_element_ix)
650                        }
651                        Axis::Vertical => {
652                            let mut cumulative_size = px(0.);
653                            let mut first_visible_element_ix = 0;
654                            for (i, &size) in item_sizes.iter().enumerate() {
655                                cumulative_size += size;
656                                if cumulative_size > -(scroll_offset.y + paddings.top) {
657                                    first_visible_element_ix = i;
658                                    break;
659                                }
660                            }
661
662                            cumulative_size = px(0.);
663                            let mut last_visible_element_ix = 0;
664                            for (i, &size) in item_sizes.iter().enumerate() {
665                                cumulative_size += size;
666                                if cumulative_size > (-scroll_offset.y + content_bounds.size.height)
667                                {
668                                    last_visible_element_ix = i + 1;
669                                    break;
670                                }
671                            }
672                            if last_visible_element_ix == 0 {
673                                last_visible_element_ix = self.items_count;
674                            } else {
675                                last_visible_element_ix += 1;
676                            }
677                            (first_visible_element_ix, last_visible_element_ix)
678                        }
679                    };
680
681                    let visible_range = first_visible_element_ix
682                        ..cmp::min(last_visible_element_ix, self.items_count);
683
684                    let items = (self.render_items)(visible_range.clone(), window, cx);
685
686                    let content_mask = ContentMask { bounds };
687                    window.with_content_mask(Some(content_mask), |window| {
688                        for (mut item, ix) in items.into_iter().zip(visible_range.clone()) {
689                            let item_origin = match self.axis {
690                                Axis::Horizontal => {
691                                    content_bounds.origin
692                                        + point(item_origins[ix] + scroll_offset.x, scroll_offset.y)
693                                }
694                                Axis::Vertical => {
695                                    content_bounds.origin
696                                        + point(scroll_offset.x, item_origins[ix] + scroll_offset.y)
697                                }
698                            };
699
700                            let available_space = match self.axis {
701                                Axis::Horizontal => size(
702                                    AvailableSpace::Definite(item_sizes[ix]),
703                                    AvailableSpace::Definite(content_bounds.size.height),
704                                ),
705                                Axis::Vertical => size(
706                                    AvailableSpace::Definite(content_bounds.size.width),
707                                    AvailableSpace::Definite(item_sizes[ix]),
708                                ),
709                            };
710
711                            item.layout_as_root(available_space, window, cx);
712                            item.prepaint_at(item_origin, window, cx);
713                            layout.items.push(item);
714                        }
715                    });
716                }
717
718                hitbox
719            },
720        )
721    }
722
723    fn paint(
724        &mut self,
725        global_id: Option<&GlobalElementId>,
726        inspector_id: Option<&gpui::InspectorElementId>,
727        bounds: Bounds<Pixels>,
728        layout: &mut Self::RequestLayoutState,
729        hitbox: &mut Self::PrepaintState,
730        window: &mut Window,
731        cx: &mut App,
732    ) {
733        self.base.interactivity().paint(
734            global_id,
735            inspector_id,
736            bounds,
737            hitbox.as_ref(),
738            window,
739            cx,
740            |_, window, cx| {
741                for item in &mut layout.items {
742                    item.paint(window, cx);
743                }
744            },
745        )
746    }
747}