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