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