Skip to content

Commit c54ddfe

Browse files
authored
feat: expose window cursor APIs, closes #3888 #3890 (#3935)
1 parent 76950e9 commit c54ddfe

File tree

19 files changed

+743
-128
lines changed

19 files changed

+743
-128
lines changed

.changes/api-window-cursor.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"api": patch
3+
---
4+
5+
Added the `setCursorGrab`, `setCursorVisible`, `setCursorIcon` and `setCursorPosition` methods to the `WebviewWindow` class.

.changes/cursor-apis.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
"tauri-runtime-wry": patch
5+
---
6+
7+
Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tauri_runtime::{
1414
webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
1515
window::{
1616
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
17-
DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
17+
CursorIcon, DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
1818
},
1919
ClipboardManager, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction,
2020
GlobalShortcutManager, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
@@ -63,7 +63,7 @@ use wry::{
6363
},
6464
monitor::MonitorHandle,
6565
window::{
66-
Fullscreen, Icon as WryWindowIcon, Theme as WryTheme,
66+
CursorIcon as WryCursorIcon, Fullscreen, Icon as WryWindowIcon, Theme as WryTheme,
6767
UserAttentionType as WryUserAttentionType,
6868
},
6969
},
@@ -752,7 +752,7 @@ impl From<Position> for PositionWrapper {
752752
pub struct UserAttentionTypeWrapper(WryUserAttentionType);
753753

754754
impl From<UserAttentionType> for UserAttentionTypeWrapper {
755-
fn from(request_type: UserAttentionType) -> UserAttentionTypeWrapper {
755+
fn from(request_type: UserAttentionType) -> Self {
756756
let o = match request_type {
757757
UserAttentionType::Critical => WryUserAttentionType::Critical,
758758
UserAttentionType::Informational => WryUserAttentionType::Informational,
@@ -761,6 +761,54 @@ impl From<UserAttentionType> for UserAttentionTypeWrapper {
761761
}
762762
}
763763

764+
#[derive(Debug)]
765+
pub struct CursorIconWrapper(WryCursorIcon);
766+
767+
impl From<CursorIcon> for CursorIconWrapper {
768+
fn from(icon: CursorIcon) -> Self {
769+
use CursorIcon::*;
770+
let i = match icon {
771+
Default => WryCursorIcon::Default,
772+
Crosshair => WryCursorIcon::Crosshair,
773+
Hand => WryCursorIcon::Hand,
774+
Arrow => WryCursorIcon::Arrow,
775+
Move => WryCursorIcon::Move,
776+
Text => WryCursorIcon::Text,
777+
Wait => WryCursorIcon::Wait,
778+
Help => WryCursorIcon::Help,
779+
Progress => WryCursorIcon::Progress,
780+
NotAllowed => WryCursorIcon::NotAllowed,
781+
ContextMenu => WryCursorIcon::ContextMenu,
782+
Cell => WryCursorIcon::Cell,
783+
VerticalText => WryCursorIcon::VerticalText,
784+
Alias => WryCursorIcon::Alias,
785+
Copy => WryCursorIcon::Copy,
786+
NoDrop => WryCursorIcon::NoDrop,
787+
Grab => WryCursorIcon::Grab,
788+
Grabbing => WryCursorIcon::Grabbing,
789+
AllScroll => WryCursorIcon::AllScroll,
790+
ZoomIn => WryCursorIcon::ZoomIn,
791+
ZoomOut => WryCursorIcon::ZoomOut,
792+
EResize => WryCursorIcon::EResize,
793+
NResize => WryCursorIcon::NResize,
794+
NeResize => WryCursorIcon::NeResize,
795+
NwResize => WryCursorIcon::NwResize,
796+
SResize => WryCursorIcon::SResize,
797+
SeResize => WryCursorIcon::SeResize,
798+
SwResize => WryCursorIcon::SwResize,
799+
WResize => WryCursorIcon::WResize,
800+
EwResize => WryCursorIcon::EwResize,
801+
NsResize => WryCursorIcon::NsResize,
802+
NeswResize => WryCursorIcon::NeswResize,
803+
NwseResize => WryCursorIcon::NwseResize,
804+
ColResize => WryCursorIcon::ColResize,
805+
RowResize => WryCursorIcon::RowResize,
806+
_ => WryCursorIcon::Default,
807+
};
808+
Self(i)
809+
}
810+
}
811+
764812
#[derive(Debug, Clone, Default)]
765813
pub struct WindowBuilderWrapper {
766814
inner: WryWindowBuilder,
@@ -1079,6 +1127,10 @@ pub enum WindowMessage {
10791127
SetFocus,
10801128
SetIcon(WryWindowIcon),
10811129
SetSkipTaskbar(bool),
1130+
SetCursorGrab(bool),
1131+
SetCursorVisible(bool),
1132+
SetCursorIcon(CursorIcon),
1133+
SetCursorPosition(Position),
10821134
DragWindow,
10831135
UpdateMenuItem(u16, MenuUpdate),
10841136
RequestRedraw,
@@ -1505,6 +1557,37 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
15051557
)
15061558
}
15071559

1560+
fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {
1561+
send_user_message(
1562+
&self.context,
1563+
Message::Window(self.window_id, WindowMessage::SetCursorGrab(grab)),
1564+
)
1565+
}
1566+
1567+
fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {
1568+
send_user_message(
1569+
&self.context,
1570+
Message::Window(self.window_id, WindowMessage::SetCursorVisible(visible)),
1571+
)
1572+
}
1573+
1574+
fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {
1575+
send_user_message(
1576+
&self.context,
1577+
Message::Window(self.window_id, WindowMessage::SetCursorIcon(icon)),
1578+
)
1579+
}
1580+
1581+
fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
1582+
send_user_message(
1583+
&self.context,
1584+
Message::Window(
1585+
self.window_id,
1586+
WindowMessage::SetCursorPosition(position.into()),
1587+
),
1588+
)
1589+
}
1590+
15081591
fn start_dragging(&self) -> Result<()> {
15091592
send_user_message(
15101593
&self.context,
@@ -2176,9 +2259,26 @@ fn handle_user_message<T: UserEvent>(
21762259
WindowMessage::SetIcon(icon) => {
21772260
window.set_window_icon(Some(icon));
21782261
}
2179-
WindowMessage::SetSkipTaskbar(_skip) => {
2262+
#[allow(unused_variables)]
2263+
WindowMessage::SetSkipTaskbar(skip) => {
21802264
#[cfg(any(windows, target_os = "linux"))]
2181-
window.set_skip_taskbar(_skip);
2265+
window.set_skip_taskbar(skip);
2266+
}
2267+
#[allow(unused_variables)]
2268+
WindowMessage::SetCursorGrab(grab) => {
2269+
#[cfg(any(windows, target_os = "macos"))]
2270+
let _ = window.set_cursor_grab(grab);
2271+
}
2272+
WindowMessage::SetCursorVisible(visible) => {
2273+
window.set_cursor_visible(visible);
2274+
}
2275+
WindowMessage::SetCursorIcon(icon) => {
2276+
window.set_cursor_icon(CursorIconWrapper::from(icon).0);
2277+
}
2278+
#[allow(unused_variables)]
2279+
WindowMessage::SetCursorPosition(position) => {
2280+
#[cfg(any(windows, target_os = "macos"))]
2281+
let _ = window.set_cursor_position(PositionWrapper::from(position).0);
21822282
}
21832283
WindowMessage::DragWindow => {
21842284
let _ = window.drag_window();

core/tauri-runtime/src/lib.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use monitor::Monitor;
2626
use webview::WindowBuilder;
2727
use window::{
2828
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
29-
DetachedWindow, PendingWindow, WindowEvent,
29+
CursorIcon, DetachedWindow, PendingWindow, WindowEvent,
3030
};
3131

3232
use crate::http::{
@@ -565,6 +565,23 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
565565
/// Whether to show the window icon in the task bar or not.
566566
fn set_skip_taskbar(&self, skip: bool) -> Result<()>;
567567

568+
/// Grabs the cursor, preventing it from leaving the window.
569+
///
570+
/// There's no guarantee that the cursor will be hidden. You should
571+
/// hide it by yourself if you want so.
572+
fn set_cursor_grab(&self, grab: bool) -> Result<()>;
573+
574+
/// Modifies the cursor's visibility.
575+
///
576+
/// If `false`, this will hide the cursor. If `true`, this will show the cursor.
577+
fn set_cursor_visible(&self, visible: bool) -> Result<()>;
578+
579+
// Modifies the cursor icon of the window.
580+
fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()>;
581+
582+
/// Changes the position of the cursor in window coordinates.
583+
fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()>;
584+
568585
/// Starts dragging the window.
569586
fn start_dragging(&self) -> Result<()>;
570587

core/tauri-runtime/src/window.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
webview::{WebviewAttributes, WebviewIpcHandler},
1111
Dispatch, Runtime, UserEvent, WindowBuilder,
1212
};
13-
use serde::Serialize;
13+
use serde::{Deserialize, Deserializer, Serialize};
1414
use tauri_utils::{config::WindowConfig, Theme};
1515

1616
use std::{
@@ -98,6 +98,118 @@ fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &Menu) {
9898
}
9999
}
100100

101+
/// Describes the appearance of the mouse cursor.
102+
#[non_exhaustive]
103+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
104+
pub enum CursorIcon {
105+
/// The platform-dependent default cursor.
106+
Default,
107+
/// A simple crosshair.
108+
Crosshair,
109+
/// A hand (often used to indicate links in web browsers).
110+
Hand,
111+
/// Self explanatory.
112+
Arrow,
113+
/// Indicates something is to be moved.
114+
Move,
115+
/// Indicates text that may be selected or edited.
116+
Text,
117+
/// Program busy indicator.
118+
Wait,
119+
/// Help indicator (often rendered as a "?")
120+
Help,
121+
/// Progress indicator. Shows that processing is being done. But in contrast
122+
/// with "Wait" the user may still interact with the program. Often rendered
123+
/// as a spinning beach ball, or an arrow with a watch or hourglass.
124+
Progress,
125+
126+
/// Cursor showing that something cannot be done.
127+
NotAllowed,
128+
ContextMenu,
129+
Cell,
130+
VerticalText,
131+
Alias,
132+
Copy,
133+
NoDrop,
134+
/// Indicates something can be grabbed.
135+
Grab,
136+
/// Indicates something is grabbed.
137+
Grabbing,
138+
AllScroll,
139+
ZoomIn,
140+
ZoomOut,
141+
142+
/// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
143+
/// is used when the movement starts from the south-east corner of the box.
144+
EResize,
145+
NResize,
146+
NeResize,
147+
NwResize,
148+
SResize,
149+
SeResize,
150+
SwResize,
151+
WResize,
152+
EwResize,
153+
NsResize,
154+
NeswResize,
155+
NwseResize,
156+
ColResize,
157+
RowResize,
158+
}
159+
160+
impl<'de> Deserialize<'de> for CursorIcon {
161+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162+
where
163+
D: Deserializer<'de>,
164+
{
165+
let s = String::deserialize(deserializer)?;
166+
Ok(match s.to_lowercase().as_str() {
167+
"default" => CursorIcon::Default,
168+
"crosshair" => CursorIcon::Crosshair,
169+
"hand" => CursorIcon::Hand,
170+
"arrow" => CursorIcon::Arrow,
171+
"move" => CursorIcon::Move,
172+
"text" => CursorIcon::Text,
173+
"wait" => CursorIcon::Wait,
174+
"help" => CursorIcon::Help,
175+
"progress" => CursorIcon::Progress,
176+
"notallowed" => CursorIcon::NotAllowed,
177+
"contextmenu" => CursorIcon::ContextMenu,
178+
"cell" => CursorIcon::Cell,
179+
"verticaltext" => CursorIcon::VerticalText,
180+
"alias" => CursorIcon::Alias,
181+
"copy" => CursorIcon::Copy,
182+
"nodrop" => CursorIcon::NoDrop,
183+
"grab" => CursorIcon::Grab,
184+
"grabbing" => CursorIcon::Grabbing,
185+
"allscroll" => CursorIcon::AllScroll,
186+
"zoomun" => CursorIcon::ZoomIn,
187+
"zoomout" => CursorIcon::ZoomOut,
188+
"eresize" => CursorIcon::EResize,
189+
"nresize" => CursorIcon::NResize,
190+
"neresize" => CursorIcon::NeResize,
191+
"nwresize" => CursorIcon::NwResize,
192+
"sresize" => CursorIcon::SResize,
193+
"seresize" => CursorIcon::SeResize,
194+
"swresize" => CursorIcon::SwResize,
195+
"wresize" => CursorIcon::WResize,
196+
"ewresize" => CursorIcon::EwResize,
197+
"nsresize" => CursorIcon::NsResize,
198+
"neswresize" => CursorIcon::NeswResize,
199+
"nwseresize" => CursorIcon::NwseResize,
200+
"colresize" => CursorIcon::ColResize,
201+
"rowresize" => CursorIcon::RowResize,
202+
_ => CursorIcon::Default,
203+
})
204+
}
205+
}
206+
207+
impl Default for CursorIcon {
208+
fn default() -> Self {
209+
CursorIcon::Default
210+
}
211+
}
212+
101213
/// A webview window that has yet to be built.
102214
pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
103215
/// The label that the window will be named.

core/tauri/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ window-all = [
230230
"window-set-focus",
231231
"window-set-icon",
232232
"window-set-skip-taskbar",
233+
"window-set-cursor-grab",
234+
"window-set-cursor-visible",
235+
"window-set-cursor-icon",
236+
"window-set-cursor-position",
233237
"window-start-dragging",
234238
"window-print"
235239
]
@@ -255,6 +259,10 @@ window-set-fullscreen = [ ]
255259
window-set-focus = [ ]
256260
window-set-icon = [ ]
257261
window-set-skip-taskbar = [ ]
262+
window-set-cursor-grab = [ ]
263+
window-set-cursor-visible = [ ]
264+
window-set-cursor-icon = [ ]
265+
window-set-cursor-position = [ ]
258266
window-start-dragging = [ ]
259267
window-print = [ ]
260268
config-json5 = [ "tauri-macros/config-json5" ]

core/tauri/build.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn main() {
2929
window_create: { any(window_all, feature = "window-create") },
3030
window_center: { any(window_all, feature = "window-center") },
3131
window_request_user_attention: { any(window_all, feature = "window-request-user-attention") },
32-
window_set_izable: { any(window_all, feature = "window-set-resizable") },
32+
window_set_resizable: { any(window_all, feature = "window-set-resizable") },
3333
window_set_title: { any(window_all, feature = "window-set-title") },
3434
window_maximize: { any(window_all, feature = "window-maximize") },
3535
window_unmaximize: { any(window_all, feature = "window-unmaximize") },
@@ -48,6 +48,10 @@ fn main() {
4848
window_set_focus: { any(window_all, feature = "window-set-focus") },
4949
window_set_icon: { any(window_all, feature = "window-set-icon") },
5050
window_set_skip_taskbar: { any(window_all, feature = "window-set-skip-taskbar") },
51+
window_set_cursor_grab: { any(window_all, feature = "window-set-cursor-grab") },
52+
window_set_cursor_visible: { any(window_all, feature = "window-set-cursor-visible") },
53+
window_set_cursor_icon: { any(window_all, feature = "window-set-cursor-icon") },
54+
window_set_cursor_position: { any(window_all, feature = "window-set-cursor-position") },
5155
window_start_dragging: { any(window_all, feature = "window-start-dragging") },
5256
window_print: { any(window_all, feature = "window-print") },
5357

core/tauri/scripts/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ impl<R: Runtime> Builder<R> {
776776
///
777777
/// ## Platform-specific
778778
///
779-
/// - **macOS**: on macOS the application *must* be executed on the main thread, so this function is not exposed.
779+
/// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed.
780780
#[cfg(any(windows, target_os = "linux"))]
781781
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
782782
#[must_use]

0 commit comments

Comments
 (0)