uniform_list.rs

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