1use std::{
21 cell::Cell,
22 fmt,
23 hash::{Hash, Hasher},
24 rc::Rc,
25};
26
27use cranpose_foundation::{
28 impl_pointer_input_node, DelegatableNode, ModifierNode, ModifierNodeElement, NodeCapabilities,
29 NodeState, PointerEvent, PointerEventKind, PointerInputNode, RotaryScrollEvent,
30};
31
32use super::{inspector_metadata, Modifier};
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
36enum RotaryPass {
37 Pre,
39 Bubble,
41}
42
43impl RotaryPass {
44 fn matches(self, kind: PointerEventKind) -> bool {
45 matches!(
46 (self, kind),
47 (RotaryPass::Pre, PointerEventKind::RotaryScrollPre)
48 | (RotaryPass::Bubble, PointerEventKind::RotaryScroll)
49 )
50 }
51}
52
53type RotaryHandler = Rc<dyn Fn(RotaryScrollEvent) -> bool>;
54
55impl Modifier {
56 pub fn on_rotary_scroll_event<F>(self, handler: F) -> Self
72 where
73 F: Fn(RotaryScrollEvent) -> bool + 'static,
74 {
75 self.rotary_element(RotaryPass::Bubble, Rc::new(handler), "onRotaryScrollEvent")
76 }
77
78 pub fn on_pre_rotary_scroll_event<F>(self, handler: F) -> Self
85 where
86 F: Fn(RotaryScrollEvent) -> bool + 'static,
87 {
88 self.rotary_element(RotaryPass::Pre, Rc::new(handler), "onPreRotaryScrollEvent")
89 }
90
91 fn rotary_element(
92 self,
93 pass: RotaryPass,
94 handler: RotaryHandler,
95 inspector_name: &'static str,
96 ) -> Self {
97 let element = RotaryInputElement::new(pass, handler);
98 let handler_id = element.handler_id;
99 self.then(
100 Self::with_element(element).with_inspector_metadata(inspector_metadata(
101 inspector_name,
102 move |info| {
103 info.add_property("handlerId", handler_id.to_string());
104 },
105 )),
106 )
107 }
108}
109
110#[derive(Clone)]
111struct RotaryInputElement {
112 pass: RotaryPass,
113 handler: RotaryHandler,
114 handler_id: u64,
115}
116
117impl RotaryInputElement {
118 fn new(pass: RotaryPass, handler: RotaryHandler) -> Self {
119 let handler_id = Rc::as_ptr(&handler) as *const () as usize as u64;
120 Self {
121 pass,
122 handler,
123 handler_id,
124 }
125 }
126}
127
128impl fmt::Debug for RotaryInputElement {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.debug_struct("RotaryInputElement")
131 .field("pass", &self.pass)
132 .field("handler_id", &self.handler_id)
133 .finish()
134 }
135}
136
137impl PartialEq for RotaryInputElement {
138 fn eq(&self, other: &Self) -> bool {
139 self.pass == other.pass
143 }
144}
145
146impl Eq for RotaryInputElement {}
147
148impl Hash for RotaryInputElement {
149 fn hash<H: Hasher>(&self, state: &mut H) {
150 self.pass.hash(state);
151 }
152}
153
154impl ModifierNodeElement for RotaryInputElement {
155 type Node = RotaryInputModifierNode;
156
157 fn create(&self) -> Self::Node {
158 RotaryInputModifierNode::new(self.pass, self.handler.clone())
159 }
160
161 fn update(&self, node: &mut Self::Node) {
162 node.handler.set(Some(self.handler.clone()));
165 }
166
167 fn always_update(&self) -> bool {
168 true
169 }
170
171 fn capabilities(&self) -> NodeCapabilities {
172 NodeCapabilities::POINTER_INPUT
173 }
174}
175
176pub struct RotaryInputModifierNode {
183 handler: Rc<Cell<Option<RotaryHandler>>>,
184 dispatch: Rc<dyn Fn(PointerEvent)>,
185 state: NodeState,
186}
187
188impl RotaryInputModifierNode {
189 fn new(pass: RotaryPass, handler: RotaryHandler) -> Self {
190 let handler_cell: Rc<Cell<Option<RotaryHandler>>> = Rc::new(Cell::new(Some(handler)));
191 let handler_for_dispatch = Rc::clone(&handler_cell);
192 let dispatch = Rc::new(move |event: PointerEvent| {
194 if !pass.matches(event.kind) || event.is_consumed() {
195 return;
196 }
197 let Some(rotary) = event.rotary_scroll_event() else {
198 return;
199 };
200 let Some(handler) = handler_for_dispatch.take() else {
204 return;
205 };
206 let consumed = handler(rotary);
207 if handler_for_dispatch.take().is_none() {
208 handler_for_dispatch.set(Some(handler));
210 }
211 if consumed {
212 event.consume();
213 }
214 });
215
216 Self {
217 handler: handler_cell,
218 dispatch,
219 state: NodeState::new(),
220 }
221 }
222}
223
224impl ModifierNode for RotaryInputModifierNode {
225 impl_pointer_input_node!();
226}
227
228impl DelegatableNode for RotaryInputModifierNode {
229 fn node_state(&self) -> &NodeState {
230 &self.state
231 }
232}
233
234impl PointerInputNode for RotaryInputModifierNode {
235 fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
236 Some(Rc::clone(&self.dispatch))
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use std::cell::RefCell;
243
244 use cranpose_ui_graphics::Point;
245
246 use super::*;
247
248 fn rotary_event(kind: PointerEventKind, vertical: f32) -> PointerEvent {
249 PointerEvent::rotary(
250 kind,
251 RotaryScrollEvent::new(vertical, 0.0, 42),
252 Point { x: 0.0, y: 0.0 },
253 )
254 }
255
256 fn node(pass: RotaryPass, handler: RotaryHandler) -> RotaryInputModifierNode {
257 RotaryInputModifierNode::new(pass, handler)
258 }
259
260 #[test]
261 fn bubble_node_receives_bubble_events_only() {
262 let seen = Rc::new(RefCell::new(Vec::new()));
263 let sink = Rc::clone(&seen);
264 let node = node(
265 RotaryPass::Bubble,
266 Rc::new(move |event: RotaryScrollEvent| {
267 sink.borrow_mut().push(event.vertical_scroll_pixels);
268 false
269 }),
270 );
271 let dispatch = node.pointer_input_handler().expect("handler");
272
273 dispatch(rotary_event(PointerEventKind::RotaryScrollPre, -1.0));
274 dispatch(rotary_event(PointerEventKind::RotaryScroll, -2.0));
275 dispatch(PointerEvent::new(
276 PointerEventKind::Scroll,
277 Point { x: 0.0, y: 0.0 },
278 Point { x: 0.0, y: 0.0 },
279 ));
280
281 assert_eq!(*seen.borrow(), vec![-2.0]);
282 }
283
284 #[test]
285 fn pre_node_receives_capture_events_only() {
286 let seen = Rc::new(RefCell::new(Vec::new()));
287 let sink = Rc::clone(&seen);
288 let node = node(
289 RotaryPass::Pre,
290 Rc::new(move |event: RotaryScrollEvent| {
291 sink.borrow_mut().push(event.vertical_scroll_pixels);
292 false
293 }),
294 );
295 let dispatch = node.pointer_input_handler().expect("handler");
296
297 dispatch(rotary_event(PointerEventKind::RotaryScrollPre, -1.0));
298 dispatch(rotary_event(PointerEventKind::RotaryScroll, -2.0));
299
300 assert_eq!(*seen.borrow(), vec![-1.0]);
301 }
302
303 #[test]
304 fn returning_true_consumes_the_event() {
305 let node = node(RotaryPass::Bubble, Rc::new(|_| true));
306 let dispatch = node.pointer_input_handler().expect("handler");
307
308 let event = rotary_event(PointerEventKind::RotaryScroll, -8.0);
309 dispatch(event.clone());
310
311 assert!(event.is_consumed());
312 }
313
314 #[test]
315 fn returning_false_leaves_the_event_unconsumed() {
316 let node = node(RotaryPass::Bubble, Rc::new(|_| false));
317 let dispatch = node.pointer_input_handler().expect("handler");
318
319 let event = rotary_event(PointerEventKind::RotaryScroll, -8.0);
320 dispatch(event.clone());
321
322 assert!(!event.is_consumed());
323 }
324
325 #[test]
326 fn an_already_consumed_event_never_reaches_the_handler() {
327 let calls = Rc::new(Cell::new(0));
330 let counter = Rc::clone(&calls);
331 let node = node(
332 RotaryPass::Bubble,
333 Rc::new(move |_| {
334 counter.set(counter.get() + 1);
335 false
336 }),
337 );
338 let dispatch = node.pointer_input_handler().expect("handler");
339
340 let event = rotary_event(PointerEventKind::RotaryScroll, -8.0);
341 event.consume();
342 dispatch(event);
343
344 assert_eq!(calls.get(), 0);
345 }
346
347 #[test]
348 fn handler_sees_the_full_rotary_payload() {
349 let captured = Rc::new(Cell::new(None));
350 let sink = Rc::clone(&captured);
351 let node = node(
352 RotaryPass::Bubble,
353 Rc::new(move |event: RotaryScrollEvent| {
354 sink.set(Some(event));
355 true
356 }),
357 );
358 let dispatch = node.pointer_input_handler().expect("handler");
359
360 dispatch(PointerEvent::rotary(
361 PointerEventKind::RotaryScroll,
362 RotaryScrollEvent::new(-64.0, 32.0, 777),
363 Point { x: 0.0, y: 0.0 },
364 ));
365
366 assert_eq!(
367 captured.get(),
368 Some(RotaryScrollEvent::new(-64.0, 32.0, 777))
369 );
370 }
371
372 #[test]
373 fn element_reuses_the_node_across_recomposition() {
374 let first = RotaryInputElement::new(RotaryPass::Bubble, Rc::new(|_| false));
377 let second = RotaryInputElement::new(RotaryPass::Bubble, Rc::new(|_| true));
378 assert_eq!(first, second);
379 assert!(first.always_update());
380
381 let mut node = first.create();
382 second.update(&mut node);
383
384 let event = rotary_event(PointerEventKind::RotaryScroll, -1.0);
385 node.pointer_input_handler().expect("handler")(event.clone());
386
387 assert!(event.is_consumed(), "updated handler should be in effect");
388 }
389
390 #[test]
391 fn pre_and_bubble_elements_are_distinct() {
392 let pre = RotaryInputElement::new(RotaryPass::Pre, Rc::new(|_| false));
393 let bubble = RotaryInputElement::new(RotaryPass::Bubble, Rc::new(|_| false));
394
395 assert_ne!(pre, bubble);
396 }
397
398 #[test]
399 fn modifier_builders_add_pointer_input_elements() {
400 let modifier = Modifier::empty()
401 .on_pre_rotary_scroll_event(|_| false)
402 .on_rotary_scroll_event(|_| true);
403
404 assert_eq!(modifier.elements().len(), 2);
405 }
406}