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