From b8299a90caf16f809cc745bfc365f59d957cc535 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Sat, 23 May 2026 16:36:01 +0200 Subject: [PATCH] feat(style-engine): add PushStack as ImRaii bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DI-singleton that pairs Token lookups with ImRaii's tracked push/pop machinery. Begin() returns a disposable PushScope with fluent Color, Style (float/Vector2) and Font methods; reverse-order dispose runs through the collected IDisposables. Counter-symmetry and exception-safety come from ImRaii, this layer just handles the token → ImGuiCol resolution and the RGBA → ABGR conversion at the ImGui boundary. --- HellionChat/PluginHostFactory.cs | 3 ++ HellionChat/Ui/StyleEngine/PushStack.cs | 71 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 HellionChat/Ui/StyleEngine/PushStack.cs diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index a3502b1..fd57739 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -93,6 +93,9 @@ internal static class PluginHostFactory )); services.AddSingleton(_ => new Ui.StyleEngine.TokenResolver()); + services.AddSingleton(sp => new Ui.StyleEngine.PushStack( + sp.GetRequiredService() + )); services.AddSingleton(sp => new GameFunctions.GameFunctions( sp.GetRequiredService(), diff --git a/HellionChat/Ui/StyleEngine/PushStack.cs b/HellionChat/Ui/StyleEngine/PushStack.cs new file mode 100644 index 0000000..9109505 --- /dev/null +++ b/HellionChat/Ui/StyleEngine/PushStack.cs @@ -0,0 +1,71 @@ +using System.Numerics; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.ManagedFontAtlas; +using Dalamud.Interface.Utility.Raii; +using HellionChat.Themes; +using HellionChat.Util; + +namespace HellionChat.Ui.StyleEngine; + +// Token-aware bridge over Dalamud's ImRaii. Couples semantic Token lookups +// to the already-tracked push/pop machinery so callers express style intent +// instead of raw ImGuiCol slots. Counter-symmetry and exception-safety come +// from ImRaii, not from this class. +internal sealed class PushStack +{ + private readonly TokenResolver _resolver; + + public PushStack(TokenResolver resolver) + { + _resolver = resolver; + } + + public PushScope Begin() => new(_resolver); + + // Disposable scope handed to the using-block. Each Push.* call is a thin + // delegate to ImRaii / IFontHandle.Push with the token resolve in front; + // disposes in reverse order via the captured IDisposables. + internal sealed class PushScope : IDisposable + { + private readonly List _items = new(32); + private readonly TokenResolver _resolver; + + internal PushScope(TokenResolver resolver) + { + _resolver = resolver; + } + + public PushScope Color(Token token, Theme theme) + { + var slot = TokenMap.ToImGuiCol(token); + var rgba = _resolver.Resolve(token, theme.Colors); + _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba))); + return this; + } + + public PushScope Style(ImGuiStyleVar var, float value) + { + _items.Add(ImRaii.PushStyle(var, value)); + return this; + } + + public PushScope Style(ImGuiStyleVar var, Vector2 value) + { + _items.Add(ImRaii.PushStyle(var, value)); + return this; + } + + public PushScope Font(IFontHandle font) + { + _items.Add(font.Push()); + return this; + } + + public void Dispose() + { + for (var i = _items.Count - 1; i >= 0; i--) + _items[i].Dispose(); + _items.Clear(); + } + } +}