diff --git a/TUI.Test/StackPanelTests.cs b/TUI.Test/StackPanelTests.cs index 9d8eea8..f8128ec 100644 --- a/TUI.Test/StackPanelTests.cs +++ b/TUI.Test/StackPanelTests.cs @@ -628,6 +628,43 @@ public void StackPanelVerticalDrawsEveryWrappedLineOfTextInsideABorder() Assert.AreEqual(new Position(1, 2), Assert.ContainsSingle(provider.WritesOf("world foo")).Position); } + /// + /// Tests that a horizontal panel does not ask for the height of a child it collapses for lack of + /// width, so a tall child that no longer fits leaves no blank rows and later siblings move up + /// (ktsu-dev/TUI#224). + /// + [TestMethod] + public void StackPanelHorizontalRequiredHeightIgnoresChildrenThatDoNotFit() + { + TextElement wide = new(new string('A', 20)); + TextElement tall = new("l1\nl2\nl3\nl4\nl5"); + StackPanel row = new(Orientation.Horizontal) { wide, tall }; + TextElement footer = new("footer"); + StackPanel root = [row, footer]; + root.Dimensions = new Dimensions(20, 10); + + root.ArrangeChildren(); + row.ArrangeChildren(); + + Assert.AreEqual(new Dimensions(20, 1), row.CalculateRequiredDimensions(20)); + Assert.AreEqual(Dimensions.Empty, tall.Dimensions); + Assert.AreEqual(new Dimensions(20, 1), row.Dimensions); + Assert.AreEqual(1, footer.Position.Y); + } + + /// + /// Tests that a horizontal panel still measures every child when no width constrains it. + /// + [TestMethod] + public void StackPanelHorizontalUnconstrainedMeasureCountsEveryChild() + { + StackPanel row = new(Orientation.Horizontal) { Spacing = 1 }; + row.Add(new TextElement(new string('A', 20))); + row.Add(new TextElement("l1\nl2\nl3\nl4\nl5")); + + Assert.AreEqual(new Dimensions(23, 5), row.CalculateRequiredDimensions()); + } + /// /// Tests that a horizontal panel measures word-wrapped text against the width it has, so it /// reports the wrapped height to a parent rather than one row (ktsu-dev/TUI#161). diff --git a/TUI/Elements/Layouts/StackPanel.cs b/TUI/Elements/Layouts/StackPanel.cs index 4a4a666..b7eee60 100644 --- a/TUI/Elements/Layouts/StackPanel.cs +++ b/TUI/Elements/Layouts/StackPanel.cs @@ -165,11 +165,20 @@ private Dimensions CalculateRequiredDimensionsForChildren(int? availableWidth) int maxHeight = 0; IUIElement[] visibleChildren = [.. GetVisibleChildren()]; - int totalSpacing = Math.Max(0, (visibleChildren.Length - 1) * Spacing); + int measuredChildren = 0; int consumedWidth = 0; foreach (IUIElement? child in visibleChildren) { + // A horizontal child that starts where the width has run out is collapsed when arranged, + // so it adds neither width nor height, matching the arrange pass's break (ktsu-dev/TUI#224). + if (Orientation == Orientation.Horizontal && availableWidth is int limit && consumedWidth >= limit) + { + break; + } + + measuredChildren++; + // A horizontal child gets the width its earlier siblings and the spacing between them // leave, as it does when arranged (ktsu-dev/TUI#161). Dimensions childDimensions = availableWidth switch @@ -192,6 +201,8 @@ private Dimensions CalculateRequiredDimensionsForChildren(int? availableWidth) } } + int totalSpacing = Math.Max(0, (measuredChildren - 1) * Spacing); + return Orientation == Orientation.Vertical ? new Dimensions(maxWidth, totalHeight + totalSpacing) : new Dimensions(totalWidth + totalSpacing, maxHeight);