Skip to main content

cranpose_ui/modifier_nodes/
clickable.rs

1use std::{
2    cell::Cell,
3    hash::{Hash, Hasher},
4    rc::Rc,
5};
6
7use cranpose_foundation::{
8    DRAG_THRESHOLD, DelegatableNode, InvalidationKind, ModifierNode, ModifierNodeContext,
9    ModifierNodeElement, NodeCapabilities, NodeState, PointerEvent, PointerEventKind, PointerId,
10    PointerInputNode,
11};
12
13use crate::modifier::Point;
14
15/// Where a clickable's press stands.
16#[derive(Clone, Copy, Debug)]
17enum ClickPress {
18    Idle,
19    /// A pointer went down here, and the id of that pointer.
20    Pressed(Point, PointerId),
21    /// The press was cancelled, by a long press that took it among others:
22    /// its release does not click.
23    Cancelled,
24}
25
26/// Node that reports a click when a pointer goes down and up without moving farther than the drag
27/// threshold: the first pointer that goes down holds the press, and the others pass by until it
28/// lifts, so a second finger does not click, and each controller of a headset clicks with its own.
29/// Callbacks get positions in the node's local coordinates, in logical pixels.
30pub struct ClickableNode {
31    on_press: Option<Rc<dyn Fn(Point)>>,
32    on_click: Rc<dyn Fn(Point)>,
33    state: NodeState,
34    press_position: Rc<Cell<ClickPress>>,
35    cached_handler: Rc<dyn Fn(PointerEvent)>,
36}
37
38impl std::fmt::Debug for ClickableNode {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("ClickableNode").finish()
41    }
42}
43
44impl ClickableNode {
45    /// Creates a node that calls `on_click` with the position of the click.
46    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
47        Self::with_handler(Rc::new(on_click))
48    }
49
50    /// Creates a node that calls the shared callback `on_click` with the position of the click.
51    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
52        Self::with_handlers(None, on_click)
53    }
54
55    /// Creates a node that calls `on_press`, when given, as the pointer goes down, and `on_click`
56    /// on a click. Both get the pointer position.
57    pub fn with_handlers(on_press: Option<Rc<dyn Fn(Point)>>, on_click: Rc<dyn Fn(Point)>) -> Self {
58        let press_position = Rc::new(Cell::new(ClickPress::Idle));
59        let cached_handler = Self::create_handler(
60            on_press.clone(),
61            Rc::clone(&on_click),
62            Rc::clone(&press_position),
63        );
64        Self {
65            on_press,
66            on_click,
67            state: NodeState::new(),
68            press_position,
69            cached_handler,
70        }
71    }
72
73    fn create_handler(
74        on_press: Option<Rc<dyn Fn(Point)>>,
75        on_click: Rc<dyn Fn(Point)>,
76        press_position: Rc<Cell<ClickPress>>,
77    ) -> Rc<dyn Fn(PointerEvent)> {
78        Rc::new(move |event: PointerEvent| {
79            if matches!(press_position.get(), ClickPress::Pressed(_, holder) if holder != event.id)
80            {
81                return;
82            }
83
84            if event.is_consumed() {
85                press_position.set(ClickPress::Idle);
86                return;
87            }
88
89            match event.kind {
90                PointerEventKind::Down => {
91                    press_position.set(ClickPress::Pressed(event.travelled_to(), event.id));
92                    if let Some(on_press) = on_press.as_ref() {
93                        on_press(event.position);
94                    }
95                }
96                PointerEventKind::Move => {}
97                PointerEventKind::Up => {
98                    let should_click = match press_position.replace(ClickPress::Idle) {
99                        ClickPress::Pressed(press_pos, _) => {
100                            let travelled_to = event.travelled_to();
101                            let dx = travelled_to.x - press_pos.x;
102                            let dy = travelled_to.y - press_pos.y;
103                            let distance = (dx * dx + dy * dy).sqrt();
104                            distance <= DRAG_THRESHOLD
105                        }
106                        // A release with no press before it clicks for the
107                        // primary pointer only: another finger's release after
108                        // the first one clicked is not a click of its own.
109                        ClickPress::Idle => event.id.is_primary(),
110                        ClickPress::Cancelled => false,
111                    };
112
113                    if should_click {
114                        on_click(Point {
115                            x: event.position.x,
116                            y: event.position.y,
117                        });
118                        event.consume();
119                    }
120                }
121                PointerEventKind::Cancel => {
122                    press_position.set(ClickPress::Cancelled);
123                }
124                _ => {}
125            }
126        })
127    }
128
129    /// Returns the click callback.
130    pub fn handler(&self) -> Rc<dyn Fn(Point)> {
131        Rc::clone(&self.on_click)
132    }
133}
134
135impl DelegatableNode for ClickableNode {
136    fn node_state(&self) -> &NodeState {
137        &self.state
138    }
139}
140
141impl ModifierNode for ClickableNode {
142    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
143        context.invalidate(InvalidationKind::PointerInput);
144    }
145
146    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
147        Some(self)
148    }
149
150    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
151        Some(self)
152    }
153}
154
155impl PointerInputNode for ClickableNode {
156    fn on_pointer_event(
157        &mut self,
158        _context: &mut dyn ModifierNodeContext,
159        event: &PointerEvent,
160    ) -> bool {
161        (self.cached_handler)(event.clone());
162        event.is_consumed()
163    }
164
165    fn hit_test(&self, _x: f32, _y: f32) -> bool {
166        true
167    }
168
169    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
170        Some(Rc::clone(&self.cached_handler))
171    }
172}
173
174/// Element that creates and updates clickable nodes.
175#[derive(Clone)]
176pub struct ClickableElement {
177    on_press: Option<Rc<dyn Fn(Point)>>,
178    on_click: Rc<dyn Fn(Point)>,
179}
180
181impl ClickableElement {
182    /// Creates an element that calls `on_click` with the position of the click.
183    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
184        Self {
185            on_press: None,
186            on_click: Rc::new(on_click),
187        }
188    }
189
190    /// Creates an element that calls the shared callback `on_click` with the position of the click.
191    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
192        Self {
193            on_press: None,
194            on_click,
195        }
196    }
197
198    /// Creates an element that calls `on_press` as the pointer goes down and `on_click` on a click.
199    /// Both get the pointer position.
200    pub fn with_handlers(on_press: Rc<dyn Fn(Point)>, on_click: Rc<dyn Fn(Point)>) -> Self {
201        Self {
202            on_press: Some(on_press),
203            on_click,
204        }
205    }
206}
207
208impl std::fmt::Debug for ClickableElement {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        f.debug_struct("ClickableElement").finish()
211    }
212}
213
214impl PartialEq for ClickableElement {
215    fn eq(&self, _other: &Self) -> bool {
216        true
217    }
218}
219
220impl Eq for ClickableElement {}
221
222impl Hash for ClickableElement {
223    fn hash<H: Hasher>(&self, state: &mut H) {
224        "clickable".hash(state);
225    }
226}
227
228impl ModifierNodeElement for ClickableElement {
229    type Node = ClickableNode;
230
231    fn create(&self) -> Self::Node {
232        ClickableNode::with_handlers(self.on_press.clone(), Rc::clone(&self.on_click))
233    }
234
235    fn update(&self, node: &mut Self::Node) {
236        node.on_press.clone_from(&self.on_press);
237        node.on_click.clone_from(&self.on_click);
238        node.cached_handler = ClickableNode::create_handler(
239            node.on_press.clone(),
240            Rc::clone(&node.on_click),
241            Rc::clone(&node.press_position),
242        );
243    }
244
245    fn capabilities(&self) -> NodeCapabilities {
246        NodeCapabilities::POINTER_INPUT
247    }
248
249    fn always_update(&self) -> bool {
250        true
251    }
252}