diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 9cc9fc9523..88cdf4d9de 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -2,6 +2,7 @@ #include "AvnString.h" @interface AvnAppDelegate : NSObject -(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events; +-(void) releaseEvents; @end NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; @@ -15,6 +16,11 @@ ComPtr _events; return self; } +- (void)releaseEvents +{ + _events = nil; +} + - (void)applicationWillFinishLaunching:(NSNotification *)notification { if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy) @@ -105,6 +111,18 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDeleg } } +extern void ReleaseAvnAppEvents() +{ + NSApplication* app = [AvnApplication sharedApplication]; + id delegate = [app delegate]; + if ([delegate isMemberOfClass:[AvnAppDelegate class]]) + { + AvnAppDelegate* avnDelegate = delegate; + [avnDelegate releaseEvents]; + [app setDelegate:nil]; + } +} + HRESULT AvnApplicationCommands::HideApp() { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 5cf1b94a2f..672525c64a 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -38,6 +38,7 @@ extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate); +extern void ReleaseAvnAppEvents(); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSPoint ToNSPoint (AvnPoint p); extern NSRect ToNSRect (AvnRect r); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 1c7e2cf25a..3fddb72529 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -197,6 +197,14 @@ class AvaloniaNative : public ComSingleObject - - - - - - - diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs index b4a1f02f09..fb8e9488a7 100644 --- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs @@ -89,6 +89,7 @@ public class Rotate3DTransition: PageSlide { Easing = SlideInEasing, Duration = Duration, + FillMode = FillMode.Forward, Children = { CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1), diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index b3f41eb420..833cd4b034 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -60,6 +60,8 @@ namespace Avalonia } /// + /// The type of the values held by the . + /// The type of the value returned by the . /// /// /// A method which is executed to convert each property value to . @@ -71,6 +73,15 @@ namespace Avalonia converter ?? throw new ArgumentNullException(nameof(converter))); } + /// + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property, Func converter) + { + return new AvaloniaPropertyObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter ?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable for an . /// @@ -92,6 +103,15 @@ namespace Avalonia property ?? throw new ArgumentNullException(nameof(property))); } + /// + public static IObservable> GetBindingObservable(this AvaloniaObject o, AvaloniaProperty property, Func converter) + { + return new AvaloniaPropertyBindingObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable for an . /// diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index bd3d2b5171..4ff8e7cfef 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -473,18 +473,31 @@ namespace Avalonia.Controls.Presenters } Viewport = finalSize; + Extent = ComputeExtent(finalSize); + _isAnchorElementDirty = true; + return finalSize; + } + + private Size ComputeExtent(Size viewportSize) + { var childMargin = Child!.Margin; + if (Child.UseLayoutRounding) { var scale = LayoutHelper.GetLayoutScale(Child); childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale, scale); } - Extent = Child!.Bounds.Size.Inflate(childMargin); - _isAnchorElementDirty = true; + var extent = Child!.Bounds.Size.Inflate(childMargin); - return finalSize; + if (MathUtilities.AreClose(extent.Width, viewportSize.Width, LayoutHelper.LayoutEpsilon)) + extent = extent.WithWidth(viewportSize.Width); + + if (MathUtilities.AreClose(extent.Height, viewportSize.Height, LayoutHelper.LayoutEpsilon)) + extent = extent.WithHeight(viewportSize.Height); + + return extent; } private void OnScrollGesture(object? sender, ScrollGestureEventArgs e) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ce4fc8dd5c..dc58947994 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -169,6 +169,7 @@ namespace Avalonia.Controls private readonly Size _maxPlatformClientSize; private bool _shown; private bool _showingAsDialog; + private bool _wasShownBefore; /// /// Initializes static members of the class. @@ -718,6 +719,7 @@ namespace Avalonia.Controls StartRendering(); PlatformImpl?.Show(ShowActivated, false); OnOpened(EventArgs.Empty); + _wasShownBefore = true; } } @@ -871,6 +873,11 @@ namespace Avalonia.Controls private void SetWindowStartupLocation(Window? owner = null) { + if (_wasShownBefore == true) + { + return; + } + var startupLocation = WindowStartupLocation; if (startupLocation == WindowStartupLocation.CenterOwner && diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 37e5e41faf..b7c58cded9 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -3,9 +3,7 @@ using System.Runtime.InteropServices; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.MicroCom; using Avalonia.Native.Interop; -using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; @@ -163,6 +161,14 @@ namespace Avalonia.Native Compositor = new Compositor(_platformGraphics, true); + + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + } + + private void OnProcessExit(object? sender, EventArgs e) + { + AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; + _factory.Dispose(); } public ITrayIconImpl CreateTrayIcon() diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json index 12757fd7a0..fd4c6a9539 100644 --- a/src/Browser/Avalonia.Browser/webapp/package-lock.json +++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json @@ -3296,9 +3296,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5560,9 +5560,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "wrappy": { diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index c7ea5c1b69..30628b1af8 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -275,6 +275,37 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(new Size(203.2, 203.2), target.Extent); } + [Fact] + public void Extent_Should_Be_Rounded_To_Viewport_When_Close() + { + var root = new TestRoot + { + LayoutScaling = 1.75, + UseLayoutRounding = true + }; + + var target = new ScrollContentPresenter + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Content = new Border + { + Width = 164.57142857142858, + Height = 164.57142857142858, + Margin = new Thickness(6) + } + }; + + root.Child = target; + target.UpdateChild(); + target.Measure(new Size(1000, 1000)); + target.Arrange(new Rect(0, 0, 1000, 1000)); + + Assert.Equal(new Size(176.00000000000003, 176.00000000000003), target.Child!.DesiredSize); + Assert.Equal(new Size(176, 176), target.Viewport); + Assert.Equal(new Size(176, 176), target.Extent); + } + [Fact] public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False() { diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index cd45a3d920..186b2f6836 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -513,6 +513,41 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Window_Should_Not_Be_Centered_When_WindowStartupLocation_Is_CenterScreen_And_Window_Is_Hidden_And_Shown() + { + var screen1 = new Mock(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + + var screens = new Mock(); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + + + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.Screen).Returns(screens.Object); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(windowImpl.Object) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + + window.Show(); + + var expected = new PixelPoint(150, 400); + window.Position = expected; + + window.IsVisible = false; + window.IsVisible = true; + + Assert.Equal(expected, window.Position); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() {