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}