Skip to main content

cranpose_ui/
mouse_input.rs

1//! App-scoped mouse movement through the host's normal input dispatch.
2
3use std::{
4    cell::RefCell,
5    rc::{Rc, Weak},
6};
7
8use cranpose_core::{RuntimeHandle, current_runtime_handle};
9use cranpose_ui_graphics::Point;
10use smallvec::SmallVec;
11
12use crate::render_state::{AppContext, require_current_app_context};
13
14/// The surface that receives an injected mouse sample.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum MouseInputTarget {
17    /// The app's primary window.
18    Primary,
19    /// The surface identified by `Modifier::window_root`.
20    Window(u64),
21}
22
23/// A mouse movement waiting for the host to dispatch it.
24#[doc(hidden)]
25#[derive(Clone, Copy)]
26pub struct MouseMoveRequest {
27    pub target: MouseInputTarget,
28    pub position: Point,
29}
30
31#[derive(Default)]
32pub(crate) struct MouseInputQueue(RefCell<SmallVec<[MouseMoveRequest; 2]>>);
33
34impl MouseInputQueue {
35    fn push(&self, request: MouseMoveRequest) {
36        let mut pending = self.0.borrow_mut();
37        if let Some(previous) = pending
38            .iter_mut()
39            .find(|item| item.target == request.target)
40        {
41            *previous = request;
42        } else {
43            pending.push(request);
44        }
45    }
46
47    pub(crate) fn take(&self) -> Option<MouseMoveRequest> {
48        let mut pending = self.0.borrow_mut();
49        if pending.is_empty() {
50            None
51        } else {
52            Some(pending.remove(0))
53        }
54    }
55
56    pub(crate) fn len(&self) -> usize {
57        self.0.borrow().len()
58    }
59}
60
61/// Sends mouse movements to the app that created this handle.
62///
63/// Requests made before a frame are coalesced to the last position per surface.
64/// The host dispatches them through ordinary hit testing, enter/exit handling,
65/// and active pointer capture. This does not move the operating system cursor.
66#[derive(Clone)]
67pub struct MouseInput {
68    context: Weak<AppContext>,
69    runtime: Option<RuntimeHandle>,
70}
71
72impl MouseInput {
73    /// Requests a mouse movement in the target surface's logical coordinates.
74    /// Graphics-layer transforms must already have been applied to `position`.
75    /// Non-finite positions and requests after the app closes are ignored.
76    pub fn move_to(&self, target: MouseInputTarget, position: Point) {
77        if !position.x.is_finite() || !position.y.is_finite() {
78            return;
79        }
80        if let Some(context) = self.context.upgrade() {
81            context
82                .mouse_input()
83                .push(MouseMoveRequest { target, position });
84            if let Some(runtime) = &self.runtime {
85                runtime.schedule();
86            }
87        }
88    }
89}
90
91/// Returns a mouse-input handle for the current app context.
92/// Obtain it during composition so requests also wake that app's runtime.
93pub fn local_mouse_input() -> MouseInput {
94    MouseInput {
95        context: Rc::downgrade(&require_current_app_context("mouse input")),
96        runtime: current_runtime_handle(),
97    }
98}