Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve frameless window on windows #1324

Merged
merged 1 commit into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You can find its changes [documented below](#060---2020-06-01).
- Added documentation to resizable() and show_titlebar() in WindowDesc. ([#1037] by [@rhzk])
- Windows: Added internal functions to handle Re-entrancy. ([#1037] by [@rhzk])
- Windows: WindowDesc: Create window with disabled titlebar, maximized or minimized state, and with position. ([#1037] by [@rhzk])
- Windows: WindowHandle: Change window state. Toggle titlebar. Change size and position of window. ([#1037] by [@rhzk])
- Windows: WindowHandle: Change window state. Toggle titlebar. Change size and position of window. ([#1037], [#1324] by [@rhzk])
- Windows: WindowHandle: Added handle_titlebar(), Allowing a custom titlebar to behave like the OS one. ([#1037] by [@rhzk])
- `OPEN_PANEL_CANCELLED` and `SAVE_PANEL_CANCELLED` commands. ([#1061] by @cmyr)
- Export `Image` and `ImageData` by default. ([#1011] by [@covercash2])
Expand Down Expand Up @@ -523,6 +523,7 @@ Last release without a changelog :(
[#1306]: https://github.com/linebender/druid/pull/1306
[#1311]: https://github.com/linebender/druid/pull/1311
[#1320]: https://github.com/linebender/druid/pull/1320
[#1324]: https://github.com/linebender/druid/pull/1324
[#1326]: https://github.com/linebender/druid/pull/1326
[#1328]: https://github.com/linebender/druid/pull/1328
[#1346]: https://github.com/linebender/druid/pull/1346
Expand Down
174 changes: 100 additions & 74 deletions druid-shell/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ use winapi::shared::dxgitype::*;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::shared::winerror::*;
use winapi::um::dwmapi::DwmExtendFrameIntoClientArea;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::shellscalingapi::MDT_EFFECTIVE_DPI;
use winapi::um::unknwnbase::*;
use winapi::um::uxtheme::*;
use winapi::um::wingdi::*;
use winapi::um::winnt::*;
use winapi::um::winuser::*;
Expand Down Expand Up @@ -519,6 +521,22 @@ impl MyWndProc {
warn!("Could not get HWND");
}
}

fn get_system_metric(&self, metric: c_int) -> i32 {
unsafe {
// This is only supported on windows 10.
if let Some(func) = OPTIONAL_FUNCTIONS.GetSystemMetricsForDpi {
let dpi = self.scale().x() * SCALE_TARGET_DPI;
func(metric, dpi as u32)
}
// Support for older versions of windows
else {
// Note: On Windows 8.1 GetSystemMetrics() is scaled to the DPI the window
// was created with, and not the current DPI of the window
GetSystemMetrics(metric)
}
}
}
}

impl WndProc for MyWndProc {
Expand Down Expand Up @@ -587,6 +605,18 @@ impl WndProc for MyWndProc {
WM_ACTIVATE => {
if LOWORD(wparam as u32) as u32 != 0 {
unsafe {
if !self.has_titlebar() {
// This makes windows paint the dropshadow around the window
// since we give it a "1 pixel frame" that we paint over anyway.
// From my testing top seems to be the best option when it comes to avoiding resize artifacts.
let margins = MARGINS {
cxLeftWidth: 0,
cxRightWidth: 0,
cyTopHeight: 1,
cyBottomHeight: 0,
};
DwmExtendFrameIntoClientArea(hwnd, &margins);
}
if SetWindowPos(
hwnd,
HWND_TOPMOST,
Expand All @@ -602,7 +632,7 @@ impl WndProc for MyWndProc {
) == 0
{
warn!(
"failed to update window style: {}",
"SetWindowPos failed with error: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
Expand Down Expand Up @@ -665,86 +695,78 @@ impl WndProc for MyWndProc {
Some(0)
},
WM_NCCALCSIZE => unsafe {
// Workaround to get rid of caption but keeping the borders created by it.
let style = GetWindowLongPtrW(hwnd, GWL_STYLE) as u32;
if style == 0 {
warn!(
"failed to get window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return Some(0);
}

if !self.has_titlebar() && (style & WS_CAPTION) != 0 {
let s: *mut NCCALCSIZE_PARAMS = lparam as *mut NCCALCSIZE_PARAMS;
if let Some(mut s) = s.as_mut() {
if let Some(func) = OPTIONAL_FUNCTIONS.GetSystemMetricsForDpi {
// This function is only supported on windows 10
let dpi = self.scale().x() * SCALE_TARGET_DPI;
// Height of the different parts that make the titlebar
let border = func(SM_CXPADDEDBORDER, dpi as u32);
let frame = func(SM_CYSIZEFRAME, dpi as u32);
let caption = func(SM_CYCAPTION, dpi as u32);
// Maximized window titlebar height is just the caption
if (style & WS_MAXIMIZE) != 0 {
s.rgrc[0].top -= (caption) as i32;
}
// Normal window titlebar height is a combination of border frame and caption
else {
s.rgrc[0].top -= (border + frame + caption) as i32;
}
}
// Support for windows 8.1
else {
// Note: With SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) that we use on 8.1,
// Windows will not scale the titlebar area when DPI changes.
// Instead it is "stuck" at the DPI setting the window was created with.
// GetSystemMetrics() also reports values with the DPI the window was created with.
let border = GetSystemMetrics(SM_CXPADDEDBORDER);
let frame = GetSystemMetrics(SM_CYSIZEFRAME);
let caption = GetSystemMetrics(SM_CYCAPTION);
if (style & WS_MAXIMIZE) != 0 {
s.rgrc[0].top -= (caption) as i32;
} else {
s.rgrc[0].top -= (border + frame + caption) as i32;
if wparam != 0 as usize && !self.has_titlebar() {
if let Ok(handle) = self.handle.try_borrow() {
if handle.get_window_state() == window::WindowState::MAXIMIZED {
// When maximized, windows still adds offsets for the frame
// so we counteract them here.
let s: *mut NCCALCSIZE_PARAMS = lparam as *mut NCCALCSIZE_PARAMS;
if let Some(mut s) = s.as_mut() {
let border = self.get_system_metric(SM_CXPADDEDBORDER);
let frame = self.get_system_metric(SM_CYSIZEFRAME);
s.rgrc[0].top += (border + frame) as i32;
s.rgrc[0].right -= (border + frame) as i32;
s.rgrc[0].left += (border + frame) as i32;
s.rgrc[0].bottom -= (border + frame) as i32;
}
}
}
return Some(0);
}
None
},
WM_NCHITTEST => unsafe {
let mut hit = DefWindowProcW(hwnd, msg, wparam, lparam);
if !self.has_titlebar() {
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetWindowRect(hwnd, &mut rect) == 0 {
warn!(
"failed to get window rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
let a = HIWORD(lparam as u32) as i16 as i32 - rect.top;
if (a == 0) && (hit != HTTOPLEFT) && (hit != HTTOPRIGHT) && self.resizable() {
hit = HTTOP;
if !self.has_titlebar() && self.resizable() {
if let Ok(handle) = self.handle.try_borrow() {
if handle.get_window_state() != window::WindowState::MAXIMIZED {
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetWindowRect(hwnd, &mut rect) == 0 {
warn!(
"failed to get window rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
let y_cord = HIWORD(lparam as u32) as i16 as i32;
let x_cord = LOWORD(lparam as u32) as i16 as i32;
let HIT_SIZE = self.get_system_metric(SM_CYSIZEFRAME)
+ self.get_system_metric(SM_CXPADDEDBORDER);

if y_cord - rect.top <= HIT_SIZE {
if x_cord - rect.left <= HIT_SIZE {
hit = HTTOPLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTTOPRIGHT;
} else {
hit = HTTOP;
}
} else if rect.bottom - y_cord <= HIT_SIZE {
if x_cord - rect.left <= HIT_SIZE {
hit = HTBOTTOMLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTBOTTOMRIGHT;
} else {
hit = HTBOTTOM;
}
} else if x_cord - rect.left <= HIT_SIZE {
hit = HTLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTRIGHT;
}
}
}
}
if hit != HTTOP {
let mouseDown = GetAsyncKeyState(VK_LBUTTON) < 0;

if self.with_window_state(|state| state.handle_titlebar.get()) && !mouseDown {
self.with_window_state(move |state| state.handle_titlebar.set(false));
};

if self.with_window_state(|state| state.handle_titlebar.get())
&& hit == HTCLIENT
{
hit = HTCAPTION;
}
let mouseDown = GetAsyncKeyState(VK_LBUTTON) < 0;
if self.with_window_state(|state| state.handle_titlebar.get()) && !mouseDown {
self.with_window_state(move |state| state.handle_titlebar.set(false));
};
if self.with_window_state(|state| state.handle_titlebar.get()) && hit == HTCLIENT {
hit = HTCAPTION;
}
Some(hit)
},
Expand Down Expand Up @@ -778,8 +800,12 @@ impl WndProc for MyWndProc {
&self.dwrite_factory,
&size_dp.to_rect().into(),
);
let present_after = match self.present_strategy {
PresentStrategy::Sequential => 1,
_ => 0,
};
if let Some(ref mut dxgi_state) = s.dxgi_state {
(*dxgi_state.swap_chain).Present(0, 0);
(*dxgi_state.swap_chain).Present(present_after, 0);
}
ValidateRect(hwnd, null_mut());
} else {
Expand Down Expand Up @@ -974,8 +1000,8 @@ impl WndProc for MyWndProc {
let count = if down {
// TODO: it may be more precise to use the timestamp from the event.
let this_click = Instant::now();
let thresh_x = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
let thresh_y = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
let thresh_x = self.get_system_metric(SM_CXDOUBLECLK);
let thresh_y = self.get_system_metric(SM_CYDOUBLECLK);
let in_box = (x - s.last_click_pos.0).abs() <= thresh_x / 2
&& (y - s.last_click_pos.1).abs() <= thresh_y / 2;
let threshold = Duration::from_millis(dct as u64);
Expand Down