Skip to main content

cranpose_ui/
interaction.rs

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;
14use std::sync::atomic::{AtomicU64, Ordering};
15
16#[derive(Clone)]
17pub struct MutableInteractionSource {
18    inner: Rc<MutableInteractionSourceInner>,
19}
20
21struct MutableInteractionSourceInner {
22    id: u64,
23    active_presses: RefCell<HashSet<u64>>,
24    pressed: OwnedMutableState<bool>,
25    last_interaction: OwnedMutableState<Option<Interaction>>,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum Interaction {
30    Press(PressInteraction),
31}
32
33#[derive(Clone, Copy, Debug, PartialEq)]
34pub enum PressInteraction {
35    Press(PressInteractionPress),
36    Release(PressInteractionRelease),
37    Cancel(PressInteractionCancel),
38}
39
40#[derive(Clone, Copy, Debug, PartialEq)]
41pub struct PressInteractionPress {
42    id: u64,
43    pub press_position: Point,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq)]
47pub struct PressInteractionRelease {
48    pub press: PressInteractionPress,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct PressInteractionCancel {
53    pub press: PressInteractionPress,
54}
55
56impl MutableInteractionSource {
57    pub fn new() -> Self {
58        let runtime = with_current_composer(|composer| composer.runtime_handle());
59        Self::with_runtime(runtime)
60    }
61
62    pub fn with_runtime(runtime: RuntimeHandle) -> Self {
63        static NEXT_SOURCE_ID: AtomicU64 = AtomicU64::new(1);
64        Self {
65            inner: Rc::new(MutableInteractionSourceInner {
66                id: NEXT_SOURCE_ID.fetch_add(1, Ordering::Relaxed),
67                active_presses: RefCell::new(HashSet::new()),
68                pressed: OwnedMutableState::with_runtime(false, runtime.clone()),
69                last_interaction: OwnedMutableState::with_runtime(None, runtime),
70            }),
71        }
72    }
73
74    pub fn id(&self) -> u64 {
75        self.inner.id
76    }
77
78    pub fn press(&self, press_position: Point) -> PressInteractionPress {
79        static NEXT_PRESS_ID: AtomicU64 = AtomicU64::new(1);
80        let press = PressInteractionPress {
81            id: NEXT_PRESS_ID.fetch_add(1, Ordering::Relaxed),
82            press_position,
83        };
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> {
124        self.inner.pressed.as_state()
125    }
126
127    pub fn collectLastInteractionAsState(&self) -> State<Option<Interaction>> {
128        self.inner.last_interaction.as_state()
129    }
130}
131
132impl PressInteractionPress {
133    pub fn id(&self) -> u64 {
134        self.id
135    }
136}
137
138impl std::fmt::Debug for MutableInteractionSource {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        f.debug_struct("MutableInteractionSource")
141            .field("id", &self.id())
142            .finish()
143    }
144}
145
146impl PartialEq for MutableInteractionSource {
147    fn eq(&self, other: &Self) -> bool {
148        self.id() == other.id()
149    }
150}
151
152impl Eq for MutableInteractionSource {}
153
154impl Default for MutableInteractionSource {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160#[composable]
161pub fn rememberMutableInteractionSource() -> MutableInteractionSource {
162    let runtime = with_current_composer(|composer| composer.runtime_handle());
163    remember(move || MutableInteractionSource::with_runtime(runtime.clone()))
164        .with(|source| source.clone())
165}
166
167impl Modifier {
168    pub fn press_interaction_source(self, interaction_source: MutableInteractionSource) -> Self {
169        let source_id = interaction_source.id();
170        let modifier = Self::with_element(PressInteractionElement::new(interaction_source))
171            .with_inspector_metadata(inspector_metadata("pressInteractionSource", move |info| {
172                info.add_property("sourceId", source_id.to_string());
173            }));
174        self.then(modifier)
175    }
176}
177
178#[derive(Clone)]
179struct PressInteractionElement {
180    interaction_source: MutableInteractionSource,
181}
182
183impl PressInteractionElement {
184    fn new(interaction_source: MutableInteractionSource) -> Self {
185        Self { interaction_source }
186    }
187}
188
189impl std::fmt::Debug for PressInteractionElement {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        f.debug_struct("PressInteractionElement")
192            .field("source_id", &self.interaction_source.id())
193            .finish()
194    }
195}
196
197impl PartialEq for PressInteractionElement {
198    fn eq(&self, other: &Self) -> bool {
199        self.interaction_source == other.interaction_source
200    }
201}
202
203impl Eq for PressInteractionElement {}
204
205impl Hash for PressInteractionElement {
206    fn hash<H: Hasher>(&self, state: &mut H) {
207        "pressInteractionSource".hash(state);
208        self.interaction_source.id().hash(state);
209    }
210}
211
212impl ModifierNodeElement for PressInteractionElement {
213    type Node = PressInteractionNode;
214
215    fn create(&self) -> Self::Node {
216        PressInteractionNode::new(self.interaction_source.clone())
217    }
218
219    fn update(&self, node: &mut Self::Node) {
220        node.update(self.interaction_source.clone());
221    }
222
223    fn capabilities(&self) -> NodeCapabilities {
224        NodeCapabilities::POINTER_INPUT
225    }
226}
227
228struct PressInteractionNode {
229    interaction_source: MutableInteractionSource,
230    active_press: Rc<RefCell<Option<PressInteractionPress>>>,
231    cached_handler: Rc<dyn Fn(PointerEvent)>,
232    state: NodeState,
233}
234
235impl PressInteractionNode {
236    fn new(interaction_source: MutableInteractionSource) -> Self {
237        let active_press = Rc::new(RefCell::new(None));
238        let cached_handler = Self::create_handler(interaction_source.clone(), active_press.clone());
239        Self {
240            interaction_source,
241            active_press,
242            cached_handler,
243            state: NodeState::new(),
244        }
245    }
246
247    fn update(&mut self, interaction_source: MutableInteractionSource) {
248        if self.interaction_source == interaction_source {
249            return;
250        }
251        if let Some(press) = self.active_press.borrow_mut().take() {
252            self.interaction_source.cancel(press);
253        }
254        self.interaction_source = interaction_source;
255        self.cached_handler =
256            Self::create_handler(self.interaction_source.clone(), self.active_press.clone());
257    }
258
259    fn create_handler(
260        interaction_source: MutableInteractionSource,
261        active_press: Rc<RefCell<Option<PressInteractionPress>>>,
262    ) -> Rc<dyn Fn(PointerEvent)> {
263        Rc::new(move |event: PointerEvent| {
264            if event.is_consumed() {
265                if let Some(press) = active_press.borrow_mut().take() {
266                    interaction_source.cancel(press);
267                }
268                return;
269            }
270
271            match event.kind {
272                PointerEventKind::Down => {
273                    if active_press.borrow().is_none() {
274                        let press = interaction_source.press(event.position);
275                        *active_press.borrow_mut() = Some(press);
276                    }
277                }
278                PointerEventKind::Up => {
279                    if let Some(press) = active_press.borrow_mut().take() {
280                        interaction_source.release(press);
281                    }
282                }
283                PointerEventKind::Cancel => {
284                    if let Some(press) = active_press.borrow_mut().take() {
285                        interaction_source.cancel(press);
286                    }
287                }
288                PointerEventKind::Move
289                | PointerEventKind::Scroll
290                | PointerEventKind::Enter
291                | PointerEventKind::Exit => {}
292            }
293        })
294    }
295}
296
297impl std::fmt::Debug for PressInteractionNode {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        f.debug_struct("PressInteractionNode")
300            .field("source_id", &self.interaction_source.id())
301            .finish()
302    }
303}
304
305impl DelegatableNode for PressInteractionNode {
306    fn node_state(&self) -> &NodeState {
307        &self.state
308    }
309}
310
311impl ModifierNode for PressInteractionNode {
312    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
313        context.invalidate(InvalidationKind::PointerInput);
314    }
315
316    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
317        Some(self)
318    }
319
320    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
321        Some(self)
322    }
323
324    fn on_detach(&mut self) {
325        if let Some(press) = self.active_press.borrow_mut().take() {
326            self.interaction_source.cancel(press);
327        }
328    }
329}
330
331impl PointerInputNode for PressInteractionNode {
332    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
333        Some(self.cached_handler.clone())
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use cranpose_core::{Composition, MemoryApplier};
341
342    #[test]
343    fn interaction_source_tracks_active_press_state() {
344        let composition = Composition::new(MemoryApplier::new());
345        let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
346        let pressed = source.collectIsPressedAsState();
347
348        assert!(!pressed.get());
349
350        let first = source.press(Point { x: 1.0, y: 2.0 });
351        assert!(pressed.get());
352
353        let second = source.press(Point { x: 3.0, y: 4.0 });
354        assert_ne!(first.id(), second.id());
355        source.release(first);
356        assert!(pressed.get());
357
358        source.cancel(second);
359        assert!(!pressed.get());
360    }
361
362    #[test]
363    fn interaction_source_exposes_latest_interaction() {
364        let composition = Composition::new(MemoryApplier::new());
365        let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
366        let last_interaction = source.collectLastInteractionAsState();
367
368        assert_eq!(last_interaction.get(), None);
369
370        let press = source.press(Point { x: 8.0, y: 12.0 });
371        assert_eq!(
372            last_interaction.get(),
373            Some(Interaction::Press(PressInteraction::Press(press)))
374        );
375        assert_eq!(press.press_position, Point { x: 8.0, y: 12.0 });
376
377        source.release(press);
378        assert_eq!(
379            last_interaction.get(),
380            Some(Interaction::Press(PressInteraction::Release(
381                PressInteractionRelease { press }
382            )))
383        );
384    }
385}