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