Skip to main content

cranpose_ui/modifier/
pointer_input.rs

1use std::{
2    any::TypeId,
3    cell::{Cell, RefCell},
4    collections::{HashMap, VecDeque},
5    fmt,
6    future::Future,
7    hash::{Hash, Hasher},
8    pin::Pin,
9    rc::Rc,
10    sync::Arc,
11    task::{Context, Poll, Waker},
12};
13
14use cranpose_core::hash::default;
15use cranpose_foundation::{
16    impl_pointer_input_node, DelegatableNode, ModifierNode, ModifierNodeContext,
17    ModifierNodeElement, NodeCapabilities, NodeState, PointerInputNode,
18};
19use cranpose_ui_graphics::Size;
20use futures_task::{waker, ArcWake};
21
22use super::{inspector_metadata, Modifier, PointerEvent};
23
24impl Modifier {
25    /// A suspending gesture handler. It restarts when `key` changes and
26    /// otherwise runs on across recomposition, which is what makes a drag
27    /// survive the frames it spans.
28    ///
29    /// Its identity is the declaration — this call, with these keys — not the
30    /// node it happens to land on. Two branches of an `if` are two different
31    /// gestures even when both pass `()`, and if the slot table hands the
32    /// second branch the first one's node, the arriving gesture is the one
33    /// that has to run: keeping the departed branch's handler would deliver
34    /// its events to code that is no longer on screen.
35    #[track_caller]
36    pub fn pointer_input<K, F, Fut>(self, key: K, handler: F) -> Self
37    where
38        K: Hash + 'static,
39        F: Fn(PointerInputScope) -> Fut + 'static,
40        Fut: Future<Output = ()> + 'static,
41    {
42        let element = PointerInputElement::new(
43            KeyToken::declaration_site(std::panic::Location::caller()),
44            vec![KeyToken::new(&key)],
45            pointer_input_handler(handler),
46        );
47        let key_count = element.key_count();
48        let handler_id = element.handler_id();
49        self.then(
50            Self::with_element(element).with_inspector_metadata(inspector_metadata(
51                "pointerInput",
52                move |info| {
53                    info.add_property("keyCount", key_count.to_string());
54                    info.add_property("handlerId", handler_id.to_string());
55                },
56            )),
57        )
58    }
59}
60
61fn pointer_input_handler<F, Fut>(handler: F) -> PointerInputHandler
62where
63    F: Fn(PointerInputScope) -> Fut + 'static,
64    Fut: Future<Output = ()> + 'static,
65{
66    Rc::new(move |scope| Box::pin(handler(scope.clone())))
67}
68
69type PointerInputFuture = Pin<Box<dyn Future<Output = ()>>>;
70type PointerInputHandler = Rc<dyn Fn(PointerInputScope) -> PointerInputFuture>;
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub(crate) enum PointerInputTaskOwner {
74    App(crate::render_state::AppContextId),
75}
76
77pub(crate) struct PointerInputTaskRegistry {
78    tasks: RefCell<HashMap<u64, Rc<PointerInputTaskInner>>>,
79}
80
81impl PointerInputTaskRegistry {
82    pub(crate) fn new() -> Self {
83        Self {
84            tasks: RefCell::new(HashMap::new()),
85        }
86    }
87
88    pub(crate) fn insert(&self, task_id: u64, task: Rc<PointerInputTaskInner>) {
89        self.tasks.borrow_mut().insert(task_id, task);
90    }
91
92    pub(crate) fn remove(&self, task_id: u64) {
93        self.tasks.borrow_mut().remove(&task_id);
94    }
95
96    pub(crate) fn request_poll(&self, task_id: u64, owner: PointerInputTaskOwner) {
97        if let Some(task) = self.tasks.borrow().get(&task_id).cloned() {
98            task.request_poll(owner, task_id);
99        }
100    }
101}
102
103#[derive(Clone)]
104struct PointerInputElement {
105    /// Where the gesture was declared. Part of its identity, and not one of
106    /// the `keys` the caller passed, which are what `keyCount` reports.
107    site: KeyToken,
108    keys: Vec<KeyToken>,
109    handler: PointerInputHandler,
110    handler_id: u64,
111}
112
113impl PointerInputElement {
114    fn new(site: KeyToken, keys: Vec<KeyToken>, handler: PointerInputHandler) -> Self {
115        let handler_id = pointer_handler_identity(&handler);
116        Self {
117            site,
118            keys,
119            handler,
120            handler_id,
121        }
122    }
123
124    fn key_count(&self) -> usize {
125        self.keys.len()
126    }
127
128    fn handler_id(&self) -> u64 {
129        self.handler_id
130    }
131}
132
133fn pointer_handler_identity(handler: &PointerInputHandler) -> u64 {
134    Rc::as_ptr(handler) as *const () as usize as u64
135}
136
137impl fmt::Debug for PointerInputElement {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        f.debug_struct("PointerInputElement")
140            .field("site", &self.site)
141            .field("keys", &self.keys)
142            .field("handler", &Rc::as_ptr(&self.handler))
143            .field("handler_id", &self.handler_id)
144            .finish()
145    }
146}
147
148impl PartialEq for PointerInputElement {
149    fn eq(&self, other: &Self) -> bool {
150        // Only compare keys, not handler_id. In Compose, elements are equal if their
151        // keys match, even if the handler closure is recreated on recomposition.
152        // This ensures nodes are reused instead of being dropped and recreated.
153        self.site == other.site && self.keys == other.keys
154    }
155}
156
157impl Eq for PointerInputElement {}
158
159impl Hash for PointerInputElement {
160    fn hash<H: Hasher>(&self, state: &mut H) {
161        // Only hash keys, not handler_id. This ensures stable hashing across
162        // recompositions when the closure is recreated but keys remain the same.
163        self.site.hash(state);
164        self.keys.hash(state);
165    }
166}
167
168impl ModifierNodeElement for PointerInputElement {
169    type Node = SuspendingPointerInputNode;
170
171    fn create(&self) -> Self::Node {
172        SuspendingPointerInputNode::new(self.site, self.keys.clone(), self.handler.clone())
173    }
174
175    fn update(&self, node: &mut Self::Node) {
176        node.update(self.site, self.keys.clone(), self.handler.clone());
177    }
178
179    fn capabilities(&self) -> NodeCapabilities {
180        NodeCapabilities::POINTER_INPUT
181    }
182}
183
184#[derive(Clone)]
185pub struct PointerInputScope {
186    state: Rc<PointerInputScopeState>,
187}
188
189impl PointerInputScope {
190    fn new(state: Rc<PointerInputScopeState>) -> Self {
191        Self { state }
192    }
193
194    /// The size of the layout node this handler is attached to, in the same
195    /// local coordinate space as the [`PointerEvent`] positions the scope
196    /// delivers (origin at the node's top-left).
197    ///
198    /// The layout pass publishes the node's resolved size every pass, so this
199    /// is current from the first laid-out frame onwards — including before any
200    /// pointer event has arrived. It is `0x0` only while the node has never
201    /// been laid out.
202    pub fn size(&self) -> Size {
203        self.state.size.get()
204    }
205
206    pub async fn await_pointer_event_scope<R, F, Fut>(&self, block: F) -> R
207    where
208        F: FnOnce(AwaitPointerEventScope) -> Fut,
209        Fut: Future<Output = R>,
210    {
211        let scope = AwaitPointerEventScope {
212            state: self.state.clone(),
213        };
214        block(scope).await
215    }
216}
217
218#[derive(Clone)]
219pub struct AwaitPointerEventScope {
220    state: Rc<PointerInputScopeState>,
221}
222
223impl AwaitPointerEventScope {
224    /// The size of the layout node this handler is attached to. See
225    /// [`PointerInputScope::size`].
226    pub fn size(&self) -> Size {
227        self.state.size.get()
228    }
229
230    pub async fn await_pointer_event(&self) -> PointerEvent {
231        NextPointerEvent {
232            state: self.state.clone(),
233        }
234        .await
235    }
236}
237
238struct NextPointerEvent {
239    state: Rc<PointerInputScopeState>,
240}
241
242impl Future for NextPointerEvent {
243    type Output = PointerEvent;
244
245    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
246        self.state.poll_event(cx)
247    }
248}
249
250struct PointerInputScopeState {
251    events: RefCell<VecDeque<PointerEvent>>,
252    waiting: RefCell<Option<Waker>>,
253    /// Shared with the owning [`SuspendingPointerInputNode`] (and therefore
254    /// with every scope the node ever hands out): the layout pass publishes the
255    /// node's resolved size into this cell, so `scope.size()` reports live
256    /// dimensions rather than the `0x0` a per-scope cell would be stuck at.
257    size: Rc<Cell<Size>>,
258}
259
260impl PointerInputScopeState {
261    fn new(size: Rc<Cell<Size>>) -> Self {
262        Self {
263            events: RefCell::new(VecDeque::new()),
264            waiting: RefCell::new(None),
265            size,
266        }
267    }
268
269    fn push_event(&self, event: PointerEvent) {
270        self.events.borrow_mut().push_back(event);
271        let waker = {
272            let mut waiting = self.waiting.borrow_mut();
273            waiting.take()
274        };
275        if let Some(waker) = waker {
276            waker.wake();
277        }
278    }
279
280    fn poll_event(&self, cx: &mut Context<'_>) -> Poll<PointerEvent> {
281        if let Some(event) = self.events.borrow_mut().pop_front() {
282            Poll::Ready(event)
283        } else {
284            self.waiting.replace(Some(cx.waker().clone()));
285            Poll::Pending
286        }
287    }
288}
289
290struct PointerEventDispatcher {
291    state: Rc<RefCell<Option<Rc<PointerInputScopeState>>>>,
292    handler: Rc<dyn Fn(PointerEvent)>,
293}
294
295impl PointerEventDispatcher {
296    fn new() -> Self {
297        let state = Rc::new(RefCell::new(None::<Rc<PointerInputScopeState>>));
298        let state_for_handler = state.clone();
299        let handler = Rc::new(move |event: PointerEvent| {
300            if let Some(inner) = state_for_handler.borrow().as_ref() {
301                inner.push_event(event);
302            }
303        });
304        Self { state, handler }
305    }
306
307    fn handler(&self) -> Rc<dyn Fn(PointerEvent)> {
308        self.handler.clone()
309    }
310
311    fn set_state(&self, state: Option<Rc<PointerInputScopeState>>) {
312        *self.state.borrow_mut() = state;
313    }
314}
315
316struct PointerInputTask {
317    id: u64,
318    owner: PointerInputTaskOwner,
319    inner: Rc<PointerInputTaskInner>,
320}
321
322impl PointerInputTask {
323    fn new(future: PointerInputFuture) -> Self {
324        let inner = Rc::new(PointerInputTaskInner::new(future));
325        let id = Rc::as_ptr(&inner) as usize as u64;
326        let owner = crate::render_state::register_pointer_input_task(id, inner.clone());
327        Self { id, owner, inner }
328    }
329
330    fn poll(&self) {
331        self.inner.poll(self.owner, self.id);
332    }
333
334    fn cancel(self) {
335        self.inner.cancel();
336        crate::render_state::remove_pointer_input_task(self.owner, self.id);
337    }
338}
339
340impl Drop for PointerInputTask {
341    fn drop(&mut self) {
342        self.inner.cancel();
343        crate::render_state::remove_pointer_input_task(self.owner, self.id);
344    }
345}
346
347pub(crate) struct PointerInputTaskInner {
348    future: RefCell<Option<PointerInputFuture>>,
349    is_polling: Cell<bool>,
350    needs_poll: Cell<bool>,
351}
352
353impl PointerInputTaskInner {
354    fn new(future: PointerInputFuture) -> Self {
355        Self {
356            future: RefCell::new(Some(future)),
357            is_polling: Cell::new(false),
358            needs_poll: Cell::new(false),
359        }
360    }
361
362    fn cancel(&self) {
363        self.future.borrow_mut().take();
364    }
365
366    fn request_poll(&self, owner: PointerInputTaskOwner, task_id: u64) {
367        if self.is_polling.get() {
368            self.needs_poll.set(true);
369        } else {
370            self.poll(owner, task_id);
371        }
372    }
373
374    fn poll(&self, owner: PointerInputTaskOwner, task_id: u64) {
375        if self.is_polling.replace(true) {
376            self.needs_poll.set(true);
377            return;
378        }
379        loop {
380            self.needs_poll.set(false);
381            let waker = waker(Arc::new(PointerInputTaskWaker { task_id, owner }));
382            let mut cx = Context::from_waker(&waker);
383            let mut future_slot = self.future.borrow_mut();
384            if let Some(future) = future_slot.as_mut() {
385                let poll_result = future.as_mut().poll(&mut cx);
386                if poll_result.is_ready() {
387                    future_slot.take();
388                }
389            }
390            if !self.needs_poll.get() {
391                break;
392            }
393        }
394        self.is_polling.set(false);
395    }
396}
397
398struct PointerInputTaskWaker {
399    task_id: u64,
400    owner: PointerInputTaskOwner,
401}
402
403impl ArcWake for PointerInputTaskWaker {
404    fn wake_by_ref(arc_self: &Arc<Self>) {
405        crate::render_state::request_pointer_input_task_poll(arc_self.owner, arc_self.task_id);
406    }
407}
408
409pub struct SuspendingPointerInputNode {
410    /// Which `pointer_input` call the running gesture belongs to. A node that
411    /// is handed to a different declaration is running the wrong gesture.
412    site: KeyToken,
413    keys: Vec<KeyToken>,
414    handler: PointerInputHandler,
415    dispatcher: PointerEventDispatcher,
416    task: Option<PointerInputTask>,
417    /// The node's resolved layout size, published by the layout pass through
418    /// [`PointerInputNode::layout_size_sink`]. Lives on the node rather than on
419    /// the scope state so it survives handler restarts (a key change recreates
420    /// the scope but not the node, and the size has not changed).
421    layout_size: Rc<Cell<Size>>,
422    state: NodeState,
423}
424
425impl SuspendingPointerInputNode {
426    fn new(site: KeyToken, keys: Vec<KeyToken>, handler: PointerInputHandler) -> Self {
427        Self {
428            site,
429            keys,
430            handler,
431            dispatcher: PointerEventDispatcher::new(),
432            task: None,
433            layout_size: Rc::new(Cell::new(Size {
434                width: 0.0,
435                height: 0.0,
436            })),
437            state: NodeState::new(),
438        }
439    }
440
441    fn update(&mut self, site: KeyToken, keys: Vec<KeyToken>, handler: PointerInputHandler) {
442        // Only restart if keys changed - not if handler Rc pointer changed.
443        // In Compose, closures are recreated every composition but the task should
444        // continue running as long as the keys are the same. This matches Jetpack
445        // Compose behavior where rememberUpdatedState keeps the task alive.
446        //
447        // A different declaration site is a different gesture, whatever its
448        // keys say: the node has been handed from one `pointer_input` call to
449        // another, and the arriving one is the gesture that is on screen.
450        let should_restart = self.site != site || self.keys != keys;
451        self.site = site;
452        self.keys = keys;
453        self.handler = handler; // Update handler even if not restarting
454        if should_restart {
455            self.restart();
456        }
457    }
458
459    fn restart(&mut self) {
460        self.cancel();
461        self.start();
462    }
463
464    fn start(&mut self) {
465        let state = Rc::new(PointerInputScopeState::new(self.layout_size.clone()));
466        self.dispatcher.set_state(Some(state.clone()));
467        let scope = PointerInputScope::new(state);
468        let future = (self.handler)(scope);
469        let task = PointerInputTask::new(future);
470        task.poll();
471        self.task = Some(task);
472    }
473
474    fn cancel(&mut self) {
475        if let Some(task) = self.task.take() {
476            task.cancel();
477        }
478        self.dispatcher.set_state(None);
479    }
480}
481
482impl Drop for SuspendingPointerInputNode {
483    fn drop(&mut self) {
484        self.cancel();
485    }
486}
487
488impl ModifierNode for SuspendingPointerInputNode {
489    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
490        self.start();
491    }
492
493    fn on_detach(&mut self) {
494        self.cancel();
495    }
496
497    fn on_reset(&mut self) {
498        // Don't restart on reset - only restart when keys/handler actually change
499        // (which is handled by update() method). Restarting here would kill the
500        // active task and lose its registered waker, preventing events from being delivered.
501    }
502
503    // Capability-driven implementation using helper macro
504    impl_pointer_input_node!();
505}
506
507impl DelegatableNode for SuspendingPointerInputNode {
508    fn node_state(&self) -> &NodeState {
509        &self.state
510    }
511}
512
513impl PointerInputNode for SuspendingPointerInputNode {
514    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
515        Some(self.dispatcher.handler())
516    }
517
518    fn layout_size_sink(&self) -> Option<Rc<Cell<Size>>> {
519        Some(self.layout_size.clone())
520    }
521}
522
523#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
524struct KeyToken {
525    type_id: TypeId,
526    hash: u64,
527}
528
529impl KeyToken {
530    fn declaration_site(site: &'static std::panic::Location<'static>) -> Self {
531        Self::new(&(site.file(), site.line(), site.column()))
532    }
533
534    fn new<T: Hash + 'static>(value: &T) -> Self {
535        let mut hasher = default::new();
536        value.hash(&mut hasher);
537        Self {
538            type_id: TypeId::of::<T>(),
539            hash: hasher.finish(),
540        }
541    }
542}