From ef936e0adbed939bce58a2854565e5fb625b8def Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Thu, 1 Dec 2022 20:36:10 +0200 Subject: [PATCH] Respect scale when setting window position during creation. (#2296) --- druid-shell/src/backend/windows/window.rs | 136 +++++++++++----------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/druid-shell/src/backend/windows/window.rs b/druid-shell/src/backend/windows/window.rs index 5a1f38f6c2..6e63f6eb02 100644 --- a/druid-shell/src/backend/windows/window.rs +++ b/druid-shell/src/backend/windows/window.rs @@ -1361,32 +1361,14 @@ impl WindowBuilder { present_strategy: self.present_strategy, }; - // TODO: pos_x and pos_y are only scaled for windows with parents. But they need to be - // scaled for windows without parents too. - let (mut pos_x, mut pos_y) = match self.position { - Some(pos) => (pos.x as i32, pos.y as i32), - None => (CW_USEDEFAULT, CW_USEDEFAULT), - }; - let scale = Scale::new(1.0, 1.0); - - let mut area = ScaledArea::default(); - let (width, height) = self - .size - .map(|size| { - area = ScaledArea::from_dp(size, scale); - let size_px = area.size_px(); - (size_px.width as i32, size_px.height as i32) - }) - .unwrap_or((CW_USEDEFAULT, CW_USEDEFAULT)); - - let (hmenu, accels, has_menu) = match self.menu { - Some(menu) => { - let accels = menu.accels(); - (menu.into_hmenu(), accels, true) - } - None => (0 as HMENU, None, false), - }; - + // TODO: Can we know the DPI of the screen where the window will be created ahead of time? + // Could probably make a better guess, e.g. if all screens have some non-1.0 ratio. + // For now we just use 1.0 and fix it after creation, but before showing the window. + // Unless we have a parent window, in which case we use its scale as our guess. + let mut scale = Scale::new(1.0, 1.0); + let (mut width, mut height) = (CW_USEDEFAULT, CW_USEDEFAULT); + let (mut pos_x, mut pos_y) = (CW_USEDEFAULT, CW_USEDEFAULT); + let mut parent_pos_dp = None; // When set, the pos is parent_pos_dp + self.position let mut dwStyle = WS_OVERLAPPEDWINDOW; let mut dwExStyle: DWORD = 0; let mut focusable = true; @@ -1399,22 +1381,13 @@ impl WindowBuilder { WindowLevel::Tooltip(parent_window_handle) | WindowLevel::DropDown(parent_window_handle) | WindowLevel::Modal(parent_window_handle) => { + // Guess the scale will be the same as the parent's + scale = parent_window_handle.get_scale().unwrap_or_default(); + parent_pos_dp = Some(parent_window_handle.get_position()); parent_hwnd = parent_window_handle.0.get_hwnd(); dwStyle = WS_POPUP; dwExStyle = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW; focusable = false; - if let Some(point_in_window_coord) = self.position { - let screen_point = parent_window_handle.get_position() - + point_in_window_coord.to_vec2(); - let scaled_point = WindowBuilder::scale_sub_window_position( - screen_point, - parent_window_handle.get_scale(), - ); - pos_x = scaled_point.x as i32; - pos_y = scaled_point.y as i32; - } else { - warn!("No position provided for subwindow!"); - } } } } else { @@ -1422,6 +1395,27 @@ impl WindowBuilder { window_level = WindowLevel::AppWindow; } + // Calculate the window position in pixels + if let Some(pos_dp) = self.position { + (pos_x, pos_y) = calculate_window_pos(parent_pos_dp, pos_dp, scale); + } + + let mut area = ScaledArea::default(); + // Calculate the window size in pixels + if let Some(size_dp) = self.size { + area = ScaledArea::from_dp(size_dp, scale); + let size_px = area.size_px(); + (width, height) = (size_px.width as i32, size_px.height as i32); + } + + let (hmenu, accels, has_menu) = match self.menu { + Some(menu) => { + let accels = menu.accels(); + (menu.into_hmenu(), accels, true) + } + None => (0 as HMENU, None, false), + }; + let window = WindowState { hwnd: Cell::new(0 as HWND), scale: Cell::new(scale), @@ -1496,24 +1490,39 @@ impl WindowBuilder { return Err(Error::NullHwnd); } - if let Some(size_dp) = self.size { - if let Ok(scale) = handle.get_scale() { - let size_px = size_dp.to_px(scale); - if SetWindowPos( - hwnd, - HWND_TOPMOST, - 0, - 0, - size_px.width.round() as i32, - size_px.height.round() as i32, - SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER, - ) == 0 + // Now that the window has been created we finally have the correct scale, + // so we might need to update the window position & size. + if let Ok(new_scale) = handle.get_scale() { + if new_scale != scale { + scale = new_scale; + let mut flags = + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER; + let mut needs_any_change = false; + + // Calculate the window position in pixels + if let Some(pos_dp) = self.position { + (pos_x, pos_y) = calculate_window_pos(parent_pos_dp, pos_dp, scale); + flags &= !SWP_NOMOVE; + needs_any_change = true; + } + + // Calculate the window size in pixels + if let Some(size_dp) = self.size { + area = ScaledArea::from_dp(size_dp, scale); + let size_px = area.size_px(); + (width, height) = (size_px.width as i32, size_px.height as i32); + flags &= !SWP_NOSIZE; + needs_any_change = true; + } + + if needs_any_change + && SetWindowPos(hwnd, HWND_TOPMOST, pos_x, pos_y, width, height, flags) == 0 { warn!( - "failed to resize window: {}", + "failed to update window size/pos: {}", Error::Hr(HRESULT_FROM_WIN32(GetLastError())) ); - }; + } } } @@ -1537,21 +1546,16 @@ impl WindowBuilder { Ok(handle) } } +} - /// When creating a sub-window, we need to scale its position with respect to its parent. - /// If there is any error while scaling, log it as a warn and show sub-window in top left corner of screen/window. - fn scale_sub_window_position( - un_scaled_sub_window_position: Point, - parent_window_scale: Result, - ) -> Point { - match parent_window_scale { - Ok(s) => un_scaled_sub_window_position.to_px(s), - Err(e) => { - warn!("Error with scale: {:?}", e); - Point::new(0., 0.) - } - } - } +/// Returns a pair of integers representing the pixel values of the position. +fn calculate_window_pos(parent_pos_dp: Option, pos_dp: Point, scale: Scale) -> (i32, i32) { + let pos_px = if let Some(parent_pos_dp) = parent_pos_dp { + (parent_pos_dp + pos_dp.to_vec2()).to_px(scale) + } else { + pos_dp.to_px(scale) + }; + (pos_px.x.round() as i32, pos_px.y.round() as i32) } /// Attempt to read the registry and see if the system is set to a dark or