1#![allow(non_snake_case)]
2
3use crate::composable;
4use crate::modifier::{inspector_metadata, Modifier, Point, PointerEvent, PointerEventKind};
5use cranpose_core::{remember, with_current_composer, OwnedMutableState, RuntimeHandle, State};
6use cranpose_foundation::{
7 DelegatableNode, InvalidationKind, ModifierNode, ModifierNodeContext, ModifierNodeElement,
8 NodeCapabilities, NodeState, PointerInputNode,
9};
10use std::cell::RefCell;
11use std::collections::HashSet;
12use std::hash::{Hash, Hasher};
13use std::rc::Rc;
14
15#[derive(Clone)]
16pub struct MutableInteractionSource {
17 inner: Rc<MutableInteractionSourceInner>,
18}
19
20struct MutableInteractionSourceInner {
21 next_press_id: RefCell<u64>,
22 active_presses: RefCell<HashSet<u64>>,
23 pressed: OwnedMutableState<bool>,
24 last_interaction: OwnedMutableState<Option<Interaction>>,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub enum Interaction {
29 Press(PressInteraction),
30}
31
32#[derive(Clone, Copy, Debug, PartialEq)]
33pub enum PressInteraction {
34 Press(PressInteractionPress),
35 Release(PressInteractionRelease),
36 Cancel(PressInteractionCancel),
37}
38
39#[derive(Clone, Copy, Debug, PartialEq)]
40pub struct PressInteractionPress {
41 id: u64,
42 pub press_position: Point,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq)]
46pub struct PressInteractionRelease {
47 pub press: PressInteractionPress,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq)]
51pub struct PressInteractionCancel {
52 pub press: PressInteractionPress,
53}
54
55impl MutableInteractionSource {
56 pub fn new() -> Self {
57 let runtime = with_current_composer(|composer| composer.runtime_handle());
58 Self::with_runtime(runtime)
59 }
60
61 pub fn with_runtime(runtime: RuntimeHandle) -> Self {
62 Self {
63 inner: Rc::new(MutableInteractionSourceInner {
64 next_press_id: RefCell::new(1),
65 active_presses: RefCell::new(HashSet::new()),
66 pressed: OwnedMutableState::with_runtime(false, runtime.clone()),
67 last_interaction: OwnedMutableState::with_runtime(None, runtime),
68 }),
69 }
70 }
71
72 pub fn id(&self) -> u64 {
73 Rc::as_ptr(&self.inner) as usize as u64
74 }
75
76 pub fn press(&self, press_position: Point) -> PressInteractionPress {
77 let id = {
78 let mut next_press_id = self.inner.next_press_id.borrow_mut();
79 let id = *next_press_id;
80 *next_press_id = next_press_id.saturating_add(1);
81 id
82 };
83 let press = PressInteractionPress { id, press_position };
84 self.emit(Interaction::Press(PressInteraction::Press(press)));
85 press
86 }
87
88 pub fn release(&self, press: PressInteractionPress) {
89 self.emit(Interaction::Press(PressInteraction::Release(
90 PressInteractionRelease { press },
91 )));
92 }
93
94 pub fn cancel(&self, press: PressInteractionPress) {
95 self.emit(Interaction::Press(PressInteraction::Cancel(
96 PressInteractionCancel { press },
97 )));
98 }
99
100 pub fn emit(&self, interaction: Interaction) {
101 self.inner.last_interaction.set(Some(interaction));
102 let is_pressed = {
103 let mut active_presses = self.inner.active_presses.borrow_mut();
104 match interaction {
105 Interaction::Press(PressInteraction::Press(press)) => {
106 active_presses.insert(press.id);
107 }
108 Interaction::Press(PressInteraction::Release(release)) => {
109 active_presses.remove(&release.press.id);
110 }
111 Interaction::Press(PressInteraction::Cancel(cancel)) => {
112 active_presses.remove(&cancel.press.id);
113 }
114 }
115 !active_presses.is_empty()
116 };
117
118 if self.inner.pressed.get_non_reactive() != is_pressed {
119 self.inner.pressed.set(is_pressed);
120 }
121 }
122
123 pub fn collectIsPressedAsState(&self) -> State<bool> {
134 self.inner.pressed.as_state()
135 }
136
137 pub fn collectLastInteractionAsState(&self) -> State<Option<Interaction>> {
138 self.inner.last_interaction.as_state()
139 }
140}
141
142impl PressInteractionPress {
143 pub fn id(&self) -> u64 {
144 self.id
145 }
146}
147
148impl std::fmt::Debug for MutableInteractionSource {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("MutableInteractionSource")
151 .field("id", &self.id())
152 .finish()
153 }
154}
155
156impl PartialEq for MutableInteractionSource {
157 fn eq(&self, other: &Self) -> bool {
158 self.id() == other.id()
159 }
160}
161
162impl Eq for MutableInteractionSource {}
163
164impl Default for MutableInteractionSource {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[composable]
171pub fn rememberMutableInteractionSource() -> MutableInteractionSource {
172 let runtime = with_current_composer(|composer| composer.runtime_handle());
173 remember(move || MutableInteractionSource::with_runtime(runtime.clone()))
174 .with(|source| source.clone())
175}
176
177pub fn collect_is_pressed_as_state(interaction_source: &MutableInteractionSource) -> State<bool> {
186 interaction_source.collectIsPressedAsState()
187}
188
189impl Modifier {
190 pub fn press_interaction_source(self, interaction_source: MutableInteractionSource) -> Self {
191 let source_id = interaction_source.id();
192 let modifier = Self::with_element(PressInteractionElement::new(interaction_source))
193 .with_inspector_metadata(inspector_metadata("pressInteractionSource", move |info| {
194 info.add_property("sourceId", source_id.to_string());
195 }));
196 self.then(modifier)
197 }
198}
199
200#[derive(Clone)]
201struct PressInteractionElement {
202 interaction_source: MutableInteractionSource,
203}
204
205impl PressInteractionElement {
206 fn new(interaction_source: MutableInteractionSource) -> Self {
207 Self { interaction_source }
208 }
209}
210
211impl std::fmt::Debug for PressInteractionElement {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("PressInteractionElement")
214 .field("source_id", &self.interaction_source.id())
215 .finish()
216 }
217}
218
219impl PartialEq for PressInteractionElement {
220 fn eq(&self, other: &Self) -> bool {
221 self.interaction_source == other.interaction_source
222 }
223}
224
225impl Eq for PressInteractionElement {}
226
227impl Hash for PressInteractionElement {
228 fn hash<H: Hasher>(&self, state: &mut H) {
229 "pressInteractionSource".hash(state);
230 self.interaction_source.id().hash(state);
231 }
232}
233
234impl ModifierNodeElement for PressInteractionElement {
235 type Node = PressInteractionNode;
236
237 fn create(&self) -> Self::Node {
238 PressInteractionNode::new(self.interaction_source.clone())
239 }
240
241 fn update(&self, node: &mut Self::Node) {
242 node.update(self.interaction_source.clone());
243 }
244
245 fn capabilities(&self) -> NodeCapabilities {
246 NodeCapabilities::POINTER_INPUT
247 }
248}
249
250struct PressInteractionNode {
251 interaction_source: MutableInteractionSource,
252 active_press: Rc<RefCell<Option<PressInteractionPress>>>,
253 cached_handler: Rc<dyn Fn(PointerEvent)>,
254 state: NodeState,
255}
256
257impl PressInteractionNode {
258 fn new(interaction_source: MutableInteractionSource) -> Self {
259 let active_press = Rc::new(RefCell::new(None));
260 let cached_handler = Self::create_handler(interaction_source.clone(), active_press.clone());
261 Self {
262 interaction_source,
263 active_press,
264 cached_handler,
265 state: NodeState::new(),
266 }
267 }
268
269 fn update(&mut self, interaction_source: MutableInteractionSource) {
270 if self.interaction_source == interaction_source {
271 return;
272 }
273 if let Some(press) = self.active_press.borrow_mut().take() {
274 self.interaction_source.cancel(press);
275 }
276 self.interaction_source = interaction_source;
277 self.cached_handler =
278 Self::create_handler(self.interaction_source.clone(), self.active_press.clone());
279 }
280
281 fn create_handler(
282 interaction_source: MutableInteractionSource,
283 active_press: Rc<RefCell<Option<PressInteractionPress>>>,
284 ) -> Rc<dyn Fn(PointerEvent)> {
285 Rc::new(move |event: PointerEvent| {
286 if event.id != 0 {
288 return;
289 }
290
291 if event.is_consumed() {
292 if let Some(press) = active_press.borrow_mut().take() {
293 interaction_source.cancel(press);
294 }
295 return;
296 }
297
298 match event.kind {
299 PointerEventKind::Down => {
300 if active_press.borrow().is_none() {
301 let press = interaction_source.press(event.position);
302 *active_press.borrow_mut() = Some(press);
303 }
304 }
305 PointerEventKind::Up => {
306 if let Some(press) = active_press.borrow_mut().take() {
307 interaction_source.release(press);
308 }
309 }
310 PointerEventKind::Cancel => {
311 if let Some(press) = active_press.borrow_mut().take() {
312 interaction_source.cancel(press);
313 }
314 }
315 PointerEventKind::Move
316 | PointerEventKind::Scroll
317 | PointerEventKind::Zoom
318 | PointerEventKind::Enter
319 | PointerEventKind::Exit => {}
320 }
321 })
322 }
323}
324
325impl std::fmt::Debug for PressInteractionNode {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 f.debug_struct("PressInteractionNode")
328 .field("source_id", &self.interaction_source.id())
329 .finish()
330 }
331}
332
333impl DelegatableNode for PressInteractionNode {
334 fn node_state(&self) -> &NodeState {
335 &self.state
336 }
337}
338
339impl ModifierNode for PressInteractionNode {
340 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
341 context.invalidate(InvalidationKind::PointerInput);
342 }
343
344 fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
345 Some(self)
346 }
347
348 fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
349 Some(self)
350 }
351
352 fn on_detach(&mut self) {
353 if let Some(press) = self.active_press.borrow_mut().take() {
354 self.interaction_source.cancel(press);
355 }
356 }
357}
358
359impl PointerInputNode for PressInteractionNode {
360 fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
361 Some(self.cached_handler.clone())
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use cranpose_core::{Composition, MemoryApplier};
369
370 #[test]
371 fn interaction_ids_do_not_use_process_global_counters() {
372 let source = include_str!("interaction.rs");
373 let source_counter = ["static ", "NEXT_SOURCE_ID"].concat();
374 let press_counter = ["static ", "NEXT_PRESS_ID"].concat();
375
376 assert!(
377 !source.contains(&source_counter) && !source.contains(&press_counter),
378 "interaction source and press ids must be owned by the interaction source instance"
379 );
380 }
381
382 #[test]
383 fn interaction_source_tracks_active_press_state() {
384 let composition = Composition::new(MemoryApplier::new());
385 let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
386 let pressed = source.collectIsPressedAsState();
387
388 assert!(!pressed.get());
389
390 let first = source.press(Point { x: 1.0, y: 2.0 });
391 assert!(pressed.get());
392
393 let second = source.press(Point { x: 3.0, y: 4.0 });
394 assert_ne!(first.id(), second.id());
395 source.release(first);
396 assert!(pressed.get());
397
398 source.cancel(second);
399 assert!(!pressed.get());
400 }
401
402 #[test]
403 fn interaction_source_ids_are_instance_owned() {
404 let composition = Composition::new(MemoryApplier::new());
405 let first = MutableInteractionSource::with_runtime(composition.runtime_handle());
406 let first_clone = first.clone();
407 let second = MutableInteractionSource::with_runtime(composition.runtime_handle());
408
409 assert_eq!(first.id(), first_clone.id());
410 assert_ne!(first.id(), second.id());
411 assert_eq!(first.press(Point { x: 0.0, y: 0.0 }).id(), 1);
412 assert_eq!(first.press(Point { x: 1.0, y: 1.0 }).id(), 2);
413 assert_eq!(second.press(Point { x: 0.0, y: 0.0 }).id(), 1);
414 }
415
416 #[test]
417 fn collect_is_pressed_as_state_tracks_press_emissions() {
418 let composition = Composition::new(MemoryApplier::new());
419 let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
420 let pressed = collect_is_pressed_as_state(&source);
421
422 assert!(!pressed.get());
423
424 let press = PressInteractionPress {
425 id: 7,
426 press_position: Point { x: 4.0, y: 5.0 },
427 };
428 source.emit(Interaction::Press(PressInteraction::Press(press)));
429 assert!(pressed.get(), "pressed after a Press emission");
430
431 source.emit(Interaction::Press(PressInteraction::Release(
432 PressInteractionRelease { press },
433 )));
434 assert!(!pressed.get(), "released after a Release emission");
435
436 source.emit(Interaction::Press(PressInteraction::Press(press)));
437 assert!(pressed.get(), "pressed again after a new Press emission");
438
439 source.emit(Interaction::Press(PressInteraction::Cancel(
440 PressInteractionCancel { press },
441 )));
442 assert!(!pressed.get(), "released after a Cancel emission");
443 }
444
445 #[composable]
446 fn PressedReader(
447 observed: Rc<RefCell<Vec<bool>>>,
448 source_slot: Rc<RefCell<Option<MutableInteractionSource>>>,
449 ) {
450 let source = rememberMutableInteractionSource();
451 source_slot.borrow_mut().replace(source.clone());
452 let pressed = collect_is_pressed_as_state(&source);
453 observed.borrow_mut().push(pressed.value());
454 }
455
456 #[test]
457 fn collect_is_pressed_as_state_recomposes_readers() {
458 let observed = Rc::new(RefCell::new(Vec::<bool>::new()));
459 let source_slot = Rc::new(RefCell::new(None::<MutableInteractionSource>));
460
461 let mut composition = {
462 let observed = Rc::clone(&observed);
463 let source_slot = Rc::clone(&source_slot);
464 crate::run_test_composition(move || {
465 PressedReader(Rc::clone(&observed), Rc::clone(&source_slot));
466 })
467 };
468
469 assert_eq!(observed.borrow().as_slice(), &[false]);
470
471 let source = source_slot
472 .borrow()
473 .as_ref()
474 .expect("interaction source captured")
475 .clone();
476 let press = composition.with_app_context(|| source.press(Point { x: 1.0, y: 1.0 }));
477 while composition
478 .process_invalid_scopes()
479 .expect("process press invalidation")
480 {}
481 assert_eq!(
482 observed.borrow().last(),
483 Some(&true),
484 "press emission should recompose readers with pressed=true"
485 );
486
487 composition.with_app_context(|| source.release(press));
488 while composition
489 .process_invalid_scopes()
490 .expect("process release invalidation")
491 {}
492 assert_eq!(
493 observed.borrow().last(),
494 Some(&false),
495 "release emission should recompose readers with pressed=false"
496 );
497 }
498
499 #[test]
500 fn interaction_source_exposes_latest_interaction() {
501 let composition = Composition::new(MemoryApplier::new());
502 let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
503 let last_interaction = source.collectLastInteractionAsState();
504
505 assert_eq!(last_interaction.get(), None);
506
507 let press = source.press(Point { x: 8.0, y: 12.0 });
508 assert_eq!(
509 last_interaction.get(),
510 Some(Interaction::Press(PressInteraction::Press(press)))
511 );
512 assert_eq!(press.press_position, Point { x: 8.0, y: 12.0 });
513
514 source.release(press);
515 assert_eq!(
516 last_interaction.get(),
517 Some(Interaction::Press(PressInteraction::Release(
518 PressInteractionRelease { press }
519 )))
520 );
521 }
522}