uniform_list.rs

  1use super::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
  2use crate::{
  3    geometry::{
  4        rect::RectF,
  5        vector::{vec2f, Vector2F},
  6    },
  7    json::{self, json},
  8    ElementBox,
  9};
 10use json::ToJson;
 11use parking_lot::Mutex;
 12use std::{cmp, ops::Range, sync::Arc};
 13
 14#[derive(Clone, Default)]
 15pub struct UniformListState(Arc<Mutex<StateInner>>);
 16
 17#[derive(Debug)]
 18pub enum ScrollTarget {
 19    Show(usize),
 20    Center(usize),
 21}
 22
 23impl UniformListState {
 24    pub fn scroll_to(&self, scroll_to: ScrollTarget) {
 25        self.0.lock().scroll_to = Some(scroll_to);
 26    }
 27
 28    pub fn scroll_top(&self) -> f32 {
 29        self.0.lock().scroll_top
 30    }
 31}
 32
 33#[derive(Default)]
 34struct StateInner {
 35    scroll_top: f32,
 36    scroll_to: Option<ScrollTarget>,
 37}
 38
 39pub struct LayoutState {
 40    scroll_max: f32,
 41    item_height: f32,
 42    items: Vec<ElementBox>,
 43}
 44
 45pub struct UniformList<F>
 46where
 47    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
 48{
 49    state: UniformListState,
 50    item_count: usize,
 51    append_items: F,
 52    padding_top: f32,
 53    padding_bottom: f32,
 54    get_width_from_item: Option<usize>,
 55}
 56
 57impl<F> UniformList<F>
 58where
 59    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
 60{
 61    pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
 62        Self {
 63            state,
 64            item_count,
 65            append_items,
 66            padding_top: 0.,
 67            padding_bottom: 0.,
 68            get_width_from_item: None,
 69        }
 70    }
 71
 72    pub fn with_width_from_item(mut self, item_ix: Option<usize>) -> Self {
 73        self.get_width_from_item = item_ix;
 74        self
 75    }
 76
 77    pub fn with_padding_top(mut self, padding: f32) -> Self {
 78        self.padding_top = padding;
 79        self
 80    }
 81
 82    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
 83        self.padding_bottom = padding;
 84        self
 85    }
 86
 87    fn scroll(
 88        &self,
 89        _: Vector2F,
 90        mut delta: Vector2F,
 91        precise: bool,
 92        scroll_max: f32,
 93        cx: &mut EventContext,
 94    ) -> bool {
 95        if !precise {
 96            delta *= 20.;
 97        }
 98
 99        let mut state = self.state.0.lock();
100        state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
101        cx.notify();
102
103        true
104    }
105
106    fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
107        let mut state = self.state.0.lock();
108
109        if let Some(scroll_to) = state.scroll_to.take() {
110            let item_ix;
111            let center;
112            match scroll_to {
113                ScrollTarget::Show(ix) => {
114                    item_ix = ix;
115                    center = false;
116                }
117                ScrollTarget::Center(ix) => {
118                    item_ix = ix;
119                    center = true;
120                }
121            }
122
123            let item_top = self.padding_top + item_ix as f32 * item_height;
124            let item_bottom = item_top + item_height;
125            if center {
126                let item_center = item_top + item_height / 2.;
127                state.scroll_top = (item_center - list_height / 2.).max(0.);
128            } else {
129                let scroll_bottom = state.scroll_top + list_height;
130                if item_top < state.scroll_top {
131                    state.scroll_top = item_top;
132                } else if item_bottom > scroll_bottom {
133                    state.scroll_top = item_bottom - list_height;
134                }
135            }
136        }
137
138        if state.scroll_top > scroll_max {
139            state.scroll_top = scroll_max;
140        }
141    }
142
143    fn scroll_top(&self) -> f32 {
144        self.state.0.lock().scroll_top
145    }
146}
147
148impl<F> Element for UniformList<F>
149where
150    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
151{
152    type LayoutState = LayoutState;
153    type PaintState = ();
154
155    fn layout(
156        &mut self,
157        constraint: SizeConstraint,
158        cx: &mut LayoutContext,
159    ) -> (Vector2F, Self::LayoutState) {
160        if constraint.max.y().is_infinite() {
161            unimplemented!(
162                "UniformList does not support being rendered with an unconstrained height"
163            );
164        }
165
166        if self.item_count == 0 {
167            return (
168                constraint.min,
169                LayoutState {
170                    item_height: 0.,
171                    scroll_max: 0.,
172                    items: Default::default(),
173                },
174            );
175        }
176
177        let mut items = Vec::new();
178        let mut size = constraint.max;
179        let mut item_size;
180        let sample_item_ix;
181        let mut sample_item;
182        if let Some(sample_ix) = self.get_width_from_item {
183            (self.append_items)(sample_ix..sample_ix + 1, &mut items, cx);
184            sample_item_ix = sample_ix;
185            sample_item = items.pop().unwrap();
186            item_size = sample_item.layout(constraint, cx);
187            size.set_x(item_size.x());
188        } else {
189            (self.append_items)(0..1, &mut items, cx);
190            sample_item_ix = 0;
191            sample_item = items.pop().unwrap();
192            item_size = sample_item.layout(
193                SizeConstraint::new(
194                    vec2f(constraint.max.x(), 0.0),
195                    vec2f(constraint.max.x(), f32::INFINITY),
196                ),
197                cx,
198            );
199            item_size.set_x(size.x());
200        }
201
202        let item_constraint = SizeConstraint {
203            min: item_size,
204            max: vec2f(constraint.max.x(), item_size.y()),
205        };
206        let item_height = item_size.y();
207
208        let scroll_height = self.item_count as f32 * item_height;
209        if scroll_height < size.y() {
210            size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
211        }
212
213        let scroll_height =
214            item_height * self.item_count as f32 + self.padding_top + self.padding_bottom;
215        let scroll_max = (scroll_height - size.y()).max(0.);
216        self.autoscroll(scroll_max, size.y(), item_height);
217
218        let start = cmp::min(
219            ((self.scroll_top() - self.padding_top) / item_height) as usize,
220            self.item_count,
221        );
222        let end = cmp::min(
223            self.item_count,
224            start + (size.y() / item_height).ceil() as usize + 1,
225        );
226
227        if (start..end).contains(&sample_item_ix) {
228            if sample_item_ix > start {
229                (self.append_items)(start..sample_item_ix, &mut items, cx);
230            }
231
232            items.push(sample_item);
233
234            if sample_item_ix < end {
235                (self.append_items)(sample_item_ix + 1..end, &mut items, cx);
236            }
237        } else {
238            (self.append_items)(start..end, &mut items, cx);
239        }
240
241        for item in &mut items {
242            let item_size = item.layout(item_constraint, cx);
243            if item_size.x() > size.x() {
244                size.set_x(item_size.x());
245            }
246        }
247
248        (
249            size,
250            LayoutState {
251                item_height,
252                scroll_max,
253                items,
254            },
255        )
256    }
257
258    fn paint(
259        &mut self,
260        bounds: RectF,
261        visible_bounds: RectF,
262        layout: &mut Self::LayoutState,
263        cx: &mut PaintContext,
264    ) -> Self::PaintState {
265        cx.scene.push_layer(Some(bounds));
266
267        let mut item_origin = bounds.origin()
268            - vec2f(
269                0.,
270                (self.state.scroll_top() - self.padding_top) % layout.item_height,
271            );
272
273        for item in &mut layout.items {
274            item.paint(item_origin, visible_bounds, cx);
275            item_origin += vec2f(0.0, layout.item_height);
276        }
277
278        cx.scene.pop_layer();
279    }
280
281    fn dispatch_event(
282        &mut self,
283        event: &Event,
284        bounds: RectF,
285        layout: &mut Self::LayoutState,
286        _: &mut Self::PaintState,
287        cx: &mut EventContext,
288    ) -> bool {
289        let mut handled = false;
290        for item in &mut layout.items {
291            handled = item.dispatch_event(event, cx) || handled;
292        }
293
294        match event {
295            Event::ScrollWheel {
296                position,
297                delta,
298                precise,
299            } => {
300                if bounds.contains_point(*position) {
301                    if self.scroll(*position, *delta, *precise, layout.scroll_max, cx) {
302                        handled = true;
303                    }
304                }
305            }
306            _ => {}
307        }
308
309        handled
310    }
311
312    fn debug(
313        &self,
314        bounds: RectF,
315        layout: &Self::LayoutState,
316        _: &Self::PaintState,
317        cx: &crate::DebugContext,
318    ) -> json::Value {
319        json!({
320            "type": "UniformList",
321            "bounds": bounds.to_json(),
322            "scroll_max": layout.scroll_max,
323            "item_height": layout.item_height,
324            "items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
325
326        })
327    }
328}