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}