浏览代码

fix(core): fix undecorated window resizing, #8519 (#8537)

* fix(core): fix undecorated window resizing, fixes #8519

* js api

* fix invoke call

* Update tauri-window-start-resize-dragging.md

* clippy
Amr Bashir 1 年之前
父节点
当前提交
7f033f6dcd

+ 5 - 0
.changes/api-start-resize-dragging.md

@@ -0,0 +1,5 @@
+---
+'@tauri-apps/api': 'patch:feat'
+---
+
+Add `Window.startResizeDragging`.

+ 5 - 0
.changes/tauri-undecorated-resizing.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'patch:bug'
+---
+
+Fix undecorated window resizing on Windows and Linux.

+ 7 - 0
.changes/tauri-window-start-resize-dragging.md

@@ -0,0 +1,7 @@
+---
+'tauri': 'patch:feat'
+'tauri-runtime': 'patch'
+'tauri-runtime-wry': 'patch'
+---
+
+Add `Window::start_resize_dragging` and `ResizeDirection` enum.

+ 20 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -1065,6 +1065,7 @@ pub enum WindowMessage {
   SetIgnoreCursorEvents(bool),
   SetProgressBar(ProgressBarState),
   DragWindow,
+  ResizeDragWindow(tauri_runtime::ResizeDirection),
   RequestRedraw,
 }
 
@@ -1583,6 +1584,13 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
     )
   }
 
+  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::ResizeDragWindow(direction)),
+    )
+  }
+
   #[cfg(all(feature = "tracing", not(target_os = "android")))]
   fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
     // use a channel so the EvaluateScript task uses the current span as parent
@@ -2423,6 +2431,18 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::DragWindow => {
             let _ = window.drag_window();
           }
+          WindowMessage::ResizeDragWindow(direction) => {
+            let _ = window.drag_resize_window(match direction {
+              tauri_runtime::ResizeDirection::East => tao::window::ResizeDirection::East,
+              tauri_runtime::ResizeDirection::North => tao::window::ResizeDirection::North,
+              tauri_runtime::ResizeDirection::NorthEast => tao::window::ResizeDirection::NorthEast,
+              tauri_runtime::ResizeDirection::NorthWest => tao::window::ResizeDirection::NorthWest,
+              tauri_runtime::ResizeDirection::South => tao::window::ResizeDirection::South,
+              tauri_runtime::ResizeDirection::SouthEast => tao::window::ResizeDirection::SouthEast,
+              tauri_runtime::ResizeDirection::SouthWest => tao::window::ResizeDirection::SouthWest,
+              tauri_runtime::ResizeDirection::West => tao::window::ResizeDirection::West,
+            });
+          }
           WindowMessage::RequestRedraw => {
             window.request_redraw();
           }

+ 16 - 0
core/tauri-runtime/src/lib.rs

@@ -69,6 +69,19 @@ impl Default for DeviceEventFilter {
   }
 }
 
+/// Defines the orientation that a window resize will be performed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
+pub enum ResizeDirection {
+  East,
+  North,
+  NorthEast,
+  NorthWest,
+  South,
+  SouthEast,
+  SouthWest,
+  West,
+}
+
 #[derive(Debug, thiserror::Error)]
 #[non_exhaustive]
 pub enum Error {
@@ -590,6 +603,9 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   /// Starts dragging the window.
   fn start_dragging(&self) -> Result<()>;
 
+  /// Starts resize-dragging the window.
+  fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>;
+
   /// Executes javascript on the window this [`Dispatch`] represents.
   fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
 

文件差异内容过多而无法显示
+ 0 - 0
core/tauri/scripts/bundle.global.js


+ 4 - 0
core/tauri/src/test/mock_runtime.rs

@@ -676,6 +676,10 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
     Ok(())
   }
 
+  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
+    Ok(())
+  }
+
   fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
     self
       .last_evaluated_script

+ 10 - 0
core/tauri/src/window/mod.rs

@@ -8,6 +8,7 @@ pub(crate) mod plugin;
 
 use http::HeaderMap;
 pub use tauri_runtime::window::PageLoadEvent;
+use tauri_runtime::ResizeDirection;
 pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};
 use url::Url;
 
@@ -2256,6 +2257,15 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.start_dragging().map_err(Into::into)
   }
 
+  /// Starts resize-dragging the window.
+  pub fn start_resize_dragging(&self, direction: ResizeDirection) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .start_resize_dragging(direction)
+      .map_err(Into::into)
+  }
+
   /// Sets the taskbar progress state.
   ///
   /// ## Platform-specific

+ 121 - 0
core/tauri/src/window/plugin.rs

@@ -12,6 +12,7 @@ use crate::{
 #[cfg(desktop)]
 mod desktop_commands {
   use serde::Deserialize;
+  use tauri_runtime::ResizeDirection;
   use tauri_utils::ProgressBarState;
 
   use super::*;
@@ -155,6 +156,7 @@ mod desktop_commands {
   setter!(set_cursor_position, Position);
   setter!(set_ignore_cursor_events, bool);
   setter!(start_dragging);
+  setter!(start_resize_dragging, ResizeDirection);
   setter!(set_progress_bar, ProgressBarState);
   setter!(print);
 
@@ -211,6 +213,107 @@ mod desktop_commands {
     }
     Ok(())
   }
+
+  #[derive(Debug)]
+  enum HitTestResult {
+    Client,
+    Left,
+    Right,
+    Top,
+    Bottom,
+    TopLeft,
+    TopRight,
+    BottomLeft,
+    BottomRight,
+    NoWhere,
+  }
+
+  impl HitTestResult {
+    fn drag_resize_window<R: Runtime>(&self, window: &Window<R>) {
+      let _ = window.start_resize_dragging(match self {
+        HitTestResult::Left => ResizeDirection::West,
+        HitTestResult::Right => ResizeDirection::East,
+        HitTestResult::Top => ResizeDirection::North,
+        HitTestResult::Bottom => ResizeDirection::South,
+        HitTestResult::TopLeft => ResizeDirection::NorthWest,
+        HitTestResult::TopRight => ResizeDirection::NorthEast,
+        HitTestResult::BottomLeft => ResizeDirection::SouthWest,
+        HitTestResult::BottomRight => ResizeDirection::SouthEast,
+        _ => unreachable!(),
+      });
+    }
+
+    fn change_cursor<R: Runtime>(&self, window: &Window<R>) {
+      let _ = window.set_cursor_icon(match self {
+        HitTestResult::Left => CursorIcon::WResize,
+        HitTestResult::Right => CursorIcon::EResize,
+        HitTestResult::Top => CursorIcon::NResize,
+        HitTestResult::Bottom => CursorIcon::SResize,
+        HitTestResult::TopLeft => CursorIcon::NwResize,
+        HitTestResult::TopRight => CursorIcon::NeResize,
+        HitTestResult::BottomLeft => CursorIcon::SwResize,
+        HitTestResult::BottomRight => CursorIcon::SeResize,
+        _ => CursorIcon::Default,
+      });
+    }
+  }
+
+  fn hit_test(window_size: PhysicalSize<u32>, x: i32, y: i32, scale: f64) -> HitTestResult {
+    const BORDERLESS_RESIZE_INSET: f64 = 5.0;
+
+    const CLIENT: isize = 0b0000;
+    const LEFT: isize = 0b0001;
+    const RIGHT: isize = 0b0010;
+    const TOP: isize = 0b0100;
+    const BOTTOM: isize = 0b1000;
+    const TOPLEFT: isize = TOP | LEFT;
+    const TOPRIGHT: isize = TOP | RIGHT;
+    const BOTTOMLEFT: isize = BOTTOM | LEFT;
+    const BOTTOMRIGHT: isize = BOTTOM | RIGHT;
+
+    let top = 0;
+    let left = 0;
+    let bottom = top + window_size.height as i32;
+    let right = left + window_size.width as i32;
+
+    let inset = (BORDERLESS_RESIZE_INSET * scale) as i32;
+
+    #[rustfmt::skip]
+        let result =
+            (LEFT * (if x < (left + inset) { 1 } else { 0 }))
+          | (RIGHT * (if x >= (right - inset) { 1 } else { 0 }))
+          | (TOP * (if y < (top + inset) { 1 } else { 0 }))
+          | (BOTTOM * (if y >= (bottom - inset) { 1 } else { 0 }));
+
+    match result {
+      CLIENT => HitTestResult::Client,
+      LEFT => HitTestResult::Left,
+      RIGHT => HitTestResult::Right,
+      TOP => HitTestResult::Top,
+      BOTTOM => HitTestResult::Bottom,
+      TOPLEFT => HitTestResult::TopLeft,
+      TOPRIGHT => HitTestResult::TopRight,
+      BOTTOMLEFT => HitTestResult::BottomLeft,
+      BOTTOMRIGHT => HitTestResult::BottomRight,
+      _ => HitTestResult::NoWhere,
+    }
+  }
+
+  #[command(root = "crate")]
+  pub async fn on_mousemove<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
+    hit_test(window.inner_size()?, x, y, window.scale_factor()?).change_cursor(&window);
+    Ok(())
+  }
+
+  #[command(root = "crate")]
+  pub async fn on_mousedown<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
+    let res = hit_test(window.inner_size()?, x, y, window.scale_factor()?);
+    match res {
+      HitTestResult::Client | HitTestResult::NoWhere => {}
+      _ => res.drag_resize_window(&window),
+    };
+    Ok(())
+  }
 }
 
 /// Initializes the plugin.
@@ -239,6 +342,21 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
     .into_string(),
   );
 
+  #[derive(Template)]
+  #[default_template("./scripts/undecorated-resizing.js")]
+  struct UndecoratedResizingJavascript<'a> {
+    os_name: &'a str,
+  }
+
+  init_script.push_str(
+    &UndecoratedResizingJavascript {
+      os_name: std::env::consts::OS,
+    }
+    .render_default(&Default::default())
+    .unwrap()
+    .into_string(),
+  );
+
   #[cfg(any(debug_assertions, feature = "devtools"))]
   {
     #[derive(Template)]
@@ -320,6 +438,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::set_cursor_position,
             desktop_commands::set_ignore_cursor_events,
             desktop_commands::start_dragging,
+            desktop_commands::start_resize_dragging,
             desktop_commands::set_progress_bar,
             desktop_commands::print,
             desktop_commands::set_icon,
@@ -327,6 +446,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::internal_toggle_maximize,
             #[cfg(any(debug_assertions, feature = "devtools"))]
             desktop_commands::internal_toggle_devtools,
+            desktop_commands::on_mousemove,
+            desktop_commands::on_mousedown,
           ]);
         handler(invoke)
       }

+ 21 - 0
core/tauri/src/window/scripts/undecorated-resizing.js

@@ -0,0 +1,21 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+;(function () {
+  const osName = __TEMPLATE_os_name__
+  if (osName !== 'macos') {
+    document.addEventListener('mousemove', (e) => {
+      window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousemove', {
+        x: e.clientX,
+        y: e.clientY
+      })
+    })
+    document.addEventListener('mousedown', (e) => {
+      window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousedown', {
+        x: e.clientX,
+        y: e.clientY
+      })
+    })
+  }
+})()

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

@@ -45,6 +45,16 @@ export interface Monitor {
 type Theme = 'light' | 'dark'
 type TitleBarStyle = 'visible' | 'transparent' | 'overlay'
 
+type ResizeDirection =
+  | 'East'
+  | 'North'
+  | 'NorthEast'
+  | 'NorthWest'
+  | 'South'
+  | 'SouthEast'
+  | 'SouthWest'
+  | 'West'
+
 /**
  * The payload for the `scaleChange` event.
  *
@@ -1507,6 +1517,23 @@ class Window {
     })
   }
 
+  /**
+   * Starts resize-dragging the window.
+   * @example
+   * ```typescript
+   * import { getCurrent } from '@tauri-apps/api/window';
+   * await getCurrent().startResizeDragging();
+   * ```
+   *
+   * @return A promise indicating the success or failure of the operation.
+   */
+  async startResizeDragging(direction: ResizeDirection): Promise<void> {
+    return invoke('plugin:window|start_resize_dragging', {
+      label: this.label,
+      value: direction
+    })
+  }
+
   /**
    * Sets the taskbar progress state.
    *

部分文件因为文件数量过多而无法显示