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: id.into(),
 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 =
145                    (padded_bounds.size.height / item_height).ceil() as usize + 1;
146                let scroll_offset = element_state
147                    .scroll_offset()
148                    .map_or((0.0).into(), |offset| offset.y);
149                let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
150                let visible_range = first_visible_element_ix
151                    ..cmp::min(
152                        first_visible_element_ix + visible_item_count,
153                        self.item_count,
154                    );
155
156                let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
157
158                content_size = Size {
159                    width: padded_bounds.size.width,
160                    height: item_height * self.item_count,
161                };
162
163                cx.with_z_index(1, |cx| {
164                    for (item, ix) in items.iter_mut().zip(visible_range) {
165                        item.initialize(view_state, cx);
166
167                        let layout_id = item.layout(view_state, cx);
168                        cx.compute_layout(
169                            layout_id,
170                            Size {
171                                width: AvailableSpace::Definite(bounds.size.width),
172                                height: AvailableSpace::Definite(item_height),
173                            },
174                        );
175                        let offset =
176                            padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
177                        cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
178                    }
179                });
180            } else {
181                content_size = Size {
182                    width: bounds.size.width,
183                    height: px(0.),
184                };
185            }
186
187            let overflow = point(style.overflow.x, Overflow::Scroll);
188
189            cx.with_z_index(0, |cx| {
190                self.interactivity
191                    .paint(bounds, content_size, overflow, element_state, cx);
192            });
193        })
194    }
195}
196
197impl<V> UniformList<V> {
198    fn measure_item_height(
199        &self,
200        view_state: &mut V,
201        list_bounds: Bounds<Pixels>,
202        cx: &mut ViewContext<V>,
203    ) -> Pixels {
204        let mut items = (self.render_items)(view_state, 0..1, cx);
205        debug_assert!(items.len() == 1);
206        let mut item_to_measure = items.pop().unwrap();
207        item_to_measure.initialize(view_state, cx);
208        let layout_id = item_to_measure.layout(view_state, cx);
209        cx.compute_layout(
210            layout_id,
211            Size {
212                width: AvailableSpace::Definite(list_bounds.size.width),
213                height: AvailableSpace::MinContent,
214            },
215        );
216        cx.layout_bounds(layout_id).size.height
217    }
218
219    pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
220        self.scroll_handle = Some(handle);
221        self
222    }
223}
224
225impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
226    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
227        self.interactivity.as_stateless_mut()
228    }
229}
230
231impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
232    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
233        &mut self.interactivity
234    }
235}
236
237impl<V: 'static> Component<V> for UniformList<V> {
238    fn render(self) -> AnyElement<V> {
239        AnyElement::new(self)
240    }
241}