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