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.
120 lines
4.5 KiB
C#
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));
|
|
}
|
|
}
|