1use crate::{
2 geometry::{
3 rect::RectF,
4 vector::{vec2f, Vector2F},
5 },
6 json::json,
7 sum_tree::{self, Bias, SumTree},
8 DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
9 SizeConstraint,
10};
11use std::{cell::RefCell, ops::Range, rc::Rc};
12
13pub struct List {
14 state: ListState,
15}
16
17#[derive(Clone)]
18pub struct ListState(Rc<RefCell<StateInner>>);
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum Orientation {
22 Top,
23 Bottom,
24}
25
26struct StateInner {
27 last_layout_width: Option<f32>,
28 render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> ElementBox>,
29 rendered_range: Range<usize>,
30 items: SumTree<ListItem>,
31 scroll_top: Option<ScrollTop>,
32 orientation: Orientation,
33 overdraw: f32,
34 scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
35}
36
37#[derive(Clone, Copy, Debug, Default, PartialEq)]
38pub struct ScrollTop {
39 item_ix: usize,
40 offset_in_item: f32,
41}
42
43#[derive(Clone)]
44enum ListItem {
45 Unrendered,
46 Rendered(ElementRc),
47 Removed(f32),
48}
49
50impl std::fmt::Debug for ListItem {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::Unrendered => write!(f, "Unrendered"),
54 Self::Rendered(_) => f.debug_tuple("Rendered").finish(),
55 Self::Removed(height) => f.debug_tuple("Removed").field(height).finish(),
56 }
57 }
58}
59
60#[derive(Clone, Debug, Default, PartialEq)]
61struct ListItemSummary {
62 count: usize,
63 rendered_count: usize,
64 unrendered_count: usize,
65 height: f32,
66}
67
68#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
69struct Count(usize);
70
71#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
72struct RenderedCount(usize);
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
75struct UnrenderedCount(usize);
76
77#[derive(Clone, Debug, Default)]
78struct Height(f32);
79
80impl List {
81 pub fn new(state: ListState) -> Self {
82 Self { state }
83 }
84}
85
86impl Element for List {
87 type LayoutState = ScrollTop;
88 type PaintState = ();
89
90 fn layout(
91 &mut self,
92 constraint: SizeConstraint,
93 cx: &mut LayoutContext,
94 ) -> (Vector2F, Self::LayoutState) {
95 let state = &mut *self.state.0.borrow_mut();
96 let size = constraint.max;
97 let mut item_constraint = constraint;
98 item_constraint.min.set_y(0.);
99 item_constraint.max.set_y(f32::INFINITY);
100
101 if state.last_layout_width != Some(size.x()) {
102 state.rendered_range = 0..0;
103 state.items = SumTree::from_iter(
104 (0..state.items.summary().count).map(|_| ListItem::Unrendered),
105 &(),
106 )
107 }
108
109 let overdraw = state.overdraw;
110 let old_rendered_range = state.rendered_range.clone();
111 let old_items = state.items.clone();
112 let orientation = state.orientation;
113 let mut stored_scroll_top = state.scroll_top;
114 let mut new_items = SumTree::new();
115
116 let mut render_item = |ix, old_item: &ListItem| {
117 let element = if let ListItem::Rendered(element) = old_item {
118 element.clone()
119 } else {
120 let mut element = (state.render_item)(ix, cx);
121 element.layout(item_constraint, cx);
122 element.into()
123 };
124 element
125 };
126
127 // Determine the items that need to be rendered in order to cover the visible area plus the
128 // overdraw. When parked at the end of a bottom-oriented list, this requires rendering items
129 // starting from the end of the list until the visible region is full. In other cases, the
130 // stored scroll can be used.
131 let mut new_rendered_start_ix;
132 let mut rendered_items = Vec::new();
133 let mut scroll_top;
134 if let (Orientation::Bottom, None) = (orientation, stored_scroll_top) {
135 let mut rendered_height = 0.;
136 let mut cursor = old_items.cursor::<Count, ()>();
137 cursor.seek(&Count(old_items.summary().count), Bias::Left, &());
138
139 while let Some(item) = cursor.item() {
140 let element = render_item(cursor.seek_start().0, item);
141 rendered_height += element.size().y();
142 rendered_items.push(ListItem::Rendered(element));
143 if rendered_height >= size.y() {
144 break;
145 } else {
146 cursor.prev(&());
147 }
148 }
149 scroll_top = ScrollTop {
150 item_ix: cursor.seek_start().0,
151 offset_in_item: rendered_height - size.y(),
152 };
153
154 cursor.prev(&());
155 while let Some(item) = cursor.item() {
156 let element = render_item(cursor.seek_start().0, item);
157 rendered_height += element.size().y();
158 rendered_items.push(ListItem::Rendered(element));
159 if rendered_height >= size.y() + overdraw {
160 break;
161 } else {
162 cursor.prev(&());
163 }
164 }
165
166 rendered_items.reverse();
167 new_rendered_start_ix = cursor.seek_start().0;
168 } else {
169 scroll_top = stored_scroll_top.unwrap_or_default();
170 let mut rendered_height = 0.;
171 let mut cursor = old_items.cursor::<Count, ()>();
172 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
173 for (ix, item) in cursor.by_ref().enumerate() {
174 if rendered_height >= scroll_top.offset_in_item + size.y() + overdraw {
175 break;
176 }
177
178 let element = render_item(scroll_top.item_ix + ix, item);
179 rendered_height += element.size().y();
180 rendered_items.push(ListItem::Rendered(element));
181 }
182 rendered_items.reverse();
183
184 new_rendered_start_ix = scroll_top.item_ix;
185 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
186 cursor.prev(&());
187 let mut remaining_overdraw = overdraw - scroll_top.offset_in_item;
188 while let Some(item) = cursor.item() {
189 let element = render_item(cursor.seek_start().0, item);
190 if rendered_height < size.y() && rendered_height + element.size().y() >= size.y() {
191 let new_scroll_top = ScrollTop {
192 item_ix: cursor.seek_start().0,
193 offset_in_item: rendered_height + element.size().y() - size.y(),
194 };
195 stored_scroll_top = Some(new_scroll_top);
196 remaining_overdraw = overdraw + (size.y() - rendered_height);
197 } else if rendered_height >= size.y() && remaining_overdraw <= 0. {
198 break;
199 }
200
201 rendered_height += element.size().y();
202 remaining_overdraw -= element.size().y();
203 rendered_items.push(ListItem::Rendered(element));
204 new_rendered_start_ix -= 1;
205 cursor.prev(&());
206 }
207 rendered_items.reverse();
208
209 if rendered_height < size.y() {
210 stored_scroll_top = None;
211 scroll_top = match orientation {
212 Orientation::Top => Default::default(),
213 Orientation::Bottom => ScrollTop {
214 item_ix: cursor.seek_start().0,
215 offset_in_item: rendered_height - size.y(),
216 },
217 };
218 }
219 }
220 let new_rendered_range =
221 new_rendered_start_ix..new_rendered_start_ix + rendered_items.len();
222
223 let mut cursor = old_items.cursor::<Count, ()>();
224
225 // Discard any rendered elements before the new rendered range.
226 if old_rendered_range.start < new_rendered_range.start {
227 new_items.push_tree(
228 cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()),
229 &(),
230 );
231 let remove_to = old_rendered_range.end.min(new_rendered_range.start);
232 while cursor.seek_start().0 < remove_to {
233 new_items.push(cursor.item().unwrap().remove(), &());
234 cursor.next(&());
235 }
236 }
237
238 new_items.push_tree(
239 cursor.slice(&Count(new_rendered_range.start), Bias::Right, &()),
240 &(),
241 );
242 new_items.extend(rendered_items, &());
243 cursor.seek(&Count(new_rendered_range.end), Bias::Right, &());
244
245 if new_rendered_range.end < old_rendered_range.start {
246 new_items.push_tree(
247 cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()),
248 &(),
249 );
250 }
251
252 while cursor.seek_start().0 < old_rendered_range.end {
253 new_items.push(cursor.item().unwrap().remove(), &());
254 cursor.next(&());
255 }
256
257 new_items.push_tree(cursor.suffix(&()), &());
258
259 drop(cursor);
260 state.items = new_items;
261 state.rendered_range = new_rendered_range;
262 state.last_layout_width = Some(size.x());
263 state.scroll_top = stored_scroll_top;
264 (size, scroll_top)
265 }
266
267 fn paint(&mut self, bounds: RectF, scroll_top: &mut ScrollTop, cx: &mut PaintContext) {
268 cx.scene.push_layer(Some(bounds));
269
270 let state = &mut *self.state.0.borrow_mut();
271 for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
272 element.paint(origin, cx);
273 }
274
275 cx.scene.pop_layer();
276 }
277
278 fn dispatch_event(
279 &mut self,
280 event: &Event,
281 bounds: RectF,
282 scroll_top: &mut ScrollTop,
283 _: &mut (),
284 cx: &mut EventContext,
285 ) -> bool {
286 let mut handled = false;
287
288 let mut state = self.state.0.borrow_mut();
289 for (mut element, _) in state.visible_elements(bounds, scroll_top) {
290 handled = element.dispatch_event(event, cx) || handled;
291 }
292
293 match event {
294 Event::ScrollWheel {
295 position,
296 delta,
297 precise,
298 } => {
299 if bounds.contains_point(*position) {
300 if state.scroll(scroll_top, bounds.height(), *delta, *precise, cx) {
301 handled = true;
302 }
303 }
304 }
305 _ => {}
306 }
307
308 handled
309 }
310
311 fn debug(
312 &self,
313 bounds: RectF,
314 scroll_top: &Self::LayoutState,
315 _: &(),
316 cx: &DebugContext,
317 ) -> serde_json::Value {
318 let state = self.state.0.borrow_mut();
319 let visible_elements = state
320 .visible_elements(bounds, scroll_top)
321 .map(|e| e.0.debug(cx))
322 .collect::<Vec<_>>();
323 let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
324 json!({
325 "visible_range": visible_range,
326 "visible_elements": visible_elements,
327 "scroll_top": state.scroll_top.map(|top| (top.item_ix, top.offset_in_item)),
328 })
329 }
330}
331
332impl ListState {
333 pub fn new<F>(
334 element_count: usize,
335 orientation: Orientation,
336 overdraw: f32,
337 render_item: F,
338 ) -> Self
339 where
340 F: 'static + FnMut(usize, &mut LayoutContext) -> ElementBox,
341 {
342 let mut items = SumTree::new();
343 items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
344 Self(Rc::new(RefCell::new(StateInner {
345 last_layout_width: None,
346 render_item: Box::new(render_item),
347 rendered_range: 0..0,
348 items,
349 scroll_top: None,
350 orientation,
351 overdraw,
352 scroll_handler: None,
353 })))
354 }
355
356 pub fn reset(&self, element_count: usize) {
357 let state = &mut *self.0.borrow_mut();
358 state.scroll_top = None;
359 state.items = SumTree::new();
360 state
361 .items
362 .extend((0..element_count).map(|_| ListItem::Unrendered), &());
363 }
364
365 pub fn splice(&self, old_range: Range<usize>, count: usize) {
366 let state = &mut *self.0.borrow_mut();
367
368 if let Some(ScrollTop {
369 item_ix,
370 offset_in_item,
371 }) = state.scroll_top.as_mut()
372 {
373 if old_range.contains(item_ix) {
374 *item_ix = old_range.start;
375 *offset_in_item = 0.;
376 } else if old_range.end <= *item_ix {
377 *item_ix = *item_ix - (old_range.end - old_range.start) + count;
378 }
379 }
380
381 let new_end = old_range.start + count;
382 if old_range.start < state.rendered_range.start {
383 state.rendered_range.start =
384 new_end + state.rendered_range.start.saturating_sub(old_range.end);
385 }
386 if old_range.start < state.rendered_range.end {
387 state.rendered_range.end =
388 new_end + state.rendered_range.end.saturating_sub(old_range.end);
389 }
390
391 let mut old_heights = state.items.cursor::<Count, ()>();
392 let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
393 old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
394
395 new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
396 new_heights.push_tree(old_heights.suffix(&()), &());
397 drop(old_heights);
398 state.items = new_heights;
399 }
400
401 pub fn set_scroll_handler(
402 &mut self,
403 handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
404 ) {
405 self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
406 }
407}
408
409impl StateInner {
410 fn visible_range(&self, height: f32, scroll_top: &ScrollTop) -> Range<usize> {
411 let mut cursor = self.items.cursor::<Count, Height>();
412 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
413 let start_y = cursor.sum_start().0 + scroll_top.offset_in_item;
414 let mut cursor = cursor.swap_dimensions();
415 cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
416 scroll_top.item_ix..cursor.sum_start().0 + 1
417 }
418
419 fn visible_elements<'a>(
420 &'a self,
421 bounds: RectF,
422 scroll_top: &ScrollTop,
423 ) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
424 let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
425 let mut cursor = self.items.cursor::<Count, ()>();
426 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
427 std::iter::from_fn(move || {
428 while let Some(item) = cursor.item() {
429 if item_origin.y() > bounds.max_y() {
430 break;
431 }
432
433 if let ListItem::Rendered(element) = item {
434 let result = (element.clone(), item_origin);
435 item_origin.set_y(item_origin.y() + element.size().y());
436 cursor.next(&());
437 return Some(result);
438 }
439
440 cursor.next(&());
441 }
442
443 None
444 })
445 }
446
447 fn scroll(
448 &mut self,
449 scroll_top: &ScrollTop,
450 height: f32,
451 mut delta: Vector2F,
452 precise: bool,
453 cx: &mut EventContext,
454 ) -> bool {
455 if !precise {
456 delta *= 20.;
457 }
458
459 let scroll_max = (self.items.summary().height - height).max(0.);
460 let new_scroll_top = (self.scroll_top(height) + delta.y())
461 .max(0.)
462 .min(scroll_max);
463
464 if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
465 self.scroll_top = None;
466 } else {
467 let mut cursor = self.items.cursor::<Height, Count>();
468 cursor.seek(&Height(new_scroll_top), Bias::Right, &());
469 let item_ix = cursor.sum_start().0;
470 let offset_in_item = new_scroll_top - cursor.seek_start().0;
471 self.scroll_top = Some(ScrollTop {
472 item_ix,
473 offset_in_item,
474 });
475 }
476
477 if self.scroll_handler.is_some() {
478 let visible_range = self.visible_range(height, scroll_top);
479 self.scroll_handler.as_mut().unwrap()(visible_range, cx);
480 }
481 cx.notify();
482
483 true
484 }
485
486 fn scroll_top(&self, height: f32) -> f32 {
487 let scroll_max = (self.items.summary().height - height).max(0.);
488 if let Some(ScrollTop {
489 item_ix,
490 offset_in_item,
491 }) = self.scroll_top
492 {
493 let mut cursor = self.items.cursor::<Count, Height>();
494 cursor.seek(&Count(item_ix), Bias::Right, &());
495 (cursor.sum_start().0 + offset_in_item).min(scroll_max)
496 } else {
497 match self.orientation {
498 Orientation::Top => 0.,
499 Orientation::Bottom => scroll_max,
500 }
501 }
502 }
503}
504
505impl ListItem {
506 fn remove(&self) -> Self {
507 match self {
508 ListItem::Unrendered => ListItem::Unrendered,
509 ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
510 ListItem::Removed(height) => ListItem::Removed(*height),
511 }
512 }
513}
514
515impl sum_tree::Item for ListItem {
516 type Summary = ListItemSummary;
517
518 fn summary(&self) -> Self::Summary {
519 match self {
520 ListItem::Unrendered => ListItemSummary {
521 count: 1,
522 rendered_count: 0,
523 unrendered_count: 1,
524 height: 0.,
525 },
526 ListItem::Rendered(element) => ListItemSummary {
527 count: 1,
528 rendered_count: 1,
529 unrendered_count: 0,
530 height: element.size().y(),
531 },
532 ListItem::Removed(height) => ListItemSummary {
533 count: 1,
534 rendered_count: 0,
535 unrendered_count: 1,
536 height: *height,
537 },
538 }
539 }
540}
541
542impl sum_tree::Summary for ListItemSummary {
543 type Context = ();
544
545 fn add_summary(&mut self, summary: &Self, _: &()) {
546 self.count += summary.count;
547 self.rendered_count += summary.rendered_count;
548 self.unrendered_count += summary.unrendered_count;
549 self.height += summary.height;
550 }
551}
552
553impl<'a> sum_tree::Dimension<'a, ListItemSummary> for ListItemSummary {
554 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
555 sum_tree::Summary::add_summary(self, summary, &());
556 }
557}
558
559impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
560 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
561 self.0 += summary.count;
562 }
563}
564
565impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
566 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
567 self.0 += summary.rendered_count;
568 }
569}
570
571impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
572 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
573 self.0 += summary.unrendered_count;
574 }
575}
576
577impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
578 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
579 self.0 += summary.height;
580 }
581}
582
583impl<'a> sum_tree::SeekDimension<'a, ListItemSummary> for Height {
584 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
585 self.0.partial_cmp(&other.0).unwrap()
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use crate::geometry::vector::vec2f;
593 use rand::prelude::*;
594 use std::env;
595
596 #[crate::test(self)]
597 fn test_layout(cx: &mut crate::MutableAppContext) {
598 let mut presenter = cx.build_presenter(0, 0.);
599
600 let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
601 let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
602 let elements = elements.clone();
603 move |ix, _| {
604 let (id, height) = elements.borrow()[ix];
605 TestElement::new(id, height).boxed()
606 }
607 });
608
609 let mut list = List::new(state.clone()).boxed();
610 let size = list.layout(
611 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
612 &mut presenter.build_layout_context(cx),
613 );
614 assert_eq!(size, vec2f(100., 40.));
615 assert_eq!(
616 state.0.borrow().items.summary(),
617 ListItemSummary {
618 count: 3,
619 rendered_count: 3,
620 unrendered_count: 0,
621 height: 150.
622 }
623 );
624
625 state.0.borrow_mut().scroll(
626 &ScrollTop {
627 item_ix: 0,
628 offset_in_item: 0.,
629 },
630 40.,
631 vec2f(0., 54.),
632 true,
633 &mut presenter.build_event_context(cx),
634 );
635 assert_eq!(
636 state.0.borrow().scroll_top,
637 Some(ScrollTop {
638 item_ix: 2,
639 offset_in_item: 4.
640 })
641 );
642 assert_eq!(state.0.borrow().scroll_top(size.y()), 54.);
643
644 elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
645 elements.borrow_mut().push((5, 60.));
646 state.splice(1..2, 2);
647 state.splice(4..4, 1);
648 assert_eq!(
649 state.0.borrow().items.summary(),
650 ListItemSummary {
651 count: 5,
652 rendered_count: 2,
653 unrendered_count: 3,
654 height: 120.
655 }
656 );
657
658 let mut list = List::new(state.clone()).boxed();
659 let size = list.layout(
660 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
661 &mut presenter.build_layout_context(cx),
662 );
663 assert_eq!(size, vec2f(100., 40.));
664 assert_eq!(
665 state.0.borrow().items.summary(),
666 ListItemSummary {
667 count: 5,
668 rendered_count: 5,
669 unrendered_count: 0,
670 height: 270.
671 }
672 );
673 assert_eq!(
674 state.0.borrow().scroll_top,
675 Some(ScrollTop {
676 item_ix: 3,
677 offset_in_item: 4.
678 })
679 );
680 assert_eq!(state.0.borrow().scroll_top(size.y()), 114.);
681 }
682
683 #[crate::test(self, iterations = 10000)]
684 fn test_random(cx: &mut crate::MutableAppContext, mut rng: StdRng) {
685 let operations = env::var("OPERATIONS")
686 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
687 .unwrap_or(10);
688
689 let mut presenter = cx.build_presenter(0, 0.);
690 let mut next_id = 0;
691 let elements = Rc::new(RefCell::new(
692 (0..rng.gen_range(0..=20))
693 .map(|_| {
694 let id = next_id;
695 next_id += 1;
696 (id, rng.gen_range(0_f32..=100_f32))
697 })
698 .collect::<Vec<_>>(),
699 ));
700 let orientation = *[Orientation::Top, Orientation::Bottom]
701 .choose(&mut rng)
702 .unwrap();
703 let overdraw = rng.gen_range(1_f32..=100_f32);
704 let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
705 let elements = elements.clone();
706 move |ix, _| {
707 let (id, height) = elements.borrow()[ix];
708 TestElement::new(id, height).boxed()
709 }
710 });
711
712 let mut width = rng.gen_range(0_f32..=1000_f32);
713 let mut height = rng.gen_range(0_f32..=1000_f32);
714 log::info!("orientation: {:?}", orientation);
715 log::info!("overdraw: {}", overdraw);
716 log::info!("elements: {:?}", elements.borrow());
717 log::info!("size: ({:?}, {:?})", width, height);
718 log::info!("==================");
719
720 let mut scroll_top = None;
721 for _ in 0..operations {
722 match rng.gen_range(0..=100) {
723 0..=29 if scroll_top.is_some() => {
724 let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
725 log::info!(
726 "Scrolling by {:?}, previous scroll top: {:?}",
727 delta,
728 scroll_top.unwrap()
729 );
730 state.0.borrow_mut().scroll(
731 scroll_top.as_ref().unwrap(),
732 height,
733 delta,
734 true,
735 &mut presenter.build_event_context(cx),
736 );
737 }
738 30..=34 => {
739 width = rng.gen_range(0_f32..=1000_f32);
740 log::info!("changing width: {:?}", width);
741 }
742 35..=54 => {
743 height = rng.gen_range(0_f32..=1000_f32);
744 log::info!("changing height: {:?}", height);
745 }
746 _ => {
747 let mut elements = elements.borrow_mut();
748 let end_ix = rng.gen_range(0..=elements.len());
749 let start_ix = rng.gen_range(0..=end_ix);
750 let new_elements = (0..rng.gen_range(0..10))
751 .map(|_| {
752 let id = next_id;
753 next_id += 1;
754 (id, rng.gen_range(0_f32..=100_f32))
755 })
756 .collect::<Vec<_>>();
757 log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
758 state.splice(start_ix..end_ix, new_elements.len());
759 elements.splice(start_ix..end_ix, new_elements);
760 for (ix, item) in state.0.borrow().items.cursor::<(), ()>().enumerate() {
761 if let ListItem::Rendered(element) = item {
762 let (expected_id, _) = elements[ix];
763 element.with_metadata(|metadata: Option<&usize>| {
764 assert_eq!(*metadata.unwrap(), expected_id);
765 });
766 }
767 }
768 }
769 }
770
771 let mut list = List::new(state.clone());
772 let (size, new_scroll_top) = list.layout(
773 SizeConstraint::new(vec2f(0., 0.), vec2f(width, height)),
774 &mut presenter.build_layout_context(cx),
775 );
776 assert_eq!(size, vec2f(width, height));
777 scroll_top = Some(new_scroll_top);
778
779 let state = state.0.borrow();
780 let rendered_top = (state.scroll_top(height) - overdraw).max(0.);
781 let rendered_bottom = state.scroll_top(height) + height + overdraw;
782 let mut item_top = 0.;
783 log::info!(
784 "rendered top {:?}, rendered bottom {:?}",
785 rendered_top,
786 rendered_bottom
787 );
788 log::info!("items {:?}", state.items.items(&()));
789 assert_eq!(state.items.summary().count, elements.borrow().len());
790 for (ix, item) in state.items.cursor::<(), ()>().enumerate() {
791 match item {
792 ListItem::Unrendered => {
793 let item_bottom = item_top;
794 assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
795 item_top = item_bottom;
796 }
797 ListItem::Removed(height) => {
798 let (id, expected_height) = elements.borrow()[ix];
799 assert_eq!(
800 *height, expected_height,
801 "element {} height didn't match",
802 id
803 );
804 let item_bottom = item_top + height;
805 assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
806 item_top = item_bottom;
807 }
808 ListItem::Rendered(element) => {
809 let (expected_id, expected_height) = elements.borrow()[ix];
810 element.with_metadata(|metadata: Option<&usize>| {
811 assert_eq!(*metadata.unwrap(), expected_id);
812 });
813 assert_eq!(element.size().y(), expected_height);
814 let item_bottom = item_top + element.size().y();
815 assert!(item_bottom > rendered_top || item_top < rendered_bottom);
816 item_top = item_bottom;
817 }
818 }
819 }
820 }
821 }
822
823 struct TestElement {
824 id: usize,
825 size: Vector2F,
826 }
827
828 impl TestElement {
829 fn new(id: usize, height: f32) -> Self {
830 Self {
831 id,
832 size: vec2f(100., height),
833 }
834 }
835 }
836
837 impl Element for TestElement {
838 type LayoutState = ();
839 type PaintState = ();
840
841 fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
842 (self.size, ())
843 }
844
845 fn paint(&mut self, _: RectF, _: &mut (), _: &mut PaintContext) {
846 todo!()
847 }
848
849 fn dispatch_event(
850 &mut self,
851 _: &Event,
852 _: RectF,
853 _: &mut (),
854 _: &mut (),
855 _: &mut EventContext,
856 ) -> bool {
857 todo!()
858 }
859
860 fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
861 self.id.into()
862 }
863
864 fn metadata(&self) -> Option<&dyn std::any::Any> {
865 Some(&self.id)
866 }
867 }
868}