feat(settings): add ColorPicker skeleton with lifecycle

This commit is contained in:
2026-05-26 17:05:40 +02:00
parent c6f7266194
commit 21689655da
@@ -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.");
}
}
}