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;
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}