From ce92286112282e5cead7fd5f9fc40590c4bb46cc Mon Sep 17 00:00:00 2001 From: JaggerJo Date: Mon, 22 Jul 2019 00:21:43 +0200 Subject: [PATCH 01/39] add basic operations as static methods and use them in the declared operators. - Dot - Cross - Normalize - Divide - Multiply - Add - Subtract - Negate --- src/Avalonia.Visuals/Vector.cs | 115 +++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 2f1690184d..f185682dc0 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -65,9 +65,7 @@ namespace Avalonia /// Second vector /// The dot product public static double operator *(Vector a, Vector b) - { - return a.X * b.X + a.Y * b.Y; - } + => Dot(a, b); /// /// Scales a vector. @@ -76,9 +74,7 @@ namespace Avalonia /// The scaling factor. /// The scaled vector. public static Vector operator *(Vector vector, double scale) - { - return new Vector(vector._x * scale, vector._y * scale); - } + => Multiply(vector, scale); /// /// Scales a vector. @@ -87,9 +83,7 @@ namespace Avalonia /// The divisor. /// The scaled vector. public static Vector operator /(Vector vector, double scale) - { - return new Vector(vector._x / scale, vector._y / scale); - } + => Divide(vector, scale); /// /// Length of the vector @@ -102,9 +96,7 @@ namespace Avalonia /// The vector. /// The negated vector. public static Vector operator -(Vector a) - { - return new Vector(-a._x, -a._y); - } + => Negate(a); /// /// Adds two vectors. @@ -113,9 +105,7 @@ namespace Avalonia /// The second vector. /// A vector that is the result of the addition. public static Vector operator +(Vector a, Vector b) - { - return new Vector(a._x + b._x, a._y + b._y); - } + => Add(a, b); /// /// Subtracts two vectors. @@ -124,9 +114,7 @@ namespace Avalonia /// The second vector. /// A vector that is the result of the subtraction. public static Vector operator -(Vector a, Vector b) - { - return new Vector(a._x - b._x, a._y - b._y); - } + => Subtract(a, b); /// /// Check if two vectors are equal (bitwise). @@ -155,7 +143,8 @@ namespace Avalonia public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(null, obj)) + return false; return obj is Vector vector && Equals(vector); } @@ -206,5 +195,93 @@ namespace Avalonia { return new Vector(_x, y); } + + /// + /// Returns the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product. + public static double Dot(Vector a, Vector b) + => a._x * b._x + a._y * b._y; + + /// + /// Returns the cross product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The cross product. + public static double Cross(Vector a, Vector b) + => a._x * b._y - a._y * b._x; + + /// + /// Normalizes the given vector. + /// + /// The vector + /// The normalized vector. + public static Vector Normalize(Vector vector) + => Divide(vector, vector.Length); + + /// + /// Divides the first vector by the second. + /// + /// The first vector. + /// The second vector. + /// The scaled vector. + public static Vector Divide(Vector a, Vector b) + => new Vector(a._x / b._x, a._y / b._y); + + /// + /// Divides the vector by the given scalar. + /// + /// The vector + /// The scalar value + /// The scaled vector. + public static Vector Divide(Vector vector, double scalar) + => new Vector(vector._x / scalar, vector._y / scalar); + + /// + /// Multiplies the first vector by the second. + /// + /// The first vector. + /// The second vector. + /// The scaled vector. + public static Vector Multiply(Vector a, Vector b) + => new Vector(a._x * b._x, a._y * b._y); + + /// + /// Multiplies the vector by the given scalar. + /// + /// The vector + /// The scalar value + /// The scaled vector. + public static Vector Multiply(Vector vector, double scalar) + => new Vector(vector._x * scalar, vector._y * scalar); + + /// + /// Adds the second to the first vector + /// + /// The first vector. + /// The second vector. + /// The summed vector. + public static Vector Add(Vector a, Vector b) + => new Vector(a._x + b._x, a._y + b._y); + + /// + /// Subtracts the second from the first vector + /// + /// The first vector. + /// The second vector. + /// The difference vector. + public static Vector Subtract(Vector a, Vector b) + => new Vector(a._x - b._x, a._y - b._y); + + /// + /// Negates the vector + /// + /// The vector to negate. + /// The scaled vector. + public static Vector Negate(Vector vector) + => new Vector(-vector._x, -vector._y); } } From 7d96244e3c2480d63782245e0e52b2744d0dee5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josua=20J=C3=A4ger?= Date: Thu, 15 Aug 2019 20:17:58 +0200 Subject: [PATCH 02/39] add squared length --- src/Avalonia.Visuals/Vector.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index f185682dc0..78ed688295 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -90,6 +90,11 @@ namespace Avalonia /// public double Length => Math.Sqrt(X * X + Y * Y); + /// + /// Squared Length of the vector + /// + public double SquaredLength => Math.Sqrt(Length); + /// /// Negates a vector. /// From 22ac34d89b912cef0b5de1b5660201e27916e011 Mon Sep 17 00:00:00 2001 From: JaggerJo Date: Thu, 15 Aug 2019 20:28:42 +0200 Subject: [PATCH 03/39] add - Zero - One - UnitX - UnitY --- src/Avalonia.Visuals/Vector.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index f185682dc0..aede9f3905 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -283,5 +283,29 @@ namespace Avalonia /// The scaled vector. public static Vector Negate(Vector vector) => new Vector(-vector._x, -vector._y); + + /// + /// Returnes the vector (0.0, 0.0) + /// + public static Vector Zero + => new Vector(0, 0); + + /// + /// Returnes the vector (1.0, 1.0) + /// + public static Vector One + => new Vector(1, 1); + + /// + /// Returnes the vector (1.0, 0.0) + /// + public static Vector UnitX + => new Vector(1, 0); + + /// + /// Returnes the vector (0.0, 1.0) + /// + public static Vector UnitY + => new Vector(0, 1); } } From b475acab1e677c3d570e83fe791fdffac54f0e0b Mon Sep 17 00:00:00 2001 From: JaggerJo Date: Thu, 15 Aug 2019 21:17:38 +0200 Subject: [PATCH 04/39] - add tests - add instance methods for normalize & negate - make length squared simpler --- src/Avalonia.Visuals/Vector.cs | 18 ++- .../Avalonia.Visuals.UnitTests/VectorTests.cs | 112 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/VectorTests.cs diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 2c02c00d00..782a917c62 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -88,12 +88,12 @@ namespace Avalonia /// /// Length of the vector /// - public double Length => Math.Sqrt(X * X + Y * Y); + public double Length => Math.Sqrt(_x * _x + _y * _y); /// /// Squared Length of the vector /// - public double SquaredLength => Math.Sqrt(Length); + public double SquaredLength => _x * _x + _y * _y; /// /// Negates a vector. @@ -201,6 +201,20 @@ namespace Avalonia return new Vector(_x, y); } + /// + /// Returns a normalized version of this vector. + /// + /// The normalized vector. + public Vector Normalize() + => Normalize(this); + + /// + /// Returns a negated version of this vector. + /// + /// The negated vector. + public Vector Negate() + => Negate(this); + /// /// Returns the dot product of two vectors. /// diff --git a/tests/Avalonia.Visuals.UnitTests/VectorTests.cs b/tests/Avalonia.Visuals.UnitTests/VectorTests.cs new file mode 100644 index 0000000000..1bcc165aef --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/VectorTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Xunit; +using Avalonia; +using System; + +namespace Avalonia.Visuals.UnitTests +{ + public class VectorTests + { + [Fact] + public void Length_Should_Return_Correct_Length_Of_Vector() + { + var vector = new Vector(2, 4); + var length = Math.Sqrt(2 * 2 + 4 * 4); + + Assert.Equal(length, vector.Length); + } + + [Fact] + public void Length_Squared_Should_Return_Correct_Length_Of_Vector() + { + var vectorA = new Vector(2, 4); + var squaredLengthA = 2 * 2 + 4 * 4; + + Assert.Equal(squaredLengthA, vectorA.SquaredLength); + } + + [Fact] + public void Normalize_Should_Return_Normalized_Vector() + { + // the length of a normalized vector must be 1 + + var vectorA = new Vector(13, 84); + var vectorB = new Vector(-34, 345); + var vectorC = new Vector(-34, -84); + + Assert.Equal(1.0, vectorA.Normalize().Length); + Assert.Equal(1.0, vectorB.Normalize().Length); + Assert.Equal(1.0, vectorC.Normalize().Length); + } + + [Fact] + public void Negate_Should_Return_Negated_Vector() + { + var vector = new Vector(2, 4); + var negated = new Vector(-2, -4); + + Assert.Equal(negated, vector.Negate()); + } + + [Fact] + public void Dot_Should_Return_Correct_Value() + { + var a = new Vector(-6, 8.0); + var b = new Vector(5, 12.0); + + Assert.Equal(66.0, Vector.Dot(a, b)); + } + + [Fact] + public void Cross_Should_Return_Correct_Value() + { + var a = new Vector(-6, 8.0); + var b = new Vector(5, 12.0); + + Assert.Equal(-112.0, Vector.Cross(a, b)); + } + + [Fact] + public void Divied_By_Vector_Should_Return_Correct_Value() + { + var a = new Vector(10, 2); + var b = new Vector(5, 2); + + var expected = new Vector(2, 1); + + Assert.Equal(expected, Vector.Divide(a, b)); + } + + [Fact] + public void Divied_Should_Return_Correct_Value() + { + var vector = new Vector(10, 2); + var expected = new Vector(5, 1); + + Assert.Equal(expected, Vector.Divide(vector, 2)); + } + + [Fact] + public void Multiply_By_Vector_Should_Return_Correct_Value() + { + var a = new Vector(10, 2); + var b = new Vector(2, 2); + + var expected = new Vector(20, 4); + + Assert.Equal(expected, Vector.Multiply(a, b)); + } + + [Fact] + public void Multiply_Should_Return_Correct_Value() + { + var vector = new Vector(10, 2); + + var expected = new Vector(20, 4); + + Assert.Equal(expected, Vector.Multiply(vector, 2)); + } + } +} From ba873bf33936234ad533c56da161a2890954c958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josua=20J=C3=A4ger?= Date: Sat, 17 Aug 2019 17:48:01 +0200 Subject: [PATCH 05/39] use field directly instead of property Co-Authored-By: Benedikt Schroeder --- src/Avalonia.Visuals/Vector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 782a917c62..11bda8b00e 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -88,7 +88,7 @@ namespace Avalonia /// /// Length of the vector /// - public double Length => Math.Sqrt(_x * _x + _y * _y); + public double Length => Math.Sqrt(SquaredLength); /// /// Squared Length of the vector From f3dfddc112739ac8c1602fbf9516004b803d54c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 21 Aug 2019 01:15:55 +0100 Subject: [PATCH 06/39] Fixed Binding.DoNothing for MultiBinding. --- .../Avalonia.Markup/Data/MultiBinding.cs | 12 +-- .../Converters/MultiValueConverterTests.cs | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 19f92149ec..29945e25c3 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -76,7 +76,12 @@ namespace Avalonia.Data } var children = Bindings.Select(x => x.Initiate(target, null)); - var input = children.Select(x => x.Observable).CombineLatest().Select(x => ConvertValue(x, targetType, converter)); + + var input = children.Select(x => x.Observable) + .CombineLatest() + .Select(x => ConvertValue(x, targetType, converter)) + .Where(x => x != BindingOperations.DoNothing); + var mode = Mode == BindingMode.Default ? targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode; @@ -97,11 +102,6 @@ namespace Avalonia.Data var culture = CultureInfo.CurrentCulture; var converted = converter.Convert(values, targetType, ConverterParameter, culture); - if (converted == BindingOperations.DoNothing) - { - return converted; - } - if (converted == AvaloniaProperty.UnsetValue) { converted = FallbackValue; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs new file mode 100644 index 0000000000..a77723afe1 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class MultiValueConverterTests : XamlTestBase + { + [Fact] + public void MultiValueConverter_Special_Values_Work() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.ApplyTemplate(); + + window.DataContext = Tuple.Create(2, 2); + Assert.Equal("foo", textBlock.Text); + + window.DataContext = Tuple.Create(-3, 3); + Assert.Equal("foo", textBlock.Text); + + window.DataContext = Tuple.Create(0, 2); + Assert.Equal("bar", textBlock.Text); + } + } + } + + public class TestMultiValueConverter : IMultiValueConverter + { + public static readonly TestMultiValueConverter Instance = new TestMultiValueConverter(); + + public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is int i && values[1] is int j) + { + var p = i * j; + + if (p > 0) + { + return "foo"; + } + + if (p == 0) + { + return AvaloniaProperty.UnsetValue; + } + + return BindingOperations.DoNothing; + } + + return "(default)"; + } + } +} From c64cc02ec68d5d5bdecc64e9001d2c09691cc43b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 28 Aug 2019 14:06:00 +0800 Subject: [PATCH 07/39] Move transitions initializer to setter. --- src/Avalonia.Animation/Animatable.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 3a3d00b94a..89bcd2efa5 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -44,6 +44,10 @@ namespace Avalonia.Animation public Transitions Transitions { get + { + return _transitions; + } + set { if (_transitions == null) _transitions = new Transitions(); @@ -51,10 +55,6 @@ namespace Avalonia.Animation if (_previousTransitions == null) _previousTransitions = new Dictionary(); - return _transitions; - } - set - { SetAndRaise(TransitionsProperty, ref _transitions, value); } } From 87245c90b46684a01c0c9283a40af4180002eae4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 28 Aug 2019 14:20:43 +0800 Subject: [PATCH 08/39] Fixes: part 2 of n. --- src/Avalonia.Animation/Animatable.cs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 89bcd2efa5..09991a5990 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -49,10 +49,10 @@ namespace Avalonia.Animation } set { - if (_transitions == null) - _transitions = new Transitions(); + if (value is null) + return; - if (_previousTransitions == null) + if (_previousTransitions is null) _previousTransitions = new Dictionary(); SetAndRaise(TransitionsProperty, ref _transitions, value); @@ -66,19 +66,18 @@ namespace Avalonia.Animation /// The event args. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null) - { - var match = Transitions.FirstOrDefault(x => x.Property == e.Property); + if (Transitions is null || e.Priority == BindingPriority.Animation) return; + + var match = Transitions.FirstOrDefault(x => x.Property == e.Property); - if (match != null) - { - if (_previousTransitions.TryGetValue(e.Property, out var dispose)) - dispose.Dispose(); + if (match != null) + { + if (_previousTransitions.TryGetValue(e.Property, out var dispose)) + dispose.Dispose(); - var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); - _previousTransitions[e.Property] = instance; - } + _previousTransitions[e.Property] = instance; } } } From b376313acff353872ce610d39997c9c553180ed4 Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 28 Aug 2019 16:28:17 +0100 Subject: [PATCH 09/39] speed up GetValue and GetDefaultValue --- src/Avalonia.Base/AvaloniaObject.cs | 49 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 0e2f0feada..2fee277f21 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -208,20 +208,9 @@ namespace Avalonia { return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); } - else if (_values != null) - { - var result = Values.GetValue(property); - - if (result == AvaloniaProperty.UnsetValue) - { - result = GetDefaultValue(property); - } - - return result; - } else { - return GetDefaultValue(property); + return GetValueOrDefault(property); } } @@ -598,10 +587,44 @@ namespace Avalonia private object GetDefaultValue(AvaloniaProperty property) { if (property.Inherits && InheritanceParent is AvaloniaObject aobj) - return aobj.GetValue(property); + return aobj.GetValueOrDefault(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } + /// + /// Gets the value or default value for a property. + /// + /// The property. + /// The default value. + private object GetValueOrDefault(AvaloniaProperty property) + { + var aobj = this; + if (aobj.Values != null) + { + var result = aobj.Values.GetValue(property); + if (result != AvaloniaProperty.UnsetValue) + { + return result; + } + } + if (property.Inherits) + { + while(aobj.InheritanceParent is AvaloniaObject parent) + { + aobj = parent; + if (aobj.Values != null) + { + var result = aobj.Values.GetValue(property); + if (result != AvaloniaProperty.UnsetValue) + { + return result; + } + } + } + } + return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType()); + } + /// /// Sets the value of a direct property. /// From ebccccb8e4c8a6138274eeb1e51769f93919c857 Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 28 Aug 2019 17:37:01 +0100 Subject: [PATCH 10/39] add UnChecked suffix --- src/Avalonia.Base/AvaloniaObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2fee277f21..5fa6dab29e 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -210,7 +210,7 @@ namespace Avalonia } else { - return GetValueOrDefault(property); + return GetValueOrDefaultUnChecked(property); } } @@ -596,7 +596,7 @@ namespace Avalonia /// /// The property. /// The default value. - private object GetValueOrDefault(AvaloniaProperty property) + private object GetValueOrDefaultUnChecked(AvaloniaProperty property) { var aobj = this; if (aobj.Values != null) From e8ba46160d3aad056a121b23b82f5290d8d360e6 Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 28 Aug 2019 17:39:59 +0100 Subject: [PATCH 11/39] UnChecked suffix fixed --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 5fa6dab29e..f8efb36562 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -587,7 +587,7 @@ namespace Avalonia private object GetDefaultValue(AvaloniaProperty property) { if (property.Inherits && InheritanceParent is AvaloniaObject aobj) - return aobj.GetValueOrDefault(property); + return aobj.GetValueOrDefaultUnChecked(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } From d176b1d7dc10e22137a21b7e2cf1836ec4cd2e27 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 29 Aug 2019 12:34:03 +0800 Subject: [PATCH 12/39] Fix unit tests. --- src/Avalonia.Animation/Animatable.cs | 4 ++-- tests/Avalonia.Animation.UnitTests/TransitionsTests.cs | 4 ++-- tests/Avalonia.LeakTests/TransitionTests.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 09991a5990..5ff3b17fb5 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -35,7 +35,7 @@ namespace Avalonia.Animation (o, v) => o.Transitions = v); private Transitions _transitions; - + private bool _isTransitionsSet = false; private Dictionary _previousTransitions; /// @@ -66,7 +66,7 @@ namespace Avalonia.Animation /// The event args. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - if (Transitions is null || e.Priority == BindingPriority.Animation) return; + if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; var match = Transitions.FirstOrDefault(x => x.Property == e.Property); diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index f1b4b0d071..a8efc2c8ae 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = new Transitions { new DoubleTransition { @@ -51,7 +51,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = new Transitions { new DoubleTransition { diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs index c7add1fe11..699dec7229 100644 --- a/tests/Avalonia.LeakTests/TransitionTests.cs +++ b/tests/Avalonia.LeakTests/TransitionTests.cs @@ -27,7 +27,7 @@ namespace Avalonia.LeakTests { var border = new Border { - Transitions = + Transitions = new Transitions { new DoubleTransition { From 77d9ae1cacfee35930c0543643c34a167ffe3b87 Mon Sep 17 00:00:00 2001 From: ahopper Date: Fri, 30 Aug 2019 10:18:30 +0100 Subject: [PATCH 13/39] change UnChecked suffix to Unchecked --- src/Avalonia.Base/AvaloniaObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index f8efb36562..48a222d5b4 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -210,7 +210,7 @@ namespace Avalonia } else { - return GetValueOrDefaultUnChecked(property); + return GetValueOrDefaultUnchecked(property); } } @@ -587,7 +587,7 @@ namespace Avalonia private object GetDefaultValue(AvaloniaProperty property) { if (property.Inherits && InheritanceParent is AvaloniaObject aobj) - return aobj.GetValueOrDefaultUnChecked(property); + return aobj.GetValueOrDefaultUnchecked(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } @@ -596,7 +596,7 @@ namespace Avalonia /// /// The property. /// The default value. - private object GetValueOrDefaultUnChecked(AvaloniaProperty property) + private object GetValueOrDefaultUnchecked(AvaloniaProperty property) { var aobj = this; if (aobj.Values != null) From 4d8973226d10ed188181367d5b522f0e2fd99bc7 Mon Sep 17 00:00:00 2001 From: ahopper Date: Fri, 30 Aug 2019 10:35:24 +0100 Subject: [PATCH 14/39] unbreak lazy initialization of Values --- src/Avalonia.Base/AvaloniaObject.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 48a222d5b4..035e0056e0 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -599,9 +599,9 @@ namespace Avalonia private object GetValueOrDefaultUnchecked(AvaloniaProperty property) { var aobj = this; - if (aobj.Values != null) + if (aobj._values != null) { - var result = aobj.Values.GetValue(property); + var result = aobj._values.GetValue(property); if (result != AvaloniaProperty.UnsetValue) { return result; @@ -612,9 +612,9 @@ namespace Avalonia while(aobj.InheritanceParent is AvaloniaObject parent) { aobj = parent; - if (aobj.Values != null) + if (aobj._values != null) { - var result = aobj.Values.GetValue(property); + var result = aobj._values.GetValue(property); if (result != AvaloniaProperty.UnsetValue) { return result; From a9da85e4aed730e286c3b07a6288f525e8556204 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 1 Sep 2019 12:49:30 +0200 Subject: [PATCH 15/39] Avoid per-frame allocations in renderer lock. --- .../Rendering/ManagedDeferredRendererLock.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs index 2d4a39e026..1295961a1b 100644 --- a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs +++ b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive.Disposables; using System.Threading; namespace Avalonia.Rendering @@ -7,7 +6,13 @@ namespace Avalonia.Rendering public class ManagedDeferredRendererLock : IDeferredRendererLock { private readonly object _lock = new object(); - + private readonly LockDisposable _lockDisposable; + + public ManagedDeferredRendererLock() + { + _lockDisposable = new LockDisposable(_lock); + } + /// /// Tries to lock the target surface or window /// @@ -15,7 +20,7 @@ namespace Avalonia.Rendering public IDisposable TryLock() { if (Monitor.TryEnter(_lock)) - return Disposable.Create(() => Monitor.Exit(_lock)); + return _lockDisposable; return null; } @@ -25,7 +30,22 @@ namespace Avalonia.Rendering public IDisposable Lock() { Monitor.Enter(_lock); - return Disposable.Create(() => Monitor.Exit(_lock)); + return _lockDisposable; + } + + private class LockDisposable : IDisposable + { + private readonly object _lock; + + public LockDisposable(object @lock) + { + _lock = @lock; + } + + public void Dispose() + { + Monitor.Exit(_lock); + } } } } From 26b1320971d3e7b8f75e0a7f5b4a2f84f71d9fb0 Mon Sep 17 00:00:00 2001 From: ahopper Date: Sun, 1 Sep 2019 19:39:18 +0100 Subject: [PATCH 16/39] reduce repeated field access --- src/Avalonia.Base/AvaloniaObject.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 035e0056e0..c619d80e23 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -599,9 +599,10 @@ namespace Avalonia private object GetValueOrDefaultUnchecked(AvaloniaProperty property) { var aobj = this; - if (aobj._values != null) + var valuestore = aobj._values; + if (valuestore != null) { - var result = aobj._values.GetValue(property); + var result = valuestore.GetValue(property); if (result != AvaloniaProperty.UnsetValue) { return result; @@ -612,9 +613,10 @@ namespace Avalonia while(aobj.InheritanceParent is AvaloniaObject parent) { aobj = parent; - if (aobj._values != null) + valuestore = aobj._values; + if (valuestore != null) { - var result = aobj._values.GetValue(property); + var result = valuestore.GetValue(property); if (result != AvaloniaProperty.UnsetValue) { return result; From 54399d2a51eac2c1664779c2f546b428ed1c2c5c Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 5 Sep 2019 22:53:48 +0200 Subject: [PATCH 17/39] Use ValueTuple instead of Tuple to reduce alocations. Invoke raise callback without reflection. --- src/Avalonia.Interactivity/RoutedEvent.cs | 50 ++++++----------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs index 2d752133c1..cfbaddb327 100644 --- a/src/Avalonia.Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Interactivity/RoutedEvent.cs @@ -4,7 +4,6 @@ using System; using System.Reactive.Subjects; using System.Reflection; -using System.Runtime.ExceptionServices; namespace Avalonia.Interactivity { @@ -18,8 +17,8 @@ namespace Avalonia.Interactivity public class RoutedEvent { - private Subject> _raised = new Subject>(); - private Subject _routeFinished = new Subject(); + private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>(); + private readonly Subject _routeFinished = new Subject(); public RoutedEvent( string name, @@ -38,31 +37,15 @@ namespace Avalonia.Interactivity RoutingStrategies = routingStrategies; } - public Type EventArgsType - { - get; - private set; - } + public Type EventArgsType { get; } - public string Name - { - get; - private set; - } + public string Name { get; } - public Type OwnerType - { - get; - private set; - } + public Type OwnerType { get; } - public RoutingStrategies RoutingStrategies - { - get; - private set; - } + public RoutingStrategies RoutingStrategies { get; } - public IObservable> Raised => _raised; + public IObservable<(object, RoutedEventArgs)> Raised => _raised; public IObservable RouteFinished => _routeFinished; public static RoutedEvent Register( @@ -98,29 +81,20 @@ namespace Avalonia.Interactivity { return Raised.Subscribe(args => { - var sender = args.Item1; - var e = args.Item2; + (object sender, RoutedEventArgs e) = args; - if (targetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) && - ((e.Route == RoutingStrategies.Direct) || (e.Route & routes) != 0) && + if (targetType.IsInstanceOfType(sender) && + (e.Route == RoutingStrategies.Direct || (e.Route & routes) != 0) && (!e.Handled || handledEventsToo)) { - try - { - handler.DynamicInvoke(sender, e); - } - catch (TargetInvocationException ex) - { - // Unwrap the inner exception. - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } + handler(sender, e); } }); } internal void InvokeRaised(object sender, RoutedEventArgs e) { - _raised.OnNext(Tuple.Create(sender, e)); + _raised.OnNext((sender, e)); } internal void InvokeRouteFinished(RoutedEventArgs e) From 50a8fc3571d6d911ccbca80ec4bc504c2385e943 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 5 Sep 2019 23:28:33 +0200 Subject: [PATCH 18/39] Get rid of pointless allocations in PriorityBindingEntry. --- src/Avalonia.Base/PriorityBindingEntry.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index d4a47306a7..95add0dfac 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Runtime.ExceptionServices; using Avalonia.Data; using Avalonia.Threading; @@ -10,9 +11,9 @@ namespace Avalonia /// /// A registered binding in a . /// - internal class PriorityBindingEntry : IDisposable + internal class PriorityBindingEntry : IDisposable, IObserver { - private PriorityLevel _owner; + private readonly PriorityLevel _owner; private IDisposable _subscription; /// @@ -85,7 +86,7 @@ namespace Avalonia Description = ((IDescription)binding).Description; } - _subscription = binding.Subscribe(ValueChanged, Completed); + _subscription = binding.Subscribe(this); } /// @@ -96,7 +97,7 @@ namespace Avalonia _subscription?.Dispose(); } - private void ValueChanged(object value) + void IObserver.OnNext(object value) { void Signal() { @@ -132,7 +133,7 @@ namespace Avalonia } } - private void Completed() + void IObserver.OnCompleted() { HasCompleted = true; @@ -145,5 +146,10 @@ namespace Avalonia Dispatcher.UIThread.Post(() => _owner.Completed(this)); } } + + void IObserver.OnError(Exception error) + { + ExceptionDispatchInfo.Capture(error).Throw(); + } } } From 4e312e909a6ac575e816ccd66c6894535161fa1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 6 Sep 2019 18:48:22 +0200 Subject: [PATCH 19/39] Avoid constant boxing of StyledProperty default value. --- src/Avalonia.Base/BoxedValue.cs | 28 +++++++++++++++++++ src/Avalonia.Base/StyledPropertyBase.cs | 11 ++++++-- src/Avalonia.Base/StyledPropertyMetadata`1.cs | 14 ++++------ 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Base/BoxedValue.cs diff --git a/src/Avalonia.Base/BoxedValue.cs b/src/Avalonia.Base/BoxedValue.cs new file mode 100644 index 0000000000..5fc515f299 --- /dev/null +++ b/src/Avalonia.Base/BoxedValue.cs @@ -0,0 +1,28 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia +{ + /// + /// Represents boxed value of type . + /// + /// Type of stored value. + internal readonly struct BoxedValue + { + public BoxedValue(T value) + { + Boxed = value; + Typed = value; + } + + /// + /// Boxed value. + /// + public object Boxed { get; } + + /// + /// Typed value. + /// + public T Typed { get; } + } +} diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index eb112e753a..27a502246a 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -68,7 +68,7 @@ namespace Avalonia { Contract.Requires(type != null); - return GetMetadata(type).DefaultValue; + return GetMetadata(type).DefaultValue.Typed; } /// @@ -164,7 +164,14 @@ namespace Avalonia } /// - object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type); + object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); + + private object GetDefaultBoxedValue(Type type) + { + Contract.Requires(type != null); + + return GetMetadata(type).DefaultValue.Boxed; + } [DebuggerHidden] private Func Cast(Func validate) diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs index ed01f1bc70..d1a0e2dc53 100644 --- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs +++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs @@ -19,26 +19,26 @@ namespace Avalonia /// A validation function. /// The default binding mode. public StyledPropertyMetadata( - TValue defaultValue = default(TValue), + TValue defaultValue = default, Func validate = null, BindingMode defaultBindingMode = BindingMode.Default) : base(defaultBindingMode) { - DefaultValue = defaultValue; + DefaultValue = new BoxedValue(defaultValue); Validate = validate; } /// /// Gets the default value for the property. /// - public TValue DefaultValue { get; private set; } + internal BoxedValue DefaultValue { get; private set; } /// /// Gets the validation callback. /// public Func Validate { get; private set; } - object IStyledPropertyMetadata.DefaultValue => DefaultValue; + object IStyledPropertyMetadata.DefaultValue => DefaultValue.Boxed; Func IStyledPropertyMetadata.Validate => Cast(Validate); @@ -47,11 +47,9 @@ namespace Avalonia { base.Merge(baseMetadata, property); - var src = baseMetadata as StyledPropertyMetadata; - - if (src != null) + if (baseMetadata is StyledPropertyMetadata src) { - if (DefaultValue == null) + if (DefaultValue.Boxed == null) { DefaultValue = src.DefaultValue; } From 33f154148b8929853480df3ef6c0946da7763762 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 7 Sep 2019 22:29:01 +0100 Subject: [PATCH 20/39] Add static method to open devtools. --- src/Avalonia.Diagnostics/DevTools.xaml.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index 1fcfb525cb..037e80e372 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -28,6 +28,11 @@ namespace Avalonia { Diagnostics.DevTools.Attach(control, gesture); } + + public static void OpenDevTools(this TopLevel control) + { + Diagnostics.DevTools.OpenDevTools(control); + } } } @@ -73,7 +78,7 @@ namespace Avalonia.Diagnostics RoutingStrategies.Tunnel); } - private static void OpenDevTools(TopLevel control) + internal static void OpenDevTools(TopLevel control) { if (s_open.TryGetValue(control, out var devToolsWindow)) { From 66d79c025da7bfe9046def4c72e1514804d4c601 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Sat, 7 Sep 2019 22:34:38 +0100 Subject: [PATCH 21/39] Update Default.xaml --- src/Avalonia.Controls.DataGrid/Themes/Default.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index eaa267ba66..7b6870fec3 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -217,12 +217,12 @@ - + - + From 3dddb569821d2235b923662b8e6e4c590b940e59 Mon Sep 17 00:00:00 2001 From: ahopper Date: Sun, 8 Sep 2019 06:58:09 +0100 Subject: [PATCH 22/39] add space after while --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index c619d80e23..94558c4367 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -610,7 +610,7 @@ namespace Avalonia } if (property.Inherits) { - while(aobj.InheritanceParent is AvaloniaObject parent) + while (aobj.InheritanceParent is AvaloniaObject parent) { aobj = parent; valuestore = aobj._values; From 04c2dfcc5484891761522764a8359e3b73638c9b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 14:26:35 +0200 Subject: [PATCH 23/39] Compiled expressions for RoutedEvent. --- .../EventSubscription.cs | 4 +++ src/Avalonia.Interactivity/Interactive.cs | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index 9f763edcd3..ff2a28e570 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,8 +5,12 @@ using System; namespace Avalonia.Interactivity { + internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal class EventSubscription { + public InvokeSignature RaiseHandler { get; set; } + public Delegate Handler { get; set; } public RoutingStrategies Routes { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 911fc2130e..033686a8c8 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Layout; @@ -28,6 +29,10 @@ namespace Avalonia.Interactivity get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } } + + + private static Dictionary s_invokeCache = new Dictionary(); + /// /// Adds a handler for the specified routed event. /// @@ -53,8 +58,27 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } + if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + { + ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); + ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); + ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); + + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + + InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); + + raiseFunc = Expression + .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) + .Compile(); + + s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + } + var sub = new EventSubscription { + RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, @@ -196,7 +220,7 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.Handler.DynamicInvoke(this, e); + sub.RaiseHandler(sub.Handler, this, e); } } } From edd6fd626f7e21005bc828792cb7ee7848a01fe7 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 17:31:18 +0200 Subject: [PATCH 24/39] Make sure that unlock API is still safe. --- .../AvaloniaNativeDeferredRendererLock.cs | 23 ++++++++++++++-- .../Rendering/ManagedDeferredRendererLock.cs | 26 ++++++++++--------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs b/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs index 6dd5337b27..0031cc5b03 100644 --- a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs +++ b/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs @@ -1,5 +1,8 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; -using System.Reactive.Disposables; +using System.Threading; using Avalonia.Native.Interop; using Avalonia.Rendering; @@ -13,11 +16,27 @@ namespace Avalonia.Native { _window = window; } + public IDisposable TryLock() { if (_window.TryLock()) - return Disposable.Create(() => _window.Unlock()); + return new UnlockDisposable(_window); return null; } + + private class UnlockDisposable : IDisposable + { + private IAvnWindowBase _window; + + public UnlockDisposable(IAvnWindowBase window) + { + _window = window; + } + + public void Dispose() + { + Interlocked.Exchange(ref _window, null)?.Unlock(); + } + } } } diff --git a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs index 1295961a1b..a87fc9e39f 100644 --- a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs +++ b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; using System.Threading; @@ -6,12 +9,6 @@ namespace Avalonia.Rendering public class ManagedDeferredRendererLock : IDeferredRendererLock { private readonly object _lock = new object(); - private readonly LockDisposable _lockDisposable; - - public ManagedDeferredRendererLock() - { - _lockDisposable = new LockDisposable(_lock); - } /// /// Tries to lock the target surface or window @@ -20,7 +17,7 @@ namespace Avalonia.Rendering public IDisposable TryLock() { if (Monitor.TryEnter(_lock)) - return _lockDisposable; + return new UnlockDisposable(_lock); return null; } @@ -30,21 +27,26 @@ namespace Avalonia.Rendering public IDisposable Lock() { Monitor.Enter(_lock); - return _lockDisposable; + return new UnlockDisposable(_lock); } - private class LockDisposable : IDisposable + private class UnlockDisposable : IDisposable { - private readonly object _lock; + private object _lock; - public LockDisposable(object @lock) + public UnlockDisposable(object @lock) { _lock = @lock; } public void Dispose() { - Monitor.Exit(_lock); + object @lock = Interlocked.Exchange(ref _lock, null); + + if (@lock != null) + { + Monitor.Exit(@lock); + } } } } From c37b911fe5a8af3dcca826d556d88c70f3f64e68 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 17:33:41 +0200 Subject: [PATCH 25/39] Seal unlock classes. --- src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs | 2 +- src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs b/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs index 0031cc5b03..ce2b03e355 100644 --- a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs +++ b/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native return null; } - private class UnlockDisposable : IDisposable + private sealed class UnlockDisposable : IDisposable { private IAvnWindowBase _window; diff --git a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs index a87fc9e39f..259874423e 100644 --- a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs +++ b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs @@ -30,7 +30,7 @@ namespace Avalonia.Rendering return new UnlockDisposable(_lock); } - private class UnlockDisposable : IDisposable + private sealed class UnlockDisposable : IDisposable { private object _lock; From 64fdb5828ee6943a5add0f2877bcb29a1de32361 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 9 Sep 2019 02:02:54 +0800 Subject: [PATCH 26/39] un-LINQ the Transition property matching codepath. --- src/Avalonia.Animation/Animatable.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 5ff3b17fb5..3f548d207d 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -68,16 +68,19 @@ namespace Avalonia.Animation { if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; - var match = Transitions.FirstOrDefault(x => x.Property == e.Property); - - if (match != null) + // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations). + foreach (var transition in Transitions) { - if (_previousTransitions.TryGetValue(e.Property, out var dispose)) - dispose.Dispose(); + if (transition.Property == e.Property) + { + if (_previousTransitions.TryGetValue(e.Property, out var dispose)) + dispose.Dispose(); - var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + var instance = transition.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); - _previousTransitions[e.Property] = instance; + _previousTransitions[e.Property] = instance; + return; + } } } } From b8e4909b104423cb7163b3fbb8ea75b8c466c997 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 9 Sep 2019 02:18:47 +0800 Subject: [PATCH 27/39] Restore Lazy Init to avoid API break. --- src/Avalonia.Animation/Animatable.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 3f548d207d..68ca03f910 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -45,15 +45,16 @@ namespace Avalonia.Animation { get { + if (_transitions is null) + _transitions = new Transitions(); + + if (_previousTransitions is null) + _previousTransitions = new Dictionary(); + return _transitions; } set { - if (value is null) - return; - - if (_previousTransitions is null) - _previousTransitions = new Dictionary(); SetAndRaise(TransitionsProperty, ref _transitions, value); } From 3549f00fcba37fffe35c827fc20ded88743f8250 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 9 Sep 2019 02:19:25 +0800 Subject: [PATCH 28/39] Revert unit test changes. --- tests/Avalonia.Animation.UnitTests/TransitionsTests.cs | 4 ++-- tests/Avalonia.LeakTests/TransitionTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index a8efc2c8ae..4ebe472125 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = new Transitions + Transitions = { new DoubleTransition { @@ -51,7 +51,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = new Transitions + Transitions = { new DoubleTransition { diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs index 699dec7229..5ab8c5c0dd 100644 --- a/tests/Avalonia.LeakTests/TransitionTests.cs +++ b/tests/Avalonia.LeakTests/TransitionTests.cs @@ -27,7 +27,7 @@ namespace Avalonia.LeakTests { var border = new Border { - Transitions = new Transitions + Transitions = { new DoubleTransition { From 4a7f370fc520413a4c59709c8c10ab25048722e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 20:23:38 +0200 Subject: [PATCH 29/39] Cleanup. --- src/Avalonia.Interactivity/Interactive.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 033686a8c8..59dfbcb8ab 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Layout; using Avalonia.VisualTree; @@ -19,19 +18,14 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; + private static readonly Dictionary s_invokeCache = new Dictionary(); + /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; - private Dictionary> EventHandlers - { - get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } - } - - - - private static Dictionary s_invokeCache = new Dictionary(); + private Dictionary> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary>()); /// /// Adds a handler for the specified routed event. @@ -58,14 +52,16 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } - if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + Type eventArgsType = routedEvent.EventArgsType; + + if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) { ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); @@ -73,7 +69,7 @@ namespace Avalonia.Interactivity .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) .Compile(); - s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + s_invokeCache.Add(eventArgsType, raiseFunc); } var sub = new EventSubscription From 45f86a925f4bf0766b40b209cfd42558a8a7bcc9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 8 Sep 2019 22:43:47 +0300 Subject: [PATCH 30/39] Fixed possible NRE in Gestures.cs --- src/Avalonia.Input/Gestures.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index bb8c8b8c40..ea4892ebfc 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -31,7 +31,7 @@ namespace Avalonia.Input RoutedEvent.Register( "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); - private static WeakReference s_lastPress; + private static WeakReference s_lastPress = new WeakReference(null); static Gestures() { From 5cae6bee7b94253df022fe20ecf48bd4f3660780 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 21:55:17 +0200 Subject: [PATCH 31/39] Simplify creation of invoke adapters. --- .../EventSubscription.cs | 2 +- src/Avalonia.Interactivity/Interactive.cs | 115 ++++++++++++------ 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index ff2a28e570..b495142998 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -9,7 +9,7 @@ namespace Avalonia.Interactivity internal class EventSubscription { - public InvokeSignature RaiseHandler { get; set; } + public InvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 59dfbcb8ab..0f607241ec 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Reactive.Disposables; using Avalonia.Layout; using Avalonia.VisualTree; @@ -44,45 +42,14 @@ namespace Avalonia.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List subscriptions; - - if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) - { - subscriptions = new List(); - EventHandlers.Add(routedEvent, subscriptions); - } - - Type eventArgsType = routedEvent.EventArgsType; - - if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) - { - ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); - ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); - ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); - - InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); - - raiseFunc = Expression - .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) - .Compile(); - - s_invokeCache.Add(eventArgsType, raiseFunc); - } - - var sub = new EventSubscription + var subscription = new EventSubscription { - RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, }; - subscriptions.Add(sub); - - return Disposable.Create(() => subscriptions.Remove(sub)); + return AddEventSubscription(routedEvent, subscription); } /// @@ -100,7 +67,37 @@ namespace Avalonia.Interactivity RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TEventArgs : RoutedEventArgs { - return AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo); + Contract.Requires(routedEvent != null); + Contract.Requires(handler != null); + + // EventHandler delegate is not covariant, this forces us to create small wrapper + // that will cast our type erased instance and invoke it. + Type eventArgsType = routedEvent.EventArgsType; + + if (!s_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + { + void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + { + var typedHandler = (EventHandler)func; + var typedArgs = (TEventArgs)args; + + typedHandler(sender, typedArgs); + } + + invokeAdapter = InvokeAdapter; + + s_invokeCache.Add(eventArgsType, invokeAdapter); + } + + var subscription = new EventSubscription + { + InvokeAdapter = invokeAdapter, + Handler = handler, + Routes = routes, + AlsoIfHandled = handledEventsToo, + }; + + return AddEventSubscription(routedEvent, subscription); } /// @@ -216,10 +213,54 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.RaiseHandler(sub.Handler, this, e); + if (sub.InvokeAdapter != null) + { + sub.InvokeAdapter(sub.Handler, this, e); + } + else + { + sub.Handler.DynamicInvoke(this, e); + } } } } } + + private List GetEventSubscriptions(RoutedEvent routedEvent) + { + if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions)) + { + subscriptions = new List(); + EventHandlers.Add(routedEvent, subscriptions); + } + + return subscriptions; + } + + private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) + { + List subscriptions = GetEventSubscriptions(routedEvent); + + subscriptions.Add(subscription); + + return new UnsubscribeDisposable(subscriptions, subscription); + } + + private sealed class UnsubscribeDisposable : IDisposable + { + private readonly List _subscriptions; + private readonly EventSubscription _subscription; + + public UnsubscribeDisposable(List subscriptions, EventSubscription subscription) + { + _subscriptions = subscriptions; + _subscription = subscription; + } + + public void Dispose() + { + _subscriptions.Remove(_subscription); + } + } } } From f5b317decbf7bededba9d4746a09261d6e6c1aa4 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 22:33:39 +0200 Subject: [PATCH 32/39] Naming. --- src/Avalonia.Interactivity/EventSubscription.cs | 4 ++-- src/Avalonia.Interactivity/Interactive.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index b495142998..e8fb1bfaf1 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,11 +5,11 @@ using System; namespace Avalonia.Interactivity { - internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args); internal class EventSubscription { - public InvokeSignature InvokeAdapter { get; set; } + public HandlerInvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 0f607241ec..f8d388ec89 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -16,7 +16,7 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; - private static readonly Dictionary s_invokeCache = new Dictionary(); + private static readonly Dictionary s_invokeHandlerCache = new Dictionary(); /// /// Gets the interactive parent of the object for bubbling and tunneling events. @@ -74,11 +74,11 @@ namespace Avalonia.Interactivity // that will cast our type erased instance and invoke it. Type eventArgsType = routedEvent.EventArgsType; - if (!s_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) { - void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args) { - var typedHandler = (EventHandler)func; + var typedHandler = (EventHandler)baseHandler; var typedArgs = (TEventArgs)args; typedHandler(sender, typedArgs); @@ -86,7 +86,7 @@ namespace Avalonia.Interactivity invokeAdapter = InvokeAdapter; - s_invokeCache.Add(eventArgsType, invokeAdapter); + s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); } var subscription = new EventSubscription From d368d9675a17beb045e1e94dce880ee2f0b45042 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 9 Sep 2019 14:25:59 +0800 Subject: [PATCH 33/39] Address review. --- src/Avalonia.Animation/Animatable.cs | 2 +- tests/Avalonia.Animation.UnitTests/TransitionsTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 68ca03f910..2c321b8b28 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -35,7 +35,7 @@ namespace Avalonia.Animation (o, v) => o.Transitions = v); private Transitions _transitions; - private bool _isTransitionsSet = false; + private Dictionary _previousTransitions; /// diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index 4ebe472125..f1b4b0d071 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = { new DoubleTransition { @@ -51,7 +51,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = { new DoubleTransition { From 9970bf67223624c33d751f3aa7c9f8726aed7d0c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 9 Sep 2019 14:38:53 +0800 Subject: [PATCH 34/39] Address review pt. 2 --- tests/Avalonia.LeakTests/TransitionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs index 5ab8c5c0dd..c7add1fe11 100644 --- a/tests/Avalonia.LeakTests/TransitionTests.cs +++ b/tests/Avalonia.LeakTests/TransitionTests.cs @@ -27,7 +27,7 @@ namespace Avalonia.LeakTests { var border = new Border { - Transitions = + Transitions = { new DoubleTransition { From 9c04daace9a965af9205b89f3ce1d01b5bda3310 Mon Sep 17 00:00:00 2001 From: artyom Date: Mon, 9 Sep 2019 11:18:47 +0300 Subject: [PATCH 35/39] Add package metadata to SharedVersion.props --- build/SharedVersion.props | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 76abcf6912..44d5c239ef 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -4,11 +4,16 @@ Avalonia 0.8.999 Copyright 2019 © The AvaloniaUI Project - https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md - https://github.com/AvaloniaUI/Avalonia/ + https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ true CS1591 latest + MIT + https://avatars2.githubusercontent.com/u/14075148?s=200 + Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS. + avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin + https://github.com/AvaloniaUI/Avalonia/releases + git From 90eb54f8eb28df515c4c957fab32af8ad5ee73cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 9 Sep 2019 12:25:26 +0100 Subject: [PATCH 36/39] fix nuget replace script. --- scripts/ReplaceNugetCache.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh index 4cc11edd60..e1c0487d60 100755 --- a/scripts/ReplaceNugetCache.sh +++ b/scripts/ReplaceNugetCache.sh @@ -2,7 +2,6 @@ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/ - cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/ From f56a47ed49343b804ec3041a22217eddfe7e5213 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 9 Sep 2019 12:22:31 +0100 Subject: [PATCH 37/39] fix osx managed file dialogs. --- .../MacOSMountedVolumeInfoProvider.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index eea695d77e..92b2915e2e 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -8,16 +8,16 @@ using Avalonia.Controls.Platform; namespace Avalonia.Native { - internal class WindowsMountedVolumeInfoListener : IDisposable + internal class MacOSMountedVolumeInfoListener : IDisposable { private readonly CompositeDisposable _disposables; - private readonly ObservableCollection _targetObs; private bool _beenDisposed = false; private ObservableCollection mountedDrives; - public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) + public MacOSMountedVolumeInfoListener(ObservableCollection mountedDrives) { this.mountedDrives = mountedDrives; + _disposables = new CompositeDisposable(); var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) @@ -30,7 +30,8 @@ namespace Avalonia.Native private void Poll(long _) { - var mountVolInfos = Directory.GetDirectories("/Volumes") + var mountVolInfos = Directory.GetDirectories("/Volumes/") + .Where(p=> p != null) .Select(p => new MountedVolumeInfo() { VolumeLabel = Path.GetFileName(p), @@ -38,15 +39,15 @@ namespace Avalonia.Native VolumeSizeBytes = 0 }) .ToArray(); - - if (_targetObs.SequenceEqual(mountVolInfos)) + + if (mountedDrives.SequenceEqual(mountVolInfos)) return; else { - _targetObs.Clear(); + mountedDrives.Clear(); foreach (var i in mountVolInfos) - _targetObs.Add(i); + mountedDrives.Add(i); } } @@ -72,7 +73,7 @@ namespace Avalonia.Native public IDisposable Listen(ObservableCollection mountedDrives) { Contract.Requires(mountedDrives != null); - return new WindowsMountedVolumeInfoListener(mountedDrives); + return new MacOSMountedVolumeInfoListener(mountedDrives); } } } From 734b9f39b39b38c091e5a36a4c8dcb18de88b2dd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 9 Sep 2019 12:25:06 +0100 Subject: [PATCH 38/39] fix windows mounted listener. --- .../Avalonia.Win32/WindowsMountedVolumeInfoListener.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index 102e027584..a17e6b8b51 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -10,8 +10,7 @@ namespace Avalonia.Win32 { internal class WindowsMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; - private readonly ObservableCollection _targetObs = new ObservableCollection(); + private readonly CompositeDisposable _disposables; private bool _beenDisposed = false; private ObservableCollection mountedDrives; @@ -41,14 +40,14 @@ namespace Avalonia.Win32 }) .ToArray(); - if (_targetObs.SequenceEqual(mountVolInfos)) + if (mountedDrives.SequenceEqual(mountVolInfos)) return; else { - _targetObs.Clear(); + mountedDrives.Clear(); foreach (var i in mountVolInfos) - _targetObs.Add(i); + mountedDrives.Add(i); } } From 0eb7b2d25e458b2df2172b43fe3102119e03993a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 9 Sep 2019 18:01:43 +0200 Subject: [PATCH 39/39] Enable adding event handlers to Gestures events. Added the `Add*Handler` and `Remove*Handler` events required by XAML in order to use attached event syntax. Added unit test to ensure this works. --- src/Avalonia.Input/Gestures.cs | 30 ++++++++++++ .../Xaml/EventTests.cs | 46 +++++++++++-------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index ea4892ebfc..6b06151773 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -39,6 +39,36 @@ namespace Avalonia.Input InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); } + public static void AddTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(TappedEvent, handler); + } + + public static void AddDoubleTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(DoubleTappedEvent, handler); + } + + public static void AddRightTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(RightTappedEvent, handler); + } + + public static void RemoveTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(TappedEvent, handler); + } + + public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(DoubleTappedEvent, handler); + } + + public static void RemoveRightTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(RightTappedEvent, handler); + } + private static void PointerPressed(RoutedEventArgs ev) { if (ev.Route == RoutingStrategies.Bubble) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs index dcb6533b5e..bde4e34643 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -12,45 +11,56 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml public class EventTests : XamlTestBase { [Fact] - public void Event_Is_Attached() + public void Event_Is_Assigned() { var xaml = @"