uniform_list.rs

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