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