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}