diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 446b366dc8..1ea369954a 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -30,6 +30,21 @@ namespace Avalonia.Utilities return (-eps < delta) && (eps > delta); } + /// + /// AreClose - Returns whether or not two doubles are "close". That is, whether or + /// not they are within epsilon of each other. + /// + /// The first double to compare. + /// The second double to compare. + /// The fixed epsilon value used to compare. + public static bool AreClose(double value1, double value2, double eps) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + double delta = value1 - value2; + 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. diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index a2d53f4f06..26c8464576 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -609,10 +609,20 @@ namespace Avalonia.Controls private void MoveSplitter(double horizontalChange, double verticalChange) { Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter"); - - // Calculate the offset to adjust the splitter. - var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange; - + + // Calculate the offset to adjust the splitter. If layout rounding is enabled, we + // need to round to an integer physical pixel value to avoid round-ups of children that + // expand the bounds of the Grid. In practice this only happens in high dpi because + // horizontal/vertical offsets here are never fractional (they correspond to mouse movement + // across logical pixels). Rounding error only creeps in when converting to a physical + // display with something other than the logical 96 dpi. + double delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange; + + if (UseLayoutRounding) + { + delta = LayoutHelper.RoundLayoutValue(delta, LayoutHelper.GetLayoutScale(this)); + } + DefinitionBase definition1 = _resizeData.Definition1; DefinitionBase definition2 = _resizeData.Definition2; @@ -622,11 +632,11 @@ namespace Avalonia.Controls double actualLength2 = GetActualLength(definition2); // When splitting, Check to see if the total pixels spanned by the definitions - // is the same asbefore starting resize. If not cancel the drag + // is the same as before starting resize. If not cancel the drag. if (_resizeData.SplitBehavior == SplitBehavior.Split && !MathUtilities.AreClose( actualLength1 + actualLength2, - _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength)) + _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, LayoutHelper.LayoutEpsilon)) { CancelResize(); diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index b28927c969..472727823a 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -378,7 +378,7 @@ namespace Avalonia.Controls.Presenters var useLayoutRounding = UseLayoutRounding; var availableSize = finalSize; var sizeForChild = availableSize; - var scale = GetLayoutScale(); + var scale = LayoutHelper.GetLayoutScale(this); var originX = offset.X; var originY = offset.Y; @@ -462,18 +462,6 @@ namespace Avalonia.Controls.Presenters PseudoClasses.Set(":empty", Content is null); } - private double GetLayoutScale() - { - var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; - - if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) - { - throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}"); - } - - return result; - } - private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) { var host = e.NewValue as IContentPresenterHost; diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index 3708748ad1..2fe59b49ca 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/src/Avalonia.Layout/LayoutHelper.cs @@ -9,6 +9,12 @@ namespace Avalonia.Layout /// public static class LayoutHelper { + /// + /// Epsilon value used for certain layout calculations. + /// Based on the value in WPF LayoutDoubleUtil. + /// + public static double LayoutEpsilon { get; } = 0.00000153; + /// /// Calculates a control's size based on its , /// , , @@ -82,6 +88,25 @@ namespace Avalonia.Layout InnerInvalidateMeasure(control); } + /// + /// Obtains layout scale of the given control. + /// + /// The control. + /// Thrown when control has no root or returned layout scaling is invalid. + public static double GetLayoutScale(ILayoutable control) + { + var visualRoot = control.VisualRoot; + + var result = (visualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; + + if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) + { + throw new Exception($"Invalid LayoutScaling returned from {visualRoot.GetType()}"); + } + + return result; + } + /// /// Rounds a size to integer values for layout purposes, compensating for high DPI screen /// coordinates. diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index aca2965ea6..a1d00017ed 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -590,7 +590,7 @@ namespace Avalonia.Layout if (UseLayoutRounding) { - var scale = GetLayoutScale(); + var scale = LayoutHelper.GetLayoutScale(this); width = LayoutHelper.RoundLayoutValue(width, scale); height = LayoutHelper.RoundLayoutValue(height, scale); } @@ -652,7 +652,7 @@ namespace Avalonia.Layout var horizontalAlignment = HorizontalAlignment; var verticalAlignment = VerticalAlignment; var size = availableSizeMinusMargins; - var scale = GetLayoutScale(); + var scale = LayoutHelper.GetLayoutScale(this); var useLayoutRounding = UseLayoutRounding; if (horizontalAlignment != HorizontalAlignment.Stretch) @@ -833,17 +833,5 @@ namespace Avalonia.Layout { return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0)); } - - private double GetLayoutScale() - { - var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; - - if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) - { - throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}"); - } - - return result; - } } }