Skip to main content

cranpose_ui/modifier/
pointer_input.rs

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