diff --git a/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs b/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs index 3ea600b18..a9437e33a 100644 --- a/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs +++ b/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs @@ -3,7 +3,6 @@ using System.Linq; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Layout; @@ -33,7 +32,8 @@ public Orientation Orientation /// Defines the Proportion attached property. /// public static readonly AttachedProperty ProportionProperty = - AvaloniaProperty.RegisterAttached("Proportion", double.NaN, false, BindingMode.TwoWay); + AvaloniaProperty.RegisterAttached("Proportion", double.NaN, false, + BindingMode.TwoWay); /// /// Gets the value of the Proportion attached property on the specified control. @@ -59,7 +59,8 @@ public static void SetProportion(AvaloniaObject control, double value) /// Defines the IsCollapsed attached property. /// public static readonly AttachedProperty IsCollapsedProperty = - AvaloniaProperty.RegisterAttached("IsCollapsed", false, false, BindingMode.TwoWay); + AvaloniaProperty.RegisterAttached("IsCollapsed", false, false, + BindingMode.TwoWay); /// /// Gets the value of the IsCollapsed attached property on the specified control. @@ -191,7 +192,7 @@ private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls chil continue; } } - + var thickness = proportionalStackPanelSplitter.Thickness; totalThickness += thickness; } @@ -209,11 +210,11 @@ protected override Size MeasureOverride(Size constraint) { var horizontal = Orientation == Orientation.Horizontal; - if (constraint == Size.Infinity - || (horizontal && double.IsInfinity(constraint.Width)) + if (constraint == Size.Infinity + || (horizontal && double.IsInfinity(constraint.Width)) || (!horizontal && double.IsInfinity(constraint.Height))) { - throw new Exception("Proportional StackPanel cannot be inside a control that offers infinite space."); + throw new Exception("Proportional StackPanel cannot be inside a control that offers infinite space."); } var usedWidth = 0.0; @@ -225,7 +226,8 @@ protected override Size MeasureOverride(Size constraint) AssignProportions(Children); var needsNextSplitter = false; - + double sumOfFractions = 0; + // Measure each of the Children for (var i = 0; i < Children.Count; i++) { @@ -255,14 +257,16 @@ protected override Size MeasureOverride(Size constraint) { case Orientation.Horizontal: { - var width = CalculateDimension(constraint.Width - splitterThickness, proportion, i); + var width = CalculateDimension(constraint.Width - splitterThickness, proportion, + ref sumOfFractions); var size = constraint.WithWidth(width); control.Measure(size); break; } case Orientation.Vertical: { - var height = CalculateDimension(constraint.Height - splitterThickness, proportion, i); + var height = CalculateDimension(constraint.Height - splitterThickness, proportion, + ref sumOfFractions); var size = constraint.WithHeight(height); control.Measure(size); break; @@ -299,7 +303,8 @@ protected override Size MeasureOverride(Size constraint) } else { - usedWidth += CalculateDimension(constraint.Width - splitterThickness, proportion, i); + usedWidth += CalculateDimension(constraint.Width - splitterThickness, proportion, + ref sumOfFractions); } break; @@ -314,7 +319,8 @@ protected override Size MeasureOverride(Size constraint) } else { - usedHeight += CalculateDimension(constraint.Height - splitterThickness, proportion, i); + usedHeight += CalculateDimension(constraint.Height - splitterThickness, proportion, + ref sumOfFractions); } break; @@ -343,6 +349,7 @@ protected override Size ArrangeOverride(Size arrangeSize) AssignProportions(Children); var needsNextSplitter = false; + double sumOfFractions = 0; for (var i = 0; i < Children.Count; i++) { @@ -397,7 +404,7 @@ protected override Size ArrangeOverride(Size arrangeSize) else { Debug.Assert(!double.IsNaN(proportion)); - var width = CalculateDimension(arrangeSize.Width - splitterThickness, proportion, i); + var width = CalculateDimension(arrangeSize.Width - splitterThickness, proportion, ref sumOfFractions); remainingRect = remainingRect.WithWidth(width); left += width; } @@ -414,7 +421,7 @@ protected override Size ArrangeOverride(Size arrangeSize) else { Debug.Assert(!double.IsNaN(proportion)); - var height = CalculateDimension(arrangeSize.Height - splitterThickness, proportion, i); + var height = CalculateDimension(arrangeSize.Height - splitterThickness, proportion, ref sumOfFractions); remainingRect = remainingRect.WithHeight(height); top += height; } @@ -430,21 +437,26 @@ protected override Size ArrangeOverride(Size arrangeSize) return arrangeSize; } - - private double CalculateDimension(double dimension, double proportion, int childIndex) + + private double CalculateDimension( + double dimension, + double proportion, + ref double sumOfFractions) { var childDimension = dimension * proportion; var flooredChildDimension = Math.Floor(childDimension); - - // checks whether division doesn't leave a fraction - if (childDimension == flooredChildDimension) - return Math.Max(0, flooredChildDimension); - else + + // sums fractions from the division + sumOfFractions += childDimension - flooredChildDimension; + + // if the sum of fractions made up a whole pixel/pixels, add it to the dimension + if (Math.Round(sumOfFractions, 1) - Math.Clamp(Math.Floor(sumOfFractions), 1, double.MaxValue) >= 0) { - // if it leaves, it assigns the divided pixel to the first control in proportional split - int isFirst = childIndex == 0 ? 1 : 0; - return Math.Max(0, flooredChildDimension + isFirst); + sumOfFractions -= Math.Round(sumOfFractions); + return Math.Max(0, flooredChildDimension + 1); } + + return Math.Max(0, flooredChildDimension); } /// diff --git a/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs b/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs index e306acf2a..cd705fcbb 100644 --- a/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs +++ b/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs @@ -65,7 +65,7 @@ private static IEnumerable GetBorderTestsData() yield return [0.5, 604, 300, 300]; yield return [0.25, 604, 150, 450]; yield return [0.6283185307179586476925286766559, 604, 377, 223]; - yield return [0.3141592653589793238462643383279, 604, 189, 411]; + yield return [0.3141592653589793238462643383279, 604, 188, 412]; } [Theory] @@ -187,10 +187,11 @@ public void Lays_Out_Children_Default() target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); + // values have to add up to width/height of the parent control Assert.Equal(new Size(1000, 500), target.Bounds.Size); - Assert.Equal(new Rect(0, 0, 331, 500), target.Children[0].Bounds); - Assert.Equal(new Rect(331, 0, 4, 500), target.Children[1].Bounds); - Assert.Equal(new Rect(335, 0, 331, 500), target.Children[2].Bounds); + Assert.Equal(new Rect(0, 0, 330, 500), target.Children[0].Bounds); + Assert.Equal(new Rect(330, 0, 4, 500), target.Children[1].Bounds); + Assert.Equal(new Rect(334, 0, 331, 500), target.Children[2].Bounds); Assert.Equal(new Rect(665, 0, 4, 500), target.Children[3].Bounds); Assert.Equal(new Rect(669, 0, 331, 500), target.Children[4].Bounds); }