diff --git a/src/Avalonia.Controls/Primitives/UniformGrid.cs b/src/Avalonia.Controls/Primitives/UniformGrid.cs index fea35d867a3..32ebabba2c9 100644 --- a/src/Avalonia.Controls/Primitives/UniformGrid.cs +++ b/src/Avalonia.Controls/Primitives/UniformGrid.cs @@ -89,27 +89,45 @@ protected override Size MeasureOverride(Size availableSize) protected override Size ArrangeOverride(Size finalSize) { - var x = FirstColumn; - var y = 0; - - var width = finalSize.Width / _columns; - var height = finalSize.Height / _rows; + var columnIndex = FirstColumn; + var columnWidth = finalSize.Width / _columns; + var rowIndex = 0; + var rowHeight = finalSize.Height / _rows; + var x = 0.0; + var y = 0.0; + var nextY = 0.0; foreach (var child in Children) { - if (!child.IsVisible) + if (child.IsVisible) { - continue; - } + // Layout scaling may cause the child to take a different size than the one + // requested. Layout each each child with it's top-left aligned against the + // previous column/row bounds and request the bottom-right to be placed in + // the ideal position. + var topLeft = new Point(x, y); + var bottomRight = new Point((columnIndex + 1) * columnWidth, (rowIndex + 1) * rowHeight); + + child.Arrange(new Rect(topLeft, bottomRight)); - child.Arrange(new Rect(x * width, y * height, width, height)); + x = child.Bounds.Right; + nextY = Math.Max(nextY, child.Bounds.Bottom); + } + else + { + x += columnWidth; + nextY = Math.Max(nextY, rowHeight); + } - x++; + columnIndex++; - if (x >= _columns) + if (columnIndex >= _columns) { x = 0; - y++; + y = nextY; + nextY = 0; + columnIndex = 0; + rowIndex++; } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs index 340bd09611e..cbfdad43832 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/UniformGridTests.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Primitives; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -140,5 +141,75 @@ public void Not_Visible_Children_Are_Ignored() // 2 * 2 grid Assert.Equal(new Size(2 * 50, 2 * 70), target.Bounds.Size); } + + [Fact] + public void Children_Do_Not_Overlap_With_125_Percent_Scaling_1() + { + // Issue #17699 + var target = new UniformGrid + { + Columns = 2, + Children = + { + new Border(), + new Border(), + new Border(), + new Border(), + } + }; + + var root = new TestRoot + { + LayoutScaling = 1.25, + Child = new Border + { + Width = 100, + Height = 100, + Child = target, + } + }; + + root.ExecuteInitialLayoutPass(); + + Assert.Equal(new(0, 0, 50.4, 50.4), target.Children[0].Bounds); + Assert.Equal(new(50.4, 0, 49.6, 50.4), target.Children[1].Bounds); + Assert.Equal(new(0, 50.4, 50.4, 49.6), target.Children[2].Bounds); + Assert.Equal(new(50.4, 50.4, 49.6, 49.6), target.Children[3].Bounds); + } + + [Fact] + public void Children_Do_Not_Overlap_With_125_Percent_Scaling_2() + { + // Issue #17699 + var target = new UniformGrid + { + Columns = 4, + Children = + { + new Border(), + new Border(), + new Border(), + new Border(), + } + }; + + var root = new TestRoot + { + LayoutScaling = 1.25, + Child = new Border + { + Width = 100, + Height = 100, + Child = target, + } + }; + + root.ExecuteInitialLayoutPass(); + + Assert.Equal(new(0, 0, 25.6, 100), target.Children[0].Bounds); + Assert.Equal(new(25.6, 0, 24.8, 100), target.Children[1].Bounds); + Assert.Equal(new(50.4, 0, 24.8, 100), target.Children[2].Bounds); + Assert.Equal(new(75.2, 0, 24.8, 100), target.Children[3].Bounds); + } } } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 788f2b3dae4..9ba304cab75 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -55,7 +55,7 @@ public TestRoot(bool useGlobalStyles, Control child) internal ILayoutManager LayoutManager { get; set; } ILayoutManager ILayoutRoot.LayoutManager => LayoutManager; - public double RenderScaling => 1; + public double RenderScaling => LayoutScaling; internal IRenderer Renderer { get; set; } internal IHitTester HitTester { get; set; }