1use super::{
2 try_rect, AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext,
3 MutableAppContext, PaintContext, SizeConstraint,
4};
5use crate::geometry::{
6 rect::RectF,
7 vector::{vec2f, Vector2F},
8};
9use parking_lot::Mutex;
10use std::{cmp, ops::Range, sync::Arc};
11
12#[derive(Clone)]
13pub struct UniformListState(Arc<Mutex<StateInner>>);
14
15struct StateInner {
16 scroll_top: f32,
17 scroll_to: Option<usize>,
18}
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
33pub struct UniformList<F>
34where
35 F: Fn(Range<usize>, &mut Vec<Box<dyn Element>>, &AppContext),
36{
37 state: UniformListState,
38 item_count: usize,
39 append_items: F,
40 scroll_max: Option<f32>,
41 items: Vec<Box<dyn Element>>,
42 origin: Option<Vector2F>,
43 size: Option<Vector2F>,
44}
45
46impl<F> UniformList<F>
47where
48 F: Fn(Range<usize>, &mut Vec<Box<dyn Element>>, &AppContext),
49{
50 pub fn new(state: UniformListState, item_count: usize, build_items: F) -> Self {
51 Self {
52 state,
53 item_count,
54 append_items: build_items,
55 scroll_max: None,
56 items: Default::default(),
57 origin: None,
58 size: None,
59 }
60 }
61
62 fn scroll(
63 &self,
64 position: Vector2F,
65 delta: Vector2F,
66 precise: bool,
67 ctx: &mut EventContext,
68 _: &AppContext,
69 ) -> bool {
70 if !self.rect().unwrap().contains_point(position) {
71 return false;
72 }
73
74 if !precise {
75 todo!("still need to handle non-precise scroll events from a mouse wheel");
76 }
77
78 let mut state = self.state.0.lock();
79 state.scroll_top = (state.scroll_top - delta.y())
80 .max(0.0)
81 .min(self.scroll_max.unwrap());
82 ctx.dispatch_action("uniform_list:scroll", state.scroll_top);
83
84 true
85 }
86
87 fn autoscroll(&mut self, list_height: f32, item_height: f32) {
88 let mut state = self.state.0.lock();
89
90 let scroll_max = self.item_count as f32 * item_height - list_height;
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 fn rect(&self) -> Option<RectF> {
112 try_rect(self.origin, self.size)
113 }
114}
115
116impl<F> Element for UniformList<F>
117where
118 F: Fn(Range<usize>, &mut Vec<Box<dyn Element>>, &AppContext),
119{
120 fn layout(
121 &mut self,
122 constraint: SizeConstraint,
123 ctx: &mut LayoutContext,
124 app: &AppContext,
125 ) -> Vector2F {
126 if constraint.max.y().is_infinite() {
127 unimplemented!(
128 "UniformList does not support being rendered with an unconstrained height"
129 );
130 }
131 let mut size = constraint.max;
132 let mut item_constraint =
133 SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
134
135 self.items.clear();
136 (self.append_items)(0..1, &mut self.items, app);
137 if let Some(first_item) = self.items.first_mut() {
138 let mut item_size = first_item.layout(item_constraint, ctx, app);
139 item_size.set_x(size.x());
140 item_constraint.min = item_size;
141 item_constraint.max = item_size;
142
143 let scroll_height = self.item_count as f32 * item_size.y();
144 if scroll_height < size.y() {
145 size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
146 }
147
148 self.autoscroll(size.y(), item_size.y());
149
150 let start = cmp::min(
151 (self.scroll_top() / item_size.y()) as usize,
152 self.item_count,
153 );
154 let end = cmp::min(
155 self.item_count,
156 start + (size.y() / item_size.y()).ceil() as usize + 1,
157 );
158 self.items.clear();
159 (self.append_items)(start..end, &mut self.items, app);
160
161 self.scroll_max = Some(item_size.y() * self.item_count as f32 - size.y());
162
163 for item in &mut self.items {
164 item.layout(item_constraint, ctx, app);
165 }
166 }
167
168 self.size = Some(size);
169 size
170 }
171
172 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
173 for item in &mut self.items {
174 item.after_layout(ctx, app);
175 }
176 }
177
178 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
179 // self.origin = Some(origin);
180
181 // if let Some(item) = self.items.first() {
182 // ctx.canvas.save();
183 // let mut clip_path = Path2D::new();
184 // clip_path.rect(RectF::new(origin, self.size.unwrap()));
185 // ctx.canvas.clip_path(clip_path, FillRule::Winding);
186
187 // let item_height = item.size().unwrap().y();
188 // let mut item_origin = origin - vec2f(0.0, self.state.0.lock().scroll_top % item_height);
189 // for item in &mut self.items {
190 // item.paint(item_origin, ctx, app);
191 // item_origin += vec2f(0.0, item_height);
192 // }
193 // ctx.canvas.restore();
194 // }
195 }
196
197 fn size(&self) -> Option<Vector2F> {
198 self.size
199 }
200
201 fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
202 let mut handled = false;
203 for item in &self.items {
204 if item.dispatch_event(event, ctx, app) {
205 handled = true;
206 }
207 }
208
209 match event {
210 Event::ScrollWheel {
211 position,
212 delta,
213 precise,
214 } => {
215 if self.scroll(*position, *delta, *precise, ctx, app) {
216 handled = true;
217 }
218 }
219 _ => {}
220 }
221
222 handled
223 }
224}