diff --git a/HellionChat/Ui/Components/Settings/ColorPicker.cs b/HellionChat/Ui/Components/Settings/ColorPicker.cs new file mode 100644 index 0000000..d13cfcd --- /dev/null +++ b/HellionChat/Ui/Components/Settings/ColorPicker.cs @@ -0,0 +1,128 @@ +using System.Numerics; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility.Raii; +using HellionChat.Themes; +using HellionChat.Util; + +namespace HellionChat.Ui.Components.Settings; + +internal sealed class ColorPicker +{ + private readonly ThemeRegistry _themes; + + public ColorPicker(ThemeRegistry themes) + { + _themes = themes; + } + + public void Draw() + { + if (_themes.EditingThemeBuffer is null) + { + DrawIdleState(); + return; + } + + DrawEditState(_themes.EditingThemeBuffer); + } + + private void DrawIdleState() + { + var active = _themes.Active; + ImGui.TextDisabled($"Active theme: {active.Name}"); + + // Fork built-ins before editing: Switch() prefers built-in slugs over custom + // files with the same slug, so an in-place edit would silently no-op. + if (active.IsBuiltIn) + { + if (ImGui.Button("Fork & Edit")) + { + ForkAndBeginEditing(active); + } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip( + "Built-in themes cannot be edited in place. Fork creates a custom copy you can edit and save." + ); + } + } + else + { + if (ImGui.Button("Edit theme")) + { + _themes.BeginEditing(active); + } + } + } + + private void ForkAndBeginEditing(Theme source) + { + // 100 attempts is already absurd for one base slug; past that means the + // themes folder is broken, not a real user collision. + var newSlug = $"{source.Slug}_fork"; + var attempt = 2; + const int MaxAttempts = 100; + while (_themes.TryGet(newSlug, out _)) + { + if (attempt > MaxAttempts) + { + return; + } + newSlug = $"{source.Slug}_fork_{attempt++}"; + } + + var forked = source with + { + Slug = newSlug, + Name = $"{source.Name} (fork)", + IsBuiltIn = false, + }; + // The `with`-clone leaves AbgrCache empty (private setter outside primary ctor). + // OK here because EditingBuffer render path goes through TokenResolver.Resolve(token, buffer.Colors), + // not AbgrCache. Save triggers Switch() which recomputes for the active theme. + _themes.BeginEditing(forked); + } + + private void DrawEditState(Theme buffer) + { + ImGui.TextUnformatted($"Editing: {buffer.Name}"); + ImGui.Separator(); + + // Sections land in Step 2; placeholder keeps the skeleton compiling. + ImGui.TextDisabled("(sections land in next sub-step)"); + + ImGui.Separator(); + DrawActionButtons(buffer); + } + + private void DrawActionButtons(Theme buffer) + { + if (ImGui.Button("Save")) + { + _themes.SaveEditingBuffer(out _); + } + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + _themes.DiscardEditingBuffer(); + } + ImGui.SameLine(); + + // Reset is disabled during a fork edit: the active theme is still the built-in + // source until Save, so BeginEditing(Active) would throw away the fork's slug/name + // framing and effectively cancel the fork. Proper Reset-during-fork needs a tracked + // _editingSource field in ThemeRegistry (out of v1.7.0 scope). + var isForkBuffer = !buffer.IsBuiltIn && _themes.Active.Slug != buffer.Slug; + using (ImRaii.Disabled(isForkBuffer)) + { + if (ImGui.Button("Reset to source")) + { + _themes.BeginEditing(_themes.Active); + } + } + if (isForkBuffer && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + { + ImGui.SetTooltip("Reset is unavailable while editing a fork. Save or Cancel first."); + } + } +}