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}