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