Skip to content

Commit

Permalink
Rewrite WindowImpl.Resize to use SetWindowPlacement
Browse files Browse the repository at this point in the history
  • Loading branch information
TomEdwardsEnscape committed Feb 20, 2024
1 parent d6f5e7d commit 92ab0e3
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 33 deletions.
59 changes: 56 additions & 3 deletions src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,58 @@ public enum SizeCommand

public enum ShowWindowCommand
{
/// <summary>
/// Hides the window and activates another window.
/// </summary>
Hide = 0,
/// <summary>
/// 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.
/// </summary>
Normal = 1,
/// <summary>
/// Activates the window and displays it as a minimized window.
/// </summary>
ShowMinimized = 2,
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
Maximize = 3,
ShowMaximized = 3,
/// <inheritdoc cref="Maximize"/>
ShowMaximized = Maximize,
/// <summary>
/// Displays a window in its most recent size and position. This value is similar to <see cref="Normal"/>, except that the window is not activated.
/// </summary>
ShowNoActivate = 4,
/// <summary>
/// Activates the window and displays it in its current size and position.
/// </summary>
Show = 5,
/// <summary>
/// Minimizes the specified window and activates the next top-level window in the Z order.
/// </summary>
Minimize = 6,
/// <summary>
/// Displays the window as a minimized window. This value is similar to <see cref="ShowMinimized"/>, except the window is not activated.
/// </summary>
ShowMinNoActive = 7,
/// <summary>
/// Displays the window in its current size and position. This value is similar to <see cref="Show"/>, except that the window is not activated.
/// </summary>
ShowNA = 8,
/// <summary>
/// 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.
/// </summary>
Restore = 9,
/// <summary>
/// Sets the show state based on the <see cref="ShowWindowCommand"/> value specified in the STARTUPINFO structure passed to the CreateProcess function
/// by the program that started the application.
/// </summary>
ShowDefault = 10,
/// <summary>
/// Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.
/// </summary>
ForceMinimize = 11
}

Expand Down Expand Up @@ -1160,6 +1200,9 @@ public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest,
[DllImport("user32.dll", SetLastError = true)]
public static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, bool bMenu, uint dwExStyle);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool AdjustWindowRectExForDpi(ref RECT lpRect, WindowStyles dwStyle, bool bMenu, WindowStyles dwExStyle, uint dpi);

[DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);

Expand Down Expand Up @@ -1287,7 +1330,7 @@ public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle)
public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
Expand Down Expand Up @@ -1374,6 +1417,8 @@ public static extern IntPtr CreateIconFromResourceEx(byte* pbIconBits, uint cbIc
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll")]
public static extern bool SetWindowPlacement(IntPtr hWnd, in WINDOWPLACEMENT windowPlacement);
[DllImport("user32.dll")]
public static extern bool SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
Expand Down Expand Up @@ -2240,6 +2285,14 @@ public struct TRACKMOUSEEVENT
public int dwHoverTime;
}

[Flags]
public enum WindowPlacementFlags : uint
{
SetMinPosition = 0x0001,
RestoreToMaximized = 0x0002,
AsyncWindowPlacement = 0x0004,
}

[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
Expand All @@ -2254,7 +2307,7 @@ public struct WINDOWPLACEMENT
/// <summary>
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
/// </summary>
public int Flags;
public WindowPlacementFlags Flags;

/// <summary>
/// The current show state of the window.
Expand Down
4 changes: 4 additions & 0 deletions src/Windows/Avalonia.Win32/PlatformConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ internal static class PlatformConstants
public const string CursorHandleType = "HCURSOR";

public static readonly Version Windows10 = new Version(10, 0);
/// <summary>
/// Windows 10 Anniversary Update
/// </summary>
public static readonly Version Windows10_1607 = new Version(10, 0, 1607);
public static readonly Version Windows8 = new Version(6, 2);
public static readonly Version Windows8_1 = new Version(6, 3);
public static readonly Version Windows7 = new Version(6, 1);
Expand Down
24 changes: 13 additions & 11 deletions src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
{
_dpi = (uint)wParam >> 16;
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
_scaling = _dpi / 96.0;
_scaling = _dpi / StandardDpi;
RefreshIcon();
ScalingChanged?.Invoke(_scaling);

Expand Down Expand Up @@ -613,14 +613,6 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
{
var size = (SizeCommand)wParam;

if (Resized != null &&
(size == SizeCommand.Restored ||
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / RenderScaling, _resizeReason);
}

var windowState = size switch
{
SizeCommand.Maximized => WindowState.Maximized,
Expand All @@ -629,10 +621,20 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
_ => WindowState.Normal,
};

if (windowState != _lastWindowState)
var stateChanged = windowState != _lastWindowState;
_lastWindowState = windowState;

if (Resized != null &&
(size == SizeCommand.Restored ||
size == SizeCommand.Maximized))
{
_lastWindowState = windowState;
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / RenderScaling, _resizeReason);
}


if (stateChanged)
{
var newWindowProperties = _windowProperties;

newWindowProperties.WindowState = windowState;
Expand Down
90 changes: 71 additions & 19 deletions src/Windows/Avalonia.Win32/WindowImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Avalonia.Threading;
using static Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl;
using static Avalonia.Controls.Platform.Win32SpecificOptions;
using Avalonia.Logging;

namespace Avalonia.Win32
{
Expand All @@ -54,6 +55,11 @@ internal partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindow
{ WindowEdge.West, HitTestValues.HTLEFT }
};

/// <summary>
/// The Windows DPI which equates to a <see cref="RenderScaling"/> of 1.0.
/// </summary>
public const double StandardDpi = 96;

private SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive;
private bool _isClientAreaExtended;
Expand Down Expand Up @@ -287,8 +293,7 @@ public WindowState WindowState
return WindowState.FullScreen;
}

var placement = default(WINDOWPLACEMENT);
GetWindowPlacement(_hwnd, ref placement);
GetWindowPlacement(_hwnd, out var placement);

return placement.ShowCmd switch
{
Expand Down Expand Up @@ -559,29 +564,59 @@ public void SetMinMaxSize(Size minSize, Size maxSize)

public void Resize(Size value, WindowResizeReason reason)
{
if (WindowState != WindowState.Normal)
return;

int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * RenderScaling);

GetClientRect(_hwnd, out var clientRect);
GetClientRect(_hwnd, out var currentClientRect);
if (currentClientRect.Width == requestedClientWidth && currentClientRect.Height == requestedClientHeight)
{
// Don't update our window position if the client size is already correct. This leads to Windows updating our
// "normal position" (i.e. restored bounds) to match our maximised or areo snap size, which is incorrect behaviour.
// We only want to proceed with this method if the new size is coming from Avalonia.
return;
}

// do comparison after scaling to avoid rounding issues
if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
if (_lastWindowState == WindowState.FullScreen)
{
GetWindowRect(_hwnd, out var windowRect);
// Fullscreen mode is really a restored window without a frame filling the whole monitor.
// It doesn't make sense to resize the window in this state, so ignore this request.
Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)?.Log(this, "Ignoring resize event on fullscreen window.");
return;
}

using var scope = SetResizeReason(reason);
SetWindowPos(
_hwnd,
IntPtr.Zero,
0,
0,
requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
SetWindowPosFlags.SWP_RESIZE);
GetWindowPlacement(_hwnd, out var windowPlacement);

var clientScreenOrigin = new POINT();
ClientToScreen(_hwnd, ref clientScreenOrigin);

var requestedClientRect = new RECT
{
left = clientScreenOrigin.X,
right = clientScreenOrigin.X + requestedClientWidth,

top = clientScreenOrigin.Y,
bottom = clientScreenOrigin.Y + requestedClientHeight,
};

var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect);

if (requestedWindowRect.Width == windowPlacement.NormalPosition.Width && requestedWindowRect.Height == windowPlacement.NormalPosition.Height)
{
return;
}

windowPlacement.NormalPosition = requestedWindowRect;

windowPlacement.ShowCmd = _lastWindowState switch
{
WindowState.Minimized => ShowWindowCommand.ShowMinNoActive,
WindowState.Maximized => ShowWindowCommand.ShowMaximized,
WindowState.Normal => ShowWindowCommand.ShowNoActivate,
_ => throw new NotImplementedException(),
};

using var scope = SetResizeReason(reason);
SetWindowPlacement(_hwnd, in windowPlacement);
}

public void Activate()
Expand Down Expand Up @@ -913,7 +948,7 @@ private void CreateWindow()
out _dpi,
out _) == 0)
{
_scaling = _dpi / 96.0;
_scaling = _dpi / StandardDpi;
}
}
}
Expand Down Expand Up @@ -1473,6 +1508,23 @@ private static void EnableCloseButton(IntPtr hwnd)
MF_BYCOMMAND | MF_ENABLED);
}

private RECT ClientRectToWindowRect(RECT clientRect, WindowStyles? styleOverride = null, WindowStyles? extendedStyleOverride = null)
{
var style = styleOverride ?? GetStyle();
var extendedStyle = extendedStyleOverride ?? GetExtendedStyle();

var result = Win32Platform.WindowsVersion < PlatformConstants.Windows10_1607
? AdjustWindowRectEx(ref clientRect, (uint)style, false, (uint)extendedStyle)
: AdjustWindowRectExForDpi(ref clientRect, style, false, extendedStyle, (uint)(RenderScaling * StandardDpi));

if (!result)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}

return clientRect;
}

#if USE_MANAGED_DRAG
private Point ScreenToClient(Point point)
{
Expand Down

0 comments on commit 92ab0e3

Please sign in to comment.