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