refactor(ui): scale the remaining layout constants

What blocks A to E did not already touch: the honorific header height and its
two offsets, the message list dummy widths, and the quick-button reserve in the
input bar.

The reserve is the one with visible consequences. At 150% the buttons grow with
the font while a fixed 130px column does not, so they stopped fitting.

The honorific offsets are centred rather than scaled. The 8f there was
(30 - 14) / 2 for the old font, structurally the same case as the sidebar: a
scaled constant keeps its mis-centering, a computed one does not.
This commit is contained in:
2026-08-18 00:05:23 +02:00
parent 0d6877ddc6
commit bfc61909cf
3 changed files with 31 additions and 8 deletions
+14 -4
View File
@@ -13,7 +13,8 @@ namespace HellionChat.Ui.Components;
// bracketed title only appears when there is actually a title to show.
internal sealed class HonorificHeader
{
public const float Height = 30f;
// Scaled: MainWindow reserves body height against this, so it follows.
public static float Height => StyleEngine.Metrics.HonorificHeight;
// SelfTest observables — set on the real Draw path so a headless step can
// assert the gate/colour/truncation outcome instead of re-implementing it.
@@ -68,7 +69,11 @@ internal sealed class HonorificHeader
using (_fonts.FontAwesome.Push())
{
crownWidth = ImGui.CalcTextSize(crownGlyph).X;
dl.AddText(origin + new Vector2(0f, 8f), crownColor, crownGlyph);
dl.AddText(
origin + new Vector2(0f, StyleEngine.Metrics.CenterY(Height)),
crownColor,
crownGlyph
);
}
// Gate the bracketed title through the 1.5.6 contract (toggle, IPC
@@ -92,12 +97,17 @@ internal sealed class HonorificHeader
// TruncateToFitWidth measures the *Regular* font, so this must run
// OUTSIDE the FontAwesome.Push block above (crownWidth was measured
// inside it, which is correct).
var maxTitleWidth = maxWidth - crownWidth - 6f - 8f;
var gap = StyleEngine.Metrics.HonorificBracketGap;
var maxTitleWidth = maxWidth - crownWidth - gap - StyleEngine.Metrics.HonorificInset;
if (maxTitleWidth > 0f)
{
var rendered = StringUtil.TruncateToFitWidth($"«{current.Title}»", maxTitleWidth);
LastRenderedTitle = rendered;
dl.AddText(origin + new Vector2(crownWidth + 6f, 8f), titleColor, rendered);
dl.AddText(
origin + new Vector2(crownWidth + gap, StyleEngine.Metrics.CenterY(Height)),
titleColor,
rendered
);
LastTitleRendered = true;
}
}
+4 -1
View File
@@ -30,7 +30,10 @@ internal sealed class InputBar
// they follow automatically. The pill's own metrics live in PillStyle now.
public static float Height => StyleEngine.Metrics.InputBarHeight;
private const int BufferCapacity = 500;
private const float QuickButtonsReserve = 130f;
// Scaled: the buttons themselves grow with the font, so a fixed reserve
// stops fitting them at 150%.
private static float QuickButtonsReserve => StyleEngine.Metrics.InputQuickButtonsReserve;
private readonly SymbolPicker _symbolPicker;
private readonly FontManager _fonts;
+13 -3
View File
@@ -308,12 +308,17 @@ internal sealed class MessageList
{
// Scrolled into a gap: one full-height dummy keeps the scrollbar honest.
var gap = CompensatedDummy(plan.LeadDummyHeight + plan.EndDummyHeight);
ImGui.Dummy(new Vector2(10f, gap));
ImGui.Dummy(new Vector2(StyleEngine.Metrics.MessageDummyWidth, gap));
return;
}
if (plan.LeadDummyHeight > 0f)
ImGui.Dummy(new Vector2(10f, CompensatedDummy(plan.LeadDummyHeight)));
ImGui.Dummy(
new Vector2(
StyleEngine.Metrics.MessageDummyWidth,
CompensatedDummy(plan.LeadDummyHeight)
)
);
for (var i = plan.FirstVisible; i <= plan.LastVisible; i++)
{
@@ -329,7 +334,12 @@ internal sealed class MessageList
}
if (plan.EndDummyHeight > 0f)
ImGui.Dummy(new Vector2(10f, CompensatedDummy(plan.EndDummyHeight)));
ImGui.Dummy(
new Vector2(
StyleEngine.Metrics.MessageDummyWidth,
CompensatedDummy(plan.EndDummyHeight)
)
);
}
// First-frame / post-invalidation fallback: draw + measure every row into the