From c0b6d167aeaa5ed7b6d047bec1c10244ca2bad0d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 05:27:49 +0000 Subject: [PATCH] fix: stretch StackPanel children across the cross axis so alignment applies [patch] A StackPanel gave each child its measured size on both axes, so a child's box was exactly as wide (vertical) or tall (horizontal) as its content and HorizontalAlignment.Center/Right had no room to act. Children now span the panel on the cross axis and keep their measured size on the stacking axis, so stacking offsets are unchanged. Fixes ktsu-dev/TUI#158 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015YFg3aG1od37a8RytUTWQy --- TUI.Test/StackPanelTests.cs | 55 +++++++++++++++++++++++++++- TUI.Test/UIApplicationResizeTests.cs | 2 +- TUI/Elements/Layouts/StackPanel.cs | 9 +++-- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/TUI.Test/StackPanelTests.cs b/TUI.Test/StackPanelTests.cs index b6d436a..b08469a 100644 --- a/TUI.Test/StackPanelTests.cs +++ b/TUI.Test/StackPanelTests.cs @@ -510,11 +510,64 @@ public void StackPanelVerticalMeasuresWidthIndependentElementsAtTheirOwnSize() FixedSizeElementBase derived = new() { Dimensions = new Dimensions(4, 3) }; Assert.AreEqual(direct.Size, ((IUIElement)direct).CalculateRequiredDimensions(1)); - Assert.AreEqual(new Dimensions(3, 2), direct.Dimensions); + // The height is the measured one; the width spans the panel (ktsu-dev/TUI#158) + Assert.AreEqual(new Dimensions(10, 2), direct.Dimensions); Assert.AreEqual(new Position(0, 2), next.Position); Assert.AreEqual(derived.CalculateRequiredDimensions(), derived.CalculateRequiredDimensions(1)); } + /// + /// A vertical stack gives each child the panel's full width, so a centered or right-aligned + /// child has room to align in (ktsu-dev/TUI#158). + /// + /// The horizontal alignment under test. + /// The column the text should be written at. + [TestMethod] + [DataRow(HorizontalAlignment.Left, 0)] + [DataRow(HorizontalAlignment.Center, 19)] + [DataRow(HorizontalAlignment.Right, 38)] + public void StackPanelVerticalChildAlignsWithinThePanelWidth(HorizontalAlignment alignment, int expectedX) + { + // Arrange + TextElement text = new("hi") { HorizontalAlignment = alignment }; + StackPanel panel = [text]; + panel.Position = Position.Origin; + panel.Dimensions = new Dimensions(40, 10); + RecordingConsoleProvider provider = new(); + + // Act + panel.ArrangeChildren(); + panel.Render(provider); + + // Assert + Assert.AreEqual(new Dimensions(40, 1), text.Dimensions); + Assert.AreEqual(expectedX, provider.WritesOf("hi").Single().Position.X); + } + + /// + /// A horizontal stack gives each child the panel's full height and keeps the measured width on + /// the stacking axis, so the stacking offsets do not change (ktsu-dev/TUI#158). + /// + [TestMethod] + public void StackPanelHorizontalChildSpansThePanelHeight() + { + // Arrange + TextElement first = new("abc"); + TextElement second = new("de"); + StackPanel panel = new(Orientation.Horizontal) { Spacing = 1 }; + panel.AddChild(first); + panel.AddChild(second); + panel.Dimensions = new Dimensions(20, 5); + + // Act + panel.ArrangeChildren(); + + // Assert + Assert.AreEqual(new Dimensions(3, 5), first.Dimensions); + Assert.AreEqual(new Dimensions(2, 5), second.Dimensions); + Assert.AreEqual(new Position(4, 0), second.Position); + } + /// /// Tests that a container that does not use the available width, such as BorderElement, measures /// the same with or without one. diff --git a/TUI.Test/UIApplicationResizeTests.cs b/TUI.Test/UIApplicationResizeTests.cs index 3e23878..e76d27f 100644 --- a/TUI.Test/UIApplicationResizeTests.cs +++ b/TUI.Test/UIApplicationResizeTests.cs @@ -90,7 +90,7 @@ public void RenderAfterAResizeRelaysOutEveryContainerBeneathTheRoot() UIApplication app = new(provider) { RootElement = root }; app.Render(); Assert.AreEqual(LaunchSize.WithoutPadding(root.Padding), panel.Dimensions, "The panel should start out filling the terminal, inside the border"); - Assert.AreEqual(WideText.Length, leaf.Dimensions.Width, "The label should start out at its full width"); + Assert.AreEqual(panel.Dimensions.Width, leaf.Dimensions.Width, "The label should start out spanning the panel"); // Act provider.Dimensions = ResizedSize; diff --git a/TUI/Elements/Layouts/StackPanel.cs b/TUI/Elements/Layouts/StackPanel.cs index d1858f8..321c565 100644 --- a/TUI/Elements/Layouts/StackPanel.cs +++ b/TUI/Elements/Layouts/StackPanel.cs @@ -99,10 +99,11 @@ protected override void OnArrangeChildren() if (Orientation == Orientation.Vertical) { - // Vertical stacking + // Vertical stacking. The child spans the panel's width rather than its own measured + // width, so it has room to apply its own horizontal alignment (ktsu-dev/TUI#158). child.Position = contentPosition.Offset(0, currentOffset); child.Dimensions = new Dimensions( - Math.Min(childDimensions.Width, contentArea.Width), + contentArea.Width, Math.Min(childDimensions.Height, contentArea.Height - currentOffset) ); @@ -116,11 +117,11 @@ protected override void OnArrangeChildren() } else { - // Horizontal stacking + // Horizontal stacking. The child spans the panel's height, matching the vertical case. child.Position = contentPosition.Offset(currentOffset, 0); child.Dimensions = new Dimensions( Math.Min(childDimensions.Width, contentArea.Width - currentOffset), - Math.Min(childDimensions.Height, contentArea.Height) + contentArea.Height ); currentOffset += child.Dimensions.Width + Spacing;