From 7b9891890fe69b81879768cabf522431153a3115 Mon Sep 17 00:00:00 2001 From: Matthew Edmondson Date: Tue, 29 Sep 2026 12:33:11 +0000 Subject: [PATCH] fix: measure a container's children from its content origin [patch] The default OnCalculateRequiredDimensionsForChildren measured each child's extent from screen column/row 0, so a custom container's required size included its own position and its padding, which OnCalculateRequiredDimensions then added again. A 5x3 container at (10,5) measured 16x9. Children are now measured relative to GetContentPosition(). Fixes ktsu-dev/TUI#145 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VCL6GX9wshgqiBeq6z98ZH --- TUI.Test/UIContainerBaseMeasureTests.cs | 67 +++++++++++++++++++++++++ TUI/Elements/UIContainerBase.cs | 7 ++- 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 TUI.Test/UIContainerBaseMeasureTests.cs diff --git a/TUI.Test/UIContainerBaseMeasureTests.cs b/TUI.Test/UIContainerBaseMeasureTests.cs new file mode 100644 index 0000000..70cb036 --- /dev/null +++ b/TUI.Test/UIContainerBaseMeasureTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.TUI.Test; + +using ktsu.TUI.Core.Contracts; +using ktsu.TUI.Core.Elements; +using ktsu.TUI.Core.Elements.Primitives; +using ktsu.TUI.Core.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Tests for the default measurement a custom container inherits from UIContainerBase +/// +[TestClass] +public sealed class UIContainerBaseMeasureTests +{ + /// + /// A container that places every child at its content origin and keeps the default measurement + /// + private sealed class OriginContainer : UIContainerBase + { + protected override void OnRender(IConsoleProvider provider) { } + + protected override void OnArrangeChildren() + { + foreach (IUIElement child in GetVisibleChildren()) + { + child.Position = GetContentPosition(); + } + } + } + + private static OriginContainer CreateContainer(Position position) + { + OriginContainer container = new() + { + Position = position, + Padding = Padding.Uniform(1), + }; + container.AddChild(new TextElement { Text = "abc" }); + container.ArrangeChildren(); + return container; + } + + /// + /// The required size is the content plus one padding, not the screen position plus two paddings + /// + [TestMethod] + public void DefaultMeasurementIsContentPlusPadding() + { + OriginContainer container = CreateContainer(new Position(10, 5)); + + Assert.AreEqual(new Dimensions(5, 3), container.CalculateRequiredDimensions()); + } + + /// + /// Moving the container on screen does not change how large it asks to be + /// + [TestMethod] + public void DefaultMeasurementDoesNotDependOnPosition() + { + Dimensions atOrigin = CreateContainer(Position.Origin).CalculateRequiredDimensions(); + Dimensions elsewhere = CreateContainer(new Position(40, 12)).CalculateRequiredDimensions(); + + Assert.AreEqual(atOrigin, elsewhere); + } +} diff --git a/TUI/Elements/UIContainerBase.cs b/TUI/Elements/UIContainerBase.cs index ed7e5a7..306c05d 100644 --- a/TUI/Elements/UIContainerBase.cs +++ b/TUI/Elements/UIContainerBase.cs @@ -132,14 +132,17 @@ protected virtual Dimensions OnCalculateRequiredDimensionsForChildren() return Dimensions.Empty; } + // Children sit at absolute screen positions, so measure them from the content origin; the + // container's own position and padding are not part of the content's size + Position origin = GetContentPosition(); int maxWidth = 0; int maxHeight = 0; foreach (IUIElement child in _children) { Dimensions childDimensions = child.CalculateRequiredDimensions(); - int childRight = child.Position.X + childDimensions.Width; - int childBottom = child.Position.Y + childDimensions.Height; + int childRight = Math.Max(0, child.Position.X - origin.X + childDimensions.Width); + int childBottom = Math.Max(0, child.Position.Y - origin.Y + childDimensions.Height); maxWidth = Math.Max(maxWidth, childRight); maxHeight = Math.Max(maxHeight, childBottom);