diff --git a/HellionChat/Ui/Settings.cs b/HellionChat/Ui/Settings.cs index 538fc9f..cec9ea5 100755 --- a/HellionChat/Ui/Settings.cs +++ b/HellionChat/Ui/Settings.cs @@ -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), diff --git a/HellionChat/Ui/SettingsOverview.cs b/HellionChat/Ui/SettingsOverview.cs index 0574021..9715bbe 100644 --- a/HellionChat/Ui/SettingsOverview.cs +++ b/HellionChat/Ui/SettingsOverview.cs @@ -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"), diff --git a/HellionChat/Ui/SettingsTabs/Themes.cs b/HellionChat/Ui/SettingsTabs/Themes.cs new file mode 100644 index 0000000..0fe85af --- /dev/null +++ b/HellionChat/Ui/SettingsTabs/Themes.cs @@ -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 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); + } + } +}