Skip to main content

agg_gui/
cursor.rs

1//! Cursor icon type and global cursor state.
2//!
3//! Widgets call [`set_cursor_icon`] in their `on_event` handler when hovered.
4//! The platform harness calls [`current_cursor_icon`] after each mouse-move
5//! dispatch and applies it to the OS window or browser canvas.
6//!
7//! The framework resets the cursor to [`CursorIcon::Default`] before each
8//! mouse-move dispatch so the deepest hovered widget always wins without any
9//! explicit reset in widget code.
10
11use std::cell::Cell;
12
13/// Logical cursor shape — mirrors egui's `CursorIcon` for portability.
14///
15/// Variants map 1-to-1 to CSS cursor names and to winit's `CursorIcon`.
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub enum CursorIcon {
18    /// Normal OS arrow cursor.
19    #[default]
20    Default,
21    /// Hide the cursor entirely.
22    None,
23    /// A context menu is available (e.g. right-click target).
24    ContextMenu,
25    /// Question mark — hover for help.
26    Help,
27    /// Pointing hand, used for links and clickable items.
28    PointingHand,
29    /// Processing in progress, but the app is still interactive.
30    Progress,
31    /// Not yet ready — try later.
32    Wait,
33    /// Hover a cell in a table.
34    Cell,
35    /// For precision work (e.g. image editors).
36    Crosshair,
37    /// Text insertion caret.
38    Text,
39    /// Vertical text insertion caret.
40    VerticalText,
41    /// Alias / shortcut.
42    Alias,
43    /// Indicates that a copy will be made.
44    Copy,
45    /// Omnidirectional move.
46    Move,
47    /// Cannot drop here.
48    NoDrop,
49    /// Forbidden / not allowed.
50    NotAllowed,
51    /// The item under the cursor can be grabbed.
52    Grab,
53    /// Currently grabbing the item.
54    Grabbing,
55    /// Can scroll in any direction.
56    AllScroll,
57    /// Horizontal resize (left ↔ right).
58    ResizeHorizontal,
59    /// Diagonal resize `/` (NE ↔ SW).
60    ResizeNeSw,
61    /// Diagonal resize `\` (NW ↔ SE).
62    ResizeNwSe,
63    /// Vertical resize (up ↕ down).
64    ResizeVertical,
65    /// Resize rightwards.
66    ResizeEast,
67    /// Resize down-right.
68    ResizeSouthEast,
69    /// Resize downwards.
70    ResizeSouth,
71    /// Resize down-left.
72    ResizeSouthWest,
73    /// Resize leftwards.
74    ResizeWest,
75    /// Resize up-left.
76    ResizeNorthWest,
77    /// Resize upwards.
78    ResizeNorth,
79    /// Resize up-right.
80    ResizeNorthEast,
81    /// Resize a column.
82    ResizeColumn,
83    /// Resize a row.
84    ResizeRow,
85    /// Zoom in.
86    ZoomIn,
87    /// Zoom out.
88    ZoomOut,
89}
90
91impl CursorIcon {
92    /// CSS cursor value string for this icon (used by the WASM platform layer).
93    pub fn to_css(self) -> &'static str {
94        match self {
95            Self::Default => "default",
96            Self::None => "none",
97            Self::ContextMenu => "context-menu",
98            Self::Help => "help",
99            Self::PointingHand => "pointer",
100            Self::Progress => "progress",
101            Self::Wait => "wait",
102            Self::Cell => "cell",
103            Self::Crosshair => "crosshair",
104            Self::Text => "text",
105            Self::VerticalText => "vertical-text",
106            Self::Alias => "alias",
107            Self::Copy => "copy",
108            Self::Move => "move",
109            Self::NoDrop => "no-drop",
110            Self::NotAllowed => "not-allowed",
111            Self::Grab => "grab",
112            Self::Grabbing => "grabbing",
113            Self::AllScroll => "all-scroll",
114            Self::ResizeHorizontal => "ew-resize",
115            Self::ResizeNeSw => "nesw-resize",
116            Self::ResizeNwSe => "nwse-resize",
117            Self::ResizeVertical => "ns-resize",
118            Self::ResizeEast => "e-resize",
119            Self::ResizeSouthEast => "se-resize",
120            Self::ResizeSouth => "s-resize",
121            Self::ResizeSouthWest => "sw-resize",
122            Self::ResizeWest => "w-resize",
123            Self::ResizeNorthWest => "nw-resize",
124            Self::ResizeNorth => "n-resize",
125            Self::ResizeNorthEast => "ne-resize",
126            Self::ResizeColumn => "col-resize",
127            Self::ResizeRow => "row-resize",
128            Self::ZoomIn => "zoom-in",
129            Self::ZoomOut => "zoom-out",
130        }
131    }
132}
133
134thread_local! {
135    static CURSOR_ICON: Cell<CursorIcon> = Cell::new(CursorIcon::Default);
136}
137
138/// Set the cursor icon for this frame.
139///
140/// Widgets call this in their [`MouseMove`][crate::Event::MouseMove] handler.
141/// The cursor is automatically reset to [`CursorIcon::Default`] before each
142/// mouse-move dispatch.
143pub fn set_cursor_icon(icon: CursorIcon) {
144    CURSOR_ICON.with(|c| c.set(icon));
145}
146
147/// Read the cursor icon set by widgets during the current frame.
148///
149/// Called by the platform harness after each `on_mouse_move` dispatch.
150pub fn current_cursor_icon() -> CursorIcon {
151    CURSOR_ICON.with(|c| c.get())
152}
153
154/// Reset to [`CursorIcon::Default`].
155///
156/// Called by the framework before each mouse-move dispatch so widgets can
157/// opt-in to a custom cursor without needing to opt-out.
158pub fn reset_cursor_icon() {
159    CURSOR_ICON.with(|c| c.set(CursorIcon::Default));
160}