@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel ;
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
using Avalonia.Collections.Pooled ;
using Avalonia.Controls ;
@ -61,7 +62,6 @@ namespace Avalonia.Win32
private bool _ isFullScreenActive ;
private bool _ isClientAreaExtended ;
private Thickness _ extendedMargins ;
private Thickness _ offScreenMargin ;
private double _ extendTitleBarHint = - 1 ;
private WindowResizeReason _ resizeReason ;
private MOUSEMOVEPOINT _l astWmMousePoint ;
@ -150,6 +150,8 @@ namespace Avalonia.Win32
_ wmPointerEnabled = Win32Platform . WindowsVersion > = PlatformConstants . Windows8 ;
Screen = Win32Platform . Instance . Screen ;
CreateWindow ( ) ;
_f ramebuffer = new FramebufferManager ( _ hwnd ) ;
@ -173,7 +175,6 @@ namespace Avalonia.Win32
}
}
Screen = Win32Platform . Instance . Screen ;
_ storageProvider = new Win32StorageProvider ( this ) ;
_ inputPane = WindowsInputPane . TryCreate ( this ) ;
_ nativeControlHost = new Win32NativeControlHost ( this , ! UseRedirectionBitmap ) ;
@ -240,8 +241,6 @@ namespace Avalonia.Win32
}
}
private double PrimaryScreenRenderScaling = > Screen . AllScreens . FirstOrDefault ( screen = > screen . IsPrimary ) ? . Scaling ? ? 1 ;
private ICompositionEffectsSurface ? CompositionEffectsSurface = > _ glSurface as ICompositionEffectsSurface ;
private bool UseRedirectionBitmap { get ; }
@ -573,10 +572,11 @@ namespace Avalonia.Win32
return ;
}
if ( _l astWindowState = = WindowState . FullScreen )
if ( _l astWindowState = = WindowState . FullScreen & & _ isFullScreenActive )
{
// 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.
// (If the fullscreen mode isn't yet active, continue normally so that our normal window size gets saved.)
Logger . TryGet ( LogEventLevel . Warning , LogArea . Win32Platform ) ? . Log ( this , "Ignoring resize event on fullscreen window." ) ;
return ;
}
@ -591,7 +591,19 @@ namespace Avalonia.Win32
bottom = requestedClientHeight
} ;
var requestedWindowRect = _ isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect ( requestedClientRect ) ;
var requestedWindowRect = ClientRectToWindowRect ( requestedClientRect ) ;
if ( _ isClientAreaExtended )
{
// We told Windows we have a caption, but since we're actually extending into it,
// it should be excluded from the final window bounds.
if ( _ windowProperties . Decorations ! = SystemDecorations . None )
{
var borderOnlyRect = ClientRectToWindowRect ( requestedClientRect , WindowStyles . WS_BORDER ) ;
requestedWindowRect . top = borderOnlyRect . top ;
}
}
var windowWidth = requestedWindowRect . Width ;
var windowHeight = requestedWindowRect . Height ;
@ -1048,29 +1060,53 @@ namespace Avalonia.Win32
{
if ( fullscreen )
{
GetWindowRect ( _ hwnd , out var windowRect ) ;
_ savedWindowInfo . WindowRect = windowRect ;
var current = GetStyle ( ) ;
var currentEx = GetExtendedStyle ( ) ;
Screen ? screen ;
GetWindowPlacement ( _ hwnd , out var placement ) ;
var isMinimized = placement . ShowCmd = = ShowWindowCommand . ShowMinimized ;
RECT windowRect ;
// When minimized, we can't use GetWindowRect since the window is actually way outside the screen.
// Instead, fall back to WINDOWPLACEMENT.NormalPosition (which is in working area coordinates).
if ( isMinimized )
{
windowRect = placement . NormalPosition ;
screen = Screen . ScreenFromRect ( windowRect . ToPixelRect ( ) ) ;
if ( screen ? . WorkingArea is { } workingArea )
{
windowRect . left + = workingArea . X ;
windowRect . top + = workingArea . Y ;
windowRect . right + = workingArea . X ;
windowRect . bottom + = workingArea . Y ;
}
}
else
{
GetWindowRect ( _ hwnd , out windowRect ) ;
screen = Screen . ScreenFromHwnd ( _ hwnd , MONITOR . MONITOR_DEFAULTTONEAREST ) ;
}
_ savedWindowInfo . WindowRect = windowRect ;
_ savedWindowInfo . Style = current ;
_ savedWindowInfo . ExStyle = currentEx ;
// Set new window style and size.
SetStyle ( current & ~ ( WindowStyles . WS_CAPTION | WindowStyles . WS_THICKFRAME ) , false ) ;
SetStyle ( current & ~ WindowStyles . WS_OVERLAPPEDWINDOW , false ) ;
SetExtendedStyle ( currentEx & ~ ( WindowStyles . WS_EX_DLGMODALFRAME | WindowStyles . WS_EX_WINDOWEDGE | WindowStyles . WS_EX_CLIENTEDGE | WindowStyles . WS_EX_STATICEDGE ) , false ) ;
// On expand, if we're given a window_rect, grow to it, otherwise do
// not resize.
var screen = Screen . ScreenFromHwnd ( _ hwnd , MONITOR . MONITOR_DEFAULTTONEAREST ) ;
if ( screen ? . Bounds is { } window_rect )
if ( screen ? . Bounds is { } screenBounds )
{
_ isFullScreenActive = true ;
SetWindowPos ( _ hwnd , IntPtr . Zero , window_rect . X , window_rect . Y ,
window_rect . Width , window_rect . Height ,
SetWindowPosFlags . SWP_NOZORDER | SetWindowPosFlags . SWP_NOACTIVATE | SetWindowPosFlags . SWP_FRAMECHANGED ) ;
if ( isMinimized )
UnmanagedMethods . ShowWindow ( _ hwnd , ShowWindowCommand . Restore ) ;
SetWindowPos ( _ hwnd , IntPtr . Zero , screenBounds . X , screenBounds . Y , screenBounds . Width , screenBounds . Height ,
SetWindowPosFlags . SWP_NOZORDER | SetWindowPosFlags . SWP_NOACTIVATE | SetWindowPosFlags . SWP_FRAMECHANGED ) ;
}
}
else
@ -1101,44 +1137,27 @@ namespace Avalonia.Win32
private MARGINS UpdateExtendMargins ( )
{
RECT borderThickness = new RECT ( ) ;
RECT borderCaptionThickness = new RECT ( ) ;
var scaling = ( uint ) ( RenderScaling * StandardDpi ) ;
var relativeScaling = RenderScaling / PrimaryScreenRenderScaling ;
if ( Win32Platform . WindowsVersion < PlatformConstants . Windows10_1607 )
{
AdjustWindowRectEx ( ref borderCaptionThickness , ( uint ) GetStyle ( ) , false , 0 ) ;
AdjustWindowRectEx ( ref borderThickness , ( uint ) ( GetStyle ( ) & ~ WindowStyles . WS_CAPTION ) , false , 0 ) ;
var borderThickness = new RECT ( ) ;
var borderCaptionThickness = new RECT ( ) ;
var style = GetStyle ( ) ;
borderCaptionThickness . top = ( int ) ( borderCaptionThickness . top * relativeScaling ) ;
borderCaptionThickness . right = ( int ) ( borderCaptionThickness . right * relativeScaling ) ;
borderCaptionThickness . left = ( int ) ( borderCaptionThickness . left * relativeScaling ) ;
borderCaptionThickness . bottom = ( int ) ( borderCaptionThickness . bottom * relativeScaling ) ;
borderThickness . top = ( int ) ( borderThickness . top * relativeScaling ) ;
borderThickness . right = ( int ) ( borderThickness . right * relativeScaling ) ;
borderThickness . left = ( int ) ( borderThickness . left * relativeScaling ) ;
borderThickness . bottom = ( int ) ( borderThickness . bottom * relativeScaling ) ;
}
else
{
AdjustWindowRectExForDpi ( ref borderCaptionThickness , GetStyle ( ) , false , 0 , scaling ) ;
AdjustWindowRectExForDpi ( ref borderThickness , GetStyle ( ) & ~ WindowStyles . WS_CAPTION , false , 0 , scaling ) ;
}
var adjuster = CreateWindowRectAdjuster ( ) ;
adjuster . Adjust ( ref borderCaptionThickness , style , 0 ) ;
adjuster . Adjust ( ref borderThickness , style & ~ WindowStyles . WS_CAPTION , 0 ) ;
borderThickness . left * = - 1 ;
borderThickness . top * = - 1 ;
borderCaptionThickness . left * = - 1 ;
borderCaptionThickness . top * = - 1 ;
bool wantsTitleBar = _ extendChromeHints . HasAllFlags ( ExtendClientAreaChromeHints . SystemChrome ) | | _ extendTitleBarHint = = - 1 ;
if ( ! wantsTitleBar )
if ( _ extendChromeHints . HasAnyFlag ( ExtendClientAreaChromeHints . SystemChrome | ExtendClientAreaChromeHints . PreferSystemChrome ) & &
_ windowProperties . Decorations = = SystemDecorations . Full )
{
borderCaptionThickness . top = 1 ;
if ( _ extendTitleBarHint ! = - 1 )
borderCaptionThickness . top = ( int ) ( _ extendTitleBarHint * RenderScaling ) ;
}
else
borderCaptionThickness . top = borderThickness . top ;
//using a default margin of 0 when using WinUiComp removes artefacts when resizing. See issue #8316
var defaultMargin = UseRedirectionBitmap ? 1 : 0 ;
@ -1148,22 +1167,15 @@ namespace Avalonia.Win32
margins . cxRightWidth = defaultMargin ;
margins . cyBottomHeight = defaultMargin ;
if ( _ extendTitleBarHint ! = - 1 )
{
borderCaptionThickness . top = ( int ) ( _ extendTitleBarHint * RenderScaling ) ;
}
margins . cyTopHeight = _ extendChromeHints . HasAllFlags ( ExtendClientAreaChromeHints . SystemChrome ) & & ! _ extendChromeHints . HasAllFlags ( ExtendClientAreaChromeHints . PreferSystemChrome ) ? borderCaptionThickness . top : defaultMargin ;
if ( WindowState = = WindowState . Maximized )
{
_ extendedMargins = new Thickness ( 0 , ( borderCaptionThickness . top - borderThickness . top ) / RenderScaling , 0 , 0 ) ;
_ offScreenMargin = new Thickness ( borderThickness . left / RenderScaling , borderThickness . top / RenderScaling , borderThickness . right / RenderScaling , borderThickness . bottom / RenderScaling ) ;
}
else
{
_ extendedMargins = new Thickness ( 0 , ( borderCaptionThickness . top ) / RenderScaling , 0 , 0 ) ;
_ offScreenMargin = new Thickness ( ) ;
}
return margins ;
@ -1183,15 +1195,22 @@ namespace Avalonia.Win32
}
GetWindowRect ( _ hwnd , out var rcWindow ) ;
if ( _ isClientAreaExtended & & WindowState ! = WindowState . FullScreen )
if ( _ isClientAreaExtended & & WindowState ! = WindowState . FullScreen & & GetStyle ( ) . HasAllFlags ( WindowStyles . WS_BORDER ) )
{
var margins = UpdateExtendMargins ( ) ;
DwmExtendFrameIntoClientArea ( _ hwnd , ref margins ) ;
unsafe
// On Windows 11 21H2 and later, corners are configurable.
// When doing so, we need to make sure that DWM draws the non-client frame for that to work correctly.
if ( OperatingSystem . IsWindowsVersionAtLeast ( 1 0 , 0 , 2 2 0 0 0 ) ) {
SetWindowCornerPreference ( DwmWindowCornerPreference . DWMWCP_ROUND ) ;
SetNCRenderingPolicy ( DwmNCRenderingPolicy . DWMNCRP_ENABLED ) ;
}
else
{
int cornerPreference = ( int ) DwmWindowCornerPreference . DWMWCP_ROUND ;
DwmSetWindowAttribute ( _ hwnd , ( int ) DwmWindowAttribute . DWMWA_WINDOW_CORNER_PREFERENCE , & cornerPreference , sizeof ( int ) ) ;
// On older versions, we need to disable painting the non-client area to avoid issues
// (alternatively, we could return 0 in WM_NCPAINT in this case).
SetNCRenderingPolicy ( DwmNCRenderingPolicy . DWMNCRP_DISABLED ) ;
}
}
else
@ -1199,9 +1218,13 @@ namespace Avalonia.Win32
var margins = new MARGINS ( ) ;
DwmExtendFrameIntoClientArea ( _ hwnd , ref margins ) ;
_ offScreenMargin = new Thickness ( ) ;
_ extendedMargins = new Thickness ( ) ;
if ( OperatingSystem . IsWindowsVersionAtLeast ( 1 0 , 0 , 2 2 0 0 0 ) )
SetWindowCornerPreference ( DwmWindowCornerPreference . DWMWCP_DEFAULT ) ;
SetNCRenderingPolicy ( DwmNCRenderingPolicy . DWMNCRP_USEWINDOWSTYLE ) ;
unsafe
{
int cornerPreference = ( int ) DwmWindowCornerPreference . DWMWCP_DEFAULT ;
@ -1229,6 +1252,12 @@ namespace Avalonia.Win32
ExtendClientAreaToDecorationsChanged ? . Invoke ( _ isClientAreaExtended ) ;
}
private unsafe void SetWindowCornerPreference ( DwmWindowCornerPreference value )
= > DwmSetWindowAttribute ( _ hwnd , ( int ) DwmWindowAttribute . DWMWA_WINDOW_CORNER_PREFERENCE , & value , sizeof ( int ) ) ;
private unsafe void SetNCRenderingPolicy ( DwmNCRenderingPolicy value )
= > DwmSetWindowAttribute ( _ hwnd , ( int ) DwmWindowAttribute . DWMWA_NCRENDERING_POLICY , & value , sizeof ( int ) ) ;
private void ShowWindow ( WindowState state , bool activate )
{
if ( _ isClientAreaExtended )
@ -1275,11 +1304,6 @@ namespace Avalonia.Win32
UnmanagedMethods . ShowWindow ( _ hwnd , command . Value ) ;
}
if ( state = = WindowState . Maximized )
{
MaximizeWithoutCoveringTaskbar ( ) ;
}
if ( ! Design . IsDesignMode & & activate )
{
SetFocus ( _ hwnd ) ;
@ -1322,33 +1346,6 @@ namespace Avalonia.Win32
}
}
private void MaximizeWithoutCoveringTaskbar ( )
{
var screen = Screen . ScreenFromHwnd ( Hwnd , MONITOR . MONITOR_DEFAULTTONEAREST ) ;
if ( screen ? . WorkingArea is { } workingArea )
{
var x = workingArea . X ;
var y = workingArea . Y ;
var cx = workingArea . Width ;
var cy = workingArea . Height ;
var style = ( WindowStyles ) GetWindowLong ( _ hwnd , ( int ) WindowLongParam . GWL_STYLE ) ;
if ( ! style . HasFlag ( WindowStyles . WS_THICKFRAME ) )
{
// When calling SetWindowPos on a maximized window it automatically adjusts
// for "hidden" borders which are placed offscreen, EVEN IF THE WINDOW HAS
// NO BORDERS, meaning that the window is placed wrong when we have CanResize
// == false. Account for this here.
var borderThickness = BorderThickness ;
x - = ( int ) borderThickness . Left ;
cx + = ( int ) borderThickness . Left + ( int ) borderThickness . Right ;
cy + = ( int ) borderThickness . Bottom ;
}
SetWindowPos ( _ hwnd , WindowPosZOrder . HWND_NOTOPMOST , x , y , cx , cy , SetWindowPosFlags . SWP_SHOWWINDOW | SetWindowPosFlags . SWP_FRAMECHANGED ) ;
}
}
private WindowStyles GetWindowStateStyles ( )
{
return GetStyle ( ) & WindowStateMask ;
@ -1462,7 +1459,7 @@ namespace Avalonia.Win32
exStyle & = ~ WindowStyles . WS_EX_APPWINDOW ;
}
WindowStyles style = WindowStyles . WS_CLIPCHILDREN | WindowStyles . WS_OVERLAPPEDWINDOW | WindowStyles . WS_CLIPSIBLINGS ;
var style = WindowStyles . WS_CLIPCHILDREN | WindowStyles . WS_CLIPSIBLINGS ;
if ( this is EmbeddedWindowImpl )
style | = WindowStyles . WS_CHILD ;
@ -1470,40 +1467,26 @@ namespace Avalonia.Win32
if ( IsWindowVisible ( _ hwnd ) )
style | = WindowStyles . WS_VISIBLE ;
if ( newProperties . IsResizable | | newProperties . WindowState = = WindowState . Maximized )
style | = WindowStyles . WS_THICKFRAME ;
els e
style & = ~ WindowStyles . WS_THICKFRAME ;
switch ( newProperties . Decorations )
{
cas e Syst emDecorations . Full :
style | = WindowStyles . WS_BORDER | WindowStyles . WS_CAPTION | WindowStyles . WS_SYSMENU ;
if ( newProperties . IsMinimizable )
style | = WindowStyles . WS_MINIMIZEBOX ;
else
style & = ~ WindowStyles . WS_MINIMIZEBOX ;
if ( newProperties . IsMinimizable )
style | = WindowStyles . WS_MINIMIZEBOX ;
if ( newProperties . IsMaximizable | | ( newProperties . WindowState = = WindowState . Maximized & & newProperties . IsResizable ) )
style | = WindowStyles . WS_MAXIMIZEBOX ;
else
style & = ~ WindowStyles . WS_MAXIMIZEBOX ;
if ( newProperties . IsMaximizable | | ( newProperties . WindowState = = WindowState . Maximized & & newProperties . IsResizable ) )
style | = WindowStyles . WS_MAXIMIZEBOX ;
const WindowStyles fullDecorationFlags = WindowStyles . WS_CAPTION | WindowStyles . WS_BORDER | WindowStyles . WS_SYSMENU ;
break ;
if ( newProperties . Decorations = = SystemDecorations . Full )
{
style | = fullDecorationFlags ;
case SystemDecorations . BorderOnly :
style | = WindowStyles . WS_BORDER ;
break ;
}
else
{
style & = ~ ( fullDecorationFlags | WindowStyles . WS_THICKFRAME ) ;
if ( newProperties . Decorations = = SystemDecorations . BorderOnly & & newProperties . WindowState ! = WindowState . Maximized & & newProperties . IsResizable )
{
style | = WindowStyles . WS_THICKFRAME | WindowStyles . WS_BORDER ;
}
else if ( newProperties . WindowState = = WindowState . Maximized & & _ isClientAreaExtended )
{
style | = WindowStyles . WS_THICKFRAME ;
}
}
if ( newProperties . Decorations ! = SystemDecorations . None & & newProperties . IsResizable )
style | = WindowStyles . WS_THICKFRAME ;
var windowStates = GetWindowStateStyles ( ) ;
style & = ~ WindowStateMask ;
@ -1577,14 +1560,8 @@ namespace Avalonia.Win32
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 ( ) ) ;
}
var adjuster = CreateWindowRectAdjuster ( ) ;
adjuster . Adjust ( ref clientRect , style , extendedStyle ) ;
return clientRect ;
}
@ -1681,7 +1658,7 @@ namespace Avalonia.Win32
public Thickness ExtendedMargins = > _ extendedMargins ;
/// <inheritdoc/>
public Thickness OffScreenMargin = > _ offScreenMargin ;
public Thickness OffScreenMargin = > default ;
/// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get ; } = new AcrylicPlatformCompensationLevels ( 1 , 0.8 , 0 ) ;
@ -1692,6 +1669,9 @@ namespace Avalonia.Win32
/// <inheritdoc/>
public CustomWndProcHookCallback ? WndProcHookCallback { get ; set ; }
private WindowRectAdjuster CreateWindowRectAdjuster ( )
= > new ( this ) ;
private ResizeReasonScope SetResizeReason ( WindowResizeReason reason )
{
var old = _ resizeReason ;
@ -1704,7 +1684,7 @@ namespace Avalonia.Win32
public WindowStyles Style { get ; set ; }
public WindowStyles ExStyle { get ; set ; }
public RECT WindowRect { get ; set ; }
} ;
}
protected struct WindowProperties
{
@ -1748,5 +1728,38 @@ namespace Avalonia.Win32
public int Time ;
public PixelPoint Pt ;
}
private struct WindowRectAdjuster
{
private static readonly bool s_hasAdjustWindowRectExForDpi = OperatingSystem . IsWindowsVersionAtLeast ( 1 0 , 0 , 1 4 3 9 3 ) ;
private readonly double _ relativeScaling ;
private readonly uint _d pi ;
public WindowRectAdjuster ( WindowImpl owner )
{
if ( s_hasAdjustWindowRectExForDpi )
_d pi = ( uint ) ( owner . RenderScaling * StandardDpi ) ;
else
{
var primaryScaling = owner . Screen . AllScreens . FirstOrDefault ( screen = > screen . IsPrimary ) ? . Scaling ? ? 1 ;
_ relativeScaling = owner . RenderScaling / primaryScaling ;
}
}
public void Adjust ( ref RECT rect , WindowStyles style , WindowStyles exStyle )
{
if ( s_hasAdjustWindowRectExForDpi )
AdjustWindowRectExForDpi ( ref rect , style , false , exStyle , _d pi ) ;
else
{
AdjustWindowRectEx ( ref rect , ( uint ) style , false , ( uint ) exStyle ) ;
rect . top = ( int ) ( rect . top * _ relativeScaling ) ;
rect . right = ( int ) ( rect . right * _ relativeScaling ) ;
rect . left = ( int ) ( rect . left * _ relativeScaling ) ;
rect . bottom = ( int ) ( rect . bottom * _ relativeScaling ) ;
}
}
}
}
}