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