feat(themes): settings tab with built-in and custom theme grids

This commit is contained in:
2026-05-05 14:05:59 +02:00
parent cb5c940a84
commit c878d24d11
3 changed files with 150 additions and 2 deletions
+1
View File
@@ -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),
+2 -2
View File
@@ -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"),
+147
View File
@@ -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);
}
}
}