Browse Source

Merge pull request #5767 from MarchingCube/fix-grid-splitter-resize

Fix GridSplitter not resizing correctly
Max Katz 4 years ago
parent
commit
e2f618dcb9

+ 15 - 0
src/Avalonia.Base/Utilities/MathUtilities.cs

@@ -30,6 +30,21 @@ namespace Avalonia.Utilities
             return (-eps < delta) && (eps > delta);
         }
 
+        /// <summary>
+        /// AreClose - Returns whether or not two doubles are "close".  That is, whether or
+        /// not they are within epsilon of each other.
+        /// </summary>
+        /// <param name="value1"> The first double to compare. </param>
+        /// <param name="value2"> The second double to compare. </param>
+        /// <param name="eps"> The fixed epsilon value used to compare.</param>
+        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);
+        }
+
         /// <summary>
         /// AreClose - Returns whether or not two floats are "close".  That is, whether or 
         /// not they are within epsilon of each other.

+ 16 - 6
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();
 

+ 1 - 13
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;

+ 25 - 0
src/Avalonia.Layout/LayoutHelper.cs

@@ -9,6 +9,12 @@ namespace Avalonia.Layout
     /// </summary>
     public static class LayoutHelper
     {
+        /// <summary>
+        /// Epsilon value used for certain layout calculations.
+        /// Based on the value in WPF LayoutDoubleUtil.
+        /// </summary>
+        public static double LayoutEpsilon { get; } = 0.00000153;
+
         /// <summary>
         /// Calculates a control's size based on its <see cref="ILayoutable.Width"/>,
         /// <see cref="ILayoutable.Height"/>, <see cref="ILayoutable.MinWidth"/>,
@@ -82,6 +88,25 @@ namespace Avalonia.Layout
             InnerInvalidateMeasure(control);
         }
 
+        /// <summary>
+        /// Obtains layout scale of the given control.
+        /// </summary>
+        /// <param name="control">The control.</param>
+        /// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception>
+        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;
+        }
+
         /// <summary>
         /// Rounds a size to integer values for layout purposes, compensating for high DPI screen
         /// coordinates.

+ 2 - 14
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;
-        }
     }
 }