From a4544be13451f40e26344608bb008f3c80552fd9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 13:53:38 -0300 Subject: [PATCH 001/109] Add WndProcs to handle extended frame windows. --- .../Interop/UnmanagedMethods.cs | 3 + .../Avalonia.Win32/WindowImpl.WndProc.cs | 155 +++++++++++++++++- 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ba3775200b..b66de8e0a8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1311,6 +1311,9 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern int DwmIsCompositionEnabled(out bool enabled); + [DllImport("dwmapi.dll")] + public static extern bool DwmDefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult); + [DllImport("dwmapi.dll")] public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index 50e71aeebe..55b5db3bba 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -12,11 +12,164 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { + public partial class WindowImpl + { + private int LEFTEXTENDWIDTH = 4; + private int RIGHTEXTENDWIDTH = 4; + private int BOTTOMEXTENDWIDTH = 4; + private int TOPEXTENDWIDTH = 100; + + // Hit test the frame for resizing and moving. + HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) + { + // Get the point coordinates for the hit test. + var ptMouse = PointFromLParam(lParam); + + // Get the window rectangle. + GetWindowRect(hWnd, out var rcWindow); + + // Get the frame rectangle, adjusted for the style without a caption. + RECT rcFrame = new RECT(); + AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); + + // Determine if the hit test is for resizing. Default middle (1,1). + ushort uRow = 1; + ushort uCol = 1; + bool fOnResizeBorder = false; + + // Determine if the point is at the top or bottom of the window. + if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + TOPEXTENDWIDTH) + { + fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); + uRow = 0; + } + else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - BOTTOMEXTENDWIDTH) + { + uRow = 2; + } + + // Determine if the point is at the left or right of the window. + if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + LEFTEXTENDWIDTH) + { + uCol = 0; // left side + } + else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - RIGHTEXTENDWIDTH) + { + uCol = 2; // right side + } + + // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + HitTestValues[][] hitTests = new[] + { + new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, + new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, + new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, + }; + + System.Diagnostics.Debug.WriteLine(hitTests[uRow][uCol]); + + return hitTests[uRow][uCol]; + } + + protected virtual unsafe 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); + + MARGINS margins = new MARGINS { cxLeftWidth = 0, cxRightWidth = 0, cyBottomHeight = 0, cyTopHeight = 100 }; + RECT border_thickness = new RECT(); + + switch ((WindowsMessage)msg) + { + case WindowsMessage.WM_ACTIVATE: + { + if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) + { + AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); + border_thickness.left *= -1; + border_thickness.top *= -1; + } + else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) + { + border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + } + + LEFTEXTENDWIDTH = border_thickness.left; + RIGHTEXTENDWIDTH = border_thickness.right; + BOTTOMEXTENDWIDTH = border_thickness.bottom; + + // Extend the frame into the client area. + margins.cxLeftWidth = border_thickness.left; + margins.cxRightWidth = border_thickness.right; + margins.cyBottomHeight = border_thickness.bottom; + margins.cyTopHeight = border_thickness.top; + + var hr = DwmExtendFrameIntoClientArea(hWnd, ref margins); + + //if (hr < 0) + { + // Handle the error. + } + + lRet = IntPtr.Zero; + callDwp = true; + break; + } + + case WindowsMessage.WM_NCCALCSIZE: + { + if (ToInt32(wParam) == 1) + { + lRet = IntPtr.Zero; + callDwp = false; + } + break; + } + + case WindowsMessage.WM_NCHITTEST: + if (lRet == IntPtr.Zero) + { + lRet = (IntPtr)HitTestNCA(hWnd, wParam, lParam); + + if (((HitTestValues)lRet) != HitTestValues.HTNOWHERE) + { + callDwp = false; + } + } + break; + } + + return lRet; + } + } + + public partial class WindowImpl + { + protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + IntPtr lRet = IntPtr.Zero; + bool callDwp = true; + + if (DwmIsCompositionEnabled(out bool enabled) == 0) + { + lRet = CustomCaptionProc(hWnd, msg, wParam, lParam, ref callDwp); + } + + if (callDwp) + { + lRet = AppWndProc(hWnd, msg, wParam, lParam); + } + + return lRet; + } + } + public partial class WindowImpl { [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] - protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { const double wheelDelta = 120.0; uint timestamp = unchecked((uint)GetMessageTime()); From 9b9b2d8330ba870c77bb4b9b3b77d1ed5e57fcfc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 15:12:32 -0300 Subject: [PATCH 002/109] fallback transparency should be null so that it doesnt hit test. --- src/Avalonia.Controls/TopLevel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5d34444eb8..0a3e758423 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -399,7 +399,7 @@ namespace Avalonia.Controls } else { - _transparencyFallbackBorder.Background = Brushes.Transparent; + _transparencyFallbackBorder.Background = null; } } From 9a60bbf528c5c5f4a1034a60fb497e8be0810e63 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 15:13:13 -0300 Subject: [PATCH 003/109] render hit test non-client area. --- .../Avalonia.Win32/WindowImpl.WndProc.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index 55b5db3bba..bc13fb83e9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -98,13 +98,14 @@ namespace Avalonia.Win32 LEFTEXTENDWIDTH = border_thickness.left; RIGHTEXTENDWIDTH = border_thickness.right; BOTTOMEXTENDWIDTH = border_thickness.bottom; + TOPEXTENDWIDTH = border_thickness.top; // Extend the frame into the client area. margins.cxLeftWidth = border_thickness.left; margins.cxRightWidth = border_thickness.right; margins.cyBottomHeight = border_thickness.bottom; margins.cyTopHeight = border_thickness.top; - + var hr = DwmExtendFrameIntoClientArea(hWnd, ref margins); //if (hr < 0) @@ -132,6 +133,21 @@ namespace Avalonia.Win32 { lRet = (IntPtr)HitTestNCA(hWnd, wParam, lParam); + uint timestamp = unchecked((uint)GetMessageTime()); + + if (((HitTestValues)lRet) == HitTestValues.HTCAPTION) + { + var position = PointToClient(PointFromLParam(lParam)); + + var visual = (_owner as Window).Renderer.HitTestFirst(position, _owner as Window, null); + + if(visual != null) + { + lRet = (IntPtr)HitTestValues.HTCLIENT; + } + + } + if (((HitTestValues)lRet) != HitTestValues.HTNOWHERE) { callDwp = false; From 1d12a4c044f95d8419eff874040e4717a0d61538 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 15:39:46 -0300 Subject: [PATCH 004/109] working win32 non-client area hit testing. --- src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index bc13fb83e9..dedef2ded3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -138,8 +138,16 @@ namespace Avalonia.Win32 if (((HitTestValues)lRet) == HitTestValues.HTCAPTION) { var position = PointToClient(PointFromLParam(lParam)); - - var visual = (_owner as Window).Renderer.HitTestFirst(position, _owner as Window, null); + + var visual = (_owner as Window).Renderer.HitTestFirst(position, _owner as Window, x => + { + if(x is IInputElement ie && !ie.IsHitTestVisible) + { + return false; + } + + return true; + }); if(visual != null) { From 1168dd186d5d5bd0e470994c06ff20de21c31bc0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 16:54:20 -0300 Subject: [PATCH 005/109] add client area extending api to window. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 6 ++ src/Avalonia.Controls/Window.cs | 62 +++++++++++++++ .../Remote/PreviewerWindowImpl.cs | 6 ++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 6 ++ src/Avalonia.Native/WindowImpl.cs | 6 ++ src/Avalonia.X11/X11Window.cs | 6 ++ .../Avalonia.Win32/WindowImpl.WndProc.cs | 77 ++++++++++--------- 7 files changed, 134 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index cf31d30332..9c4808a41c 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -94,5 +94,11 @@ namespace Avalonia.Platform /// /// void SetMinMaxSize(Size minSize, Size maxSize); + + bool ExtendClientAreaToDecorationsHint { get; set; } + + Action ExtendClientAreaToDecorationsChanged { get; set; } + + Thickness ExtendedMargins { get; } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ff7cc41e3b..04b48d7f9f 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -71,6 +71,8 @@ namespace Avalonia.Controls { private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); + private bool _isExtendedIntoWindowDecorations; + /// /// Defines the property. /// @@ -87,6 +89,29 @@ namespace Avalonia.Controls o => o.HasSystemDecorations, (o, v) => o.HasSystemDecorations = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = + AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); + + + /// + /// Defines the property. + /// + public static readonly DirectProperty IsExtendedIntoWindowDecorationsProperty = + AvaloniaProperty.RegisterDirect(nameof(IsExtendedIntoWindowDecorations), + o => o.IsExtendedIntoWindowDecorations, + unsetValue: false); + + /// + /// Defines the property. + /// + public static readonly DirectProperty WindowDecorationMarginsProperty = + AvaloniaProperty.RegisterDirect(nameof(WindowDecorationMargins), + o => o.WindowDecorationMargins); + + /// /// Defines the property. /// @@ -164,6 +189,9 @@ namespace Avalonia.Controls WindowStateProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); + ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler( + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (bool)e.NewValue; }); + MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); @@ -189,6 +217,7 @@ namespace Avalonia.Controls impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); + impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); @@ -237,6 +266,32 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). + /// + public bool ExtendClientAreaToDecorationsHint + { + get { return GetValue(ExtendClientAreaToDecorationsHintProperty); } + set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); } + } + + /// + /// Gets if the ClientArea is Extended into the Window Decorations. + /// + public bool IsExtendedIntoWindowDecorations + { + get => _isExtendedIntoWindowDecorations; + private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value); + } + + private Thickness _windowDecorationMargins; + + public Thickness WindowDecorationMargins + { + get => _windowDecorationMargins; + private set => SetAndRaise(WindowDecorationMarginsProperty, ref _windowDecorationMargins, value); + } + /// /// Sets the system decorations (title bar, border, etc) /// @@ -435,6 +490,13 @@ namespace Avalonia.Controls } } + protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended) + { + IsExtendedIntoWindowDecorations = isExtended; + + WindowDecorationMargins = PlatformImpl.ExtendedMargins; + } + /// /// Hides the window but does not close it. /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index cd64af60e2..441a79531a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -84,6 +84,12 @@ namespace Avalonia.DesignerSupport.Remote public IScreenImpl Screen { get; } = new ScreenStub(); public Action GotInputWhenDisabled { get; set; } + + public bool ExtendClientAreaToDecorationsHint { get; set; } + + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + public Thickness ExtendedMargins { get; } = new Thickness(); public void Activate() { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index b001bc1b76..f962382575 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -39,6 +39,12 @@ namespace Avalonia.DesignerSupport.Remote public Action TransparencyLevelChanged { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } + + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + public Thickness ExtendedMargins { get; } = new Thickness(); + public WindowStub(IWindowImpl parent = null) { if (parent != null) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index e91445000a..3495c4b392 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -98,6 +98,12 @@ namespace Avalonia.Native public Action WindowStateChanged { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } + + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + public Thickness ExtendedMargins { get; } = new Thickness(); + public void ShowTaskbarIcon(bool value) { // NO OP On OSX diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 90064cb28d..ac9048adad 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -311,6 +311,12 @@ namespace Avalonia.X11 set => _transparencyHelper.TransparencyLevelChanged = value; } + public bool ExtendClientAreaToDecorationsHint { get; set; } + + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + public Thickness ExtendedMargins { get; } = new Thickness(); + public Action Closed { get; set; } public Action PositionChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index dedef2ded3..3ce34250fa 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -14,11 +14,6 @@ namespace Avalonia.Win32 { public partial class WindowImpl { - private int LEFTEXTENDWIDTH = 4; - private int RIGHTEXTENDWIDTH = 4; - private int BOTTOMEXTENDWIDTH = 4; - private int TOPEXTENDWIDTH = 100; - // Hit test the frame for resizing and moving. HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) { @@ -32,28 +27,40 @@ namespace Avalonia.Win32 RECT rcFrame = new RECT(); AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); + RECT border_thickness = new RECT(); + if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) + { + AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); + border_thickness.left *= -1; + border_thickness.top *= -1; + } + else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) + { + border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + } + // Determine if the hit test is for resizing. Default middle (1,1). ushort uRow = 1; ushort uCol = 1; bool fOnResizeBorder = false; // Determine if the point is at the top or bottom of the window. - if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + TOPEXTENDWIDTH) + if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) { fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); uRow = 0; } - else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - BOTTOMEXTENDWIDTH) + else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) { uRow = 2; } // Determine if the point is at the left or right of the window. - if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + LEFTEXTENDWIDTH) + if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) { uCol = 0; // left side } - else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - RIGHTEXTENDWIDTH) + else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) { uCol = 2; // right side } @@ -66,8 +73,6 @@ namespace Avalonia.Win32 new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, }; - System.Diagnostics.Debug.WriteLine(hitTests[uRow][uCol]); - return hitTests[uRow][uCol]; } @@ -84,35 +89,37 @@ namespace Avalonia.Win32 { case WindowsMessage.WM_ACTIVATE: { - if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) - { - AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); - border_thickness.left *= -1; - border_thickness.top *= -1; - } - else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) - { - border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; - } + if (!_isClientAreaExtended) + { + if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) + { + AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); + border_thickness.left *= -1; + border_thickness.top *= -1; + } + else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) + { + border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + } - LEFTEXTENDWIDTH = border_thickness.left; - RIGHTEXTENDWIDTH = border_thickness.right; - BOTTOMEXTENDWIDTH = border_thickness.bottom; - TOPEXTENDWIDTH = border_thickness.top; + // Extend the frame into the client area. + margins.cxLeftWidth = border_thickness.left; + margins.cxRightWidth = border_thickness.right; + margins.cyBottomHeight = border_thickness.bottom; + margins.cyTopHeight = border_thickness.top; - // Extend the frame into the client area. - margins.cxLeftWidth = border_thickness.left; - margins.cxRightWidth = border_thickness.right; - margins.cyBottomHeight = border_thickness.bottom; - margins.cyTopHeight = border_thickness.top; + var hr = DwmExtendFrameIntoClientArea(hWnd, ref margins); - var hr = DwmExtendFrameIntoClientArea(hWnd, ref margins); + //if (hr < 0) + { + // Handle the error. + } - //if (hr < 0) - { - // Handle the error. - } + _isClientAreaExtended = true; + _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); + ExtendClientAreaToDecorationsChanged?.Invoke(true); + } lRet = IntPtr.Zero; callDwp = true; break; From d2bc5505c5eb8ea51533ffe295c696b9009702ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 16:54:42 -0300 Subject: [PATCH 006/109] Add win32 impl for client extending based on property value. --- .../Avalonia.Win32/WindowImpl.WndProc.cs | 54 ++++------------- src/Windows/Avalonia.Win32/WindowImpl.cs | 59 ++++++++++++++++++- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index 3ce34250fa..e9664d234f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -80,50 +80,20 @@ namespace Avalonia.Win32 { IntPtr lRet = IntPtr.Zero; - callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet); - - MARGINS margins = new MARGINS { cxLeftWidth = 0, cxRightWidth = 0, cyBottomHeight = 0, cyTopHeight = 100 }; - RECT border_thickness = new RECT(); - + callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet); + switch ((WindowsMessage)msg) { - case WindowsMessage.WM_ACTIVATE: - { - if (!_isClientAreaExtended) - { - if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) - { - AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); - border_thickness.left *= -1; - border_thickness.top *= -1; - } - else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) - { - border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; - } - - // Extend the frame into the client area. - margins.cxLeftWidth = border_thickness.left; - margins.cxRightWidth = border_thickness.right; - margins.cyBottomHeight = border_thickness.bottom; - margins.cyTopHeight = border_thickness.top; - - var hr = DwmExtendFrameIntoClientArea(hWnd, ref margins); - - //if (hr < 0) - { - // Handle the error. - } - - _isClientAreaExtended = true; - _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); - ExtendClientAreaToDecorationsChanged?.Invoke(true); - - } - lRet = IntPtr.Zero; - callDwp = true; - break; - } + //case WindowsMessage.WM_ACTIVATE: + // { + // if (!_isClientAreaExtended) + // { + // ExtendClientArea(); + // } + // lRet = IntPtr.Zero; + // callDwp = true; + // break; + // } case WindowsMessage.WM_NCCALCSIZE: { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6024dc01da..6cacd0b6cc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -40,6 +40,7 @@ namespace Avalonia.Win32 private SavedWindowInfo _savedWindowInfo; private bool _isFullScreenActive; + private bool _isClientAreaExtended; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -68,6 +69,7 @@ namespace Avalonia.Win32 private Size _maxSize; private POINT _maxTrackSize; private WindowImpl _parent; + private bool _extendClientAreaToDecorationsHint; public WindowImpl() { @@ -664,6 +666,43 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } + private void ExtendClientArea () + { + if (!_isClientAreaExtended) + { + RECT border_thickness = new RECT(); + MARGINS margins = new MARGINS(); + + if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) + { + AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); + border_thickness.left *= -1; + border_thickness.top *= -1; + } + else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) + { + border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + } + + // Extend the frame into the client area. + margins.cxLeftWidth = border_thickness.left; + margins.cxRightWidth = border_thickness.right; + margins.cyBottomHeight = border_thickness.bottom; + margins.cyTopHeight = border_thickness.top; + + var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + //if (hr < 0) + { + // Handle the error. + } + + _isClientAreaExtended = true; + _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); + ExtendClientAreaToDecorationsChanged?.Invoke(true); + } + } + private void ShowWindow(WindowState state) { ShowWindowCommand command; @@ -904,7 +943,25 @@ namespace Avalonia.Win32 } } - IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + + public bool ExtendClientAreaToDecorationsHint + { + get => _extendClientAreaToDecorationsHint; + set + { + _extendClientAreaToDecorationsHint = true; + + ExtendClientArea(); + // TODO Trigger transition. + } + } + + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + private Thickness _extendedMargins; + + public Thickness ExtendedMargins => _extendedMargins; private struct SavedWindowInfo { From d43108d442e09b61c53b173d2537428f503da8ee Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 16:55:32 -0300 Subject: [PATCH 007/109] only report extended if we were able to. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6cacd0b6cc..af1e8e06ce 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -692,14 +692,12 @@ namespace Avalonia.Win32 var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); - //if (hr < 0) + if (hr == 0) { - // Handle the error. + _isClientAreaExtended = true; + _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); + ExtendClientAreaToDecorationsChanged?.Invoke(true); } - - _isClientAreaExtended = true; - _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); - ExtendClientAreaToDecorationsChanged?.Invoke(true); } } From 3a1486be5622a53c8340cd8bcef8a34d34afd9ed Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 17:06:00 -0300 Subject: [PATCH 008/109] check composition is allowed before allowing extended windows. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index af1e8e06ce..7474bdd10a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -670,6 +670,11 @@ namespace Avalonia.Win32 { if (!_isClientAreaExtended) { + if(DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled) + { + return; + } + RECT border_thickness = new RECT(); MARGINS margins = new MARGINS(); From f7f1ed56a4fcad21f9989e35b02fad0bf83c9e40 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 17:06:30 -0300 Subject: [PATCH 009/109] separate WndProcs --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 518 ++++++++++++++ .../WindowImpl.CustomCaptionProc.cs | 117 ++++ .../Avalonia.Win32/WindowImpl.WndProc.cs | 644 +----------------- 3 files changed, 636 insertions(+), 643 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs create mode 100644 src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs new file mode 100644 index 0000000000..b81634dce0 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -0,0 +1,518 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Win32.Input; +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32 +{ + public partial class WindowImpl + { + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Using Win32 naming for consistency.")] + protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + const double wheelDelta = 120.0; + uint timestamp = unchecked((uint)GetMessageTime()); + + RawInputEventArgs e = null; + + switch ((WindowsMessage)msg) + { + case WindowsMessage.WM_ACTIVATE: + { + var wa = (WindowActivate)(ToInt32(wParam) & 0xffff); + + switch (wa) + { + case WindowActivate.WA_ACTIVE: + case WindowActivate.WA_CLICKACTIVE: + { + Activated?.Invoke(); + break; + } + + case WindowActivate.WA_INACTIVE: + { + Deactivated?.Invoke(); + break; + } + } + + return IntPtr.Zero; + } + + case WindowsMessage.WM_NCCALCSIZE: + { + if (ToInt32(wParam) == 1 && !HasFullDecorations || _isClientAreaExtended) + { + return IntPtr.Zero; + } + + break; + } + + case WindowsMessage.WM_CLOSE: + { + bool? preventClosing = Closing?.Invoke(); + if (preventClosing == true) + { + return IntPtr.Zero; + } + + break; + } + + case WindowsMessage.WM_DESTROY: + { + //Window doesn't exist anymore + _hwnd = IntPtr.Zero; + //Remove root reference to this class, so unmanaged delegate can be collected + s_instances.Remove(this); + Closed?.Invoke(); + + _mouseDevice.Dispose(); + _touchDevice?.Dispose(); + //Free other resources + Dispose(); + return IntPtr.Zero; + } + + case WindowsMessage.WM_DPICHANGED: + { + var dpi = ToInt32(wParam) & 0xffff; + var newDisplayRect = Marshal.PtrToStructure(lParam); + _scaling = dpi / 96.0; + ScalingChanged?.Invoke(_scaling); + SetWindowPos(hWnd, + IntPtr.Zero, + newDisplayRect.left, + newDisplayRect.top, + newDisplayRect.right - newDisplayRect.left, + newDisplayRect.bottom - newDisplayRect.top, + SetWindowPosFlags.SWP_NOZORDER | + SetWindowPosFlags.SWP_NOACTIVATE); + return IntPtr.Zero; + } + + case WindowsMessage.WM_KEYDOWN: + case WindowsMessage.WM_SYSKEYDOWN: + { + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + timestamp, + _owner, + RawKeyEventType.KeyDown, + KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), + WindowsKeyboardDevice.Instance.Modifiers); + break; + } + + case WindowsMessage.WM_MENUCHAR: + { + // mute the system beep + return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16); + } + + case WindowsMessage.WM_KEYUP: + case WindowsMessage.WM_SYSKEYUP: + { + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + timestamp, + _owner, + RawKeyEventType.KeyUp, + KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), + WindowsKeyboardDevice.Instance.Modifiers); + break; + } + case WindowsMessage.WM_CHAR: + { + // Ignore control chars + if (ToInt32(wParam) >= 32) + { + e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, + new string((char)ToInt32(wParam), 1)); + } + + break; + } + + case WindowsMessage.WM_LBUTTONDOWN: + case WindowsMessage.WM_RBUTTONDOWN: + case WindowsMessage.WM_MBUTTONDOWN: + case WindowsMessage.WM_XBUTTONDOWN: + { + if (ShouldIgnoreTouchEmulatedMessage()) + { + break; + } + + e = new RawPointerEventArgs( + _mouseDevice, + timestamp, + _owner, + (WindowsMessage)msg switch + { + WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, + WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, + WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, + WindowsMessage.WM_XBUTTONDOWN => + HighWord(ToInt32(wParam)) == 1 ? + RawPointerEventType.XButton1Down : + RawPointerEventType.XButton2Down + }, + DipFromLParam(lParam), GetMouseModifiers(wParam)); + break; + } + + case WindowsMessage.WM_LBUTTONUP: + case WindowsMessage.WM_RBUTTONUP: + case WindowsMessage.WM_MBUTTONUP: + case WindowsMessage.WM_XBUTTONUP: + { + if (ShouldIgnoreTouchEmulatedMessage()) + { + break; + } + + e = new RawPointerEventArgs( + _mouseDevice, + timestamp, + _owner, + (WindowsMessage)msg switch + { + WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, + WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, + WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, + WindowsMessage.WM_XBUTTONUP => + HighWord(ToInt32(wParam)) == 1 ? + RawPointerEventType.XButton1Up : + RawPointerEventType.XButton2Up, + }, + DipFromLParam(lParam), GetMouseModifiers(wParam)); + break; + } + + case WindowsMessage.WM_MOUSEMOVE: + { + if (ShouldIgnoreTouchEmulatedMessage()) + { + break; + } + + if (!_trackingMouse) + { + var tm = new TRACKMOUSEEVENT + { + cbSize = Marshal.SizeOf(), + dwFlags = 2, + hwndTrack = _hwnd, + dwHoverTime = 0, + }; + + TrackMouseEvent(ref tm); + } + + e = new RawPointerEventArgs( + _mouseDevice, + timestamp, + _owner, + RawPointerEventType.Move, + DipFromLParam(lParam), GetMouseModifiers(wParam)); + + break; + } + + case WindowsMessage.WM_MOUSEWHEEL: + { + e = new RawMouseWheelEventArgs( + _mouseDevice, + timestamp, + _owner, + PointToClient(PointFromLParam(lParam)), + new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); + break; + } + + case WindowsMessage.WM_MOUSEHWHEEL: + { + e = new RawMouseWheelEventArgs( + _mouseDevice, + timestamp, + _owner, + PointToClient(PointFromLParam(lParam)), + new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); + break; + } + + case WindowsMessage.WM_MOUSELEAVE: + { + _trackingMouse = false; + e = new RawPointerEventArgs( + _mouseDevice, + timestamp, + _owner, + RawPointerEventType.LeaveWindow, + new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); + break; + } + + case WindowsMessage.WM_NCLBUTTONDOWN: + case WindowsMessage.WM_NCRBUTTONDOWN: + case WindowsMessage.WM_NCMBUTTONDOWN: + case WindowsMessage.WM_NCXBUTTONDOWN: + { + e = new RawPointerEventArgs( + _mouseDevice, + timestamp, + _owner, + (WindowsMessage)msg switch + { + WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType + .NonClientLeftButtonDown, + WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, + WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, + WindowsMessage.WM_NCXBUTTONDOWN => + HighWord(ToInt32(wParam)) == 1 ? + RawPointerEventType.XButton1Down : + RawPointerEventType.XButton2Down, + }, + PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); + break; + } + case WindowsMessage.WM_TOUCH: + { + var touchInputCount = wParam.ToInt32(); + + var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount]; + var touchInputs = new Span(pTouchInputs, touchInputCount); + + if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf())) + { + foreach (var touchInput in touchInputs) + { + Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, + _owner, + touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? + RawPointerEventType.TouchEnd : + touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? + RawPointerEventType.TouchBegin : + RawPointerEventType.TouchUpdate, + PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), + WindowsKeyboardDevice.Instance.Modifiers, + touchInput.Id)); + } + + CloseTouchInputHandle(lParam); + return IntPtr.Zero; + } + + break; + } + case WindowsMessage.WM_NCPAINT: + { + if (!HasFullDecorations) + { + return IntPtr.Zero; + } + + break; + } + + case WindowsMessage.WM_NCACTIVATE: + { + if (!HasFullDecorations) + { + return new IntPtr(1); + } + + break; + } + + case WindowsMessage.WM_PAINT: + { + using (_rendererLock.Lock()) + { + if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) + { + var f = Scaling; + var r = ps.rcPaint; + Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, + (r.bottom - r.top) / f)); + EndPaint(_hwnd, ref ps); + } + } + + return IntPtr.Zero; + } + + case WindowsMessage.WM_SIZE: + { + using (_rendererLock.Lock()) + { + // Do nothing here, just block until the pending frame render is completed on the render thread + } + + 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 / Scaling); + } + + var windowState = size == SizeCommand.Maximized ? + WindowState.Maximized : + (size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); + + if (windowState != _lastWindowState) + { + _lastWindowState = windowState; + WindowStateChanged?.Invoke(windowState); + } + + return IntPtr.Zero; + } + + case WindowsMessage.WM_MOVE: + { + PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), + (short)(ToInt32(lParam) >> 16))); + return IntPtr.Zero; + } + + case WindowsMessage.WM_GETMINMAXINFO: + { + MINMAXINFO mmi = Marshal.PtrToStructure(lParam); + + if (_minSize.Width > 0) + { + mmi.ptMinTrackSize.X = + (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + } + + if (_minSize.Height > 0) + { + mmi.ptMinTrackSize.Y = + (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + } + + if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) + { + mmi.ptMaxTrackSize.X = + (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + } + + if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) + { + mmi.ptMaxTrackSize.Y = + (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + } + + Marshal.StructureToPtr(mmi, lParam, true); + return IntPtr.Zero; + } + + case WindowsMessage.WM_DISPLAYCHANGE: + { + (Screen as ScreenImpl)?.InvalidateScreensCache(); + return IntPtr.Zero; + } + } + +#if USE_MANAGED_DRAG + if (_managedDrag.PreprocessInputEvent(ref e)) + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); +#endif + + if (e != null && Input != null) + { + Input(e); + + if (e.Handled) + { + return IntPtr.Zero; + } + } + + using (_rendererLock.Lock()) + { + return DefWindowProc(hWnd, msg, wParam, lParam); + } + } + + private static int ToInt32(IntPtr ptr) + { + if (IntPtr.Size == 4) + return ptr.ToInt32(); + + return (int)(ptr.ToInt64() & 0xffffffff); + } + + private static int HighWord(int param) => param >> 16; + + private Point DipFromLParam(IntPtr lParam) + { + return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; + } + + private PixelPoint PointFromLParam(IntPtr lParam) + { + return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); + } + + private bool ShouldIgnoreTouchEmulatedMessage() + { + if (!_multitouch) + { + return false; + } + + // MI_WP_SIGNATURE + // https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages + const long marker = 0xFF515700L; + + var info = GetMessageExtraInfo().ToInt64(); + return (info & marker) == marker; + } + + private static RawInputModifiers GetMouseModifiers(IntPtr wParam) + { + var keys = (ModifierKeys)ToInt32(wParam); + var modifiers = WindowsKeyboardDevice.Instance.Modifiers; + + if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON)) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + + if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON)) + { + modifiers |= RawInputModifiers.RightMouseButton; + } + + if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON)) + { + modifiers |= RawInputModifiers.MiddleMouseButton; + } + + if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1)) + { + modifiers |= RawInputModifiers.XButton1MouseButton; + } + + if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2)) + { + modifiers |= RawInputModifiers.XButton2MouseButton; + } + + return modifiers; + } + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs new file mode 100644 index 0000000000..faf83c6c9b --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Win32 +{ + public partial class WindowImpl + { + // Hit test the frame for resizing and moving. + HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) + { + // Get the point coordinates for the hit test. + var ptMouse = PointFromLParam(lParam); + + // Get the window rectangle. + GetWindowRect(hWnd, out var rcWindow); + + // Get the frame rectangle, adjusted for the style without a caption. + RECT rcFrame = new RECT(); + AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); + + RECT border_thickness = new RECT(); + if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) + { + AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); + border_thickness.left *= -1; + border_thickness.top *= -1; + } + else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) + { + border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + } + + // Determine if the hit test is for resizing. Default middle (1,1). + ushort uRow = 1; + ushort uCol = 1; + bool fOnResizeBorder = false; + + // Determine if the point is at the top or bottom of the window. + if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) + { + fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); + uRow = 0; + } + else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) + { + uRow = 2; + } + + // Determine if the point is at the left or right of the window. + if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) + { + uCol = 0; // left side + } + else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) + { + uCol = 2; // right side + } + + // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + HitTestValues[][] hitTests = new[] + { + new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, + new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, + new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, + }; + + return hitTests[uRow][uCol]; + } + + 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_NCHITTEST: + if (lRet == IntPtr.Zero) + { + lRet = (IntPtr)HitTestNCA(hWnd, wParam, lParam); + + uint timestamp = unchecked((uint)GetMessageTime()); + + if (((HitTestValues)lRet) == HitTestValues.HTCAPTION) + { + var position = PointToClient(PointFromLParam(lParam)); + + var visual = (_owner as Window).Renderer.HitTestFirst(position, _owner as Window, x => + { + if (x is IInputElement ie && !ie.IsHitTestVisible) + { + return false; + } + + return true; + }); + + if (visual != null) + { + lRet = (IntPtr)HitTestValues.HTCLIENT; + } + } + + if (((HitTestValues)lRet) != HitTestValues.HTNOWHERE) + { + callDwp = false; + } + } + break; + } + + return lRet; + } + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index e9664d234f..bb6ab180cf 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -12,139 +12,6 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { - public partial class WindowImpl - { - // Hit test the frame for resizing and moving. - HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) - { - // Get the point coordinates for the hit test. - var ptMouse = PointFromLParam(lParam); - - // Get the window rectangle. - GetWindowRect(hWnd, out var rcWindow); - - // Get the frame rectangle, adjusted for the style without a caption. - RECT rcFrame = new RECT(); - AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); - - RECT border_thickness = new RECT(); - if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) - { - AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); - border_thickness.left *= -1; - border_thickness.top *= -1; - } - else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) - { - border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; - } - - // Determine if the hit test is for resizing. Default middle (1,1). - ushort uRow = 1; - ushort uCol = 1; - bool fOnResizeBorder = false; - - // Determine if the point is at the top or bottom of the window. - if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) - { - fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); - uRow = 0; - } - else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) - { - uRow = 2; - } - - // Determine if the point is at the left or right of the window. - if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) - { - uCol = 0; // left side - } - else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) - { - uCol = 2; // right side - } - - // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) - HitTestValues[][] hitTests = new[] - { - new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, - new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, - new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, - }; - - return hitTests[uRow][uCol]; - } - - protected virtual unsafe 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_ACTIVATE: - // { - // if (!_isClientAreaExtended) - // { - // ExtendClientArea(); - // } - // lRet = IntPtr.Zero; - // callDwp = true; - // break; - // } - - case WindowsMessage.WM_NCCALCSIZE: - { - if (ToInt32(wParam) == 1) - { - lRet = IntPtr.Zero; - callDwp = false; - } - break; - } - - case WindowsMessage.WM_NCHITTEST: - if (lRet == IntPtr.Zero) - { - lRet = (IntPtr)HitTestNCA(hWnd, wParam, lParam); - - uint timestamp = unchecked((uint)GetMessageTime()); - - if (((HitTestValues)lRet) == HitTestValues.HTCAPTION) - { - var position = PointToClient(PointFromLParam(lParam)); - - var visual = (_owner as Window).Renderer.HitTestFirst(position, _owner as Window, x => - { - if(x is IInputElement ie && !ie.IsHitTestVisible) - { - return false; - } - - return true; - }); - - if(visual != null) - { - lRet = (IntPtr)HitTestValues.HTCLIENT; - } - - } - - if (((HitTestValues)lRet) != HitTestValues.HTNOWHERE) - { - callDwp = false; - } - } - break; - } - - return lRet; - } - } - public partial class WindowImpl { protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) @@ -152,7 +19,7 @@ namespace Avalonia.Win32 IntPtr lRet = IntPtr.Zero; bool callDwp = true; - if (DwmIsCompositionEnabled(out bool enabled) == 0) + if (_isClientAreaExtended) { lRet = CustomCaptionProc(hWnd, msg, wParam, lParam, ref callDwp); } @@ -165,513 +32,4 @@ namespace Avalonia.Win32 return lRet; } } - - public partial class WindowImpl - { - [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Using Win32 naming for consistency.")] - protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - const double wheelDelta = 120.0; - uint timestamp = unchecked((uint)GetMessageTime()); - - RawInputEventArgs e = null; - - switch ((WindowsMessage)msg) - { - case WindowsMessage.WM_ACTIVATE: - { - var wa = (WindowActivate)(ToInt32(wParam) & 0xffff); - - switch (wa) - { - case WindowActivate.WA_ACTIVE: - case WindowActivate.WA_CLICKACTIVE: - { - Activated?.Invoke(); - break; - } - - case WindowActivate.WA_INACTIVE: - { - Deactivated?.Invoke(); - break; - } - } - - return IntPtr.Zero; - } - - case WindowsMessage.WM_NCCALCSIZE: - { - if (ToInt32(wParam) == 1 && !HasFullDecorations) - { - return IntPtr.Zero; - } - - break; - } - - case WindowsMessage.WM_CLOSE: - { - bool? preventClosing = Closing?.Invoke(); - if (preventClosing == true) - { - return IntPtr.Zero; - } - - break; - } - - case WindowsMessage.WM_DESTROY: - { - //Window doesn't exist anymore - _hwnd = IntPtr.Zero; - //Remove root reference to this class, so unmanaged delegate can be collected - s_instances.Remove(this); - Closed?.Invoke(); - - _mouseDevice.Dispose(); - _touchDevice?.Dispose(); - //Free other resources - Dispose(); - return IntPtr.Zero; - } - - case WindowsMessage.WM_DPICHANGED: - { - var dpi = ToInt32(wParam) & 0xffff; - var newDisplayRect = Marshal.PtrToStructure(lParam); - _scaling = dpi / 96.0; - ScalingChanged?.Invoke(_scaling); - SetWindowPos(hWnd, - IntPtr.Zero, - newDisplayRect.left, - newDisplayRect.top, - newDisplayRect.right - newDisplayRect.left, - newDisplayRect.bottom - newDisplayRect.top, - SetWindowPosFlags.SWP_NOZORDER | - SetWindowPosFlags.SWP_NOACTIVATE); - return IntPtr.Zero; - } - - case WindowsMessage.WM_KEYDOWN: - case WindowsMessage.WM_SYSKEYDOWN: - { - e = new RawKeyEventArgs( - WindowsKeyboardDevice.Instance, - timestamp, - _owner, - RawKeyEventType.KeyDown, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), - WindowsKeyboardDevice.Instance.Modifiers); - break; - } - - case WindowsMessage.WM_MENUCHAR: - { - // mute the system beep - return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16); - } - - case WindowsMessage.WM_KEYUP: - case WindowsMessage.WM_SYSKEYUP: - { - e = new RawKeyEventArgs( - WindowsKeyboardDevice.Instance, - timestamp, - _owner, - RawKeyEventType.KeyUp, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), - WindowsKeyboardDevice.Instance.Modifiers); - break; - } - case WindowsMessage.WM_CHAR: - { - // Ignore control chars - if (ToInt32(wParam) >= 32) - { - e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, - new string((char)ToInt32(wParam), 1)); - } - - break; - } - - case WindowsMessage.WM_LBUTTONDOWN: - case WindowsMessage.WM_RBUTTONDOWN: - case WindowsMessage.WM_MBUTTONDOWN: - case WindowsMessage.WM_XBUTTONDOWN: - { - if (ShouldIgnoreTouchEmulatedMessage()) - { - break; - } - - e = new RawPointerEventArgs( - _mouseDevice, - timestamp, - _owner, - (WindowsMessage)msg switch - { - WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, - WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, - WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, - WindowsMessage.WM_XBUTTONDOWN => - HighWord(ToInt32(wParam)) == 1 ? - RawPointerEventType.XButton1Down : - RawPointerEventType.XButton2Down - }, - DipFromLParam(lParam), GetMouseModifiers(wParam)); - break; - } - - case WindowsMessage.WM_LBUTTONUP: - case WindowsMessage.WM_RBUTTONUP: - case WindowsMessage.WM_MBUTTONUP: - case WindowsMessage.WM_XBUTTONUP: - { - if (ShouldIgnoreTouchEmulatedMessage()) - { - break; - } - - e = new RawPointerEventArgs( - _mouseDevice, - timestamp, - _owner, - (WindowsMessage)msg switch - { - WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, - WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, - WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, - WindowsMessage.WM_XBUTTONUP => - HighWord(ToInt32(wParam)) == 1 ? - RawPointerEventType.XButton1Up : - RawPointerEventType.XButton2Up, - }, - DipFromLParam(lParam), GetMouseModifiers(wParam)); - break; - } - - case WindowsMessage.WM_MOUSEMOVE: - { - if (ShouldIgnoreTouchEmulatedMessage()) - { - break; - } - - if (!_trackingMouse) - { - var tm = new TRACKMOUSEEVENT - { - cbSize = Marshal.SizeOf(), - dwFlags = 2, - hwndTrack = _hwnd, - dwHoverTime = 0, - }; - - TrackMouseEvent(ref tm); - } - - e = new RawPointerEventArgs( - _mouseDevice, - timestamp, - _owner, - RawPointerEventType.Move, - DipFromLParam(lParam), GetMouseModifiers(wParam)); - - break; - } - - case WindowsMessage.WM_MOUSEWHEEL: - { - e = new RawMouseWheelEventArgs( - _mouseDevice, - timestamp, - _owner, - PointToClient(PointFromLParam(lParam)), - new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); - break; - } - - case WindowsMessage.WM_MOUSEHWHEEL: - { - e = new RawMouseWheelEventArgs( - _mouseDevice, - timestamp, - _owner, - PointToClient(PointFromLParam(lParam)), - new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); - break; - } - - case WindowsMessage.WM_MOUSELEAVE: - { - _trackingMouse = false; - e = new RawPointerEventArgs( - _mouseDevice, - timestamp, - _owner, - RawPointerEventType.LeaveWindow, - new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); - break; - } - - case WindowsMessage.WM_NCLBUTTONDOWN: - case WindowsMessage.WM_NCRBUTTONDOWN: - case WindowsMessage.WM_NCMBUTTONDOWN: - case WindowsMessage.WM_NCXBUTTONDOWN: - { - e = new RawPointerEventArgs( - _mouseDevice, - timestamp, - _owner, - (WindowsMessage)msg switch - { - WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType - .NonClientLeftButtonDown, - WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, - WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, - WindowsMessage.WM_NCXBUTTONDOWN => - HighWord(ToInt32(wParam)) == 1 ? - RawPointerEventType.XButton1Down : - RawPointerEventType.XButton2Down, - }, - PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); - break; - } - case WindowsMessage.WM_TOUCH: - { - var touchInputCount = wParam.ToInt32(); - - var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount]; - var touchInputs = new Span(pTouchInputs, touchInputCount); - - if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf())) - { - foreach (var touchInput in touchInputs) - { - Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, - _owner, - touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? - RawPointerEventType.TouchEnd : - touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? - RawPointerEventType.TouchBegin : - RawPointerEventType.TouchUpdate, - PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), - WindowsKeyboardDevice.Instance.Modifiers, - touchInput.Id)); - } - - CloseTouchInputHandle(lParam); - return IntPtr.Zero; - } - - break; - } - case WindowsMessage.WM_NCPAINT: - { - if (!HasFullDecorations) - { - return IntPtr.Zero; - } - - break; - } - - case WindowsMessage.WM_NCACTIVATE: - { - if (!HasFullDecorations) - { - return new IntPtr(1); - } - - break; - } - - case WindowsMessage.WM_PAINT: - { - using (_rendererLock.Lock()) - { - if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) - { - var f = Scaling; - var r = ps.rcPaint; - Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, - (r.bottom - r.top) / f)); - EndPaint(_hwnd, ref ps); - } - } - - return IntPtr.Zero; - } - - case WindowsMessage.WM_SIZE: - { - using (_rendererLock.Lock()) - { - // Do nothing here, just block until the pending frame render is completed on the render thread - } - - 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 / Scaling); - } - - var windowState = size == SizeCommand.Maximized ? - WindowState.Maximized : - (size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); - - if (windowState != _lastWindowState) - { - _lastWindowState = windowState; - WindowStateChanged?.Invoke(windowState); - } - - return IntPtr.Zero; - } - - case WindowsMessage.WM_MOVE: - { - PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), - (short)(ToInt32(lParam) >> 16))); - return IntPtr.Zero; - } - - case WindowsMessage.WM_GETMINMAXINFO: - { - MINMAXINFO mmi = Marshal.PtrToStructure(lParam); - - _maxTrackSize = mmi.ptMaxTrackSize; - - if (_minSize.Width > 0) - { - mmi.ptMinTrackSize.X = - (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); - } - - if (_minSize.Height > 0) - { - mmi.ptMinTrackSize.Y = - (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); - } - - if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) - { - mmi.ptMaxTrackSize.X = - (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); - } - - if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) - { - mmi.ptMaxTrackSize.Y = - (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); - } - - Marshal.StructureToPtr(mmi, lParam, true); - return IntPtr.Zero; - } - - case WindowsMessage.WM_DISPLAYCHANGE: - { - (Screen as ScreenImpl)?.InvalidateScreensCache(); - return IntPtr.Zero; - } - } - -#if USE_MANAGED_DRAG - if (_managedDrag.PreprocessInputEvent(ref e)) - return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); -#endif - - if (e != null && Input != null) - { - Input(e); - - if (e.Handled) - { - return IntPtr.Zero; - } - } - - using (_rendererLock.Lock()) - { - return DefWindowProc(hWnd, msg, wParam, lParam); - } - } - - private static int ToInt32(IntPtr ptr) - { - if (IntPtr.Size == 4) - return ptr.ToInt32(); - - return (int)(ptr.ToInt64() & 0xffffffff); - } - - private static int HighWord(int param) => param >> 16; - - private Point DipFromLParam(IntPtr lParam) - { - return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; - } - - private PixelPoint PointFromLParam(IntPtr lParam) - { - return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); - } - - private bool ShouldIgnoreTouchEmulatedMessage() - { - if (!_multitouch) - { - return false; - } - - // MI_WP_SIGNATURE - // https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages - const long marker = 0xFF515700L; - - var info = GetMessageExtraInfo().ToInt64(); - return (info & marker) == marker; - } - - private static RawInputModifiers GetMouseModifiers(IntPtr wParam) - { - var keys = (ModifierKeys)ToInt32(wParam); - var modifiers = WindowsKeyboardDevice.Instance.Modifiers; - - if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON)) - { - modifiers |= RawInputModifiers.LeftMouseButton; - } - - if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON)) - { - modifiers |= RawInputModifiers.RightMouseButton; - } - - if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON)) - { - modifiers |= RawInputModifiers.MiddleMouseButton; - } - - if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1)) - { - modifiers |= RawInputModifiers.XButton1MouseButton; - } - - if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2)) - { - modifiers |= RawInputModifiers.XButton2MouseButton; - } - - return modifiers; - } - } } From b83d85362150a5ef2729dd589b1902d012084a80 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 17:08:28 -0300 Subject: [PATCH 010/109] TransparencyFallback layer needs to obey ExtendedClient margins to not cover titlebar. --- src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml | 2 +- src/Avalonia.Themes.Default/OverlayPopupHost.xaml | 2 +- src/Avalonia.Themes.Default/PopupRoot.xaml | 2 +- src/Avalonia.Themes.Default/Window.xaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml index 9ffe51fae8..f0e884832f 100644 --- a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml @@ -4,7 +4,7 @@ - + - + - + - + Date: Wed, 20 May 2020 17:24:27 -0300 Subject: [PATCH 011/109] fix embeddable control root. --- src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml | 2 +- src/Avalonia.Themes.Default/OverlayPopupHost.xaml | 2 +- src/Avalonia.Themes.Default/PopupRoot.xaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml index f0e884832f..9ffe51fae8 100644 --- a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml @@ -4,7 +4,7 @@ - + - + - + Date: Wed, 20 May 2020 17:29:07 -0300 Subject: [PATCH 012/109] fix build --- src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index faf83c6c9b..6716691872 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Text; +using Avalonia.Controls; +using Avalonia.Input; +using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { From 68e8541f7534a0c5191c1255aaf801472cb219fa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 20 May 2020 17:57:21 -0300 Subject: [PATCH 013/109] use thickness as the ExtendClientArea hint. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 +- src/Avalonia.Controls/Window.cs | 10 +++++----- .../Remote/PreviewerWindowImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 +++++++++---------- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 9c4808a41c..604c3a61aa 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -95,7 +95,7 @@ namespace Avalonia.Platform /// void SetMinMaxSize(Size minSize, Size maxSize); - bool ExtendClientAreaToDecorationsHint { get; set; } + Thickness ExtendClientAreaToDecorationsHint { get; set; } Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 04b48d7f9f..cbb69fbeb7 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -92,8 +92,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = - AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); + public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = + AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), default); /// @@ -109,7 +109,7 @@ namespace Avalonia.Controls /// public static readonly DirectProperty WindowDecorationMarginsProperty = AvaloniaProperty.RegisterDirect(nameof(WindowDecorationMargins), - o => o.WindowDecorationMargins); + o => o.WindowDecorationMargins); /// @@ -190,7 +190,7 @@ namespace Avalonia.Controls (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler( - (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (bool)e.NewValue; }); + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (Thickness)e.NewValue; }); MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); @@ -269,7 +269,7 @@ namespace Avalonia.Controls /// /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). /// - public bool ExtendClientAreaToDecorationsHint + public Thickness ExtendClientAreaToDecorationsHint { get { return GetValue(ExtendClientAreaToDecorationsHintProperty); } set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 441a79531a..26c3bed86d 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -85,7 +85,7 @@ namespace Avalonia.DesignerSupport.Remote public IScreenImpl Screen { get; } = new ScreenStub(); public Action GotInputWhenDisabled { get; set; } - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Thickness ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f962382575..605e0e45a8 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -39,7 +39,7 @@ namespace Avalonia.DesignerSupport.Remote public Action TransparencyLevelChanged { get; set; } - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Thickness ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 3495c4b392..2e81a66d1f 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -98,7 +98,7 @@ namespace Avalonia.Native public Action WindowStateChanged { get; set; } - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Thickness ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index ac9048adad..4bff5b9b03 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -311,7 +311,7 @@ namespace Avalonia.X11 set => _transparencyHelper.TransparencyLevelChanged = value; } - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Thickness ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7474bdd10a..fe8f175a5e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -69,7 +69,7 @@ namespace Avalonia.Win32 private Size _maxSize; private POINT _maxTrackSize; private WindowImpl _parent; - private bool _extendClientAreaToDecorationsHint; + private Thickness _extendClientAreaToDecorationsHint; public WindowImpl() { @@ -666,7 +666,7 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } - private void ExtendClientArea () + private void ExtendClientArea (Thickness thickness) { if (!_isClientAreaExtended) { @@ -690,10 +690,10 @@ namespace Avalonia.Win32 } // Extend the frame into the client area. - margins.cxLeftWidth = border_thickness.left; - margins.cxRightWidth = border_thickness.right; - margins.cyBottomHeight = border_thickness.bottom; - margins.cyTopHeight = border_thickness.top; + margins.cxLeftWidth = thickness.Left == -1 ? border_thickness.left : (int)(thickness.Left * Scaling); + margins.cxRightWidth = thickness.Right == -1 ? border_thickness.right : (int)(thickness.Right * Scaling); + margins.cyBottomHeight = thickness.Bottom == -1 ? border_thickness.bottom : (int)(thickness.Bottom * Scaling); + margins.cyTopHeight = thickness.Top == -1 ? border_thickness.top : (int)(thickness.Top * Scaling); var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); @@ -948,15 +948,14 @@ namespace Avalonia.Win32 IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; - public bool ExtendClientAreaToDecorationsHint + public Thickness ExtendClientAreaToDecorationsHint { get => _extendClientAreaToDecorationsHint; set { - _extendClientAreaToDecorationsHint = true; + _extendClientAreaToDecorationsHint = value; - ExtendClientArea(); - // TODO Trigger transition. + ExtendClientArea(value); } } From 29661b45bad93b1b27ce4177e80ecd0d513c3d09 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 09:33:17 -0300 Subject: [PATCH 014/109] add handler for DWM_COMPOSITION_CHANGED. --- src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 6716691872..a55a22c3c3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -79,6 +79,10 @@ namespace Avalonia.Win32 switch ((WindowsMessage)msg) { + case WindowsMessage.WM_DWMCOMPOSITIONCHANGED: + // TODO handle composition changed. + break; + case WindowsMessage.WM_NCHITTEST: if (lRet == IntPtr.Zero) { From aea6f7bff9f915e5113438c9c53d6b6e47757693 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 09:33:40 -0300 Subject: [PATCH 015/109] Revert "use thickness as the ExtendClientArea hint." This reverts commit 42673554085035e8a8a3435bc623ceb29f72e4db. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 +- src/Avalonia.Controls/Window.cs | 10 +++++----- .../Remote/PreviewerWindowImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 ++++++++++--------- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 604c3a61aa..9c4808a41c 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -95,7 +95,7 @@ namespace Avalonia.Platform /// void SetMinMaxSize(Size minSize, Size maxSize); - Thickness ExtendClientAreaToDecorationsHint { get; set; } + bool ExtendClientAreaToDecorationsHint { get; set; } Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index cbb69fbeb7..04b48d7f9f 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -92,8 +92,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = - AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), default); + public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = + AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); /// @@ -109,7 +109,7 @@ namespace Avalonia.Controls /// public static readonly DirectProperty WindowDecorationMarginsProperty = AvaloniaProperty.RegisterDirect(nameof(WindowDecorationMargins), - o => o.WindowDecorationMargins); + o => o.WindowDecorationMargins); /// @@ -190,7 +190,7 @@ namespace Avalonia.Controls (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler( - (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (Thickness)e.NewValue; }); + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (bool)e.NewValue; }); MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); @@ -269,7 +269,7 @@ namespace Avalonia.Controls /// /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). /// - public Thickness ExtendClientAreaToDecorationsHint + public bool ExtendClientAreaToDecorationsHint { get { return GetValue(ExtendClientAreaToDecorationsHintProperty); } set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 26c3bed86d..441a79531a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -85,7 +85,7 @@ namespace Avalonia.DesignerSupport.Remote public IScreenImpl Screen { get; } = new ScreenStub(); public Action GotInputWhenDisabled { get; set; } - public Thickness ExtendClientAreaToDecorationsHint { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 605e0e45a8..f962382575 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -39,7 +39,7 @@ namespace Avalonia.DesignerSupport.Remote public Action TransparencyLevelChanged { get; set; } - public Thickness ExtendClientAreaToDecorationsHint { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 2e81a66d1f..3495c4b392 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -98,7 +98,7 @@ namespace Avalonia.Native public Action WindowStateChanged { get; set; } - public Thickness ExtendClientAreaToDecorationsHint { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 4bff5b9b03..ac9048adad 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -311,7 +311,7 @@ namespace Avalonia.X11 set => _transparencyHelper.TransparencyLevelChanged = value; } - public Thickness ExtendClientAreaToDecorationsHint { get; set; } + public bool ExtendClientAreaToDecorationsHint { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fe8f175a5e..7474bdd10a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -69,7 +69,7 @@ namespace Avalonia.Win32 private Size _maxSize; private POINT _maxTrackSize; private WindowImpl _parent; - private Thickness _extendClientAreaToDecorationsHint; + private bool _extendClientAreaToDecorationsHint; public WindowImpl() { @@ -666,7 +666,7 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } - private void ExtendClientArea (Thickness thickness) + private void ExtendClientArea () { if (!_isClientAreaExtended) { @@ -690,10 +690,10 @@ namespace Avalonia.Win32 } // Extend the frame into the client area. - margins.cxLeftWidth = thickness.Left == -1 ? border_thickness.left : (int)(thickness.Left * Scaling); - margins.cxRightWidth = thickness.Right == -1 ? border_thickness.right : (int)(thickness.Right * Scaling); - margins.cyBottomHeight = thickness.Bottom == -1 ? border_thickness.bottom : (int)(thickness.Bottom * Scaling); - margins.cyTopHeight = thickness.Top == -1 ? border_thickness.top : (int)(thickness.Top * Scaling); + margins.cxLeftWidth = border_thickness.left; + margins.cxRightWidth = border_thickness.right; + margins.cyBottomHeight = border_thickness.bottom; + margins.cyTopHeight = border_thickness.top; var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); @@ -948,14 +948,15 @@ namespace Avalonia.Win32 IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; - public Thickness ExtendClientAreaToDecorationsHint + public bool ExtendClientAreaToDecorationsHint { get => _extendClientAreaToDecorationsHint; set { - _extendClientAreaToDecorationsHint = value; + _extendClientAreaToDecorationsHint = true; - ExtendClientArea(value); + ExtendClientArea(); + // TODO Trigger transition. } } From 37e4e68b9d31f5c016ea4c672f9f7906f0e416fd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 11:19:20 -0300 Subject: [PATCH 016/109] add hints for further control over chrome. --- .../Platform/ExtendClientAreaChromeHints.cs | 17 +++++ src/Avalonia.Controls/Platform/IWindowImpl.cs | 6 +- src/Avalonia.Controls/Window.cs | 14 +++- .../Remote/PreviewerWindowImpl.cs | 14 +++- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 +++- src/Avalonia.Native/WindowImpl.cs | 14 +++- src/Avalonia.X11/X11Window.cs | 14 +++- .../Interop/UnmanagedMethods.cs | 6 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 74 +++++++++++++++---- 9 files changed, 144 insertions(+), 29 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs new file mode 100644 index 0000000000..af33e3093b --- /dev/null +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -0,0 +1,17 @@ +using System; + +namespace Avalonia.Platform +{ + [Flags] + public enum ExtendClientAreaChromeHints + { + Default = SystemTitleBar | SystemChromeButtons, + NoChrome, + SystemTitleBar = 0x01, + SystemChromeButtons = 0x02, + ManagedChromeButtons = 0x04, + PreferSystemChromeButtons = 0x08, + AdaptiveChromeWithTitleBar = SystemTitleBar | PreferSystemChromeButtons, + AdaptiveChromeWithoutTitleBar = PreferSystemChromeButtons, + } +} diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 9c4808a41c..d7bf93a603 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -95,7 +95,11 @@ namespace Avalonia.Platform /// void SetMinMaxSize(Size minSize, Size maxSize); - bool ExtendClientAreaToDecorationsHint { get; set; } + void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint); + + bool IsClientAreaExtendedToDecorations { get; } + + void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints); Action ExtendClientAreaToDecorationsChanged { get; set; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 04b48d7f9f..85935d3d2a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -95,6 +95,9 @@ namespace Avalonia.Controls public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); + public static readonly StyledProperty ExtendClientAreaChromeHintsProperty = + AvaloniaProperty.Register(nameof(ExtendClientAreaChromeHints), ExtendClientAreaChromeHints.Default); + /// /// Defines the property. @@ -190,7 +193,10 @@ namespace Avalonia.Controls (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler( - (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.ExtendClientAreaToDecorationsHint = (bool)e.NewValue; }); + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue); }); + + ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler( + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); }); MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); @@ -275,6 +281,12 @@ namespace Avalonia.Controls set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); } } + public ExtendClientAreaChromeHints ExtendClientAreaChromeHints + { + get => GetValue(ExtendClientAreaChromeHintsProperty); + set => SetValue(ExtendClientAreaChromeHintsProperty, value); + } + /// /// Gets if the ClientArea is Extended into the Window Decorations. /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 441a79531a..06c7ac6411 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -83,14 +83,14 @@ namespace Avalonia.DesignerSupport.Remote } public IScreenImpl Screen { get; } = new ScreenStub(); - public Action GotInputWhenDisabled { get; set; } - - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Action GotInputWhenDisabled { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } public Thickness ExtendedMargins { get; } = new Thickness(); + public bool IsClientAreaExtendedToDecorations { get; } + public void Activate() { } @@ -130,5 +130,13 @@ namespace Avalonia.DesignerSupport.Remote public void SetEnabled(bool enable) { } + + public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) + { + } + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) + { + } } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f962382575..6f5a0e6051 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -37,9 +37,7 @@ namespace Avalonia.DesignerSupport.Remote public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } - public Action TransparencyLevelChanged { get; set; } - - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Action TransparencyLevelChanged { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } @@ -146,6 +144,14 @@ namespace Avalonia.DesignerSupport.Remote { } + public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) + { + } + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) + { + } + public IPopupPositioner PopupPositioner { get; } public Action GotInputWhenDisabled { get; set; } @@ -157,6 +163,8 @@ namespace Avalonia.DesignerSupport.Remote } public WindowTransparencyLevel TransparencyLevel { get; private set; } + + public bool IsClientAreaExtendedToDecorations { get; } } class ClipboardStub : IClipboard diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 3495c4b392..47c8291803 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -96,14 +96,22 @@ namespace Avalonia.Native } } - public Action WindowStateChanged { get; set; } - - public bool ExtendClientAreaToDecorationsHint { get; set; } + public Action WindowStateChanged { get; set; } public Action ExtendClientAreaToDecorationsChanged { get; set; } public Thickness ExtendedMargins { get; } = new Thickness(); + public bool IsClientAreaExtendedToDecorations { get; } + + public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) + { + } + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) + { + } + public void ShowTaskbarIcon(bool value) { // NO OP On OSX diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index ac9048adad..d64f993299 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -309,14 +309,14 @@ namespace Avalonia.X11 { get => _transparencyHelper.TransparencyLevelChanged; set => _transparencyHelper.TransparencyLevelChanged = value; - } - - public bool ExtendClientAreaToDecorationsHint { get; set; } + } public Action ExtendClientAreaToDecorationsChanged { get; set; } public Thickness ExtendedMargins { get; } = new Thickness(); + public bool IsClientAreaExtendedToDecorations { get; } + public Action Closed { get; set; } public Action PositionChanged { get; set; } @@ -1041,6 +1041,14 @@ namespace Avalonia.X11 _disabled = !enable; } + public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) + { + } + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) + { + } + public Action GotInputWhenDisabled { get; set; } public void SetIcon(IWindowIconImpl icon) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b66de8e0a8..6f843324f2 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -989,6 +989,12 @@ namespace Avalonia.Win32.Interop } } + [DllImport("user32.dll")] + public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); + + [DllImport("user32.dll")] + 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); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7474bdd10a..7c720b5612 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -41,6 +41,7 @@ namespace Avalonia.Win32 private SavedWindowInfo _savedWindowInfo; private bool _isFullScreenActive; private bool _isClientAreaExtended; + private Thickness _extendedMargins; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -666,7 +667,7 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } - private void ExtendClientArea () + private void ExtendClientArea (ExtendClientAreaChromeHints hints) { if (!_isClientAreaExtended) { @@ -683,6 +684,16 @@ namespace Avalonia.Win32 AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); border_thickness.left *= -1; border_thickness.top *= -1; + + border_thickness.left = 1; + border_thickness.bottom = 1; + border_thickness.right = 1; + + if (!hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar)) + { + border_thickness.top = 1; + } + } else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) { @@ -697,6 +708,19 @@ namespace Avalonia.Win32 var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); + if(!hints.HasFlag(ExtendClientAreaChromeHints.SystemChromeButtons) || + (hints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons) && + !hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar))) + { + var style = GetStyle(); + + style &= ~(WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); + + SetStyle(style); + + DisableCloseButton(_hwnd); + } + if (hr == 0) { _isClientAreaExtended = true; @@ -854,9 +878,10 @@ namespace Avalonia.Win32 // Otherwise it will still show in the taskbar. } + WindowStyles style; if ((oldProperties.IsResizable != newProperties.IsResizable) || forceChanges) { - var style = GetStyle(); + style = GetStyle(); if (newProperties.IsResizable) { @@ -877,7 +902,7 @@ namespace Avalonia.Win32 if ((oldProperties.Decorations != newProperties.Decorations) || forceChanges) { - var style = GetStyle(); + style = GetStyle(); const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU; @@ -922,7 +947,26 @@ namespace Avalonia.Win32 SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); } - } + } + } + + private const int MF_BYCOMMAND = 0x0; + private const int MF_BYPOSITION = 0x400; + private const int MF_REMOVE = 0x1000; + private const int MF_ENABLED = 0x0; + private const int MF_GRAYED = 0x1; + private const int MF_DISABLED = 0x2; + private const int SC_CLOSE = 0xF060; + + void DisableCloseButton(IntPtr hwnd) + { + EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, + MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); + } + void EnableCloseButton(IntPtr hwnd) + { + EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, + MF_BYCOMMAND | MF_ENABLED); } #if USE_MANAGED_DRAG @@ -946,23 +990,23 @@ namespace Avalonia.Win32 } } - IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; - public bool ExtendClientAreaToDecorationsHint + public void SetExtendClientAreaToDecorationsHint(bool hint) { - get => _extendClientAreaToDecorationsHint; - set - { - _extendClientAreaToDecorationsHint = true; + ExtendClientArea(_extendChromeHints); + } - ExtendClientArea(); - // TODO Trigger transition. - } + private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) + { + _extendChromeHints = hints; } - public Action ExtendClientAreaToDecorationsChanged { get; set; } + public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; - private Thickness _extendedMargins; + public Action ExtendClientAreaToDecorationsChanged { get; set; } public Thickness ExtendedMargins => _extendedMargins; From 73d50254483ef97afa84b875892684d3587d0393 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 13:35:50 -0300 Subject: [PATCH 017/109] Add a readonly offscreen margin, to get the portion of the window that is offscreen. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 ++ src/Avalonia.Controls/Window.cs | 16 ++++++++- .../Remote/PreviewerWindowImpl.cs | 2 ++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 ++ src/Avalonia.Native/WindowImpl.cs | 2 ++ src/Avalonia.X11/X11Window.cs | 2 ++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 5 +++ src/Windows/Avalonia.Win32/WindowImpl.cs | 36 ++++++++++++++++++- 8 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index d7bf93a603..999f4f4201 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -104,5 +104,7 @@ namespace Avalonia.Platform Action ExtendClientAreaToDecorationsChanged { get; set; } Thickness ExtendedMargins { get; } + + Thickness OffScreenMargin { get; } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 85935d3d2a..54e2fbb629 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -113,7 +113,11 @@ namespace Avalonia.Controls public static readonly DirectProperty WindowDecorationMarginsProperty = AvaloniaProperty.RegisterDirect(nameof(WindowDecorationMargins), o => o.WindowDecorationMargins); - + + public static readonly DirectProperty OffScreenMarginProperty = + AvaloniaProperty.RegisterDirect(nameof(OffScreenMargin), + o => o.OffScreenMargin); + /// /// Defines the property. @@ -304,6 +308,14 @@ namespace Avalonia.Controls private set => SetAndRaise(WindowDecorationMarginsProperty, ref _windowDecorationMargins, value); } + private Thickness _offScreenMargin; + + public Thickness OffScreenMargin + { + get => _offScreenMargin; + private set => SetAndRaise(OffScreenMarginProperty, ref _offScreenMargin, value); + } + /// /// Sets the system decorations (title bar, border, etc) /// @@ -507,6 +519,8 @@ namespace Avalonia.Controls IsExtendedIntoWindowDecorations = isExtended; WindowDecorationMargins = PlatformImpl.ExtendedMargins; + + OffScreenMargin = PlatformImpl.OffScreenMargin; } /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 06c7ac6411..da7cbf815a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -91,6 +91,8 @@ namespace Avalonia.DesignerSupport.Remote public bool IsClientAreaExtendedToDecorations { get; } + public Thickness OffScreenMargin { get; } = new Thickness(); + public void Activate() { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 6f5a0e6051..6859c408dd 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -43,6 +43,8 @@ namespace Avalonia.DesignerSupport.Remote public Thickness ExtendedMargins { get; } = new Thickness(); + public Thickness OffScreenMargin { get; } = new Thickness(); + public WindowStub(IWindowImpl parent = null) { if (parent != null) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 47c8291803..cbea340246 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -102,6 +102,8 @@ namespace Avalonia.Native public Thickness ExtendedMargins { get; } = new Thickness(); + public Thickness OffScreenMargin { get; } = new Thickness(); + public bool IsClientAreaExtendedToDecorations { get; } public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index d64f993299..086863796c 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -315,6 +315,8 @@ namespace Avalonia.X11 public Thickness ExtendedMargins { get; } = new Thickness(); + public Thickness OffScreenMargin { get; } = new Thickness(); + public bool IsClientAreaExtendedToDecorations { get; } public Action Closed { get; set; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index b81634dce0..0c2160de2b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -374,6 +374,11 @@ namespace Avalonia.Win32 if (windowState != _lastWindowState) { _lastWindowState = windowState; + + UpdateExtendMargins(); + + ExtendClientAreaToDecorationsChanged?.Invoke(true); + WindowStateChanged?.Invoke(windowState); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7c720b5612..fd7084ea29 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -42,7 +42,8 @@ namespace Avalonia.Win32 private bool _isFullScreenActive; private bool _isClientAreaExtended; private Thickness _extendedMargins; - + private Thickness _offScreenMargin; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -667,6 +668,37 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } + private MARGINS UpdateExtendMargins() + { + RECT borderThickness = new RECT(); + RECT borderCaptionThickness = new RECT(); + + AdjustWindowRectEx(ref borderCaptionThickness, (uint)(GetStyle()), false, 0); + AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle() & ~WindowStyles.WS_CAPTION), false, 0); + borderThickness.left *= -1; + borderThickness.top *= -1; + borderCaptionThickness.left *= -1; + borderCaptionThickness.top *= -1; + + _extendedMargins = new Thickness(1 / Scaling, borderCaptionThickness.top / Scaling, 1 / Scaling, 1 / Scaling); + + if (WindowState == WindowState.Maximized) + { + _offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling); + } + else + { + _offScreenMargin = new Thickness(); + } + + MARGINS margins = new MARGINS(); + margins.cxLeftWidth = 1; + margins.cxRightWidth = 1; + margins.cyBottomHeight = 1; + margins.cyTopHeight = borderThickness.top; + return margins; + } + private void ExtendClientArea (ExtendClientAreaChromeHints hints) { if (!_isClientAreaExtended) @@ -1010,6 +1042,8 @@ namespace Avalonia.Win32 public Thickness ExtendedMargins => _extendedMargins; + public Thickness OffScreenMargin => _offScreenMargin; + private struct SavedWindowInfo { public WindowStyles Style { get; set; } From bd717d8b5c7f4fb9aff2996ff6c225d5221462b7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 13:41:57 -0300 Subject: [PATCH 018/109] calculate extend metrics in function. --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 39 ++++--------------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 0c2160de2b..5ffed48f2d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -375,7 +375,7 @@ namespace Avalonia.Win32 { _lastWindowState = windowState; - UpdateExtendMargins(); + UpdateExtendMargins(_extendChromeHints); ExtendClientAreaToDecorationsChanged?.Invoke(true); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fd7084ea29..e10ce99bc4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -668,7 +668,7 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } - private MARGINS UpdateExtendMargins() + private MARGINS UpdateExtendMargins(ExtendClientAreaChromeHints hints) { RECT borderThickness = new RECT(); RECT borderCaptionThickness = new RECT(); @@ -680,6 +680,11 @@ namespace Avalonia.Win32 borderCaptionThickness.left *= -1; borderCaptionThickness.top *= -1; + if (!hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar)) + { + borderCaptionThickness.top = 1; + } + _extendedMargins = new Thickness(1 / Scaling, borderCaptionThickness.top / Scaling, 1 / Scaling, 1 / Scaling); if (WindowState == WindowState.Maximized) @@ -695,7 +700,7 @@ namespace Avalonia.Win32 margins.cxLeftWidth = 1; margins.cxRightWidth = 1; margins.cyBottomHeight = 1; - margins.cyTopHeight = borderThickness.top; + margins.cyTopHeight = borderCaptionThickness.top; return margins; } @@ -708,35 +713,7 @@ namespace Avalonia.Win32 return; } - RECT border_thickness = new RECT(); - MARGINS margins = new MARGINS(); - - if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) - { - AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); - border_thickness.left *= -1; - border_thickness.top *= -1; - - border_thickness.left = 1; - border_thickness.bottom = 1; - border_thickness.right = 1; - - if (!hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar)) - { - border_thickness.top = 1; - } - - } - else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) - { - border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; - } - - // Extend the frame into the client area. - margins.cxLeftWidth = border_thickness.left; - margins.cxRightWidth = border_thickness.right; - margins.cyBottomHeight = border_thickness.bottom; - margins.cyTopHeight = border_thickness.top; + var margins = UpdateExtendMargins(hints); var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); From 693487313d8dfd3f5c41ca68fa8f631bb9fadb62 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 15:05:28 -0300 Subject: [PATCH 019/109] fix hit testing. --- .../WindowImpl.CustomCaptionProc.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index a55a22c3c3..fee6bb90bf 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Diagnostics; using Avalonia.Controls; using Avalonia.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -34,6 +33,11 @@ namespace Avalonia.Win32 border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; } + if (_extendTitleBarHint >= 0) + { + border_thickness.top = (int)(_extendedMargins.Top * Scaling); + } + // Determine if the hit test is for resizing. Default middle (1,1). ushort uRow = 1; ushort uCol = 1; @@ -86,11 +90,13 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCHITTEST: if (lRet == IntPtr.Zero) { - lRet = (IntPtr)HitTestNCA(hWnd, wParam, lParam); + var hittestResult = HitTestNCA(hWnd, wParam, lParam); + + lRet = (IntPtr)hittestResult; uint timestamp = unchecked((uint)GetMessageTime()); - if (((HitTestValues)lRet) == HitTestValues.HTCAPTION) + if (hittestResult == HitTestValues.HTCAPTION) { var position = PointToClient(PointFromLParam(lParam)); @@ -106,11 +112,12 @@ namespace Avalonia.Win32 if (visual != null) { - lRet = (IntPtr)HitTestValues.HTCLIENT; + hittestResult = HitTestValues.HTCLIENT; + lRet = (IntPtr)hittestResult; } } - if (((HitTestValues)lRet) != HitTestValues.HTNOWHERE) + if (hittestResult != HitTestValues.HTNOWHERE) { callDwp = false; } From a838129ff9b153dda37f65e817dc4b84451e29de Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 15:08:03 -0300 Subject: [PATCH 020/109] Configurable titlebar height. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e10ce99bc4..e18ee26996 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -680,12 +680,26 @@ namespace Avalonia.Win32 borderCaptionThickness.left *= -1; borderCaptionThickness.top *= -1; - if (!hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar)) + bool wantsTitleBar = hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) || _extendTitleBarHint == -1; + + if (!wantsTitleBar) { borderCaptionThickness.top = 1; } - _extendedMargins = new Thickness(1 / Scaling, borderCaptionThickness.top / Scaling, 1 / Scaling, 1 / Scaling); + MARGINS margins = new MARGINS(); + margins.cxLeftWidth = 1; + margins.cxRightWidth = 1; + margins.cyBottomHeight = 1; + + if (_extendTitleBarHint != -1) + { + borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling); + } + + margins.cyTopHeight = hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) ? borderCaptionThickness.top : 1; + + _extendedMargins = new Thickness(0, borderCaptionThickness.top / Scaling, 0, 0); if (WindowState == WindowState.Maximized) { @@ -696,11 +710,6 @@ namespace Avalonia.Win32 _offScreenMargin = new Thickness(); } - MARGINS margins = new MARGINS(); - margins.cxLeftWidth = 1; - margins.cxRightWidth = 1; - margins.cyBottomHeight = 1; - margins.cyTopHeight = borderCaptionThickness.top; return margins; } @@ -732,8 +741,7 @@ namespace Avalonia.Win32 if (hr == 0) { - _isClientAreaExtended = true; - _extendedMargins = new Thickness(margins.cxLeftWidth / Scaling, margins.cyTopHeight / Scaling, margins.cxRightWidth / Scaling, margins.cyBottomHeight / Scaling); + _isClientAreaExtended = true; ExtendClientAreaToDecorationsChanged?.Invoke(true); } } @@ -1013,6 +1021,12 @@ namespace Avalonia.Win32 _extendChromeHints = hints; } + private double _extendTitleBarHint = -1; + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) + { + _extendTitleBarHint = titleBarHeight; + } + public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; public Action ExtendClientAreaToDecorationsChanged { get; set; } From d587096e88438306965755b07de13d6f5c9cf6a0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 15:08:33 -0300 Subject: [PATCH 021/109] Add TitleBarHeight Hint. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 ++ src/Avalonia.Controls/Window.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 999f4f4201..15315063fe 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -101,6 +101,8 @@ namespace Avalonia.Platform void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints); + void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); + Action ExtendClientAreaToDecorationsChanged { get; set; } Thickness ExtendedMargins { get; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 54e2fbb629..12b61fce23 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -98,6 +98,9 @@ namespace Avalonia.Controls public static readonly StyledProperty ExtendClientAreaChromeHintsProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaChromeHints), ExtendClientAreaChromeHints.Default); + public static readonly StyledProperty ExtendClientAreaTitleBarHeightHintProperty = + AvaloniaProperty.Register(nameof(ExtendClientAreaTitleBarHeightHint), -1); + /// /// Defines the property. @@ -202,6 +205,9 @@ namespace Avalonia.Controls ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); }); + ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler( + (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue); }); + MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); @@ -291,6 +297,12 @@ namespace Avalonia.Controls set => SetValue(ExtendClientAreaChromeHintsProperty, value); } + public double ExtendClientAreaTitleBarHeightHint + { + get => GetValue(ExtendClientAreaTitleBarHeightHintProperty); + set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value); + } + /// /// Gets if the ClientArea is Extended into the Window Decorations. /// From 72e24625a02e54ca1283ca009a1e4f2d800484c8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 15:08:48 -0300 Subject: [PATCH 022/109] Stubs for TitleBarHeight Hint. --- src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs | 4 ++++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 4 ++++ src/Avalonia.Native/WindowImpl.cs | 4 ++++ src/Avalonia.X11/X11Window.cs | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index da7cbf815a..0f35545d13 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -140,5 +140,9 @@ namespace Avalonia.DesignerSupport.Remote public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { } + + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) + { + } } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 6859c408dd..400ecda405 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -154,6 +154,10 @@ namespace Avalonia.DesignerSupport.Remote { } + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) + { + } + public IPopupPositioner PopupPositioner { get; } public Action GotInputWhenDisabled { get; set; } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index cbea340246..532869e751 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -114,6 +114,10 @@ namespace Avalonia.Native { } + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) + { + } + public void ShowTaskbarIcon(bool value) { // NO OP On OSX diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 086863796c..0a58e03c2f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1051,6 +1051,10 @@ namespace Avalonia.X11 { } + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) + { + } + public Action GotInputWhenDisabled { get; set; } public void SetIcon(IWindowIconImpl icon) From df41ca8f6e1875e742b381cf4a9be842c232f8f8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 15:42:23 -0300 Subject: [PATCH 023/109] Allow extend hints to be changed at runtime. --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 11 ++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 60 ++++++++++++------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 5ffed48f2d..18283432e4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -373,13 +373,16 @@ namespace Avalonia.Win32 if (windowState != _lastWindowState) { - _lastWindowState = windowState; + _lastWindowState = windowState; - UpdateExtendMargins(_extendChromeHints); + WindowStateChanged?.Invoke(windowState); - ExtendClientAreaToDecorationsChanged?.Invoke(true); + if (_isClientAreaExtended) + { + UpdateExtendMargins(); - WindowStateChanged?.Invoke(windowState); + ExtendClientAreaToDecorationsChanged?.Invoke(true); + } } return IntPtr.Zero; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e18ee26996..a88a342b00 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -668,7 +668,7 @@ namespace Avalonia.Win32 TaskBarList.MarkFullscreen(_hwnd, fullscreen); } - private MARGINS UpdateExtendMargins(ExtendClientAreaChromeHints hints) + private MARGINS UpdateExtendMargins() { RECT borderThickness = new RECT(); RECT borderCaptionThickness = new RECT(); @@ -680,7 +680,7 @@ namespace Avalonia.Win32 borderCaptionThickness.left *= -1; borderCaptionThickness.top *= -1; - bool wantsTitleBar = hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) || _extendTitleBarHint == -1; + bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) || _extendTitleBarHint == -1; if (!wantsTitleBar) { @@ -697,7 +697,7 @@ namespace Avalonia.Win32 borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling); } - margins.cyTopHeight = hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) ? borderCaptionThickness.top : 1; + margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) ? borderCaptionThickness.top : 1; _extendedMargins = new Thickness(0, borderCaptionThickness.top / Scaling, 0, 0); @@ -713,22 +713,32 @@ namespace Avalonia.Win32 return margins; } - private void ExtendClientArea (ExtendClientAreaChromeHints hints) + private void ExtendClientArea () { - if (!_isClientAreaExtended) + if (DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled) { - if(DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled) - { - return; - } + _isClientAreaExtended = false; + return; + } - var margins = UpdateExtendMargins(hints); + GetWindowRect(_hwnd, out var rcClient); - var hr = DwmExtendFrameIntoClientArea(_hwnd, ref margins); + // Inform the application of the frame change. + SetWindowPos(_hwnd, + IntPtr.Zero, + rcClient.left, rcClient.top, + rcClient.Width, rcClient.Height, + SetWindowPosFlags.SWP_FRAMECHANGED); + + if(_isClientAreaExtended) + { + var margins = UpdateExtendMargins(); - if(!hints.HasFlag(ExtendClientAreaChromeHints.SystemChromeButtons) || - (hints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons) && - !hints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar))) + DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + if(!_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChromeButtons) || + (_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons) && + !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar))) { var style = GetStyle(); @@ -738,13 +748,17 @@ namespace Avalonia.Win32 DisableCloseButton(_hwnd); } + } + else + { + var margins = new MARGINS(); + DwmExtendFrameIntoClientArea(_hwnd, ref margins); - if (hr == 0) - { - _isClientAreaExtended = true; - ExtendClientAreaToDecorationsChanged?.Invoke(true); - } + _offScreenMargin = new Thickness(); + _extendedMargins = new Thickness(); } + + ExtendClientAreaToDecorationsChanged?.Invoke(true); } private void ShowWindow(WindowState state) @@ -1011,7 +1025,9 @@ namespace Avalonia.Win32 public void SetExtendClientAreaToDecorationsHint(bool hint) { - ExtendClientArea(_extendChromeHints); + _isClientAreaExtended = hint; + + ExtendClientArea(); } private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; @@ -1019,12 +1035,16 @@ namespace Avalonia.Win32 public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { _extendChromeHints = hints; + + ExtendClientArea(); } private double _extendTitleBarHint = -1; public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) { _extendTitleBarHint = titleBarHeight; + + ExtendClientArea(); } public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; From 454cc06184ae1dc459732905679f9661d65f15e9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 16:26:51 -0300 Subject: [PATCH 024/109] Add a WindowCustomization page to the control catalog. --- samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/MainWindow.xaml | 8 ++- .../Pages/WindowCustomizationsPage.xaml | 13 ++++ .../Pages/WindowCustomizationsPage.xaml.cs | 19 ++++++ .../ViewModels/MainWindowViewModel.cs | 60 +++++++++++++++++++ 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml create mode 100644 samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index fef68bb5f5..5e0495adf5 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -61,6 +61,7 @@ + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 76422bc130..4c3a00a402 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}"> + ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" + ExtendClientAreaChromeHints="{Binding ChromeHints}" + ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" + x:Name="MainWindow" + x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}"> @@ -61,7 +65,7 @@ - + diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml new file mode 100644 index 0000000000..168b504b40 --- /dev/null +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs new file mode 100644 index 0000000000..d8d4f3f371 --- /dev/null +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class WindowCustomizationsPage : UserControl + { + public WindowCustomizationsPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 0257b4ce66..26b7e1bea8 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -3,6 +3,8 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Dialogs; +using Avalonia.Platform; +using System; using ReactiveUI; namespace ControlCatalog.ViewModels @@ -62,8 +64,66 @@ namespace ControlCatalog.ViewModels WindowState.Maximized, WindowState.FullScreen, }; + + this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x => x.SystemTitleBarEnabled) + .Subscribe(x => + { + ChromeHints = ExtendClientAreaChromeHints.NoChrome; + + if(x.Item1) + { + ChromeHints |= ExtendClientAreaChromeHints.SystemChromeButtons; + } + + if(x.Item2) + { + ChromeHints |= ExtendClientAreaChromeHints.SystemTitleBar; + } + }); + } + + private ExtendClientAreaChromeHints _chromeHints; + + public ExtendClientAreaChromeHints ChromeHints + { + get { return _chromeHints; } + set { this.RaiseAndSetIfChanged(ref _chromeHints, value); } + } + + + private bool _extendClientAreaEnabled; + + public bool ExtendClientAreaEnabled + { + get { return _extendClientAreaEnabled; } + set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); } } + private bool _systemTitleBarEnabled; + + public bool SystemTitleBarEnabled + { + get { return _systemTitleBarEnabled; } + set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); } + } + + private bool _systemChromeButtonsEnabled; + + public bool SystemChromeButtonsEnabled + { + get { return _systemChromeButtonsEnabled; } + set { this.RaiseAndSetIfChanged(ref _systemChromeButtonsEnabled, value); } + } + + private double _titleBarHeight; + + public double TitleBarHeight + { + get { return _titleBarHeight; } + set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); } + } + + public WindowState WindowState { get { return _windowState; } From 1ce5fdfd03744932edcba0d7ef1654a4e454565e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 16:27:33 -0300 Subject: [PATCH 025/109] Update when caption buttons are turned on or off. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a88a342b00..d09e3196da 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -748,6 +748,16 @@ namespace Avalonia.Win32 DisableCloseButton(_hwnd); } + else + { + var style = GetStyle(); + + style |= (WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); + + SetStyle(style); + + EnableCloseButton(_hwnd); + } } else { @@ -756,6 +766,14 @@ namespace Avalonia.Win32 _offScreenMargin = new Thickness(); _extendedMargins = new Thickness(); + + var style = GetStyle(); + + style |= (WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); + + SetStyle(style); + + EnableCloseButton(_hwnd); } ExtendClientAreaToDecorationsChanged?.Invoke(true); From 6e809937c6eaaeeb7ce8ba827a7962820a8074d3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 16:27:57 -0300 Subject: [PATCH 026/109] NoChome is 0 --- src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index af33e3093b..e98124b2d8 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -4,9 +4,9 @@ namespace Avalonia.Platform { [Flags] public enum ExtendClientAreaChromeHints - { - Default = SystemTitleBar | SystemChromeButtons, + { NoChrome, + Default = SystemTitleBar | SystemChromeButtons, SystemTitleBar = 0x01, SystemChromeButtons = 0x02, ManagedChromeButtons = 0x04, From d4fa9ad406354c917ec7b15ee1be3340db0b64c1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 16:36:49 -0300 Subject: [PATCH 027/109] Move transparency to window customizations page. --- samples/ControlCatalog/MainView.xaml.cs | 7 ------- samples/ControlCatalog/MainWindow.xaml | 1 + .../Pages/WindowCustomizationsPage.xaml | 5 +++++ .../ControlCatalog/ViewModels/MainWindowViewModel.cs | 12 ++++++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index d6d4a71ad3..b0c205246e 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -58,13 +58,6 @@ namespace ControlCatalog if (VisualRoot is Window window) window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex; }; - - var transparencyLevels = this.Find("TransparencyLevels"); - transparencyLevels.SelectionChanged += (sender, e) => - { - if (VisualRoot is Window window) - window.TransparencyLevelHint = (WindowTransparencyLevel)transparencyLevels.SelectedIndex; - }; } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 4c3a00a402..e92339416b 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -10,6 +10,7 @@ ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" ExtendClientAreaChromeHints="{Binding ChromeHints}" ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" + TransparencyLevelHint="{Binding TransparencyLevel}" x:Name="MainWindow" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}"> diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index 168b504b40..44973f5b79 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -9,5 +9,10 @@ + + None + Transparent + Blur + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 26b7e1bea8..80231a4c15 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -80,6 +80,18 @@ namespace ControlCatalog.ViewModels ChromeHints |= ExtendClientAreaChromeHints.SystemTitleBar; } }); + + SystemTitleBarEnabled = true; + SystemChromeButtonsEnabled = true; + TitleBarHeight = -1; + } + + private int _transparencyLevel; + + public int TransparencyLevel + { + get { return _transparencyLevel; } + set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); } } private ExtendClientAreaChromeHints _chromeHints; From bb83df834a6e524935b31edf0f263ba4fc2b102a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 16:55:19 -0300 Subject: [PATCH 028/109] fully working client area extension demo --- samples/ControlCatalog/MainWindow.xaml | 32 ++++++++++++------- .../Pages/WindowCustomizationsPage.xaml | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index e92339416b..661975d0b5 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -61,20 +61,28 @@ - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index 44973f5b79..2f9c2d3938 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> - + From ae40af4d99a3e29551fe7aee6a85cdbc4da5ae4d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 17:57:21 -0300 Subject: [PATCH 029/109] fix layout. --- samples/ControlCatalog/MainWindow.xaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 661975d0b5..a6750f6444 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -66,8 +66,9 @@ - - + + + @@ -78,11 +79,12 @@ - + + - + From d32c9b25394fc8611d3172b14955d82eb421039e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 21 May 2020 17:58:45 -0300 Subject: [PATCH 030/109] only show titlebar content when in dwm mode. --- samples/ControlCatalog/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index a6750f6444..b110451421 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -80,7 +80,7 @@ - + From e93554d0da4544784fb7fac35e12abb50853d66a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 14:38:59 -0300 Subject: [PATCH 031/109] Add managed CaptionButtonsControl. --- .../Chrome/CaptionButtons.cs | 89 +++++++++++++++++++ .../Primitives/ChromeOverlayLayer.cs | 37 ++++++++ .../Primitives/VisualLayerManager.cs | 15 +++- .../Properties/AssemblyInfo.cs | 1 + .../CaptionButtons.xaml | 70 +++++++++++++++ src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + 6 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Controls/Chrome/CaptionButtons.cs create mode 100644 src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs create mode 100644 src/Avalonia.Themes.Default/CaptionButtons.xaml diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs new file mode 100644 index 0000000000..20a42aadf6 --- /dev/null +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Text; +using Avalonia.Controls.Primitives; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.Chrome +{ + public class CaptionButtons : TemplatedControl + { + private CompositeDisposable _disposables; + private Window _hostWindow; + + public CaptionButtons(Window hostWindow) + { + _hostWindow = hostWindow; + } + + public void Attach() + { + if (_disposables == null) + { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer.Children.Add(this); + + _disposables = new CompositeDisposable + { + _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) + .Subscribe(x => + { + Height = x.Top; + }), + + + _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) + .Subscribe(x => InvalidateSize()), + + _hostWindow.GetObservable(Window.OffScreenMarginProperty) + .Subscribe(x => InvalidateSize()), + + _hostWindow.GetObservable(Window.WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":minimized", x == WindowState.Minimized); + PseudoClasses.Set(":normal", x == WindowState.Normal); + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }) + }; + } + } + + void InvalidateSize () + { + Margin = new Thickness(1, _hostWindow.OffScreenMargin.Top, 1, 1); + Height = _hostWindow.WindowDecorationMargins.Top - _hostWindow.OffScreenMargin.Top; + } + + public void Detach() + { + if (_disposables != null) + { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer.Children.Remove(this); + + _disposables.Dispose(); + _disposables = null; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + var closeButton = e.NameScope.Find("PART_CloseButton"); + var restoreButton = e.NameScope.Find("PART_RestoreButton"); + var minimiseButton = e.NameScope.Find("PART_MinimiseButton"); + var fullScreenButton = e.NameScope.Find("PART_FullScreenButton"); + + closeButton.PointerPressed += (sender, e) => _hostWindow.Close(); + restoreButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + minimiseButton.PointerPressed += (sender, e) => _hostWindow.WindowState = WindowState.Minimized; + fullScreenButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; + } + } +} diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs new file mode 100644 index 0000000000..5deb9ac408 --- /dev/null +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Avalonia.Rendering; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.Primitives +{ + public class ChromeOverlayLayer : Canvas, ICustomSimpleHitTest + { + public Size AvailableSize { get; private set; } + + public static ChromeOverlayLayer GetOverlayLayer(IVisual visual) + { + foreach (var v in visual.GetVisualAncestors()) + if (v is VisualLayerManager vlm) + if (vlm.OverlayLayer != null) + return vlm.ChromeOverlayLayer; + + if (visual is TopLevel tl) + { + var layers = tl.GetVisualDescendants().OfType().FirstOrDefault(); + return layers?.ChromeOverlayLayer; + } + + return null; + } + + public bool HitTest(Point point) => Children.HitTestCustom(point); + + protected override Size ArrangeOverride(Size finalSize) + { + // We are saving it here since child controls might need to know the entire size of the overlay + // and Bounds won't be updated in time + AvailableSize = finalSize; + return base.ArrangeOverride(finalSize); + } + } +} diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 86aeb5c62a..3084d7fa72 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -6,7 +6,9 @@ namespace Avalonia.Controls.Primitives public class VisualLayerManager : Decorator { private const int AdornerZIndex = int.MaxValue - 100; - private const int OverlayZIndex = int.MaxValue - 99; + private const int ChromeZIndex = int.MaxValue - 99; + private const int OverlayZIndex = int.MaxValue - 98; + private ILogicalRoot _logicalRoot; private readonly List _layers = new List(); @@ -24,6 +26,17 @@ namespace Avalonia.Controls.Primitives } } + public ChromeOverlayLayer ChromeOverlayLayer + { + get + { + var rv = FindLayer(); + if (rv == null) + AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex); + return rv; + } + } + public OverlayLayer OverlayLayer { get diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index 060db46212..672fbe294e 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -13,3 +13,4 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Chrome")] diff --git a/src/Avalonia.Themes.Default/CaptionButtons.xaml b/src/Avalonia.Themes.Default/CaptionButtons.xaml new file mode 100644 index 0000000000..0cc0d6b9cd --- /dev/null +++ b/src/Avalonia.Themes.Default/CaptionButtons.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 94d26e798b..97cff5d94b 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -9,6 +9,7 @@ + From 6e1ccb6333bfaa04556c7486fbca0d458671be58 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 14:39:16 -0300 Subject: [PATCH 032/109] Add acrylic blur option to control catalog. --- samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index 2f9c2d3938..f2513da8ec 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -13,6 +13,7 @@ None Transparent Blur + AcrylicBlur From 1e798be7848f9120e69db73a36b9247fe3b2e4c0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 15:04:01 -0300 Subject: [PATCH 033/109] insert managed chrome when extend client area is enabled and hint flag is set. --- .../Pages/WindowCustomizationsPage.xaml | 1 + .../ViewModels/MainWindowViewModel.cs | 16 ++++++++- src/Avalonia.Controls/Window.cs | 34 +++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index f2513da8ec..cea8e4b94e 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -8,6 +8,7 @@ + None diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 80231a4c15..b3ea25e470 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.ViewModels WindowState.FullScreen, }; - this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x => x.SystemTitleBarEnabled) + this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x=>x.ManagedChromeButtonsEnabled, x => x.SystemTitleBarEnabled) .Subscribe(x => { ChromeHints = ExtendClientAreaChromeHints.NoChrome; @@ -76,6 +76,11 @@ namespace ControlCatalog.ViewModels } if(x.Item2) + { + ChromeHints |= ExtendClientAreaChromeHints.ManagedChromeButtons; + } + + if(x.Item3) { ChromeHints |= ExtendClientAreaChromeHints.SystemTitleBar; } @@ -127,6 +132,15 @@ namespace ControlCatalog.ViewModels set { this.RaiseAndSetIfChanged(ref _systemChromeButtonsEnabled, value); } } + private bool _managedChromeButtonsEnabled; + + public bool ManagedChromeButtonsEnabled + { + get { return _managedChromeButtonsEnabled; } + set { this.RaiseAndSetIfChanged(ref _managedChromeButtonsEnabled, value); } + } + + private double _titleBarHeight; public double TitleBarHeight diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 12b61fce23..8d66e03f98 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; +using Avalonia.Controls.Chrome; using Avalonia.Controls.Platform; using Avalonia.Data; using Avalonia.Input; @@ -69,7 +70,8 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot { - private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); + private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); + private CaptionButtons _managedCaptions; private bool _isExtendedIntoWindowDecorations; @@ -203,7 +205,15 @@ namespace Avalonia.Controls (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue); }); ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler( - (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); }); + (w, e) => + { + if (w.PlatformImpl != null) + { + w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); + } + + w.HandleChromeHintsChanged((ExtendClientAreaChromeHints)e.NewValue); + }); ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue); }); @@ -897,6 +907,26 @@ namespace Avalonia.Controls base.HandleResized(clientSize); } + private void HandleChromeHintsChanged (ExtendClientAreaChromeHints hints) + { + if(hints.HasFlag(ExtendClientAreaChromeHints.ManagedChromeButtons)) + { + if(_managedCaptions == null) + { + _managedCaptions = new CaptionButtons(this); + } + + _managedCaptions.Attach(); + } + else + { + if(_managedCaptions != null) + { + _managedCaptions.Detach(); + } + } + } + /// /// Raises the event. /// From fbe25123cea123da785f6047bbbdefd637b6ce0e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 16:14:41 -0300 Subject: [PATCH 034/109] When system captions are disabled on win32 and window is maximized border will not be hidden offscreen. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d09e3196da..4c694b8b55 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -701,7 +701,11 @@ namespace Avalonia.Win32 _extendedMargins = new Thickness(0, borderCaptionThickness.top / Scaling, 0, 0); - if (WindowState == WindowState.Maximized) + if (WindowState == WindowState.Maximized && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChromeButtons)) + { + _offScreenMargin = new Thickness(); + } + else if (WindowState == WindowState.Maximized) { _offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling); } From 4152b2e365da44e03955346ec98988a67661930e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 17:18:34 -0300 Subject: [PATCH 035/109] Decorations Margin shouldnt include the border margin other wise users have to do subtractions. --- src/Avalonia.Controls/Chrome/CaptionButtons.cs | 2 +- src/Avalonia.Controls/Window.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 20a42aadf6..556c759825 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -55,7 +55,7 @@ namespace Avalonia.Controls.Chrome void InvalidateSize () { Margin = new Thickness(1, _hostWindow.OffScreenMargin.Top, 1, 1); - Height = _hostWindow.WindowDecorationMargins.Top - _hostWindow.OffScreenMargin.Top; + Height = _hostWindow.WindowDecorationMargins.Top; } public void Detach() diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 8d66e03f98..d63b1350bc 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -540,7 +540,11 @@ namespace Avalonia.Controls { IsExtendedIntoWindowDecorations = isExtended; - WindowDecorationMargins = PlatformImpl.ExtendedMargins; + WindowDecorationMargins = new Thickness( + PlatformImpl.ExtendedMargins.Left, + PlatformImpl.ExtendedMargins.Top - PlatformImpl.OffScreenMargin.Top, + PlatformImpl.ExtendedMargins.Right, + PlatformImpl.ExtendedMargins.Bottom); OffScreenMargin = PlatformImpl.OffScreenMargin; } From 9e812454159520abd19d819165a422934b0ee6d1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 May 2020 17:27:33 -0300 Subject: [PATCH 036/109] extend api only allows to turn titlebar on or off to maintain window integrity, and decoration margins dont include border margins. --- .../Platform/ExtendClientAreaChromeHints.cs | 7 ++- src/Avalonia.Controls/Window.cs | 6 +-- .../CaptionButtons.xaml | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 43 ++----------------- 4 files changed, 9 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index e98124b2d8..075e4ba436 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -6,11 +6,10 @@ namespace Avalonia.Platform public enum ExtendClientAreaChromeHints { NoChrome, - Default = SystemTitleBar | SystemChromeButtons, + Default = SystemTitleBar, SystemTitleBar = 0x01, - SystemChromeButtons = 0x02, - ManagedChromeButtons = 0x04, - PreferSystemChromeButtons = 0x08, + ManagedChromeButtons = 0x02, + PreferSystemChromeButtons = 0x04, AdaptiveChromeWithTitleBar = SystemTitleBar | PreferSystemChromeButtons, AdaptiveChromeWithoutTitleBar = PreferSystemChromeButtons, } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index d63b1350bc..8d66e03f98 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -540,11 +540,7 @@ namespace Avalonia.Controls { IsExtendedIntoWindowDecorations = isExtended; - WindowDecorationMargins = new Thickness( - PlatformImpl.ExtendedMargins.Left, - PlatformImpl.ExtendedMargins.Top - PlatformImpl.OffScreenMargin.Top, - PlatformImpl.ExtendedMargins.Right, - PlatformImpl.ExtendedMargins.Bottom); + WindowDecorationMargins = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; } diff --git a/src/Avalonia.Themes.Default/CaptionButtons.xaml b/src/Avalonia.Themes.Default/CaptionButtons.xaml index 0cc0d6b9cd..8ee8a3b0dd 100644 --- a/src/Avalonia.Themes.Default/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Default/CaptionButtons.xaml @@ -5,7 +5,7 @@ - + - @@ -27,13 +24,13 @@ - + - + @@ -42,13 +39,13 @@ - + - + diff --git a/src/Avalonia.Themes.Default/TitleBar.xaml b/src/Avalonia.Themes.Default/TitleBar.xaml new file mode 100644 index 0000000000..856b90857c --- /dev/null +++ b/src/Avalonia.Themes.Default/TitleBar.xaml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml new file mode 100644 index 0000000000..20efcafcfd --- /dev/null +++ b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Styles> diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 49b2d9561b..a1f8d97c97 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -7,7 +7,8 @@ - + + @@ -36,6 +37,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml new file mode 100644 index 0000000000..6512ee342c --- /dev/null +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index fee6bb90bf..d4d7528ef4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -90,6 +90,10 @@ namespace Avalonia.Win32 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; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9139a8ccbd..bed6922076 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -181,6 +181,11 @@ namespace Avalonia.Win32 { get { + if(_isFullScreenActive) + { + return WindowState.FullScreen; + } + var placement = default(WINDOWPLACEMENT); GetWindowPlacement(_hwnd, ref placement); From a2cf709164e87c40793fe8daf0ad4d2bc469a75e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Jun 2020 15:04:21 -0300 Subject: [PATCH 059/109] fix xaml error. --- src/Avalonia.Themes.Fluent/CaptionButtons.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml index 20efcafcfd..e5177bbdad 100644 --- a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml @@ -64,4 +64,4 @@ - Styles> + From b0b7a65531d51bc30e4a272a3d47dfc369a260a1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Jun 2020 15:08:48 -0300 Subject: [PATCH 060/109] hide certain caption buttons in fullscreen mode. --- src/Avalonia.Themes.Fluent/CaptionButtons.xaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml index e5177bbdad..7ed27a1747 100644 --- a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/CaptionButtons.xaml @@ -64,4 +64,7 @@ + From 6e47b625cfe3fa61f170b494909232ff5580a10c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Jun 2020 15:49:36 -0300 Subject: [PATCH 061/109] fix animations --- .../ControlCatalog/ViewModels/MainWindowViewModel.cs | 10 ++++++---- src/Avalonia.Controls/Chrome/TitleBar.cs | 3 +++ src/Avalonia.Themes.Fluent/TitleBar.xaml | 7 ++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 939f44d772..27ea1ffb15 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -68,22 +68,24 @@ namespace ControlCatalog.ViewModels this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x=>x.ManagedChromeButtonsEnabled, x => x.SystemTitleBarEnabled) .Subscribe(x => { - ChromeHints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; + var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; if(x.Item1) { - ChromeHints |= ExtendClientAreaChromeHints.SystemChromeButtons; + hints |= ExtendClientAreaChromeHints.SystemChromeButtons; } if(x.Item2) { - ChromeHints |= ExtendClientAreaChromeHints.ManagedChromeButtons; + hints |= ExtendClientAreaChromeHints.ManagedChromeButtons; } if(x.Item3) { - ChromeHints |= ExtendClientAreaChromeHints.SystemTitleBar; + hints |= ExtendClientAreaChromeHints.SystemTitleBar; } + + ChromeHints = hints; }); SystemTitleBarEnabled = true; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 23eefb5d1b..d4ac5cd855 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -2,6 +2,7 @@ using System.Reactive.Disposables; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.LogicalTree; using Avalonia.Media; namespace Avalonia.Controls.Chrome @@ -63,6 +64,8 @@ namespace Avalonia.Controls.Chrome PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); }) }; + + _captionButtons?.Attach(_hostWindow); } } diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 6512ee342c..f7d9f1df10 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -30,15 +30,13 @@ - - - + - - - - From 765bd84c3e64f623201dd486dc9c51f43d11ab17 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Jun 2020 13:35:58 -0300 Subject: [PATCH 063/109] polish managed titlebar. --- .../Chrome/CaptionButtons.cs | 18 ------- src/Avalonia.Controls/Chrome/TitleBar.cs | 34 +++++-------- src/Avalonia.Themes.Fluent/TitleBar.xaml | 12 +++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 48 +++++++++++-------- 4 files changed, 49 insertions(+), 63 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 618c3c4fb2..99db4e550b 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -20,18 +20,6 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable { - _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) - .Subscribe(x => - { - Height = x.Top; - }), - - _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => InvalidateSize()), - - _hostWindow.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => InvalidateSize()), - _hostWindow.GetObservable(Window.WindowStateProperty) .Subscribe(x => { @@ -44,12 +32,6 @@ namespace Avalonia.Controls.Chrome } } - void InvalidateSize () - { - Margin = new Thickness(1, _hostWindow.OffScreenMargin.Top, 1, 1); - Height = _hostWindow.WindowDecorationMargins.Top; - } - public void Detach() { if (_disposables != null) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index d4ac5cd855..b0ae25892e 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -18,21 +18,6 @@ namespace Avalonia.Controls.Chrome _hostWindow = hostWindow; } - public TitleBar() - { - - } - - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } - - protected override Size ArrangeOverride(Size finalSize) - { - return base.ArrangeOverride(finalSize); - } - public void Attach() { if (_disposables == null) @@ -44,10 +29,7 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) - .Subscribe(x => - { - Height = x.Top; - }), + .Subscribe(x => InvalidateSize()), _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) .Subscribe(x => InvalidateSize()), @@ -71,8 +53,16 @@ namespace Avalonia.Controls.Chrome void InvalidateSize() { - Margin = new Thickness(1, _hostWindow.OffScreenMargin.Top, 1, 1); - Height = _hostWindow.WindowDecorationMargins.Top; + Margin = new Thickness( + _hostWindow.OffScreenMargin.Left, + _hostWindow.OffScreenMargin.Top, + _hostWindow.OffScreenMargin.Right, + _hostWindow.OffScreenMargin.Bottom); + + if (_hostWindow.WindowState != WindowState.FullScreen) + { + Height = _hostWindow.WindowDecorationMargins.Top; + } } public void Detach() @@ -94,7 +84,7 @@ namespace Avalonia.Controls.Chrome { base.OnApplyTemplate(e); - _captionButtons = e.NameScope.Find("PART_CaptionButtons"); + _captionButtons = e.NameScope.Find("PART_CaptionButtons"); _captionButtons.Attach(_hostWindow); } diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 5083f4c15a..db4dbe95a2 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -5,23 +5,27 @@ + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bed6922076..5ffcc2034c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -671,6 +671,8 @@ namespace Avalonia.Win32 } TaskBarList.MarkFullscreen(_hwnd, fullscreen); + + ExtendClientArea(); } private MARGINS UpdateExtendMargins() @@ -724,33 +726,41 @@ namespace Avalonia.Win32 { _isClientAreaExtended = false; return; - } - - GetWindowRect(_hwnd, out var rcClient); + } - // Inform the application of the frame change. - SetWindowPos(_hwnd, - IntPtr.Zero, - rcClient.left, rcClient.top, - rcClient.Width, rcClient.Height, - SetWindowPosFlags.SWP_FRAMECHANGED); - - if(_isClientAreaExtended) + if (!_isClientAreaExtended || WindowState == WindowState.FullScreen) { - var margins = UpdateExtendMargins(); - - DwmExtendFrameIntoClientArea(_hwnd, ref margins); + _extendedMargins = new Thickness(0, 0, 0, 0); + _offScreenMargin = new Thickness(); } else { - var margins = new MARGINS(); - DwmExtendFrameIntoClientArea(_hwnd, ref margins); + GetWindowRect(_hwnd, out var rcClient); - _offScreenMargin = new Thickness(); - _extendedMargins = new Thickness(); + // Inform the application of the frame change. + SetWindowPos(_hwnd, + IntPtr.Zero, + rcClient.left, rcClient.top, + rcClient.Width, rcClient.Height, + SetWindowPosFlags.SWP_FRAMECHANGED); + + if (_isClientAreaExtended) + { + var margins = UpdateExtendMargins(); + + DwmExtendFrameIntoClientArea(_hwnd, ref margins); + } + else + { + var margins = new MARGINS(); + DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + _offScreenMargin = new Thickness(); + _extendedMargins = new Thickness(); + } } - ExtendClientAreaToDecorationsChanged?.Invoke(true); + ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); } private void ShowWindow(WindowState state) From df7f6a4fdb6da342dd2106e93c669f4bca5f66fe Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 10 Jun 2020 20:57:56 -0300 Subject: [PATCH 064/109] fix titlebar sizing. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 5 +++++ src/Avalonia.Themes.Fluent/TitleBar.xaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index b0ae25892e..aeac9b76d4 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -62,6 +62,11 @@ namespace Avalonia.Controls.Chrome if (_hostWindow.WindowState != WindowState.FullScreen) { Height = _hostWindow.WindowDecorationMargins.Top; + + if (_captionButtons != null) + { + _captionButtons.Height = Height; + } } } diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index db4dbe95a2..3c194e5d44 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -15,7 +15,7 @@ - + From 3ba935c2071d1a28c0efb129af9367396822094f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 14 Jun 2020 13:50:57 -0300 Subject: [PATCH 065/109] fix managed titlebar sizing. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index aeac9b76d4..5b938c6830 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -48,6 +48,8 @@ namespace Avalonia.Controls.Chrome }; _captionButtons?.Attach(_hostWindow); + + InvalidateSize(); } } From e1de29cf540f14c0e162aad5cc30925b4ca1614a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 14 Jun 2020 15:31:44 -0300 Subject: [PATCH 066/109] fix titlebar and caption buttons --- src/Avalonia.Controls/Chrome/TitleBar.cs | 10 ++++++--- .../CaptionButtons.xaml | 21 ++++++++++++------- src/Avalonia.Themes.Fluent/TitleBar.xaml | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 5b938c6830..8e8010d3d4 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -1,9 +1,6 @@ using System; using System.Reactive.Disposables; using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.LogicalTree; -using Avalonia.Media; namespace Avalonia.Controls.Chrome { @@ -18,6 +15,11 @@ namespace Avalonia.Controls.Chrome _hostWindow = hostWindow; } + public TitleBar() + { + + } + public void Attach() { if (_disposables == null) @@ -94,6 +96,8 @@ namespace Avalonia.Controls.Chrome _captionButtons = e.NameScope.Find("PART_CaptionButtons"); _captionButtons.Attach(_hostWindow); + + InvalidateSize(); } } } diff --git a/src/Avalonia.Themes.Default/CaptionButtons.xaml b/src/Avalonia.Themes.Default/CaptionButtons.xaml index 8d6d6fb6fc..c47ab05fd6 100644 --- a/src/Avalonia.Themes.Default/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Default/CaptionButtons.xaml @@ -1,6 +1,11 @@ - - - + - + - + @@ -43,7 +48,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 3c194e5d44..45798d3fa1 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -5,7 +5,7 @@ - - + - + - + - + - + @@ -69,4 +64,7 @@ - + + diff --git a/src/Avalonia.Themes.Default/TitleBar.xaml b/src/Avalonia.Themes.Default/TitleBar.xaml index 856b90857c..45798d3fa1 100644 --- a/src/Avalonia.Themes.Default/TitleBar.xaml +++ b/src/Avalonia.Themes.Default/TitleBar.xaml @@ -5,15 +5,49 @@ + + + + + + + + + + + + From 50ec4da4cd3b08a53f60b2ec5c5c8e4e008d5b15 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 16 Jun 2020 15:01:53 -0300 Subject: [PATCH 069/109] dont keep capture when minimising. --- src/Avalonia.Controls/Chrome/CaptionButtons.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 99db4e550b..58f5b09c0d 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -54,10 +54,10 @@ namespace Avalonia.Controls.Chrome var minimiseButton = e.NameScope.Find("PART_MinimiseButton"); var fullScreenButton = e.NameScope.Find("PART_FullScreenButton"); - closeButton.PointerPressed += (sender, e) => _hostWindow.Close(); - restoreButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - minimiseButton.PointerPressed += (sender, e) => _hostWindow.WindowState = WindowState.Minimized; - fullScreenButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; + closeButton.PointerReleased += (sender, e) => _hostWindow.Close(); + restoreButton.PointerReleased += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + minimiseButton.PointerReleased += (sender, e) => _hostWindow.WindowState = WindowState.Minimized; + fullScreenButton.PointerReleased += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; } } } From 28fdb43ae0fc0710122637f7fb20f23fd74aa9b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 17:25:30 -0300 Subject: [PATCH 070/109] windowimpl tells window then managed chrome is required. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 ++ src/Avalonia.Controls/Window.cs | 36 ++++++++----------- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 ++- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 15315063fe..55fe8c328b 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -104,6 +104,8 @@ namespace Avalonia.Platform void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); Action ExtendClientAreaToDecorationsChanged { get; set; } + + bool NeedsManagedDecorations { get; } Thickness ExtendedMargins { get; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3bbe0ce4e1..d1ad33e9e8 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -211,8 +211,6 @@ namespace Avalonia.Controls { w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); } - - w.HandleChromeHintsChanged((ExtendClientAreaChromeHints)e.NewValue); }); ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler( @@ -543,6 +541,20 @@ namespace Avalonia.Controls WindowDecorationMargins = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; + + if(PlatformImpl.NeedsManagedDecorations) + { + if (_managedTitleBar == null) + { + _managedTitleBar = new TitleBar(this); + _managedTitleBar.Attach(); + } + } + else + { + _managedTitleBar?.Detach(); + _managedTitleBar = null; + } } /// @@ -907,26 +919,6 @@ namespace Avalonia.Controls base.HandleResized(clientSize); } - private void HandleChromeHintsChanged (ExtendClientAreaChromeHints hints) - { - if(hints.HasFlag(ExtendClientAreaChromeHints.ManagedChromeButtons)) - { - if(_managedTitleBar == null) - { - _managedTitleBar = new TitleBar(this); - } - - _managedTitleBar.Attach(); - } - else - { - if(_managedTitleBar != null) - { - _managedTitleBar.Detach(); - } - } - } - /// /// Raises the event. /// diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index b3c8387950..250b0cf029 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1057,7 +1057,9 @@ namespace Avalonia.Win32 public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; - public Action ExtendClientAreaToDecorationsChanged { get; set; } + public Action ExtendClientAreaToDecorationsChanged { get; set; } + + public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons); public Thickness ExtendedMargins => _extendedMargins; From 42cb926e92269d15c8b49260a3af36bd0571f502 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 17:38:40 -0300 Subject: [PATCH 071/109] indicate if managed decorations are needed via platform. --- src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs | 2 ++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 ++ src/Avalonia.Native/WindowImpl.cs | 2 ++ src/Avalonia.X11/X11Window.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index c03ccf902a..dce24df9d9 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -88,6 +88,8 @@ namespace Avalonia.DesignerSupport.Remote public Thickness OffScreenMargin { get; } = new Thickness(); + public bool NeedsManagedDecorations => false; + public void Activate() { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index cb0a9cd524..84c52d6fbf 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -172,6 +172,8 @@ namespace Avalonia.DesignerSupport.Remote public WindowTransparencyLevel TransparencyLevel { get; private set; } public bool IsClientAreaExtendedToDecorations { get; } + + public bool NeedsManagedDecorations => false; } class ClipboardStub : IClipboard diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 898cf2dea8..ce312b30d2 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -181,6 +181,8 @@ namespace Avalonia.Native ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended); } + public bool NeedsManagedDecorations => false; + public void ShowTaskbarIcon(bool value) { // NO OP On OSX diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 24392f61d6..56ac7c833e 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1127,5 +1127,7 @@ namespace Avalonia.X11 } public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel; + + public bool NeedsManagedDecorations => false; } } From 2cc40e3c6ab6958116d66d97f6bf8a3aab3c4a87 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 17:39:00 -0300 Subject: [PATCH 072/109] add a prefer managed chrome to demo page --- .../Pages/WindowCustomizationsPage.xaml | 1 + .../ViewModels/MainWindowViewModel.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index cea8e4b94e..e3fdbeaa69 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -9,6 +9,7 @@ + None diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 27ea1ffb15..2dab49cf0a 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.ViewModels WindowState.FullScreen, }; - this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x=>x.ManagedChromeButtonsEnabled, x => x.SystemTitleBarEnabled) + this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x=>x.ManagedChromeButtonsEnabled, x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeButtonsEnabled) .Subscribe(x => { var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; @@ -85,6 +85,11 @@ namespace ControlCatalog.ViewModels hints |= ExtendClientAreaChromeHints.SystemTitleBar; } + if(x.Item4) + { + hints |= ExtendClientAreaChromeHints.PreferSystemChromeButtons; + } + ChromeHints = hints; }); @@ -142,6 +147,13 @@ namespace ControlCatalog.ViewModels set { this.RaiseAndSetIfChanged(ref _managedChromeButtonsEnabled, value); } } + private bool _preferSystemChromeButtonsEnabled; + + public bool PreferSystemChromeButtonsEnabled + { + get { return _preferSystemChromeButtonsEnabled; } + set { this.RaiseAndSetIfChanged(ref _preferSystemChromeButtonsEnabled, value); } + } private double _titleBarHeight; From 122821317f14cc968b78c61b342f36cf762b0c9e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 17:44:34 -0300 Subject: [PATCH 073/109] remove unused hint flags. --- .../Pages/WindowCustomizationsPage.xaml | 6 +- .../ViewModels/MainWindowViewModel.cs | 71 +++++-------------- .../Platform/ExtendClientAreaChromeHints.cs | 11 +-- 3 files changed, 22 insertions(+), 66 deletions(-) diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index e3fdbeaa69..db2a8a465a 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -6,10 +6,8 @@ x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> - - - - + + None diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 2dab49cf0a..e8dd12269b 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -16,6 +16,12 @@ namespace ControlCatalog.ViewModels private bool _isMenuItemChecked = true; private WindowState _windowState; private WindowState[] _windowStates; + private int _transparencyLevel; + private ExtendClientAreaChromeHints _chromeHints; + private bool _extendClientAreaEnabled; + private bool _systemTitleBarEnabled; + private bool _preferSystemChromeEnabled; + private double _titleBarHeight; public MainWindowViewModel(IManagedNotificationManager notificationManager) { @@ -65,27 +71,17 @@ namespace ControlCatalog.ViewModels WindowState.FullScreen, }; - this.WhenAnyValue(x => x.SystemChromeButtonsEnabled, x=>x.ManagedChromeButtonsEnabled, x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeButtonsEnabled) + this.WhenAnyValue(x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeEnabled) .Subscribe(x => { var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; if(x.Item1) - { - hints |= ExtendClientAreaChromeHints.SystemChromeButtons; - } - - if(x.Item2) - { - hints |= ExtendClientAreaChromeHints.ManagedChromeButtons; - } - - if(x.Item3) { hints |= ExtendClientAreaChromeHints.SystemTitleBar; } - if(x.Item4) + if(x.Item2) { hints |= ExtendClientAreaChromeHints.PreferSystemChromeButtons; } @@ -93,69 +89,39 @@ namespace ControlCatalog.ViewModels ChromeHints = hints; }); - SystemTitleBarEnabled = true; - SystemChromeButtonsEnabled = true; + SystemTitleBarEnabled = true; TitleBarHeight = -1; - } - - private int _transparencyLevel; + } public int TransparencyLevel { get { return _transparencyLevel; } set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); } - } - - private ExtendClientAreaChromeHints _chromeHints; + } public ExtendClientAreaChromeHints ChromeHints { get { return _chromeHints; } set { this.RaiseAndSetIfChanged(ref _chromeHints, value); } - } - - - private bool _extendClientAreaEnabled; + } public bool ExtendClientAreaEnabled { get { return _extendClientAreaEnabled; } set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); } - } - - private bool _systemTitleBarEnabled; + } public bool SystemTitleBarEnabled { get { return _systemTitleBarEnabled; } set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); } - } - - private bool _systemChromeButtonsEnabled; + } - public bool SystemChromeButtonsEnabled + public bool PreferSystemChromeEnabled { - get { return _systemChromeButtonsEnabled; } - set { this.RaiseAndSetIfChanged(ref _systemChromeButtonsEnabled, value); } - } - - private bool _managedChromeButtonsEnabled; - - public bool ManagedChromeButtonsEnabled - { - get { return _managedChromeButtonsEnabled; } - set { this.RaiseAndSetIfChanged(ref _managedChromeButtonsEnabled, value); } - } - - private bool _preferSystemChromeButtonsEnabled; - - public bool PreferSystemChromeButtonsEnabled - { - get { return _preferSystemChromeButtonsEnabled; } - set { this.RaiseAndSetIfChanged(ref _preferSystemChromeButtonsEnabled, value); } - } - - private double _titleBarHeight; + get { return _preferSystemChromeEnabled; } + set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); } + } public double TitleBarHeight { @@ -163,7 +129,6 @@ namespace ControlCatalog.ViewModels set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); } } - public WindowState WindowState { get { return _windowState; } diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index 022b609699..fb6a4056db 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -8,14 +8,7 @@ namespace Avalonia.Platform NoChrome, Default = SystemTitleBar, SystemTitleBar = 0x01, - ManagedChromeButtons = 0x02, - SystemChromeButtons = 0x04, - - OSXThickTitleBar = 0x08, - - PreferSystemChromeButtons = 0x10, - - AdaptiveChromeWithTitleBar = SystemTitleBar | PreferSystemChromeButtons, - AdaptiveChromeWithoutTitleBar = PreferSystemChromeButtons, + PreferSystemChromeButtons = 0x02, + OSXThickTitleBar = 0x08, } } From ce6beb8851aa0cdb1c1601a69b56a277cfcfe5f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 17:59:06 -0300 Subject: [PATCH 074/109] add some documentation. --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 30 +++++++++++++++++++ src/Avalonia.Native/WindowImpl.cs | 13 ++++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 22 ++++++++------ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 55fe8c328b..43647dc7d0 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -95,20 +95,50 @@ namespace Avalonia.Platform /// void SetMinMaxSize(Size minSize, Size maxSize); + /// + /// Sets if the ClientArea is extended into the non-client area. + /// + /// true to enable, false to disable void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint); + /// + /// Gets a value to indicate if the platform was able to extend client area to non-client area. + /// bool IsClientAreaExtendedToDecorations { get; } + /// + /// Sets hints that configure how the client area extends. + /// + /// void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints); + /// + /// Sets how big the non-client titlebar area should be. + /// + /// -1 for platform default, otherwise the height in DIPs. void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); + /// + /// Gets or Sets an action that is called whenever one of the extend client area properties changed. + /// Action ExtendClientAreaToDecorationsChanged { get; set; } + /// + /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required. + /// This property is used when is set. + /// bool NeedsManagedDecorations { get; } + /// + /// Gets a thickness that describes the amount each side of the non-client area extends into the client area. + /// It includes the titlebar. + /// Thickness ExtendedMargins { get; } + /// + /// Gets a thickness that describes the margin around the window that is offscreen. + /// This may happen when a window is maximized and is set. + /// Thickness OffScreenMargin { get; } } } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index ce312b30d2..ef2bd32bfc 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -137,7 +137,7 @@ namespace Avalonia.Native return false; } - + private void InvalidateExtendedMargins () { if(WindowState == WindowState.FullScreen) @@ -152,6 +152,7 @@ namespace Avalonia.Native ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended); } + /// public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) { _isExtended = extendIntoClientAreaHint; @@ -161,16 +162,13 @@ namespace Avalonia.Native InvalidateExtendedMargins(); } + /// public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) - { - if(hints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons)) - { - hints |= ExtendClientAreaChromeHints.SystemChromeButtons; - } - + { _native.SetExtendClientAreaHints ((AvnExtendClientAreaChromeHints)hints); } + /// public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) { _extendTitleBarHeight = titleBarHeight; @@ -181,6 +179,7 @@ namespace Avalonia.Native ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended); } + /// public bool NeedsManagedDecorations => false; public void ShowTaskbarIcon(bool value) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 250b0cf029..6025f96d22 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -45,7 +45,8 @@ namespace Avalonia.Win32 private bool _isClientAreaExtended; private Thickness _extendedMargins; private Thickness _offScreenMargin; - + private double _extendTitleBarHint = -1; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -73,8 +74,8 @@ namespace Avalonia.Win32 private Size _minSize; private Size _maxSize; private POINT _maxTrackSize; - private WindowImpl _parent; - private bool _extendClientAreaToDecorationsHint; + private WindowImpl _parent; + private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; public WindowImpl() { @@ -1036,9 +1037,7 @@ namespace Avalonia.Win32 _isClientAreaExtended = hint; ExtendClientArea(); - } - - private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; + } public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { @@ -1046,8 +1045,8 @@ namespace Avalonia.Win32 ExtendClientArea(); } - - private double _extendTitleBarHint = -1; + + /// public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) { _extendTitleBarHint = titleBarHeight; @@ -1055,14 +1054,19 @@ namespace Avalonia.Win32 ExtendClientArea(); } + /// public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; + /// public Action ExtendClientAreaToDecorationsChanged { get; set; } - + + /// public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons); + /// public Thickness ExtendedMargins => _extendedMargins; + /// public Thickness OffScreenMargin => _offScreenMargin; private struct SavedWindowInfo From aa4dfc54953855395fb8ed132c5542329c1f1c4f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 18:17:13 -0300 Subject: [PATCH 075/109] rename flags and fix win32 implementation. --- .../ViewModels/MainWindowViewModel.cs | 4 +- .../Platform/ExtendClientAreaChromeHints.cs | 6 +-- src/Windows/Avalonia.Win32/WindowImpl.cs | 50 ++++++++----------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index e8dd12269b..4356a032fa 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -78,12 +78,12 @@ namespace ControlCatalog.ViewModels if(x.Item1) { - hints |= ExtendClientAreaChromeHints.SystemTitleBar; + hints |= ExtendClientAreaChromeHints.SystemChrome; } if(x.Item2) { - hints |= ExtendClientAreaChromeHints.PreferSystemChromeButtons; + hints |= ExtendClientAreaChromeHints.PreferSystemChrome; } ChromeHints = hints; diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index fb6a4056db..deb9e417ab 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -6,9 +6,9 @@ namespace Avalonia.Platform public enum ExtendClientAreaChromeHints { NoChrome, - Default = SystemTitleBar, - SystemTitleBar = 0x01, - PreferSystemChromeButtons = 0x02, + Default = SystemChrome, + SystemChrome = 0x01, + PreferSystemChrome = 0x02, OSXThickTitleBar = 0x08, } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6025f96d22..4b64eabc26 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -694,7 +694,7 @@ namespace Avalonia.Win32 borderCaptionThickness.left *= -1; borderCaptionThickness.top *= -1; - bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) || _extendTitleBarHint == -1; + bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1; if (!wantsTitleBar) { @@ -711,7 +711,7 @@ namespace Avalonia.Win32 borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling); } - margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemTitleBar) ? borderCaptionThickness.top : 1; + margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1; if (WindowState == WindowState.Maximized) { @@ -727,7 +727,7 @@ namespace Avalonia.Win32 return margins; } - private void ExtendClientArea () + private void ExtendClientArea() { if (DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled) { @@ -735,36 +735,28 @@ namespace Avalonia.Win32 return; } - if (!_isClientAreaExtended || WindowState == WindowState.FullScreen) + GetWindowRect(_hwnd, out var rcClient); + + // Inform the application of the frame change. + SetWindowPos(_hwnd, + IntPtr.Zero, + rcClient.left, rcClient.top, + rcClient.Width, rcClient.Height, + SetWindowPosFlags.SWP_FRAMECHANGED); + + if (_isClientAreaExtended && WindowState != WindowState.FullScreen) { - _extendedMargins = new Thickness(0, 0, 0, 0); - _offScreenMargin = new Thickness(); + var margins = UpdateExtendMargins(); + + DwmExtendFrameIntoClientArea(_hwnd, ref margins); } else { - GetWindowRect(_hwnd, out var rcClient); - - // Inform the application of the frame change. - SetWindowPos(_hwnd, - IntPtr.Zero, - rcClient.left, rcClient.top, - rcClient.Width, rcClient.Height, - SetWindowPosFlags.SWP_FRAMECHANGED); - - if (_isClientAreaExtended) - { - var margins = UpdateExtendMargins(); + var margins = new MARGINS(); + DwmExtendFrameIntoClientArea(_hwnd, ref margins); - DwmExtendFrameIntoClientArea(_hwnd, ref margins); - } - else - { - var margins = new MARGINS(); - DwmExtendFrameIntoClientArea(_hwnd, ref margins); - - _offScreenMargin = new Thickness(); - _extendedMargins = new Thickness(); - } + _offScreenMargin = new Thickness(); + _extendedMargins = new Thickness(); } ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); @@ -1061,7 +1053,7 @@ namespace Avalonia.Win32 public Action ExtendClientAreaToDecorationsChanged { get; set; } /// - public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChromeButtons); + public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome); /// public Thickness ExtendedMargins => _extendedMargins; From 466f015836721450bc8337c751f4130f5509ac46 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 18:26:02 -0300 Subject: [PATCH 076/109] fix up osx implementation. --- native/Avalonia.Native/inc/avalonia-native.h | 11 +++++------ native/Avalonia.Native/src/OSX/window.mm | 14 ++++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 8b905ea8d2..97ab4ffefb 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -207,12 +207,11 @@ enum AvnMenuItemToggleType enum AvnExtendClientAreaChromeHints { - AvnChromeHintsNoChrome, - AvnChromeHintsSystemTitleBar = 0x01, - AvnChromeHintsManagedChromeButtons = 0x02, - AvnChromeHintsSystemChromeButtons = 0x04, - AvnChromeHintsOSXThickTitleBar = 0x08, - AvnChromeHintsDefault = AvnChromeHintsSystemTitleBar | AvnChromeHintsSystemChromeButtons, + AvnNoChrome = 0, + AvnSystemChrome = 0x01, + AvnPreferSystemChrome = 0x02, + AvnOSXThickTitleBar = 0x08, + AvnDefaultChrome = AvnSystemChrome, }; AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 265f500bae..551c5afea9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -488,7 +488,7 @@ private: WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) { _isClientAreaExtended = false; - _extendClientHints = AvnChromeHintsDefault; + _extendClientHints = AvnDefaultChrome; _fullScreenActive = false; _canResize = true; _decorations = SystemDecorationsFull; @@ -511,7 +511,7 @@ private: { if(_isClientAreaExtended) { - [button setHidden: !(_extendClientHints & AvnChromeHintsSystemChromeButtons)]; + [button setHidden: !((_extendClientHints & AvnSystemChrome)) || (_extendClientHints & AvnPreferSystemChrome)]; } else { @@ -599,7 +599,7 @@ private: if(_lastWindowState == FullScreen) { // we exited fs. - if(_extendClientHints & AvnChromeHintsOSXThickTitleBar) + if(_extendClientHints & AvnOSXThickTitleBar) { Window.toolbar = [NSToolbar new]; Window.toolbar.showsBaselineSeparator = false; @@ -612,7 +612,7 @@ private: else if(state == FullScreen) { // we entered fs. - if(_extendClientHints & AvnChromeHintsOSXThickTitleBar) + if(_extendClientHints & AvnOSXThickTitleBar) { Window.toolbar = nullptr; } @@ -815,7 +815,9 @@ private: [Window setTitlebarAppearsTransparent:true]; - if(_extendClientHints & AvnChromeHintsSystemTitleBar) + auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + + if (wantsTitleBar) { [StandardContainer ShowTitleBar:true]; } @@ -824,7 +826,7 @@ private: [StandardContainer ShowTitleBar:false]; } - if(_extendClientHints & AvnChromeHintsOSXThickTitleBar) + if(_extendClientHints & AvnOSXThickTitleBar) { Window.toolbar = [NSToolbar new]; Window.toolbar.showsBaselineSeparator = false; From c8cede080f174514f89357f0bc05db3bf8e328ff Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 Jun 2020 18:32:08 -0300 Subject: [PATCH 077/109] fix hiding and showing traffic lights. --- native/Avalonia.Native/src/OSX/window.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 551c5afea9..269d71a25a 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -511,7 +511,9 @@ private: { if(_isClientAreaExtended) { - [button setHidden: !((_extendClientHints & AvnSystemChrome)) || (_extendClientHints & AvnPreferSystemChrome)]; + auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + + [button setHidden: !wantsChrome]; } else { From 6ce1d94a090b76d29aec46f2b0eab09e16b66404 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 28 Jun 2020 12:59:53 +0200 Subject: [PATCH 078/109] Remove a few warnings. --- src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs | 7 +------ src/Avalonia.Styling/IStyledElement.cs | 5 ----- src/Avalonia.Styling/StyledElement.cs | 1 - src/Avalonia.Styling/Styling/Styles.cs | 1 - 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs index 6ed6c2ef52..4d82381323 100644 --- a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs @@ -1,7 +1,4 @@ -using System; -using Avalonia.Data; - -#nullable enable +#nullable enable namespace Avalonia.PropertyStore { @@ -10,8 +7,6 @@ namespace Avalonia.PropertyStore /// internal interface IPriorityValueEntry : IValue { - BindingPriority Priority { get; } - void Reparent(IValueSink sink); } diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs index 610c743a3e..37e6ed6fbb 100644 --- a/src/Avalonia.Styling/IStyledElement.cs +++ b/src/Avalonia.Styling/IStyledElement.cs @@ -17,11 +17,6 @@ namespace Avalonia /// event EventHandler Initialized; - /// - /// Raised when resources on the element are changed. - /// - event EventHandler ResourcesChanged; - /// /// Gets a value that indicates whether the element has finished initialization. /// diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 05e031c9ec..65885ddebe 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -67,7 +67,6 @@ namespace Avalonia private List? _appliedStyles; private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; - private bool _notifyingResourcesChanged; /// /// Initializes static members of the class. diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 7c79060930..c752bdfeb8 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -21,7 +21,6 @@ namespace Avalonia.Styling private IResourceHost? _owner; private IResourceDictionary? _resources; private Dictionary?>? _cache; - private bool _notifyingResourcesChanged; public Styles() { From 128fbee51fc77b2b931be1cded41fd723921dc40 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 28 Jun 2020 13:51:34 +0200 Subject: [PATCH 079/109] Remove nullable and cref warnings. --- src/Avalonia.Base/AvaloniaProperty.cs | 4 +-- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 4 +-- .../Collections/Pooled/IReadOnlyPooledList.cs | 2 ++ .../Collections/Pooled/PooledList.cs | 3 +- src/Avalonia.Base/Data/BindingValue.cs | 36 +++++++++---------- src/Avalonia.Base/Data/Optional.cs | 10 +++--- src/Avalonia.Base/DirectPropertyBase.cs | 2 +- src/Avalonia.Base/IStyledPropertyMetadata.cs | 2 -- .../PropertyStore/LocalValueEntry.cs | 4 +-- src/Avalonia.Base/StyledPropertyMetadata`1.cs | 3 +- src/Avalonia.Base/ValueStore.cs | 2 +- 11 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index daa7191cc5..09480f2701 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -159,8 +159,6 @@ namespace Avalonia /// internal int Id { get; } - internal bool HasChangedSubscriptions => _changed?.HasObservers ?? false; - /// /// Provides access to a property's binding via the /// indexer. @@ -512,7 +510,7 @@ namespace Avalonia /// /// An if setting the property can be undone, otherwise null. /// - internal abstract IDisposable? RouteSetValue( + internal abstract IDisposable RouteSetValue( IAvaloniaObject o, object value, BindingPriority priority); diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 4a3b104f2a..4cde965400 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -362,7 +362,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void Register(Type type, AvaloniaProperty property) @@ -413,7 +413,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void RegisterAttached(Type type, AvaloniaProperty property) diff --git a/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs b/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs index 9bc3609dc5..7a233a62ab 100644 --- a/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs @@ -13,9 +13,11 @@ namespace Avalonia.Collections.Pooled public interface IReadOnlyPooledList : IReadOnlyList { +#pragma warning disable CS0419 /// /// Gets a for the items currently in the collection. /// +#pragma warning restore CS0419 ReadOnlySpan Span { get; } } } diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs index f0d6b292cc..e50e100d32 100644 --- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs @@ -138,7 +138,6 @@ namespace Avalonia.Collections.Pooled /// initially empty, but will have room for the given number of elements /// before any reallocations are required. /// - /// If true, Count of list equals capacity. Depending on ClearMode, rented items may or may not hold dirty values. public PooledList(int capacity, ClearMode clearMode, ArrayPool customPool, bool sizeToCapacity) { if (capacity < 0) @@ -499,11 +498,13 @@ namespace Avalonia.Collections.Pooled public void AddRange(T[] array) => AddRange(array.AsSpan()); +#pragma warning disable CS0419 /// /// Adds the elements of the given to the end of this list. If /// required, the capacity of the list is increased to twice the previous /// capacity or the new size, whichever is larger. /// +#pragma warning restore CS0419 public void AddRange(ReadOnlySpan span) { var newSpan = InsertSpan(_size, span.Length, false); diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 9aac1bacba..56f37ff89c 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -190,7 +190,7 @@ namespace Avalonia.Data /// Gets the value of the binding value if present, otherwise the default value. /// /// The value. - public T GetValueOrDefault() => HasValue ? _value : default; + public T GetValueOrDefault() => HasValue ? _value : default!; /// /// Gets the value of the binding value if present, otherwise a default value. @@ -209,8 +209,8 @@ namespace Avalonia.Data public TResult GetValueOrDefault() { return HasValue ? - _value is TResult result ? result : default - : default; + _value is TResult result ? result : default! + : default!; } /// @@ -225,7 +225,7 @@ namespace Avalonia.Data public TResult GetValueOrDefault(TResult defaultValue) { return HasValue ? - _value is TResult result ? result : default + _value is TResult result ? result : default! : defaultValue; } @@ -242,7 +242,7 @@ namespace Avalonia.Data UnsetValueType _ => Unset, DoNothingType _ => DoNothing, BindingNotification n => n.ToBindingValue().Cast(), - _ => (T)value + _ => (T)value! }; } @@ -259,18 +259,18 @@ namespace Avalonia.Data public static implicit operator BindingValue(Optional optional) { - return optional.HasValue ? optional.Value : Unset; + return optional.HasValue ? optional.Value! : Unset; } /// /// Returns a binding value with a type of . /// - public static BindingValue Unset => new BindingValue(BindingValueType.UnsetValue, default, null); + public static BindingValue Unset => new BindingValue(BindingValueType.UnsetValue, default!, null); /// /// Returns a binding value with a type of . /// - public static BindingValue DoNothing => new BindingValue(BindingValueType.DoNothing, default, null); + public static BindingValue DoNothing => new BindingValue(BindingValueType.DoNothing, default!, null); /// /// Returns a binding value with a type of . @@ -278,9 +278,9 @@ namespace Avalonia.Data /// The binding error. public static BindingValue BindingError(Exception e) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); - return new BindingValue(BindingValueType.BindingError, default, e); + return new BindingValue(BindingValueType.BindingError, default!, e); } /// @@ -290,7 +290,7 @@ namespace Avalonia.Data /// The fallback value. public static BindingValue BindingError(Exception e, T fallbackValue) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); return new BindingValue(BindingValueType.BindingErrorWithFallback, fallbackValue, e); } @@ -303,13 +303,13 @@ namespace Avalonia.Data /// The fallback value. public static BindingValue BindingError(Exception e, Optional fallbackValue) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); return new BindingValue( fallbackValue.HasValue ? BindingValueType.BindingErrorWithFallback : BindingValueType.BindingError, - fallbackValue.HasValue ? fallbackValue.Value : default, + fallbackValue.HasValue ? fallbackValue.Value : default!, e); } @@ -319,9 +319,9 @@ namespace Avalonia.Data /// The data validation error. public static BindingValue DataValidationError(Exception e) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); - return new BindingValue(BindingValueType.DataValidationError, default, e); + return new BindingValue(BindingValueType.DataValidationError, default!, e); } /// @@ -331,7 +331,7 @@ namespace Avalonia.Data /// The fallback value. public static BindingValue DataValidationError(Exception e, T fallbackValue) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); return new BindingValue(BindingValueType.DataValidationErrorWithFallback, fallbackValue, e); } @@ -344,13 +344,13 @@ namespace Avalonia.Data /// The fallback value. public static BindingValue DataValidationError(Exception e, Optional fallbackValue) { - e = e ?? throw new ArgumentNullException("e"); + e = e ?? throw new ArgumentNullException(nameof(e)); return new BindingValue( fallbackValue.HasValue ? BindingValueType.DataValidationErrorWithFallback : BindingValueType.DataValidationError, - fallbackValue.HasValue ? fallbackValue.Value : default, + fallbackValue.HasValue ? fallbackValue.Value : default!, e); } diff --git a/src/Avalonia.Base/Data/Optional.cs b/src/Avalonia.Base/Data/Optional.cs index dd952c895c..cc064e0faa 100644 --- a/src/Avalonia.Base/Data/Optional.cs +++ b/src/Avalonia.Base/Data/Optional.cs @@ -60,7 +60,7 @@ namespace Avalonia.Data /// Casts the value (if any) to an . /// /// The cast optional value. - public Optional ToObject() => HasValue ? new Optional(_value) : default; + public Optional ToObject() => HasValue ? new Optional(_value!) : default; /// public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)"; @@ -69,7 +69,7 @@ namespace Avalonia.Data /// Gets the value if present, otherwise the default value. /// /// The value. - public T GetValueOrDefault() => HasValue ? _value : default; + public T GetValueOrDefault() => HasValue ? _value : default!; /// /// Gets the value if present, otherwise a default value. @@ -88,8 +88,8 @@ namespace Avalonia.Data public TResult GetValueOrDefault() { return HasValue ? - _value is TResult result ? result : default - : default; + _value is TResult result ? result : default! + : default!; } /// @@ -104,7 +104,7 @@ namespace Avalonia.Data public TResult GetValueOrDefault(TResult defaultValue) { return HasValue ? - _value is TResult result ? result : default + _value is TResult result ? result : default! : defaultValue; } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 0e65379abd..b59e86f8d5 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -122,7 +122,7 @@ namespace Avalonia internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) { - return o.GetValue(this); + return o.GetValue(this)!; } /// diff --git a/src/Avalonia.Base/IStyledPropertyMetadata.cs b/src/Avalonia.Base/IStyledPropertyMetadata.cs index f567cd930c..a68b65e5e0 100644 --- a/src/Avalonia.Base/IStyledPropertyMetadata.cs +++ b/src/Avalonia.Base/IStyledPropertyMetadata.cs @@ -1,5 +1,3 @@ -using System; - namespace Avalonia { /// diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs index 59c017bc09..5d3930469d 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs @@ -15,11 +15,11 @@ namespace Avalonia.PropertyStore public LocalValueEntry(T value) => _value = value; public BindingPriority Priority => BindingPriority.LocalValue; - Optional IValue.GetValue() => new Optional(_value); + Optional IValue.GetValue() => new Optional(_value!); public Optional GetValue(BindingPriority maxPriority) { - return BindingPriority.LocalValue >= maxPriority ? _value : Optional.Empty; + return BindingPriority.LocalValue >= maxPriority ? _value! : Optional.Empty; } public void SetValue(T value) => _value = value; diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs index 300548db0a..cf0a0c34ec 100644 --- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs +++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Avalonia.Data; namespace Avalonia @@ -35,7 +34,7 @@ namespace Avalonia /// /// Gets the value coercion callback, if any. /// - public Func? CoerceValue { get; private set; } + public Func CoerceValue { get; private set; } object IStyledPropertyMetadata.DefaultValue => DefaultValue; diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 05e66f2e0a..0e87a1f242 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -162,7 +162,7 @@ namespace Avalonia _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( _owner, property, - old, + old!, default, BindingPriority.Unset)); } From ed43a5753a3a844c5daa2c459163b1cf7e3a5016 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 09:19:13 +0200 Subject: [PATCH 080/109] Handle BindingNotifications in LogicalNotNode. Fixes handling binding notifications in `LogicalNotNote`. Previously if a `!` was placed in a binding that produced a `BindingNotification`, then `LogicalNotNode` tried to convert the `BindingNotification` to `bool` and failed, throwing an exception. Handle this properly. --- src/Avalonia.Base/Data/Core/LogicalNotNode.cs | 36 ++++++--- ...ExpressionObserverBuilderTests_Negation.cs | 78 ++++++++++++++++++- 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs index 7c402f42f6..6d09befeba 100644 --- a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs +++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs @@ -12,8 +12,19 @@ namespace Avalonia.Data.Core base.NextValueChanged(Negate(value)); } - private static object Negate(object v) + private static object Negate(object value) { + var notification = value as BindingNotification; + var v = BindingNotification.ExtractValue(value); + + BindingNotification GenerateError(Exception e) + { + notification ??= new BindingNotification(AvaloniaProperty.UnsetValue); + notification.AddError(e, BindingErrorType.Error); + notification.ClearValue(); + return notification; + } + if (v != AvaloniaProperty.UnsetValue) { var s = v as string; @@ -28,9 +39,7 @@ namespace Avalonia.Data.Core } else { - return new BindingNotification( - new InvalidCastException($"Unable to convert '{s}' to bool."), - BindingErrorType.Error); + return GenerateError(new InvalidCastException($"Unable to convert '{s}' to bool.")); } } else @@ -38,24 +47,31 @@ namespace Avalonia.Data.Core try { var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture); - return !boolean; + + if (notification is object) + { + notification.SetValue(!boolean); + return notification; + } + else + { + return !boolean; + } } catch (InvalidCastException) { // The error message here is "Unable to cast object of type 'System.Object' // to type 'System.IConvertible'" which is kinda useless so provide our own. - return new BindingNotification( - new InvalidCastException($"Unable to convert '{v}' to bool."), - BindingErrorType.Error); + return GenerateError(new InvalidCastException($"Unable to convert '{v}' to bool.")); } catch (Exception e) { - return new BindingNotification(e, BindingErrorType.Error); + return GenerateError(e); } } } - return AvaloniaProperty.UnsetValue; + return notification ?? AvaloniaProperty.UnsetValue; } public object Transform(object value) diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs index 757cd628b7..0be3bbbb9f 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.ComponentModel; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Data; @@ -89,6 +91,69 @@ namespace Avalonia.Markup.UnitTests.Parsers GC.KeepAlive(data); } + [Fact] + public async Task Should_Negate_BindingNotification_Value() + { + var data = new { Foo = true }; + var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true); + var result = await target.Take(1); + + Assert.Equal(new BindingNotification(false), result); + + GC.KeepAlive(data); + } + + [Fact] + public async Task Should_Pass_Through_BindingNotification_Error() + { + var data = new { }; + var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true); + var result = await target.Take(1); + + Assert.Equal( + new BindingNotification( + new MissingMemberException("Could not find a matching property accessor for 'Foo' on '{ }'"), + BindingErrorType.Error), + result); + + GC.KeepAlive(data); + } + + [Fact] + public async Task Should_Negate_BindingNotification_Error_FallbackValue() + { + var data = new Test { DataValidationError = "Test error" }; + var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true); + var result = await target.Take(1); + + Assert.Equal( + new BindingNotification( + new DataValidationException("Test error"), + BindingErrorType.DataValidationError, + true), + result); + + GC.KeepAlive(data); + } + + [Fact] + public async Task Should_Add_Error_To_BindingNotification_For_FallbackValue_Not_Convertible_To_Boolean() + { + var data = new Test { Bar = new object(), DataValidationError = "Test error" }; + var target = ExpressionObserverBuilder.Build(data, "!Bar", enableDataValidation: true); + var result = await target.Take(1); + + Assert.Equal( + new BindingNotification( + new AggregateException( + new DataValidationException("Test error"), + new InvalidCastException($"Unable to convert 'System.Object' to bool.")), + BindingErrorType.Error), + result); + + GC.KeepAlive(data); + } + [Fact] public void SetValue_Should_Return_False_For_Invalid_Value() { @@ -101,9 +166,20 @@ namespace Avalonia.Markup.UnitTests.Parsers GC.KeepAlive(data); } - private class Test + private class Test : INotifyDataErrorInfo { public bool Foo { get; set; } + public object Bar { get; set; } + + public string DataValidationError { get; set; } + public bool HasErrors => !string.IsNullOrWhiteSpace(DataValidationError); + + public event EventHandler ErrorsChanged; + + public IEnumerable GetErrors(string propertyName) + { + return DataValidationError is object ? new[] { DataValidationError } : null; + } } } } From 8d2af508081b9dfe27b5051e7a20280d925fd7a2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 10:05:49 +0200 Subject: [PATCH 081/109] Add failing tests for #3552. --- .../Xaml/StyleTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 9642f5719d..184bc3bae9 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -382,5 +382,59 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Border.WidthProperty, border.Transitions[0].Property); } } + + [Fact] + public void Style_Can_Use_Class_Selector_With_Dash() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var foo = window.FindControl("foo"); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color); + } + } + + [Fact] + public void Style_Can_Use_Pseudolass_Selector_With_Dash() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var foo = window.FindControl("foo"); + + Assert.Null(foo.Background); + + ((IPseudoClasses)foo.Classes).Add(":foo-bar"); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color); + } + } } } From 15e0507afe1015f410984a06726fc6ec3031b37d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 10:06:31 +0200 Subject: [PATCH 082/109] Allow dash in style class identifiers. --- .../Utilities/StyleClassParser.cs | 45 +++++++++++++++++++ .../Markup/Parsers/SelectorGrammar.cs | 4 +- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/StyleClassParser.cs diff --git a/src/Avalonia.Base/Utilities/StyleClassParser.cs b/src/Avalonia.Base/Utilities/StyleClassParser.cs new file mode 100644 index 0000000000..2db58f73d9 --- /dev/null +++ b/src/Avalonia.Base/Utilities/StyleClassParser.cs @@ -0,0 +1,45 @@ +using System; +using System.Globalization; + +namespace Avalonia.Utilities +{ +#if !BUILDTASK + public +#endif + static class StyleClassParser + { + public static ReadOnlySpan ParseStyleClass(this ref CharacterReader r) + { + if (IsValidIdentifierStart(r.Peek)) + { + return r.TakeWhile(c => IsValidIdentifierChar(c)); + } + else + { + return ReadOnlySpan.Empty; + } + } + + private static bool IsValidIdentifierStart(char c) + { + return char.IsLetter(c) || c == '_'; + } + + private static bool IsValidIdentifierChar(char c) + { + if (IsValidIdentifierStart(c) || c == '-') + { + return true; + } + else + { + var cat = CharUnicodeInfo.GetUnicodeCategory(c); + return cat == UnicodeCategory.NonSpacingMark || + cat == UnicodeCategory.SpacingCombiningMark || + cat == UnicodeCategory.ConnectorPunctuation || + cat == UnicodeCategory.Format || + cat == UnicodeCategory.DecimalDigitNumber; + } + } + } +} diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 5e0f43e066..b25e9490cd 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -154,7 +154,7 @@ namespace Avalonia.Markup.Parsers private static (State, ISyntax) ParseColon(ref CharacterReader r) { - var identifier = r.ParseIdentifier(); + var identifier = r.ParseStyleClass(); if (identifier.IsEmpty) { @@ -214,7 +214,7 @@ namespace Avalonia.Markup.Parsers private static (State, ISyntax) ParseClass(ref CharacterReader r) { - var @class = r.ParseIdentifier(); + var @class = r.ParseStyleClass(); if (@class.IsEmpty) { throw new ExpressionParseException(r.Position, $"Expected a class name after '.'."); From 717905bded5e9d690600cf5f5b85e272004a9f05 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 13:34:53 +0200 Subject: [PATCH 083/109] Added StyleClassParser to Avalonia.Build.Tasks. --- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 582e4499c5..5b2484382e 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -42,7 +42,10 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + From 5ed9f0f42038e5f1e786afb12ce811b20c2c2682 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jul 2020 15:09:06 -0300 Subject: [PATCH 084/109] fix some nits --- src/Avalonia.Controls/Window.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 14b2568222..3b36cad649 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -72,8 +72,9 @@ namespace Avalonia.Controls { private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); private TitleBar _managedTitleBar; - private bool _isExtendedIntoWindowDecorations; + private Thickness _windowDecorationMargins; + private Thickness _offScreenMargin; /// /// Defines the property. @@ -318,17 +319,13 @@ namespace Avalonia.Controls { get => _isExtendedIntoWindowDecorations; private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value); - } - - private Thickness _windowDecorationMargins; + } public Thickness WindowDecorationMargins { get => _windowDecorationMargins; private set => SetAndRaise(WindowDecorationMarginsProperty, ref _windowDecorationMargins, value); - } - - private Thickness _offScreenMargin; + } public Thickness OffScreenMargin { @@ -542,7 +539,7 @@ namespace Avalonia.Controls OffScreenMargin = PlatformImpl.OffScreenMargin; - if(PlatformImpl.NeedsManagedDecorations) + if (PlatformImpl.NeedsManagedDecorations) { if (_managedTitleBar == null) { From 9f64463060bb6a6214b48faffcda5f86f88de0e4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jul 2020 15:17:10 -0300 Subject: [PATCH 085/109] fix nits. --- .../Chrome/CaptionButtons.cs | 10 ++-- src/Avalonia.Controls/Chrome/TitleBar.cs | 32 +++++----- src/Avalonia.Controls/Platform/IWindowImpl.cs | 60 +++++++++---------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 58f5b09c0d..cc49b8b643 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls.Chrome { var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - layer.Children.Remove(this); + layer?.Children.Remove(this); _disposables.Dispose(); _disposables = null; @@ -49,10 +49,10 @@ namespace Avalonia.Controls.Chrome { base.OnApplyTemplate(e); - var closeButton = e.NameScope.Find("PART_CloseButton"); - var restoreButton = e.NameScope.Find("PART_RestoreButton"); - var minimiseButton = e.NameScope.Find("PART_MinimiseButton"); - var fullScreenButton = e.NameScope.Find("PART_FullScreenButton"); + var closeButton = e.NameScope.Get("PART_CloseButton"); + var restoreButton = e.NameScope.Get("PART_RestoreButton"); + var minimiseButton = e.NameScope.Get("PART_MinimiseButton"); + var fullScreenButton = e.NameScope.Get("PART_FullScreenButton"); closeButton.PointerReleased += (sender, e) => _hostWindow.Close(); restoreButton.PointerReleased += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 8e8010d3d4..70ab172cc6 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -26,36 +26,36 @@ namespace Avalonia.Controls.Chrome { var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - layer.Children.Add(this); + layer?.Children.Add(this); _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) - .Subscribe(x => InvalidateSize()), + .Subscribe(x => UpdateSize()), _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => InvalidateSize()), + .Subscribe(x => UpdateSize()), _hostWindow.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => InvalidateSize()), + .Subscribe(x => UpdateSize()), _hostWindow.GetObservable(Window.WindowStateProperty) - .Subscribe(x => - { - PseudoClasses.Set(":minimized", x == WindowState.Minimized); - PseudoClasses.Set(":normal", x == WindowState.Normal); - PseudoClasses.Set(":maximized", x == WindowState.Maximized); - PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }) + .Subscribe(x => + { + PseudoClasses.Set(":minimized", x == WindowState.Minimized); + PseudoClasses.Set(":normal", x == WindowState.Normal); + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }) }; _captionButtons?.Attach(_hostWindow); - InvalidateSize(); + UpdateSize(); } } - void InvalidateSize() + void UpdateSize() { Margin = new Thickness( _hostWindow.OffScreenMargin.Left, @@ -80,7 +80,7 @@ namespace Avalonia.Controls.Chrome { var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - layer.Children.Remove(this); + layer?.Children.Remove(this); _disposables.Dispose(); _disposables = null; @@ -93,11 +93,11 @@ namespace Avalonia.Controls.Chrome { base.OnApplyTemplate(e); - _captionButtons = e.NameScope.Find("PART_CaptionButtons"); + _captionButtons = e.NameScope.Get("PART_CaptionButtons"); _captionButtons.Attach(_hostWindow); - InvalidateSize(); + UpdateSize(); } } } diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 43647dc7d0..8a1554d344 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -68,6 +68,34 @@ namespace Avalonia.Platform /// Func Closing { get; set; } + /// + /// Gets a value to indicate if the platform was able to extend client area to non-client area. + /// + bool IsClientAreaExtendedToDecorations { get; } + + /// + /// Gets or Sets an action that is called whenever one of the extend client area properties changed. + /// + Action ExtendClientAreaToDecorationsChanged { get; set; } + + /// + /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required. + /// This property is used when is set. + /// + bool NeedsManagedDecorations { get; } + + /// + /// Gets a thickness that describes the amount each side of the non-client area extends into the client area. + /// It includes the titlebar. + /// + Thickness ExtendedMargins { get; } + + /// + /// Gets a thickness that describes the margin around the window that is offscreen. + /// This may happen when a window is maximized and is set. + /// + Thickness OffScreenMargin { get; } + /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. /// @@ -99,12 +127,7 @@ namespace Avalonia.Platform /// Sets if the ClientArea is extended into the non-client area. /// /// true to enable, false to disable - void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint); - - /// - /// Gets a value to indicate if the platform was able to extend client area to non-client area. - /// - bool IsClientAreaExtendedToDecorations { get; } + void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint); /// /// Sets hints that configure how the client area extends. @@ -116,29 +139,6 @@ namespace Avalonia.Platform /// Sets how big the non-client titlebar area should be. /// /// -1 for platform default, otherwise the height in DIPs. - void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); - - /// - /// Gets or Sets an action that is called whenever one of the extend client area properties changed. - /// - Action ExtendClientAreaToDecorationsChanged { get; set; } - - /// - /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required. - /// This property is used when is set. - /// - bool NeedsManagedDecorations { get; } - - /// - /// Gets a thickness that describes the amount each side of the non-client area extends into the client area. - /// It includes the titlebar. - /// - Thickness ExtendedMargins { get; } - - /// - /// Gets a thickness that describes the margin around the window that is offscreen. - /// This may happen when a window is maximized and is set. - /// - Thickness OffScreenMargin { get; } + void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); } } From d75ca95648c0893954805c763165ef949ea6eece Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jul 2020 15:27:13 -0300 Subject: [PATCH 086/109] add nullable and null checks on titlebar. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 76 +++++++++++++----------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 70ab172cc6..5e7bd8bebe 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -4,11 +4,13 @@ using Avalonia.Controls.Primitives; namespace Avalonia.Controls.Chrome { +#nullable enable + public class TitleBar : TemplatedControl { - private CompositeDisposable _disposables; - private Window _hostWindow; - private CaptionButtons _captionButtons; + private CompositeDisposable? _disposables; + private Window? _hostWindow; + private CaptionButtons? _captionButtons; public TitleBar(Window hostWindow) { @@ -28,28 +30,31 @@ namespace Avalonia.Controls.Chrome layer?.Children.Add(this); - _disposables = new CompositeDisposable + if (_hostWindow != null) { - _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.WindowStateProperty) - .Subscribe(x => - { - PseudoClasses.Set(":minimized", x == WindowState.Minimized); - PseudoClasses.Set(":normal", x == WindowState.Normal); - PseudoClasses.Set(":maximized", x == WindowState.Maximized); - PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }) - }; - - _captionButtons?.Attach(_hostWindow); + _disposables = new CompositeDisposable + { + _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) + .Subscribe(x => UpdateSize()), + + _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) + .Subscribe(x => UpdateSize()), + + _hostWindow.GetObservable(Window.OffScreenMarginProperty) + .Subscribe(x => UpdateSize()), + + _hostWindow.GetObservable(Window.WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":minimized", x == WindowState.Minimized); + PseudoClasses.Set(":normal", x == WindowState.Normal); + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }) + }; + + _captionButtons?.Attach(_hostWindow); + } UpdateSize(); } @@ -57,19 +62,22 @@ namespace Avalonia.Controls.Chrome void UpdateSize() { - Margin = new Thickness( - _hostWindow.OffScreenMargin.Left, - _hostWindow.OffScreenMargin.Top, - _hostWindow.OffScreenMargin.Right, - _hostWindow.OffScreenMargin.Bottom); - - if (_hostWindow.WindowState != WindowState.FullScreen) + if (_hostWindow != null) { - Height = _hostWindow.WindowDecorationMargins.Top; + Margin = new Thickness( + _hostWindow.OffScreenMargin.Left, + _hostWindow.OffScreenMargin.Top, + _hostWindow.OffScreenMargin.Right, + _hostWindow.OffScreenMargin.Bottom); - if (_captionButtons != null) + if (_hostWindow.WindowState != WindowState.FullScreen) { - _captionButtons.Height = Height; + Height = _hostWindow.WindowDecorationMargins.Top; + + if (_captionButtons != null) + { + _captionButtons.Height = Height; + } } } } From 0b9a4fc3ab9f11563e4abf05c5778e8062a65809 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jul 2020 15:33:23 -0300 Subject: [PATCH 087/109] nullable and null checks on titlebar / captionbuttons. --- .../Chrome/CaptionButtons.cs | 38 ++++++++++++++----- src/Avalonia.Controls/Chrome/TitleBar.cs | 5 ++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index cc49b8b643..a66eb853cd 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -1,16 +1,15 @@ using System; -using System.Collections.Generic; using System.Reactive.Disposables; -using System.Text; using Avalonia.Controls.Primitives; -using Avalonia.VisualTree; namespace Avalonia.Controls.Chrome { +#nullable enable + public class CaptionButtons : TemplatedControl { - private CompositeDisposable _disposables; - private Window _hostWindow; + private CompositeDisposable? _disposables; + private Window? _hostWindow; public void Attach(Window hostWindow) { @@ -54,10 +53,31 @@ namespace Avalonia.Controls.Chrome var minimiseButton = e.NameScope.Get("PART_MinimiseButton"); var fullScreenButton = e.NameScope.Get("PART_FullScreenButton"); - closeButton.PointerReleased += (sender, e) => _hostWindow.Close(); - restoreButton.PointerReleased += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - minimiseButton.PointerReleased += (sender, e) => _hostWindow.WindowState = WindowState.Minimized; - fullScreenButton.PointerReleased += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; + closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); + + restoreButton.PointerReleased += (sender, e) => + { + if (_hostWindow != null) + { + _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + } + }; + + minimiseButton.PointerReleased += (sender, e) => + { + if (_hostWindow != null) + { + _hostWindow.WindowState = WindowState.Minimized; + } + }; + + fullScreenButton.PointerReleased += (sender, e) => + { + if (_hostWindow != null) + { + _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; + } + }; } } } diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 5e7bd8bebe..c3b4dbf3ea 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -103,7 +103,10 @@ namespace Avalonia.Controls.Chrome _captionButtons = e.NameScope.Get("PART_CaptionButtons"); - _captionButtons.Attach(_hostWindow); + if (_hostWindow != null) + { + _captionButtons.Attach(_hostWindow); + } UpdateSize(); } From 928189c8420498bc6c310dc27ee9790a0c3d92ab Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jul 2020 15:42:55 -0300 Subject: [PATCH 088/109] nits --- src/Avalonia.Controls/Chrome/CaptionButtons.cs | 7 +++++-- src/Avalonia.Controls/Chrome/TitleBar.cs | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index a66eb853cd..75d6c366b8 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -2,10 +2,13 @@ using System.Reactive.Disposables; using Avalonia.Controls.Primitives; -namespace Avalonia.Controls.Chrome -{ #nullable enable +namespace Avalonia.Controls.Chrome +{ + /// + /// Draws window minimize / maximize / close buttons in a when managed client decorations are enabled. + /// public class CaptionButtons : TemplatedControl { private CompositeDisposable? _disposables; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index c3b4dbf3ea..b044ffc59c 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -2,14 +2,17 @@ using System.Reactive.Disposables; using Avalonia.Controls.Primitives; -namespace Avalonia.Controls.Chrome -{ #nullable enable +namespace Avalonia.Controls.Chrome +{ + /// + /// Draws a titlebar when managed client decorations are enabled. + /// public class TitleBar : TemplatedControl { private CompositeDisposable? _disposables; - private Window? _hostWindow; + private readonly Window? _hostWindow; private CaptionButtons? _captionButtons; public TitleBar(Window hostWindow) @@ -60,7 +63,7 @@ namespace Avalonia.Controls.Chrome } } - void UpdateSize() + private void UpdateSize() { if (_hostWindow != null) { From 6f7dac83d18b2a18a0a7ab9e7cef186f70622ae1 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Wed, 1 Jul 2020 19:53:34 -0400 Subject: [PATCH 089/109] Add focus-visible support with tests --- src/Avalonia.Input/InputElement.cs | 7 +- .../InputElement_Focus.cs | 79 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 407b28b665..0616c70d82 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -158,6 +158,7 @@ namespace Avalonia.Input private bool _isEffectivelyEnabled = true; private bool _isFocused; + private bool _isFocusVisible; private bool _isPointerOver; private GestureRecognizerCollection _gestureRecognizers; @@ -427,7 +428,9 @@ namespace Avalonia.Input /// The event args. protected virtual void OnGotFocus(GotFocusEventArgs e) { - IsFocused = e.Source == this; + var isFocused = e.Source == this; + _isFocusVisible = isFocused && (e.NavigationMethod == NavigationMethod.Directional || e.NavigationMethod == NavigationMethod.Tab); + IsFocused = isFocused; } /// @@ -436,6 +439,7 @@ namespace Avalonia.Input /// The event args. protected virtual void OnLostFocus(RoutedEventArgs e) { + _isFocusVisible = false; IsFocused = false; } @@ -602,6 +606,7 @@ namespace Avalonia.Input if (isFocused.HasValue) { PseudoClasses.Set(":focus", isFocused.Value); + PseudoClasses.Set(":focus-visible", _isFocusVisible); } if (isPointerOver.HasValue) diff --git a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs index d396119555..09fae7207f 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs @@ -42,5 +42,84 @@ namespace Avalonia.Input.UnitTests Assert.Null(FocusManager.Instance.Current); } } + + [Fact] + public void Focus_Pseudoclass_Should_Be_Applied_On_Focus() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + target1, + target2 + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus")); + Assert.False(target2.IsFocused); + Assert.False(target2.Classes.Contains(":focus")); + + FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); + Assert.False(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus")); + Assert.True(target2.IsFocused); + Assert.True(target2.Classes.Contains(":focus")); + } + } + + [Fact] + public void Control_FocusVsisible_Pseudoclass_Should_Be_Applied_On_Tab_And_DirectionalFocus() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + target1, + target2 + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus-visible")); + Assert.False(target2.IsFocused); + Assert.False(target2.Classes.Contains(":focus-visible")); + + FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); + Assert.False(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus-visible")); + Assert.True(target2.IsFocused); + Assert.True(target2.Classes.Contains(":focus-visible")); + + FocusManager.Instance?.Focus(target1, NavigationMethod.Directional); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus-visible")); + Assert.False(target2.IsFocused); + Assert.False(target2.Classes.Contains(":focus-visible")); + } + } } } From de6553a0dc52917c84198f10f420ea903fe884ed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 11:15:56 +0200 Subject: [PATCH 090/109] Added nullable attributes and use them instead of `!`. --- src/Avalonia.Base/Data/BindingValue.cs | 44 +++--- src/Avalonia.Base/Data/Optional.cs | 26 ++-- src/Avalonia.Base/DirectPropertyBase.cs | 4 +- .../Metadata/NullableAttributes.cs | 140 ++++++++++++++++++ .../PropertyStore/LocalValueEntry.cs | 11 +- src/Avalonia.Base/ValueStore.cs | 2 +- 6 files changed, 188 insertions(+), 39 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/NullableAttributes.cs diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 56f37ff89c..6e3c9ae67b 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Utilities; #nullable enable @@ -81,14 +82,14 @@ namespace Avalonia.Data /// public readonly struct BindingValue { - private readonly T _value; + [AllowNull] private readonly T _value; /// /// Initializes a new instance of the struct with a type of /// /// /// The value. - public BindingValue(T value) + public BindingValue([AllowNull] T value) { ValidateValue(value); _value = value; @@ -96,7 +97,7 @@ namespace Avalonia.Data Error = null; } - private BindingValue(BindingValueType type, T value, Exception? error) + private BindingValue(BindingValueType type, [AllowNull] T value, Exception? error) { _value = value; Type = type; @@ -154,7 +155,7 @@ namespace Avalonia.Data BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue, BindingValueType.DoNothing => BindingOperations.DoNothing, BindingValueType.Value => _value, - BindingValueType.BindingError => + BindingValueType.BindingError => new BindingNotification(Error, BindingErrorType.Error), BindingValueType.BindingErrorWithFallback => new BindingNotification(Error, BindingErrorType.Error, Value), @@ -175,7 +176,7 @@ namespace Avalonia.Data /// The binding type is or /// . /// - public BindingValue WithValue(T value) + public BindingValue WithValue([AllowNull] T value) { if (Type == BindingValueType.DoNothing) { @@ -190,7 +191,8 @@ namespace Avalonia.Data /// Gets the value of the binding value if present, otherwise the default value. /// /// The value. - public T GetValueOrDefault() => HasValue ? _value : default!; + [return: MaybeNull] + public T GetValueOrDefault() => HasValue ? _value : default; /// /// Gets the value of the binding value if present, otherwise a default value. @@ -206,11 +208,12 @@ namespace Avalonia.Data /// The value if present and of the correct type, `default(TResult)` if the value is /// not present or of an incorrect type. /// + [return: MaybeNull] public TResult GetValueOrDefault() { return HasValue ? - _value is TResult result ? result : default! - : default!; + _value is TResult result ? result : default + : default; } /// @@ -222,10 +225,11 @@ namespace Avalonia.Data /// present but not of the correct type or null, or if the /// value is not present. /// - public TResult GetValueOrDefault(TResult defaultValue) + [return: MaybeNull] + public TResult GetValueOrDefault([AllowNull] TResult defaultValue) { return HasValue ? - _value is TResult result ? result : default! + _value is TResult result ? result : default : defaultValue; } @@ -242,7 +246,7 @@ namespace Avalonia.Data UnsetValueType _ => Unset, DoNothingType _ => DoNothing, BindingNotification n => n.ToBindingValue().Cast(), - _ => (T)value! + _ => new BindingValue((T)value) }; } @@ -250,7 +254,7 @@ namespace Avalonia.Data /// Creates a binding value from an instance of the underlying value type. /// /// The value. - public static implicit operator BindingValue(T value) => new BindingValue(value); + public static implicit operator BindingValue([AllowNull] T value) => new BindingValue(value); /// /// Creates a binding value from an . @@ -259,18 +263,18 @@ namespace Avalonia.Data public static implicit operator BindingValue(Optional optional) { - return optional.HasValue ? optional.Value! : Unset; + return optional.HasValue ? optional.Value : Unset; } /// /// Returns a binding value with a type of . /// - public static BindingValue Unset => new BindingValue(BindingValueType.UnsetValue, default!, null); + public static BindingValue Unset => new BindingValue(BindingValueType.UnsetValue, default, null); /// /// Returns a binding value with a type of . /// - public static BindingValue DoNothing => new BindingValue(BindingValueType.DoNothing, default!, null); + public static BindingValue DoNothing => new BindingValue(BindingValueType.DoNothing, default, null); /// /// Returns a binding value with a type of . @@ -280,7 +284,7 @@ namespace Avalonia.Data { e = e ?? throw new ArgumentNullException(nameof(e)); - return new BindingValue(BindingValueType.BindingError, default!, e); + return new BindingValue(BindingValueType.BindingError, default, e); } /// @@ -309,7 +313,7 @@ namespace Avalonia.Data fallbackValue.HasValue ? BindingValueType.BindingErrorWithFallback : BindingValueType.BindingError, - fallbackValue.HasValue ? fallbackValue.Value : default!, + fallbackValue.HasValue ? fallbackValue.Value : default, e); } @@ -321,7 +325,7 @@ namespace Avalonia.Data { e = e ?? throw new ArgumentNullException(nameof(e)); - return new BindingValue(BindingValueType.DataValidationError, default!, e); + return new BindingValue(BindingValueType.DataValidationError, default, e); } /// @@ -350,11 +354,11 @@ namespace Avalonia.Data fallbackValue.HasValue ? BindingValueType.DataValidationErrorWithFallback : BindingValueType.DataValidationError, - fallbackValue.HasValue ? fallbackValue.Value : default!, + fallbackValue.HasValue ? fallbackValue.Value : default, e); } - private static void ValidateValue(T value) + private static void ValidateValue([AllowNull] T value) { if (value is UnsetValueType) { diff --git a/src/Avalonia.Base/Data/Optional.cs b/src/Avalonia.Base/Data/Optional.cs index cc064e0faa..8e044d7896 100644 --- a/src/Avalonia.Base/Data/Optional.cs +++ b/src/Avalonia.Base/Data/Optional.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; #nullable enable @@ -22,13 +23,13 @@ namespace Avalonia.Data /// public readonly struct Optional : IEquatable> { - private readonly T _value; + [AllowNull] private readonly T _value; /// /// Initializes a new instance of the struct with value. /// /// The value. - public Optional(T value) + public Optional([AllowNull] T value) { _value = value; HasValue = true; @@ -48,7 +49,7 @@ namespace Avalonia.Data public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value."); /// - public override bool Equals(object obj) => obj is Optional o && this == o; + public override bool Equals(object? obj) => obj is Optional o && this == o; /// public bool Equals(Optional other) => this == other; @@ -60,7 +61,7 @@ namespace Avalonia.Data /// Casts the value (if any) to an . /// /// The cast optional value. - public Optional ToObject() => HasValue ? new Optional(_value!) : default; + public Optional ToObject() => HasValue ? new Optional(_value) : default; /// public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)"; @@ -69,7 +70,8 @@ namespace Avalonia.Data /// Gets the value if present, otherwise the default value. /// /// The value. - public T GetValueOrDefault() => HasValue ? _value : default!; + [return: MaybeNull] + public T GetValueOrDefault() => HasValue ? _value : default; /// /// Gets the value if present, otherwise a default value. @@ -85,11 +87,12 @@ namespace Avalonia.Data /// The value if present and of the correct type, `default(TResult)` if the value is /// not present or of an incorrect type. /// + [return: MaybeNull] public TResult GetValueOrDefault() { return HasValue ? - _value is TResult result ? result : default! - : default!; + _value is TResult result ? result : default + : default; } /// @@ -101,10 +104,11 @@ namespace Avalonia.Data /// present but not of the correct type or null, or if the /// value is not present. /// - public TResult GetValueOrDefault(TResult defaultValue) + [return: MaybeNull] + public TResult GetValueOrDefault([AllowNull] TResult defaultValue) { return HasValue ? - _value is TResult result ? result : default! + _value is TResult result ? result : default : defaultValue; } @@ -112,7 +116,7 @@ namespace Avalonia.Data /// Creates an from an instance of the underlying value type. /// /// The value. - public static implicit operator Optional(T value) => new Optional(value); + public static implicit operator Optional([AllowNull] T value) => new Optional(value); /// /// Compares two s for inequality. @@ -128,7 +132,7 @@ namespace Avalonia.Data /// The first value. /// The second value. /// True if the values are equal; otherwise false. - public static bool operator==(Optional x, Optional y) + public static bool operator ==(Optional x, Optional y) { if (!x.HasValue && !y.HasValue) { diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index b59e86f8d5..d42c030245 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -120,9 +120,9 @@ namespace Avalonia return o.GetValue(this); } - internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) + internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) { - return o.GetValue(this)!; + return o.GetValue(this); } /// diff --git a/src/Avalonia.Base/Metadata/NullableAttributes.cs b/src/Avalonia.Base/Metadata/NullableAttributes.cs new file mode 100644 index 0000000000..91f5e81863 --- /dev/null +++ b/src/Avalonia.Base/Metadata/NullableAttributes.cs @@ -0,0 +1,140 @@ +#pragma warning disable MA0048 // File name must match type name +#define INTERNAL_NULLABLE_ATTRIBUTES +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + +// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +} +#endif diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs index 5d3930469d..859e9ba81c 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs @@ -1,4 +1,5 @@ -using Avalonia.Data; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; #nullable enable @@ -11,15 +12,15 @@ namespace Avalonia.PropertyStore /// The property type. internal class LocalValueEntry : IValue { - private T _value; + [AllowNull] private T _value; - public LocalValueEntry(T value) => _value = value; + public LocalValueEntry([AllowNull] T value) => _value = value; public BindingPriority Priority => BindingPriority.LocalValue; - Optional IValue.GetValue() => new Optional(_value!); + Optional IValue.GetValue() => new Optional(_value); public Optional GetValue(BindingPriority maxPriority) { - return BindingPriority.LocalValue >= maxPriority ? _value! : Optional.Empty; + return BindingPriority.LocalValue >= maxPriority ? _value : Optional.Empty; } public void SetValue(T value) => _value = value; diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 0e87a1f242..6b89fcbdb9 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -162,7 +162,7 @@ namespace Avalonia _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( _owner, property, - old!, + new Optional(old), default, BindingPriority.Unset)); } From a5f43c7ee1b07d852f275f61500bf3dd7862a7ec Mon Sep 17 00:00:00 2001 From: Mihai Stan Date: Thu, 2 Jul 2020 14:49:20 +0200 Subject: [PATCH 091/109] Add ticks property on Slider This property will allow setting custom ticks on the slider without having to create a custom template for the TickBar. --- samples/ControlCatalog/Pages/SliderPage.xaml | 20 ++++++++++++++----- .../ControlCatalog/Pages/SliderPage.xaml.cs | 11 ++++++++++ src/Avalonia.Controls/Slider.cs | 19 +++++++++++++++++- src/Avalonia.Themes.Default/Slider.xaml | 9 ++++++--- src/Avalonia.Themes.Fluent/Slider.xaml | 1 + 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index c6f5521e60..ca289e21fd 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -6,11 +6,21 @@ A control that lets the user select from a range of values by moving a Thumb control along a Track. - + + + + ("CustomTickedSlider"); + slider.Ticks = new List + { + 0d, + 5d, + 20d, + 50d, + 100d + }; } } } diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index fe1a4f5ac1..00a26a4c91 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -64,6 +65,12 @@ namespace Avalonia.Controls public static readonly StyledProperty TickPlacementProperty = AvaloniaProperty.Register(nameof(TickPlacement), 0d); + /// + /// Defines the property. + /// + public static readonly StyledProperty> TicksProperty = + TickBar.TicksProperty.AddOwner(); + // Slider required parts private bool _isDragging = false; private Track _track; @@ -83,7 +90,8 @@ namespace Avalonia.Controls PressedMixin.Attach(); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); - Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); + Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), + RoutingStrategies.Bubble); } /// @@ -94,6 +102,15 @@ namespace Avalonia.Controls UpdatePseudoClasses(Orientation); } + /// + /// Defines the ticks to be drawn on the tick bar. + /// + public List Ticks + { + get => GetValue(TicksProperty); + set => SetValue(TicksProperty, value); + } + /// /// Gets or sets the orientation of a . /// diff --git a/src/Avalonia.Themes.Default/Slider.xaml b/src/Avalonia.Themes.Default/Slider.xaml index 1d48a946fc..ba4b1ee998 100644 --- a/src/Avalonia.Themes.Default/Slider.xaml +++ b/src/Avalonia.Themes.Default/Slider.xaml @@ -87,7 +87,10 @@ - + + diff --git a/src/Avalonia.Themes.Fluent/Slider.xaml b/src/Avalonia.Themes.Fluent/Slider.xaml index 539c448e0f..099c2000b8 100644 --- a/src/Avalonia.Themes.Fluent/Slider.xaml +++ b/src/Avalonia.Themes.Fluent/Slider.xaml @@ -182,6 +182,7 @@ From 6561cb739ad0ea3ae5292e3e17d84e75a01e0e94 Mon Sep 17 00:00:00 2001 From: Mihai Stan Date: Thu, 2 Jul 2020 14:50:17 +0200 Subject: [PATCH 092/109] Snap thumb on custom ticks when they are set --- src/Avalonia.Controls/Slider.cs | 47 +++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 00a26a4c91..b94f9bb7c9 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -257,19 +257,50 @@ namespace Avalonia.Controls /// Value that want to snap to closest Tick. private double SnapToTick(double value) { - var previous = Minimum; - var next = Maximum; - - if (TickFrequency > 0.0) + if (IsSnapToTickEnabled) { - previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency); - next = Math.Min(Maximum, previous + TickFrequency); + double previous = Minimum; + double next = Maximum; + + // This property is rarely set so let's try to avoid the GetValue + List ticks = Ticks; + + // If ticks collection is available, use it. + // Note that ticks may be unsorted. + if ((ticks != null) && (ticks.Count > 0)) + { + for (int i = 0; i < ticks.Count; i++) + { + double tick = ticks[i]; + if (MathUtilities.AreClose(tick, value)) + { + return value; + } + + if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous)) + { + previous = tick; + } + else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next)) + { + next = tick; + } + } + } + else if (MathUtilities.GreaterThan(TickFrequency, 0.0)) + { + previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency); + next = Math.Min(Maximum, previous + TickFrequency); + } + + // Choose the closest value between previous and next. If tie, snap to 'next'. + value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous; } - // Choose the closest value between previous and next. If tie, snap to 'next'. - return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous; + return value; } + private void UpdatePseudoClasses(Orientation o) { PseudoClasses.Set(":vertical", o == Orientation.Vertical); From f1e4a6a50aa4d1bb3007da1b388256f4443241da Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 16:30:56 +0200 Subject: [PATCH 093/109] Register a font manager. `DatePickerTests` are proving to be flaky, register the services that are reported missing when they fail. --- tests/Avalonia.Controls.UnitTests/DatePickerTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index 7c95f02a7a..7bcb120850 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs @@ -203,7 +203,9 @@ namespace Avalonia.Controls.UnitTests } private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + fontManagerImpl: new MockFontManagerImpl(), + standardCursorFactory: Mock.Of(), + textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() { From d8d9a1865dc4abe7466a43f14edbab7262222f7a Mon Sep 17 00:00:00 2001 From: Mihai Stan Date: Thu, 2 Jul 2020 16:34:28 +0200 Subject: [PATCH 094/109] Use AvaloniaList instead of List --- samples/ControlCatalog/Pages/SliderPage.xaml | 1 + samples/ControlCatalog/Pages/SliderPage.xaml.cs | 11 ----------- src/Avalonia.Controls/Slider.cs | 8 ++++---- src/Avalonia.Controls/TickBar.cs | 12 ++++-------- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index ca289e21fd..ea31ed0050 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -19,6 +19,7 @@ Maximum="100" TickPlacement="BottomRight" IsSnapToTickEnabled="True" + Ticks="0,20,25,40,75,100" Width="300" /> ("CustomTickedSlider"); - slider.Ticks = new List - { - 0d, - 5d, - 20d, - 50d, - 100d - }; } } } diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index b94f9bb7c9..293cbac82f 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using Avalonia.Collections; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -68,7 +68,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty> TicksProperty = + public static readonly StyledProperty> TicksProperty = TickBar.TicksProperty.AddOwner(); // Slider required parts @@ -105,7 +105,7 @@ namespace Avalonia.Controls /// /// Defines the ticks to be drawn on the tick bar. /// - public List Ticks + public AvaloniaList Ticks { get => GetValue(TicksProperty); set => SetValue(TicksProperty, value); @@ -263,7 +263,7 @@ namespace Avalonia.Controls double next = Maximum; // This property is rarely set so let's try to avoid the GetValue - List ticks = Ticks; + var ticks = Ticks; // If ticks collection is available, use it. // Note that ticks may be unsorted. diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 16e063beb3..22145d8742 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls.Primitives; -using Avalonia.Data; -using Avalonia.Data.Converters; +using Avalonia.Collections; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Utilities; @@ -135,15 +131,15 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty> TicksProperty = - AvaloniaProperty.Register>(nameof(Ticks)); + public static readonly StyledProperty> TicksProperty = + AvaloniaProperty.Register>(nameof(Ticks)); /// /// The Ticks property contains collection of value of type Double which /// are the logical positions use to draw the ticks. /// The property value is a . /// - public List Ticks + public AvaloniaList Ticks { get { return GetValue(TicksProperty); } set { SetValue(TicksProperty, value); } From ccd5a9272531385558712fff7304a1910b1b57c6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 2 Jul 2020 12:33:45 -0300 Subject: [PATCH 095/109] apply fix to timepicker tests also. --- tests/Avalonia.Controls.UnitTests/TimePickerTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index c803b93364..682f0eaadb 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests { public class TimePickerTests { - [Fact(Skip = "FIX ME ASAP")] + [Fact] public void SelectedTimeChanged_Should_Fire_When_SelectedTime_Set() { using (UnitTestApplication.Start(Services)) @@ -98,9 +98,10 @@ namespace Avalonia.Controls.UnitTests } } - private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + fontManagerImpl: new MockFontManagerImpl(), + standardCursorFactory: Mock.Of(), + textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() { From a8aff643f009af65c295c6f788b6897a5dbe6ea2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 2 Jul 2020 12:44:03 -0300 Subject: [PATCH 096/109] fix more nits. --- samples/ControlCatalog/MainWindow.xaml | 2 +- samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml | 2 +- src/Avalonia.Controls/Window.cs | 4 ---- src/Avalonia.Native/WindowImpl.cs | 4 ++-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.Themes.Default/CaptionButtons.xaml | 3 +-- src/Avalonia.Themes.Fluent/CaptionButtons.xaml | 3 +-- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 +- 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 157df05030..121878423a 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -84,7 +84,7 @@ - + diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index db2a8a465a..b90f43c3b6 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -6,7 +6,7 @@ x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> - + diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3b36cad649..5ef26f1b27 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -104,7 +104,6 @@ namespace Avalonia.Controls public static readonly StyledProperty ExtendClientAreaTitleBarHeightHintProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaTitleBarHeightHint), -1); - /// /// Defines the property. /// @@ -124,7 +123,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(OffScreenMargin), o => o.OffScreenMargin); - /// /// Defines the property. /// @@ -534,9 +532,7 @@ namespace Avalonia.Controls protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended) { IsExtendedIntoWindowDecorations = isExtended; - WindowDecorationMargins = PlatformImpl.ExtendedMargins; - OffScreenMargin = PlatformImpl.OffScreenMargin; if (PlatformImpl.NeedsManagedDecorations) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index ef2bd32bfc..885591495b 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -138,9 +138,9 @@ namespace Avalonia.Native return false; } - private void InvalidateExtendedMargins () + private void InvalidateExtendedMargins() { - if(WindowState == WindowState.FullScreen) + if (WindowState == WindowState.FullScreen) { ExtendedMargins = new Thickness(); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index aac4b1a5dd..f19f9e97aa 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -266,7 +266,7 @@ namespace Avalonia.Native return args.Handled; } - protected virtual bool ChromeHitTest (RawPointerEventArgs e) + protected virtual bool ChromeHitTest(RawPointerEventArgs e) { return false; } diff --git a/src/Avalonia.Themes.Default/CaptionButtons.xaml b/src/Avalonia.Themes.Default/CaptionButtons.xaml index 282448bdd5..68249905a1 100644 --- a/src/Avalonia.Themes.Default/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Default/CaptionButtons.xaml @@ -1,7 +1,6 @@ + diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index b044ffc59c..f48ca4e4e8 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -14,28 +14,32 @@ namespace Avalonia.Controls.Chrome private CompositeDisposable? _disposables; private readonly Window? _hostWindow; private CaptionButtons? _captionButtons; + private Panel _underlay; - public TitleBar(Window hostWindow) + public TitleBar(Window hostWindow) : this() { _hostWindow = hostWindow; } public TitleBar() { - + _underlay = new Panel + { + IsHitTestVisible = false, + [~Panel.BackgroundProperty] = this[~BackgroundProperty], + VerticalAlignment = Layout.VerticalAlignment.Top + }; } public void Attach() { - if (_disposables == null) + if (_disposables == null && _hostWindow != null) { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + var overlay = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - layer?.Children.Add(this); + overlay?.Children.Add(this); - if (_hostWindow != null) - { - _disposables = new CompositeDisposable + _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) .Subscribe(x => UpdateSize()), @@ -56,11 +60,15 @@ namespace Avalonia.Controls.Chrome }) }; - _captionButtons?.Attach(_hostWindow); - } + _captionButtons?.Attach(_hostWindow); + + var underlay = ChromeUnderlayLayer.GetUnderlayLayer(_hostWindow); + + underlay?.Children.Add(_underlay); UpdateSize(); } + } private void UpdateSize() @@ -76,6 +84,7 @@ namespace Avalonia.Controls.Chrome if (_hostWindow.WindowState != WindowState.FullScreen) { Height = _hostWindow.WindowDecorationMargins.Top; + _underlay.Height = Height; if (_captionButtons != null) { @@ -89,9 +98,14 @@ namespace Avalonia.Controls.Chrome { if (_disposables != null) { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + if (_hostWindow != null) + { + var overlay = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + overlay?.Children.Remove(this); - layer?.Children.Remove(this); + var underlay = ChromeUnderlayLayer.GetUnderlayLayer(_hostWindow); + underlay?.Children.Remove(_underlay); + } _disposables.Dispose(); _disposables = null; diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs index ba0fdfd535..2313222bd3 100644 --- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -6,6 +6,27 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { + public class ChromeUnderlayLayer : Panel, ICustomSimpleHitTest + { + public static ChromeUnderlayLayer? GetUnderlayLayer(IVisual visual) + { + foreach (var v in visual.GetVisualAncestors()) + if (v is VisualLayerManager vlm) + if (vlm.OverlayLayer != null) + return vlm.ChromeUnderlayLayer; + + if (visual is TopLevel tl) + { + var layers = tl.GetVisualDescendants().OfType().FirstOrDefault(); + return layers?.ChromeUnderlayLayer; + } + + return null; + } + + public bool HitTest(Point point) => Children.HitTestCustom(point); + } + public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest { public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 3084d7fa72..0213c53c57 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -8,6 +8,7 @@ namespace Avalonia.Controls.Primitives private const int AdornerZIndex = int.MaxValue - 100; private const int ChromeZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 98; + private const int UnderlayZIndex = int.MinValue; private ILogicalRoot _logicalRoot; private readonly List _layers = new List(); @@ -26,6 +27,17 @@ namespace Avalonia.Controls.Primitives } } + public ChromeUnderlayLayer ChromeUnderlayLayer + { + get + { + var rv = FindLayer(); + if (rv == null) + AddLayer(rv = new ChromeUnderlayLayer(), UnderlayZIndex); + return rv; + } + } + public ChromeOverlayLayer ChromeOverlayLayer { get diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 45798d3fa1..72998583b8 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -14,7 +14,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Window.xaml index aee15347eb..086b05bb01 100644 --- a/src/Avalonia.Themes.Fluent/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Window.xaml @@ -1,11 +1,11 @@ - diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index f48ca4e4e8..b044ffc59c 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -14,32 +14,28 @@ namespace Avalonia.Controls.Chrome private CompositeDisposable? _disposables; private readonly Window? _hostWindow; private CaptionButtons? _captionButtons; - private Panel _underlay; - public TitleBar(Window hostWindow) : this() + public TitleBar(Window hostWindow) { _hostWindow = hostWindow; } public TitleBar() { - _underlay = new Panel - { - IsHitTestVisible = false, - [~Panel.BackgroundProperty] = this[~BackgroundProperty], - VerticalAlignment = Layout.VerticalAlignment.Top - }; + } public void Attach() { - if (_disposables == null && _hostWindow != null) + if (_disposables == null) { - var overlay = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - overlay?.Children.Add(this); + layer?.Children.Add(this); - _disposables = new CompositeDisposable + if (_hostWindow != null) + { + _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) .Subscribe(x => UpdateSize()), @@ -60,15 +56,11 @@ namespace Avalonia.Controls.Chrome }) }; - _captionButtons?.Attach(_hostWindow); - - var underlay = ChromeUnderlayLayer.GetUnderlayLayer(_hostWindow); - - underlay?.Children.Add(_underlay); + _captionButtons?.Attach(_hostWindow); + } UpdateSize(); } - } private void UpdateSize() @@ -84,7 +76,6 @@ namespace Avalonia.Controls.Chrome if (_hostWindow.WindowState != WindowState.FullScreen) { Height = _hostWindow.WindowDecorationMargins.Top; - _underlay.Height = Height; if (_captionButtons != null) { @@ -98,14 +89,9 @@ namespace Avalonia.Controls.Chrome { if (_disposables != null) { - if (_hostWindow != null) - { - var overlay = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - overlay?.Children.Remove(this); + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - var underlay = ChromeUnderlayLayer.GetUnderlayLayer(_hostWindow); - underlay?.Children.Remove(_underlay); - } + layer?.Children.Remove(this); _disposables.Dispose(); _disposables = null; diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs index 2313222bd3..ba0fdfd535 100644 --- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -6,27 +6,6 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { - public class ChromeUnderlayLayer : Panel, ICustomSimpleHitTest - { - public static ChromeUnderlayLayer? GetUnderlayLayer(IVisual visual) - { - foreach (var v in visual.GetVisualAncestors()) - if (v is VisualLayerManager vlm) - if (vlm.OverlayLayer != null) - return vlm.ChromeUnderlayLayer; - - if (visual is TopLevel tl) - { - var layers = tl.GetVisualDescendants().OfType().FirstOrDefault(); - return layers?.ChromeUnderlayLayer; - } - - return null; - } - - public bool HitTest(Point point) => Children.HitTestCustom(point); - } - public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest { public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 0213c53c57..3084d7fa72 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -8,7 +8,6 @@ namespace Avalonia.Controls.Primitives private const int AdornerZIndex = int.MaxValue - 100; private const int ChromeZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 98; - private const int UnderlayZIndex = int.MinValue; private ILogicalRoot _logicalRoot; private readonly List _layers = new List(); @@ -27,17 +26,6 @@ namespace Avalonia.Controls.Primitives } } - public ChromeUnderlayLayer ChromeUnderlayLayer - { - get - { - var rv = FindLayer(); - if (rv == null) - AddLayer(rv = new ChromeUnderlayLayer(), UnderlayZIndex); - return rv; - } - } - public ChromeOverlayLayer ChromeOverlayLayer { get diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 72998583b8..45798d3fa1 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -14,7 +14,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Window.xaml index 086b05bb01..aee15347eb 100644 --- a/src/Avalonia.Themes.Fluent/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Window.xaml @@ -1,11 +1,11 @@