From 460217459ce93dfb2d830a4db1eb227cdb52fab9 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Mon, 17 Aug 2026 23:42:05 +0200 Subject: [PATCH] chore(style): add a widget gallery for visual verification Every widget in its states, reachable via /hellion widgets. The point is to check them one at a time before they land in real components: the v2.x style engine grew three primitives that were never wired to a call site (DrawGlowBorder, DrawSlipPolygon, DrawHonorificHeader), and this is the cheap way to notice that before a cycle closes. DEBUG-only, like SeStringDebugger. It is a verification aid, not a feature, so it never reaches a release build -- verified against a Release compile. The header line also shows the live GlobalScale and the hover registry size, which makes both the scaling work and the eviction contract observable while dragging the Dalamud scale slider. --- HellionChat/Plugin.cs | 13 ++ HellionChat/PluginHostFactory.cs | 6 + HellionChat/PluginLifecycle.cs | 3 + HellionChat/Ui/Windows/WidgetGalleryWindow.cs | 196 ++++++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 HellionChat/Ui/Windows/WidgetGalleryWindow.cs diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 713b7f0..c117d0c 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -109,6 +109,9 @@ public sealed class Plugin : IAsyncDalamudPlugin internal static InputPreview InputPreview { get; private set; } = null!; internal CommandHelpWindow CommandHelpWindow { get; private set; } = null!; public SeStringDebugger SeStringDebugger { get; private set; } = null!; +#if DEBUG + internal Ui.Windows.WidgetGalleryWindow WidgetGallery { get; private set; } = null!; +#endif public FirstRunWizard FirstRunWizard { get; private set; } = null!; internal DebuggerWindow DebuggerWindow { get; private set; } = null!; @@ -365,6 +368,9 @@ public sealed class Plugin : IAsyncDalamudPlugin InputPreview = _host.Services.GetRequiredService(); CommandHelpWindow = _host.Services.GetRequiredService(); SeStringDebugger = _host.Services.GetRequiredService(); +#if DEBUG + WidgetGallery = _host.Services.GetRequiredService(); +#endif DebuggerWindow = _host.Services.GetRequiredService(); FirstRunWizard = _host.Services.GetRequiredService(); ChannelPopoutPool = _host.Services.GetRequiredService(); @@ -925,6 +931,13 @@ public sealed class Plugin : IAsyncDalamudPlugin SettingsWindow.Toggle(); return; } +#if DEBUG + if (arg.Equals("widgets", StringComparison.OrdinalIgnoreCase)) + { + WidgetGallery.Toggle(); + return; + } +#endif if (arg.Equals("reset", StringComparison.OrdinalIgnoreCase)) { // Recovery path documented in the v2.x master spec — drops a diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index d700eaf..794d132 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -340,6 +340,12 @@ internal static class PluginHostFactory sp.GetRequiredService>() )); services.AddSingleton(sp => new SeStringDebugger(sp.GetRequiredService())); +#if DEBUG + services.AddSingleton(sp => new Ui.Windows.WidgetGalleryWindow( + sp.GetRequiredService(), + sp.GetRequiredService() + )); +#endif services.AddSingleton(sp => new DebuggerWindow( sp.GetRequiredService(), sp.GetRequiredService() diff --git a/HellionChat/PluginLifecycle.cs b/HellionChat/PluginLifecycle.cs index 369bd24..e0a27df 100644 --- a/HellionChat/PluginLifecycle.cs +++ b/HellionChat/PluginLifecycle.cs @@ -64,6 +64,9 @@ internal sealed class PluginLifecycle : IAsyncDisposable plugin.WindowSystem.AddWindow(Plugin.InputPreview); plugin.WindowSystem.AddWindow(plugin.CommandHelpWindow); plugin.WindowSystem.AddWindow(plugin.SeStringDebugger); +#if DEBUG + plugin.WindowSystem.AddWindow(plugin.WidgetGallery); +#endif plugin.WindowSystem.AddWindow(plugin.DebuggerWindow); plugin.WindowSystem.AddWindow(plugin.FirstRunWizard); diff --git a/HellionChat/Ui/Windows/WidgetGalleryWindow.cs b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs new file mode 100644 index 0000000..ba17a31 --- /dev/null +++ b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs @@ -0,0 +1,196 @@ +#if DEBUG +using System.Numerics; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using HellionChat.Themes; +using HellionChat.Ui.StyleEngine; +using HellionChat.Ui.StyleEngine.Widgets; + +namespace HellionChat.Ui.Windows; + +// Every widget in Ui/StyleEngine/Widgets shown in its states, so they can be +// checked one at a time before they land in real components. The v2.x style +// engine grew three primitives that were never wired to a call site +// (DrawGlowBorder, DrawSlipPolygon, DrawHonorificHeader) -- this is the cheap +// way to notice that before a cycle closes. +// +// DEBUG-only, like SeStringDebugger: it is a verification aid, not a feature. +internal sealed class WidgetGalleryWindow : Window +{ + private readonly Plugin _plugin; + private readonly WidgetPalette _palette; + + private int _badgeCount = 3; + private bool _rowActive = true; + + internal WidgetGalleryWindow(Plugin plugin, TokenResolver resolver) + : base("Widget Gallery###hellion-widget-gallery") + { + _plugin = plugin; + _palette = new WidgetPalette(resolver); + Size = new Vector2(460, 560); + SizeCondition = ImGuiCond.FirstUseEver; + } + + public override void Draw() + { + var c = _plugin.ThemeRegistry.Active.Colors; + + ImGui.TextDisabled( + $"GlobalScale {Metrics.Scale:0.00} · hover entries {HoverState.TrackedCount}" + ); + ImGui.Separator(); + + DrawRowSection(c); + DrawBadgeSection(c); + DrawPillSection(c); + DrawIconButtonSection(c); + DrawDividerSection(c); + } + + private void DrawRowSection(ThemeColors c) + { + ImGui.TextUnformatted("Row"); + ImGui.Checkbox("active##row", ref _rowActive); + + var width = ImGui.GetContentRegionAvail().X; + var height = Metrics.SidebarRowHeight; + + for (var i = 0; i < 3; i++) + { + var origin = ImGui.GetCursorScreenPos(); + var id = ImGui.GetID($"gallery.row.{i}"); + ImGui.InvisibleButton($"##gallery-row-{i}", new Vector2(width, height)); + var hovered = ImGui.IsItemHovered(); + + Row.Draw( + origin, + new Vector2(width, height), + new RowVisualState + { + IsActive = _rowActive && i == 1, + HoverAmount = HoverState.Query(id, hovered), + SurfaceAbgr = _palette.Abgr(Token.SurfaceBase, c), + SurfaceHoverAbgr = _palette.Abgr(Token.SurfaceHover, c), + SurfaceActiveAbgr = _palette.Abgr(Token.SurfaceActive, c), + AccentAbgr = _palette.Abgr(Token.AccentPrimary, c), + BorderAbgr = _palette.Abgr(Token.Border, c), + } + ); + + ImGui + .GetWindowDrawList() + .AddText( + origin + new Vector2(12f * Metrics.Scale, Metrics.CenterY(height)), + _palette.Abgr(Token.Text, c), + $"Row {i}" + ); + } + + ImGui.Spacing(); + } + + private void DrawBadgeSection(ThemeColors c) + { + ImGui.TextUnformatted("Badge"); + ImGui.SliderInt("count##badge", ref _badgeCount, 0, 150); + + int[] samples = [1, 9, _badgeCount, 120]; + var origin = ImGui.GetCursorScreenPos(); + var x = origin.X; + foreach (var n in samples) + { + Badge.Draw( + new Vector2(x, origin.Y), + n, + _palette.Abgr(Token.AccentEmber, c), + _palette.Abgr(Token.Text, c) + ); + x += Badge.CalcSize(n).X + 8f * Metrics.Scale; + } + + ImGui.Dummy(new Vector2(0, Badge.CalcSize(1).Y + 8f * Metrics.Scale)); + ImGui.Spacing(); + } + + private void DrawPillSection(ThemeColors c) + { + ImGui.TextUnformatted("Pill"); + + var origin = ImGui.GetCursorScreenPos(); + var x = origin.X; + + Pill.Draw( + new Vector2(x, origin.Y), + "filled", + _palette.Abgr(Token.SurfaceRaised, c), + _palette.Abgr(Token.Text, c) + ); + x += Pill.CalcSize("filled", withDot: false).X + 8f * Metrics.Scale; + + Pill.Draw( + new Vector2(x, origin.Y), + "with dot", + _palette.Abgr(Token.SurfaceRaised, c), + _palette.Abgr(Token.Text, c), + _palette.Abgr(Token.AccentPrimary, c) + ); + x += Pill.CalcSize("with dot", withDot: true).X + 8f * Metrics.Scale; + + Pill.Draw( + new Vector2(x, origin.Y), + "outlined", + _palette.Abgr(Token.AccentPrimary, c), + _palette.Abgr(Token.TextMuted, c), + styleOverride: new PillStyle { Outlined = true } + ); + + ImGui.Dummy(new Vector2(0, Pill.CalcSize("x", false).Y + 8f * Metrics.Scale)); + ImGui.Spacing(); + } + + private void DrawIconButtonSection(ThemeColors c) + { + ImGui.TextUnformatted("IconButton"); + + var size = new Vector2(Metrics.SidebarPopOutHitWidth, Metrics.SidebarRowHeight); + FontAwesomeIcon[] glyphs = + [ + FontAwesomeIcon.ArrowUpRightFromSquare, + FontAwesomeIcon.Check, + FontAwesomeIcon.CheckCircle, + ]; + + for (var i = 0; i < glyphs.Length; i++) + { + if (i > 0) + ImGui.SameLine(); + + IconButton.Draw( + ImGui.GetID($"gallery.icon.{i}"), + size, + glyphs[i], + _palette.Abgr(Token.TextMuted, c), + _palette.Abgr(Token.SurfaceHover, c), + _plugin.FontManager.FontAwesome + ); + } + + ImGui.Spacing(); + } + + private void DrawDividerSection(ThemeColors c) + { + ImGui.TextUnformatted("LineDivider"); + + LineDivider.Draw( + "Section (2)", + _palette.Abgr(Token.Border, c), + _palette.Abgr(Token.TextMuted, c) + ); + LineDivider.Draw(null, _palette.Abgr(Token.Border, c), _palette.Abgr(Token.TextMuted, c)); + } +} +#endif