feat(ui): add HonorificHeader component
30px header row pinned to the top of the chat window. Crown always renders as a brand anchor — even with the Honorific IPC down — while the bracketed title only appears when CurrentTitle has content. First-frame guard reads FontManager.FontsReady so layout math never runs against placeholder font metrics.
This commit is contained in:
@@ -116,6 +116,13 @@ internal static class PluginHostFactory
|
||||
sp.GetRequiredService<IChatGui>(),
|
||||
sp.GetRequiredService<ILogger<Services.TellRouterService>>()
|
||||
));
|
||||
|
||||
services.AddSingleton(sp => new Ui.Components.HonorificHeader(
|
||||
sp.GetRequiredService<Integrations.HonorificService>(),
|
||||
sp.GetRequiredService<FontManager>(),
|
||||
sp.GetRequiredService<ThemeRegistry>(),
|
||||
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>()
|
||||
));
|
||||
services.AddSingleton(sp => new Integrations.FailedTellNotifier(
|
||||
sp.GetRequiredService<ILogger<Integrations.FailedTellNotifier>>()
|
||||
));
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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
|
||||
{
|
||||
public const float Height = 30f;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void Draw(float maxWidth)
|
||||
{
|
||||
// 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, 8f), crownColor, crownGlyph);
|
||||
}
|
||||
|
||||
var title = _honorific.IsAvailable ? _honorific.CurrentTitle?.Title : null;
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
var titleColor = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
|
||||
dl.AddText(origin + new Vector2(crownWidth + 6f, 8f), titleColor, $"«{title}»");
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user