diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 336ec6d339..a86828b4bc 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -229,6 +229,21 @@ public: } } + virtual HRESULT GetFrameSize(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 { START_COM_CALL; diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 253ea46cb0..0afb1db141 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? FrameSize => null; + 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 ac6db5bdf7..acd4c796c6 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -9,6 +9,8 @@ MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.Off MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the contract but not in the implementation. MembersMustExist : Member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' does not exist in the implementation but it does exist in the contract. @@ -24,4 +26,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. -Total Issues: 25 +Total Issues: 27 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 89e391d9db..e2afbd3bdc 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,6 +35,8 @@ namespace Avalonia.Controls.Embedding.Offscreen } } + public Size? FrameSize => null; + public double RenderScaling { get { return _scaling; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 0fbff74aa2..f4f4d29168 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -57,6 +57,11 @@ namespace Avalonia.Platform /// Size ClientSize { get; } + /// + /// Gets the total size of the toplevel, excluding shadows. + /// + Size? FrameSize { 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 e882c75def..ee12e358cf 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 FrameSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); + /// /// Defines the property. /// @@ -74,6 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; + private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -162,6 +169,7 @@ namespace Avalonia.Controls styler?.ApplyStyles(this); ClientSize = impl.ClientSize; + FrameSize = impl.FrameSize; this.GetObservable(PointerOverElementProperty) .Select( @@ -198,6 +206,15 @@ namespace Avalonia.Controls protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } + /// + /// Gets or sets the total size of the window. + /// + public Size? FrameSize + { + get { return _frameSize; } + protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, value); } + } + /// /// Gets or sets the that the TopLevel should use when possible. /// @@ -371,6 +388,7 @@ namespace Avalonia.Controls protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 0fca28cbf1..7cfc5f8be8 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -212,6 +212,7 @@ namespace Avalonia.Controls protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; LayoutManager.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 74ee891ee4..9dcd4d8e87 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? FrameSize => null; 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 30810a861e..31315848d1 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? FrameSize => null; 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 6cb32fc3d3..4a3baa2788 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,6 +104,20 @@ namespace Avalonia.Native } } + public Size? FrameSize + { + get + { + if (_native != null) + { + var s = _native.FrameSize; + 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 43d45fadde..70d85dacdd 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -444,6 +444,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); + HRESULT GetFrameSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height, AvnPlatformResizeReason reason); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 5a929dfd39..149d7fb9b2 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -296,6 +296,30 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); + public Size? FrameSize + { + 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 null; + } + + 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 diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 0596bcc09a..b097e0917f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -61,6 +61,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; + public Size? FrameSize => null; 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 78abb9aa4a..d36db107e3 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.FrameSize => null; 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 74495c1cc7..0b7bd13082 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -214,6 +214,21 @@ namespace Avalonia.Win32 } } + public Size? FrameSize + { + get + { + if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) + { + GetWindowRect(_hwnd, out var rcWindow); + return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling; + } + + 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 d649fe86cd..5bb2f64879 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? FrameSize => null; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; }