diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index b1f64bca88..3a54bd4b79 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -10,6 +10,8 @@ class WindowBaseImpl; -(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose; -(void) onClosed; -(AvnPixelSize) getPixelSize; +-(AvnPlatformResizeReason) getResizeReason; +-(void) setResizeReason:(AvnPlatformResizeReason)reason; @end @interface AutoFitContentView : NSView @@ -34,6 +36,7 @@ class WindowBaseImpl; -(double) getScaling; -(double) getExtendedTitleBarHeight; -(void) setIsExtended:(bool)value; +-(bool) isDialog; @end struct INSWindowHolder @@ -50,4 +53,23 @@ struct IWindowStateChanged virtual AvnWindowState WindowState () = 0; }; +class ResizeScope +{ +public: + ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason) + { + _view = view; + _restore = [view getResizeReason]; + [view setResizeReason:reason]; + } + + ~ResizeScope() + { + [_view setResizeReason:_restore]; + } +private: + AvnView* _Nonnull _view; + AvnPlatformResizeReason _restore; +}; + #endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index bb686e3902..9c6a0e6187 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -52,6 +52,7 @@ public: [Window setBackingType:NSBackingStoreBuffered]; [Window setOpaque:false]; + [Window setContentView: StandardContainer]; } virtual HRESULT ObtainNSWindowHandle(void** ret) override @@ -115,7 +116,7 @@ public: return Window; } - virtual HRESULT Show(bool activate) override + virtual HRESULT Show(bool activate, bool isDialog) override { START_COM_CALL; @@ -124,7 +125,6 @@ public: SetPosition(lastPositionSet); UpdateStyle(); - [Window setContentView: StandardContainer]; [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) @@ -277,7 +277,7 @@ public: } } - virtual HRESULT Resize(double x, double y) override + virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override { if(_inResize) { @@ -287,6 +287,7 @@ public: _inResize = true; START_COM_CALL; + auto resizeBlock = ResizeScope(View, reason); @autoreleasepool { @@ -317,7 +318,7 @@ public: { if(!_shown) { - BaseEvents->Resized(AvnSize{x,y}); + BaseEvents->Resized(AvnSize{x,y}, reason); } [Window setContentSize:NSSize{x, y}]; @@ -577,6 +578,11 @@ public: return S_OK; } + virtual bool IsDialog() + { + return false; + } + protected: virtual NSWindowStyleMask GetStyle() { @@ -607,6 +613,7 @@ private: NSRect _preZoomSize; bool _transitioningWindowState; bool _isClientAreaExtended; + bool _isDialog; AvnExtendClientAreaChromeHints _extendClientHints; FORWARD_IUNKNOWN() @@ -667,13 +674,14 @@ private: } } - virtual HRESULT Show (bool activate) override + virtual HRESULT Show (bool activate, bool isDialog) override { START_COM_CALL; @autoreleasepool { - WindowBaseImpl::Show(activate); + _isDialog = isDialog; + WindowBaseImpl::Show(activate, isDialog); HideOrShowTrafficLights(); @@ -1213,6 +1221,11 @@ private: } } + virtual bool IsDialog() override + { + return _isDialog; + } + protected: virtual NSWindowStyleMask GetStyle() override { @@ -1373,6 +1386,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent bool _lastKeyHandled; AvnPixelSize _lastPixelSize; NSObject* _renderTarget; + AvnPlatformResizeReason _resizeReason; } - (void)onClosed @@ -1484,7 +1498,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _lastPixelSize.Height = (int)fsize.height; [self updateRenderTarget]; - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + auto reason = [self inLiveResize] ? ResizeUser : _resizeReason; + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason); } } @@ -1983,6 +1998,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } +- (AvnPlatformResizeReason)getResizeReason +{ + return _resizeReason; +} + +- (void)setResizeReason:(AvnPlatformResizeReason)reason +{ + _resizeReason = reason; +} + @end @@ -2002,6 +2027,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _isExtended = value; } +-(bool) isDialog +{ + return _parent->IsDialog(); +} + -(double) getScaling { return _lastScaling; @@ -2180,7 +2210,22 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent -(BOOL)canBecomeKeyWindow { - return _canBecomeKeyAndMain; + if (_canBecomeKeyAndMain) + { + // If the window has a child window being shown as a dialog then don't allow it to become the key window. + for(NSWindow* uch in [self childWindows]) + { + auto ch = objc_cast(uch); + if(ch == nil) + continue; + if (ch.isDialog) + return false; + } + + return true; + } + + return false; } -(BOOL)canBecomeMainWindow @@ -2188,22 +2233,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return _canBecomeKeyAndMain; } --(bool) activateAppropriateChild: (bool)activating -{ - for(NSWindow* uch in [self childWindows]) - { - auto ch = objc_cast(uch); - if(ch == nil) - continue; - [ch activateAppropriateChild:false]; - return FALSE; - } - - if(!activating) - [self makeKeyAndOrderFront:self]; - return TRUE; -} - -(bool)shouldTryToHandleEvents { return _isEnabled; @@ -2214,26 +2243,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _isEnabled = enable; } --(void)makeKeyWindow -{ - if([self activateAppropriateChild: true]) - { - [super makeKeyWindow]; - } -} - -(void)becomeKeyWindow { [self showWindowMenuWithAppMenu]; - if([self activateAppropriateChild: true]) + if(_parent != nullptr) { - if(_parent != nullptr) - { - _parent->BaseEvents->Activated(); - } + _parent->BaseEvents->Activated(); } - + [super becomeKeyWindow]; } @@ -2243,7 +2261,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent if(parent != nil) { [parent removeChildWindow:self]; - [parent activateAppropriateChild: false]; } } @@ -2378,7 +2395,7 @@ protected: return NSWindowStyleMaskBorderless; } - virtual HRESULT Resize(double x, double y) override + virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) 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 a72742580c..0afb1db141 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } @@ -134,7 +134,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform protected virtual void OnResized(Size size) { - Resized?.Invoke(size); + Resized?.Invoke(size, PlatformResizeReason.Unspecified); } class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index ea62a8d843..fac5923db5 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -30,15 +30,29 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +MembersMustExist : Member 'public System.Action Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. 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. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. 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: 42 +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract. +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: 56 diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 79780dbd0b..2a42d99ac5 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes public event EventHandler Startup; /// - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; /// public event EventHandler Exit; @@ -134,7 +134,7 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private void OnShutdownRequested(object sender, CancelEventArgs e) + private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) { ShutdownRequested?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index ecf8a0358f..a70d5dd2f1 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -37,14 +37,21 @@ namespace Avalonia.Controls.ApplicationLifetimes IReadOnlyList Windows { get; } /// - /// Raised by the platform when a shutdown is requested. + /// Raised by the platform when an application shutdown is requested. /// /// - /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event - /// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application + /// Application Shutdown can be requested for various reasons like OS shutdown. + /// + /// On Windows this will be called when an OS Session (logout or shutdown) terminates. Cancelling the eventargs will + /// block OS shutdown. + /// + /// On OSX this has the same behavior as on Windows and in addition: + /// This event is raised via the Quit menu or right-clicking on the application icon and selecting Quit. + /// + /// This event provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application /// will try to close each non-owned open window, invoking the event on each and allowing - /// each window to cancel the shutdown. + /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs new file mode 100644 index 0000000000..62bc3a8904 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ShutdownRequestedEventArgs : CancelEventArgs + { + + } +} diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 83470f161d..e2afbd3bdc 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen set { _clientSize = value; - Resized?.Invoke(value); + Resized?.Invoke(value, PlatformResizeReason.Unspecified); } } @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index 8e660777e9..4cd6640453 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Platform { @@ -11,6 +12,6 @@ namespace Avalonia.Platform /// /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 6e53233898..f4f4d29168 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -3,11 +3,46 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Layout; using Avalonia.Rendering; using JetBrains.Annotations; namespace Avalonia.Platform { + /// + /// Describes the reason for a message. + /// + public enum PlatformResizeReason + { + /// + /// The resize reason is unknown or unspecified. + /// + Unspecified, + + /// + /// The resize was due to the user resizing the window, for example by dragging the + /// window frame. + /// + User, + + /// + /// The resize was initiated by the application, for example by setting one of the sizing- + /// related properties on such as or + /// . + /// + Application, + + /// + /// The resize was initiated by the layout system. + /// + Layout, + + /// + /// The resize was due to a change in DPI. + /// + DpiChange, + } + /// /// Defines a platform-specific top-level window implementation. /// @@ -57,7 +92,7 @@ namespace Avalonia.Platform /// /// Gets or sets a method called when the toplevel is resized. /// - Action Resized { get; set; } + Action Resized { get; set; } /// /// Gets or sets a method called when the toplevel's scaling changes. diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0d303a6666..5172569726 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -7,7 +7,9 @@ namespace Avalonia.Platform /// /// Shows the window. /// - void Show(bool activate); + /// Whether to activate the shown window. + /// Whether the window is being shown as a dialog. + void Show(bool activate, bool isDialog); /// /// Hides the window. diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 8a1554d344..17a90eddfe 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -110,7 +110,9 @@ namespace Avalonia.Platform /// /// Sets the client size of the top level. /// - void Resize(Size clientSize); + /// The new client size. + /// The reason for the resize. + void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application); /// /// Sets the client size of the top level. diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index da7352b77f..1a11778db2 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -161,12 +161,9 @@ namespace Avalonia.Controls.Primitives protected override sealed Size ArrangeSetBounds(Size size) { - using (BeginAutoSizing()) - { - _positionerParameters.Size = size; - UpdatePosition(); - return ClientSize; - } + _positionerParameters.Size = size; + UpdatePosition(); + return ClientSize; } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 9fe844c8a6..ee12e358cf 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -377,11 +377,15 @@ namespace Avalonia.Controls LayoutManager?.Dispose(); } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// /// Handles a resize notification from . /// /// The new client size. - protected virtual void HandleResized(Size clientSize) + /// The reason for the resize. + protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; FrameSize = PlatformImpl.FrameSize; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ae314a33ce..0ca28ca196 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -244,7 +244,7 @@ namespace Avalonia.Controls impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; - this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); + this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); } @@ -258,6 +258,18 @@ namespace Avalonia.Controls /// /// Gets or sets a value indicating how the window will size itself to fit its content. /// + /// + /// If has a value other than , + /// is automatically set to + /// if a user resizes the window by using the resize grip or dragging the border. + /// + /// NOTE: Because of a limitation of X11, will be reset on X11 to + /// on any resize - including the resize that happens when + /// the window is first shown. This is because X11 resize notifications are asynchronous and + /// there is no way to know whether a resize came from the user or the layout system. To avoid + /// this, consider setting to false, which will disable user resizing + /// of the window. + /// public SizeToContent SizeToContent { get { return GetValue(SizeToContentProperty); } @@ -583,28 +595,23 @@ namespace Avalonia.Controls return; } - using (BeginAutoSizing()) - { - Renderer?.Stop(); + Renderer?.Stop(); - if (Owner is Window owner) - { - owner.RemoveChild(this); - } + if (Owner is Window owner) + { + owner.RemoveChild(this); + } - if (_children.Count > 0) + if (_children.Count > 0) + { + foreach (var child in _children.ToArray()) { - foreach (var child in _children.ToArray()) - { - child.child.Hide(); - } + child.child.Hide(); } - - Owner = null; - - PlatformImpl?.Hide(); } + Owner = null; + PlatformImpl?.Hide(); IsVisible = false; } @@ -675,29 +682,23 @@ namespace Avalonia.Controls if (initialSize != ClientSize) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(initialSize); - } + PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); - using (BeginAutoSizing()) + if (parent != null) { - if (parent != null) - { - PlatformImpl?.SetParent(parent.PlatformImpl); - } - - Owner = parent; - parent?.AddChild(this, false); - - SetWindowStartupLocation(Owner?.PlatformImpl); - - PlatformImpl?.Show(ShowActivated); - Renderer?.Start(); + PlatformImpl?.SetParent(parent.PlatformImpl); } + + Owner = parent; + parent?.AddChild(this, false); + + SetWindowStartupLocation(Owner?.PlatformImpl); + + PlatformImpl?.Show(ShowActivated, false); + Renderer?.Start(); OnOpened(EventArgs.Empty); } @@ -760,41 +761,34 @@ namespace Avalonia.Controls if (initialSize != ClientSize) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(initialSize); - } + PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); var result = new TaskCompletionSource(); - using (BeginAutoSizing()) - { - PlatformImpl?.SetParent(owner.PlatformImpl); - Owner = owner; - owner.AddChild(this, true); - - SetWindowStartupLocation(owner.PlatformImpl); - - PlatformImpl?.Show(ShowActivated); + PlatformImpl?.SetParent(owner.PlatformImpl); + Owner = owner; + owner.AddChild(this, true); - Renderer?.Start(); + SetWindowStartupLocation(owner.PlatformImpl); - Observable.FromEventPattern( - x => Closed += x, - x => Closed -= x) - .Take(1) - .Subscribe(_ => - { - owner.Activate(); - result.SetResult((TResult)(_dialogResult ?? default(TResult))); - }); + PlatformImpl?.Show(ShowActivated, true); - OnOpened(EventArgs.Empty); - } + Renderer?.Start(); + + Observable.FromEventPattern( + x => Closed += x, + x => Closed -= x) + .Take(1) + .Subscribe(_ => + { + owner.Activate(); + result.SetResult((TResult)(_dialogResult ?? default(TResult))); + }); + OnOpened(EventArgs.Empty); return result.Task; } @@ -937,11 +931,8 @@ namespace Avalonia.Controls protected sealed override Size ArrangeSetBounds(Size size) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(size); - return ClientSize; - } + PlatformImpl?.Resize(size, PlatformResizeReason.Layout); + return ClientSize; } protected sealed override void HandleClosed() @@ -958,18 +949,36 @@ namespace Avalonia.Controls Owner = null; } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// - protected sealed override void HandleResized(Size clientSize) + protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { - if (!AutoSizing) + if (ClientSize == clientSize) + return; + + var sizeToContent = SizeToContent; + + // If auto-sizing is enabled, and the resize came from a user resize (or the reason was + // unspecified) then turn off auto-resizing for any window dimension that is not equal + // to the requested size. + if (sizeToContent != SizeToContent.Manual && + CanResize && + reason == PlatformResizeReason.Unspecified || + reason == PlatformResizeReason.User) { - SizeToContent = SizeToContent.Manual; + if (clientSize.Width != ClientSize.Width) + sizeToContent &= ~SizeToContent.Width; + if (clientSize.Height != ClientSize.Height) + sizeToContent &= ~SizeToContent.Height; + SizeToContent = sizeToContent; } Width = clientSize.Width; Height = clientSize.Height; - base.HandleResized(clientSize); + base.HandleResized(clientSize, reason); } /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 2b31cef8bd..7cfc5f8be8 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -39,7 +39,6 @@ namespace Avalonia.Controls public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); - private int _autoSizing; private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -95,10 +94,8 @@ namespace Avalonia.Controls public Screens Screens { get; private set; } - /// - /// Whether an auto-size operation is in progress. - /// - protected bool AutoSizing => _autoSizing > 0; + [Obsolete("No longer used. Always returns false.")] + protected bool AutoSizing => false; /// /// Gets or sets the owner of the window. @@ -162,7 +159,7 @@ namespace Avalonia.Controls LayoutManager.ExecuteInitialLayoutPass(); _hasExecutedInitialLayoutPass = true; } - PlatformImpl?.Show(true); + PlatformImpl?.Show(true, false); Renderer?.Start(); OnOpened(EventArgs.Empty); } @@ -172,20 +169,9 @@ namespace Avalonia.Controls } } - /// - /// Begins an auto-resize operation. - /// - /// A disposable used to finish the operation. - /// - /// When an auto-resize operation is in progress any resize events received will not be - /// cause the new size to be written to the and - /// properties. - /// - protected IDisposable BeginAutoSizing() - { - ++_autoSizing; - return Disposable.Create(() => --_autoSizing); - } + + [Obsolete("No longer used. Has no effect.")] + protected IDisposable BeginAutoSizing() => Disposable.Empty; /// /// Ensures that the window is initialized. @@ -215,11 +201,15 @@ namespace Avalonia.Controls } } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// /// Handles a resize notification from . /// /// The new client size. - protected override void HandleResized(Size clientSize) + /// The reason for the resize. + protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; FrameSize = PlatformImpl.FrameSize; diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 787f44887f..5cae29cafd 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote ClientSize = new Size(1, 1); } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { } @@ -58,7 +58,7 @@ namespace Avalonia.DesignerSupport.Remote base.OnMessage(transport, obj); } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { _transport.Send(new RequestViewportResizeMessage { @@ -99,10 +99,6 @@ namespace Avalonia.DesignerSupport.Remote { } - public void ShowDialog(IWindowImpl parent) - { - } - public void SetSystemDecorations(SystemDecorations enabled) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index c8203686f9..9dcd4d8e87 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -27,7 +27,7 @@ namespace Avalonia.DesignerSupport.Remote public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Func Closing { get; set; } public Action Closed { get; set; } @@ -54,7 +54,7 @@ namespace Avalonia.DesignerSupport.Remote PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, (_, size, __) => { - Resize(size); + Resize(size, PlatformResizeReason.Unspecified); })); } @@ -78,7 +78,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { } @@ -98,7 +98,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 7f4b9face4..31315848d1 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -47,7 +47,7 @@ namespace Avalonia.Headless public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public IRenderer CreateRenderer(IRenderRoot root) @@ -76,7 +76,7 @@ namespace Avalonia.Headless public Action Closed { get; set; } public IMouseDevice MouseDevice { get; } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { if (activate) Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); @@ -108,7 +108,7 @@ namespace Avalonia.Headless public Action Activated { get; set; } public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB"); public Size MaxClientSize { get; } = new Size(1920, 1280); - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { // Emulate X11 behavior here if (IsPopup) @@ -126,7 +126,7 @@ namespace Avalonia.Headless if (ClientSize != clientSize) { ClientSize = clientSize; - Resized?.Invoke(clientSize); + Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified); } } @@ -148,11 +148,6 @@ namespace Avalonia.Headless } - public void ShowDialog(IWindowImpl parent) - { - Show(true); - } - public void SetSystemDecorations(bool enabled) { diff --git a/src/Avalonia.Input/TappedEventArgs.cs b/src/Avalonia.Input/TappedEventArgs.cs index 02add509cd..daaab70632 100644 --- a/src/Avalonia.Input/TappedEventArgs.cs +++ b/src/Avalonia.Input/TappedEventArgs.cs @@ -13,6 +13,10 @@ namespace Avalonia.Input this.lastPointerEventArgs = lastPointerEventArgs; } + public IPointer Pointer => lastPointerEventArgs.Pointer; + public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers; + public ulong Timestamp => lastPointerEventArgs.Timestamp; + public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); } } diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 77c0794d04..8084e06d28 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Native.Interop; using Avalonia.Platform; @@ -7,7 +8,7 @@ namespace Avalonia.Native { internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl { - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) { @@ -17,7 +18,7 @@ namespace Avalonia.Native public int TryShutdown() { if (ShutdownRequested is null) return 1; - var e = new CancelEventArgs(); + var e = new ShutdownRequestedEventArgs(); ShutdownRequested(this, e); return (!e.Cancel).AsComBool(); } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c36675afcd..4680a2af17 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Native private void MoveResize(PixelPoint position, Size size, double scaling) { Position = position; - Resize(size); + Resize(size, PlatformResizeReason.Layout); //TODO: We ignore the scaling override for now } @@ -60,14 +60,14 @@ namespace Avalonia.Native } } - public override void Show(bool activate) + public override void Show(bool activate, bool isDialog) { var parent = _parent; while (parent is PopupImpl p) parent = p._parent; if (parent is WindowImpl w) w.Native.TakeFocusFromChildren(); - base.Show(false); + base.Show(false, isDialog); } public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index ced9cea3a8..4a3baa2788 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -87,7 +87,7 @@ namespace Avalonia.Native var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) .FirstOrDefault(m => m.Bounds.Contains(Position)); - Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d)); + Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); } public Size ClientSize @@ -146,7 +146,7 @@ namespace Avalonia.Native public Action LostFocus { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action Closed { get; set; } public IMouseDevice MouseDevice => _mouse; public abstract IPopupImpl CreatePopup(); @@ -186,13 +186,13 @@ namespace Avalonia.Native _parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height)); } - void IAvnWindowBaseEvents.Resized(AvnSize* size) + void IAvnWindowBaseEvents.Resized(AvnSize* size, AvnPlatformResizeReason reason) { if (_parent?._native != null) { var s = new Size(size->Width, size->Height); _parent._savedLogicalSize = s; - _parent.Resized?.Invoke(s); + _parent.Resized?.Invoke(s, (PlatformResizeReason)reason); } } @@ -320,9 +320,9 @@ namespace Avalonia.Native } } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { - _native.Resize(clientSize.Width, clientSize.Height); + _native.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } public IRenderer CreateRenderer(IRenderRoot root) @@ -365,9 +365,9 @@ namespace Avalonia.Native } - public virtual void Show(bool activate) + public virtual void Show(bool activate, bool isDialog) { - _native.Show(activate.AsComBool()); + _native.Show(activate.AsComBool(), isDialog.AsComBool()); } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index d75d914044..70d85dacdd 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -400,6 +400,15 @@ enum AvnExtendClientAreaChromeHints AvnDefaultChrome = AvnPreferSystemChrome, } +enum AvnPlatformResizeReason +{ + ResizeUnspecified, + ResizeUser, + ResizeApplication, + ResizeLayout, + ResizeDpiChange, +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -430,7 +439,7 @@ interface IAvnString : IUnknown [uuid(e5aca675-02b7-4129-aa79-d6e417210bda)] interface IAvnWindowBase : IUnknown { - HRESULT Show(bool activate); + HRESULT Show(bool activate, bool isDialog); HRESULT Hide(); HRESULT Close(); HRESULT Activate(); @@ -438,7 +447,7 @@ interface IAvnWindowBase : IUnknown HRESULT GetFrameSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); - HRESULT Resize(double width, double height); + HRESULT Resize(double width, double height, AvnPlatformResizeReason reason); HRESULT Invalidate(AvnRect rect); HRESULT BeginMoveDrag(); HRESULT BeginResizeDrag(AvnWindowEdge edge); @@ -492,7 +501,7 @@ interface IAvnWindowBaseEvents : IUnknown void Closed(); void Activated(); void Deactivated(); - void Resized([const] AvnSize& size); + void Resized([const] AvnSize& size, AvnPlatformResizeReason reason); void PositionChanged(AvnPoint position); void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, diff --git a/src/Avalonia.Visuals/Media/DrawingGroup.cs b/src/Avalonia.Visuals/Media/DrawingGroup.cs index e581c8c553..eeb6318ebd 100644 --- a/src/Avalonia.Visuals/Media/DrawingGroup.cs +++ b/src/Avalonia.Visuals/Media/DrawingGroup.cs @@ -12,6 +12,12 @@ namespace Avalonia.Media public static readonly StyledProperty TransformProperty = AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty ClipGeometryProperty = + AvaloniaProperty.Register(nameof(ClipGeometry)); + + public static readonly StyledProperty OpacityMaskProperty = + AvaloniaProperty.Register(nameof(OpacityMask)); + public double Opacity { get => GetValue(OpacityProperty); @@ -24,6 +30,18 @@ namespace Avalonia.Media set => SetValue(TransformProperty, value); } + public Geometry ClipGeometry + { + get => GetValue(ClipGeometryProperty); + set => SetValue(ClipGeometryProperty, value); + } + + public IBrush OpacityMask + { + get => GetValue(OpacityMaskProperty); + set => SetValue(OpacityMaskProperty, value); + } + [Content] public AvaloniaList Children { get; } = new AvaloniaList(); @@ -31,6 +49,8 @@ namespace Avalonia.Media { using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushOpacity(Opacity)) + using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState)) + using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState)) { foreach (var drawing in Children) { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index bcb655245a..149d7fb9b2 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -336,7 +336,7 @@ namespace Avalonia.X11 public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } //TODO public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } @@ -510,7 +510,7 @@ namespace Avalonia.X11 UpdateImePosition(); if (changedSize && !updatedSizeViaScaling && !_popup) - Resized?.Invoke(ClientSize); + Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified); Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); }, DispatcherPriority.Layout); @@ -565,7 +565,7 @@ namespace Avalonia.X11 UpdateImePosition(); SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); if(!skipResize) - Resize(oldScaledSize, true); + Resize(oldScaledSize, true, PlatformResizeReason.DpiChange); return true; } @@ -616,7 +616,7 @@ namespace Avalonia.X11 { // Occurs once the window has been mapped, which is the earliest the extents // can be retrieved, so invoke event to force update of TopLevel.FrameSize. - Resized.Invoke(ClientSize); + Resized.Invoke(ClientSize, PlatformResizeReason.Unspecified); } if (atom == _x11.Atoms._NET_WM_STATE) @@ -839,7 +839,7 @@ namespace Avalonia.X11 XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle); } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { _wasMappedAtLeastOnce = true; XMapWindow(_x11.Display, _handle); @@ -862,19 +862,19 @@ namespace Avalonia.X11 } - public void Resize(Size clientSize) => Resize(clientSize, false); + public void Resize(Size clientSize, PlatformResizeReason reason) => Resize(clientSize, false, reason); public void Move(PixelPoint point) => Position = point; private void MoveResize(PixelPoint position, Size size, double scaling) { Move(position); _scalingOverride = scaling; UpdateScaling(true); - Resize(size, true); + Resize(size, true, PlatformResizeReason.Layout); } PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling)); - void Resize(Size clientSize, bool force) + void Resize(Size clientSize, bool force, PlatformResizeReason reason) { if (!force && clientSize == ClientSize) return; @@ -891,7 +891,7 @@ namespace Avalonia.X11 if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize)) { _realSize = pixelSize; - Resized?.Invoke(ClientSize); + Resized?.Invoke(ClientSize, reason); } } @@ -1024,15 +1024,22 @@ namespace Avalonia.X11 side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; BeginMoveResize(side, e); } - + public void SetTitle(string title) { - var data = Encoding.UTF8.GetBytes(title); - fixed (void* pdata = data) + if (string.IsNullOrEmpty(title)) { - XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8, - PropertyMode.Replace, pdata, data.Length); - XStoreName(_x11.Display, _handle, title); + XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME); + } + else + { + var data = Encoding.UTF8.GetBytes(title); + fixed (void* pdata = data) + { + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8, + PropertyMode.Replace, pdata, data.Length); + XStoreName(_x11.Display, _handle, title); + } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 5a1da9058a..b097e0917f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -69,7 +69,7 @@ namespace Avalonia.LinuxFramebuffer public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 73e46b9e13..d36db107e3 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -44,7 +44,7 @@ namespace Avalonia.Win32.Interop.Wpf ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); } - protected override void HandleResized(Size clientSize) + protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; LayoutManager.ExecuteLayoutPass(); @@ -114,7 +114,7 @@ namespace Avalonia.Win32.Interop.Wpf if (_finalSize == _previousSize) return finalSize; _previousSize = _finalSize; - _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize()); + _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), PlatformResizeReason.Unspecified); return base.ArrangeOverride(finalSize); } @@ -236,7 +236,7 @@ namespace Avalonia.Win32.Interop.Wpf Action ITopLevelImpl.Input { get; set; } //TODO Action ITopLevelImpl.Paint { get; set; } - Action ITopLevelImpl.Resized { get; set; } + Action ITopLevelImpl.Resized { get; set; } Action ITopLevelImpl.ScalingChanged { get; set; } Action ITopLevelImpl.TransparencyLevelChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index dd3fd1342c..38dc4a16dc 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -17,7 +17,7 @@ namespace Avalonia.Win32 [ThreadStatic] private static IntPtr s_parentHandle; - public override void Show(bool activate) + public override void Show(bool activate, bool isDialog) { // Popups are always shown non-activated. UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); @@ -135,7 +135,7 @@ namespace Avalonia.Win32 private void MoveResize(PixelPoint position, Size size, double scaling) { Move(position); - Resize(size); + Resize(size, PlatformResizeReason.Layout); //TODO: We ignore the scaling override for now } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 84e61ca007..10a6db0b57 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -6,18 +6,16 @@ using System.IO; using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; -using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.Utilities; -using Avalonia.Win32; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -65,7 +63,7 @@ namespace Avalonia namespace Avalonia.Win32 { - class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader + class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -122,7 +120,8 @@ namespace Avalonia.Win32 }) .Bind().ToConstant(s_instance) .Bind().ToConstant(new NonPumpingSyncContext.HelperImpl()) - .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); + .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) + .Bind().ToConstant(s_instance); Win32GlManager.Initialize(); @@ -207,6 +206,8 @@ namespace Avalonia.Win32 public event Action Signaled; + public event EventHandler ShutdownRequested; + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -214,6 +215,22 @@ namespace Avalonia.Win32 { Signaled?.Invoke(null); } + + if(msg == (uint)WindowsMessage.WM_QUERYENDSESSION) + { + if (ShutdownRequested != null) + { + var e = new ShutdownRequestedEventArgs(); + + ShutdownRequested(this, e); + + if(e.Cancel) + { + return IntPtr.Zero; + } + } + } + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } @@ -253,7 +270,7 @@ namespace Avalonia.Win32 public IWindowImpl CreateEmbeddableWindow() { var embedded = new EmbeddedWindowImpl(); - embedded.Show(true); + embedded.Show(true, false); return embedded; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8e755a33bc..d163b3d068 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -2,9 +2,10 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.Controls; -using Avalonia.Controls.Platform; +using Avalonia.Controls.Remote; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Platform; using Avalonia.Win32.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -18,7 +19,6 @@ namespace Avalonia.Win32 { const double wheelDelta = 120.0; uint timestamp = unchecked((uint)GetMessageTime()); - RawInputEventArgs e = null; var shouldTakeFocus = false; @@ -94,14 +94,19 @@ namespace Avalonia.Win32 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); + + using (SetResizeReason(PlatformResizeReason.DpiChange)) + { + 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; } @@ -364,6 +369,11 @@ namespace Avalonia.Win32 return IntPtr.Zero; } + + case WindowsMessage.WM_ENTERSIZEMOVE: + _resizeReason = PlatformResizeReason.User; + break; + case WindowsMessage.WM_SIZE: { using(NonPumpingSyncContext.Use()) @@ -379,7 +389,7 @@ namespace Avalonia.Win32 size == SizeCommand.Maximized)) { var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); - Resized(clientSize / RenderScaling); + Resized(clientSize / RenderScaling, _resizeReason); } var windowState = size == SizeCommand.Maximized ? @@ -403,6 +413,10 @@ namespace Avalonia.Win32 return IntPtr.Zero; } + case WindowsMessage.WM_EXITSIZEMOVE: + _resizeReason = PlatformResizeReason.Unspecified; + break; + case WindowsMessage.WM_MOVE: { PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 646a6f5739..f20dae3e50 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -53,6 +53,7 @@ namespace Avalonia.Win32 private double _extendTitleBarHint = -1; private bool _isUsingComposition; private IBlurHost _blurHost; + private PlatformResizeReason _resizeReason; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -160,7 +161,7 @@ namespace Avalonia.Win32 public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } @@ -482,7 +483,7 @@ namespace Avalonia.Win32 : new ImmediateRenderer(root); } - public void Resize(Size value) + public void Resize(Size value, PlatformResizeReason reason) { int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientHeight = (int)(value.Height * RenderScaling); @@ -494,6 +495,7 @@ namespace Avalonia.Win32 { GetWindowRect(_hwnd, out var windowRect); + using var scope = SetResizeReason(reason); SetWindowPos( _hwnd, IntPtr.Zero, @@ -585,7 +587,7 @@ namespace Avalonia.Win32 _shown = false; } - public virtual void Show(bool activate) + public virtual void Show(bool activate, bool isDialog) { SetParent(_parent); ShowWindow(_showWindowState, activate); @@ -930,7 +932,7 @@ namespace Avalonia.Win32 _offScreenMargin = new Thickness(); _extendedMargins = new Thickness(); - Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling)); + Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout); } if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && @@ -1135,7 +1137,7 @@ namespace Avalonia.Win32 SetParent(null); if (shown) - Show(activated); + Show(activated, false); } } else @@ -1311,6 +1313,13 @@ namespace Avalonia.Win32 /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); + private ResizeReasonScope SetResizeReason(PlatformResizeReason reason) + { + var old = _resizeReason; + _resizeReason = reason; + return new ResizeReasonScope(this, old); + } + private struct SavedWindowInfo { public WindowStyles Style { get; set; } @@ -1325,5 +1334,19 @@ namespace Avalonia.Win32 public SystemDecorations Decorations; public bool IsFullScreen; } + + private struct ResizeReasonScope : IDisposable + { + private readonly WindowImpl _owner; + private readonly PlatformResizeReason _restore; + + public ResizeReasonScope(WindowImpl owner, PlatformResizeReason restore) + { + _owner = owner; + _restore = restore; + } + + public void Dispose() => _owner._resizeReason = _restore; + } } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 0371a7759a..5bb2f64879 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -96,7 +96,7 @@ namespace Avalonia.iOS public IEnumerable Surfaces { get; set; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } public Action Closed { get; set; } @@ -127,7 +127,7 @@ namespace Avalonia.iOS public override void LayoutSubviews() { - _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize); + _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, PlatformResizeReason.Layout); base.LayoutSubviews(); } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 3ed0af39a2..ba01f3db40 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -230,7 +230,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var window = PreparedWindow(); @@ -268,7 +268,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(c.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Never); - popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1)); } } @@ -277,7 +277,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var window = PreparedWindow(); @@ -306,7 +306,7 @@ namespace Avalonia.Controls.UnitTests Assert.False(c.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Exactly(1)); - popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1)); } } @@ -315,7 +315,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -336,7 +336,7 @@ namespace Avalonia.Controls.UnitTests _mouse.Up(target); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Once); + popupImpl.Verify(x => x.Show(true, false), Times.Once); popupImpl.Verify(x => x.Hide(), Times.Once); } } @@ -346,7 +346,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -368,7 +368,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(sut.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Once); - popupImpl.Verify(x => x.Show(true), Times.Exactly(2)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(2)); } } @@ -413,7 +413,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); bool eventCalled = false; var sut = new ContextMenu(); @@ -429,7 +429,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Never); + popupImpl.Verify(x => x.Show(true, false), Times.Never); } } @@ -533,7 +533,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); bool eventCalled = false; @@ -560,7 +560,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.True(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Once()); + popupImpl.Verify(x => x.Show(true, false), Times.Once()); popupImpl.Verify(x => x.Hide(), Times.Never); } } diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 38713834c3..f7a3bdea1c 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests ++raised; }; - lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs()); + lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs()); Assert.Equal(1, raised); Assert.Equal(new[] { window }, lifetime.Windows); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 6b30aed257..9c2d760733 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests // The user has resized the window, so we can no longer auto-size. var target = new TestTopLevel(impl.Object); - impl.Object.Resized(new Size(100, 200)); + impl.Object.Resized(new Size(100, 200), PlatformResizeReason.Unspecified); Assert.Equal(100, target.Width); Assert.Equal(200, target.Height); diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 8109b037c5..1b4214e0c7 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -137,7 +137,7 @@ namespace Avalonia.Controls.UnitTests var target = new TestWindowBase(windowImpl.Object); target.IsVisible = true; - windowImpl.Verify(x => x.Show(true)); + windowImpl.Verify(x => x.Show(true, false)); } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 6b9921d83d..eb128ef038 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -663,10 +663,11 @@ namespace Avalonia.Controls.UnitTests var clientSize = new Size(200, 200); var maxClientSize = new Size(480, 480); - windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(size => + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) + .Callback((size, reason) => { clientSize = size.Constrain(maxClientSize); - windowImpl.Object.Resized?.Invoke(clientSize); + windowImpl.Object.Resized?.Invoke(clientSize, reason); }); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); @@ -739,6 +740,36 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void SizeToContent_Should_Not_Be_Lost_On_Scaling_Change() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Canvas + { + Width = 209, + Height = 117, + }; + + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = child + }; + + Show(target); + + // Size before and after DPI change is a real-world example, with size after DPI + // change coming from Win32 WM_DPICHANGED. + target.PlatformImpl.ScalingChanged(1.5); + target.PlatformImpl.Resized( + new Size(210.66666666666666, 118.66666666666667), + PlatformResizeReason.DpiChange); + + Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent); + } + } + [Fact] public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight() { @@ -791,8 +822,91 @@ namespace Avalonia.Controls.UnitTests target.LayoutManager.ExecuteLayoutPass(); var windowImpl = Mock.Get(target.PlatformImpl); - windowImpl.Verify(x => x.Resize(new Size(410, 800))); + windowImpl.Verify(x => x.Resize(new Size(410, 800), PlatformResizeReason.Application)); + Assert.Equal(410, target.Width); + } + } + + + [Fact] + public void User_Resize_Of_Window_Width_Should_Reset_SizeToContent() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(410, 800), PlatformResizeReason.User); + Assert.Equal(410, target.Width); + Assert.Equal(800, target.Height); + Assert.Equal(SizeToContent.Height, target.SizeToContent); + } + } + + [Fact] + public void User_Resize_Of_Window_Height_Should_Reset_SizeToContent() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(400, 810), PlatformResizeReason.User); + + Assert.Equal(400, target.Width); + Assert.Equal(810, target.Height); + Assert.Equal(SizeToContent.Width, target.SizeToContent); + } + } + + [Fact] + public void Window_Resize_Should_Not_Reset_SizeToContent_If_CanResize_False() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + CanResize = false, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(410, 810), PlatformResizeReason.Unspecified); + + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent); } } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..bc003537f4 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -52,13 +52,14 @@ namespace Avalonia.UnitTests windowImpl.Object.PositionChanged?.Invoke(x); }); - windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(x => + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) + .Callback((x, y) => { clientSize = x.Constrain(s_screenSize); - windowImpl.Object.Resized?.Invoke(clientSize); + windowImpl.Object.Resized?.Invoke(clientSize, y); }); - windowImpl.Setup(x => x.Show(true)).Callback(() => + windowImpl.Setup(x => x.Show(true, It.IsAny())).Callback(() => { windowImpl.Object.Activated?.Invoke(); }); @@ -75,7 +76,7 @@ namespace Avalonia.UnitTests { clientSize = size.Constrain(s_screenSize); popupImpl.Object.PositionChanged?.Invoke(pos); - popupImpl.Object.Resized?.Invoke(clientSize); + popupImpl.Object.Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified); }); var positioner = new ManagedPopupPositioner(positionerHelper);