list.rs

  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}