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