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