diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 6da695407b..77c8eea0aa 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -11,6 +11,8 @@ namespace Avalonia.Utilities // smallest such that 1.0+DoubleEpsilon != 1.0 internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; + private const float FloatEpsilon = 1.192092896e-07F; + /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or /// not they are within epsilon of each other. @@ -26,6 +28,21 @@ namespace Avalonia.Utilities return (-eps < delta) && (eps > delta); } + /// + /// AreClose - Returns whether or not two floats are "close". That is, whether or + /// not they are within epsilon of each other. + /// + /// The first float to compare. + /// The second float to compare. + public static bool AreClose(float value1, float value2) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + float eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatEpsilon; + float delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + /// /// LessThan - Returns whether or not the first double is less than the second double. /// That is, whether or not the first is strictly less than *and* not within epsilon of @@ -38,6 +55,18 @@ namespace Avalonia.Utilities return (value1 < value2) && !AreClose(value1, value2); } + /// + /// LessThan - Returns whether or not the first float is less than the second float. + /// That is, whether or not the first is strictly less than *and* not within epsilon of + /// the other number. + /// + /// The first single float to compare. + /// The second single float to compare. + public static bool LessThan(float value1, float value2) + { + return (value1 < value2) && !AreClose(value1, value2); + } + /// /// GreaterThan - Returns whether or not the first double is greater than the second double. /// That is, whether or not the first is strictly greater than *and* not within epsilon of @@ -50,6 +79,18 @@ namespace Avalonia.Utilities return (value1 > value2) && !AreClose(value1, value2); } + /// + /// GreaterThan - Returns whether or not the first float is greater than the second float. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool GreaterThan(float value1, float value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + /// /// LessThanOrClose - Returns whether or not the first double is less than or close to /// the second double. That is, whether or not the first is strictly less than or within @@ -62,6 +103,18 @@ namespace Avalonia.Utilities return (value1 < value2) || AreClose(value1, value2); } + /// + /// LessThanOrClose - Returns whether or not the first float is less than or close to + /// the second float. That is, whether or not the first is strictly less than or within + /// epsilon of the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool LessThanOrClose(float value1, float value2) + { + return (value1 < value2) || AreClose(value1, value2); + } + /// /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to /// the second double. That is, whether or not the first is strictly greater than or within @@ -74,6 +127,18 @@ namespace Avalonia.Utilities return (value1 > value2) || AreClose(value1, value2); } + /// + /// GreaterThanOrClose - Returns whether or not the first float is greater than or close to + /// the second float. That is, whether or not the first is strictly greater than or within + /// epsilon of the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool GreaterThanOrClose(float value1, float value2) + { + return (value1 > value2) || AreClose(value1, value2); + } + /// /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), /// but this is faster. @@ -84,6 +149,16 @@ namespace Avalonia.Utilities return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon; } + /// + /// IsOne - Returns whether or not the float is "close" to 1. Same as AreClose(float, 1), + /// but this is faster. + /// + /// The float to compare to 1. + public static bool IsOne(float value) + { + return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon; + } + /// /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), /// but this is faster. @@ -94,6 +169,16 @@ namespace Avalonia.Utilities return Math.Abs(value) < 10.0 * DoubleEpsilon; } + /// + /// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0), + /// but this is faster. + /// + /// The float to compare to 0. + public static bool IsZero(float value) + { + return Math.Abs(value) < 10.0f * FloatEpsilon; + } + /// /// Clamps a value between a minimum and maximum value. /// diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index b38cc56a17..31101dc0f1 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -468,10 +468,11 @@ namespace Avalonia.Controls /// /// dependency property. public static readonly DirectProperty TextProperty = - AvaloniaProperty.RegisterDirect( - nameof(Text), + TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, - (o, v) => o.Text = v); + (o, v) => o.Text = v, + defaultBindingMode: BindingMode.TwoWay, + enableDataValidation: true); /// /// Identifies the @@ -1244,6 +1245,20 @@ namespace Avalonia.Controls base.OnApplyTemplate(e); } + + /// + /// Called to update the validation state for properties for which data validation is + /// enabled. + /// + /// The property. + /// The new binding value for the property. + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) + { + if (property == TextProperty) + { + DataValidationErrors.SetError(this, value.Error); + } + } /// /// Provides handling for the diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs index ccb5aef315..61f7a2a548 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -6,50 +6,89 @@ namespace Avalonia.Base.UnitTests.Utilities { public class MathUtilitiesTests { - [Fact] - public void Two_Equivalent_Double_Values_Are_Close() + private const double AnyValue = 42.42; + private readonly double _calculatedAnyValue; + private readonly double _one; + private readonly double _zero; + + public MathUtilitiesTests() { + _calculatedAnyValue = 0.0; + _one = 0.0; + _zero = 1.0; + const int N = 10; - var x = 42.42; - var y = 0.0; - var dx = x / N; + var dxAny = AnyValue / N; + var dxOne = 1.0 / N; + var dxZero = _zero / N; for (var i = 0; i < N; ++i) - y += dx; - var actual = MathUtilities.AreClose(x, y); + { + _calculatedAnyValue += dxAny; + _one += dxOne; + _zero -= dxZero; + } + } + + [Fact] + public void Two_Equivalent_Double_Values_Are_Close() + { + var actual = MathUtilities.AreClose(AnyValue, _calculatedAnyValue); Assert.True(actual); - Assert.Equal(x, Math.Round(y, 14)); + Assert.Equal(AnyValue, Math.Round(_calculatedAnyValue, 14)); + } + + [Fact] + public void Two_Equivalent_Single_Values_Are_Close() + { + var expectedValue = (float)AnyValue; + var actualValue = (float)_calculatedAnyValue; + + var actual = MathUtilities.AreClose(expectedValue, actualValue); + + Assert.True(actual); + Assert.Equal((float) Math.Round(expectedValue, 5), (float) Math.Round(actualValue, 4)); } [Fact] public void Calculated_Double_One_Is_One() { - const int N = 10; - var dx = 1.0 / N; - var x = 0.0; + var actual = MathUtilities.IsOne(_one); + + Assert.True(actual); + Assert.Equal(1.0, Math.Round(_one, 15)); + } + + [Fact] + public void Calculated_Single_One_Is_One() + { + var actualValue = (float)_one; - for (var i = 0; i < N; ++i) - x += dx; - var actual = MathUtilities.IsOne(x); + var actual = MathUtilities.IsOne(actualValue); Assert.True(actual); - Assert.Equal(1.0, Math.Round(x, 15)); + Assert.Equal(1.0f, (float) Math.Round(actualValue, 7)); } [Fact] public void Calculated_Double_Zero_Is_Zero() { - const int N = 10; - var x = 1.0; - var dx = x / N; - - for (var i = 0; i < N; ++i) - x -= dx; - var actual = MathUtilities.IsZero(x); + var actual = MathUtilities.IsZero(_zero); + + Assert.True(actual); + Assert.Equal(0.0, Math.Round(_zero, 15)); + } + + [Fact] + public void Calculated_Single_Zero_Is_Zero() + { + var actualValue = (float)_zero; + + var actual = MathUtilities.IsZero(actualValue); Assert.True(actual); - Assert.Equal(0.0, Math.Round(x, 15)); + Assert.Equal(0.0f, (float) Math.Round(actualValue, 7)); } [Fact]