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