Parcourir la source

feat: expose window cursor APIs, closes #3888 #3890 (#3935)

Lucas Fernandes Nogueira il y a 3 ans
Parent
commit
c54ddfe933

+ 5 - 0
.changes/api-window-cursor.md

@@ -0,0 +1,5 @@
+---
+"api": patch
+---
+
+Added the `setCursorGrab`, `setCursorVisible`, `setCursorIcon` and `setCursorPosition` methods to the `WebviewWindow` class.

+ 7 - 0
.changes/cursor-apis.md

@@ -0,0 +1,7 @@
+---
+"tauri": patch
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`.

+ 105 - 5
core/tauri-runtime-wry/src/lib.rs

@@ -14,7 +14,7 @@ use tauri_runtime::{
   webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
   window::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
-    DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
+    CursorIcon, DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
   },
   ClipboardManager, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction,
   GlobalShortcutManager, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
@@ -63,7 +63,7 @@ use wry::{
     },
     monitor::MonitorHandle,
     window::{
-      Fullscreen, Icon as WryWindowIcon, Theme as WryTheme,
+      CursorIcon as WryCursorIcon, Fullscreen, Icon as WryWindowIcon, Theme as WryTheme,
       UserAttentionType as WryUserAttentionType,
     },
   },
@@ -752,7 +752,7 @@ impl From<Position> for PositionWrapper {
 pub struct UserAttentionTypeWrapper(WryUserAttentionType);
 
 impl From<UserAttentionType> for UserAttentionTypeWrapper {
-  fn from(request_type: UserAttentionType) -> UserAttentionTypeWrapper {
+  fn from(request_type: UserAttentionType) -> Self {
     let o = match request_type {
       UserAttentionType::Critical => WryUserAttentionType::Critical,
       UserAttentionType::Informational => WryUserAttentionType::Informational,
@@ -761,6 +761,54 @@ impl From<UserAttentionType> for UserAttentionTypeWrapper {
   }
 }
 
+#[derive(Debug)]
+pub struct CursorIconWrapper(WryCursorIcon);
+
+impl From<CursorIcon> for CursorIconWrapper {
+  fn from(icon: CursorIcon) -> Self {
+    use CursorIcon::*;
+    let i = match icon {
+      Default => WryCursorIcon::Default,
+      Crosshair => WryCursorIcon::Crosshair,
+      Hand => WryCursorIcon::Hand,
+      Arrow => WryCursorIcon::Arrow,
+      Move => WryCursorIcon::Move,
+      Text => WryCursorIcon::Text,
+      Wait => WryCursorIcon::Wait,
+      Help => WryCursorIcon::Help,
+      Progress => WryCursorIcon::Progress,
+      NotAllowed => WryCursorIcon::NotAllowed,
+      ContextMenu => WryCursorIcon::ContextMenu,
+      Cell => WryCursorIcon::Cell,
+      VerticalText => WryCursorIcon::VerticalText,
+      Alias => WryCursorIcon::Alias,
+      Copy => WryCursorIcon::Copy,
+      NoDrop => WryCursorIcon::NoDrop,
+      Grab => WryCursorIcon::Grab,
+      Grabbing => WryCursorIcon::Grabbing,
+      AllScroll => WryCursorIcon::AllScroll,
+      ZoomIn => WryCursorIcon::ZoomIn,
+      ZoomOut => WryCursorIcon::ZoomOut,
+      EResize => WryCursorIcon::EResize,
+      NResize => WryCursorIcon::NResize,
+      NeResize => WryCursorIcon::NeResize,
+      NwResize => WryCursorIcon::NwResize,
+      SResize => WryCursorIcon::SResize,
+      SeResize => WryCursorIcon::SeResize,
+      SwResize => WryCursorIcon::SwResize,
+      WResize => WryCursorIcon::WResize,
+      EwResize => WryCursorIcon::EwResize,
+      NsResize => WryCursorIcon::NsResize,
+      NeswResize => WryCursorIcon::NeswResize,
+      NwseResize => WryCursorIcon::NwseResize,
+      ColResize => WryCursorIcon::ColResize,
+      RowResize => WryCursorIcon::RowResize,
+      _ => WryCursorIcon::Default,
+    };
+    Self(i)
+  }
+}
+
 #[derive(Debug, Clone, Default)]
 pub struct WindowBuilderWrapper {
   inner: WryWindowBuilder,
@@ -1079,6 +1127,10 @@ pub enum WindowMessage {
   SetFocus,
   SetIcon(WryWindowIcon),
   SetSkipTaskbar(bool),
+  SetCursorGrab(bool),
+  SetCursorVisible(bool),
+  SetCursorIcon(CursorIcon),
+  SetCursorPosition(Position),
   DragWindow,
   UpdateMenuItem(u16, MenuUpdate),
   RequestRedraw,
@@ -1505,6 +1557,37 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
     )
   }
 
+  fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::SetCursorGrab(grab)),
+    )
+  }
+
+  fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::SetCursorVisible(visible)),
+    )
+  }
+
+  fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::SetCursorIcon(icon)),
+    )
+  }
+
+  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(
+        self.window_id,
+        WindowMessage::SetCursorPosition(position.into()),
+      ),
+    )
+  }
+
   fn start_dragging(&self) -> Result<()> {
     send_user_message(
       &self.context,
@@ -2176,9 +2259,26 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::SetIcon(icon) => {
             window.set_window_icon(Some(icon));
           }
-          WindowMessage::SetSkipTaskbar(_skip) => {
+          #[allow(unused_variables)]
+          WindowMessage::SetSkipTaskbar(skip) => {
             #[cfg(any(windows, target_os = "linux"))]
-            window.set_skip_taskbar(_skip);
+            window.set_skip_taskbar(skip);
+          }
+          #[allow(unused_variables)]
+          WindowMessage::SetCursorGrab(grab) => {
+            #[cfg(any(windows, target_os = "macos"))]
+            let _ = window.set_cursor_grab(grab);
+          }
+          WindowMessage::SetCursorVisible(visible) => {
+            window.set_cursor_visible(visible);
+          }
+          WindowMessage::SetCursorIcon(icon) => {
+            window.set_cursor_icon(CursorIconWrapper::from(icon).0);
+          }
+          #[allow(unused_variables)]
+          WindowMessage::SetCursorPosition(position) => {
+            #[cfg(any(windows, target_os = "macos"))]
+            let _ = window.set_cursor_position(PositionWrapper::from(position).0);
           }
           WindowMessage::DragWindow => {
             let _ = window.drag_window();

+ 18 - 1
core/tauri-runtime/src/lib.rs

@@ -26,7 +26,7 @@ use monitor::Monitor;
 use webview::WindowBuilder;
 use window::{
   dpi::{PhysicalPosition, PhysicalSize, Position, Size},
-  DetachedWindow, PendingWindow, WindowEvent,
+  CursorIcon, DetachedWindow, PendingWindow, WindowEvent,
 };
 
 use crate::http::{
@@ -565,6 +565,23 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   /// Whether to show the window icon in the task bar or not.
   fn set_skip_taskbar(&self, skip: bool) -> Result<()>;
 
+  /// Grabs the cursor, preventing it from leaving the window.
+  ///
+  /// There's no guarantee that the cursor will be hidden. You should
+  /// hide it by yourself if you want so.
+  fn set_cursor_grab(&self, grab: bool) -> Result<()>;
+
+  /// Modifies the cursor's visibility.
+  ///
+  /// If `false`, this will hide the cursor. If `true`, this will show the cursor.
+  fn set_cursor_visible(&self, visible: bool) -> Result<()>;
+
+  // Modifies the cursor icon of the window.
+  fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()>;
+
+  /// Changes the position of the cursor in window coordinates.
+  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()>;
+
   /// Starts dragging the window.
   fn start_dragging(&self) -> Result<()>;
 

+ 113 - 1
core/tauri-runtime/src/window.rs

@@ -10,7 +10,7 @@ use crate::{
   webview::{WebviewAttributes, WebviewIpcHandler},
   Dispatch, Runtime, UserEvent, WindowBuilder,
 };
-use serde::Serialize;
+use serde::{Deserialize, Deserializer, Serialize};
 use tauri_utils::{config::WindowConfig, Theme};
 
 use std::{
@@ -98,6 +98,118 @@ fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &Menu) {
   }
 }
 
+/// Describes the appearance of the mouse cursor.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum CursorIcon {
+  /// The platform-dependent default cursor.
+  Default,
+  /// A simple crosshair.
+  Crosshair,
+  /// A hand (often used to indicate links in web browsers).
+  Hand,
+  /// Self explanatory.
+  Arrow,
+  /// Indicates something is to be moved.
+  Move,
+  /// Indicates text that may be selected or edited.
+  Text,
+  /// Program busy indicator.
+  Wait,
+  /// Help indicator (often rendered as a "?")
+  Help,
+  /// Progress indicator. Shows that processing is being done. But in contrast
+  /// with "Wait" the user may still interact with the program. Often rendered
+  /// as a spinning beach ball, or an arrow with a watch or hourglass.
+  Progress,
+
+  /// Cursor showing that something cannot be done.
+  NotAllowed,
+  ContextMenu,
+  Cell,
+  VerticalText,
+  Alias,
+  Copy,
+  NoDrop,
+  /// Indicates something can be grabbed.
+  Grab,
+  /// Indicates something is grabbed.
+  Grabbing,
+  AllScroll,
+  ZoomIn,
+  ZoomOut,
+
+  /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
+  /// is used when the movement starts from the south-east corner of the box.
+  EResize,
+  NResize,
+  NeResize,
+  NwResize,
+  SResize,
+  SeResize,
+  SwResize,
+  WResize,
+  EwResize,
+  NsResize,
+  NeswResize,
+  NwseResize,
+  ColResize,
+  RowResize,
+}
+
+impl<'de> Deserialize<'de> for CursorIcon {
+  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+  where
+    D: Deserializer<'de>,
+  {
+    let s = String::deserialize(deserializer)?;
+    Ok(match s.to_lowercase().as_str() {
+      "default" => CursorIcon::Default,
+      "crosshair" => CursorIcon::Crosshair,
+      "hand" => CursorIcon::Hand,
+      "arrow" => CursorIcon::Arrow,
+      "move" => CursorIcon::Move,
+      "text" => CursorIcon::Text,
+      "wait" => CursorIcon::Wait,
+      "help" => CursorIcon::Help,
+      "progress" => CursorIcon::Progress,
+      "notallowed" => CursorIcon::NotAllowed,
+      "contextmenu" => CursorIcon::ContextMenu,
+      "cell" => CursorIcon::Cell,
+      "verticaltext" => CursorIcon::VerticalText,
+      "alias" => CursorIcon::Alias,
+      "copy" => CursorIcon::Copy,
+      "nodrop" => CursorIcon::NoDrop,
+      "grab" => CursorIcon::Grab,
+      "grabbing" => CursorIcon::Grabbing,
+      "allscroll" => CursorIcon::AllScroll,
+      "zoomun" => CursorIcon::ZoomIn,
+      "zoomout" => CursorIcon::ZoomOut,
+      "eresize" => CursorIcon::EResize,
+      "nresize" => CursorIcon::NResize,
+      "neresize" => CursorIcon::NeResize,
+      "nwresize" => CursorIcon::NwResize,
+      "sresize" => CursorIcon::SResize,
+      "seresize" => CursorIcon::SeResize,
+      "swresize" => CursorIcon::SwResize,
+      "wresize" => CursorIcon::WResize,
+      "ewresize" => CursorIcon::EwResize,
+      "nsresize" => CursorIcon::NsResize,
+      "neswresize" => CursorIcon::NeswResize,
+      "nwseresize" => CursorIcon::NwseResize,
+      "colresize" => CursorIcon::ColResize,
+      "rowresize" => CursorIcon::RowResize,
+      _ => CursorIcon::Default,
+    })
+  }
+}
+
+impl Default for CursorIcon {
+  fn default() -> Self {
+    CursorIcon::Default
+  }
+}
+
 /// A webview window that has yet to be built.
 pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
   /// The label that the window will be named.

+ 8 - 0
core/tauri/Cargo.toml

@@ -230,6 +230,10 @@ window-all = [
   "window-set-focus",
   "window-set-icon",
   "window-set-skip-taskbar",
+  "window-set-cursor-grab",
+  "window-set-cursor-visible",
+  "window-set-cursor-icon",
+  "window-set-cursor-position",
   "window-start-dragging",
   "window-print"
 ]
@@ -255,6 +259,10 @@ window-set-fullscreen = [ ]
 window-set-focus = [ ]
 window-set-icon = [ ]
 window-set-skip-taskbar = [ ]
+window-set-cursor-grab = [ ]
+window-set-cursor-visible = [ ]
+window-set-cursor-icon = [ ]
+window-set-cursor-position = [ ]
 window-start-dragging = [ ]
 window-print = [ ]
 config-json5 = [ "tauri-macros/config-json5" ]

+ 5 - 1
core/tauri/build.rs

@@ -29,7 +29,7 @@ fn main() {
     window_create: { any(window_all, feature = "window-create") },
     window_center: { any(window_all, feature = "window-center") },
     window_request_user_attention: { any(window_all, feature = "window-request-user-attention") },
-    window_set_izable: { any(window_all, feature = "window-set-resizable") },
+    window_set_resizable: { any(window_all, feature = "window-set-resizable") },
     window_set_title: { any(window_all, feature = "window-set-title") },
     window_maximize: { any(window_all, feature = "window-maximize") },
     window_unmaximize: { any(window_all, feature = "window-unmaximize") },
@@ -48,6 +48,10 @@ fn main() {
     window_set_focus: { any(window_all, feature = "window-set-focus") },
     window_set_icon: { any(window_all, feature = "window-set-icon") },
     window_set_skip_taskbar: { any(window_all, feature = "window-set-skip-taskbar") },
+    window_set_cursor_grab: { any(window_all, feature = "window-set-cursor-grab") },
+    window_set_cursor_visible: { any(window_all, feature = "window-set-cursor-visible") },
+    window_set_cursor_icon: { any(window_all, feature = "window-set-cursor-icon") },
+    window_set_cursor_position: { any(window_all, feature = "window-set-cursor-position") },
     window_start_dragging: { any(window_all, feature = "window-start-dragging") },
     window_print: { any(window_all, feature = "window-print") },
 

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
core/tauri/scripts/bundle.js


+ 1 - 1
core/tauri/src/app.rs

@@ -776,7 +776,7 @@ impl<R: Runtime> Builder<R> {
   ///
   /// ## Platform-specific
   ///
-  /// - **macOS**: on macOS the application *must* be executed on the main thread, so this function is not exposed.
+  /// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed.
   #[cfg(any(windows, target_os = "linux"))]
   #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
   #[must_use]

+ 25 - 1
core/tauri/src/endpoints/window.rs

@@ -11,7 +11,7 @@ use crate::{
     UserAttentionType,
   },
   utils::config::WindowConfig,
-  Icon, Manager, Runtime,
+  CursorIcon, Icon, Manager, Runtime,
 };
 use serde::Deserialize;
 use tauri_macros::{module_command_handler, CommandModule};
@@ -95,6 +95,10 @@ pub enum WindowManagerCmd {
     icon: IconDto,
   },
   SetSkipTaskbar(bool),
+  SetCursorGrab(bool),
+  SetCursorVisible(bool),
+  SetCursorIcon(CursorIcon),
+  SetCursorPosition(Position),
   StartDragging,
   Print,
   // internals
@@ -141,6 +145,18 @@ impl WindowManagerCmd {
       Self::SetSkipTaskbar(_) => {
         crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string())
       }
+      Self::SetCursorGrab(_) => {
+        crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string())
+      }
+      Self::SetCursorVisible(_) => {
+        crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string())
+      }
+      Self::SetCursorIcon(_) => {
+        crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string())
+      }
+      Self::SetCursorPosition(_) => {
+        crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string())
+      }
       Self::StartDragging => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()),
       Self::Print => crate::Error::ApiNotAllowlisted("window > print".to_string()),
       Self::InternalToggleMaximize => {
@@ -270,6 +286,14 @@ impl Cmd {
       WindowManagerCmd::SetIcon { icon } => window.set_icon(icon.into())?,
       #[cfg(window_set_skip_taskbar)]
       WindowManagerCmd::SetSkipTaskbar(skip) => window.set_skip_taskbar(skip)?,
+      #[cfg(window_set_cursor_grab)]
+      WindowManagerCmd::SetCursorGrab(grab) => window.set_cursor_grab(grab)?,
+      #[cfg(window_set_cursor_visible)]
+      WindowManagerCmd::SetCursorVisible(visible) => window.set_cursor_visible(visible)?,
+      #[cfg(window_set_cursor_icon)]
+      WindowManagerCmd::SetCursorIcon(icon) => window.set_cursor_icon(icon)?,
+      #[cfg(window_set_cursor_position)]
+      WindowManagerCmd::SetCursorPosition(position) => window.set_cursor_position(position)?,
       #[cfg(window_start_dragging)]
       WindowManagerCmd::StartDragging => window.start_dragging()?,
       #[cfg(window_print)]

+ 1 - 1
core/tauri/src/lib.rs

@@ -214,7 +214,7 @@ pub use {
     webview::WebviewAttributes,
     window::{
       dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
-      FileDropEvent,
+      CursorIcon, FileDropEvent,
     },
     ClipboardManager, GlobalShortcutManager, RunIteration, TrayIcon, UserAttentionType,
   },

+ 17 - 1
core/tauri/src/test/mock_runtime.rs

@@ -10,7 +10,7 @@ use tauri_runtime::{
   webview::{WindowBuilder, WindowBuilderBase},
   window::{
     dpi::{PhysicalPosition, PhysicalSize, Position, Size},
-    DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
+    CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
   },
   ClipboardManager, Dispatch, EventLoopProxy, GlobalShortcutManager, Result, RunEvent, Runtime,
   RuntimeHandle, UserAttentionType, UserEvent, WindowIcon,
@@ -479,6 +479,22 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
     Ok(())
   }
 
+  fn set_cursor_grab(&self, grab: bool) -> Result<()> {
+    Ok(())
+  }
+
+  fn set_cursor_visible(&self, visible: bool) -> Result<()> {
+    Ok(())
+  }
+
+  fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()> {
+    Ok(())
+  }
+
+  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()> {
+    Ok(())
+  }
+
   fn start_dragging(&self) -> Result<()> {
     Ok(())
   }

+ 55 - 2
core/tauri/src/window.rs

@@ -28,7 +28,7 @@ use crate::{
   sealed::ManagerBase,
   sealed::RuntimeOrDispatch,
   utils::config::WindowUrl,
-  EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager,
+  CursorIcon, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager,
   PageLoadPayload, Runtime, Theme, WindowEvent,
 };
 
@@ -774,7 +774,7 @@ impl<R: Runtime> Window<R> {
   ///
   /// ## Platform-specific
   ///
-  /// - **macOS**: This is a private API on macOS,
+  /// - **macOS:** This is a private API on macOS,
   /// so you cannot use this if your application will be published on the App Store.
   ///
   /// # Examples
@@ -1178,6 +1178,59 @@ impl<R: Runtime> Window<R> {
       .map_err(Into::into)
   }
 
+  /// Grabs the cursor, preventing it from leaving the window.
+  ///
+  /// There's no guarantee that the cursor will be hidden. You should
+  /// hide it by yourself if you want so.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Linux:** Unsupported.
+  /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.
+  pub fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_cursor_grab(grab)
+      .map_err(Into::into)
+  }
+
+  /// Modifies the cursor's visibility.
+  ///
+  /// If `false`, this will hide the cursor. If `true`, this will show the cursor.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Linux:** Unsupported.
+  /// - **Windows:** The cursor is only hidden within the confines of the window.
+  /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
+  ///   outside of the window.
+  pub fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_cursor_visible(visible)
+      .map_err(Into::into)
+  }
+
+  /// Modifies the cursor icon of the window.
+  pub fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_cursor_icon(icon)
+      .map_err(Into::into)
+  }
+
+  /// Changes the position of the cursor in window coordinates.
+  pub fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_cursor_position(position)
+      .map_err(Into::into)
+  }
+
   /// Starts dragging the window.
   pub fn start_dragging(&self) -> crate::Result<()> {
     self.window.dispatcher.start_dragging().map_err(Into::into)

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
examples/api/dist/assets/index.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
examples/api/dist/assets/vendor.js


+ 13 - 7
examples/api/src-tauri/Cargo.lock

@@ -216,9 +216,9 @@ dependencies = [
 
 [[package]]
 name = "attohttpc"
-version = "0.18.0"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072"
+checksum = "f77e990b71f68dfa546bddbcbabd1a2f79ab74d075f8f602efd4b54b24cdcd26"
 dependencies = [
  "flate2",
  "http",
@@ -393,7 +393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c"
 dependencies = [
  "byteorder",
- "uuid",
+ "uuid 0.8.2",
 ]
 
 [[package]]
@@ -3234,7 +3234,7 @@ dependencies = [
  "thiserror",
  "tokio",
  "url",
- "uuid",
+ "uuid 1.0.0",
  "windows 0.30.0",
  "zip",
 ]
@@ -3267,7 +3267,7 @@ dependencies = [
  "sha2",
  "tauri-utils",
  "thiserror",
- "uuid",
+ "uuid 1.0.0",
  "walkdir",
 ]
 
@@ -3295,7 +3295,7 @@ dependencies = [
  "serde_json",
  "tauri-utils",
  "thiserror",
- "uuid",
+ "uuid 1.0.0",
  "webview2-com",
  "windows 0.30.0",
 ]
@@ -3308,7 +3308,7 @@ dependencies = [
  "rand 0.8.5",
  "tauri-runtime",
  "tauri-utils",
- "uuid",
+ "uuid 1.0.0",
  "webview2-com",
  "windows 0.30.0",
  "wry",
@@ -3604,6 +3604,12 @@ name = "uuid"
 version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+
+[[package]]
+name = "uuid"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
 dependencies = [
  "getrandom 0.2.6",
 ]

+ 181 - 74
examples/api/src/components/Window.svelte

@@ -1,136 +1,209 @@
 <script>
-  import { appWindow, WebviewWindow, LogicalSize, UserAttentionType, PhysicalSize, PhysicalPosition } from "@tauri-apps/api/window";
-  import { open as openDialog } from "@tauri-apps/api/dialog";
-  import { open } from "@tauri-apps/api/shell";
+  import {
+    appWindow,
+    WebviewWindow,
+    LogicalSize,
+    UserAttentionType,
+    PhysicalSize,
+    PhysicalPosition
+  } from '@tauri-apps/api/window'
+  import { open as openDialog } from '@tauri-apps/api/dialog'
+  import { open } from '@tauri-apps/api/shell'
 
-  let selectedWindow = appWindow.label;
+  let selectedWindow = appWindow.label
   const windowMap = {
     [selectedWindow]: appWindow
   }
+  const cursorIconOptions = [
+    'default',
+    'crosshair',
+    'hand',
+    'arrow',
+    'move',
+    'text',
+    'wait',
+    'help',
+    'progress',
+    // something cannot be done
+    'notAllowed',
+    'contextMenu',
+    'cell',
+    'verticalText',
+    'alias',
+    'copy',
+    'noDrop',
+    // something can be grabbed
+    'grab',
+    /// something is grabbed
+    'grabbing',
+    'allScroll',
+    'zoomIn',
+    'zoomOut',
+    // edge is to be moved
+    'eResize',
+    'nResize',
+    'neResize',
+    'nwResize',
+    'sResize',
+    'seResize',
+    'swResize',
+    'wResize',
+    'ewResize',
+    'nsResize',
+    'neswResize',
+    'nwseResize',
+    'colResize',
+    'rowResize'
+  ]
 
-  export let onMessage;
+  export let onMessage
 
-  let urlValue = "https://tauri.studio";
-  let resizable = true;
-  let maximized = false;
-  let transparent = false;
-  let decorations = true;
-  let alwaysOnTop = false;
-  let fullscreen = false;
-  let width = 900;
-  let height = 700;
-  let minWidth = 600;
-  let minHeight = 600;
-  let maxWidth = null;
-  let maxHeight = null;
-  let x = 100;
-  let y = 100;
-  let scaleFactor = 1;
-  let innerPosition = new PhysicalPosition(x, y);
-  let outerPosition = new PhysicalPosition(x, y);
-  let innerSize = new PhysicalSize(width, height);
-  let outerSize = new PhysicalSize(width, height);
-  let resizeEventUnlisten;
-  let moveEventUnlisten;
+  let urlValue = 'https://tauri.studio'
+  let resizable = true
+  let maximized = false
+  let transparent = false
+  let decorations = true
+  let alwaysOnTop = false
+  let fullscreen = false
+  let width = 900
+  let height = 700
+  let minWidth = 600
+  let minHeight = 600
+  let maxWidth = null
+  let maxHeight = null
+  let x = 100
+  let y = 100
+  let scaleFactor = 1
+  let innerPosition = new PhysicalPosition(x, y)
+  let outerPosition = new PhysicalPosition(x, y)
+  let innerSize = new PhysicalSize(width, height)
+  let outerSize = new PhysicalSize(width, height)
+  let resizeEventUnlisten
+  let moveEventUnlisten
+  let cursorGrab = false
+  let cursorVisible = true
+  let cursorX = 600
+  let cursorY = 800
+  let cursorIcon = 'default'
 
-  let windowTitle = "Awesome Tauri Example!";
+  let windowTitle = 'Awesome Tauri Example!'
 
   function openUrl() {
-    open(urlValue);
+    open(urlValue)
   }
 
   function setTitle_() {
-    windowMap[selectedWindow].setTitle(windowTitle);
+    windowMap[selectedWindow].setTitle(windowTitle)
   }
 
   function hide_() {
-    windowMap[selectedWindow].hide();
-    setTimeout(windowMap[selectedWindow].show, 2000);
+    windowMap[selectedWindow].hide()
+    setTimeout(windowMap[selectedWindow].show, 2000)
   }
 
   function minimize_() {
-    windowMap[selectedWindow].minimize();
-    setTimeout(windowMap[selectedWindow].unminimize, 2000);
+    windowMap[selectedWindow].minimize()
+    setTimeout(windowMap[selectedWindow].unminimize, 2000)
   }
 
   function getIcon() {
     openDialog({
-      multiple: false,
-    }).then(path => {
+      multiple: false
+    }).then((path) => {
       if (typeof path === 'string') {
         windowMap[selectedWindow].setIcon(path)
       }
-    });
+    })
   }
 
   function createWindow() {
-    const label = Math.random().toString().replace('.', '');
-    const webview = new WebviewWindow(label);
-    windowMap[label] = webview;
+    const label = Math.random().toString().replace('.', '')
+    const webview = new WebviewWindow(label)
+    windowMap[label] = webview
     webview.once('tauri://error', function () {
-      onMessage("Error creating new webview")
+      onMessage('Error creating new webview')
     })
   }
 
   function handleWindowResize() {
-    windowMap[selectedWindow].innerSize().then(response => {
+    windowMap[selectedWindow].innerSize().then((response) => {
       innerSize = response
       width = innerSize.width
       height = innerSize.height
-    });
-    windowMap[selectedWindow].outerSize().then(response => {
+    })
+    windowMap[selectedWindow].outerSize().then((response) => {
       outerSize = response
-    });
+    })
   }
 
   function handleWindowMove() {
-    windowMap[selectedWindow].innerPosition().then(response => {
+    windowMap[selectedWindow].innerPosition().then((response) => {
       innerPosition = response
-    });
-    windowMap[selectedWindow].outerPosition().then(response => {
+    })
+    windowMap[selectedWindow].outerPosition().then((response) => {
       outerPosition = response
       x = outerPosition.x
       y = outerPosition.y
-    });
+    })
   }
 
   async function addWindowEventListeners(window) {
     if (resizeEventUnlisten) {
-      resizeEventUnlisten();
+      resizeEventUnlisten()
     }
-    if(moveEventUnlisten) {
-      moveEventUnlisten();
+    if (moveEventUnlisten) {
+      moveEventUnlisten()
     }
-    moveEventUnlisten = await window.listen('tauri://move', handleWindowMove);
-    resizeEventUnlisten = await window.listen('tauri://resize', handleWindowResize);
+    moveEventUnlisten = await window.listen('tauri://move', handleWindowMove)
+    resizeEventUnlisten = await window.listen(
+      'tauri://resize',
+      handleWindowResize
+    )
   }
 
   async function requestUserAttention_() {
-    await windowMap[selectedWindow].minimize();
-    await windowMap[selectedWindow].requestUserAttention(UserAttentionType.Critical);
-    await new Promise(resolve => setTimeout(resolve, 3000));
-    await windowMap[selectedWindow].requestUserAttention(null);
+    await windowMap[selectedWindow].minimize()
+    await windowMap[selectedWindow].requestUserAttention(
+      UserAttentionType.Critical
+    )
+    await new Promise((resolve) => setTimeout(resolve, 3000))
+    await windowMap[selectedWindow].requestUserAttention(null)
   }
 
-  $: windowMap[selectedWindow].setResizable(resizable);
-  $: maximized ? windowMap[selectedWindow].maximize() : windowMap[selectedWindow].unmaximize();
-  $: windowMap[selectedWindow].setDecorations(decorations);
-  $: windowMap[selectedWindow].setAlwaysOnTop(alwaysOnTop);
-  $: windowMap[selectedWindow].setFullscreen(fullscreen);
+  $: windowMap[selectedWindow].setResizable(resizable)
+  $: maximized
+    ? windowMap[selectedWindow].maximize()
+    : windowMap[selectedWindow].unmaximize()
+  $: windowMap[selectedWindow].setDecorations(decorations)
+  $: windowMap[selectedWindow].setAlwaysOnTop(alwaysOnTop)
+  $: windowMap[selectedWindow].setFullscreen(fullscreen)
+
+  $: windowMap[selectedWindow].setSize(new PhysicalSize(width, height))
+  $: minWidth && minHeight
+    ? windowMap[selectedWindow].setMinSize(new LogicalSize(minWidth, minHeight))
+    : windowMap[selectedWindow].setMinSize(null)
+  $: maxWidth && maxHeight
+    ? windowMap[selectedWindow].setMaxSize(new LogicalSize(maxWidth, maxHeight))
+    : windowMap[selectedWindow].setMaxSize(null)
+  $: windowMap[selectedWindow].setPosition(new PhysicalPosition(x, y))
+  $: windowMap[selectedWindow]
+    .scaleFactor()
+    .then((factor) => (scaleFactor = factor))
+  $: addWindowEventListeners(windowMap[selectedWindow])
 
-  $: windowMap[selectedWindow].setSize(new PhysicalSize(width, height));
-  $: minWidth && minHeight ? windowMap[selectedWindow].setMinSize(new LogicalSize(minWidth, minHeight)) : windowMap[selectedWindow].setMinSize(null);
-  $: maxWidth && maxHeight ? windowMap[selectedWindow].setMaxSize(new LogicalSize(maxWidth, maxHeight)) : windowMap[selectedWindow].setMaxSize(null);
-  $: windowMap[selectedWindow].setPosition(new PhysicalPosition(x, y));
-  $: windowMap[selectedWindow].scaleFactor().then(factor => scaleFactor = factor);
-  $: addWindowEventListeners(windowMap[selectedWindow]);
+  $: windowMap[selectedWindow].setCursorGrab(cursorGrab)
+  $: windowMap[selectedWindow].setCursorVisible(cursorVisible)
+  $: windowMap[selectedWindow].setCursorIcon(cursorIcon)
+  $: windowMap[selectedWindow].setCursorPosition(
+    new PhysicalPosition(cursorX, cursorY)
+  )
 </script>
 
 <div class="flex col">
   <select class="button" bind:value={selectedWindow}>
-      {#each Object.keys(windowMap) as label}
-        <option value={label}>{label}</option>
-      {/each}
+    {#each Object.keys(windowMap) as label}
+      <option value={label}>{label}</option>
+    {/each}
   </select>
   <div>
     <label>
@@ -141,7 +214,10 @@
       <input type="checkbox" bind:checked={maximized} />
       Maximize
     </label>
-    <button title="Unminimizes after 2 seconds" on:click={() => windowMap[selectedWindow].center()}>
+    <button
+      title="Unminimizes after 2 seconds"
+      on:click={() => windowMap[selectedWindow].center()}
+    >
       Center
     </button>
     <button title="Unminimizes after 2 seconds" on:click={minimize_}>
@@ -266,6 +342,32 @@
     </div>
   </div>
 </div>
+<div>
+  <h4>Cursor</h4>
+  <label>
+    <input type="checkbox" bind:checked={cursorGrab} />
+    Grab
+  </label>
+  <label>
+    <input type="checkbox" bind:checked={cursorVisible} />
+    Visible
+  </label>
+  <select class="button" bind:value={cursorIcon}>
+    {#each cursorIconOptions as kind}
+      <option value={kind}>{kind}</option>
+    {/each}
+  </select>
+  <div class="flex col grow">
+    <div>
+      X position
+      <input type="number" bind:value={cursorX} />
+    </div>
+    <div>
+      Y position
+      <input type="number" bind:value={cursorY} />
+    </div>
+  </div>
+</div>
 <form on:submit|preventDefault={setTitle_}>
   <input id="title" bind:value={windowTitle} />
   <button class="button" type="submit">Set title</button>
@@ -274,7 +376,12 @@
   <input id="url" bind:value={urlValue} />
   <button class="button" id="open-url"> Open URL </button>
 </form>
-<button class="button" on:click={requestUserAttention_} title="Minimizes the window, requests attention for 3s and then resets it">Request attention</button>
+<button
+  class="button"
+  on:click={requestUserAttention_}
+  title="Minimizes the window, requests attention for 3s and then resets it"
+  >Request attention</button
+>
 <button class="button" on:click={createWindow}>New window</button>
 
 <style>

+ 6 - 6
examples/api/yarn.lock

@@ -23,9 +23,9 @@
     svelte-hmr "^0.14.7"
 
 "@tauri-apps/api@../../tooling/api/dist":
-  version "1.0.0-rc.2"
+  version "1.0.0-rc.3"
   dependencies:
-    type-fest "2.12.1"
+    type-fest "2.12.2"
 
 debug@^4.3.2:
   version "4.3.3"
@@ -262,10 +262,10 @@ svelte@3.35.0:
   resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.35.0.tgz#e0d0ba60c4852181c2b4fd851194be6fda493e65"
   integrity sha512-gknlZkR2sXheu/X+B7dDImwANVvK1R0QGQLd8CNIfxxGPeXBmePnxfzb6fWwTQRsYQG7lYkZXvpXJvxvpsoB7g==
 
-type-fest@2.12.1:
-  version "2.12.1"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.1.tgz#d2be8f50bf5f8f0a5fd916d29bf3e98c17e960be"
-  integrity sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ==
+type-fest@2.12.2:
+  version "2.12.2"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.2.tgz#80a53614e6b9b475eb9077472fb7498dc7aa51d0"
+  integrity sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==
 
 vite@^2.6.4:
   version "2.6.14"

+ 152 - 0
tooling/api/src/window.ts

@@ -36,6 +36,10 @@
  *         "setFocus": true,
  *         "setIcon": true,
  *         "setSkipTaskbar": true,
+ *         "setCursorGrab": true,
+ *         "setCursorVisible": true,
+ *         "setCursorIcon": true,
+ *         "setCursorPosition": true,
  *         "startDragging": true,
  *         "print": true
  *       }
@@ -212,6 +216,47 @@ enum UserAttentionType {
   Informational
 }
 
+export type CursorIcon =
+  | 'default'
+  | 'crosshair'
+  | 'hand'
+  | 'arrow'
+  | 'move'
+  | 'text'
+  | 'wait'
+  | 'help'
+  | 'progress'
+  // something cannot be done
+  | 'notAllowed'
+  | 'contextMenu'
+  | 'cell'
+  | 'verticalText'
+  | 'alias'
+  | 'copy'
+  | 'noDrop'
+  // something can be grabbed
+  | 'grab'
+  /// something is grabbed
+  | 'grabbing'
+  | 'allScroll'
+  | 'zoomIn'
+  | 'zoomOut'
+  // edge is to be moved
+  | 'eResize'
+  | 'nResize'
+  | 'neResize'
+  | 'nwResize'
+  | 'sResize'
+  | 'seResize'
+  | 'swResize'
+  | 'wResize'
+  | 'ewResize'
+  | 'nsResize'
+  | 'neswResize'
+  | 'nwseResize'
+  | 'colResize'
+  | 'rowResize'
+
 /**
  * Get an instance of `WebviewWindow` for the current webview window.
  *
@@ -1091,6 +1136,113 @@ class WindowManager extends WebviewWindowHandle {
     })
   }
 
+  /**
+   * Grabs the cursor, preventing it from leaving the window.
+   *
+   * There's no guarantee that the cursor will be hidden. You should
+   * hide it by yourself if you want so.
+   *
+   * @param grab `true` to grab the cursor icon, `false` to release it.
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async setCursorGrab(grab: boolean): Promise<void> {
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'manage',
+        data: {
+          label: this.label,
+          cmd: {
+            type: 'setCursorGrab',
+            payload: grab
+          }
+        }
+      }
+    })
+  }
+
+  /**
+   * Modifies the cursor's visibility.
+   *
+   * @param visible If `false`, this will hide the cursor. If `true`, this will show the cursor.
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async setCursorVisible(visible: boolean): Promise<void> {
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'manage',
+        data: {
+          label: this.label,
+          cmd: {
+            type: 'setCursorVisible',
+            payload: visible
+          }
+        }
+      }
+    })
+  }
+
+  /**
+   * Modifies the cursor icon of the window.
+   *
+   * @param icon The new cursor icon.
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async setCursorIcon(icon: CursorIcon): Promise<void> {
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'manage',
+        data: {
+          label: this.label,
+          cmd: {
+            type: 'setCursorIcon',
+            payload: icon
+          }
+        }
+      }
+    })
+  }
+
+  /**
+   * Changes the position of the cursor in window coordinates.
+   *
+   * @param position The new cursor position.
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async setCursorPosition(
+    position: LogicalPosition | PhysicalPosition
+  ): Promise<void> {
+    if (
+      !position ||
+      (position.type !== 'Logical' && position.type !== 'Physical')
+    ) {
+      throw new Error(
+        'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance'
+      )
+    }
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'manage',
+        data: {
+          label: this.label,
+          cmd: {
+            type: 'setCursorPosition',
+            payload: {
+              type: position.type,
+              data: {
+                x: position.x,
+                y: position.y
+              }
+            }
+          }
+        }
+      }
+    })
+  }
+
   /**
    * Starts dragging the window.
    *

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff