From 9feb2376a0b2e11ca3d1ffa4eeade4fa8034f161 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Tue, 15 Jun 2021 20:37:48 +0100 Subject: [PATCH] Squash toplevel-totalsize --- native/Avalonia.Native/src/OSX/window.mm | 15 +++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 ++ src/Avalonia.Controls/ApiCompatBaseline.txt | 4 ++- .../Offscreen/OffscreenTopLevelImpl.cs | 10 ++++++ .../Platform/ITopLevelImpl.cs | 5 +++ src/Avalonia.Controls/TopLevel.cs | 19 +++++++++++- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + src/Avalonia.Headless/HeadlessWindowImpl.cs | 1 + src/Avalonia.Native/WindowImplBase.cs | 14 +++++++++ src/Avalonia.Native/avn.idl | 1 + src/Avalonia.X11/X11Structs.cs | 10 ++++++ src/Avalonia.X11/X11Window.cs | 31 +++++++++++++++++++ .../FramebufferToplevelImpl.cs | 1 + .../Wpf/WpfTopLevelImpl.cs | 1 + .../Interop/UnmanagedMethods.cs | 23 ++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++++++ src/iOS/Avalonia.iOS/AvaloniaView.cs | 1 + .../MockWindowingPlatform.cs | 1 + 18 files changed, 148 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 870345e543..6bd09fe08b 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -202,6 +202,21 @@ public: } } + virtual HRESULT GetTotalSize(AvnSize* ret) override + { + @autoreleasepool + { + if(ret == nullptr) + return E_POINTER; + + auto frame = [Window frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + + return S_OK; + } + } + virtual HRESULT GetScaling (double* ret) override { @autoreleasepool diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 60b772a183..8b732a92da 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual Size ClientSize => Size.ToSize(RenderScaling); + public Size TotalSize => ClientSize; + public IMouseDevice MouseDevice { get; } = new MouseDevice(); public Action Closed { get; set; } diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a79b3b4d7b..166a005e4d 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -11,4 +11,6 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 12 +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize.get()' is present in the implementation but not in the contract. +Total Issues: 14 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index ca0e9d48b8..cb0c5d94e5 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,6 +35,16 @@ namespace Avalonia.Controls.Embedding.Offscreen } } + public Size TotalSize + { + get { return _clientSize; } + set + { + _clientSize = value; + Resized?.Invoke(value); + } + } + public double RenderScaling { get { return _scaling; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 09f38042a1..546184f8a6 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -22,6 +22,11 @@ namespace Avalonia.Platform /// Size ClientSize { get; } + /// + /// Gets the total size of the toplevel, excluding shadows. + /// + Size TotalSize { get; } + /// /// Gets the scaling factor for the toplevel. This is used for rendering. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..e2d9e7e697 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -42,6 +42,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ClientSizeProperty = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); + /// + /// Defines the property. + /// + public static readonly DirectProperty TotalSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(TotalSize), o => o.TotalSize); + /// /// Defines the property. /// @@ -74,6 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; + private Size _totalSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -84,6 +91,7 @@ namespace Avalonia.Controls static TopLevel() { AffectsMeasure(ClientSizeProperty); + AffectsMeasure(TotalSizeProperty); TransparencyLevelHintProperty.Changed.AddClassHandler( (tl, e) => @@ -194,9 +202,18 @@ namespace Avalonia.Controls public Size ClientSize { get { return _clientSize; } - protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } + protected set + { + SetAndRaise(ClientSizeProperty, ref _clientSize, value); + SetAndRaise(TotalSizeProperty, ref _totalSize, PlatformImpl.TotalSize); + } } + /// + /// Gets or sets the total size of the window. + /// + public Size TotalSize => _totalSize; + /// /// Gets or sets the that the TopLevel should use when possible. /// diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..91bc93d897 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,6 +21,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } + public Size TotalSize => ClientSize; public double RenderScaling { get; } = 1.0; public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..74f8c95ade 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,6 +41,7 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } + public Size TotalSize => ClientSize; public double RenderScaling { get; } = 1; public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f716464d14..18efd1a519 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,6 +104,20 @@ namespace Avalonia.Native } } + public Size TotalSize + { + get + { + if (_native != null) + { + var s = _native.TotalSize; + return new Size(s.Width, s.Height); + } + + return default; + } + } + public IEnumerable Surfaces => new[] { (_gpu ? _glSurface : (object)null), this diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index adcbeb2d3a..aeb456c3ea 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -435,6 +435,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); + HRESULT GetTotalSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height); diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index b1006b43ee..604fbf2b24 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1740,6 +1740,16 @@ namespace Avalonia.X11 { public short W; public short H; } + + [StructLayout (LayoutKind.Sequential)] + [Serializable] + internal struct XFrameExtents + { + public int Left; + public int Right; + public int Top; + public int Bottom; + } [StructLayout (LayoutKind.Sequential)] diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 5ac4c4c9d0..c0a38eee95 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -297,6 +297,30 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); + public Size TotalSize + { + get + { + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_FRAME_EXTENTS, IntPtr.Zero, + new IntPtr(4), false, (IntPtr)Atom.AnyPropertyType, out var _, + out var _, out var nitems, out var _, out var prop); + + if (nitems.ToInt64() != 4) + { + // Window hasn't been mapped by the WM yet, so can't get the extents. + return ClientSize; + } + + var data = (IntPtr*)prop.ToPointer(); + var extents = new Thickness(data[0].ToInt32(), data[2].ToInt32(), data[1].ToInt32(), data[3].ToInt32()); + XFree(prop); + + return new Size( + (_realSize.Width + extents.Left + extents.Right) / RenderScaling, + (_realSize.Height + extents.Top + extents.Bottom) / RenderScaling); + } + } + public double RenderScaling { get @@ -589,6 +613,13 @@ namespace Avalonia.X11 private void OnPropertyChange(IntPtr atom, bool hasValue) { + if (atom == _x11.Atoms._NET_FRAME_EXTENTS) + { + // Occurs once the window has been mapped, which is the earliest the extents + // can be retrieved, so invoke event to force update of TopLevel.TotalSize. + Resized.Invoke(ClientSize); + } + if (atom == _x11.Atoms._NET_WM_STATE) { WindowState state = WindowState.Normal; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 4bbb58e53e..7f231aee17 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -62,6 +62,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; + public Size TotalSize => ClientSize; public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index fe1d625efb..eedf99b33a 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -100,6 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf } Size ITopLevelImpl.ClientSize => _finalSize; + Size ITopLevelImpl.TotalSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c137926e4c..ad409810b8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -745,6 +745,26 @@ namespace Avalonia.Win32.Interop WM_DISPATCH_WORK_ITEM = WM_USER, } + public enum DwmWindowAttribute : uint + { + DWMWA_NCRENDERING_ENABLED = 1, + DWMWA_NCRENDERING_POLICY, + DWMWA_TRANSITIONS_FORCEDISABLED, + DWMWA_ALLOW_NCPAINT, + DWMWA_CAPTION_BUTTON_BOUNDS, + DWMWA_NONCLIENT_RTL_LAYOUT, + DWMWA_FORCE_ICONIC_REPRESENTATION, + DWMWA_FLIP3D_POLICY, + DWMWA_EXTENDED_FRAME_BOUNDS, + DWMWA_HAS_ICONIC_BITMAP, + DWMWA_DISALLOW_PEEK, + DWMWA_EXCLUDED_FROM_PEEK, + DWMWA_CLOAK, + DWMWA_CLOAKED, + DWMWA_FREEZE_REPRESENTATION, + DWMWA_LAST + }; + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, @@ -1388,6 +1408,9 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + [DllImport("dwmapi.dll")] + public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute); + [DllImport("dwmapi.dll")] public static extern int DwmIsCompositionEnabled(out bool enabled); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3a3342fd14..bb3f78715d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -213,6 +213,16 @@ namespace Avalonia.Win32 } } + public Size TotalSize + { + get + { + DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); + + return new Size(rect.Width, rect.Height) / RenderScaling; + } + } + public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; private set; } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 36a70ea410..ecd2a46b12 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -91,6 +91,7 @@ namespace Avalonia.iOS } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); + public Size TotalSize => ClientSize; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..51c202f6bd 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,6 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); + windowImpl.Setup(x => x.TotalSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1);