From 5a5e814ae3265933e5d3cbae73401f30d9c247c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Mon, 23 Mar 2026 13:40:43 +0100 Subject: [PATCH] Add new os_screen_scale function to Platform impls - forward `os_screen_scale` as `default_scale` in EditorHandle - comment why `os_scale` on Windows and Linux is 1.0 and why `default_scale` on macOS is 1.0 --- examples/gain-plugin/src/editor.rs | 4 +-- plugin-canvas-slint/src/editor.rs | 13 ++++++++ plugin-canvas/src/platform/interface.rs | 1 + plugin-canvas/src/platform/mac/window.rs | 13 ++++++++ .../src/platform/win32/drop_target.rs | 31 ++++++----------- plugin-canvas/src/platform/win32/window.rs | 33 ++++++++++++++----- plugin-canvas/src/platform/x11/window.rs | 11 +++++++ plugin-canvas/src/window.rs | 10 ++++-- 8 files changed, 82 insertions(+), 34 deletions(-) diff --git a/examples/gain-plugin/src/editor.rs b/examples/gain-plugin/src/editor.rs index d05468a..11f6f17 100644 --- a/examples/gain-plugin/src/editor.rs +++ b/examples/gain-plugin/src/editor.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use plinth_plugin::{raw_window_handle::RawWindowHandle, Editor, Host}; -use plugin_canvas_slint::{editor::{EditorHandle, SlintEditor}, plugin_canvas::window::WindowAttributes}; +use plugin_canvas_slint::{editor::{EditorHandle, SlintEditor}, plugin_canvas::{window::WindowAttributes}}; use crate::{parameters::GainParameters, view::GainPluginView}; @@ -12,7 +12,7 @@ pub struct EditorSettings { impl Default for EditorSettings { fn default() -> Self { Self { - scale: 1.0, + scale: EditorHandle::default_scale(), } } } diff --git a/plugin-canvas-slint/src/editor.rs b/plugin-canvas-slint/src/editor.rs index 6d3a3bc..fc2f04e 100644 --- a/plugin-canvas-slint/src/editor.rs +++ b/plugin-canvas-slint/src/editor.rs @@ -72,6 +72,19 @@ pub struct EditorHandle { } impl EditorHandle { + pub fn default_scale() -> f64 { + // On macOS the system's window backend scaling gets applied in the OSWindow impl, so there's no need to scale the slint UIs too + #[cfg(target_os="macos")] + { + 1.0 + } + // On Windows and Linux the window backends do not use the system's DPI settings, so we scale the slint UIs to match the default screens scaling by default + #[cfg(any(target_os="windows", target_os="linux"))] + { + plugin_canvas::Window::os_screen_scale() + } + } + pub fn window(&self) -> Option<&plugin_canvas::Window> { self.window_adapter() .map(|window_adapter| window_adapter.plugin_canvas_window()) diff --git a/plugin-canvas/src/platform/interface.rs b/plugin-canvas/src/platform/interface.rs index a240252..ff38d98 100644 --- a/plugin-canvas/src/platform/interface.rs +++ b/plugin-canvas/src/platform/interface.rs @@ -12,6 +12,7 @@ pub(crate) trait OsWindowInterface: HasDisplayHandle + HasWindowHandle + Sized { event_callback: Box, ) -> Result; + fn os_screen_scale() -> f64; fn os_scale(&self) -> f64; fn resized(&self, size: LogicalSize); diff --git a/plugin-canvas/src/platform/mac/window.rs b/plugin-canvas/src/platform/mac/window.rs index 713ce0f..e5943a4 100644 --- a/plugin-canvas/src/platform/mac/window.rs +++ b/plugin-canvas/src/platform/mac/window.rs @@ -129,6 +129,19 @@ impl OsWindowInterface for OsWindow { Ok(OsWindowHandle::new(window)) } + fn os_screen_scale() -> f64 { + if let Some(main_thread_marker) = MainThreadMarker::new() { + if let Some(screen) = NSScreen::mainScreen(main_thread_marker) { + return screen.backingScaleFactor() + } + } + else { + #[cfg(debug_assertions)] + panic!("Calling os_screen_scale from an unexpected thread"); + } + 1.0 + } + fn os_scale(&self) -> f64 { self.view() .window() diff --git a/plugin-canvas/src/platform/win32/drop_target.rs b/plugin-canvas/src/platform/win32/drop_target.rs index 1f9b396..128dee5 100644 --- a/plugin-canvas/src/platform/win32/drop_target.rs +++ b/plugin-canvas/src/platform/win32/drop_target.rs @@ -6,18 +6,16 @@ use std::ptr::null_mut; use std::sync::Weak; use windows::Win32::Foundation::{POINTL, POINT}; -use windows::Win32::Graphics::Gdi::MapWindowPoints; +use windows::Win32::Graphics::Gdi::ScreenToClient; use windows::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}; use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows::Win32::UI::Shell::{DragQueryFileW, HDROP}; use windows::core::implement; use windows::Win32::System::Ole::{IDropTarget, IDropTarget_Impl, DROPEFFECT, CF_HDROP, DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_MOVE, DROPEFFECT_LINK}; -use windows::Win32::UI::WindowsAndMessaging::HWND_DESKTOP; use crate::event::EventResponse; -use crate::platform::interface::OsWindowInterface; use crate::thread_bound::ThreadBound; -use crate::{LogicalPosition, PhysicalPosition}; +use crate::LogicalPosition; use crate::drag_drop::{DropData, DropOperation}; use super::window::OsWindow; @@ -92,23 +90,14 @@ impl DropTarget { } fn convert_coordinates(&self, point: &POINTL) -> LogicalPosition { - let Some(window) = self.window.upgrade() else { - return LogicalPosition::default(); - }; - - let scale = window.os_scale(); - - // It looks like MapWindowPoints isn't DPI aware (and neither is ScreenToClient), - // so we need to pre-scale the point here? - // TODO: Find out what's going on - let mut points = [POINT { x: (point.x as f64 / scale) as i32, y: (point.y as f64 / scale) as i32 }]; - - unsafe { MapWindowPoints(Some(HWND_DESKTOP), Some(window.hwnd()), &mut points); } - - PhysicalPosition { - x: points[0].x, - y: points[0].y, - }.to_logical(1.0) + if let Some(window) = self.window.upgrade() { + let mut point = POINT { x: point.x, y: point.y }; + if unsafe { ScreenToClient(window.hwnd(), &mut point).as_bool() } { + // see `OsWindow.os_scale`: we don't use DPI scaling on Windows + return LogicalPosition { x: point.x as f64, y: point.y as f64 }; + } + } + LogicalPosition::default() } } diff --git a/plugin-canvas/src/platform/win32/window.rs b/plugin-canvas/src/platform/win32/window.rs index 0076ce1..f44fd06 100644 --- a/plugin-canvas/src/platform/win32/window.rs +++ b/plugin-canvas/src/platform/win32/window.rs @@ -6,6 +6,7 @@ use cursor_icon::CursorIcon; use keyboard_types::Code; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle, Win32WindowHandle}; use uuid::Uuid; +use windows::Win32::Graphics::Gdi::{GetDC, GetDeviceCaps, LOGPIXELSX, LOGPIXELSY, ReleaseDC}; use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; use windows::Win32::UI::WindowsAndMessaging::{GetParent, WM_CANCELMODE, WM_SETFOCUS, WM_SETCURSOR}; use windows::{core::PCWSTR, Win32::UI::Input::KeyboardAndMouse::{VK_LWIN, VK_RWIN}}; @@ -36,6 +37,20 @@ pub struct OsWindow { keyboard_modifiers: RefCell, } +fn window_scale(hwnd: Option) -> f64 { + // Could use `GetDpiForWindow` here, but that's available for Windows 10 only + unsafe { + let hdc = GetDC(hwnd); + if !hdc.is_invalid() { + let dpi = GetDeviceCaps(Some(hdc), LOGPIXELSX).min(GetDeviceCaps(Some(hdc), LOGPIXELSY)); + ReleaseDC(hwnd, hdc); + dpi.max(96) as f64 / 96.0 + } else { + 1.0 + } + } +} + impl OsWindow { pub(super) fn send_event(&self, event: Event) -> EventResponse { (self.event_callback)(event) @@ -75,12 +90,11 @@ impl OsWindow { } fn logical_mouse_position(&self, lparam: LPARAM) -> LogicalPosition { - let scale = self.os_scale(); - + // see `os_scale`: we don't use DPI scaling on Windows PhysicalPosition { x: (lparam.0 & 0xFFFF) as i16 as i32, y: ((lparam.0 >> 16) & 0xFFFF) as i16 as i32, - }.to_logical(scale) + }.to_logical(1.0) } fn update_modifiers(&self) { @@ -216,7 +230,13 @@ impl OsWindowInterface for OsWindow { Ok(OsWindowHandle::new(window)) } + fn os_screen_scale() -> f64 { + window_scale(None) + } + fn os_scale(&self) -> f64 { + // os window DPI scaling is not used on Window: window sizes and positions are treated as logical positions, + // custom scaling is applied by scaling the slint window content, using `os_screen_scale` as default. 1.0 } @@ -278,12 +298,9 @@ impl OsWindowInterface for OsWindow { } fn warp_mouse(&self, position: LogicalPosition) { - let scale = self.os_scale(); - let physical_position = position.to_physical(scale); - let mut point = POINT { - x: physical_position.x, - y: physical_position.y, + x: position.x as i32, + y: position.y as i32, }; unsafe { diff --git a/plugin-canvas/src/platform/x11/window.rs b/plugin-canvas/src/platform/x11/window.rs index d6b91f8..1759454 100644 --- a/plugin-canvas/src/platform/x11/window.rs +++ b/plugin-canvas/src/platform/x11/window.rs @@ -303,7 +303,18 @@ impl OsWindowInterface for OsWindow { Ok(OsWindowHandle::new(Arc::new(window.into()))) } + fn os_screen_scale() -> f64 { + if let Ok((connection, screen_num)) = x11rb::connect(None) { + let screen = &connection.setup().roots[screen_num]; + (screen.height_in_pixels as f64 * 25.4 / screen.height_in_millimeters as f64).max(96.0) / 96.0 + } else { + 1.0 + } + } + fn os_scale(&self) -> f64 { + // os window DPI scaling is not used on Linux: window sizes and positions are treated as logical positions, + // custom scaling is applied by scaling the slint window content, using `os_screen_scale` as default. 1.0 } diff --git a/plugin-canvas/src/window.rs b/plugin-canvas/src/window.rs index 003078f..bac6966 100644 --- a/plugin-canvas/src/window.rs +++ b/plugin-canvas/src/window.rs @@ -62,14 +62,18 @@ impl Window { }) } - pub fn attributes(&self) -> &WindowAttributes { - &self.attributes + pub fn os_screen_scale() -> f64 { + OsWindow::os_screen_scale() } - + pub fn os_scale(&self) -> f64 { self.os_window_handle.os_scale() } + pub fn attributes(&self) -> &WindowAttributes { + &self.attributes + } + pub fn resized(&self, size: LogicalSize) { self.os_window_handle.resized(size); }