1use crate::{
2 geometry::{
3 rect::RectF,
4 vector::{vec2f, Vector2F},
5 },
6 json::json,
7 sum_tree::{self, Bias, SumTree},
8 DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
9};
10use parking_lot::Mutex;
11use std::{ops::Range, sync::Arc};
12
13use crate::ElementBox;
14
15pub struct List {
16 state: ListState,
17}
18
19#[derive(Clone)]
20pub struct ListState(Arc<Mutex<StateInner>>);
21
22struct StateInner {
23 last_layout_width: f32,
24 elements: Vec<ElementBox>,
25 heights: SumTree<ElementHeight>,
26 scroll_top: f32,
27}
28
29#[derive(Clone, Debug)]
30enum ElementHeight {
31 Pending,
32 Ready(f32),
33}
34
35#[derive(Clone, Debug, Default, PartialEq)]
36struct ElementHeightSummary {
37 count: usize,
38 pending_count: usize,
39 height: f32,
40}
41
42#[derive(Clone, Debug, Default)]
43struct Count(usize);
44
45#[derive(Clone, Debug, Default)]
46struct PendingCount(usize);
47
48#[derive(Clone, Debug, Default)]
49struct Height(f32);
50
51impl List {
52 pub fn new(state: ListState) -> Self {
53 Self { state }
54 }
55
56 fn scroll(
57 &self,
58 _: Vector2F,
59 delta: Vector2F,
60 precise: bool,
61 scroll_max: f32,
62 cx: &mut EventContext,
63 ) -> bool {
64 if !precise {
65 todo!("still need to handle non-precise scroll events from a mouse wheel");
66 }
67
68 let mut state = self.state.0.lock();
69 state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
70 cx.notify();
71
72 true
73 }
74}
75
76impl Element for List {
77 type LayoutState = ();
78
79 type PaintState = ();
80
81 fn layout(
82 &mut self,
83 constraint: SizeConstraint,
84 cx: &mut LayoutContext,
85 ) -> (Vector2F, Self::LayoutState) {
86 let state = &mut *self.state.0.lock();
87 let mut item_constraint = constraint;
88 item_constraint.min.set_y(0.);
89 item_constraint.max.set_y(f32::INFINITY);
90
91 if state.last_layout_width == constraint.max.x() {
92 let mut old_heights = state.heights.cursor::<PendingCount, ElementHeightSummary>();
93 let mut new_heights = old_heights.slice(&PendingCount(1), sum_tree::Bias::Left, &());
94
95 while let Some(height) = old_heights.item() {
96 if height.is_pending() {
97 let size =
98 state.elements[old_heights.sum_start().count].layout(item_constraint, cx);
99 new_heights.push(ElementHeight::Ready(size.y()), &());
100 old_heights.next(&());
101 } else {
102 new_heights.push_tree(
103 old_heights.slice(
104 &PendingCount(old_heights.sum_start().pending_count + 1),
105 Bias::Left,
106 &(),
107 ),
108 &(),
109 );
110 }
111 }
112
113 drop(old_heights);
114 state.heights = new_heights;
115 } else {
116 state.heights = SumTree::new();
117 for element in &mut state.elements {
118 let size = element.layout(item_constraint, cx);
119 state.heights.push(ElementHeight::Ready(size.y()), &());
120 }
121 state.last_layout_width = constraint.max.x();
122 }
123
124 (constraint.max, ())
125 }
126
127 fn paint(&mut self, bounds: RectF, _: &mut (), cx: &mut PaintContext) {
128 let state = &mut *self.state.0.lock();
129 let visible_range = state.visible_range(bounds.height());
130
131 let mut item_top = {
132 let mut cursor = state.heights.cursor::<Count, Height>();
133 cursor.seek(&Count(visible_range.start), Bias::Right, &());
134 cursor.sum_start().0
135 };
136 for element in &mut state.elements[visible_range] {
137 let origin = bounds.origin() + vec2f(0., item_top) - state.scroll_top;
138 element.paint(origin, cx);
139 item_top += element.size().y();
140 }
141 }
142
143 fn dispatch_event(
144 &mut self,
145 event: &Event,
146 bounds: RectF,
147 _: &mut (),
148 _: &mut (),
149 cx: &mut EventContext,
150 ) -> bool {
151 let mut handled = false;
152
153 let mut state = self.state.0.lock();
154 let visible_range = state.visible_range(bounds.height());
155 for item in &mut state.elements[visible_range] {
156 handled = item.dispatch_event(event, cx) || handled;
157 }
158
159 match event {
160 Event::ScrollWheel {
161 position,
162 delta,
163 precise,
164 } => {
165 if bounds.contains_point(*position) {
166 let scroll_max = state.scroll_max(bounds.height());
167 if self.scroll(*position, *delta, *precise, scroll_max, cx) {
168 handled = true;
169 }
170 }
171 }
172 _ => {}
173 }
174
175 handled
176 }
177
178 fn debug(&self, bounds: RectF, _: &(), _: &(), cx: &DebugContext) -> serde_json::Value {
179 let state = self.state.0.lock();
180 let visible_range = state.visible_range(bounds.height());
181 let visible_elements = state.elements[visible_range.clone()]
182 .iter()
183 .map(|e| e.debug(cx))
184 .collect::<Vec<_>>();
185 json!({
186 "visible_range": visible_range,
187 "visible_elements": visible_elements,
188 "scroll_top": state.scroll_top,
189 })
190 }
191}
192
193impl ListState {
194 pub fn new(elements: Vec<ElementBox>) -> Self {
195 let mut heights = SumTree::new();
196 heights.extend(elements.iter().map(|_| ElementHeight::Pending), &());
197 Self(Arc::new(Mutex::new(StateInner {
198 last_layout_width: 0.,
199 elements,
200 heights,
201 scroll_top: 0.,
202 })))
203 }
204
205 pub fn splice(
206 &self,
207 old_range: Range<usize>,
208 new_elements: impl IntoIterator<Item = ElementBox>,
209 ) {
210 let state = &mut *self.0.lock();
211
212 let mut old_heights = state.heights.cursor::<Count, ()>();
213 let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
214 old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
215
216 let mut len = 0;
217 let old_elements = state.elements.splice(
218 old_range,
219 new_elements.into_iter().map(|e| {
220 len += 1;
221 e
222 }),
223 );
224 drop(old_elements);
225
226 new_heights.extend((0..len).map(|_| ElementHeight::Pending), &());
227 new_heights.push_tree(old_heights.suffix(&()), &());
228 drop(old_heights);
229 state.heights = new_heights;
230 }
231}
232
233impl StateInner {
234 fn visible_range(&self, height: f32) -> Range<usize> {
235 let mut cursor = self.heights.cursor::<Height, Count>();
236 cursor.seek(&Height(self.scroll_top), Bias::Right, &());
237 let start_ix = cursor.sum_start().0;
238 cursor.seek(&Height(self.scroll_top + height), Bias::Left, &());
239 let end_ix = cursor.sum_start().0;
240 start_ix..end_ix + 1
241 }
242
243 fn scroll_max(&self, height: f32) -> f32 {
244 self.heights.summary().height - height
245 }
246}
247
248impl ElementHeight {
249 fn is_pending(&self) -> bool {
250 matches!(self, ElementHeight::Pending)
251 }
252}
253
254impl sum_tree::Item for ElementHeight {
255 type Summary = ElementHeightSummary;
256
257 fn summary(&self) -> Self::Summary {
258 match self {
259 ElementHeight::Pending => ElementHeightSummary {
260 count: 1,
261 pending_count: 1,
262 height: 0.,
263 },
264 ElementHeight::Ready(height) => ElementHeightSummary {
265 count: 1,
266 pending_count: 0,
267 height: *height,
268 },
269 }
270 }
271}
272
273impl sum_tree::Summary for ElementHeightSummary {
274 type Context = ();
275
276 fn add_summary(&mut self, summary: &Self, _: &()) {
277 self.count += summary.count;
278 self.pending_count += summary.pending_count;
279 self.height += summary.height;
280 }
281}
282
283impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for ElementHeightSummary {
284 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
285 sum_tree::Summary::add_summary(self, summary, &());
286 }
287}
288
289impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Count {
290 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
291 self.0 += summary.count;
292 }
293}
294
295impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Count {
296 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
297 self.0.cmp(&other.0)
298 }
299}
300
301impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for PendingCount {
302 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
303 self.0 += summary.pending_count;
304 }
305}
306
307impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for PendingCount {
308 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
309 self.0.cmp(&other.0)
310 }
311}
312
313impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height {
314 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
315 self.0 += summary.height;
316 }
317}
318
319impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height {
320 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
321 self.0.partial_cmp(&other.0).unwrap()
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::{elements::*, geometry::vector::vec2f};
329
330 #[crate::test(self)]
331 fn test_layout(cx: &mut crate::MutableAppContext) {
332 let mut presenter = cx.build_presenter(0, 20.0);
333 let mut layout_cx = presenter.layout_cx(cx);
334 let state = ListState::new(vec![item(20.), item(30.), item(10.)]);
335 let mut list = List::new(state.clone()).boxed();
336
337 let size = list.layout(
338 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
339 &mut layout_cx,
340 );
341 assert_eq!(size, vec2f(100., 40.));
342 assert_eq!(
343 state.0.lock().heights.summary(),
344 ElementHeightSummary {
345 count: 3,
346 pending_count: 0,
347 height: 60.
348 }
349 );
350
351 state.splice(1..2, vec![item(40.), item(50.)]);
352 state.splice(3..3, vec![item(60.)]);
353 assert_eq!(
354 state.0.lock().heights.summary(),
355 ElementHeightSummary {
356 count: 5,
357 pending_count: 3,
358 height: 30.
359 }
360 );
361 let size = list.layout(
362 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
363 &mut layout_cx,
364 );
365 assert_eq!(size, vec2f(100., 40.));
366 assert_eq!(
367 state.0.lock().heights.summary(),
368 ElementHeightSummary {
369 count: 5,
370 pending_count: 0,
371 height: 180.
372 }
373 );
374 }
375
376 fn item(height: f32) -> ElementBox {
377 ConstrainedBox::new(Empty::new().boxed())
378 .with_height(height)
379 .with_width(100.)
380 .boxed()
381 }
382}