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;
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> {
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_ids_do_not_use_process_global_counters() {
344        let source = include_str!("interaction.rs");
345        let source_counter = ["static ", "NEXT_SOURCE_ID"].concat();
346        let press_counter = ["static ", "NEXT_PRESS_ID"].concat();
347
348        assert!(
349            !source.contains(&source_counter) && !source.contains(&press_counter),
350            "interaction source and press ids must be owned by the interaction source instance"
351        );
352    }
353
354    #[test]
355    fn interaction_source_tracks_active_press_state() {
356        let composition = Composition::new(MemoryApplier::new());
357        let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
358        let pressed = source.collectIsPressedAsState();
359
360        assert!(!pressed.get());
361
362        let first = source.press(Point { x: 1.0, y: 2.0 });
363        assert!(pressed.get());
364
365        let second = source.press(Point { x: 3.0, y: 4.0 });
366        assert_ne!(first.id(), second.id());
367        source.release(first);
368        assert!(pressed.get());
369
370        source.cancel(second);
371        assert!(!pressed.get());
372    }
373
374    #[test]
375    fn interaction_source_ids_are_instance_owned() {
376        let composition = Composition::new(MemoryApplier::new());
377        let first = MutableInteractionSource::with_runtime(composition.runtime_handle());
378        let first_clone = first.clone();
379        let second = MutableInteractionSource::with_runtime(composition.runtime_handle());
380
381        assert_eq!(first.id(), first_clone.id());
382        assert_ne!(first.id(), second.id());
383        assert_eq!(first.press(Point { x: 0.0, y: 0.0 }).id(), 1);
384        assert_eq!(first.press(Point { x: 1.0, y: 1.0 }).id(), 2);
385        assert_eq!(second.press(Point { x: 0.0, y: 0.0 }).id(), 1);
386    }
387
388    #[test]
389    fn interaction_source_exposes_latest_interaction() {
390        let composition = Composition::new(MemoryApplier::new());
391        let source = MutableInteractionSource::with_runtime(composition.runtime_handle());
392        let last_interaction = source.collectLastInteractionAsState();
393
394        assert_eq!(last_interaction.get(), None);
395
396        let press = source.press(Point { x: 8.0, y: 12.0 });
397        assert_eq!(
398            last_interaction.get(),
399            Some(Interaction::Press(PressInteraction::Press(press)))
400        );
401        assert_eq!(press.press_position, Point { x: 8.0, y: 12.0 });
402
403        source.release(press);
404        assert_eq!(
405            last_interaction.get(),
406            Some(Interaction::Press(PressInteraction::Release(
407                PressInteractionRelease { press }
408            )))
409        );
410    }
411}