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}
 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        }
 68    }
 69
 70    pub fn with_padding_top(mut self, padding: f32) -> Self {
 71        self.padding_top = padding;
 72        self
 73    }
 74
 75    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
 76        self.padding_bottom = padding;
 77        self
 78    }
 79
 80    fn scroll(
 81        &self,
 82        _: Vector2F,
 83        mut delta: Vector2F,
 84        precise: bool,
 85        scroll_max: f32,
 86        cx: &mut EventContext,
 87    ) -> bool {
 88        if !precise {
 89            delta *= 20.;
 90        }
 91
 92        let mut state = self.state.0.lock();
 93        state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
 94        cx.notify();
 95
 96        true
 97    }
 98
 99    fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
100        let mut state = self.state.0.lock();
101
102        if let Some(scroll_to) = state.scroll_to.take() {
103            let item_ix;
104            let center;
105            match scroll_to {
106                ScrollTarget::Show(ix) => {
107                    item_ix = ix;
108                    center = false;
109                }
110                ScrollTarget::Center(ix) => {
111                    item_ix = ix;
112                    center = true;
113                }
114            }
115
116            let item_top = self.padding_top + item_ix as f32 * item_height;
117            let item_bottom = item_top + item_height;
118            if center {
119                let item_center = item_top + item_height / 2.;
120                state.scroll_top = (item_center - list_height / 2.).max(0.);
121            } else {
122                let scroll_bottom = state.scroll_top + list_height;
123                if item_top < state.scroll_top {
124                    state.scroll_top = item_top;
125                } else if item_bottom > scroll_bottom {
126                    state.scroll_top = item_bottom - list_height;
127                }
128            }
129        }
130
131        if state.scroll_top > scroll_max {
132            state.scroll_top = scroll_max;
133        }
134    }
135
136    fn scroll_top(&self) -> f32 {
137        self.state.0.lock().scroll_top
138    }
139}
140
141impl<F> Element for UniformList<F>
142where
143    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
144{
145    type LayoutState = LayoutState;
146    type PaintState = ();
147
148    fn layout(
149        &mut self,
150        constraint: SizeConstraint,
151        cx: &mut LayoutContext,
152    ) -> (Vector2F, Self::LayoutState) {
153        if constraint.max.y().is_infinite() {
154            unimplemented!(
155                "UniformList does not support being rendered with an unconstrained height"
156            );
157        }
158        let mut size = constraint.max;
159        let mut item_constraint =
160            SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
161        let mut item_height = 0.;
162        let mut scroll_max = 0.;
163
164        let mut items = Vec::new();
165        (self.append_items)(0..1, &mut items, cx);
166        if let Some(first_item) = items.first_mut() {
167            let mut item_size = first_item.layout(item_constraint, cx);
168            item_size.set_x(size.x());
169            item_constraint.min = item_size;
170            item_constraint.max = item_size;
171            item_height = item_size.y();
172
173            let scroll_height = self.item_count as f32 * item_height;
174            if scroll_height < size.y() {
175                size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
176            }
177
178            let scroll_height =
179                item_height * self.item_count as f32 + self.padding_top + self.padding_bottom;
180            scroll_max = (scroll_height - size.y()).max(0.);
181            self.autoscroll(scroll_max, size.y(), item_height);
182
183            items.clear();
184            let start = cmp::min(
185                ((self.scroll_top() - self.padding_top) / item_height) as usize,
186                self.item_count,
187            );
188            let end = cmp::min(
189                self.item_count,
190                start + (size.y() / item_height).ceil() as usize + 1,
191            );
192            (self.append_items)(start..end, &mut items, cx);
193            for item in &mut items {
194                item.layout(item_constraint, cx);
195            }
196        } else {
197            size = constraint.min;
198        }
199
200        (
201            size,
202            LayoutState {
203                item_height,
204                scroll_max,
205                items,
206            },
207        )
208    }
209
210    fn paint(
211        &mut self,
212        bounds: RectF,
213        visible_bounds: RectF,
214        layout: &mut Self::LayoutState,
215        cx: &mut PaintContext,
216    ) -> Self::PaintState {
217        cx.scene.push_layer(Some(bounds));
218
219        let mut item_origin = bounds.origin()
220            - vec2f(
221                0.,
222                (self.state.scroll_top() - self.padding_top) % layout.item_height,
223            );
224
225        for item in &mut layout.items {
226            item.paint(item_origin, visible_bounds, cx);
227            item_origin += vec2f(0.0, layout.item_height);
228        }
229
230        cx.scene.pop_layer();
231    }
232
233    fn dispatch_event(
234        &mut self,
235        event: &Event,
236        bounds: RectF,
237        layout: &mut Self::LayoutState,
238        _: &mut Self::PaintState,
239        cx: &mut EventContext,
240    ) -> bool {
241        let mut handled = false;
242        for item in &mut layout.items {
243            handled = item.dispatch_event(event, cx) || handled;
244        }
245
246        match event {
247            Event::ScrollWheel {
248                position,
249                delta,
250                precise,
251            } => {
252                if bounds.contains_point(*position) {
253                    if self.scroll(*position, *delta, *precise, layout.scroll_max, cx) {
254                        handled = true;
255                    }
256                }
257            }
258            _ => {}
259        }
260
261        handled
262    }
263
264    fn debug(
265        &self,
266        bounds: RectF,
267        layout: &Self::LayoutState,
268        _: &Self::PaintState,
269        cx: &crate::DebugContext,
270    ) -> json::Value {
271        json!({
272            "type": "UniformList",
273            "bounds": bounds.to_json(),
274            "scroll_max": layout.scroll_max,
275            "item_height": layout.item_height,
276            "items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
277
278        })
279    }
280}