Browse Source

Fix auto-scaling on win32.

- Adds a "resize reason" to platform `Resized` events
- Which is used by the auto-sizing code to determine whether to reset `SizeToContent`
- Also other improvements to the reset logic for `SizeToContent`:
  - Don't reset it if the size hasn't changed
  - Don't reset it if `CanResize == false`
  - Only reset the dimensions whose size has changed
- Obsolete the `BeginAutoSizing`/`AutoSizing` feature in `WindowBase`
pull/6254/head
Steven Kirk 5 years ago
parent
commit
ba369a9059
  1. 21
      native/Avalonia.Native/src/OSX/window.h
  2. 21
      native/Avalonia.Native/src/OSX/window.mm
  3. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  4. 37
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  5. 4
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  6. 9
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  7. 3
      src/Avalonia.Controls/TopLevel.cs
  8. 142
      src/Avalonia.Controls/Window.cs
  9. 27
      src/Avalonia.Controls/WindowBase.cs
  10. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  11. 6
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  12. 6
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  13. 2
      src/Avalonia.Native/PopupImpl.cs
  14. 12
      src/Avalonia.Native/WindowImplBase.cs
  15. 13
      src/Avalonia.Native/avn.idl
  16. 16
      src/Avalonia.X11/X11Window.cs
  17. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  18. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  19. 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  20. 36
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  21. 29
      src/Windows/Avalonia.Win32/WindowImpl.cs
  22. 2
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  23. 2
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  24. 120
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  25. 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) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed; -(void) onClosed;
-(AvnPixelSize) getPixelSize; -(AvnPixelSize) getPixelSize;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
@end @end
@interface AutoFitContentView : NSView @interface AutoFitContentView : NSView
@ -50,4 +52,23 @@ struct IWindowStateChanged
virtual AvnWindowState WindowState () = 0; 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 */ #endif /* window_h */

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

@ -275,9 +275,10 @@ public:
} }
} }
virtual HRESULT Resize(double x, double y) override virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
{ {
START_COM_CALL; START_COM_CALL;
auto resizeBlock = ResizeScope(View, reason);
@autoreleasepool @autoreleasepool
{ {
@ -306,7 +307,7 @@ public:
if(!_shown) if(!_shown)
{ {
BaseEvents->Resized(AvnSize{x,y}); BaseEvents->Resized(AvnSize{x,y}, reason);
} }
[StandardContainer setFrameSize:NSSize{x,y}]; [StandardContainer setFrameSize:NSSize{x,y}];
@ -1357,6 +1358,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _lastKeyHandled; bool _lastKeyHandled;
AvnPixelSize _lastPixelSize; AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget; NSObject<IRenderTarget>* _renderTarget;
AvnPlatformResizeReason _resizeReason;
} }
- (void)onClosed - (void)onClosed
@ -1468,7 +1470,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_lastPixelSize.Height = (int)fsize.height; _lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget]; [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);
} }
} }
@ -1967,6 +1970,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
} }
- (AvnPlatformResizeReason)getResizeReason
{
return _resizeReason;
}
- (void)setResizeReason:(AvnPlatformResizeReason)reason
{
_resizeReason = reason;
}
@end @end
@ -2373,7 +2386,7 @@ protected:
return NSWindowStyleMaskBorderless; return NSWindowStyleMaskBorderless;
} }
virtual HRESULT Resize(double x, double y) override virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
{ {
@autoreleasepool @autoreleasepool
{ {

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
set set
{ {
_clientSize = value; _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<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { 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<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { 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.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Rendering; using Avalonia.Rendering;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Avalonia.Platform 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> /// <summary>
/// Defines a platform-specific top-level window implementation. /// Defines a platform-specific top-level window implementation.
/// </summary> /// </summary>
@ -57,7 +92,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Gets or sets a method called when the toplevel is resized. /// Gets or sets a method called when the toplevel is resized.
/// </summary> /// </summary>
Action<Size> Resized { get; set; } Action<Size, PlatformResizeReason> Resized { get; set; }
/// <summary> /// <summary>
/// Gets or sets a method called when the toplevel's scaling changes. /// 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> /// <summary>
/// Sets the client size of the top level. /// Sets the client size of the top level.
/// </summary> /// </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> /// <summary>
/// Sets the client size of the top level. /// 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) protected override sealed Size ArrangeSetBounds(Size size)
{ {
using (BeginAutoSizing()) _positionerParameters.Size = size;
{ UpdatePosition();
_positionerParameters.Size = size; return ClientSize;
UpdatePosition();
return ClientSize;
}
} }
} }
} }

3
src/Avalonia.Controls/TopLevel.cs

@ -380,7 +380,8 @@ namespace Avalonia.Controls
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>. /// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary> /// </summary>
/// <param name="clientSize">The new client size.</param> /// <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; ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize; FrameSize = PlatformImpl.FrameSize;

142
src/Avalonia.Controls/Window.cs

@ -244,7 +244,7 @@ namespace Avalonia.Controls
impl.WindowStateChanged = HandleWindowStateChanged; impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; 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); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
} }
@ -258,6 +258,18 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content. /// Gets or sets a value indicating how the window will size itself to fit its content.
/// </summary> /// </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 public SizeToContent SizeToContent
{ {
get { return GetValue(SizeToContentProperty); } get { return GetValue(SizeToContentProperty); }
@ -583,28 +595,23 @@ namespace Avalonia.Controls
return; return;
} }
using (BeginAutoSizing()) Renderer?.Stop();
{
Renderer?.Stop();
if (Owner is Window owner) if (Owner is Window owner)
{ {
owner.RemoveChild(this); 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; IsVisible = false;
} }
@ -675,29 +682,23 @@ namespace Avalonia.Controls
if (initialSize != ClientSize) if (initialSize != ClientSize)
{ {
using (BeginAutoSizing()) PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
{
PlatformImpl?.Resize(initialSize);
}
} }
LayoutManager.ExecuteInitialLayoutPass(); LayoutManager.ExecuteInitialLayoutPass();
using (BeginAutoSizing()) if (parent != null)
{ {
if (parent != null) PlatformImpl?.SetParent(parent.PlatformImpl);
{
PlatformImpl?.SetParent(parent.PlatformImpl);
}
Owner = parent;
parent?.AddChild(this, false);
SetWindowStartupLocation(Owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
Renderer?.Start();
} }
Owner = parent;
parent?.AddChild(this, false);
SetWindowStartupLocation(Owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
Renderer?.Start();
OnOpened(EventArgs.Empty); OnOpened(EventArgs.Empty);
} }
@ -760,41 +761,34 @@ namespace Avalonia.Controls
if (initialSize != ClientSize) if (initialSize != ClientSize)
{ {
using (BeginAutoSizing()) PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
{
PlatformImpl?.Resize(initialSize);
}
} }
LayoutManager.ExecuteInitialLayoutPass(); LayoutManager.ExecuteInitialLayoutPass();
var result = new TaskCompletionSource<TResult>(); var result = new TaskCompletionSource<TResult>();
using (BeginAutoSizing()) PlatformImpl?.SetParent(owner.PlatformImpl);
{ Owner = owner;
PlatformImpl?.SetParent(owner.PlatformImpl); owner.AddChild(this, true);
Owner = owner;
owner.AddChild(this, true);
SetWindowStartupLocation(owner.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
Renderer?.Start(); SetWindowStartupLocation(owner.PlatformImpl);
Observable.FromEventPattern<EventHandler, EventArgs>( PlatformImpl?.Show(ShowActivated);
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
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; return result.Task;
} }
@ -937,11 +931,8 @@ namespace Avalonia.Controls
protected sealed override Size ArrangeSetBounds(Size size) protected sealed override Size ArrangeSetBounds(Size size)
{ {
using (BeginAutoSizing()) PlatformImpl?.Resize(size, PlatformResizeReason.Layout);
{ return ClientSize;
PlatformImpl?.Resize(size);
return ClientSize;
}
} }
protected sealed override void HandleClosed() protected sealed override void HandleClosed()
@ -959,17 +950,32 @@ namespace Avalonia.Controls
} }
/// <inheritdoc/> /// <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; Width = clientSize.Width;
Height = clientSize.Height; Height = clientSize.Height;
base.HandleResized(clientSize); base.HandleResized(clientSize, reason);
} }
/// <summary> /// <summary>

27
src/Avalonia.Controls/WindowBase.cs

@ -39,7 +39,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> TopmostProperty = public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost)); AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost));
private int _autoSizing;
private bool _hasExecutedInitialLayoutPass; private bool _hasExecutedInitialLayoutPass;
private bool _isActive; private bool _isActive;
private bool _ignoreVisibilityChange; private bool _ignoreVisibilityChange;
@ -95,10 +94,8 @@ namespace Avalonia.Controls
public Screens Screens { get; private set; } public Screens Screens { get; private set; }
/// <summary> [Obsolete("No longer used. Always returns false.")]
/// Whether an auto-size operation is in progress. protected bool AutoSizing => false;
/// </summary>
protected bool AutoSizing => _autoSizing > 0;
/// <summary> /// <summary>
/// Gets or sets the owner of the window. /// Gets or sets the owner of the window.
@ -172,20 +169,9 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Begins an auto-resize operation. [Obsolete("No longer used. Has no effect.")]
/// </summary> protected IDisposable BeginAutoSizing() => Disposable.Empty;
/// <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);
}
/// <summary> /// <summary>
/// Ensures that the window is initialized. /// Ensures that the window is initialized.
@ -219,7 +205,8 @@ namespace Avalonia.Controls
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>. /// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary> /// </summary>
/// <param name="clientSize">The new client size.</param> /// <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; ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize; FrameSize = PlatformImpl.FrameSize;

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

@ -58,7 +58,7 @@ namespace Avalonia.DesignerSupport.Remote
base.OnMessage(transport, obj); base.OnMessage(transport, obj);
} }
public void Resize(Size clientSize) public void Resize(Size clientSize, PlatformResizeReason reason)
{ {
_transport.Send(new RequestViewportResizeMessage _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 IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { 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<double> ScalingChanged { get; set; }
public Func<bool> Closing { get; set; } public Func<bool> Closing { get; set; }
public Action Closed { get; set; } public Action Closed { get; set; }
@ -54,7 +54,7 @@ namespace Avalonia.DesignerSupport.Remote
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent,
(_, size, __) => (_, 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 IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { 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<double> ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) public IRenderer CreateRenderer(IRenderRoot root)
@ -108,7 +108,7 @@ namespace Avalonia.Headless
public Action Activated { get; set; } public Action Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB"); public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280); public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize) public void Resize(Size clientSize, PlatformResizeReason reason)
{ {
// Emulate X11 behavior here // Emulate X11 behavior here
if (IsPopup) if (IsPopup)
@ -126,7 +126,7 @@ namespace Avalonia.Headless
if (ClientSize != clientSize) if (ClientSize != clientSize)
{ {
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) private void MoveResize(PixelPoint position, Size size, double scaling)
{ {
Position = position; Position = position;
Resize(size); Resize(size, PlatformResizeReason.Layout);
//TODO: We ignore the scaling override for now //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) var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position)); .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 public Size ClientSize
@ -146,7 +146,7 @@ namespace Avalonia.Native
public Action LostFocus { get; set; } public Action LostFocus { get; set; }
public Action<Rect> Paint { 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 Action Closed { get; set; }
public IMouseDevice MouseDevice => _mouse; public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup(); public abstract IPopupImpl CreatePopup();
@ -186,13 +186,13 @@ namespace Avalonia.Native
_parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height)); _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) if (_parent?._native != null)
{ {
var s = new Size(size->Width, size->Height); var s = new Size(size->Width, size->Height);
_parent._savedLogicalSize = s; _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) public IRenderer CreateRenderer(IRenderRoot root)

13
src/Avalonia.Native/avn.idl

@ -400,6 +400,15 @@ enum AvnExtendClientAreaChromeHints
AvnDefaultChrome = AvnPreferSystemChrome, AvnDefaultChrome = AvnPreferSystemChrome,
} }
enum AvnPlatformResizeReason
{
ResizeUnspecified,
ResizeUser,
ResizeApplication,
ResizeLayout,
ResizeDpiChange,
}
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
interface IAvaloniaNativeFactory : IUnknown interface IAvaloniaNativeFactory : IUnknown
{ {
@ -438,7 +447,7 @@ interface IAvnWindowBase : IUnknown
HRESULT GetFrameSize(AvnSize*ret); HRESULT GetFrameSize(AvnSize*ret);
HRESULT GetScaling(double*ret); HRESULT GetScaling(double*ret);
HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); 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 Invalidate(AvnRect rect);
HRESULT BeginMoveDrag(); HRESULT BeginMoveDrag();
HRESULT BeginResizeDrag(AvnWindowEdge edge); HRESULT BeginResizeDrag(AvnWindowEdge edge);
@ -492,7 +501,7 @@ interface IAvnWindowBaseEvents : IUnknown
void Closed(); void Closed();
void Activated(); void Activated();
void Deactivated(); void Deactivated();
void Resized([const] AvnSize& size); void Resized([const] AvnSize& size, AvnPlatformResizeReason reason);
void PositionChanged(AvnPoint position); void PositionChanged(AvnPoint position);
void RawMouseEvent(AvnRawMouseEventType type, void RawMouseEvent(AvnRawMouseEventType type,
uint timeStamp, uint timeStamp,

16
src/Avalonia.X11/X11Window.cs

@ -336,7 +336,7 @@ namespace Avalonia.X11
public IEnumerable<object> Surfaces { get; } public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; } public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; } public Action<Size, PlatformResizeReason> Resized { get; set; }
//TODO //TODO
public Action<double> ScalingChanged { get; set; } public Action<double> ScalingChanged { get; set; }
public Action Deactivated { get; set; } public Action Deactivated { get; set; }
@ -510,7 +510,7 @@ namespace Avalonia.X11
UpdateImePosition(); UpdateImePosition();
if (changedSize && !updatedSizeViaScaling && !_popup) if (changedSize && !updatedSizeViaScaling && !_popup)
Resized?.Invoke(ClientSize); Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}, DispatcherPriority.Layout); }, DispatcherPriority.Layout);
@ -565,7 +565,7 @@ namespace Avalonia.X11
UpdateImePosition(); UpdateImePosition();
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize) if(!skipResize)
Resize(oldScaledSize, true); Resize(oldScaledSize, true, PlatformResizeReason.DpiChange);
return true; return true;
} }
@ -616,7 +616,7 @@ namespace Avalonia.X11
{ {
// Occurs once the window has been mapped, which is the earliest the extents // Occurs once the window has been mapped, which is the earliest the extents
// can be retrieved, so invoke event to force update of TopLevel.FrameSize. // 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) 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; public void Move(PixelPoint point) => Position = point;
private void MoveResize(PixelPoint position, Size size, double scaling) private void MoveResize(PixelPoint position, Size size, double scaling)
{ {
Move(position); Move(position);
_scalingOverride = scaling; _scalingOverride = scaling;
UpdateScaling(true); UpdateScaling(true);
Resize(size, true); Resize(size, true, PlatformResizeReason.Layout);
} }
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling)); 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) if (!force && clientSize == ClientSize)
return; return;
@ -891,7 +891,7 @@ namespace Avalonia.X11
if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize)) if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize))
{ {
_realSize = pixelSize; _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 IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { 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<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { 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(); ((FrameworkElement)PlatformImpl)?.InvalidateMeasure();
} }
protected override void HandleResized(Size clientSize) protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{ {
ClientSize = clientSize; ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();
@ -114,7 +114,7 @@ namespace Avalonia.Win32.Interop.Wpf
if (_finalSize == _previousSize) if (_finalSize == _previousSize)
return finalSize; return finalSize;
_previousSize = _finalSize; _previousSize = _finalSize;
_ttl.Resized?.Invoke(finalSize.ToAvaloniaSize()); _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), PlatformResizeReason.Unspecified);
return base.ArrangeOverride(finalSize); return base.ArrangeOverride(finalSize);
} }
@ -236,7 +236,7 @@ namespace Avalonia.Win32.Interop.Wpf
Action<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO Action<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO
Action<Rect> ITopLevelImpl.Paint { get; set; } 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<double> ITopLevelImpl.ScalingChanged { get; set; }
Action<WindowTransparencyLevel> ITopLevelImpl.TransparencyLevelChanged { 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) private void MoveResize(PixelPoint position, Size size, double scaling)
{ {
Move(position); Move(position);
Resize(size); Resize(size, PlatformResizeReason.Layout);
//TODO: We ignore the scaling override for now //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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Remote;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -18,7 +19,6 @@ namespace Avalonia.Win32
{ {
const double wheelDelta = 120.0; const double wheelDelta = 120.0;
uint timestamp = unchecked((uint)GetMessageTime()); uint timestamp = unchecked((uint)GetMessageTime());
RawInputEventArgs e = null; RawInputEventArgs e = null;
var shouldTakeFocus = false; var shouldTakeFocus = false;
@ -94,14 +94,19 @@ namespace Avalonia.Win32
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam); var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
_scaling = dpi / 96.0; _scaling = dpi / 96.0;
ScalingChanged?.Invoke(_scaling); ScalingChanged?.Invoke(_scaling);
SetWindowPos(hWnd,
IntPtr.Zero, using (SetResizeReason(PlatformResizeReason.DpiChange))
newDisplayRect.left, {
newDisplayRect.top, SetWindowPos(hWnd,
newDisplayRect.right - newDisplayRect.left, IntPtr.Zero,
newDisplayRect.bottom - newDisplayRect.top, newDisplayRect.left,
SetWindowPosFlags.SWP_NOZORDER | newDisplayRect.top,
SetWindowPosFlags.SWP_NOACTIVATE); newDisplayRect.right - newDisplayRect.left,
newDisplayRect.bottom - newDisplayRect.top,
SetWindowPosFlags.SWP_NOZORDER |
SetWindowPosFlags.SWP_NOACTIVATE);
}
return IntPtr.Zero; return IntPtr.Zero;
} }
@ -364,6 +369,11 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
} }
case WindowsMessage.WM_ENTERSIZEMOVE:
_resizeReason = PlatformResizeReason.User;
break;
case WindowsMessage.WM_SIZE: case WindowsMessage.WM_SIZE:
{ {
using(NonPumpingSyncContext.Use()) using(NonPumpingSyncContext.Use())
@ -379,7 +389,7 @@ namespace Avalonia.Win32
size == SizeCommand.Maximized)) size == SizeCommand.Maximized))
{ {
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / RenderScaling); Resized(clientSize / RenderScaling, _resizeReason);
} }
var windowState = size == SizeCommand.Maximized ? var windowState = size == SizeCommand.Maximized ?
@ -403,6 +413,10 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
} }
case WindowsMessage.WM_EXITSIZEMOVE:
_resizeReason = PlatformResizeReason.Unspecified;
break;
case WindowsMessage.WM_MOVE: case WindowsMessage.WM_MOVE:
{ {
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), 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 double _extendTitleBarHint = -1;
private bool _isUsingComposition; private bool _isUsingComposition;
private IBlurHost _blurHost; private IBlurHost _blurHost;
private PlatformResizeReason _resizeReason;
#if USE_MANAGED_DRAG #if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag; private readonly ManagedWindowResizeDragHelper _managedDrag;
@ -160,7 +161,7 @@ namespace Avalonia.Win32
public Action<Rect> Paint { 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<double> ScalingChanged { get; set; }
@ -482,7 +483,7 @@ namespace Avalonia.Win32
: new ImmediateRenderer(root); : new ImmediateRenderer(root);
} }
public void Resize(Size value) public void Resize(Size value, PlatformResizeReason reason)
{ {
int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * RenderScaling); int requestedClientHeight = (int)(value.Height * RenderScaling);
@ -494,6 +495,7 @@ namespace Avalonia.Win32
{ {
GetWindowRect(_hwnd, out var windowRect); GetWindowRect(_hwnd, out var windowRect);
using var scope = SetResizeReason(reason);
SetWindowPos( SetWindowPos(
_hwnd, _hwnd,
IntPtr.Zero, IntPtr.Zero,
@ -930,7 +932,7 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness(); _offScreenMargin = new Thickness();
_extendedMargins = 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) && if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
@ -1311,6 +1313,13 @@ namespace Avalonia.Win32
/// <inheritdoc/> /// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); 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 private struct SavedWindowInfo
{ {
public WindowStyles Style { get; set; } public WindowStyles Style { get; set; }
@ -1325,5 +1334,19 @@ namespace Avalonia.Win32
public SystemDecorations Decorations; public SystemDecorations Decorations;
public bool IsFullScreen; 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;
}
} }
} }

2
src/iOS/Avalonia.iOS/AvaloniaView.cs

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

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. // The user has resized the window, so we can no longer auto-size.
var target = new TestTopLevel(impl.Object); 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(100, target.Width);
Assert.Equal(200, target.Height); 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 clientSize = new Size(200, 200);
var maxClientSize = new Size(480, 480); 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); clientSize = size.Constrain(maxClientSize);
windowImpl.Object.Resized?.Invoke(clientSize); windowImpl.Object.Resized?.Invoke(clientSize, reason);
}); });
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); 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] [Fact]
public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight() public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight()
{ {
@ -791,8 +822,91 @@ namespace Avalonia.Controls.UnitTests
target.LayoutManager.ExecuteLayoutPass(); target.LayoutManager.ExecuteLayoutPass();
var windowImpl = Mock.Get(target.PlatformImpl); 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(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.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); 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)).Callback(() =>
@ -75,7 +76,7 @@ namespace Avalonia.UnitTests
{ {
clientSize = size.Constrain(s_screenSize); clientSize = size.Constrain(s_screenSize);
popupImpl.Object.PositionChanged?.Invoke(pos); popupImpl.Object.PositionChanged?.Invoke(pos);
popupImpl.Object.Resized?.Invoke(clientSize); popupImpl.Object.Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified);
}); });
var positioner = new ManagedPopupPositioner(positionerHelper); var positioner = new ManagedPopupPositioner(positionerHelper);

Loading…
Cancel
Save