list.rs

  1use crate::{
  2    geometry::{rect::RectF, vector::Vector2F},
  3    sum_tree::{self, Bias, SumTree},
  4    Element,
  5};
  6use parking_lot::Mutex;
  7use std::{ops::Range, sync::Arc};
  8
  9use crate::ElementBox;
 10
 11pub struct List {
 12    state: ListState,
 13}
 14
 15#[derive(Clone)]
 16pub struct ListState(Arc<Mutex<StateInner>>);
 17
 18struct StateInner {
 19    last_layout_width: f32,
 20    elements: Vec<ElementBox>,
 21    heights: SumTree<ElementHeight>,
 22}
 23
 24#[derive(Clone, Debug)]
 25enum ElementHeight {
 26    Pending,
 27    Ready(f32),
 28}
 29
 30#[derive(Clone, Debug, Default, PartialEq)]
 31struct ElementHeightSummary {
 32    count: usize,
 33    pending_count: usize,
 34    height: f32,
 35}
 36
 37#[derive(Clone, Debug, Default)]
 38struct Count(usize);
 39
 40#[derive(Clone, Debug, Default)]
 41struct PendingCount(usize);
 42
 43#[derive(Clone, Debug, Default)]
 44struct Height(f32);
 45
 46impl List {
 47    pub fn new(state: ListState) -> Self {
 48        Self { state }
 49    }
 50}
 51
 52impl Element for List {
 53    type LayoutState = ();
 54
 55    type PaintState = ();
 56
 57    fn layout(
 58        &mut self,
 59        constraint: crate::SizeConstraint,
 60        cx: &mut crate::LayoutContext,
 61    ) -> (Vector2F, Self::LayoutState) {
 62        // TODO: Fully invalidate if width has changed since the last layout.
 63
 64        let state = &mut *self.state.0.lock();
 65        let mut old_heights = state.heights.cursor::<PendingCount, ElementHeightSummary>();
 66        let mut new_heights = old_heights.slice(&PendingCount(1), sum_tree::Bias::Left, &());
 67
 68        let mut item_constraint = constraint;
 69        item_constraint.min.set_y(0.);
 70        item_constraint.max.set_y(f32::INFINITY);
 71
 72        while let Some(height) = old_heights.item() {
 73            if height.is_pending() {
 74                let size =
 75                    state.elements[old_heights.sum_start().count].layout(item_constraint, cx);
 76                new_heights.push(ElementHeight::Ready(size.y()), &());
 77                old_heights.next(&());
 78            } else {
 79                new_heights.push_tree(
 80                    old_heights.slice(
 81                        &PendingCount(old_heights.sum_start().pending_count + 1),
 82                        Bias::Left,
 83                        &(),
 84                    ),
 85                    &(),
 86                );
 87            }
 88        }
 89
 90        drop(old_heights);
 91        state.heights = new_heights;
 92        (constraint.max, ())
 93    }
 94
 95    fn paint(
 96        &mut self,
 97        bounds: RectF,
 98        layout: &mut Self::LayoutState,
 99        cx: &mut crate::PaintContext,
100    ) -> Self::PaintState {
101        todo!()
102    }
103
104    fn dispatch_event(
105        &mut self,
106        event: &crate::Event,
107        bounds: RectF,
108        layout: &mut Self::LayoutState,
109        paint: &mut Self::PaintState,
110        cx: &mut crate::EventContext,
111    ) -> bool {
112        todo!()
113    }
114
115    fn debug(
116        &self,
117        bounds: RectF,
118        layout: &Self::LayoutState,
119        paint: &Self::PaintState,
120        cx: &crate::DebugContext,
121    ) -> serde_json::Value {
122        todo!()
123    }
124}
125
126impl ListState {
127    pub fn new(elements: Vec<ElementBox>) -> Self {
128        let mut heights = SumTree::new();
129        heights.extend(elements.iter().map(|_| ElementHeight::Pending), &());
130        Self(Arc::new(Mutex::new(StateInner {
131            last_layout_width: 0.,
132            elements,
133            heights,
134        })))
135    }
136
137    pub fn splice(
138        &self,
139        old_range: Range<usize>,
140        new_elements: impl IntoIterator<Item = ElementBox>,
141    ) {
142        let state = &mut *self.0.lock();
143
144        let mut old_heights = state.heights.cursor::<Count, ()>();
145        let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
146        old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
147
148        let mut len = 0;
149        let old_elements = state.elements.splice(
150            old_range,
151            new_elements.into_iter().map(|e| {
152                len += 1;
153                e
154            }),
155        );
156        drop(old_elements);
157
158        new_heights.extend((0..len).map(|_| ElementHeight::Pending), &());
159        new_heights.push_tree(old_heights.suffix(&()), &());
160        drop(old_heights);
161        state.heights = new_heights;
162    }
163}
164
165impl ElementHeight {
166    fn is_pending(&self) -> bool {
167        matches!(self, ElementHeight::Pending)
168    }
169}
170
171impl sum_tree::Item for ElementHeight {
172    type Summary = ElementHeightSummary;
173
174    fn summary(&self) -> Self::Summary {
175        match self {
176            ElementHeight::Pending => ElementHeightSummary {
177                count: 1,
178                pending_count: 1,
179                height: 0.,
180            },
181            ElementHeight::Ready(height) => ElementHeightSummary {
182                count: 1,
183                pending_count: 0,
184                height: *height,
185            },
186        }
187    }
188}
189
190impl sum_tree::Summary for ElementHeightSummary {
191    type Context = ();
192
193    fn add_summary(&mut self, summary: &Self, _: &()) {
194        self.count += summary.count;
195        self.pending_count += summary.pending_count;
196        self.height += summary.height;
197    }
198}
199
200impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for ElementHeightSummary {
201    fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
202        sum_tree::Summary::add_summary(self, summary, &());
203    }
204}
205
206impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Count {
207    fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
208        self.0 += summary.count;
209    }
210}
211
212impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Count {
213    fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
214        self.0.cmp(&other.0)
215    }
216}
217
218impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for PendingCount {
219    fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
220        self.0 += summary.pending_count;
221    }
222}
223
224impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for PendingCount {
225    fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
226        self.0.cmp(&other.0)
227    }
228}
229
230impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height {
231    fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
232        self.0 += summary.height;
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::{elements::*, geometry::vector::vec2f};
240
241    #[crate::test(self)]
242    fn test_layout(cx: &mut crate::MutableAppContext) {
243        let mut presenter = cx.build_presenter(0, 20.0);
244        let mut layout_cx = presenter.layout_cx(cx);
245        let state = ListState::new(vec![item(20.), item(30.), item(10.)]);
246        let mut list = List::new(state.clone()).boxed();
247
248        let size = list.layout(
249            SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
250            &mut layout_cx,
251        );
252        assert_eq!(size, vec2f(100., 40.));
253        assert_eq!(
254            state.0.lock().heights.summary(),
255            ElementHeightSummary {
256                count: 3,
257                pending_count: 0,
258                height: 60.
259            }
260        );
261
262        state.splice(1..2, vec![item(40.), item(50.)]);
263        state.splice(3..3, vec![item(60.)]);
264        assert_eq!(
265            state.0.lock().heights.summary(),
266            ElementHeightSummary {
267                count: 5,
268                pending_count: 3,
269                height: 30.
270            }
271        );
272        let size = list.layout(
273            SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
274            &mut layout_cx,
275        );
276        assert_eq!(size, vec2f(100., 40.));
277        assert_eq!(
278            state.0.lock().heights.summary(),
279            ElementHeightSummary {
280                count: 5,
281                pending_count: 0,
282                height: 180.
283            }
284        );
285    }
286
287    fn item(height: f32) -> ElementBox {
288        ConstrainedBox::new(Empty::new().boxed())
289            .with_height(height)
290            .with_width(100.)
291            .boxed()
292    }
293}