Skip to content

Commit

Permalink
fix(windows): calculate parent bounds accurately (#1483)
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir authored Feb 12, 2025
1 parent cf18194 commit 9df094a
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changes/windows-undecorated-insets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": "patch"
---

On Windows, fix webview slightly larger than the host window causing a pixel or two to be obscured.
125 changes: 125 additions & 0 deletions examples/window_border.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use dpi::LogicalSize;
use tao::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopBuilder},
window::WindowBuilder,
};
use wry::{http::Request, WebViewBuilder};

enum UserEvent {
TogglShadows,
}

fn main() -> wry::Result<()> {
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(500, 500))
.with_decorations(false)
.build(&event_loop)
.unwrap();

const HTML: &str = r#"
<html>
<head>
<style>
html {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
width: 100vw;
height: 100vh;
background-color: #1f1f1f;
border: 1px solid rgb(148, 231, 155);
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
</style>
</head>
<body>
<p>
Click the window to toggle shadows.
</p>
<script>
window.addEventListener('click', () => window.ipc.postMessage('toggleShadows'))
</script>
</body>
</html>
"#;

let proxy = event_loop.create_proxy();
let handler = move |req: Request<String>| {
let body = req.body();
match body.as_str() {
"toggleShadows" => {
let _ = proxy.send_event(UserEvent::TogglShadows);
}
_ => {}
}
};

let builder = WebViewBuilder::new()
.with_html(HTML)
.with_ipc_handler(handler)
.with_accept_first_mouse(true);

#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
))]
let webview = builder.build(&window)?;
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
)))]
let webview = {
use tao::platform::unix::WindowExtUnix;
use wry::WebViewBuilderExtUnix;
let vbox = window.default_vbox().unwrap();
builder.build_gtk(vbox)?
};

let mut webview = Some(webview);

let mut shadow = true;

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
let _ = webview.take();
*control_flow = ControlFlow::Exit
}

Event::UserEvent(e) => match e {
UserEvent::TogglShadows => {
shadow = !shadow;
#[cfg(windows)]
{
use tao::platform::windows::WindowExtWindows;
window.set_undecorated_shadow(shadow);
}
}
},
_ => (),
}
});
}
121 changes: 49 additions & 72 deletions src/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,48 @@ impl InnerWebView {
);
}

fn parent_bounds(hwnd: HWND) -> Result<(i32, i32)> {
let placement = unsafe {
let mut placement = WINDOWPLACEMENT {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
..std::mem::zeroed()
};
GetWindowPlacement(hwnd, &mut placement)?;
placement
};

let client_rect = unsafe {
let mut rect = std::mem::zeroed();
GetClientRect(hwnd, &mut rect)?;
rect
};

let window_rect = unsafe {
let mut rect = std::mem::zeroed();
GetWindowRect(hwnd, &mut rect)?;
rect
};

let width_offset =
(window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
let height_offset =
(window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);

let rect = placement.rcNormalPosition;

let left_offset = width_offset / 2;
let top_offset = height_offset / 2;

let right_offset = width_offset - left_offset;
let bottom_offset = height_offset - top_offset;
let left = rect.left + left_offset;
let top = rect.top + top_offset;
let right = rect.right - right_offset;
let bottom = rect.bottom - bottom_offset;

Ok((right - left, bottom - top))
}

unsafe extern "system" fn parent_subclass_proc(
hwnd: HWND,
msg: u32,
Expand All @@ -1105,38 +1147,10 @@ impl InnerWebView {
WM_SIZE => {
if wparam.0 != SIZE_MINIMIZED as usize {
let controller = dwrefdata as *mut ICoreWebView2Controller;
let mut rect = RECT::default();
let _ = GetClientRect(hwnd, &mut rect);
let mut width = rect.right - rect.left;
let mut height = rect.bottom - rect.top;

// adjust for borders
let mut pt: POINT = unsafe { std::mem::zeroed() };
if unsafe { ClientToScreen(hwnd, &mut pt) }.as_bool() {
let mut window_rc: RECT = unsafe { std::mem::zeroed() };
if unsafe { GetWindowRect(hwnd, &mut window_rc) }.is_ok() {
let top_b = pt.y - window_rc.top;

// this is a hack to check if the window is undecorated
// specifically for winit and tao
// or any window that uses `WM_NCCALCSIZE` to create undecorated windows
//
// tao and winit, set the top border to 0 for undecorated
// or 1 for undecorated but has shadows
//
// normal windows should have a top border of around 32 px
//
// TODO: find a better way to check if a window is decorated or not
if top_b <= 1 {
let left_b = pt.x - window_rc.left;
let right_b = pt.x + width - window_rc.right;
let bottom_b = pt.y + height - window_rc.bottom;

width = width - left_b - right_b;
height = height - top_b - bottom_b;
}
}
}

let Ok((width, height)) = Self::parent_bounds(hwnd) else {
return DefSubclassProc(hwnd, msg, wparam, lparam);
};

let _ = (*controller).SetBounds(RECT {
left: 0,
Expand All @@ -1154,7 +1168,7 @@ impl InnerWebView {
0,
width,
height,
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE,
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOZORDER,
);
}
}
Expand Down Expand Up @@ -1375,40 +1389,7 @@ impl InnerWebView {
}

fn resize_to_parent(&self) -> crate::Result<()> {
let mut rect = RECT::default();
let parent = *self.parent.borrow();
unsafe { GetClientRect(parent, &mut rect)? };
let mut width = rect.right - rect.left;
let mut height = rect.bottom - rect.top;

// adjust for borders
let mut pt: POINT = unsafe { std::mem::zeroed() };
if unsafe { ClientToScreen(parent, &mut pt) }.as_bool() {
let mut window_rc: RECT = unsafe { std::mem::zeroed() };
if unsafe { GetWindowRect(parent, &mut window_rc) }.is_ok() {
let top_b = pt.y - window_rc.top;

// this is a hack to check if the window is undecorated
// specifically for winit and tao
// or any window that uses `WM_NCCALCSIZE` to create undecorated windows
//
// tao and winit, set the top border to 0 for undecorated
// or 1 for undecorated but has shadows
//
// normal windows should have a top border of around 32 px
//
// TODO: find a better way to check if a window is decorated or not
if top_b <= 1 {
let left_b = pt.x - window_rc.left;
let right_b = pt.x + width - window_rc.right;
let bottom_b = pt.y + height - window_rc.bottom;

width = width - left_b - right_b;
height = height - top_b - bottom_b;
}
}
}

let (width, height) = Self::parent_bounds(*self.parent.borrow())?;
self.set_bounds_inner((width, height).into(), (0, 0).into())
}

Expand Down Expand Up @@ -1566,11 +1547,7 @@ impl InnerWebView {

*self.parent.borrow_mut() = parent;

let mut rect = RECT::default();
GetClientRect(parent, &mut rect)?;

let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
let (width, height) = Self::parent_bounds(parent)?;

self.set_bounds_inner((width, height).into(), (0, 0).into())?;
}
Expand Down

0 comments on commit 9df094a

Please sign in to comment.