1use super::StateMachine;
5use crate::component::{ChildOf, Layout};
6use crate::input::{MouseButton, MouseState, RawEvent, RawEventKind};
7use crate::layout::{Desc, base, fixed};
8use crate::persist::{FnPersist, VectorMap};
9use crate::{
10 AbsRect, AbsVector, Dispatchable, InputResult, PxPoint, PxRect, PxVector, RelDim, RelVector,
11 Slot, SourceID, UNSIZED_AXIS, UnResolve, layout,
12};
13use core::f32;
14use derive_where::derive_where;
15use enum_variant_type::EnumVariantType;
16use feather_macro::Dispatch;
17use smallvec::SmallVec;
18use std::collections::HashMap;
19use std::rc::Rc;
20use std::sync::Arc;
21use winit::event::DeviceId;
22use winit::keyboard::NamedKey;
23
24#[derive(Default, Clone)]
25struct MinimalArea {
26 area: crate::DRect,
27}
28
29impl base::Area for MinimalArea {
30 fn area(&self) -> &crate::DRect {
31 &self.area
32 }
33}
34impl base::Empty for MinimalArea {}
35impl base::ZIndex for MinimalArea {}
36impl base::Margin for MinimalArea {}
37impl base::Anchor for MinimalArea {}
38impl base::Limits for MinimalArea {}
39impl base::RLimits for MinimalArea {}
40impl fixed::Prop for MinimalArea {}
41impl fixed::Child for MinimalArea {}
42
43#[derive(Debug, Dispatch, EnumVariantType, Clone, PartialEq)]
44#[evt(derive(Clone), module = "scroll_area_event")]
45pub enum ScrollAreaEvent {
46 OnScroll(AbsVector),
47}
48
49#[derive(Default, Clone, PartialEq)]
50struct ScrollAreaState {
51 lastdown: HashMap<(DeviceId, u64), (PxPoint, bool)>,
52 scroll: PxVector,
53 stepsize: (Option<f32>, Option<f32>),
54 extension: crate::DAbsRect,
55}
56
57impl ScrollAreaState {
58 fn apply_scroll(
59 &mut self,
60 mut change: PxVector,
61 area: &PxRect,
62 extent: &PxRect,
63 dpi: RelDim,
64 ) -> ScrollAreaEvent {
65 let bounds = area.dim();
66 let mut scroll = self.scroll + change;
69 let max = (bounds - extent.dim()).min(crate::PxDim::zero());
70
71 scroll = scroll.max(max.to_vector().cast_unit());
75 scroll = scroll.min(PxVector::zero());
76
77 change = scroll - self.scroll;
78 self.scroll += change;
79
80 ScrollAreaEvent::OnScroll(change.unresolve(dpi))
81 }
82
83 fn stepvec(&self) -> RelVector {
84 RelVector::new(
85 if self.stepsize.0.is_some() { 1.0 } else { 0.0 },
86 if self.stepsize.1.is_some() { 1.0 } else { 0.0 },
87 )
88 }
89}
90
91impl super::EventRouter for ScrollAreaState {
92 type Input = RawEvent;
93 type Output = ScrollAreaEvent;
94
95 fn process(
96 mut this: crate::AccessCell<Self>,
97 input: Self::Input,
98 area: PxRect,
99 extent: PxRect,
100 dpi: crate::RelDim,
101 _: &std::sync::Weak<crate::Driver>,
102 ) -> InputResult<SmallVec<[Self::Output; 1]>> {
103 match input {
104 RawEvent::Key {
105 down: true,
106 logical_key: winit::keyboard::Key::Named(code),
107 ..
108 } => {
109 if let Some(change) = match (code, this.stepsize.0, this.stepsize.1) {
110 (NamedKey::ArrowUp, _, Some(y)) => Some(PxVector::new(0.0, -y)),
111 (NamedKey::ArrowDown, _, Some(y)) => Some(PxVector::new(0.0, y)),
112 (NamedKey::ArrowLeft, Some(x), _) => Some(PxVector::new(-x, 0.0)),
113 (NamedKey::ArrowRight, Some(x), _) => Some(PxVector::new(x, 0.0)),
114 (NamedKey::PageUp, _, Some(_)) => Some(PxVector::new(0.0, -area.dim().height)),
115 (NamedKey::PageDown, _, Some(_)) => Some(PxVector::new(0.0, area.dim().height)),
116 _ => None,
117 } {
118 let e = this.apply_scroll(change, &area, &extent, dpi);
119 return InputResult::Consume([e].into());
120 }
121 }
122 RawEvent::MouseScroll { delta, .. } => {
123 let change = match delta {
124 Ok(change) => change,
125 Err(change) => PxVector::new(
126 change.x * this.stepsize.0.unwrap_or_default(),
127 change.y * this.stepsize.1.unwrap_or_default(),
128 ),
129 };
130
131 let e = this.apply_scroll(change, &area, &extent, dpi);
132 return InputResult::Consume([e].into());
133 }
134 RawEvent::MouseMove { device_id, pos, .. } => {
135 let stepvec = this.stepvec();
136 if let Some((last_pos, drag)) = this.lastdown.get_mut(&(device_id, 0)) {
137 let diff = (pos - *last_pos).component_mul(stepvec.cast_unit());
138 if !*drag {
139 *drag = true;
140 }
141
142 if *drag {
143 *last_pos = pos;
144 let e = this.apply_scroll(diff, &area, &extent, dpi);
145 return InputResult::Consume([e].into());
146 }
147 return InputResult::Consume(SmallVec::new());
148 }
149 }
150 RawEvent::Mouse {
151 device_id,
152 state,
153 pos,
154 button,
155 ..
156 } => match (state, button) {
157 (MouseState::Down, MouseButton::Left) => {
158 if area.contains(pos) {
159 this.lastdown.insert((device_id, 0), (pos, false));
160 return InputResult::Consume(SmallVec::new());
161 }
162 }
163 (MouseState::Up, MouseButton::Left) => {
164 if let Some((last_pos, drag)) = this.lastdown.remove(&(device_id, 0))
165 && area.contains(pos)
166 {
167 let e = this.apply_scroll(pos - last_pos, &area, &extent, dpi);
168 return InputResult::Consume(if drag {
169 [e].into()
170 } else {
171 SmallVec::new()
172 });
173 }
174 }
175 _ => (),
176 },
177 RawEvent::Touch {
178 device_id,
179 index,
180 state,
181 pos,
182 ..
183 } => match state {
184 crate::input::TouchState::Start => {
185 if area.contains(pos.xy()) {
186 this.lastdown
187 .insert((device_id, index as u64), (pos.xy(), false));
188 return InputResult::Consume(SmallVec::new());
189 }
190 }
191 crate::input::TouchState::Move => {
192 let stepvec = this.stepvec();
193 if let Some((last_pos, drag)) =
194 this.lastdown.get_mut(&(device_id, index as u64))
195 {
196 let diff = (pos.xy() - *last_pos).component_mul(stepvec.cast_unit());
197 if !*drag {
198 *drag = true;
199 }
200
201 let e = this.apply_scroll(diff, &area, &extent, dpi);
202 return InputResult::Consume([e].into());
203 }
204 }
205 crate::input::TouchState::End => {
206 if let Some((last_pos, drag)) = this.lastdown.remove(&(device_id, index as u64))
208 && area.contains(pos.xy())
209 {
210 let stepvec = this.stepvec();
211 let e = this.apply_scroll(
212 (pos.xy() - last_pos).component_mul(stepvec.cast_unit()),
213 &area,
214 &extent,
215 dpi,
216 );
217 return InputResult::Consume(if drag {
218 [e].into()
219 } else {
220 SmallVec::new()
221 });
222 }
223 }
224 },
225 _ => (),
226 }
227
228 InputResult::Forward(SmallVec::new())
229 }
230}
231
232#[derive_where(Clone)]
233pub struct ScrollArea<T> {
234 pub id: Arc<SourceID>,
235 props: Rc<T>,
236 stepsize: (Option<f32>, Option<f32>),
237 extension: crate::DAbsRect,
238 children: im::Vector<Option<Box<ChildOf<dyn fixed::Prop>>>>,
239 slots: [Option<Slot>; ScrollAreaEvent::SIZE],
240}
241
242impl<T: fixed::Prop + 'static> ScrollArea<T> {
243 pub fn new(
244 id: Arc<SourceID>,
245 props: T,
246 stepsize: (Option<f32>, Option<f32>),
247 extension: crate::DAbsRect,
248 children: im::Vector<Option<Box<ChildOf<dyn fixed::Prop>>>>,
249 slots: [Option<Slot>; ScrollAreaEvent::SIZE],
250 ) -> Self {
251 Self {
252 id,
253 props: props.into(),
254 children,
255 slots,
256 stepsize,
257 extension,
258 }
259 }
260}
261
262impl<T: fixed::Prop + 'static> crate::StateMachineChild for ScrollArea<T> {
263 fn id(&self) -> Arc<SourceID> {
264 self.id.clone()
265 }
266 fn init(
267 &self,
268 _: &std::sync::Weak<crate::Driver>,
269 ) -> Result<Box<dyn super::StateMachineWrapper>, crate::Error> {
270 Ok(Box::new(StateMachine {
271 state: ScrollAreaState {
272 ..Default::default()
273 },
274 input_mask: RawEventKind::MouseScroll as u64
275 | RawEventKind::Mouse as u64
276 | RawEventKind::MouseMove as u64
277 | RawEventKind::Touch as u64
278 | RawEventKind::Key as u64,
279 output: self.slots.clone(),
280 changed: true,
281 }))
282 }
283 fn apply_children(
284 &self,
285 f: &mut dyn FnMut(&dyn crate::StateMachineChild) -> eyre::Result<()>,
286 ) -> eyre::Result<()> {
287 self.children
288 .iter()
289 .try_for_each(|x| f(x.as_ref().unwrap().as_ref()))
290 }
291}
292
293impl<T: fixed::Prop + 'static> super::Component for ScrollArea<T>
294where
295 for<'a> &'a T: Into<&'a (dyn fixed::Prop + 'static)>,
296{
297 type Props = T;
298
299 fn layout(
300 &self,
301 manager: &mut crate::StateManager,
302 driver: &crate::graphics::Driver,
303 window: &Arc<SourceID>,
304 ) -> Box<dyn Layout<T> + 'static> {
305 let scroll = manager
306 .get_mut::<StateMachine<ScrollAreaState, { ScrollAreaEvent::SIZE }>>(&self.id)
307 .map(|state| {
308 state.state.stepsize = self.stepsize;
309 state.state.extension = self.extension;
310 state.state.scroll
311 })
312 .unwrap();
313
314 let mut map = VectorMap::new(crate::persist::Persist::new(
317 |child: &Option<Box<ChildOf<dyn fixed::Prop>>>| -> Option<Box<dyn Layout<<dyn fixed::Prop as Desc>::Child>>> {
318 Some(child.as_ref()?.layout(manager, driver, window))
319 })
320 );
321
322 let (_, children) = map.call(Default::default(), &self.children);
323 let scrollable: Box<dyn layout::Layout<MinimalArea>> =
324 Box::new(layout::Node::<MinimalArea, dyn fixed::Prop> {
325 props: Rc::new(MinimalArea {
326 area: crate::DRect {
327 px: PxRect::zero(),
328 dp: AbsRect::new(scroll.x, scroll.y, 0.0, 0.0),
329 rel: crate::RelRect::new(
330 0.0,
331 0.0,
332 if self.stepsize.0.is_some() {
333 UNSIZED_AXIS
334 } else {
335 1.0
336 },
337 if self.stepsize.1.is_some() {
338 UNSIZED_AXIS
339 } else {
340 1.0
341 },
342 ),
343 },
344 }),
345 children,
346 id: std::sync::Weak::new(),
347 renderable: None,
348 layer: None,
349 });
350
351 let inner: Box<dyn layout::Layout<dyn fixed::Child>> = Box::new(scrollable);
352
353 Box::new(layout::Node::<T, dyn fixed::Prop> {
354 props: self.props.clone(),
355 children: im::vector![Some(inner)],
356 id: Arc::downgrade(&self.id),
357 renderable: None,
358 layer: Some((crate::color::sRGB32::white(), 0.0)),
359 })
360 }
361}
362
363