From 8314eb9dddc21223af84c9bc251767d06cae430d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 19 Sep 2022 20:31:24 +0200 Subject: [PATCH 01/10] Add spring object --- src/Avalonia.Base/Animation/Spring.cs | 143 ++++++++++++++++++ src/Avalonia.Base/Animation/SpringSolver.cs | 70 +++++++++ .../Animation/SpringTypeConverter.cs | 21 +++ 3 files changed, 234 insertions(+) create mode 100644 src/Avalonia.Base/Animation/Spring.cs create mode 100644 src/Avalonia.Base/Animation/SpringSolver.cs create mode 100644 src/Avalonia.Base/Animation/SpringTypeConverter.cs diff --git a/src/Avalonia.Base/Animation/Spring.cs b/src/Avalonia.Base/Animation/Spring.cs new file mode 100644 index 0000000000..6e8bd204a7 --- /dev/null +++ b/src/Avalonia.Base/Animation/Spring.cs @@ -0,0 +1,143 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using Avalonia; +using Avalonia.Utilities; + +namespace Avalonia.Animation; + +/// +/// Determines how an animation is used based on spring formula. +/// +[TypeConverter(typeof(SpringTypeConverter))] +public class Spring : AvaloniaObject +{ + private SpringSolver _springSolver; + private double _mass; + private double _stiffness; + private double _damping; + private double _initialVelocity; + private bool _isDirty; + + /// + /// Create a . + /// + public Spring() + { + _mass = 0.0; + _stiffness = 0.0; + _damping = 0.0; + _initialVelocity = 0.0; + _isDirty = true; + } + + /// + /// Create a with the given parameters. + /// + /// The spring mass. + /// The spring stiffness. + /// The spring damping. + /// The spring initial velocity. + public Spring(double mass, double stiffness, double damping, double initialVelocity) + { + _mass = mass; + _stiffness = stiffness; + _damping = damping; + _initialVelocity = initialVelocity; + _isDirty = true; + } + + /// + /// Parse a from a string. The string needs to contain 4 values in it. + /// + /// string with 4 values in it + /// culture of the string + /// Thrown if the string does not have 4 values + /// A with the appropriate values set + public static Spring Parse(string value, CultureInfo? culture) + { + if (culture is null) + { + culture = CultureInfo.InvariantCulture; + } + + using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid Spring string: \"{value}\"."); + return new Spring(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); + } + + /// + /// The spring mass. + /// + public double Mass + { + get => _mass; + set + { + _mass = value; + _isDirty = true; + } + } + + /// + /// The spring stiffness. + /// + public double Stiffness + { + get => _stiffness; + set + { + _stiffness = value; + _isDirty = true; + } + } + + /// + /// The spring damping. + /// + public double Damping + { + get => _damping; + set + { + _damping = value; + _isDirty = true; + } + } + + /// + /// The spring initial velocity. + /// + public double InitialVelocity + { + get => _initialVelocity; + set + { + _initialVelocity = value; + _isDirty = true; + } + } + + /// + /// Calculates spring progress from a linear progress. + /// + /// the linear progress + /// The spring progress + public double GetSpringProgress(double linearProgress) + { + if (_isDirty) + { + Build(); + } + + return _springSolver.Solve(linearProgress); + } + + /// + /// Create cached spring solver. + /// + private void Build() + { + _springSolver = new SpringSolver(_mass, _stiffness, _damping, _initialVelocity); + _isDirty = false; + } +} diff --git a/src/Avalonia.Base/Animation/SpringSolver.cs b/src/Avalonia.Base/Animation/SpringSolver.cs new file mode 100644 index 0000000000..de58224880 --- /dev/null +++ b/src/Avalonia.Base/Animation/SpringSolver.cs @@ -0,0 +1,70 @@ +// Ported from: +// https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/SpringSolver.h +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; + +namespace Avalonia.Animation; + +internal struct SpringSolver +{ + private double m_w0; + private double m_zeta; + private double m_wd; + private double m_A; + private double m_B; + + public SpringSolver(double mass, double stiffness, double damping, double initialVelocity) + { + m_w0 = Math.Sqrt(stiffness / mass); + m_zeta = damping / (2 * Math.Sqrt(stiffness * mass)); + + if (m_zeta < 1) { + // Under-damped. + m_wd = m_w0 * Math.Sqrt(1 - m_zeta * m_zeta); + m_A = 1; + m_B = (m_zeta * m_w0 + -initialVelocity) / m_wd; + } else { + // Critically damped (ignoring over-damped case for now). + m_A = 1; + m_B = -initialVelocity + m_w0; + m_wd = 0; + } + } + + public readonly double Solve(double t) + { + if (m_zeta < 1) { + // Under-damped + t = Math.Exp(-t * m_zeta * m_w0) * (m_A * Math.Cos(m_wd * t) + m_B * Math.Sin(m_wd * t)); + } else { + // Critically damped (ignoring over-damped case for now). + t = (m_A + m_B * t) * Math.Exp(-t * m_w0); + } + + // Map range from [1..0] to [0..1]. + return 1 - t; + } +} diff --git a/src/Avalonia.Base/Animation/SpringTypeConverter.cs b/src/Avalonia.Base/Animation/SpringTypeConverter.cs new file mode 100644 index 0000000000..cfcf4c4a14 --- /dev/null +++ b/src/Avalonia.Base/Animation/SpringTypeConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Avalonia.Animation; + +/// +/// Converts string values to values. +/// +public class SpringTypeConverter : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + return Spring.Parse((string)value, CultureInfo.InvariantCulture); + } +} From 78f5855b870e7fff311fe0ecbe3d70964887ae08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 19 Sep 2022 20:31:32 +0200 Subject: [PATCH 02/10] Add spring easing --- .../Animation/Easings/SpringEasing.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/Avalonia.Base/Animation/Easings/SpringEasing.cs diff --git a/src/Avalonia.Base/Animation/Easings/SpringEasing.cs b/src/Avalonia.Base/Animation/Easings/SpringEasing.cs new file mode 100644 index 0000000000..70e74c639f --- /dev/null +++ b/src/Avalonia.Base/Animation/Easings/SpringEasing.cs @@ -0,0 +1,80 @@ +namespace Avalonia.Animation.Easings; + +/// +/// Eases a value using a user-defined spring formula. +/// +public class SpringEasing : Easing +{ + private readonly Spring _internalSpring; + + /// + /// The spring mass. + /// + public double Mass + { + get => _internalSpring.Mass; + set + { + _internalSpring.Mass = value; + } + } + + /// + /// The spring stiffness. + /// + public double Stiffness + { + get => _internalSpring.Stiffness; + set + { + _internalSpring.Stiffness = value; + } + } + + /// + /// The spring damping. + /// + public double Damping + { + get => _internalSpring.Damping; + set + { + _internalSpring.Damping = value; + } + } + + /// + /// The spring initial velocity. + /// + public double InitialVelocity + { + get => _internalSpring.InitialVelocity; + set + { + _internalSpring.InitialVelocity = value; + } + } + + public SpringEasing(double mass = 0d, double stiffness = 0d, double damping = 0d, double initialVelocity = 0d) + { + _internalSpring = new Spring(); + + Mass = mass; + Stiffness = stiffness; + Damping = damping; + InitialVelocity = initialVelocity; + } + + public SpringEasing(Spring keySpline) + { + _internalSpring = keySpline; + } + + public SpringEasing() + { + _internalSpring = new Spring(); + } + + /// + public override double Ease(double progress) => _internalSpring.GetSpringProgress(progress); +} From d6deda6414a011b01959db71dcfe04728b69c167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 19 Sep 2022 20:31:53 +0200 Subject: [PATCH 03/10] Add spring animation demo page --- samples/RenderDemo/MainWindow.xaml | 3 ++ .../Pages/SpringAnimationsPage.xaml | 35 +++++++++++++++++++ .../Pages/SpringAnimationsPage.xaml.cs | 17 +++++++++ 3 files changed, 55 insertions(+) create mode 100644 samples/RenderDemo/Pages/SpringAnimationsPage.xaml create mode 100644 samples/RenderDemo/Pages/SpringAnimationsPage.xaml.cs diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index e6e62f86ed..823a0fbbef 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -39,6 +39,9 @@ + + + diff --git a/samples/RenderDemo/Pages/SpringAnimationsPage.xaml b/samples/RenderDemo/Pages/SpringAnimationsPage.xaml new file mode 100644 index 0000000000..26d572bb3f --- /dev/null +++ b/samples/RenderDemo/Pages/SpringAnimationsPage.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + diff --git a/samples/RenderDemo/Pages/SpringAnimationsPage.xaml.cs b/samples/RenderDemo/Pages/SpringAnimationsPage.xaml.cs new file mode 100644 index 0000000000..78d3df2233 --- /dev/null +++ b/samples/RenderDemo/Pages/SpringAnimationsPage.xaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace RenderDemo.Pages; + +public class SpringAnimationsPage : UserControl +{ + public SpringAnimationsPage() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} From 24427206cce84d4f2a903147123ab917497ac2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 19 Sep 2022 20:47:59 +0200 Subject: [PATCH 04/10] Add spring unit tests --- .../Animation/SpringTests.cs | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs diff --git a/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs b/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs new file mode 100644 index 0000000000..47c0e48033 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs @@ -0,0 +1,118 @@ +using System; +using Avalonia.Animation; +using Avalonia.Animation.Easings; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Styling; +using Xunit; + +namespace Avalonia.Base.UnitTests.Animation; + +public class SpringTests +{ + [Theory] + [InlineData("1,2 3,4")] + public void Can_Parse_Spring_Via_TypeConverter(string input) + { + var conv = new SpringTypeConverter(); + + var spring = (Spring)conv.ConvertFrom(input); + + Assert.Equal(1, spring.Mass); + Assert.Equal(2, spring.Stiffness); + Assert.Equal(3, spring.Damping); + Assert.Equal(4, spring.InitialVelocity); + } + + [Theory] + [InlineData("1,2F,3,4")] + [InlineData("Foo,Bar,Fee,Buzz")] + public void Can_Handle_Invalid_String_Via_TypeConverter(string input) + { + var conv = new SpringTypeConverter(); + + Assert.ThrowsAny(() => (Spring)conv.ConvertFrom(input)); + } + + [Fact] + public void SplineEasing_Can_Be_Mutated() + { + var easing = new SpringEasing(1, 1, 1, 0); + + Assert.Equal(0, easing.Ease(0)); + Assert.Equal(0.34029984660829826, easing.Ease(1)); + + easing.Mass = 2; + easing.Stiffness = 2; + easing.Damping = 2; + easing.InitialVelocity = 1; + + Assert.NotEqual(0.05136985716812037, easing.Ease(0.5)); + } + + [Fact] + public void Check_SpringEasing_Handled_properly() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(RotateTransform.AngleProperty, -2.5d), + }, + KeyTime = TimeSpan.FromSeconds(0) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(RotateTransform.AngleProperty, 2.5d), + }, + KeyTime = TimeSpan.FromSeconds(5) + }; + + var animation = new Avalonia.Animation.Animation() + { + Duration = TimeSpan.FromSeconds(5), + Children = + { + keyframe1, + keyframe2 + }, + IterationCount = new IterationCount(5), + PlaybackDirection = PlaybackDirection.Alternate, + Easing = new SpringEasing(1, 10, 1, 0) + }; + + var rotateTransform = new RotateTransform(-2.5); + var rect = new Rectangle() + { + RenderTransform = rotateTransform + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(rect, clock); + + clock.Step(TimeSpan.Zero); + Assert.Equal(rotateTransform.Angle, -2.5); + clock.Step(TimeSpan.FromSeconds(5)); + Assert.Equal(rotateTransform.Angle, 5.522828945000075); + + var tolerance = 0.01; + clock.Step(TimeSpan.Parse("00:00:10.0153932")); + var expected = -2.499763294237805; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:11.2655407")); + expected = -1.1011448950348934; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:12.6158773")); + expected = 2.1264981706749007; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:14.6495256")); + expected = 5.4337608446234782; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + } +} From ff3ac1b0b4f9508a7a5348dfd6f5c29cbc12eff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 19 Sep 2022 20:50:59 +0200 Subject: [PATCH 05/10] Move to Avalonia.Utilities --- src/Avalonia.Base/{Animation => Utilities}/SpringSolver.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/Avalonia.Base/{Animation => Utilities}/SpringSolver.cs (98%) diff --git a/src/Avalonia.Base/Animation/SpringSolver.cs b/src/Avalonia.Base/Utilities/SpringSolver.cs similarity index 98% rename from src/Avalonia.Base/Animation/SpringSolver.cs rename to src/Avalonia.Base/Utilities/SpringSolver.cs index de58224880..c0ad7a9952 100644 --- a/src/Avalonia.Base/Animation/SpringSolver.cs +++ b/src/Avalonia.Base/Utilities/SpringSolver.cs @@ -24,9 +24,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ + using System; -namespace Avalonia.Animation; +namespace Avalonia.Utilities; internal struct SpringSolver { From a17d3edfabb8b6307f7ca9ca864ec751e101b804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Sep 2022 09:46:40 +0200 Subject: [PATCH 06/10] Do not inherit from AvaloniaObject --- src/Avalonia.Base/Animation/Spring.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Animation/Spring.cs b/src/Avalonia.Base/Animation/Spring.cs index 6e8bd204a7..03ada9196d 100644 --- a/src/Avalonia.Base/Animation/Spring.cs +++ b/src/Avalonia.Base/Animation/Spring.cs @@ -10,7 +10,7 @@ namespace Avalonia.Animation; /// Determines how an animation is used based on spring formula. /// [TypeConverter(typeof(SpringTypeConverter))] -public class Spring : AvaloniaObject +public class Spring { private SpringSolver _springSolver; private double _mass; From 6b36bb495a4bd8b340422bd3da1307f98d6c2226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Sep 2022 09:53:32 +0200 Subject: [PATCH 07/10] Refactor spring solver --- src/Avalonia.Base/Utilities/SpringSolver.cs | 51 +++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Utilities/SpringSolver.cs b/src/Avalonia.Base/Utilities/SpringSolver.cs index c0ad7a9952..6a9e69d221 100644 --- a/src/Avalonia.Base/Utilities/SpringSolver.cs +++ b/src/Avalonia.Base/Utilities/SpringSolver.cs @@ -37,10 +37,55 @@ internal struct SpringSolver private double m_A; private double m_B; - public SpringSolver(double mass, double stiffness, double damping, double initialVelocity) + /// + /// + /// + /// The time period. + /// The damping ratio. + /// + public SpringSolver(TimeSpan period, double zeta, double initialVelocity) + : this( + 2 * Math.PI / period.TotalSeconds, + zeta, + initialVelocity) { - m_w0 = Math.Sqrt(stiffness / mass); - m_zeta = damping / (2 * Math.Sqrt(stiffness * mass)); + // T is period + // T = 2 * PI * sqrt(m / k) + } + + /// + /// + /// + /// The mass of the oscillating body. + /// The stiffness of the oscillated body (spring constant). + /// The actual damping. + /// The initial velocity. + public SpringSolver(double m, double k, double c, double initialVelocity) + : this( + Math.Sqrt(k / m), // ωn + c / (2 * Math.Sqrt(k * m)), // c / Cc + initialVelocity) + { + // Cc is critical damping coefficient + // Cc = 2 * Sqrt(k * m) + // Cc = 2 * m * wn + // Cc = 2 * m * Sqrt(k / m) + + // ζ is damping ratio (Greek letter zeta) + // ζ = m_zeta = c / Cc + } + + /// + /// + /// + /// The the natural frequency of the system [rad/s]. + /// The damping ratio. + /// + public SpringSolver(double ωn, double zeta, double initialVelocity) + { + // ωn = sqrt(k / m) + m_w0 = ωn; + m_zeta = zeta; if (m_zeta < 1) { // Under-damped. From c732a8245621dfb4ccca4b6de73af687cb8a7b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Sep 2022 10:04:42 +0200 Subject: [PATCH 08/10] Update comments --- src/Avalonia.Base/Utilities/SpringSolver.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Utilities/SpringSolver.cs b/src/Avalonia.Base/Utilities/SpringSolver.cs index 6a9e69d221..d067872378 100644 --- a/src/Avalonia.Base/Utilities/SpringSolver.cs +++ b/src/Avalonia.Base/Utilities/SpringSolver.cs @@ -49,8 +49,11 @@ internal struct SpringSolver zeta, initialVelocity) { - // T is period - // T = 2 * PI * sqrt(m / k) + // T is time period [s] + // T = (2*PI / sqrt(k)) * sqrt(m) + + // ωn is natural frequency of the system [Hz] [1/s] + // ωn = 2*PI / T } /// @@ -66,6 +69,9 @@ internal struct SpringSolver c / (2 * Math.Sqrt(k * m)), // c / Cc initialVelocity) { + // ωn is natural frequency of the system [Hz] [1/s] + // ωn = sqrt(k / m) + // Cc is critical damping coefficient // Cc = 2 * Sqrt(k * m) // Cc = 2 * m * wn @@ -83,7 +89,6 @@ internal struct SpringSolver /// public SpringSolver(double ωn, double zeta, double initialVelocity) { - // ωn = sqrt(k / m) m_w0 = ωn; m_zeta = zeta; From 47a6b5efdb532da78766872f6bcaf3194a1509b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Sep 2022 10:14:00 +0200 Subject: [PATCH 09/10] Fix encoding --- src/Avalonia.Base/Utilities/SpringSolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Utilities/SpringSolver.cs b/src/Avalonia.Base/Utilities/SpringSolver.cs index d067872378..ead73e9b1b 100644 --- a/src/Avalonia.Base/Utilities/SpringSolver.cs +++ b/src/Avalonia.Base/Utilities/SpringSolver.cs @@ -1,4 +1,4 @@ -// Ported from: +// Ported from: // https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/SpringSolver.h /* * Copyright (C) 2016 Apple Inc. All rights reserved. From a09db713563feb9eb748df5b0147e09f07166cdb Mon Sep 17 00:00:00 2001 From: Herman Kirshin Date: Wed, 21 Sep 2022 01:26:42 +0300 Subject: [PATCH 10/10] #8261 - terminating WinUIComposition loop on process exit to avoid crashes --- .../WinRT/Composition/WinUICompositorConnection.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 84c3cc5c51..9a6bd9572a 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -120,10 +120,14 @@ namespace Avalonia.Win32.WinRT.Composition private void RunLoop() { + var cts = new CancellationTokenSource(); + AppDomain.CurrentDomain.ProcessExit += (sender, args) => + cts.Cancel(); + using (var act = _compositor5.RequestCommitAsync()) act.SetCompleted(new RunLoopHandler(this)); - while (true) + while (!cts.IsCancellationRequested) { UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0); lock (_pumpLock)