Browse Source

Merge pull request #6254 from AvaloniaUI/fixes/4739-sizetocontent-dpi-change

Fix autosizing when DPI changed
pull/6340/head
Dan Walmsley 5 years ago
committed by GitHub
parent
commit
9dbcc98a87
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      native/Avalonia.Native/src/OSX/window.h
  2. 21
      native/Avalonia.Native/src/OSX/window.mm
  3. 4
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  4. 13
      src/Avalonia.Controls/ApiCompatBaseline.txt
  5. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  6. 37
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  7. 4
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  8. 9
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  9. 6
      src/Avalonia.Controls/TopLevel.cs
  10. 145
      src/Avalonia.Controls/Window.cs
  11. 30
      src/Avalonia.Controls/WindowBase.cs
  12. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  13. 6
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  14. 6
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  15. 2
      src/Avalonia.Native/PopupImpl.cs
  16. 12
      src/Avalonia.Native/WindowImplBase.cs
  17. 13
      src/Avalonia.Native/avn.idl
  18. 16
      src/Avalonia.X11/X11Window.cs
  19. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  20. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  21. 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  22. 36
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  23. 29
      src/Windows/Avalonia.Win32/WindowImpl.cs
  24. 4
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  25. 2
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  26. 120
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  27. 7
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

21
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
@ -51,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 */

21
native/Avalonia.Native/src/OSX/window.mm

@ -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}];
@ -1385,6 +1386,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget;
AvnPlatformResizeReason _resizeReason;
}
- (void)onClosed
@ -1496,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);
}
}
@ -1995,6 +1998,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
- (AvnPlatformResizeReason)getResizeReason
{
return _resizeReason;
}
- (void)setResizeReason:(AvnPlatformResizeReason)reason
{
_resizeReason = reason;
}
@end
@ -2382,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;

4
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -67,7 +67,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> 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

13
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -33,15 +33,26 @@ MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stret
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs> 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<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Action<Avalonia.Size> 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<Avalonia.Size>)' 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.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Action<Avalonia.Size> 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<Avalonia.Size, Avalonia.Platform.PlatformResizeReason>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action<Avalonia.Size>)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action<Avalonia.Size>)' 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.
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.
Total Issues: 45
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

4
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<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

37
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
{
/// <summary>
/// Describes the reason for a <see cref="ITopLevelImpl.Resized"/> message.
/// </summary>
public enum PlatformResizeReason
{
/// <summary>
/// The resize reason is unknown or unspecified.
/// </summary>
Unspecified,
/// <summary>
/// The resize was due to the user resizing the window, for example by dragging the
/// window frame.
/// </summary>
User,
/// <summary>
/// The resize was initiated by the application, for example by setting one of the sizing-
/// related properties on <see cref="Window"/> such as <see cref="Layoutable.Width"/> or
/// <see cref="Layoutable.Height"/>.
/// </summary>
Application,
/// <summary>
/// The resize was initiated by the layout system.
/// </summary>
Layout,
/// <summary>
/// The resize was due to a change in DPI.
/// </summary>
DpiChange,
}
/// <summary>
/// Defines a platform-specific top-level window implementation.
/// </summary>
@ -57,7 +92,7 @@ namespace Avalonia.Platform
/// <summary>
/// Gets or sets a method called when the toplevel is resized.
/// </summary>
Action<Size> Resized { get; set; }
Action<Size, PlatformResizeReason> Resized { get; set; }
/// <summary>
/// Gets or sets a method called when the toplevel's scaling changes.

4
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -110,7 +110,9 @@ namespace Avalonia.Platform
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <param name="clientSize">The new client size.</param>
/// <param name="reason">The reason for the resize.</param>
void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application);
/// <summary>
/// Sets the client size of the top level.

9
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;
}
}
}

6
src/Avalonia.Controls/TopLevel.cs

@ -376,11 +376,15 @@ namespace Avalonia.Controls
LayoutManager?.Dispose();
}
[Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
/// <summary>
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
/// <param name="clientSize">The new client size.</param>
protected virtual void HandleResized(Size clientSize)
/// <param name="reason">The reason for the resize.</param>
protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;

145
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
/// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content.
/// </summary>
/// <remarks>
/// If <see cref="SizeToContent"/> has a value other than <see cref="SizeToContent.Manual"/>,
/// <see cref="SizeToContent"/> is automatically set to <see cref="SizeToContent.Manual"/>
/// if a user resizes the window by using the resize grip or dragging the border.
///
/// NOTE: Because of a limitation of X11, <see cref="SizeToContent"/> will be reset on X11 to
/// <see cref="SizeToContent.Manual"/> 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 <see cref="CanResize"/> to false, which will disable user resizing
/// of the window.
/// </remarks>
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, false);
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<TResult>();
using (BeginAutoSizing())
{
PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner;
owner.AddChild(this, true);
SetWindowStartupLocation(owner.PlatformImpl);
PlatformImpl?.Show(ShowActivated, true);
PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner;
owner.AddChild(this, true);
Renderer?.Start();
SetWindowStartupLocation(owner.PlatformImpl);
Observable.FromEventPattern<EventHandler, EventArgs>(
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<EventHandler, EventArgs>(
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);
/// <inheritdoc/>
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);
}
/// <summary>

30
src/Avalonia.Controls/WindowBase.cs

@ -39,7 +39,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<WindowBase, bool>(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; }
/// <summary>
/// Whether an auto-size operation is in progress.
/// </summary>
protected bool AutoSizing => _autoSizing > 0;
[Obsolete("No longer used. Always returns false.")]
protected bool AutoSizing => false;
/// <summary>
/// Gets or sets the owner of the window.
@ -172,20 +169,9 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Begins an auto-resize operation.
/// </summary>
/// <returns>A disposable used to finish the operation.</returns>
/// <remarks>
/// When an auto-resize operation is in progress any resize events received will not be
/// cause the new size to be written to the <see cref="Layoutable.Width"/> and
/// <see cref="Layoutable.Height"/> properties.
/// </remarks>
protected IDisposable BeginAutoSizing()
{
++_autoSizing;
return Disposable.Create(() => --_autoSizing);
}
[Obsolete("No longer used. Has no effect.")]
protected IDisposable BeginAutoSizing() => Disposable.Empty;
/// <summary>
/// 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);
/// <summary>
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
/// <param name="clientSize">The new client size.</param>
protected override void HandleResized(Size clientSize)
/// <param name="reason">The reason for the resize.</param>
protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -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
{

6
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -27,7 +27,7 @@ namespace Avalonia.DesignerSupport.Remote
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Func<bool> 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);
}));
}
@ -98,7 +98,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void Resize(Size clientSize)
public void Resize(Size clientSize, PlatformResizeReason reason)
{
}

6
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -47,7 +47,7 @@ namespace Avalonia.Headless
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root)
@ -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);
}
}

2
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
}

12
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<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> 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)

13
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
{
@ -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,

16
src/Avalonia.X11/X11Window.cs

@ -336,7 +336,7 @@ namespace Avalonia.X11
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
//TODO
public Action<double> 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)
@ -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);
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -69,7 +69,7 @@ namespace Avalonia.LinuxFramebuffer
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

6
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<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO
Action<Rect> ITopLevelImpl.Paint { get; set; }
Action<Size> ITopLevelImpl.Resized { get; set; }
Action<Size, PlatformResizeReason> ITopLevelImpl.Resized { get; set; }
Action<double> ITopLevelImpl.ScalingChanged { get; set; }
Action<WindowTransparencyLevel> ITopLevelImpl.TransparencyLevelChanged { get; set; }

2
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -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
}

36
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<RECT>(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),

29
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<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> 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,
@ -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) &&
@ -1311,6 +1313,13 @@ namespace Avalonia.Win32
/// <inheritdoc/>
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;
}
}
}

4
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -96,7 +96,7 @@ namespace Avalonia.iOS
public IEnumerable<object> Surfaces { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> 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();
}

2
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);

120
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<Size>())).Callback<Size>(size =>
windowImpl.Setup(x => x.Resize(It.IsAny<Size>(), It.IsAny<PlatformResizeReason>()))
.Callback<Size, PlatformResizeReason>((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);
}
}

7
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -52,10 +52,11 @@ namespace Avalonia.UnitTests
windowImpl.Object.PositionChanged?.Invoke(x);
});
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(x =>
windowImpl.Setup(x => x.Resize(It.IsAny<Size>(), It.IsAny<PlatformResizeReason>()))
.Callback<Size, PlatformResizeReason>((x, y) =>
{
clientSize = x.Constrain(s_screenSize);
windowImpl.Object.Resized?.Invoke(clientSize);
windowImpl.Object.Resized?.Invoke(clientSize, y);
});
windowImpl.Setup(x => x.Show(true, It.IsAny<bool>())).Callback(() =>
@ -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);

Loading…
Cancel
Save