uniform_list.rs

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