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 render_items: Box::new(move |view, visible_range, cx| {
34 f(view, visible_range, cx)
35 .into_iter()
36 .map(|component| component.render())
37 .collect()
38 }),
39 interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
40 scroll_handle: None,
41 }
42}
43
44pub struct UniformList<V: 'static> {
45 id: ElementId,
46 style: StyleRefinement,
47 item_count: usize,
48 render_items: Box<
49 dyn for<'a> Fn(
50 &'a mut V,
51 Range<usize>,
52 &'a mut ViewContext<V>,
53 ) -> SmallVec<[AnyElement<V>; 64]>,
54 >,
55 interactivity: StatefulInteractivity<V>,
56 scroll_handle: Option<UniformListScrollHandle>,
57}
58
59#[derive(Clone, Default)]
60pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
61
62#[derive(Clone, Debug)]
63struct ScrollHandleState {
64 item_height: Pixels,
65 list_height: Pixels,
66 scroll_offset: Arc<Mutex<Point<Pixels>>>,
67}
68
69impl UniformListScrollHandle {
70 pub fn new() -> Self {
71 Self(Arc::new(Mutex::new(None)))
72 }
73
74 pub fn scroll_to_item(&self, ix: usize) {
75 if let Some(state) = &*self.0.lock() {
76 let mut scroll_offset = state.scroll_offset.lock();
77 let item_top = state.item_height * ix;
78 let item_bottom = item_top + state.item_height;
79 let scroll_top = -scroll_offset.y;
80 if item_top < scroll_top {
81 scroll_offset.y = -item_top;
82 } else if item_bottom > scroll_top + state.list_height {
83 scroll_offset.y = -(item_bottom - state.list_height);
84 }
85 }
86 }
87}
88
89impl<V: 'static> Styled for UniformList<V> {
90 fn style(&mut self) -> &mut StyleRefinement {
91 &mut self.style
92 }
93}
94
95#[derive(Default)]
96pub struct UniformListState {
97 interactive: InteractiveElementState,
98 item_size: Size<Pixels>,
99}
100
101impl<V: 'static> Element<V> for UniformList<V> {
102 type ElementState = UniformListState;
103
104 fn id(&self) -> Option<crate::ElementId> {
105 Some(self.id.clone())
106 }
107
108 fn initialize(
109 &mut self,
110 view_state: &mut V,
111 element_state: Option<Self::ElementState>,
112 cx: &mut ViewContext<V>,
113 ) -> Self::ElementState {
114 element_state.unwrap_or_else(|| {
115 let item_size = self.measure_first_item(view_state, None, cx);
116 UniformListState {
117 interactive: InteractiveElementState::default(),
118 item_size,
119 }
120 })
121 }
122
123 fn layout(
124 &mut self,
125 _view_state: &mut V,
126 element_state: &mut Self::ElementState,
127 cx: &mut ViewContext<V>,
128 ) -> LayoutId {
129 let max_items = self.item_count;
130 let item_size = element_state.item_size;
131 let rem_size = cx.rem_size();
132
133 cx.request_measured_layout(
134 self.computed_style(),
135 rem_size,
136 move |known_dimensions: Size<Option<Pixels>>, available_space: Size<AvailableSpace>| {
137 let desired_height = item_size.height * max_items;
138 let width = known_dimensions
139 .width
140 .unwrap_or(match available_space.width {
141 AvailableSpace::Definite(x) => x,
142 AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
143 });
144 let height = match available_space.height {
145 AvailableSpace::Definite(x) => desired_height.min(x),
146 AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
147 };
148 size(width, height)
149 },
150 )
151 }
152
153 fn paint(
154 &mut self,
155 bounds: crate::Bounds<crate::Pixels>,
156 view_state: &mut V,
157 element_state: &mut Self::ElementState,
158 cx: &mut ViewContext<V>,
159 ) {
160 let style = self.computed_style();
161
162 let border = style.border_widths.to_pixels(cx.rem_size());
163 let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
164
165 let padded_bounds = Bounds::from_corners(
166 bounds.origin + point(border.left + padding.left, border.top + padding.top),
167 bounds.lower_right()
168 - point(border.right + padding.right, border.bottom + padding.bottom),
169 );
170
171 cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
172 style.paint(bounds, cx);
173
174 let content_size;
175 if self.item_count > 0 {
176 let item_height = self
177 .measure_first_item(view_state, Some(padded_bounds.size.width), cx)
178 .height;
179 if let Some(scroll_handle) = self.scroll_handle.clone() {
180 scroll_handle.0.lock().replace(ScrollHandleState {
181 item_height,
182 list_height: padded_bounds.size.height,
183 scroll_offset: element_state.interactive.track_scroll_offset(),
184 });
185 }
186 let visible_item_count = if item_height > px(0.) {
187 (padded_bounds.size.height / item_height).ceil() as usize + 1
188 } else {
189 0
190 };
191 let scroll_offset = element_state
192 .interactive
193 .scroll_offset()
194 .map_or((0.0).into(), |offset| offset.y);
195 let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
196 let visible_range = first_visible_element_ix
197 ..cmp::min(
198 first_visible_element_ix + visible_item_count,
199 self.item_count,
200 );
201
202 let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
203
204 content_size = Size {
205 width: padded_bounds.size.width,
206 height: item_height * self.item_count,
207 };
208
209 cx.with_z_index(1, |cx| {
210 for (item, ix) in items.iter_mut().zip(visible_range) {
211 let item_origin =
212 padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
213 let available_space = size(
214 AvailableSpace::Definite(padded_bounds.size.width),
215 AvailableSpace::Definite(item_height),
216 );
217 item.draw(item_origin, available_space, view_state, cx);
218 }
219 });
220 } else {
221 content_size = Size {
222 width: bounds.size.width,
223 height: px(0.),
224 };
225 }
226
227 let overflow = point(style.overflow.x, Overflow::Scroll);
228
229 cx.with_z_index(0, |cx| {
230 self.interactivity.paint(
231 bounds,
232 content_size,
233 overflow,
234 &mut element_state.interactive,
235 cx,
236 );
237 });
238 })
239 }
240}
241
242impl<V> UniformList<V> {
243 fn measure_first_item(
244 &self,
245 view_state: &mut V,
246 list_width: Option<Pixels>,
247 cx: &mut ViewContext<V>,
248 ) -> Size<Pixels> {
249 let mut items = (self.render_items)(view_state, 0..1, cx);
250 debug_assert_eq!(items.len(), 1);
251 let mut item_to_measure = items.pop().unwrap();
252 let available_space = size(
253 list_width.map_or(AvailableSpace::MinContent, |width| {
254 AvailableSpace::Definite(width)
255 }),
256 AvailableSpace::MinContent,
257 );
258 item_to_measure.measure(available_space, view_state, cx)
259 }
260
261 pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
262 self.scroll_handle = Some(handle);
263 self
264 }
265}
266
267impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
268 fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
269 self.interactivity.as_stateless_mut()
270 }
271}
272
273impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
274 fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
275 &mut self.interactivity
276 }
277}
278
279impl<V: 'static> Component<V> for UniformList<V> {
280 fn render(self) -> AnyElement<V> {
281 AnyElement::new(self)
282 }
283}