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