diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index ebaf6e7..ec87080 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -116,6 +116,13 @@ internal static class PluginHostFactory sp.GetRequiredService(), sp.GetRequiredService>() )); + + services.AddSingleton(sp => new Ui.Components.HonorificHeader( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService() + )); services.AddSingleton(sp => new Integrations.FailedTellNotifier( sp.GetRequiredService>() )); diff --git a/HellionChat/Ui/Components/HonorificHeader.cs b/HellionChat/Ui/Components/HonorificHeader.cs new file mode 100644 index 0000000..2dad71f --- /dev/null +++ b/HellionChat/Ui/Components/HonorificHeader.cs @@ -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)); + } +}