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