using System; using Avalonia.Controls; using Avalonia.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; #nullable enable namespace Avalonia.Win32 { public partial class WindowImpl { // Hit test the frame for resizing and moving. private HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) { // Get the point coordinates for the hit test (screen space). var ptMouse = WindowImpl.PointFromLParam(lParam); // Get the window rectangle. GetWindowRect(hWnd, out var rcWindow); // Get the frame rectangle, adjusted for the style without a caption. var rcFrame = new RECT(); AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); var borderThickness = new RECT(); AdjustWindowRectEx(ref borderThickness, (uint)GetStyle(), false, 0); borderThickness.left *= -1; borderThickness.top *= -1; if (_extendTitleBarHint >= 0) { borderThickness.top = (int)(_extendedMargins.Top * RenderScaling); } // Determine if the hit test is for resizing. Default middle (1,1). ushort uRow = 1; ushort uCol = 1; bool onResizeBorder = false; // Determine if the point is at the left or right of the window. if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left) { uCol = 0; // left side } else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right) { uCol = 2; // right side } // Determine if the point is at the top or bottom of the window. if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + borderThickness.top) { onResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); // Two cases where we have a valid row 0 hit test: // - window resize border (top resize border hit) // - area below resize border that is actual titlebar (caption hit). if (onResizeBorder || uCol == 1) { uRow = 0; } } else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - borderThickness.bottom) { uRow = 2; } ReadOnlySpan hitZones = stackalloc HitTestValues[] { HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT, HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT, HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }; var zoneIndex = uRow * 3 + uCol; return hitZones[zoneIndex]; } protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp) { IntPtr lRet = IntPtr.Zero; callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet); switch ((WindowsMessage)msg) { case WindowsMessage.WM_DWMCOMPOSITIONCHANGED: // TODO handle composition changed. break; case WindowsMessage.WM_NCHITTEST: if (lRet == IntPtr.Zero) { if(WindowState == WindowState.FullScreen) { return (IntPtr)HitTestValues.HTCLIENT; } var hittestResult = HitTestNCA(hWnd, wParam, lParam); lRet = (IntPtr)hittestResult; uint timestamp = unchecked((uint)GetMessageTime()); if (hittestResult == HitTestValues.HTCAPTION) { var position = PointToClient(WindowImpl.PointFromLParam(lParam)); if (_owner is Window window) { var visual = window.Renderer.HitTestFirst(position, window, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { return false; } return true; }); if (visual != null) { hittestResult = HitTestValues.HTCLIENT; lRet = (IntPtr)hittestResult; } } } if (hittestResult != HitTestValues.HTNOWHERE) { callDwp = false; } } break; } return lRet; } } }