diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9901e81..5c106e3 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Interface.Internal.Notifications; +using Glamourer.Customization; +using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -14,6 +16,7 @@ namespace Glamourer.Designs; public sealed class Design : DesignBase, ISavable { #region Data + internal Design(CustomizationService customize, ItemManager items) : base(customize, items) { } @@ -40,7 +43,8 @@ public sealed class Design : DesignBase, ISavable public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = Array.Empty(); public int Index { get; internal set; } - public SortedList AssociatedMods { get; private set; } = new(); + public string Color { get; internal set; } = string.Empty; + public SortedList AssociatedMods { get; private set; } = new(); public string Incognito => Identifier.ToString()[..8]; @@ -59,6 +63,7 @@ public sealed class Design : DesignBase, ISavable ["LastEdit"] = LastEdit, ["Name"] = Name.Text, ["Description"] = Description, + ["Color"] = Color, ["Tags"] = JArray.FromObject(Tags), ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), @@ -131,6 +136,7 @@ public sealed class Design : DesignBase, ISavable LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); + design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs new file mode 100644 index 0000000..dc36e78 --- /dev/null +++ b/Glamourer/Designs/DesignColors.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Utility.Raii; +using Glamourer.Gui; +using Glamourer.Services; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Classes; + +namespace Glamourer.Designs; + +public class DesignColorUi +{ + private readonly DesignColors _colors; + private readonly DesignManager _designs; + private readonly Configuration _config; + + private string _newName = string.Empty; + + public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) + { + _colors = colors; + _designs = designs; + _config = config; + } + + public void Draw() + { + using var table = ImRaii.Table("designColors", 3, ImGuiTableFlags.RowBg); + if (!table) + return; + + var changeString = string.Empty; + uint? changeValue = null; + + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("##Select", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("Color Name", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableHeadersRow(); + + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), buttonSize, + "Revert the color used for missing design colors to its default.", _colors.MissingColor == DesignColors.MissingColorDefault, + true)) + { + changeString = DesignColors.MissingColorName; + changeValue = DesignColors.MissingColorDefault; + } + + ImGui.TableNextColumn(); + if (DrawColorButton(DesignColors.MissingColorName, _colors.MissingColor, out var newColor)) + { + changeString = DesignColors.MissingColorName; + changeValue = newColor; + } + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(DesignColors.MissingColorName); + ImGuiUtil.HoverTooltip("This color is used when the color specified in a design is not available."); + + + var disabled = !_config.DeleteDesignModifier.IsActive(); + var tt = "Delete this color. This does not remove it from designs using it."; + if (disabled) + tt += $"\nHold {_config.DeleteDesignModifier} to delete."; + + foreach (var ((name, color), idx) in _colors.WithIndex()) + { + using var id = ImRaii.PushId(idx); + ImGui.TableNextColumn(); + + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true)) + { + changeString = name; + changeValue = null; + } + + ImGui.TableNextColumn(); + if (DrawColorButton(name, color, out newColor)) + { + changeString = name; + changeValue = newColor; + } + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(name); + } + + ImGui.TableNextColumn(); + (tt, disabled) = _newName.Length == 0 + ? ("Specify a name for a new color first.", true) + : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName + ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) + : _colors.ContainsKey(_newName) + ? ($"The color {_newName} already exists, please choose a different name.", true) + : ($"Add a new color {_newName} to your list.", false); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, disabled, true)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("##newDesignColor", "New Color Name...", ref _newName, 64, ImGuiInputTextFlags.EnterReturnsTrue)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + + if (changeString.Length > 0) + { + if (!changeValue.HasValue) + _colors.DeleteColor(changeString); + else + _colors.SetColor(changeString, changeValue.Value); + } + } + + public static bool DrawColorButton(string tooltip, uint color, out uint newColor) + { + var vec = ImGui.ColorConvertU32ToFloat4(color); + if (!ImGui.ColorEdit4(tooltip, ref vec, ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs)) + { + ImGuiUtil.HoverTooltip(tooltip); + newColor = color; + return false; + } + ImGuiUtil.HoverTooltip(tooltip); + + newColor = ImGui.ColorConvertFloat4ToU32(vec); + return newColor != color; + } +} + +public class DesignColors : ISavable, IReadOnlyDictionary +{ + public const string AutomaticName = "Automatic"; + public const string MissingColorName = "Missing Color"; + public const uint MissingColorDefault = 0xFF0000D0; + + private readonly SaveService _saveService; + private readonly Dictionary _colors = new(); + public uint MissingColor { get; private set; } = MissingColorDefault; + + public event Action? ColorChanged; + + public DesignColors(SaveService saveService) + { + _saveService = saveService; + Load(); + } + + public uint GetColor(Design design) + { + if (design.Color.Length == 0) + return AutoColor(design); + + return TryGetValue(design.Color, out var color) ? color : MissingColor; + } + + public void SetColor(string key, uint newColor) + { + if (key.Length == 0) + return; + + if (key is MissingColorName && MissingColor != newColor) + { + MissingColor = newColor; + SaveAndInvoke(); + return; + } + + if (_colors.TryAdd(key, newColor)) + { + SaveAndInvoke(); + return; + } + + _colors.TryGetValue(key, out var color); + _colors[key] = newColor; + + if (color != newColor) + SaveAndInvoke(); + } + + private void SaveAndInvoke() + { + ColorChanged?.Invoke(); + _saveService.DelaySave(this, TimeSpan.FromSeconds(2)); + } + + public void DeleteColor(string key) + { + if (_colors.Remove(key)) + SaveAndInvoke(); + } + + public string ToFilename(FilenameService fileNames) + => fileNames.DesignColorFile; + + public void Save(StreamWriter writer) + { + var jObj = new JObject + { + ["Version"] = 1, + ["MissingColor"] = MissingColor, + ["Definitions"] = JToken.FromObject(_colors), + }; + writer.Write(jObj.ToString(Formatting.Indented)); + } + + private void Load() + { + _colors.Clear(); + var file = _saveService.FileNames.DesignColorFile; + if (!File.Exists(file)) + return; + + try + { + var text = File.ReadAllText(file); + var jObj = JObject.Parse(text); + var version = jObj["Version"]?.ToObject() ?? 0; + switch (version) + { + case 1: + { + var dict = jObj["Definitions"]?.ToObject>() ?? new Dictionary(); + _colors.EnsureCapacity(dict.Count); + foreach (var kvp in dict) + _colors.Add(kvp.Key, kvp.Value); + MissingColor = jObj["MissingColor"]?.ToObject() ?? MissingColorDefault; + break; + } + default: throw new Exception($"Unknown Version {version}"); + } + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, "Could not read design color file.", NotificationType.Error); + } + } + + public IEnumerator> GetEnumerator() + => _colors.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _colors.Count; + + public bool ContainsKey(string key) + => _colors.ContainsKey(key); + + public bool TryGetValue(string key, out uint value) + { + if (_colors.TryGetValue(key, out value)) + { + if (value == 0) + value = ImGui.GetColorU32(ImGuiCol.Text); + return true; + } + + return false; + } + + public static uint AutoColor(DesignBase design) + { + var customize = design.ApplyCustomize == 0; + var equip = design.ApplyEquip == 0; + return (customize, equip) switch + { + (true, true) => ColorId.StateDesign.Value(), + (true, false) => ColorId.EquipmentDesign.Value(), + (false, true) => ColorId.CustomizationDesign.Value(), + (false, false) => ColorId.NormalDesign.Value(), + }; + } + + public uint this[string key] + => _colors[key]; + + public IEnumerable Keys + => _colors.Keys; + + public IEnumerable Values + => _colors.Values; +} diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 0fd96b2..4a24f59 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers.Text; using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 960c460..db727e8 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text.RegularExpressions; using Dalamud.Interface.Internal.Notifications; using Glamourer.Events; -using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 1084fb0..b8cd9a2 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -189,6 +189,19 @@ public class DesignManager _event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); } + public void ChangeColor(Design design, string newColor) + { + var oldColor = design.Color; + if (oldColor == newColor) + return; + + design.Color = newColor; + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); + } + /// Add a new tag to a design. The tags remain sorted. public void AddTag(Design design, string tag) { diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 9c8e189..55956f0 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -32,6 +32,9 @@ public sealed class DesignChanged : EventWrapper An existing design had its description changed. Data is the prior description [string]. ChangedDescription, + /// An existing design had its associated color changed. Data is the prior color [string]. + ChangedColor, + /// An existing design had a new tag added. Data is the new tag and the index it was added at [(string, int)]. AddedTag, diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 690344d..49359b2 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -19,17 +20,19 @@ public abstract class DesignComboBase : FilterComboCache>, { private readonly Configuration _config; private readonly DesignChanged _designChanged; + private readonly DesignColors _designColors; protected readonly TabSelected TabSelected; protected float InnerWidth; private Design? _currentDesign; protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, - TabSelected tabSelected, Configuration config) + TabSelected tabSelected, Configuration config, DesignColors designColors) : base(generator, log) { _designChanged = designChanged; TabSelected = tabSelected; _config = config; + _designColors = designColors; _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignCombo); } @@ -41,8 +44,13 @@ public abstract class DesignComboBase : FilterComboCache>, protected override bool DrawSelectable(int globalIdx, bool selected) { - var ret = base.DrawSelectable(globalIdx, selected); var (design, path) = Items[globalIdx]; + bool ret; + using (var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(design))) + { + ret = base.DrawSelectable(globalIdx, selected); + } + if (path.Length > 0 && design.Name != path) { var start = ImGui.GetItemRectMin(); @@ -128,11 +136,11 @@ public sealed class DesignCombo : DesignComboBase private readonly DesignManager _manager; public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, - Configuration config) + Configuration config, DesignColors designColors) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) - .ToList(), log, designChanged, tabSelected, config) + .ToList(), log, designChanged, tabSelected, config, designColors) { _manager = designs; if (designs.Designs.Count == 0) @@ -170,19 +178,19 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable public readonly Design RevertDesign; private readonly AutoDesignManager _autoDesignManager; - public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) - : this(designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) + : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) { } - private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) .Prepend(new Tuple(revertDesign, string.Empty)) - .ToList(), log, designChanged, tabSelected, config) + .ToList(), log, designChanged, tabSelected, config, designColors) { RevertDesign = revertDesign; _autoDesignManager = autoDesignManager; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 38fd5f8..2b0cac6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; @@ -13,12 +14,35 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; +public sealed class DesignColorCombo : FilterComboCache +{ + private readonly DesignColors _designColors; + + public DesignColorCombo(DesignColors designColors) + : base(designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), Glamourer.Log) + => _designColors = designColors; + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var key = Items[globalIdx]; + var color = globalIdx == 0 ? 0 : _designColors[key]; + using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); + var ret = base.DrawSelectable(globalIdx, selected); + if (globalIdx == 0) + ImGuiUtil.HoverTooltip( + "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); + return ret; + } +} + public class DesignDetailTab { private readonly SaveService _saveService; private readonly DesignFileSystemSelector _selector; private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager; + private readonly DesignColors _colors; + private readonly DesignColorCombo _colorCombo; private readonly TagButtons _tagButtons = new(); private string? _newPath; @@ -29,12 +53,15 @@ public class DesignDetailTab private Design? _changeDesign; private DesignFileSystem.Leaf? _changeLeaf; - public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem) + public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, + DesignColors colors) { _saveService = saveService; _selector = selector; _manager = manager; _fileSystem = fileSystem; + _colors = colors; + _colorCombo = new DesignColorCombo(_colors); } public void Draw() @@ -89,7 +116,8 @@ public class DesignDetailTab } catch (Exception ex) { - Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", NotificationType.Warning); + Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", + NotificationType.Warning); } } @@ -117,6 +145,34 @@ public class DesignDetailTab Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); } + ImGuiUtil.DrawFrameColumn("Color"); + var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; + ImGui.TableNextColumn(); + if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design. Right-Click to revert to automatic coloring.", + width.X - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) + && _colorCombo.CurrentSelection != null) + { + colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; + _manager.ChangeColor(_selector.Selected!, colorName); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + _manager.ChangeColor(_selector.Selected!, string.Empty); + + if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor)) + { + ImGui.SameLine(); + if (DesignColorUi.DrawColorButton($"Color associated with {_selector.Selected!.Color}", currentColor, out var newColor)) + _colors.SetColor(_selector.Selected!.Color, newColor); + } + else if (_selector.Selected!.Color.Length != 0) + { + ImGui.SameLine(); + var size = new Vector2(ImGui.GetFrameHeight()); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); + ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + } + ImGuiUtil.DrawFrameColumn("Creation Date"); ImGui.TableNextColumn(); ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 1de6ebd..7960ad1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -25,6 +25,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector base.SelectedLeaf; - public struct DesignState - { - public ColorId Color; - } + public record struct DesignState(uint Color) + { } public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event, - Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, CustomizationService customizationService) + Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, CustomizationService customizationService, + DesignColors designColors) : base(fileSystem, keyState, log, allowMultipleSelection: true) { _designManager = designManager; @@ -58,8 +58,10 @@ public sealed class DesignFileSystemSelector : FileSystemSelector SortMode @@ -121,7 +124,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Combined wrapper for handling all filters and setting state. private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) { - var applyEquip = leaf.Value.ApplyEquip != 0; - var applyCustomize = leaf.Value.ApplyCustomize != 0; - - state.Color = (applyEquip, applyCustomize) switch - { - (false, false) => ColorId.StateDesign, - (false, true) => ColorId.CustomizationDesign, - (true, false) => ColorId.EquipmentDesign, - (true, true) => ColorId.NormalDesign, - }; - + state = new DesignState(_designColors.GetColor(leaf.Value)); return ApplyStringFilters(leaf, leaf.Value); } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index d690a42..dc20f78 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -7,6 +7,7 @@ using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; +using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -30,9 +31,11 @@ public class SettingsTab : ITab private readonly UiBuilder _uiBuilder; private readonly GlamourerChangelog _changelog; private readonly FunModule _funModule; + private readonly DesignColorUi _designColorUi; public SettingsTab(Configuration config, DesignFileSystemSelector selector, CodeService codeService, PenumbraAutoRedraw autoRedraw, - ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys) + ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys, + DesignColorUi designColorUi) { _config = config; _selector = selector; @@ -42,6 +45,7 @@ public class SettingsTab : ITab _uiBuilder = uiBuilder; _changelog = changelog; _funModule = funModule; + _designColorUi = designColorUi; _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); } @@ -177,12 +181,22 @@ public class SettingsTab : ITab if (!ImGui.CollapsingHeader("Colors")) return; - foreach (var color in Enum.GetValues()) + using (var tree = ImRaii.TreeNode("Custom Design Colors")) { - var (defaultColor, name, description) = color.Data(); - var currentColor = _config.Colors.TryGetValue(color, out var current) ? current : defaultColor; - if (Widget.ColorPicker(name, description, currentColor, c => _config.Colors[color] = c, defaultColor)) - _config.Save(); + if (tree) + _designColorUi.Draw(); + } + + using (var tree = ImRaii.TreeNode("Color Settings")) + { + if (tree) + foreach (var color in Enum.GetValues()) + { + var (defaultColor, name, description) = color.Data(); + var currentColor = _config.Colors.TryGetValue(color, out var current) ? current : defaultColor; + if (Widget.ColorPicker(name, description, currentColor, c => _config.Colors[color] = c, defaultColor)) + _config.Save(); + } } ImGui.NewLine(); diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index dfccb2a..ea71319 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -35,6 +35,7 @@ public class BackupService new(fileNames.UnlockFileCustomize), new(fileNames.UnlockFileItems), new(fileNames.FavoriteFile), + new(fileNames.DesignColorFile), }; list.AddRange(fileNames.Designs()); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 7299d32..607bcb3 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -16,6 +16,7 @@ public class FilenameService public readonly string UnlockFileCustomize; public readonly string UnlockFileItems; public readonly string FavoriteFile; + public readonly string DesignColorFile; public FilenameService(DalamudPluginInterface pi) { @@ -28,6 +29,7 @@ public class FilenameService UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); DesignDirectory = Path.Combine(ConfigDirectory, "designs"); FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); + DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2552f44..eefa761 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -106,7 +106,8 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddState(this IServiceCollection services) => services.AddSingleton() @@ -143,7 +144,8 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton()