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 _: Vector2F,
72 delta: Vector2F,
73 precise: bool,
74 scroll_max: f32,
75 ctx: &mut EventContext,
76 ) -> bool {
77 if !precise {
78 todo!("still need to handle non-precise scroll events from a mouse wheel");
79 }
80
81 let mut state = self.state.0.lock();
82 state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
83 ctx.dispatch_action("uniform_list:scroll", state.scroll_top);
84
85 true
86 }
87
88 fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
89 let mut state = self.state.0.lock();
90
91 if state.scroll_top > scroll_max {
92 state.scroll_top = scroll_max;
93 }
94
95 if let Some(item_ix) = state.scroll_to.take() {
96 let item_top = item_ix as f32 * item_height;
97 let item_bottom = item_top + item_height;
98
99 if item_top < state.scroll_top {
100 state.scroll_top = item_top;
101 } else if item_bottom > (state.scroll_top + list_height) {
102 state.scroll_top = item_bottom - list_height;
103 }
104 }
105 }
106
107 fn scroll_top(&self) -> f32 {
108 self.state.0.lock().scroll_top
109 }
110}
111
112impl<F> Element for UniformList<F>
113where
114 F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
115{
116 type LayoutState = LayoutState;
117 type PaintState = ();
118
119 fn layout(
120 &mut self,
121 constraint: SizeConstraint,
122 ctx: &mut LayoutContext,
123 ) -> (Vector2F, Self::LayoutState) {
124 if constraint.max.y().is_infinite() {
125 unimplemented!(
126 "UniformList does not support being rendered with an unconstrained height"
127 );
128 }
129 let mut size = constraint.max;
130 let mut item_constraint =
131 SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
132 let mut item_height = 0.;
133 let mut scroll_max = 0.;
134
135 let mut items = Vec::new();
136 (self.append_items)(0..1, &mut items, ctx.app);
137 if let Some(first_item) = items.first_mut() {
138 let mut item_size = first_item.layout(item_constraint, ctx);
139 item_size.set_x(size.x());
140 item_constraint.min = item_size;
141 item_constraint.max = item_size;
142 item_height = item_size.y();
143
144 let scroll_height = self.item_count as f32 * item_height;
145 if scroll_height < size.y() {
146 size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
147 }
148
149 scroll_max = item_height * self.item_count as f32 - size.y();
150 self.autoscroll(scroll_max, size.y(), item_height);
151
152 items.clear();
153 let start = cmp::min((self.scroll_top() / item_height) as usize, self.item_count);
154 let end = cmp::min(
155 self.item_count,
156 start + (size.y() / item_height).ceil() as usize + 1,
157 );
158 (self.append_items)(start..end, &mut items, ctx.app);
159 for item in &mut items {
160 item.layout(item_constraint, ctx);
161 }
162 }
163
164 (
165 size,
166 LayoutState {
167 item_height,
168 scroll_max,
169 items,
170 },
171 )
172 }
173
174 fn after_layout(
175 &mut self,
176 _: Vector2F,
177 layout: &mut Self::LayoutState,
178 ctx: &mut AfterLayoutContext,
179 ) {
180 for item in &mut layout.items {
181 item.after_layout(ctx);
182 }
183 }
184
185 fn paint(
186 &mut self,
187 bounds: RectF,
188 layout: &mut Self::LayoutState,
189 ctx: &mut PaintContext,
190 ) -> Self::PaintState {
191 ctx.scene.push_layer(Some(bounds));
192
193 let mut item_origin =
194 bounds.origin() - vec2f(0.0, self.state.scroll_top() % layout.item_height);
195
196 for item in &mut layout.items {
197 item.paint(item_origin, ctx);
198 item_origin += vec2f(0.0, layout.item_height);
199 }
200
201 ctx.scene.pop_layer();
202 }
203
204 fn dispatch_event(
205 &mut self,
206 event: &Event,
207 bounds: RectF,
208 layout: &mut Self::LayoutState,
209 _: &mut Self::PaintState,
210 ctx: &mut EventContext,
211 ) -> bool {
212 let mut handled = false;
213 for item in &mut layout.items {
214 handled = item.dispatch_event(event, ctx) || handled;
215 }
216
217 match event {
218 Event::ScrollWheel {
219 position,
220 delta,
221 precise,
222 } => {
223 if bounds.contains_point(*position) {
224 if self.scroll(*position, *delta, *precise, layout.scroll_max, ctx) {
225 handled = true;
226 }
227 }
228 }
229 _ => {}
230 }
231
232 handled
233 }
234
235 fn debug(
236 &self,
237 bounds: RectF,
238 layout: &Self::LayoutState,
239 _: &Self::PaintState,
240 ctx: &crate::DebugContext,
241 ) -> json::Value {
242 json!({
243 "type": "UniformList",
244 "bounds": bounds.to_json(),
245 "scroll_max": layout.scroll_max,
246 "item_height": layout.item_height,
247 "items": layout.items.iter().map(|item| item.debug(ctx)).collect::<Vec<json::Value>>()
248
249 })
250 }
251}