Files
HellionChat/HellionChat/Ui/Components/HonorificHeader.cs
T
JonKazama-Hellion bfc61909cf 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.
2026-08-18 00:05:23 +02:00

120 lines
4.5 KiB
C#

using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using HellionChat.Integrations;
using HellionChat.Themes;
using HellionChat.Ui.StyleEngine;
using HellionChat.Util;
namespace HellionChat.Ui.Components;
// 30px header row pinned to the top of the main chat window. Crown stays
// rendered as a brand anchor even when the Honorific IPC drops out; the
// bracketed title only appears when there is actually a title to show.
internal sealed class HonorificHeader
{
// 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.
internal bool LastTitleRendered { get; private set; }
internal uint LastTitleColorAbgr { get; private set; }
internal string? LastRenderedTitle { get; private set; }
private readonly HonorificService _honorific;
private readonly FontManager _fonts;
private readonly ThemeRegistry _themes;
private readonly TokenResolver _resolver;
public HonorificHeader(
HonorificService honorific,
FontManager fonts,
ThemeRegistry themes,
TokenResolver resolver
)
{
_honorific = honorific;
_fonts = fonts;
_themes = themes;
_resolver = resolver;
}
// Same singleton the AboutTab integrations section uses; lets a SelfTest
// drive the gate branches via HonorificService.TestOnly_SetState.
internal HonorificService GetServiceForSelfTest() => _honorific;
public void Draw(float maxWidth)
{
LastTitleRendered = false;
LastRenderedTitle = null;
// First-frame guard: components must not lay out before the atlas
// is finished or text metrics collapse into placeholder widths.
if (!_fonts.FontsReady)
{
ImGui.TextUnformatted("Loading fonts…");
return;
}
var theme = _themes.Active;
var origin = ImGui.GetCursorScreenPos();
var dl = ImGui.GetWindowDrawList();
var crownColor = ColourUtil.RgbaToAbgr(
_resolver.Resolve(Token.HonorificCrown, theme.Colors)
);
var crownGlyph = FontAwesomeIcon.Crown.ToIconString();
float crownWidth;
using (_fonts.FontAwesome.Push())
{
crownWidth = ImGui.CalcTextSize(crownGlyph).X;
dl.AddText(
origin + new Vector2(0f, StyleEngine.Metrics.CenterY(Height)),
crownColor,
crownGlyph
);
}
// Gate the bracketed title through the 1.5.6 contract (toggle, IPC
// availability, IsOriginal, empty-title) — the crown above stays
// unconditional as the permanent brand anchor. NOTE divergence from
// 1.5.6: there a failed gate hid the whole slot incl. crown; here the
// crown persists by design.
if (
HonorificService.ShouldRenderSlot(
Plugin.Config.ShowHonorificTitleInHeader,
_honorific.IsAvailable,
_honorific.CurrentTitle
)
)
{
var current = _honorific.CurrentTitle!;
var titleColor = HonorificTitleColor.ResolveTitleAbgr(current.Color, theme);
LastTitleColorAbgr = titleColor;
// Budget the title against the row width. CalcTextSize inside
// TruncateToFitWidth measures the *Regular* font, so this must run
// OUTSIDE the FontAwesome.Push block above (crownWidth was measured
// inside it, which is correct).
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 + gap, StyleEngine.Metrics.CenterY(Height)),
titleColor,
rendered
);
LastTitleRendered = true;
}
}
// Reserve the row height even when no title rendered so the layout
// below stays stable across IPC reconnect cycles.
ImGui.Dummy(new Vector2(maxWidth, Height));
}
}