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