feat(themes): settings tab with built-in and custom theme grids
This commit is contained in:
@@ -45,6 +45,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
[
|
||||
new General(Plugin, Mutable),
|
||||
new Appearance(Plugin, Mutable),
|
||||
new SettingsTabs.Themes(Plugin, Mutable),
|
||||
new SettingsTabs.Window(Plugin, Mutable),
|
||||
new Chat(Plugin, Mutable),
|
||||
new SettingsTabs.Tabs(Plugin, Mutable),
|
||||
|
||||
@@ -12,12 +12,12 @@ internal sealed class SettingsOverview
|
||||
private readonly SettingsWindow _window;
|
||||
|
||||
// Card-Reihenfolge entspricht 1:1 dem Tabs-Index in SettingsWindow.
|
||||
// Themes (Phase J) wird später als Card 2 zwischen Appearance und Window
|
||||
// eingeschoben — dabei muss diese Liste neu gemappt werden.
|
||||
// Themes ist Card-Index 2, eingeschoben zwischen Appearance und Window.
|
||||
private static readonly (FontAwesomeIcon Icon, string TitleKey, string SubtextKey)[] CardDefs =
|
||||
[
|
||||
(FontAwesomeIcon.SlidersH, "Settings_Card_General_Title", "Settings_Card_General_Subtext"),
|
||||
(FontAwesomeIcon.Palette, "Settings_Card_Appearance_Title", "Settings_Card_Appearance_Subtext"),
|
||||
(FontAwesomeIcon.Swatchbook, "Settings_Card_Themes_Title", "Settings_Card_Themes_Subtext"),
|
||||
(FontAwesomeIcon.WindowMaximize, "Settings_Card_Window_Title", "Settings_Card_Window_Subtext"),
|
||||
(FontAwesomeIcon.Comments, "Settings_Card_Chat_Title", "Settings_Card_Chat_Subtext"),
|
||||
(FontAwesomeIcon.FolderTree, "Settings_Card_Tabs_Title", "Settings_Card_Tabs_Subtext"),
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Themes : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private readonly Configuration Mutable;
|
||||
|
||||
public string Name => HellionStrings.ResourceManager.GetString("Settings_Tab_Themes") ?? "Themes" + "###tabs-themes";
|
||||
|
||||
internal Themes(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
var registry = Plugin.ThemeRegistry;
|
||||
var active = registry.Get(Mutable.Theme);
|
||||
|
||||
var activeLabelTemplate = HellionStrings.ResourceManager.GetString("Settings_Themes_Active") ?? "Active: {0}";
|
||||
ImGui.TextUnformatted(string.Format(activeLabelTemplate, active.Name));
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF8FA3B5u))
|
||||
ImGui.TextUnformatted(active.Author);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var builtInsLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_BuiltIns") ?? "Built-in themes";
|
||||
ImGui.TextUnformatted(builtInsLabel);
|
||||
ImGui.Spacing();
|
||||
DrawThemeGrid(registry.AllBuiltIns(), active.Slug);
|
||||
|
||||
var customs = registry.AllCustom().ToList();
|
||||
if (customs.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
var customLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_Custom") ?? "Custom themes";
|
||||
ImGui.TextUnformatted(customLabel);
|
||||
ImGui.Spacing();
|
||||
DrawThemeGrid(customs, active.Slug);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var openFolderLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_OpenFolder") ?? "Open themes folder";
|
||||
if (ImGui.Button(openFolderLabel))
|
||||
{
|
||||
var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(dir);
|
||||
Dalamud.Utility.Util.OpenLink(dir);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var exportLabel = HellionStrings.ResourceManager.GetString("Settings_Themes_ExportActive") ?? "Export active...";
|
||||
if (ImGui.Button(exportLabel))
|
||||
{
|
||||
// Export-Logik wird in Phase L (Task 21) ergänzt — Stub belassen, Button bleibt sichtbar.
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeGrid(IEnumerable<Theme> themes, string activeSlug)
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var columns = avail.X >= 700f ? 3 : 2;
|
||||
var cardWidth = (avail.X - (columns - 1) * 8f) / columns;
|
||||
var cardHeight = 110f;
|
||||
var i = 0;
|
||||
foreach (var theme in themes)
|
||||
{
|
||||
DrawThemeCard(theme, activeSlug, cardWidth, cardHeight);
|
||||
i++;
|
||||
if (i % columns != 0)
|
||||
ImGui.SameLine();
|
||||
else
|
||||
ImGui.NewLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeCard(Theme theme, string activeSlug, float w, float h)
|
||||
{
|
||||
var isActive = string.Equals(theme.Slug, activeSlug, StringComparison.OrdinalIgnoreCase);
|
||||
var cursorBefore = ImGui.GetCursorScreenPos();
|
||||
var clicked = ImGui.InvisibleButton($"##theme-card-{theme.Slug}", new Vector2(w, h));
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
var bg = ColourUtil.RgbaToAbgr(theme.Colors.WindowBg | 0xFFu);
|
||||
draw.AddRectFilled(cursorBefore, cursorBefore + new Vector2(w, h), bg, 4f);
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
var border = ColourUtil.RgbaToAbgr(theme.Colors.Primary);
|
||||
draw.AddRect(cursorBefore, cursorBefore + new Vector2(w, h), border, 4f, ImDrawFlags.None, 2f);
|
||||
}
|
||||
else if (hovered)
|
||||
{
|
||||
var border = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight & 0xFFFFFF99u);
|
||||
draw.AddRect(cursorBefore, cursorBefore + new Vector2(w, h), border, 4f, ImDrawFlags.None, 1f);
|
||||
}
|
||||
|
||||
// Akzent-Swatch links oben
|
||||
var swatchPos = cursorBefore + new Vector2(12f, 12f);
|
||||
var swatchSize = new Vector2(20f, 20f);
|
||||
draw.AddRectFilled(swatchPos, swatchPos + swatchSize, ColourUtil.RgbaToAbgr(theme.Colors.Primary), 3f);
|
||||
|
||||
// Name
|
||||
ImGui.SetCursorScreenPos(cursorBefore + new Vector2(40f, 12f));
|
||||
var textColor = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, textColor))
|
||||
ImGui.TextUnformatted(theme.Name);
|
||||
|
||||
// Author
|
||||
ImGui.SetCursorScreenPos(cursorBefore + new Vector2(40f, 32f));
|
||||
var mutedColor = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, mutedColor))
|
||||
ImGui.TextUnformatted(theme.Author);
|
||||
|
||||
// Description (wrapped, falls zu lang)
|
||||
ImGui.SetCursorScreenPos(cursorBefore + new Vector2(12f, 60f));
|
||||
ImGui.PushTextWrapPos(cursorBefore.X + w - 12f);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, mutedColor))
|
||||
ImGui.TextUnformatted(theme.Description);
|
||||
ImGui.PopTextWrapPos();
|
||||
|
||||
// Cursor unter die Card setzen
|
||||
ImGui.SetCursorScreenPos(cursorBefore + new Vector2(0f, h + 8f));
|
||||
|
||||
if (clicked)
|
||||
{
|
||||
Mutable.Theme = theme.Slug;
|
||||
Plugin.ThemeRegistry.Switch(theme.Slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user