Skip to content

Commit 7f033f6

Browse files
authored
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
1 parent 9f8037c commit 7f033f6

11 files changed

Lines changed: 237 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tauri-apps/api': 'patch:feat'
3+
---
4+
5+
Add `Window.startResizeDragging`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri': 'patch:bug'
3+
---
4+
5+
Fix undecorated window resizing on Windows and Linux.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'tauri': 'patch:feat'
3+
'tauri-runtime': 'patch'
4+
'tauri-runtime-wry': 'patch'
5+
---
6+
7+
Add `Window::start_resize_dragging` and `ResizeDirection` enum.

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,7 @@ pub enum WindowMessage {
10651065
SetIgnoreCursorEvents(bool),
10661066
SetProgressBar(ProgressBarState),
10671067
DragWindow,
1068+
ResizeDragWindow(tauri_runtime::ResizeDirection),
10681069
RequestRedraw,
10691070
}
10701071

@@ -1583,6 +1584,13 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
15831584
)
15841585
}
15851586

1587+
fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
1588+
send_user_message(
1589+
&self.context,
1590+
Message::Window(self.window_id, WindowMessage::ResizeDragWindow(direction)),
1591+
)
1592+
}
1593+
15861594
#[cfg(all(feature = "tracing", not(target_os = "android")))]
15871595
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
15881596
// use a channel so the EvaluateScript task uses the current span as parent
@@ -2423,6 +2431,18 @@ fn handle_user_message<T: UserEvent>(
24232431
WindowMessage::DragWindow => {
24242432
let _ = window.drag_window();
24252433
}
2434+
WindowMessage::ResizeDragWindow(direction) => {
2435+
let _ = window.drag_resize_window(match direction {
2436+
tauri_runtime::ResizeDirection::East => tao::window::ResizeDirection::East,
2437+
tauri_runtime::ResizeDirection::North => tao::window::ResizeDirection::North,
2438+
tauri_runtime::ResizeDirection::NorthEast => tao::window::ResizeDirection::NorthEast,
2439+
tauri_runtime::ResizeDirection::NorthWest => tao::window::ResizeDirection::NorthWest,
2440+
tauri_runtime::ResizeDirection::South => tao::window::ResizeDirection::South,
2441+
tauri_runtime::ResizeDirection::SouthEast => tao::window::ResizeDirection::SouthEast,
2442+
tauri_runtime::ResizeDirection::SouthWest => tao::window::ResizeDirection::SouthWest,
2443+
tauri_runtime::ResizeDirection::West => tao::window::ResizeDirection::West,
2444+
});
2445+
}
24262446
WindowMessage::RequestRedraw => {
24272447
window.request_redraw();
24282448
}

core/tauri-runtime/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ impl Default for DeviceEventFilter {
6969
}
7070
}
7171

72+
/// Defines the orientation that a window resize will be performed.
73+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
74+
pub enum ResizeDirection {
75+
East,
76+
North,
77+
NorthEast,
78+
NorthWest,
79+
South,
80+
SouthEast,
81+
SouthWest,
82+
West,
83+
}
84+
7285
#[derive(Debug, thiserror::Error)]
7386
#[non_exhaustive]
7487
pub enum Error {
@@ -590,6 +603,9 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
590603
/// Starts dragging the window.
591604
fn start_dragging(&self) -> Result<()>;
592605

606+
/// Starts resize-dragging the window.
607+
fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>;
608+
593609
/// Executes javascript on the window this [`Dispatch`] represents.
594610
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
595611

core/tauri/scripts/bundle.global.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/test/mock_runtime.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,10 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
676676
Ok(())
677677
}
678678

679+
fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
680+
Ok(())
681+
}
682+
679683
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
680684
self
681685
.last_evaluated_script

core/tauri/src/window/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub(crate) mod plugin;
88

99
use http::HeaderMap;
1010
pub use tauri_runtime::window::PageLoadEvent;
11+
use tauri_runtime::ResizeDirection;
1112
pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};
1213
use url::Url;
1314

@@ -2256,6 +2257,15 @@ impl<R: Runtime> Window<R> {
22562257
self.window.dispatcher.start_dragging().map_err(Into::into)
22572258
}
22582259

2260+
/// Starts resize-dragging the window.
2261+
pub fn start_resize_dragging(&self, direction: ResizeDirection) -> crate::Result<()> {
2262+
self
2263+
.window
2264+
.dispatcher
2265+
.start_resize_dragging(direction)
2266+
.map_err(Into::into)
2267+
}
2268+
22592269
/// Sets the taskbar progress state.
22602270
///
22612271
/// ## Platform-specific

core/tauri/src/window/plugin.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
#[cfg(desktop)]
1313
mod desktop_commands {
1414
use serde::Deserialize;
15+
use tauri_runtime::ResizeDirection;
1516
use tauri_utils::ProgressBarState;
1617

1718
use super::*;
@@ -155,6 +156,7 @@ mod desktop_commands {
155156
setter!(set_cursor_position, Position);
156157
setter!(set_ignore_cursor_events, bool);
157158
setter!(start_dragging);
159+
setter!(start_resize_dragging, ResizeDirection);
158160
setter!(set_progress_bar, ProgressBarState);
159161
setter!(print);
160162

@@ -211,6 +213,107 @@ mod desktop_commands {
211213
}
212214
Ok(())
213215
}
216+
217+
#[derive(Debug)]
218+
enum HitTestResult {
219+
Client,
220+
Left,
221+
Right,
222+
Top,
223+
Bottom,
224+
TopLeft,
225+
TopRight,
226+
BottomLeft,
227+
BottomRight,
228+
NoWhere,
229+
}
230+
231+
impl HitTestResult {
232+
fn drag_resize_window<R: Runtime>(&self, window: &Window<R>) {
233+
let _ = window.start_resize_dragging(match self {
234+
HitTestResult::Left => ResizeDirection::West,
235+
HitTestResult::Right => ResizeDirection::East,
236+
HitTestResult::Top => ResizeDirection::North,
237+
HitTestResult::Bottom => ResizeDirection::South,
238+
HitTestResult::TopLeft => ResizeDirection::NorthWest,
239+
HitTestResult::TopRight => ResizeDirection::NorthEast,
240+
HitTestResult::BottomLeft => ResizeDirection::SouthWest,
241+
HitTestResult::BottomRight => ResizeDirection::SouthEast,
242+
_ => unreachable!(),
243+
});
244+
}
245+
246+
fn change_cursor<R: Runtime>(&self, window: &Window<R>) {
247+
let _ = window.set_cursor_icon(match self {
248+
HitTestResult::Left => CursorIcon::WResize,
249+
HitTestResult::Right => CursorIcon::EResize,
250+
HitTestResult::Top => CursorIcon::NResize,
251+
HitTestResult::Bottom => CursorIcon::SResize,
252+
HitTestResult::TopLeft => CursorIcon::NwResize,
253+
HitTestResult::TopRight => CursorIcon::NeResize,
254+
HitTestResult::BottomLeft => CursorIcon::SwResize,
255+
HitTestResult::BottomRight => CursorIcon::SeResize,
256+
_ => CursorIcon::Default,
257+
});
258+
}
259+
}
260+
261+
fn hit_test(window_size: PhysicalSize<u32>, x: i32, y: i32, scale: f64) -> HitTestResult {
262+
const BORDERLESS_RESIZE_INSET: f64 = 5.0;
263+
264+
const CLIENT: isize = 0b0000;
265+
const LEFT: isize = 0b0001;
266+
const RIGHT: isize = 0b0010;
267+
const TOP: isize = 0b0100;
268+
const BOTTOM: isize = 0b1000;
269+
const TOPLEFT: isize = TOP | LEFT;
270+
const TOPRIGHT: isize = TOP | RIGHT;
271+
const BOTTOMLEFT: isize = BOTTOM | LEFT;
272+
const BOTTOMRIGHT: isize = BOTTOM | RIGHT;
273+
274+
let top = 0;
275+
let left = 0;
276+
let bottom = top + window_size.height as i32;
277+
let right = left + window_size.width as i32;
278+
279+
let inset = (BORDERLESS_RESIZE_INSET * scale) as i32;
280+
281+
#[rustfmt::skip]
282+
let result =
283+
(LEFT * (if x < (left + inset) { 1 } else { 0 }))
284+
| (RIGHT * (if x >= (right - inset) { 1 } else { 0 }))
285+
| (TOP * (if y < (top + inset) { 1 } else { 0 }))
286+
| (BOTTOM * (if y >= (bottom - inset) { 1 } else { 0 }));
287+
288+
match result {
289+
CLIENT => HitTestResult::Client,
290+
LEFT => HitTestResult::Left,
291+
RIGHT => HitTestResult::Right,
292+
TOP => HitTestResult::Top,
293+
BOTTOM => HitTestResult::Bottom,
294+
TOPLEFT => HitTestResult::TopLeft,
295+
TOPRIGHT => HitTestResult::TopRight,
296+
BOTTOMLEFT => HitTestResult::BottomLeft,
297+
BOTTOMRIGHT => HitTestResult::BottomRight,
298+
_ => HitTestResult::NoWhere,
299+
}
300+
}
301+
302+
#[command(root = "crate")]
303+
pub async fn on_mousemove<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
304+
hit_test(window.inner_size()?, x, y, window.scale_factor()?).change_cursor(&window);
305+
Ok(())
306+
}
307+
308+
#[command(root = "crate")]
309+
pub async fn on_mousedown<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
310+
let res = hit_test(window.inner_size()?, x, y, window.scale_factor()?);
311+
match res {
312+
HitTestResult::Client | HitTestResult::NoWhere => {}
313+
_ => res.drag_resize_window(&window),
314+
};
315+
Ok(())
316+
}
214317
}
215318

216319
/// Initializes the plugin.
@@ -239,6 +342,21 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
239342
.into_string(),
240343
);
241344

345+
#[derive(Template)]
346+
#[default_template("./scripts/undecorated-resizing.js")]
347+
struct UndecoratedResizingJavascript<'a> {
348+
os_name: &'a str,
349+
}
350+
351+
init_script.push_str(
352+
&UndecoratedResizingJavascript {
353+
os_name: std::env::consts::OS,
354+
}
355+
.render_default(&Default::default())
356+
.unwrap()
357+
.into_string(),
358+
);
359+
242360
#[cfg(any(debug_assertions, feature = "devtools"))]
243361
{
244362
#[derive(Template)]
@@ -320,13 +438,16 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
320438
desktop_commands::set_cursor_position,
321439
desktop_commands::set_ignore_cursor_events,
322440
desktop_commands::start_dragging,
441+
desktop_commands::start_resize_dragging,
323442
desktop_commands::set_progress_bar,
324443
desktop_commands::print,
325444
desktop_commands::set_icon,
326445
desktop_commands::toggle_maximize,
327446
desktop_commands::internal_toggle_maximize,
328447
#[cfg(any(debug_assertions, feature = "devtools"))]
329448
desktop_commands::internal_toggle_devtools,
449+
desktop_commands::on_mousemove,
450+
desktop_commands::on_mousedown,
330451
]);
331452
handler(invoke)
332453
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
;(function () {
6+
const osName = __TEMPLATE_os_name__
7+
if (osName !== 'macos') {
8+
document.addEventListener('mousemove', (e) => {
9+
window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousemove', {
10+
x: e.clientX,
11+
y: e.clientY
12+
})
13+
})
14+
document.addEventListener('mousedown', (e) => {
15+
window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousedown', {
16+
x: e.clientX,
17+
y: e.clientY
18+
})
19+
})
20+
}
21+
})()

0 commit comments

Comments
 (0)