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}