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