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}