uniform_list.rs

  1use super::{
  2    try_rect, AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext,
  3    MutableAppContext, PaintContext, SizeConstraint,
  4};
  5use crate::geometry::{
  6    rect::RectF,
  7    vector::{vec2f, Vector2F},
  8};
  9use parking_lot::Mutex;
 10use std::{cmp, ops::Range, sync::Arc};
 11
 12#[derive(Clone)]
 13pub struct UniformListState(Arc<Mutex<StateInner>>);
 14
 15struct StateInner {
 16    scroll_top: f32,
 17    scroll_to: Option<usize>,
 18}
 19
 20impl UniformListState {
 21    pub fn new() -> Self {
 22        Self(Arc::new(Mutex::new(StateInner {
 23            scroll_top: 0.0,
 24            scroll_to: None,
 25        })))
 26    }
 27
 28    pub fn scroll_to(&self, item_ix: usize) {
 29        self.0.lock().scroll_to = Some(item_ix);
 30    }
 31}
 32
 33pub struct UniformList<F, G>
 34where
 35    F: Fn(Range<usize>, &AppContext) -> G,
 36    G: Iterator<Item = Box<dyn Element>>,
 37{
 38    state: UniformListState,
 39    item_count: usize,
 40    build_items: F,
 41    scroll_max: Option<f32>,
 42    items: Vec<Box<dyn Element>>,
 43    origin: Option<Vector2F>,
 44    size: Option<Vector2F>,
 45}
 46
 47impl<F, G> UniformList<F, G>
 48where
 49    F: Fn(Range<usize>, &AppContext) -> G,
 50    G: Iterator<Item = Box<dyn Element>>,
 51{
 52    pub fn new(state: UniformListState, item_count: usize, build_items: F) -> Self {
 53        Self {
 54            state,
 55            item_count,
 56            build_items,
 57            scroll_max: None,
 58            items: Default::default(),
 59            origin: None,
 60            size: None,
 61        }
 62    }
 63
 64    fn scroll(
 65        &self,
 66        position: Vector2F,
 67        delta: Vector2F,
 68        precise: bool,
 69        ctx: &mut EventContext,
 70        _: &AppContext,
 71    ) -> bool {
 72        if !self.rect().unwrap().contains_point(position) {
 73            return false;
 74        }
 75
 76        if !precise {
 77            todo!("still need to handle non-precise scroll events from a mouse wheel");
 78        }
 79
 80        let mut state = self.state.0.lock();
 81        state.scroll_top = (state.scroll_top - delta.y())
 82            .max(0.0)
 83            .min(self.scroll_max.unwrap());
 84        ctx.dispatch_action("uniform_list:scroll", state.scroll_top);
 85
 86        true
 87    }
 88
 89    fn autoscroll(&mut self, list_height: f32, item_height: f32) {
 90        let mut state = self.state.0.lock();
 91
 92        let scroll_max = self.item_count as f32 * item_height - list_height;
 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        try_rect(self.origin, self.size)
115    }
116}
117
118impl<F, G> Element for UniformList<F, G>
119where
120    F: Fn(Range<usize>, &AppContext) -> G,
121    G: Iterator<Item = Box<dyn Element>>,
122{
123    fn layout(
124        &mut self,
125        constraint: SizeConstraint,
126        ctx: &mut LayoutContext,
127        app: &AppContext,
128    ) -> Vector2F {
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
138        let first_item = (self.build_items)(0..1, app).next();
139        if let Some(mut first_item) = first_item {
140            let mut item_size = first_item.layout(item_constraint, ctx, app);
141            item_size.set_x(size.x());
142            item_constraint.min = item_size;
143            item_constraint.max = item_size;
144
145            let scroll_height = self.item_count as f32 * item_size.y();
146            if scroll_height < size.y() {
147                size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
148            }
149
150            self.autoscroll(size.y(), item_size.y());
151
152            let start = cmp::min(
153                (self.scroll_top() / item_size.y()) as usize,
154                self.item_count,
155            );
156            let end = cmp::min(
157                self.item_count,
158                start + (size.y() / item_size.y()).ceil() as usize + 1,
159            );
160            self.items.clear();
161            self.items.extend((self.build_items)(start..end, app));
162
163            self.scroll_max = Some(item_size.y() * self.item_count as f32 - size.y());
164
165            for item in &mut self.items {
166                item.layout(item_constraint, ctx, app);
167            }
168        }
169
170        self.size = Some(size);
171        size
172    }
173
174    fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
175        for item in &mut self.items {
176            item.after_layout(ctx, app);
177        }
178    }
179
180    fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
181        // self.origin = Some(origin);
182
183        // if let Some(item) = self.items.first() {
184        //     ctx.canvas.save();
185        //     let mut clip_path = Path2D::new();
186        //     clip_path.rect(RectF::new(origin, self.size.unwrap()));
187        //     ctx.canvas.clip_path(clip_path, FillRule::Winding);
188
189        //     let item_height = item.size().unwrap().y();
190        //     let mut item_origin = origin - vec2f(0.0, self.state.0.lock().scroll_top % item_height);
191        //     for item in &mut self.items {
192        //         item.paint(item_origin, ctx, app);
193        //         item_origin += vec2f(0.0, item_height);
194        //     }
195        //     ctx.canvas.restore();
196        // }
197    }
198
199    fn size(&self) -> Option<Vector2F> {
200        self.size
201    }
202
203    fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
204        let mut handled = false;
205        for item in &self.items {
206            if item.dispatch_event(event, ctx, app) {
207                handled = true;
208            }
209        }
210
211        match event {
212            Event::ScrollWheel {
213                position,
214                delta,
215                precise,
216            } => {
217                if self.scroll(*position, *delta, *precise, ctx, app) {
218                    handled = true;
219                }
220            }
221            _ => {}
222        }
223
224        handled
225    }
226}