From ae5920403bce8384ef4e5e12310eb99560aaa99d Mon Sep 17 00:00:00 2001 From: walterlv Date: Tue, 15 Oct 2024 00:07:09 +0800 Subject: [PATCH] Use the himetric location instead of the pixel location. (#16850) * Add an overload with more precise point transformation. * Use the himetric location instead of the pixel location. * The pen also use the himetric location. * Similar codes. * Use the raw location instead of the predicted one. See: https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_info * Fix wrong multi-screen location * Add lost win32 api. (cherry picked from commit 0bd90d802a208e4d4247d188a3fadbe129087f2e) --- .../Interop/UnmanagedMethods.cs | 19 +++++++----- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 26 +++++++++++++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 29 +++++++++++++++---- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b575db6cddd..2027ef06744 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -112,7 +112,7 @@ public enum ShowWindowCommand /// Hide = 0, /// - /// Activates and displays a window. If the window is minimized, maximized, or arranged, the system restores it to its original + /// Activates and displays a window. If the window is minimized, maximized, or arranged, the system restores it to its original /// size and position. An application should specify this flag when displaying the window for the first time. /// Normal = 1, @@ -147,12 +147,12 @@ public enum ShowWindowCommand /// ShowNA = 8, /// - /// Activates and displays the window. If the window is minimized, maximized, or arranged, the system restores it to its original size and position. + /// Activates and displays the window. If the window is minimized, maximized, or arranged, the system restores it to its original size and position. /// An application should specify this flag when restoring a minimized window. /// Restore = 9, /// - /// Sets the show state based on the value specified in the STARTUPINFO structure passed to the CreateProcess function + /// Sets the show state based on the value specified in the STARTUPINFO structure passed to the CreateProcess function /// by the program that started the application. /// ShowDefault = 10, @@ -1173,6 +1173,9 @@ public struct MOUSEMOVEPOINT [DllImport("user32.dll", SetLastError = true)] public static extern bool GetPointerTouchInfo(uint pointerId, out POINTER_TOUCH_INFO touchInfo); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool GetPointerDeviceRects(IntPtr device, out RECT pointerDeviceRect, out RECT displayRect); + [DllImport("user32.dll", SetLastError = true)] public static extern bool GetPointerTouchInfoHistory(uint pointerId, ref int entriesCount, [MarshalAs(UnmanagedType.LPArray), In, Out] POINTER_TOUCH_INFO[] touchInfos); @@ -1221,12 +1224,12 @@ public static extern IntPtr CreateWindowEx( [DllImport("user32.dll", EntryPoint = "DefWindowProcW")] public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - + public const int SC_MOUSEMOVE = 0xf012; - + [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")] public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); - + [DllImport("user32.dll", EntryPoint = "DispatchMessageW")] public static extern IntPtr DispatchMessage(ref MSG lpmsg); @@ -1534,7 +1537,7 @@ public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) [DllImport("user32.dll", EntryPoint = "SetCursor")] internal static extern IntPtr SetCursor(IntPtr hCursor); - + [DllImport("ole32.dll", PreserveSig = true)] internal static extern int CoCreateInstance(in Guid clsid, IntPtr ignore1, int ignore2, in Guid iid, [Out] out IntPtr pUnkOuter); @@ -2176,7 +2179,7 @@ public enum ClipboardFormat /// CF_UNICODETEXT = 13, /// - /// A handle to type HDROP that identifies a list of files. + /// A handle to type HDROP that identifies a list of files. /// CF_HDROP = 15, } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index caa2d33a25d..1f663607ac3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -164,7 +164,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, { requestDpi = _dpi; } - + return LoadIcon(requestIcon, requestDpi)?.Handle ?? default; case WindowsMessage.WM_KEYDOWN: @@ -766,7 +766,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, { LostFocus?.Invoke(); } - + break; case WindowsMessage.WM_INPUTLANGCHANGE: @@ -1083,8 +1083,8 @@ private RawPointerPoint CreateRawPointerPoint(POINTER_INFO pointerInfo) } private RawPointerPoint CreateRawPointerPoint(POINTER_TOUCH_INFO info) { - var pointerInfo = info.pointerInfo; - var point = PointToClient(new PixelPoint(pointerInfo.ptPixelLocationX, pointerInfo.ptPixelLocationY)); + var himetricLocation = GetHimetricLocation(info.pointerInfo); + var point = PointToClient(himetricLocation); var pointerPoint = new RawPointerPoint { @@ -1117,8 +1117,8 @@ private RawPointerPoint CreateRawPointerPoint(POINTER_TOUCH_INFO info) } private RawPointerPoint CreateRawPointerPoint(POINTER_PEN_INFO info) { - var pointerInfo = info.pointerInfo; - var point = PointToClient(new PixelPoint(pointerInfo.ptPixelLocationX, pointerInfo.ptPixelLocationY)); + var himetricLocation = GetHimetricLocation(info.pointerInfo); + var point = PointToClient(himetricLocation); return new RawPointerPoint { Position = point, @@ -1186,6 +1186,20 @@ private void UpdateInputMethod(IntPtr hkl) Imm32InputMethod.Current.SetLanguageAndWindow(this, Hwnd, hkl); } + /// + /// Get the location of the pointer in himetric units. + /// + /// The pointer info. + /// The location of the pointer in himetric units. + private Point GetHimetricLocation(POINTER_INFO info) + { + GetPointerDeviceRects(info.sourceDevice, out var pointerDeviceRect, out var displayRect); + var himetricLocation = new Point( + info.ptHimetricLocationRawX * displayRect.Width / (double)pointerDeviceRect.Width + displayRect.left, + info.ptHimetricLocationRawY * displayRect.Height / (double)pointerDeviceRect.Height + displayRect.top); + return himetricLocation; + } + private static int ToInt32(IntPtr ptr) { if (IntPtr.Size == 4) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index f18c358525a..c8139f27354 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -454,7 +454,7 @@ private bool SetTransparencyMica() { SetUseHostBackdropBrush(false); SetLegacyTransparency(false); - + CompositionEffectsSurface!.SetBlur(_currentThemeVariant switch { PlatformThemeVariant.Light => BlurEffect.MicaLight, @@ -468,7 +468,7 @@ private bool SetLegacyTransparency(bool enabled) { if (Win32Platform.WindowsVersion < PlatformConstants.Windows8 || !UseRedirectionBitmap) return false; - + // On pre-Win8 this method was blurring a window, which is a different from desired behavior. // On win8+ we use this method as a fallback, when WinUI/DComp composition with true transparency isn't available. // Note: there is no guarantee that this behavior won't be changed back to true blur in Win12. @@ -496,7 +496,7 @@ private unsafe bool SetUseHostBackdropBrush(bool useHostBackdropBrush) // AcrylicBlur requires window to set DWMWA_USE_HOSTBACKDROPBRUSH flag on Win11+. // It's not necessary on older versions and it's not necessary with Mica brush. - + var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0; var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); return result == 0; @@ -642,6 +642,12 @@ public void Invalidate(Rect rect) InvalidateRect(_hwnd, ref r, false); } + /// + /// Transform a screen pixel point to the point in the client area.
+ /// To transform a point with precise value, use the overload instead. + ///
+ /// The screen pixel point to be transformed. + /// The point in the client area. public Point PointToClient(PixelPoint point) { var p = new POINT { X = point.X, Y = point.Y }; @@ -649,6 +655,19 @@ public Point PointToClient(PixelPoint point) return new Point(p.X, p.Y) / RenderScaling; } + /// + /// Transform a screen point to the point in the client area.
+ /// Comparing to the overload, this method receives double values and can be more precise. + ///
+ /// The screen point to be transformed. + /// The point in the client area. + public Point PointToClient(Point point) + { + var p = new POINT { X = 0, Y = 0 }; + ClientToScreen(_hwnd, ref p); + return new Point(point.X - p.X, point.Y - p.Y) / RenderScaling; + } + public PixelPoint PointToScreen(Point point) { point *= RenderScaling; @@ -945,7 +964,7 @@ private void CreateWindow() Handle = new WindowImplPlatformHandle(this); - RegisterTouchWindow(_hwnd, 0); + RegisterTouchWindow(_hwnd, 0); if (ShCoreAvailable && Win32Platform.WindowsVersion >= PlatformConstants.Windows8_1) {