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