Skip to content

Commit 684f2ba

Browse files
committed
Make TextBlock shrink in layout
1 parent a6a5b8b commit 684f2ba

5 files changed

Lines changed: 73 additions & 5 deletions

File tree

site/docs/controls/textblock.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ new TextBlock("This is a long single line that will be trimmed.")
3636
.Trimming(TextTrimming.EndEllipsis);
3737
```
3838

39+
When `Wrap` is `false`, `TextBlock` can shrink horizontally during layout, so it works well in patterns such as
40+
`Grid(Star + Auto)` where trailing actions should remain visible while the text trims.
41+
3942
## Alignment
4043

4144
`TextAlignment` controls how the text is aligned inside the available width:

site/docs/specs/controls/textblock.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ There are no events and no input handling.
6666

6767
### Measurement model
6868

69-
`TextBlock` measures to a fixed size based on:
69+
`TextBlock` measures based on:
7070
- the text's cell width (not string length)
7171
- the wrap flag and available width
7272
- the available height (clamped)
@@ -76,8 +76,14 @@ Let:
7676
- `naturalWidth = TerminalTextUtility.GetWidth(text)`
7777
- `width = min(constraints.MaxWidth, naturalWidth)`
7878

79-
When `Wrap` is false or `width == 0`:
80-
- desired size is `(width, 1)` (height is clamped to 1)
79+
When `Wrap` is false:
80+
- `Natural.Width` is the full text width
81+
- `Min.Width` is `1` for non-empty text (`0` for empty text), so single-line text can shrink during layout
82+
- `Max.Width` is the full text width
83+
- `FlexShrinkX = 1` when the text is wider than its minimum
84+
85+
When `Wrap` is true and `width == 0`:
86+
- desired size is `(0, 1)` (height is clamped to 1)
8187

8288
When `Wrap` is true:
8389
- height is computed as the number of wrapped lines for the given width (at least 1)
@@ -86,6 +92,8 @@ When `Wrap` is true:
8692
Notes:
8793
- The measured width does not attempt to "stretch to fill" when there is more horizontal space than the text requires.
8894
Stretching is handled by layout via `HorizontalAlignment = Align.Stretch` if desired.
95+
- This horizontal shrink budget is what allows layouts such as `Grid(Star + Auto)` to keep trailing controls visible
96+
while the text trims in the remaining width.
8997

9098
### Rendering model
9199

src/XenoAtom.Terminal.UI.Tests/GridLayoutTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,32 @@ public void Auto_Column_Shrinks_To_Preserve_Star_MinWidth()
114114
Assert.AreEqual(new Rectangle(7, 0, 3, 1), star.Bounds);
115115
}
116116

117+
[TestMethod]
118+
public void Star_Text_Column_Shrinks_Before_Auto_Button_Column()
119+
{
120+
var grid = new Grid();
121+
grid.ColumnDefinitions.AddRange(
122+
new ColumnDefinition { Width = GridLength.Star(1) },
123+
new ColumnDefinition { Width = GridLength.Auto });
124+
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
125+
126+
var text = new TextBlock("HelloWorld")
127+
{
128+
Wrap = false,
129+
Trimming = TextTrimming.EndEllipsis,
130+
};
131+
var button = new Button("X");
132+
133+
grid.Cell(text, 0, 0);
134+
grid.Cell(button, 0, 1);
135+
136+
grid.Measure(new Size(8, 1));
137+
grid.Arrange(new Rectangle(0, 0, 8, 1));
138+
139+
Assert.AreEqual(3, text.Bounds.Width);
140+
Assert.AreEqual(new Rectangle(3, 0, 5, 1), button.Bounds);
141+
}
142+
117143
private sealed class FillVisual : Visual
118144
{
119145
private readonly Size _desired;

src/XenoAtom.Terminal.UI.Tests/TextBlockRenderingTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ namespace XenoAtom.Terminal.UI.Tests;
1414
[TestClass]
1515
public sealed class TextBlockRenderingTests
1616
{
17+
[TestMethod]
18+
public void TextBlock_SingleLine_Reports_Horizontal_Shrink_Budget()
19+
{
20+
var tb = new TextBlock("HelloWorld")
21+
{
22+
Wrap = false,
23+
Trimming = TextTrimming.EndEllipsis,
24+
};
25+
26+
tb.Measure(LayoutConstraints.Unbounded);
27+
28+
Assert.AreEqual(1, tb.MeasureHints.Min.Width);
29+
Assert.AreEqual(10, tb.MeasureHints.Natural.Width);
30+
Assert.AreEqual(10, tb.MeasureHints.Max.Width);
31+
Assert.AreEqual(1, tb.MeasureHints.FlexShrinkX);
32+
}
33+
1734
[TestMethod]
1835
public void TextBlock_EndEllipsis_Trims_To_Width()
1936
{

src/XenoAtom.Terminal.UI/Controls/TextBlock.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,24 @@ protected override SizeHints MeasureCore(in LayoutConstraints constraints)
121121
var text = Text ?? string.Empty;
122122
var naturalWidth = TerminalTextUtility.GetWidth(text.AsSpan());
123123
var width = Math.Max(0, Math.Min(availableSize.Width, naturalWidth));
124+
var lineHeight = Math.Min(Math.Max(0, availableSize.Height), 1);
124125

125-
if (!Wrap || width == 0)
126+
if (!Wrap)
126127
{
127-
return SizeHints.Fixed(new Size(width, Math.Min(Math.Max(0, availableSize.Height), 1)));
128+
var minWidth = naturalWidth > 0 ? 1 : 0;
129+
return SizeHints.Flex(
130+
new Size(minWidth, lineHeight),
131+
new Size(naturalWidth, lineHeight),
132+
new Size(naturalWidth, lineHeight),
133+
growX: 0,
134+
growY: 0,
135+
shrinkX: naturalWidth > minWidth ? 1 : 0,
136+
shrinkY: 0);
137+
}
138+
139+
if (width == 0)
140+
{
141+
return SizeHints.Fixed(new Size(width, lineHeight));
128142
}
129143

130144
var height = CountWrappedLines(text.AsSpan(), Math.Max(1, width));

0 commit comments

Comments
 (0)