diff --git a/HellionChat/Ui/StyleEngine/GlobalStyleScope.cs b/HellionChat/Ui/StyleEngine/GlobalStyleScope.cs index 8820c59..8e92714 100644 --- a/HellionChat/Ui/StyleEngine/GlobalStyleScope.cs +++ b/HellionChat/Ui/StyleEngine/GlobalStyleScope.cs @@ -1,5 +1,4 @@ using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Utility.Raii; using HellionChat.Themes; using HellionChat.Util; @@ -33,60 +32,73 @@ internal static class GlobalStyleScope var childBgWithAlpha = ResolveChildBgAlpha(c.ChildBg, windowOpacity); var stack = new StackHandle(); - stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding); - stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding); - stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding); - stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding); - stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding); - stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding); - stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding); - stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize); - stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize); + // Hard contract: every push must be balanced by a pop. The pushes below + // are pure, practically non-throwing ImGui calls, but if one ever threw + // mid-stack we must still pop what we pushed before the exception escapes, + // or the global style stack stays corrupt for every other window this + // frame. Dispose then rethrow. + try + { + stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding); + stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding); + stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding); + stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding); + stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding); + stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding); + stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding); + stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize); + stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize); - stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha); - stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha); - stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg); - stack.PushColorAbgr(ImGuiCol.Border, a.Border); - stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u); + stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha); + stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha); + stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg); + stack.PushColorAbgr(ImGuiCol.Border, a.Border); + stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u); - stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg); - stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover); - stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface); + stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface); - stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg); - stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity); - stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg); + stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg); + stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg); - stack.PushColorAbgr(ImGuiCol.Button, a.Primary); - stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight); - stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark); + stack.PushColorAbgr(ImGuiCol.Button, a.Primary); + stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark); - stack.PushColorAbgr(ImGuiCol.Header, a.Surface); - stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover); - stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.Header, a.Surface); + stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity); - stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg); - stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight); - stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity); - stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg); - stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark); + stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg); + stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark); - stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg); - stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface); - stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight); - stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent); + stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent); - stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg); - stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight); - stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent); + stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight); + stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent); - stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary); - stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary); - stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight); - stack.PushColorAbgr(ImGuiCol.Separator, a.Border); - stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight); - stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary); + stack.PushColorAbgr(ImGuiCol.Separator, a.Border); + stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary); + } + catch + { + stack.Dispose(); + throw; + } return stack; } @@ -101,24 +113,46 @@ internal static class GlobalStyleScope return (themeChildBgRgba & 0xFFFFFF00u) | childBgAlpha; } + // Counter-based scope: pushes go straight onto the global ImGui style + // stack and we only remember how many of each kind we pushed. Dispose + // pops them in one batched PopStyleColor(count)/PopStyleVar(count) call. + // This replaces the old List + per-push boxed ImRaii structs + // (44 allocations/frame) with two ints — zero per-frame GC. Every style + // var pushed here is a single-float var, so PopStyleVar(count) is valid; + // both colour paths funnel into one PushStyleColor, so one colour counter + // covers them. Symmetry is the whole contract: the pop counts must equal + // the push counts or the global stack corrupts for every other window. private sealed class StackHandle : IDisposable { - private readonly List _items = new(64); + private int _colorCount; + private int _styleVarCount; - internal void PushColor(ImGuiCol slot, uint rgba) => - _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba))); + internal void PushColor(ImGuiCol slot, uint rgba) + { + ImGui.PushStyleColor(slot, ColourUtil.RgbaToAbgr(rgba)); + _colorCount++; + } - internal void PushColorAbgr(ImGuiCol slot, uint abgr) => - _items.Add(ImRaii.PushColor(slot, abgr)); + internal void PushColorAbgr(ImGuiCol slot, uint abgr) + { + ImGui.PushStyleColor(slot, abgr); + _colorCount++; + } - internal void PushStyleVar(ImGuiStyleVar var, float value) => - _items.Add(ImRaii.PushStyle(var, value)); + internal void PushStyleVar(ImGuiStyleVar var, float value) + { + ImGui.PushStyleVar(var, value); + _styleVarCount++; + } public void Dispose() { - for (var i = _items.Count - 1; i >= 0; i--) - _items[i].Dispose(); - _items.Clear(); + if (_styleVarCount > 0) + ImGui.PopStyleVar(_styleVarCount); + if (_colorCount > 0) + ImGui.PopStyleColor(_colorCount); + _styleVarCount = 0; + _colorCount = 0; } } }