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..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;
238 self.scroll_top = (self.scroll_top - delta.y()).max(0.0).min(scroll_max);
239 cx.notify();
240
241 true
242 }
243}
244
245impl ElementHeight {
246 fn is_pending(&self) -> bool {
247 matches!(self, ElementHeight::Pending)
248 }
249}
250
251impl sum_tree::Item for ElementHeight {
252 type Summary = ElementHeightSummary;
253
254 fn summary(&self) -> Self::Summary {
255 match self {
256 ElementHeight::Pending => ElementHeightSummary {
257 count: 1,
258 pending_count: 1,
259 height: 0.,
260 },
261 ElementHeight::Ready(height) => ElementHeightSummary {
262 count: 1,
263 pending_count: 0,
264 height: *height,
265 },
266 }
267 }
268}
269
270impl sum_tree::Summary for ElementHeightSummary {
271 type Context = ();
272
273 fn add_summary(&mut self, summary: &Self, _: &()) {
274 self.count += summary.count;
275 self.pending_count += summary.pending_count;
276 self.height += summary.height;
277 }
278}
279
280impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for ElementHeightSummary {
281 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
282 sum_tree::Summary::add_summary(self, summary, &());
283 }
284}
285
286impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Count {
287 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
288 self.0 += summary.count;
289 }
290}
291
292impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Count {
293 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
294 self.0.cmp(&other.0)
295 }
296}
297
298impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for PendingCount {
299 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
300 self.0 += summary.pending_count;
301 }
302}
303
304impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for PendingCount {
305 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
306 self.0.cmp(&other.0)
307 }
308}
309
310impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height {
311 fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
312 self.0 += summary.height;
313 }
314}
315
316impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height {
317 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
318 self.0.partial_cmp(&other.0).unwrap()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::{elements::*, geometry::vector::vec2f};
326
327 #[crate::test(self)]
328 fn test_layout(cx: &mut crate::MutableAppContext) {
329 let mut presenter = cx.build_presenter(0, 20.0);
330 let mut layout_cx = presenter.layout_cx(cx);
331 let state = ListState::new(vec![item(20.), item(30.), item(10.)]);
332 let mut list = List::new(state.clone()).boxed();
333
334 let size = list.layout(
335 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
336 &mut layout_cx,
337 );
338 assert_eq!(size, vec2f(100., 40.));
339 assert_eq!(
340 state.0.lock().heights.summary(),
341 ElementHeightSummary {
342 count: 3,
343 pending_count: 0,
344 height: 60.
345 }
346 );
347
348 state.splice(1..2, vec![item(40.), item(50.)]);
349 state.splice(3..3, vec![item(60.)]);
350 assert_eq!(
351 state.0.lock().heights.summary(),
352 ElementHeightSummary {
353 count: 5,
354 pending_count: 3,
355 height: 30.
356 }
357 );
358 let size = list.layout(
359 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
360 &mut layout_cx,
361 );
362 assert_eq!(size, vec2f(100., 40.));
363 assert_eq!(
364 state.0.lock().heights.summary(),
365 ElementHeightSummary {
366 count: 5,
367 pending_count: 0,
368 height: 180.
369 }
370 );
371 }
372
373 fn item(height: f32) -> ElementBox {
374 ConstrainedBox::new(Empty::new().boxed())
375 .with_height(height)
376 .with_width(100.)
377 .boxed()
378 }
379}