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