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 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),
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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