cranpose_ui/modifier_nodes/
clickable.rs1use 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#[derive(Clone, Copy, Debug)]
17enum ClickPress {
18 Idle,
19 Pressed(Point, PointerId),
21 Cancelled,
24}
25
26pub 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 pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
47 Self::with_handler(Rc::new(on_click))
48 }
49
50 pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
52 Self::with_handlers(None, on_click)
53 }
54
55 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 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 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#[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 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 pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
192 Self {
193 on_press: None,
194 on_click,
195 }
196 }
197
198 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}