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