Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Treat screenshot-reported UI precision bugs as shared geometry suspects first. Hit testing, caret placement, slider bounds, and popup placement usually belong in shared measurement, transform, bounds, or z-layer code rather than one-off component fixes.
- Keep the UI A/B sample useful as a validation tool, not just a showcase. Add visible state, labels, and layout previews when controls otherwise look inert or ambiguous.
- For retained text input, caret, selection, and scrolling should share measured text advances. Any fallback heuristic must be treated as a temporary approximation and tested against proportional text.
- For retained UI controls that render text with measured glyph widths, auto-size measurement should use the same measured text path. Character-count heuristics are only safe as fallbacks when no render measurement context is available.

## PR Review And Triage

Expand Down
10 changes: 8 additions & 2 deletions src/ModernOverlay.UI/Controls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,11 +553,17 @@ protected override SizeF MeasureCore(SizeF availableSize)
}

float fontSize = Root?.ThemeResources.Theme.FontSize ?? UiTheme.Default.FontSize;
float width = MathF.Min(availableSize.Width, Text.Length * fontSize * 0.6f + Padding.Horizontal);
float height = fontSize * 1.35f + Padding.Vertical;
SizeF textSize = MeasureButtonText(Text, fontSize);
float width = MathF.Min(availableSize.Width, textSize.Width + Padding.Horizontal);
float height = textSize.Height * 1.35f + Padding.Vertical;
return new SizeF(MathF.Max(MinWidth, width), MathF.Max(MinHeight, height));
Comment on lines 555 to 559
}

private SizeF MeasureButtonText(string value, float fontSize)
=> Root is { } root && root.TryMeasureText(value, root.ThemeResources.Font, out SizeF measured)
? measured
: new SizeF(value.Length * fontSize * 0.6f, fontSize);

protected override void ArrangeCore(RectF finalRect)
{
ArrangeContent();
Expand Down
27 changes: 26 additions & 1 deletion tests/ModernOverlay.Tests/OverlayUiButtonControlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,29 @@ public async Task ButtonTextAlignmentControlsTextOrigin()
Assert.AreEqual(left.ContentBounds.X, leftOrigin.X, 0.001f);
}

[TestMethod]
[TestCategory("WindowsIntegration")]
public async Task ButtonAutoSizeUsesMeasuredTextWidth()
{
await using OverlayWindow overlay = await CreateOverlayAsync();
using OverlayUiRoot ui = OverlayUi.Attach(overlay, new OverlayUiOptions { RegisterInputRegions = false });
UiButton button = new()
{
Text = "WWW",
Height = 28f,
HorizontalAlignment = UiHorizontalAlignment.Left,
VerticalAlignment = UiVerticalAlignment.Top,
};
Canvas.SetLeft(button, 10f);
Canvas.SetTop(button, 10f);
ui.Root.Children.Add(button);

ui.Render(new DrawContext(new RecordingDrawCommandSink()));

Assert.AreEqual(140f, button.DesiredSize.Width, 0.001f);
Assert.AreEqual(140f, button.Bounds.Width, 0.001f);
Comment on lines +173 to +176
}

[TestMethod]
[TestCategory("WindowsIntegration")]
public async Task RadioButtonClearsPeersAndHonorsDisabledDynamicGroupChanges()
Expand Down Expand Up @@ -336,7 +359,9 @@ public void DrawTextLayout(TextLayoutHandle layout, BrushHandle brush, PointF or
=> AddPrimitive();

public SizeF MeasureText(string text, FontHandle font)
=> new(text.Length, font.Options.Size);
=> text == "WWW"
? new SizeF(120f, font.Options.Size)
: new SizeF(text.Length, font.Options.Size);

public SizeF MeasureTextLayout(TextLayoutHandle layout)
=> new(layout.Text.Length, layout.Font.Options.Size);
Expand Down
Loading