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