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