From 7b9d32af85aa08580380ce81d0a999db727a3240 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Sun, 9 Feb 2020 15:50:42 +0000 Subject: [PATCH 01/10] Rework system decorations --- native/Avalonia.Native/inc/avalonia-native.h | 2 +- native/Avalonia.Native/src/OSX/window.mm | 41 +++++++++++++---- samples/ControlCatalog/MainView.xaml | 11 ++++- samples/ControlCatalog/MainView.xaml.cs | 14 ++++++ src/Avalonia.Controls/Platform/IWindowImpl.cs | 2 +- src/Avalonia.Controls/Window.cs | 44 ++++++++++++++++++- .../Remote/PreviewerWindowImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 4 +- src/Avalonia.X11/X11Window.cs | 16 ++++--- .../Interop/UnmanagedMethods.cs | 10 +++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 20 +++++---- src/iOS/Avalonia.iOS/EmbeddableImpl.cs | 2 +- 13 files changed, 137 insertions(+), 33 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 4a960d47a1..ce4a592d67 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -236,7 +236,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; virtual HRESULT SetCanResize(bool value) = 0; - virtual HRESULT SetHasDecorations(bool value) = 0; + virtual HRESULT SetHasDecorations(int value) = 0; virtual HRESULT SetTitle (void* utf8Title) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index b6ce172ffa..317d03162b 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -115,7 +115,6 @@ public: [NSApp activateIgnoringOtherApps:YES]; [Window setTitle:_lastTitle]; - [Window setTitleVisibility:NSWindowTitleVisible]; return S_OK; } @@ -411,7 +410,7 @@ class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, pub { private: bool _canResize = true; - bool _hasDecorations = true; + int _hasDecorations = 2; CGRect _lastUndecoratedFrame; AvnWindowState _lastWindowState; @@ -476,12 +475,12 @@ private: bool IsZoomed () { - return _hasDecorations ? [Window isZoomed] : UndecoratedIsMaximized(); + return _hasDecorations > 0 ? [Window isZoomed] : UndecoratedIsMaximized(); } void DoZoom() { - if (_hasDecorations) + if (_hasDecorations > 0) { [Window performZoom:Window]; } @@ -506,13 +505,36 @@ private: } } - virtual HRESULT SetHasDecorations(bool value) override + virtual HRESULT SetHasDecorations(int value) override { @autoreleasepool { _hasDecorations = value; UpdateStyle(); + // full + if (_hasDecorations == 2) + { + [Window setHasShadow:YES]; + [Window setTitleVisibility:NSWindowTitleVisible]; + [Window setTitlebarAppearsTransparent:NO]; + [Window setTitle:_lastTitle]; + } + // border only + else if (_hasDecorations == 1) + { + [Window setHasShadow:YES]; + [Window setTitleVisibility:NSWindowTitleHidden]; + [Window setTitlebarAppearsTransparent:YES]; + } + // none + else + { + [Window setHasShadow:NO]; + [Window setTitleVisibility:NSWindowTitleHidden]; + [Window setTitlebarAppearsTransparent:YES]; + } + return S_OK; } } @@ -523,7 +545,6 @@ private: { _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; [Window setTitle:_lastTitle]; - [Window setTitleVisibility:NSWindowTitleVisible]; return S_OK; } @@ -645,9 +666,11 @@ protected: virtual NSWindowStyleMask GetStyle() override { unsigned long s = NSWindowStyleMaskBorderless; - if(_hasDecorations) - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; - if(_canResize) + if(_hasDecorations == 1) + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; + if(_hasDecorations == 2) + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; + if(_hasDecorations == 2 && _canResize) s = s | NSWindowStyleMaskResizable; return s; } diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index cbe2c62890..1c2653f73a 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -58,10 +58,17 @@ - + + + No Decorations + Border Only + Full Decorations + + Light Dark - + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index acb9bc5bc6..5f71b2ecd9 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -56,6 +56,20 @@ namespace ControlCatalog } }; Styles.Add(light); + + var decorations = this.Find("Decorations"); + decorations.SelectionChanged += (sender, e) => + { + Window window = (Window)VisualRoot; + window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex; + }; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + var decorations = this.Find("Decorations"); + decorations.SelectedIndex = (int)((Window)VisualRoot).SystemDecorations; } } } diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 91b895f38a..238070bbef 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -36,7 +36,7 @@ namespace Avalonia.Platform /// /// Enables or disables system window decorations (title bar, buttons, etc) /// - void SetSystemDecorations(bool enabled); + void SetSystemDecorations(SystemDecorations enabled); /// /// Sets the icon of this window. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index f66a248aaf..853347bf41 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -45,6 +45,27 @@ namespace Avalonia.Controls WidthAndHeight = 3, } + /// + /// Determines system decorations (title bar, border, etc) for a + /// + public enum SystemDecorations + { + /// + /// No decorations + /// + None = 0, + + /// + /// Window border without titlebar + /// + BorderOnly = 1, + + /// + /// Fully decorated (default) + /// + Full = 2 + } + /// /// A top-level window. /// @@ -59,9 +80,16 @@ namespace Avalonia.Controls /// /// Enables or disables system window decorations (title bar, buttons, etc) /// + [Obsolete("Use SystemDecorationsProperty instead")] public static readonly StyledProperty HasSystemDecorationsProperty = AvaloniaProperty.Register(nameof(HasSystemDecorations), true); + /// + /// Defines the property. + /// + public static readonly StyledProperty SystemDecorationsProperty = + AvaloniaProperty.Register(nameof(SystemDecorations), SystemDecorations.Full); + /// /// Enables or disables the taskbar icon /// @@ -125,7 +153,9 @@ namespace Avalonia.Controls BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue)); HasSystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl?.SetSystemDecorations((bool)e.NewValue)); + (s, e) => s.PlatformImpl?.SetSystemDecorations(((bool)e.NewValue) ? SystemDecorations.Full : SystemDecorations.None)); + SystemDecorationsProperty.Changed.AddClassHandler( + (s, e) => s.PlatformImpl?.SetSystemDecorations((SystemDecorations)e.NewValue)); ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); @@ -140,7 +170,6 @@ namespace Avalonia.Controls 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))); MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue))); - } /// @@ -192,12 +221,23 @@ namespace Avalonia.Controls /// Enables or disables system window decorations (title bar, buttons, etc) /// /// + [Obsolete("Use SystemDecorations instead")] public bool HasSystemDecorations { get { return GetValue(HasSystemDecorationsProperty); } set { SetValue(HasSystemDecorationsProperty, value); } } + /// + /// Sets the system decorations (title bar, border, etc) + /// + /// + public SystemDecorations SystemDecorations + { + get { return GetValue(SystemDecorationsProperty); } + set { SetValue(SystemDecorationsProperty, value); } + } + /// /// Enables or disables the taskbar icon /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 86e34ca6d4..7480b3519c 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -96,7 +96,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void SetSystemDecorations(bool enabled) + public void SetSystemDecorations(SystemDecorations enabled) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 4bba5ef41b..7bf1d236bd 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -110,7 +110,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void SetSystemDecorations(bool enabled) + public void SetSystemDecorations(SystemDecorations enabled) { } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index c757576017..a540b026fa 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -68,9 +68,9 @@ namespace Avalonia.Native _native.CanResize = value; } - public void SetSystemDecorations(bool enabled) + public void SetSystemDecorations(SystemDecorations enabled) { - _native.HasDecorations = enabled; + _native.HasDecorations = (int)enabled; } public void SetTitleBarColor (Avalonia.Media.Color color) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 919abae243..b091ee212f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -173,6 +173,7 @@ namespace Avalonia.X11 Surfaces = surfaces.ToArray(); UpdateMotifHints(); + UpdateSizeHints(null); _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing, XNames.XNClientWindow, _handle, IntPtr.Zero); XFlush(_x11.Display); @@ -219,12 +220,16 @@ namespace Avalonia.X11 var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border | MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH; - if (_popup || !_systemDecorations) + if (_popup || _systemDecorations == SystemDecorations.None) { decorations = 0; } + else if (_systemDecorations == SystemDecorations.BorderOnly) + { + decorations = MotifDecorations.Border; + } - if (!_canResize) + if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) { functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); @@ -247,7 +252,7 @@ namespace Avalonia.X11 var min = _minMaxSize.minSize; var max = _minMaxSize.maxSize; - if (!_canResize) + if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) max = min = _realSize; if (preResize.HasValue) @@ -621,7 +626,7 @@ namespace Avalonia.X11 return rv; } - private bool _systemDecorations = true; + private SystemDecorations _systemDecorations = SystemDecorations.Full; private bool _canResize = true; private const int MaxWindowDimension = 100000; @@ -777,10 +782,11 @@ namespace Avalonia.X11 (int)(point.X * Scaling + Position.X), (int)(point.Y * Scaling + Position.Y)); - public void SetSystemDecorations(bool enabled) + public void SetSystemDecorations(SystemDecorations enabled) { _systemDecorations = enabled; UpdateMotifHints(); + UpdateSizeHints(null); } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 904e122382..50b568cab2 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1298,7 +1298,17 @@ namespace Avalonia.Win32.Interop [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect); + [DllImport("dwmapi.dll")] + public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + [StructLayout(LayoutKind.Sequential)] + internal struct MARGINS + { + public int cxLeftWidth; + public int cxRightWidth; + public int cyTopHeight; + public int cyBottomHeight; + } public enum MONITOR { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index c16b76b539..d13c07279c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Win32 private IInputRoot _owner; private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock(); private bool _trackingMouse; - private bool _decorated = true; + private SystemDecorations _decorated = SystemDecorations.Full; private bool _resizable = true; private bool _topmost = false; private bool _taskbarIcon = true; @@ -97,7 +97,7 @@ namespace Avalonia.Win32 { get { - if (_decorated) + if (_decorated == SystemDecorations.Full) { var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); @@ -281,7 +281,7 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide); } - public void SetSystemDecorations(bool value) + public void SetSystemDecorations(SystemDecorations value) { if (value == _decorated) { @@ -464,7 +464,7 @@ namespace Avalonia.Win32 return IntPtr.Zero; case WindowsMessage.WM_NCCALCSIZE: - if (ToInt32(wParam) == 1 && !_decorated) + if (ToInt32(wParam) == 1 && _decorated != SystemDecorations.Full) { return IntPtr.Zero; } @@ -682,14 +682,14 @@ namespace Avalonia.Win32 break; case WindowsMessage.WM_NCPAINT: - if (!_decorated) + if (_decorated != SystemDecorations.Full) { return IntPtr.Zero; } break; case WindowsMessage.WM_NCACTIVATE: - if (!_decorated) + if (_decorated != SystemDecorations.Full) { return new IntPtr(1); } @@ -1001,7 +1001,7 @@ namespace Avalonia.Win32 style |= WindowStyles.WS_OVERLAPPEDWINDOW; - if (!_decorated) + if (_decorated != SystemDecorations.Full) { style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } @@ -1011,6 +1011,10 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } + MARGINS margins = new MARGINS(); + margins.cyBottomHeight = _decorated == SystemDecorations.BorderOnly ? 1 : 0; + UnmanagedMethods.DwmExtendFrameIntoClientArea(_hwnd, ref margins); + GetClientRect(_hwnd, out var oldClientRect); var oldClientRectOrigin = new UnmanagedMethods.POINT(); ClientToScreen(_hwnd, ref oldClientRectOrigin); @@ -1024,7 +1028,7 @@ namespace Avalonia.Win32 if (oldDecorated != _decorated) { var newRect = oldClientRect; - if (_decorated) + if (_decorated == SystemDecorations.Full) AdjustWindowRectEx(ref newRect, (uint)style, false, GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs index 65a6c15971..838bf49846 100644 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.iOS return Disposable.Empty; } - public void SetSystemDecorations(bool enabled) + public void SetSystemDecorations(SystemDecorations enabled) { } From d44ad423a0b2fec410a3917dcfb27d2187e9c8b4 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Mon, 10 Feb 2020 09:08:33 +0000 Subject: [PATCH 02/10] Use enum in macOS native --- native/Avalonia.Native/src/OSX/window.h | 6 ++ native/Avalonia.Native/src/OSX/window.mm | 100 +++++++++++++---------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 3e626675d2..23e3c22db7 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -36,4 +36,10 @@ struct IWindowStateChanged virtual void WindowStateChanged () = 0; }; +typedef NS_ENUM(NSInteger, SystemDecorations) { + SystemDecorationsNone = 0, + SystemDecorationsBorderOnly = 1, + SystemDecorationsFull = 2, +}; + #endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 317d03162b..4c70f661b7 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -410,7 +410,7 @@ class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, pub { private: bool _canResize = true; - int _hasDecorations = 2; + SystemDecorations _hasDecorations = SystemDecorationsFull; CGRect _lastUndecoratedFrame; AvnWindowState _lastWindowState; @@ -475,23 +475,26 @@ private: bool IsZoomed () { - return _hasDecorations > 0 ? [Window isZoomed] : UndecoratedIsMaximized(); + return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized(); } void DoZoom() { - if (_hasDecorations > 0) + switch (_hasDecorations) { - [Window performZoom:Window]; - } - else - { - if (!UndecoratedIsMaximized()) - { - _lastUndecoratedFrame = [Window frame]; - } - - [Window zoom:Window]; + case SystemDecorationsNone: + if (!UndecoratedIsMaximized()) + { + _lastUndecoratedFrame = [Window frame]; + } + + [Window zoom:Window]; + break; + + case SystemDecorationsBorderOnly: + case SystemDecorationsFull: + [Window performZoom:Window]; + break; } } @@ -509,32 +512,31 @@ private: { @autoreleasepool { - _hasDecorations = value; + _hasDecorations = (SystemDecorations)value; UpdateStyle(); - - // full - if (_hasDecorations == 2) - { - [Window setHasShadow:YES]; - [Window setTitleVisibility:NSWindowTitleVisible]; - [Window setTitlebarAppearsTransparent:NO]; - [Window setTitle:_lastTitle]; - } - // border only - else if (_hasDecorations == 1) - { - [Window setHasShadow:YES]; - [Window setTitleVisibility:NSWindowTitleHidden]; - [Window setTitlebarAppearsTransparent:YES]; - } - // none - else + + switch (_hasDecorations) { - [Window setHasShadow:NO]; - [Window setTitleVisibility:NSWindowTitleHidden]; - [Window setTitlebarAppearsTransparent:YES]; + case SystemDecorationsNone: + [Window setHasShadow:NO]; + [Window setTitleVisibility:NSWindowTitleHidden]; + [Window setTitlebarAppearsTransparent:YES]; + break; + + case SystemDecorationsBorderOnly: + [Window setHasShadow:YES]; + [Window setTitleVisibility:NSWindowTitleHidden]; + [Window setTitlebarAppearsTransparent:YES]; + break; + + case SystemDecorationsFull: + [Window setHasShadow:YES]; + [Window setTitleVisibility:NSWindowTitleVisible]; + [Window setTitlebarAppearsTransparent:NO]; + [Window setTitle:_lastTitle]; + break; } - + return S_OK; } } @@ -666,12 +668,26 @@ protected: virtual NSWindowStyleMask GetStyle() override { unsigned long s = NSWindowStyleMaskBorderless; - if(_hasDecorations == 1) - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; - if(_hasDecorations == 2) - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; - if(_hasDecorations == 2 && _canResize) - s = s | NSWindowStyleMaskResizable; + + switch (_hasDecorations) + { + case SystemDecorationsNone: + break; + + case SystemDecorationsBorderOnly: + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; + break; + + case SystemDecorationsFull: + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; + if(_canResize) + { + s = s | NSWindowStyleMaskResizable; + } + + break; + } + return s; } }; From c0e337d61f1df5a8b53135b1179c449b3f6544dc Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Mon, 10 Feb 2020 09:44:20 +0000 Subject: [PATCH 03/10] Use enum in mac interop --- native/Avalonia.Native/inc/avalonia-native.h | 8 +++++++- native/Avalonia.Native/src/OSX/window.h | 6 ------ native/Avalonia.Native/src/OSX/window.mm | 4 ++-- src/Avalonia.Native/WindowImpl.cs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index ce4a592d67..ee57f54e59 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -25,6 +25,12 @@ struct IAvnGlSurfaceRenderingSession; struct IAvnAppMenu; struct IAvnAppMenuItem; +enum SystemDecorations { + SystemDecorationsNone = 0, + SystemDecorationsBorderOnly = 1, + SystemDecorationsFull = 2, +}; + struct AvnSize { double Width, Height; @@ -236,7 +242,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; virtual HRESULT SetCanResize(bool value) = 0; - virtual HRESULT SetHasDecorations(int value) = 0; + virtual HRESULT SetHasDecorations(SystemDecorations value) = 0; virtual HRESULT SetTitle (void* utf8Title) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 23e3c22db7..3e626675d2 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -36,10 +36,4 @@ struct IWindowStateChanged virtual void WindowStateChanged () = 0; }; -typedef NS_ENUM(NSInteger, SystemDecorations) { - SystemDecorationsNone = 0, - SystemDecorationsBorderOnly = 1, - SystemDecorationsFull = 2, -}; - #endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 4c70f661b7..2c03407732 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -508,11 +508,11 @@ private: } } - virtual HRESULT SetHasDecorations(int value) override + virtual HRESULT SetHasDecorations(SystemDecorations value) override { @autoreleasepool { - _hasDecorations = (SystemDecorations)value; + _hasDecorations = value; UpdateStyle(); switch (_hasDecorations) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index a540b026fa..73ec81ce57 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -68,9 +68,9 @@ namespace Avalonia.Native _native.CanResize = value; } - public void SetSystemDecorations(SystemDecorations enabled) + public void SetSystemDecorations(Controls.SystemDecorations enabled) { - _native.HasDecorations = (int)enabled; + _native.HasDecorations = (Interop.SystemDecorations)enabled; } public void SetTitleBarColor (Avalonia.Media.Color color) From e5f0ac3dda380bf7a7058ed31df00a7e2383db3a Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Tue, 3 Mar 2020 22:23:13 +0000 Subject: [PATCH 04/10] Fix event binding for HasSystemDecorations property --- src/Avalonia.Controls/Window.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 853347bf41..b2bd52e8a2 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -153,9 +153,13 @@ namespace Avalonia.Controls BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue)); HasSystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl?.SetSystemDecorations(((bool)e.NewValue) ? SystemDecorations.Full : SystemDecorations.None)); + (s, e) => s.SetValue(SystemDecorationsProperty, (bool)e.NewValue ? SystemDecorations.Full : SystemDecorations.None)); SystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl?.SetSystemDecorations((SystemDecorations)e.NewValue)); + (s, e) => + { + s.PlatformImpl?.SetSystemDecorations((SystemDecorations)e.NewValue); + s.SetValue(HasSystemDecorationsProperty, (SystemDecorations)e.NewValue != SystemDecorations.None); + }); ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); From 5d007606bad8a8d9509129f1b4f7e9d7d43aeadd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Mar 2020 11:04:43 +0100 Subject: [PATCH 05/10] Rework HasSystemDecorations. Make it a direct property that is backed by `SystemDecorations`. Moved the changed handlers to `OnPropertyChanged`. --- src/Avalonia.Controls/Window.cs | 62 +++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 6154db9f8a..f4a3f05f03 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -2,19 +2,19 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.ComponentModel; +using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls.Platform; +using Avalonia.Data; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; -using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; -using System.ComponentModel; -using Avalonia.Interactivity; namespace Avalonia.Controls { @@ -81,8 +81,11 @@ namespace Avalonia.Controls /// Enables or disables system window decorations (title bar, buttons, etc) /// [Obsolete("Use SystemDecorationsProperty instead")] - public static readonly StyledProperty HasSystemDecorationsProperty = - AvaloniaProperty.Register(nameof(HasSystemDecorations), true); + public static readonly DirectProperty HasSystemDecorationsProperty = + AvaloniaProperty.RegisterDirect( + nameof(HasSystemDecorations), + o => o.HasSystemDecorations, + (o, v) => o.HasSystemDecorations = v); /// /// Defines the property. @@ -152,15 +155,6 @@ namespace Avalonia.Controls { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue)); - HasSystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => s.SetValue(SystemDecorationsProperty, (bool)e.NewValue ? SystemDecorations.Full : SystemDecorations.None)); - SystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => - { - s.PlatformImpl?.SetSystemDecorations((SystemDecorations)e.NewValue); - s.SetValue(HasSystemDecorationsProperty, (SystemDecorations)e.NewValue != SystemDecorations.None); - }); - ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl)); @@ -169,7 +163,7 @@ namespace Avalonia.Controls WindowStateProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)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))); @@ -224,12 +218,20 @@ namespace Avalonia.Controls /// /// Enables or disables system window decorations (title bar, buttons, etc) /// - /// [Obsolete("Use SystemDecorations instead")] public bool HasSystemDecorations { - get { return GetValue(HasSystemDecorationsProperty); } - set { SetValue(HasSystemDecorationsProperty, value); } + get => SystemDecorations == SystemDecorations.Full; + set + { + var oldValue = HasSystemDecorations; + + if (oldValue != value) + { + SystemDecorations = value ? SystemDecorations.Full : SystemDecorations.None; + RaisePropertyChanged(HasSystemDecorationsProperty, oldValue, value); + } + } } /// @@ -628,5 +630,27 @@ namespace Avalonia.Controls /// event needs to be raised. /// protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); + + protected override void OnPropertyChanged( + AvaloniaProperty property, + Optional oldValue, + BindingValue newValue, + BindingPriority priority) + { + if (property == SystemDecorationsProperty) + { + var typedNewValue = newValue.GetValueOrDefault(); + + PlatformImpl?.SetSystemDecorations(typedNewValue); + + var o = oldValue.GetValueOrDefault() == SystemDecorations.Full; + var n = typedNewValue == SystemDecorations.Full; + + if (o != n) + { + RaisePropertyChanged(HasSystemDecorationsProperty, o, n); + } + } + } } } From f98ee717f50c125176212f85b01d89c7fdf7c7fc Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Thu, 12 Mar 2020 10:32:45 -0400 Subject: [PATCH 06/10] Adjusted TextBox right-click behavior WPF does not allow dragging to change selection for right clicks Furthermore, it only changes the selection on mouse up of right click, not mouse down, and only if the user clicked outside the current selection. This commit adjusts TextBox selection behavior to work like WPF --- src/Avalonia.Controls/TextBox.cs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index a438d7380b..f9dd98634c 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -684,11 +684,12 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { var point = e.GetPosition(_presenter); - var index = CaretIndex = _presenter.GetCaretIndex(point); + var index = _presenter.GetCaretIndex(point); var text = Text; - if (text != null && e.MouseButton == MouseButton.Left) + if (text != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { + CaretIndex = index; switch (e.ClickCount) { case 1: @@ -714,7 +715,8 @@ namespace Avalonia.Controls protected override void OnPointerMoved(PointerEventArgs e) { - if (_presenter != null && e.Pointer.Captured == _presenter) + // selection should not change during pointer move if the user right clicks + if (_presenter != null && e.Pointer.Captured == _presenter && !e.GetCurrentPoint(this).Properties.IsRightButtonPressed) { var point = e.GetPosition(_presenter); @@ -727,6 +729,22 @@ namespace Avalonia.Controls { if (_presenter != null && e.Pointer.Captured == _presenter) { + if (e.InitialPressMouseButton == MouseButton.Right) + { + var point = e.GetPosition(_presenter); + var caretIndex = _presenter.GetCaretIndex(point); + + // see if mouse clicked inside current selection + // if it did not, we change the selection to where the user clicked + var firstSelection = Math.Min(SelectionStart, SelectionEnd); + var lastSelection = Math.Max(SelectionStart, SelectionEnd); + var didClickInSelection = SelectionStart != SelectionEnd && + caretIndex >= firstSelection && caretIndex <= lastSelection; + if (!didClickInSelection) + { + CaretIndex = SelectionEnd = SelectionStart = caretIndex; + } + } e.Pointer.Capture(null); } } From 1f18bd3e66e79f55fdaa9bc04479843e75d0cee7 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Thu, 12 Mar 2020 10:41:33 -0400 Subject: [PATCH 07/10] Only calculate caret index on pointer pressed if left mouse down --- src/Avalonia.Controls/TextBox.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index f9dd98634c..c4bfb5f1b1 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -683,13 +683,12 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { - var point = e.GetPosition(_presenter); - var index = _presenter.GetCaretIndex(point); var text = Text; if (text != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { - CaretIndex = index; + var point = e.GetPosition(_presenter); + var index = CaretIndex = _presenter.GetCaretIndex(point); switch (e.ClickCount) { case 1: From dc93d14a1b09a21ab457c6d8f47827fb76d9a320 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Mar 2020 20:17:11 +0100 Subject: [PATCH 08/10] Return IReadOnlyList from most of the AvaloniaPropertyRegistry calls so users are not forced into allocating enumerators. --- src/Avalonia.Base/AvaloniaObject.cs | 6 +- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 60 +++++++++++++++---- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 88b99cd99a..51062f1568 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -80,8 +80,12 @@ namespace Avalonia _inheritanceParent?.RemoveInheritanceChild(this); _inheritanceParent = value; - foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType())) + var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()); + var propertiesCount = properties.Count; + + for (var i = 0; i < propertiesCount; i++) { + var property = properties[i]; if (valuestore?.IsSet(property) == true) { // If local value set there can be no change. diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 29ab10278b..11bd58d05c 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using Avalonia.Data; namespace Avalonia { @@ -49,7 +47,7 @@ namespace Avalonia /// /// The type. /// A collection of definitions. - public IEnumerable GetRegistered(Type type) + public IReadOnlyList GetRegistered(Type type) { Contract.Requires(type != null); @@ -83,7 +81,7 @@ namespace Avalonia /// /// The type. /// A collection of definitions. - public IEnumerable GetRegisteredAttached(Type type) + public IReadOnlyList GetRegisteredAttached(Type type) { Contract.Requires(type != null); @@ -114,7 +112,7 @@ namespace Avalonia /// /// The type. /// A collection of definitions. - public IEnumerable GetRegisteredDirect(Type type) + public IReadOnlyList GetRegisteredDirect(Type type) { Contract.Requires(type != null); @@ -145,7 +143,7 @@ namespace Avalonia /// /// The type. /// A collection of definitions. - public IEnumerable GetRegisteredInherited(Type type) + public IReadOnlyList GetRegisteredInherited(Type type) { Contract.Requires(type != null); @@ -157,16 +155,27 @@ namespace Avalonia result = new List(); var visited = new HashSet(); - foreach (var property in GetRegistered(type)) + var registered = GetRegistered(type); + var registeredCount = registered.Count; + + for (var i = 0; i < registeredCount; i++) { + var property = registered[i]; + if (property.Inherits) { result.Add(property); visited.Add(property); } } - foreach (var property in GetRegisteredAttached(type)) + + var registeredAttached = GetRegisteredAttached(type); + var registeredAttachedCount = registeredAttached.Count; + + for (var i = 0; i < registeredAttachedCount; i++) { + var property = registeredAttached[i]; + if (property.Inherits) { if (!visited.Contains(property)) @@ -185,7 +194,7 @@ namespace Avalonia /// /// The object. /// A collection of definitions. - public IEnumerable GetRegistered(IAvaloniaObject o) + public IReadOnlyList GetRegistered(IAvaloniaObject o) { Contract.Requires(o != null); @@ -229,8 +238,13 @@ namespace Avalonia throw new InvalidOperationException("Attached properties not supported."); } - foreach (AvaloniaProperty x in GetRegistered(type)) + var registered = GetRegistered(type); + var registeredCount = registered.Count; + + for (var i = 0; i < registeredCount; i++) { + AvaloniaProperty x = registered[i]; + if (x.Name == name) { return x; @@ -276,8 +290,13 @@ namespace Avalonia return property; } - foreach (var p in GetRegisteredDirect(o.GetType())) + var registeredDirect = GetRegisteredDirect(o.GetType()); + var registeredDirectCount = registeredDirect.Count; + + for (var i = 0; i < registeredDirectCount; i++) { + var p = registeredDirect[i]; + if (p == property) { return (DirectPropertyBase)p; @@ -308,8 +327,23 @@ namespace Avalonia Contract.Requires(type != null); Contract.Requires(property != null); - return Instance.GetRegistered(type).Any(x => x == property) || - Instance.GetRegisteredAttached(type).Any(x => x == property); + static bool ContainsProperty(IReadOnlyList properties, AvaloniaProperty property) + { + var propertiesCount = properties.Count; + + for (var i = 0; i < propertiesCount; i++) + { + if (properties[i] == property) + { + return true; + } + } + + return false; + } + + return ContainsProperty(Instance.GetRegistered(type), property) || + ContainsProperty(Instance.GetRegisteredAttached(type), property); } /// From 0e03dab3fecbf312e81578b23ae64acb45dff62e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Mar 2020 20:18:15 +0100 Subject: [PATCH 09/10] Remove dead code. --- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 11bd58d05c..7d3d5492b6 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -26,8 +26,6 @@ namespace Avalonia new Dictionary>(); private readonly Dictionary> _directCache = new Dictionary>(); - private readonly Dictionary> _initializedCache = - new Dictionary>(); private readonly Dictionary> _inheritedCache = new Dictionary>(); @@ -408,7 +406,6 @@ namespace Avalonia } _registeredCache.Clear(); - _initializedCache.Clear(); _inheritedCache.Clear(); } @@ -445,32 +442,7 @@ namespace Avalonia } _attachedCache.Clear(); - _initializedCache.Clear(); _inheritedCache.Clear(); } - - private readonly struct PropertyInitializationData - { - public AvaloniaProperty Property { get; } - public object Value { get; } - public bool IsDirect { get; } - public IDirectPropertyAccessor DirectAccessor { get; } - - public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor) - { - Property = property; - Value = null; - IsDirect = true; - DirectAccessor = directAccessor; - } - - public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type) - { - Property = property; - Value = styledAccessor.GetDefaultValue(type); - IsDirect = false; - DirectAccessor = null; - } - } } } From a8663cd1f349cb3525e464e4fb6e51ef4973604e Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 17 Mar 2020 12:27:05 -0400 Subject: [PATCH 10/10] Fix middle click being able to change selection --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index c4bfb5f1b1..cd5dff2c9a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -715,7 +715,7 @@ namespace Avalonia.Controls protected override void OnPointerMoved(PointerEventArgs e) { // selection should not change during pointer move if the user right clicks - if (_presenter != null && e.Pointer.Captured == _presenter && !e.GetCurrentPoint(this).Properties.IsRightButtonPressed) + if (_presenter != null && e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { var point = e.GetPosition(_presenter);