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 General(Plugin, Mutable),
new Appearance(Plugin, Mutable), new Appearance(Plugin, Mutable),
new SettingsTabs.Themes(Plugin, Mutable),
new SettingsTabs.Window(Plugin, Mutable), new SettingsTabs.Window(Plugin, Mutable),
new Chat(Plugin, Mutable), new Chat(Plugin, Mutable),
new SettingsTabs.Tabs(Plugin, Mutable), new SettingsTabs.Tabs(Plugin, Mutable),
+2 -2
View File
@@ -12,12 +12,12 @@ internal sealed class SettingsOverview
private readonly SettingsWindow _window; private readonly SettingsWindow _window;
// Card-Reihenfolge entspricht 1:1 dem Tabs-Index in SettingsWindow. // Card-Reihenfolge entspricht 1:1 dem Tabs-Index in SettingsWindow.
// Themes (Phase J) wird später als Card 2 zwischen Appearance und Window // Themes ist Card-Index 2, eingeschoben zwischen Appearance und Window.
// eingeschoben — dabei muss diese Liste neu gemappt werden.
private static readonly (FontAwesomeIcon Icon, string TitleKey, string SubtextKey)[] CardDefs = private static readonly (FontAwesomeIcon Icon, string TitleKey, string SubtextKey)[] CardDefs =
[ [
(FontAwesomeIcon.SlidersH, "Settings_Card_General_Title", "Settings_Card_General_Subtext"), (FontAwesomeIcon.SlidersH, "Settings_Card_General_Title", "Settings_Card_General_Subtext"),
(FontAwesomeIcon.Palette, "Settings_Card_Appearance_Title", "Settings_Card_Appearance_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.WindowMaximize, "Settings_Card_Window_Title", "Settings_Card_Window_Subtext"),
(FontAwesomeIcon.Comments, "Settings_Card_Chat_Title", "Settings_Card_Chat_Subtext"), (FontAwesomeIcon.Comments, "Settings_Card_Chat_Title", "Settings_Card_Chat_Subtext"),
(FontAwesomeIcon.FolderTree, "Settings_Card_Tabs_Title", "Settings_Card_Tabs_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);
}
}
}