list.rs

  1use crate::{
  2    point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
  3    ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Size, StatefulInteractive,
  4    StatefulInteractivity, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled,
  5    ViewContext,
  6};
  7use smallvec::SmallVec;
  8use std::{cmp, ops::Range};
  9use taffy::style::Overflow;
 10
 11pub fn list<Id, V, C>(
 12    id: Id,
 13    item_count: usize,
 14    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
 15) -> List<V>
 16where
 17    Id: Into<ElementId>,
 18    V: 'static,
 19    C: Component<V>,
 20{
 21    let id = id.into();
 22    List {
 23        id: id.clone(),
 24        style: Default::default(),
 25        item_count,
 26        render_items: Box::new(move |view, visible_range, cx| {
 27            f(view, visible_range, cx)
 28                .into_iter()
 29                .map(|component| component.render())
 30                .collect()
 31        }),
 32        interactivity: id.into(),
 33    }
 34}
 35
 36pub struct List<V: 'static> {
 37    id: ElementId,
 38    style: StyleRefinement,
 39    item_count: usize,
 40    render_items: Box<
 41        dyn for<'a> Fn(
 42            &'a mut V,
 43            Range<usize>,
 44            &'a mut ViewContext<V>,
 45        ) -> SmallVec<[AnyElement<V>; 64]>,
 46    >,
 47    interactivity: StatefulInteractivity<V>,
 48}
 49
 50#[derive(Default)]
 51pub struct ListState {
 52    interactive: InteractiveElementState,
 53}
 54
 55impl<V: 'static> Styled for List<V> {
 56    fn style(&mut self) -> &mut StyleRefinement {
 57        &mut self.style
 58    }
 59}
 60
 61impl<V: 'static> Element<V> for List<V> {
 62    type ElementState = ListState;
 63
 64    fn id(&self) -> Option<crate::ElementId> {
 65        Some(self.id.clone())
 66    }
 67
 68    fn initialize(
 69        &mut self,
 70        _: &mut V,
 71        element_state: Option<Self::ElementState>,
 72        _: &mut ViewContext<V>,
 73    ) -> Self::ElementState {
 74        let element_state = element_state.unwrap_or_default();
 75        element_state
 76    }
 77
 78    fn layout(
 79        &mut self,
 80        _view_state: &mut V,
 81        _element_state: &mut Self::ElementState,
 82        cx: &mut ViewContext<V>,
 83    ) -> LayoutId {
 84        cx.request_layout(&self.computed_style(), None)
 85    }
 86
 87    fn paint(
 88        &mut self,
 89        bounds: crate::Bounds<crate::Pixels>,
 90        view_state: &mut V,
 91        element_state: &mut Self::ElementState,
 92        cx: &mut ViewContext<V>,
 93    ) {
 94        let style = self.computed_style();
 95        style.paint(bounds, cx);
 96
 97        let border = style.border_widths.to_pixels(cx.rem_size());
 98        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
 99
100        let padded_bounds = Bounds::from_corners(
101            bounds.origin + point(border.left + padding.left, border.top + padding.top),
102            bounds.lower_right()
103                - point(border.right + padding.right, border.bottom + padding.bottom),
104        );
105
106        cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
107            let content_size;
108            if self.item_count > 0 {
109                let item_height = self.measure_item_height(view_state, padded_bounds, cx);
110                let visible_item_count =
111                    (padded_bounds.size.height / item_height).ceil() as usize + 1;
112                let scroll_offset = element_state
113                    .interactive
114                    .scroll_offset()
115                    .map_or((0.0).into(), |offset| offset.y);
116                let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
117                let visible_range = first_visible_element_ix
118                    ..cmp::min(
119                        first_visible_element_ix + visible_item_count,
120                        self.item_count,
121                    );
122
123                let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
124
125                content_size = Size {
126                    width: padded_bounds.size.width,
127                    height: item_height * self.item_count,
128                };
129
130                cx.with_z_index(1, |cx| {
131                    for (item, ix) in items.iter_mut().zip(visible_range) {
132                        item.initialize(view_state, cx);
133
134                        let layout_id = item.layout(view_state, cx);
135                        cx.compute_layout(
136                            layout_id,
137                            Size {
138                                width: AvailableSpace::Definite(bounds.size.width),
139                                height: AvailableSpace::Definite(item_height),
140                            },
141                        );
142                        let offset =
143                            padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
144                        cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
145                    }
146                });
147            } else {
148                content_size = Size {
149                    width: bounds.size.width,
150                    height: px(0.),
151                };
152            }
153
154            let overflow = point(style.overflow.x, Overflow::Scroll);
155
156            cx.with_z_index(0, |cx| {
157                self.interactivity.paint(
158                    bounds,
159                    content_size,
160                    overflow,
161                    &mut element_state.interactive,
162                    cx,
163                );
164            });
165        })
166    }
167}
168
169impl<V> List<V> {
170    fn measure_item_height(
171        &self,
172        view_state: &mut V,
173        list_bounds: Bounds<Pixels>,
174        cx: &mut ViewContext<V>,
175    ) -> Pixels {
176        let mut items = (self.render_items)(view_state, 0..1, cx);
177        debug_assert!(items.len() == 1);
178        let mut item_to_measure = items.pop().unwrap();
179        item_to_measure.initialize(view_state, cx);
180        let layout_id = item_to_measure.layout(view_state, cx);
181        cx.compute_layout(
182            layout_id,
183            Size {
184                width: AvailableSpace::Definite(list_bounds.size.width),
185                height: AvailableSpace::MinContent,
186            },
187        );
188        cx.layout_bounds(layout_id).size.height
189    }
190}
191
192impl<V: 'static> StatelessInteractive<V> for List<V> {
193    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
194        self.interactivity.as_stateless_mut()
195    }
196}
197
198impl<V: 'static> StatefulInteractive<V> for List<V> {
199    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
200        &mut self.interactivity
201    }
202}
203
204impl<V: 'static> Component<V> for List<V> {
205    fn render(self) -> AnyElement<V> {
206        AnyElement::new(self)
207    }
208}