diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs
index 404d19906a..0851dbaea9 100644
--- a/src/Avalonia.Base/Layout/LayoutHelper.cs
+++ b/src/Avalonia.Base/Layout/LayoutHelper.cs
@@ -251,6 +251,17 @@ namespace Avalonia.Layout
{
double newValue;
+ // Round the value to avoid FP errors. This is needed because if `value` has a floating
+ // point precision error (e.g. 79.333333333333343) then when it's multiplied by
+ // `dpiScale` and rounded up, it will be rounded up to a value one greater than it
+ // should be.
+#if NET6_0_OR_GREATER
+ value = Math.Round(value, 8, MidpointRounding.ToZero);
+#else
+ // MidpointRounding.ToZero isn't available in netstandard2.0.
+ value = Math.Truncate(value * 1e8) / 1e8;
+#endif
+
// If DPI == 1, don't use DPI-aware rounding.
if (!MathUtilities.IsOne(dpiScale))
{
diff --git a/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs
new file mode 100644
index 0000000000..421a5acb28
--- /dev/null
+++ b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+
+namespace Avalonia.Controls.Converters
+{
+ ///
+ /// Converter that will do nothing (not update bound values) when a null value is encountered.
+ /// This converter enables binding nullable with non-nullable properties in some scenarios.
+ ///
+ public class DoNothingForNullConverter : IValueConverter
+ {
+ ///
+ public object? Convert(
+ object? value,
+ Type targetType,
+ object? parameter,
+ CultureInfo culture)
+ {
+ return value ?? BindingOperations.DoNothing;
+ }
+
+ ///
+ public object? ConvertBack(
+ object? value,
+ Type targetType,
+ object? parameter,
+ CultureInfo culture)
+ {
+ return value ?? BindingOperations.DoNothing;
+ }
+ }
+}
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index 59cc48975f..dab4142001 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -11,6 +11,7 @@
+
48
30
@@ -205,7 +206,7 @@
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index ecc7cd98bb..eb9f38894d 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.Utils
_backgroundGeometryCache = null;
}
- if (boundRect.Width != 0 && innerRect.Height != 0)
+ if (boundRect.Width != 0 && boundRect.Height != 0)
{
var borderGeometryKeypoints =
new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
index 7af7d1cee2..e9ddc8e8a3 100644
--- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
@@ -78,7 +78,7 @@ namespace Avalonia.Controls.UnitTests
}
};
- var root = CreatedRoot(1.5, target);
+ var root = CreateRoot(1.5, target);
root.LayoutManager.ExecuteInitialLayoutPass();
@@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests
}
};
- var root = CreatedRoot(1.5, target);
+ var root = CreateRoot(1.5, target);
root.LayoutManager.ExecuteInitialLayoutPass();
@@ -111,7 +111,58 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(104, 104), target.DesiredSize);
}
- private static TestRoot CreatedRoot(
+ [Fact]
+ public void Measure_Arranges_Child_To_Rounded_BorderThickness()
+ {
+ Canvas child;
+ var target = new Border
+ {
+ BorderThickness = new Thickness(1),
+ Width = 82,
+ Height = 82,
+ Child = child = new Canvas(),
+ };
+
+ var root = CreateRoot(1.5, target);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666
+ // - Size of 82 needs no rounding
+ // - Minus border thickness, space for child is 82 - 2.6666 = 79.3333
+ Assert.Equal(1.3333, child.Bounds.Left, 3);
+ Assert.Equal(1.3333, child.Bounds.Top, 3);
+ Assert.Equal(79.3333, child.Bounds.Width, 3);
+ Assert.Equal(79.3333, child.Bounds.Height, 3);
+ }
+
+ [Fact]
+ public void Measure_Arranges_Child_With_Rounded_Margin()
+ {
+ Border child;
+ var target = new Border
+ {
+ Width = 220,
+ Height = 220,
+ Child = child = new Border
+ {
+ Margin = new Thickness(0, 25, 25, 25),
+ },
+ };
+
+ var root = CreateRoot(1.5, target);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ // - 25 margin gets rounded up to 25.3333
+ // - Size of 220 needs no rounding
+ Assert.Equal(0, child.Bounds.Left, 3);
+ Assert.Equal(25.3333, child.Bounds.Top, 3);
+ Assert.Equal(194.6666, child.Bounds.Width, 3);
+ Assert.Equal(169.3333, child.Bounds.Height, 3);
+ }
+
+ private static TestRoot CreateRoot(
double scaling,
Control child,
Size? constraint = null)