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