From bce6e08561ec6359e459f7c94a5b160374d2ced0 Mon Sep 17 00:00:00 2001 From: Dong Bin Date: Wed, 3 Sep 2025 15:26:38 +0800 Subject: [PATCH] feat: add tests. --- .../Panels/WrapPanelWithTrailingItem.cs | 67 +++++++- .../WrapPanelWithTrailingItemTests.cs | 155 ++++++++++++++++++ 2 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 tests/HeadlessTest.Ursa/Controls/PanelTests/WrapPanelWithTrailingItemTests.cs diff --git a/src/Ursa/Controls/Panels/WrapPanelWithTrailingItem.cs b/src/Ursa/Controls/Panels/WrapPanelWithTrailingItem.cs index d2600a3..d6cefed 100644 --- a/src/Ursa/Controls/Panels/WrapPanelWithTrailingItem.cs +++ b/src/Ursa/Controls/Panels/WrapPanelWithTrailingItem.cs @@ -30,15 +30,21 @@ public class WrapPanelWithTrailingItem: Panel base.OnPropertyChanged(change); if (change.Property == TrailingItemProperty) { - if(change.GetOldValue() is { } oldValue) + if(change.GetOldValue() is { } oldValue) { VisualChildren.Remove(oldValue); - LogicalChildren.Remove(oldValue); + if (!IsItemsHost) + { + LogicalChildren.Remove(oldValue); + } } - if(change.GetNewValue() is {} newValue) + if(change.GetNewValue() is {} newValue) { VisualChildren.Add(newValue); - LogicalChildren.Add(newValue); + if (!IsItemsHost) + { + LogicalChildren.Add(newValue); + } } } @@ -80,6 +86,7 @@ public class WrapPanelWithTrailingItem: Panel } var last = TrailingItem; + if (last is null) return new Size(availableSize.Width, totalHeight); last.Measure(availableSize); double lastDeltaX = availableSize.Width - currentLineX; // If width is not enough, add a new line, and recalculate total height @@ -96,4 +103,56 @@ public class WrapPanelWithTrailingItem: Panel return new Size(availableSize.Width, totalHeight); } + + protected override Size ArrangeOverride(Size finalSize) + { + double currentLineX = 0; + double currentLineHeight = 0; + double totalHeight = 0; + var children = Children; + for (int i = 0; i < children.Count; i++) + { + var child = children[i]; + double deltaX = finalSize.Width - currentLineX; + // Width is enough to place next child + if (MathHelpers.GreaterThan(deltaX, child.DesiredSize.Width)) + { + child.Arrange(new Rect(currentLineX, totalHeight, child.DesiredSize.Width, Math.Max(child.DesiredSize.Height, currentLineHeight))); + currentLineX += child.DesiredSize.Width; + currentLineHeight = Math.Max(currentLineHeight, child.DesiredSize.Height); + } + // Width is not enough to place next child + // reset currentLineX and currentLineHeight + // accumulate last line height to total height. + // Notice: last line height accumulation only happens when restarting a new line, so it needs to finally add one more time outside iteration. + else + { + totalHeight += currentLineHeight; + child.Arrange(new Rect(0, totalHeight, Math.Min(child.DesiredSize.Width, finalSize.Width), child.DesiredSize.Height)); + currentLineX = child.DesiredSize.Width; + currentLineHeight = child.DesiredSize.Height; + } + } + + var last = TrailingItem; + if (last is null) return new Size(finalSize.Width, totalHeight); + double lastDeltaX = finalSize.Width - currentLineX; + // If width is not enough, add a new line, and recalculate total height + if (lastDeltaX < 30) + { + totalHeight += currentLineHeight; + last.Arrange(new Rect(0, totalHeight, finalSize.Width, last.DesiredSize.Height)); + totalHeight += last.DesiredSize.Height; + } + else + { + currentLineHeight = children.Count == 1 ? finalSize.Height : currentLineHeight; + last.Arrange(new Rect(currentLineX, totalHeight, lastDeltaX, + Math.Max(currentLineHeight, last.DesiredSize.Height))); + currentLineHeight = Math.Max(currentLineHeight, last.DesiredSize.Height); + totalHeight += currentLineHeight; + } + + return new Size(finalSize.Width, totalHeight); + } } \ No newline at end of file diff --git a/tests/HeadlessTest.Ursa/Controls/PanelTests/WrapPanelWithTrailingItemTests.cs b/tests/HeadlessTest.Ursa/Controls/PanelTests/WrapPanelWithTrailingItemTests.cs new file mode 100644 index 0000000..0119e0c --- /dev/null +++ b/tests/HeadlessTest.Ursa/Controls/PanelTests/WrapPanelWithTrailingItemTests.cs @@ -0,0 +1,155 @@ +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Avalonia.Layout; +using Avalonia.LogicalTree; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Ursa.Controls.Panels; + +namespace HeadlessTest.Ursa.Controls.PanelTests; + +public class WrapPanelWithTrailingItemTests +{ + [Fact] + public void Visual_Children_Correct() + { + var panel = new WrapPanelWithTrailingItem(); + var child1 = new Button { Content = "Button 1"}; + var child2 = new Button { Content = "Button 2"}; + var trailing = new Button { Content = "Trailing"}; + + panel.Children.Add(child1); + panel.Children.Add(child2); + panel.TrailingItem = trailing; + + var visualChildren = panel.GetVisualChildren().ToList(); + Assert.Equal(3, visualChildren.Count); + Assert.Equal(child1, visualChildren[0]); + Assert.Equal(child2, visualChildren[1]); + Assert.Equal(trailing, visualChildren[2]); + + var child3 = new Button { Content = "Button 3"}; + panel.Children.Add(child3); + + visualChildren = panel.GetVisualChildren().ToList(); + Assert.Equal(4, visualChildren.Count); + Assert.Equal(child1, visualChildren[0]); + Assert.Equal(child2, visualChildren[1]); + Assert.Equal(child3, visualChildren[2]); + Assert.Equal(trailing, visualChildren[3]); + + var trailing2 = new Button { Content = "Trailing2"}; + panel.TrailingItem = trailing2; + + visualChildren = panel.GetVisualChildren().ToList(); + Assert.Equal(4, visualChildren.Count); + Assert.Equal(child1, visualChildren[0]); + Assert.Equal(child2, visualChildren[1]); + Assert.Equal(child3, visualChildren[2]); + Assert.Equal(trailing2, visualChildren[3]); + + panel.Children.Remove(child2); + + visualChildren = panel.GetVisualChildren().ToList(); + Assert.Equal(3, visualChildren.Count); + Assert.Equal(child1, visualChildren[0]); + Assert.Equal(child3, visualChildren[1]); + Assert.Equal(trailing2, visualChildren[2]); + } + + [Fact] + // Items Appears in Logical Children because IsItemsHost is false for individual Panels + public void Logical_Children_Correct() + { + var panel = new WrapPanelWithTrailingItem(); + var child1 = new Button { Content = "Button 1"}; + var child2 = new Button { Content = "Button 2"}; + var trailing = new Button { Content = "Trailing"}; + + panel.Children.Add(child1); + panel.Children.Add(child2); + panel.TrailingItem = trailing; + + var logicalChildren = panel.GetLogicalChildren().ToList(); + Assert.Equal(3, logicalChildren.Count); + Assert.Equal(child1, logicalChildren[0]); + Assert.Equal(child2, logicalChildren[1]); + Assert.Equal(trailing, logicalChildren[2]); + + var child3 = new Button { Content = "Button 3"}; + panel.Children.Add(child3); + + logicalChildren = panel.GetLogicalChildren().ToList(); + Assert.Equal(4, logicalChildren.Count); + Assert.Equal(child1, logicalChildren[0]); + Assert.Equal(child2, logicalChildren[1]); + Assert.Equal(child3, logicalChildren[2]); + Assert.Equal(trailing, logicalChildren[3]); + + var trailing2 = new Button { Content = "Trailing2"}; + panel.TrailingItem = trailing2; + + logicalChildren = panel.GetLogicalChildren().ToList(); + Assert.Equal(4, logicalChildren.Count); + Assert.Equal(child1, logicalChildren[0]); + Assert.Equal(child2, logicalChildren[1]); + Assert.Equal(child3, logicalChildren[2]); + Assert.Equal(trailing2, logicalChildren[3]); + + panel.Children.Remove(child2); + + logicalChildren = panel.GetLogicalChildren().ToList(); + Assert.Equal(3, logicalChildren.Count); + Assert.Equal(child1, logicalChildren[0]); + Assert.Equal(child3, logicalChildren[1]); + Assert.Equal(trailing2, logicalChildren[2]); + } + + [AvaloniaFact] + public void Measure_Arrange_Children() + { + var window = new Window() + { + Height = 1000, Width = 1000, VerticalContentAlignment = VerticalAlignment.Stretch + }; + var panel = new WrapPanelWithTrailingItem(); + var child1 = new Button { Content = "Button 1", Width = 200, Height = 100 }; + var child2 = new Button { Content = "Button 2", Width = 300, Height = 100 }; + var trailing = new Button + { + Content = "Trailing", + Height = 100, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + window.Content = panel; + panel.Children.Add(child1); + panel.Children.Add(child2); + panel.TrailingItem = trailing; + + window.Show(); + Dispatcher.UIThread.RunJobs(); + Assert.Equal(200, child1.Bounds.Width); + Assert.Equal(300, child2.Bounds.Width); + Assert.Equal(500, trailing.Bounds.Width); + + panel.Width = 600; + Dispatcher.UIThread.RunJobs(); + Assert.Equal(200, child1.Bounds.Width); + Assert.Equal(300, child2.Bounds.Width); + Assert.Equal(100, trailing.Bounds.Width); + + panel.Width = 510; + Dispatcher.UIThread.RunJobs(); + Assert.Equal(200, child1.Bounds.Width); + Assert.Equal(300, child2.Bounds.Width); + Assert.Equal(510, trailing.Bounds.Width); + Assert.Equal(100, trailing.Bounds.Y); + + panel.Width = 300; + Dispatcher.UIThread.RunJobs(); + Assert.Equal(200, child1.Bounds.Width); + Assert.Equal(300, child2.Bounds.Width); + Assert.Equal(300, trailing.Bounds.Width); + Assert.Equal(200, trailing.Bounds.Y); + } +} \ No newline at end of file