use crate::{ point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext, }; use smallvec::SmallVec; use std::{cmp, ops::Range}; use taffy::style::Overflow; pub fn list( id: Id, item_count: usize, f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> SmallVec<[C; 64]>, ) -> List where Id: Into, V: 'static, C: Component, { let id = id.into(); List { id: id.clone(), style: Default::default(), item_count, render_items: Box::new(move |view, visible_range, cx| { f(view, visible_range, cx) .into_iter() .map(|component| component.render()) .collect() }), interactivity: id.into(), } } pub struct List { id: ElementId, style: StyleRefinement, item_count: usize, render_items: Box< dyn for<'a> Fn( &'a mut V, Range, &'a mut ViewContext, ) -> SmallVec<[AnyElement; 64]>, >, interactivity: StatefulInteractivity, } #[derive(Default)] pub struct ListState { interactive: InteractiveElementState, } impl Styled for List { fn style(&mut self) -> &mut StyleRefinement { &mut self.style } } impl Element for List { type ElementState = ListState; fn id(&self) -> Option { Some(self.id.clone()) } fn initialize( &mut self, _: &mut V, element_state: Option, _: &mut ViewContext, ) -> Self::ElementState { let element_state = element_state.unwrap_or_default(); element_state } fn layout( &mut self, _view_state: &mut V, _element_state: &mut Self::ElementState, cx: &mut ViewContext, ) -> LayoutId { cx.request_layout(&self.computed_style(), None) } fn paint( &mut self, bounds: crate::Bounds, view_state: &mut V, element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { let style = self.computed_style(); style.paint(bounds, cx); let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); let padded_bounds = Bounds::from_corners( bounds.origin + point(border.left + padding.left, border.top + padding.top), bounds.lower_right() - point(border.right + padding.right, border.bottom + padding.bottom), ); cx.with_z_index(style.z_index.unwrap_or(0), |cx| { let content_size; if self.item_count > 0 { let item_height = self.measure_item_height(view_state, padded_bounds, cx); let visible_item_count = (padded_bounds.size.height / item_height).ceil() as usize + 1; let scroll_offset = element_state .interactive .scroll_offset() .map_or((0.0).into(), |offset| offset.y); let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize; let visible_range = first_visible_element_ix ..cmp::min( first_visible_element_ix + visible_item_count, self.item_count, ); let mut items = (self.render_items)(view_state, visible_range.clone(), cx); content_size = Size { width: padded_bounds.size.width, height: item_height * self.item_count, }; cx.with_z_index(1, |cx| { for (item, ix) in items.iter_mut().zip(visible_range) { item.initialize(view_state, cx); let layout_id = item.layout(view_state, cx); cx.compute_layout( layout_id, Size { width: AvailableSpace::Definite(bounds.size.width), height: AvailableSpace::Definite(item_height), }, ); let offset = padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset); cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx)) } }); } else { content_size = Size { width: bounds.size.width, height: px(0.), }; } let overflow = point(style.overflow.x, Overflow::Scroll); cx.with_z_index(0, |cx| { self.interactivity.paint( bounds, content_size, overflow, &mut element_state.interactive, cx, ); }); }) } } impl List { fn measure_item_height( &self, view_state: &mut V, list_bounds: Bounds, cx: &mut ViewContext, ) -> Pixels { let mut items = (self.render_items)(view_state, 0..1, cx); debug_assert!(items.len() == 1); let mut item_to_measure = items.pop().unwrap(); item_to_measure.initialize(view_state, cx); let layout_id = item_to_measure.layout(view_state, cx); cx.compute_layout( layout_id, Size { width: AvailableSpace::Definite(list_bounds.size.width), height: AvailableSpace::MinContent, }, ); cx.layout_bounds(layout_id).size.height } } impl StatelessInteractive for List { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { self.interactivity.as_stateless_mut() } } impl StatefulInteractive for List { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { &mut self.interactivity } } impl Component for List { fn render(self) -> AnyElement { AnyElement::new(self) } }