uniform_list.rs

  1use crate::{
  2    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,
  3    ElementId, ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
  4    StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
  5    StyleRefinement, Styled, ViewContext,
  6};
  7use parking_lot::Mutex;
  8use smallvec::SmallVec;
  9use std::{cmp, ops::Range, sync::Arc};
 10use taffy::style::Overflow;
 11
 12/// uniform_list provides lazy rendering for a set of items that are of uniform height.
 13/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
 14/// uniform_list will only render the visibile subset of items.
 15pub fn uniform_list<Id, V, C>(
 16    id: Id,
 17    item_count: usize,
 18    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
 19) -> UniformList<V>
 20where
 21    Id: Into<ElementId>,
 22    V: 'static,
 23    C: Component<V>,
 24{
 25    let id = id.into();
 26    let mut style = StyleRefinement::default();
 27    style.overflow.y = Some(Overflow::Hidden);
 28
 29    UniformList {
 30        id: id.clone(),
 31        style,
 32        item_count,
 33        render_items: Box::new(move |view, visible_range, cx| {
 34            f(view, visible_range, cx)
 35                .into_iter()
 36                .map(|component| component.render())
 37                .collect()
 38        }),
 39        interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
 40        scroll_handle: None,
 41    }
 42}
 43
 44pub struct UniformList<V: 'static> {
 45    id: ElementId,
 46    style: StyleRefinement,
 47    item_count: usize,
 48    render_items: Box<
 49        dyn for<'a> Fn(
 50            &'a mut V,
 51            Range<usize>,
 52            &'a mut ViewContext<V>,
 53        ) -> SmallVec<[AnyElement<V>; 64]>,
 54    >,
 55    interactivity: StatefulInteractivity<V>,
 56    scroll_handle: Option<UniformListScrollHandle>,
 57}
 58
 59#[derive(Clone, Default)]
 60pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
 61
 62#[derive(Clone, Debug)]
 63struct ScrollHandleState {
 64    item_height: Pixels,
 65    list_height: Pixels,
 66    scroll_offset: Arc<Mutex<Point<Pixels>>>,
 67}
 68
 69impl UniformListScrollHandle {
 70    pub fn new() -> Self {
 71        Self(Arc::new(Mutex::new(None)))
 72    }
 73
 74    pub fn scroll_to_item(&self, ix: usize) {
 75        if let Some(state) = &*self.0.lock() {
 76            let mut scroll_offset = state.scroll_offset.lock();
 77            let item_top = state.item_height * ix;
 78            let item_bottom = item_top + state.item_height;
 79            let scroll_top = -scroll_offset.y;
 80            if item_top < scroll_top {
 81                scroll_offset.y = -item_top;
 82            } else if item_bottom > scroll_top + state.list_height {
 83                scroll_offset.y = -(item_bottom - state.list_height);
 84            }
 85        }
 86    }
 87}
 88
 89impl<V: 'static> Styled for UniformList<V> {
 90    fn style(&mut self) -> &mut StyleRefinement {
 91        &mut self.style
 92    }
 93}
 94
 95#[derive(Default)]
 96pub struct UniformListState {
 97    interactive: InteractiveElementState,
 98    item_size: Size<Pixels>,
 99}
100
101impl<V: 'static> Element<V> for UniformList<V> {
102    type ElementState = UniformListState;
103
104    fn id(&self) -> Option<crate::ElementId> {
105        Some(self.id.clone())
106    }
107
108    fn initialize(
109        &mut self,
110        view_state: &mut V,
111        element_state: Option<Self::ElementState>,
112        cx: &mut ViewContext<V>,
113    ) -> Self::ElementState {
114        element_state.unwrap_or_else(|| {
115            let item_size = self.measure_first_item(view_state, None, cx);
116            UniformListState {
117                interactive: InteractiveElementState::default(),
118                item_size,
119            }
120        })
121    }
122
123    fn layout(
124        &mut self,
125        _view_state: &mut V,
126        element_state: &mut Self::ElementState,
127        cx: &mut ViewContext<V>,
128    ) -> LayoutId {
129        let max_items = self.item_count;
130        let item_size = element_state.item_size;
131        let rem_size = cx.rem_size();
132
133        cx.request_measured_layout(
134            self.computed_style(),
135            rem_size,
136            move |known_dimensions: Size<Option<Pixels>>, available_space: Size<AvailableSpace>| {
137                let desired_height = item_size.height * max_items;
138                let width = known_dimensions
139                    .width
140                    .unwrap_or(match available_space.width {
141                        AvailableSpace::Definite(x) => x,
142                        AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
143                    });
144                let height = match available_space.height {
145                    AvailableSpace::Definite(x) => desired_height.min(x),
146                    AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
147                };
148                size(width, height)
149            },
150        )
151    }
152
153    fn paint(
154        &mut self,
155        bounds: crate::Bounds<crate::Pixels>,
156        view_state: &mut V,
157        element_state: &mut Self::ElementState,
158        cx: &mut ViewContext<V>,
159    ) {
160        let style = self.computed_style();
161
162        let border = style.border_widths.to_pixels(cx.rem_size());
163        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
164
165        let padded_bounds = Bounds::from_corners(
166            bounds.origin + point(border.left + padding.left, border.top + padding.top),
167            bounds.lower_right()
168                - point(border.right + padding.right, border.bottom + padding.bottom),
169        );
170
171        cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
172            style.paint(bounds, cx);
173
174            let content_size;
175            if self.item_count > 0 {
176                let item_height = self
177                    .measure_first_item(view_state, Some(padded_bounds.size.width), cx)
178                    .height;
179                if let Some(scroll_handle) = self.scroll_handle.clone() {
180                    scroll_handle.0.lock().replace(ScrollHandleState {
181                        item_height,
182                        list_height: padded_bounds.size.height,
183                        scroll_offset: element_state.interactive.track_scroll_offset(),
184                    });
185                }
186                let visible_item_count = if item_height > px(0.) {
187                    (padded_bounds.size.height / item_height).ceil() as usize + 1
188                } else {
189                    0
190                };
191                let scroll_offset = element_state
192                    .interactive
193                    .scroll_offset()
194                    .map_or((0.0).into(), |offset| offset.y);
195                let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
196                let visible_range = first_visible_element_ix
197                    ..cmp::min(
198                        first_visible_element_ix + visible_item_count,
199                        self.item_count,
200                    );
201
202                let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
203
204                content_size = Size {
205                    width: padded_bounds.size.width,
206                    height: item_height * self.item_count,
207                };
208
209                cx.with_z_index(1, |cx| {
210                    for (item, ix) in items.iter_mut().zip(visible_range) {
211                        let item_origin =
212                            padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
213                        let available_space = size(
214                            AvailableSpace::Definite(padded_bounds.size.width),
215                            AvailableSpace::Definite(item_height),
216                        );
217                        item.draw(item_origin, available_space, view_state, cx);
218                    }
219                });
220            } else {
221                content_size = Size {
222                    width: bounds.size.width,
223                    height: px(0.),
224                };
225            }
226
227            let overflow = point(style.overflow.x, Overflow::Scroll);
228
229            cx.with_z_index(0, |cx| {
230                self.interactivity.paint(
231                    bounds,
232                    content_size,
233                    overflow,
234                    &mut element_state.interactive,
235                    cx,
236                );
237            });
238        })
239    }
240}
241
242impl<V> UniformList<V> {
243    fn measure_first_item(
244        &self,
245        view_state: &mut V,
246        list_width: Option<Pixels>,
247        cx: &mut ViewContext<V>,
248    ) -> Size<Pixels> {
249        let mut items = (self.render_items)(view_state, 0..1, cx);
250        debug_assert_eq!(items.len(), 1);
251        let mut item_to_measure = items.pop().unwrap();
252        let available_space = size(
253            list_width.map_or(AvailableSpace::MinContent, |width| {
254                AvailableSpace::Definite(width)
255            }),
256            AvailableSpace::MinContent,
257        );
258        item_to_measure.measure(available_space, view_state, cx)
259    }
260
261    pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
262        self.scroll_handle = Some(handle);
263        self
264    }
265}
266
267impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
268    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
269        self.interactivity.as_stateless_mut()
270    }
271}
272
273impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
274    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
275        &mut self.interactivity
276    }
277}
278
279impl<V: 'static> Component<V> for UniformList<V> {
280    fn render(self) -> AnyElement<V> {
281        AnyElement::new(self)
282    }
283}