Current state.

This commit is contained in:
Ottermandias 2026-02-21 01:31:30 +01:00
parent 04a8449967
commit b88bba1a87
115 changed files with 2225 additions and 1776 deletions

@ -1 +1 @@
Subproject commit 51b3c72e91816af0002dd543d64944e777b246ba Subproject commit 941dc7e1da694127a4405f4888ae162133131268

View file

@ -2,7 +2,6 @@
using Glamourer.Api.Enums; using Glamourer.Api.Enums;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.State; using Glamourer.State;
using ImSharp;
using Luna; using Luna;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -12,7 +11,6 @@ public class DesignsApi(
ApiHelpers helpers, ApiHelpers helpers,
DesignManager designs, DesignManager designs,
StateManager stateManager, StateManager stateManager,
DesignFileSystem fileSystem,
DesignColors color, DesignColors color,
DesignConverter converter) DesignConverter converter)
: IGlamourerApiDesigns, IApiService : IGlamourerApiDesigns, IApiService
@ -21,12 +19,11 @@ public class DesignsApi(
=> designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text);
public Dictionary<Guid, (string DisplayName, string FullPath, uint DisplayColor, bool ShownInQdb)> GetDesignListExtended() public Dictionary<Guid, (string DisplayName, string FullPath, uint DisplayColor, bool ShownInQdb)> GetDesignListExtended()
=> fileSystem.ToDictionary(kvp => kvp.Key.Identifier, => designs.Designs.ToDictionary(d => d.Identifier, d => (d.DisplayName, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign));
kvp => (kvp.Key.Name.Text, kvp.Value.FullName(), color.GetColor(kvp.Key).Color, kvp.Key.QuickDesign));
public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId)
=> designs.Designs.ByIdentifier(designId) is { } d => designs.Designs.ByIdentifier(designId) is { } d
? (d.Name.Text, fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d).Color, d.QuickDesign) ? (d.Name.Text, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign)
: (string.Empty, string.Empty, 0, false); : (string.Empty, string.Empty, 0, false);
public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags)

View file

@ -1,9 +1,10 @@
using Glamourer.Api.Api; using Glamourer.Api.Api;
using Glamourer.Config;
using Luna; using Luna;
namespace Glamourer.Api; namespace Glamourer.Api;
public class GlamourerApi(Configuration.Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
{ {
public const int CurrentApiVersionMajor = 1; public const int CurrentApiVersionMajor = 1;
public const int CurrentApiVersionMinor = 7; public const int CurrentApiVersionMinor = 7;

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using Glamourer.Events; using Glamourer.Events;
@ -16,22 +17,22 @@ namespace Glamourer.Automation;
public sealed class AutoDesignApplier : IDisposable public sealed class AutoDesignApplier : IDisposable
{ {
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly AutoDesignManager _manager; private readonly AutoDesignManager _manager;
private readonly StateManager _state; private readonly StateManager _state;
private readonly JobService _jobs; private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset; private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly AutomationChanged _event; private readonly AutomationChanged _event;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly WeaponLoading _weapons; private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger; private readonly DesignMerger _designMerger;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly JobChangeState _jobChangeState; private readonly JobChangeState _jobChangeState;
public AutoDesignApplier(Configuration.Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors,
AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
{ {

View file

@ -47,7 +47,7 @@ public class FixedDesignMigrator(JobService jobs)
var set = autoManager[^1]; var set = autoManager[^1];
foreach (var design in data.AsEnumerable().Reverse()) foreach (var design in data.AsEnumerable().Reverse())
{ {
if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf) if (!designFileSystem.Find(design.Item1, out var child) || child is not IFileSystemData<Design> leaf)
{ {
Glamourer.Messager.NotificationMessage($"Could not find design with path {design.Item1}, skipped fixed design.", Glamourer.Messager.NotificationMessage($"Could not find design with path {design.Item1}, skipped fixed design.",
NotificationType.Warning); NotificationType.Warning);

View file

@ -7,14 +7,16 @@ using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Services; using Glamourer.Services;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Luna.Generators;
using Newtonsoft.Json; using Newtonsoft.Json;
using OtterGui.Filesystem;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Glamourer.Configuration; namespace Glamourer.Config;
public class Configuration : IPluginConfiguration, ISavable public sealed partial class Configuration : IPluginConfiguration, ISavable
{ {
public const int CurrentVersion = 9;
[JsonIgnore] [JsonIgnore]
public readonly EphemeralConfig Ephemeral; public readonly EphemeralConfig Ephemeral;
@ -58,8 +60,11 @@ public class Configuration : IPluginConfiguration, ISavable
public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); public DefaultDesignSettings DefaultDesignSettings { get; set; } = new();
public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre;
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
[ConfigProperty(EventName = "OnRenameChanged")]
private RenameField _showRename = RenameField.BothDataPrio;
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control); public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control);
@ -70,7 +75,7 @@ public class Configuration : IPluginConfiguration, ISavable
[JsonConverter(typeof(SortModeConverter))] [JsonConverter(typeof(SortModeConverter))]
[JsonProperty(Order = int.MaxValue)] [JsonProperty(Order = int.MaxValue)]
public ISortMode<Design> SortMode { get; set; } = ISortMode<Design>.FoldersFirst; public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst;
public List<(string Code, bool Enabled)> Codes { get; set; } = []; public List<(string Code, bool Enabled)> Codes { get; set; } = [];
@ -80,7 +85,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool DebugMode { get; set; } = false; public bool DebugMode { get; set; } = false;
#endif #endif
public int Version { get; set; } = Constants.CurrentVersion; public int Version { get; set; } = CurrentVersion;
public Dictionary<ColorId, uint> Colors { get; private set; } public Dictionary<ColorId, uint> Colors { get; private set; }
= ColorId.Values.ToDictionary(c => c, c => c.Data().DefaultColor); = ColorId.Values.ToDictionary(c => c, c => c.Data().DefaultColor);
@ -142,45 +147,22 @@ public class Configuration : IPluginConfiguration, ISavable
serializer.Serialize(jWriter, this); serializer.Serialize(jWriter, this);
} }
public static class Constants
{
public const int CurrentVersion = 8;
public static readonly ISortMode<Design>[] ValidSortModes =
[
ISortMode<Design>.FoldersFirst,
ISortMode<Design>.Lexicographical,
new DesignFileSystem.CreationDate(),
new DesignFileSystem.InverseCreationDate(),
new DesignFileSystem.UpdateDate(),
new DesignFileSystem.InverseUpdateDate(),
ISortMode<Design>.InverseFoldersFirst,
ISortMode<Design>.InverseLexicographical,
ISortMode<Design>.FoldersLast,
ISortMode<Design>.InverseFoldersLast,
ISortMode<Design>.InternalOrder,
ISortMode<Design>.InverseInternalOrder,
];
}
/// <summary> Convert SortMode Types to their name. </summary> /// <summary> Convert SortMode Types to their name. </summary>
private class SortModeConverter : JsonConverter<ISortMode<Design>> private class SortModeConverter : JsonConverter<ISortMode>
{ {
public override void WriteJson(JsonWriter writer, ISortMode<Design>? value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer)
{ {
value ??= ISortMode<Design>.FoldersFirst; value ??= ISortMode.FoldersFirst;
serializer.Serialize(writer, value.GetType().Name); serializer.Serialize(writer, value.GetType().Name);
} }
public override ISortMode<Design> ReadJson(JsonReader reader, Type objectType, ISortMode<Design>? existingValue, public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, bool hasExistingValue,
bool hasExistingValue,
JsonSerializer serializer) JsonSerializer serializer)
{ {
var name = serializer.Deserialize<string>(reader); if (serializer.Deserialize<string>(reader) is { } name)
if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode)) return ISortMode.Valid.GetValueOrDefault(name, existingValue ?? ISortMode.FoldersFirst);
return existingValue ?? ISortMode<Design>.FoldersFirst;
return mode; return existingValue ?? ISortMode.FoldersFirst;
} }
} }
} }

View file

@ -1,4 +1,4 @@
namespace Glamourer.Configuration; namespace Glamourer.Config;
public class DefaultDesignSettings public class DefaultDesignSettings
{ {

View file

@ -1,7 +1,7 @@
using ImSharp; using ImSharp;
using Luna.Generators; using Luna.Generators;
namespace Glamourer.Configuration; namespace Glamourer.Config;
[Flags] [Flags]
[NamedEnum(Utf16: false)] [NamedEnum(Utf16: false)]
@ -40,9 +40,9 @@ public enum DesignPanelFlag : uint
public static partial class DesignPanelFlagExtensions public static partial class DesignPanelFlagExtensions
{ {
private static readonly StringU8 Expand = new("Expand"u8); private static readonly StringU8 Expand = new("Expand"u8);
public static Im.HeaderDisposable Header(this DesignPanelFlag flag, global::Glamourer.Configuration.Configuration config) public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config)
{ {
if (config.HideDesignPanel.HasFlag(flag)) if (config.HideDesignPanel.HasFlag(flag))
return default; return default;

View file

@ -6,11 +6,11 @@ using Luna.Generators;
using Newtonsoft.Json; using Newtonsoft.Json;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Glamourer.Configuration; namespace Glamourer.Config;
public partial class EphemeralConfig : ISavable public partial class EphemeralConfig : ISavable
{ {
public int Version { get; set; } = Configuration.Constants.CurrentVersion; public int Version { get; set; } = Configuration.CurrentVersion;
[ConfigProperty] [ConfigProperty]
private bool _incognitoMode; private bool _incognitoMode;

View file

@ -1,6 +1,6 @@
using Luna.Generators; using Luna.Generators;
namespace Glamourer.Configuration; namespace Glamourer.Config;
[TooltipEnum] [TooltipEnum]
public enum HeightDisplayType public enum HeightDisplayType

View file

@ -4,7 +4,7 @@ using Luna.Generators;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Glamourer.Configuration; namespace Glamourer.Config;
public sealed partial class UiConfig : ConfigurationFile<FilenameService> public sealed partial class UiConfig : ConfigurationFile<FilenameService>
{ {

View file

@ -14,7 +14,7 @@ using Notification = Luna.Notification;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public sealed class Design : DesignBase, ISavable, IDesignStandIn public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemValue<Design>
{ {
#region Data #region Data
@ -44,6 +44,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
public new const int FileVersion = 2; public new const int FileVersion = 2;
public Guid Identifier { get; internal init; } public Guid Identifier { get; internal init; }
public IFileSystemData<Design>? Node { get; set; }
public DateTimeOffset CreationDate { get; internal init; } public DateTimeOffset CreationDate { get; internal init; }
public DateTimeOffset LastEdit { get; internal set; } public DateTimeOffset LastEdit { get; internal set; }
public LowerString Name { get; internal set; } = LowerString.Empty; public LowerString Name { get; internal set; } = LowerString.Empty;
@ -57,6 +58,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
public string Color { get; internal set; } = string.Empty; public string Color { get; internal set; } = string.Empty;
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = []; public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
public LinkContainer Links { get; private set; } = []; public LinkContainer Links { get; private set; } = [];
public DataPath Path { get; } = new();
public string Incognito public string Incognito
=> Identifier.ToString()[..8]; => Identifier.ToString()[..8];
@ -124,6 +126,12 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
["Mods"] = SerializeMods(), ["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(), ["Links"] = Links.Serialize(),
}; };
if (Path.Folder.Length > 0)
ret["FileSystemFolder"] = Path.Folder;
if (Path.SortName is not null)
ret["SortOrderName"] = Path.SortName;
return ret; return ret;
} }
@ -251,6 +259,9 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
}; };
if (design.LastEdit < creationDate) if (design.LastEdit < creationDate)
design.LastEdit = creationDate; design.LastEdit = creationDate;
design.Path.Folder = json["FileSystemFolder"]?.Value<string>() ?? string.Empty;
design.Path.SortName = json["SortOrderName"]?.Value<string>()?.FixName();
design.SetWriteProtected(json["WriteProtected"]?.ToObject<bool>() ?? false); design.SetWriteProtected(json["WriteProtected"]?.ToObject<bool>() ?? false);
LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
LoadEquip(items, json["Equipment"], design, design.Name, true); LoadEquip(items, json["Equipment"], design, design.Name, true);
@ -340,7 +351,13 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
} }
public string LogName(string fileName) public string LogName(string fileName)
=> Path.GetFileNameWithoutExtension(fileName); => System.IO.Path.GetFileNameWithoutExtension(fileName);
#endregion #endregion
string IFileSystemValue.Identifier
=> Identifier.ToString();
public string DisplayName
=> Name.Text;
} }

View file

@ -1,4 +1,5 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Config;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Services; using Glamourer.Services;
using ImSharp; using ImSharp;
@ -8,7 +9,7 @@ using Newtonsoft.Json.Linq;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public class DesignColorUi(DesignColors colors, Configuration.Configuration config) public class DesignColorUi(DesignColors colors, Configuration config)
{ {
private string _newName = string.Empty; private string _newName = string.Empty;

View file

@ -1,6 +1,5 @@
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.String.Functions; using Penumbra.String.Functions;
@ -47,8 +46,8 @@ public unsafe struct DesignData
{ } { }
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public readonly bool ContainsName(LowerString name) public readonly bool ContainsName(string name)
=> ItemNames.Any(name.IsContained); => ItemNames.Any(i => i.Contains(name, StringComparison.OrdinalIgnoreCase));
public readonly StainIds Stain(EquipSlot slot) public readonly StainIds Stain(EquipSlot slot)
{ {

View file

@ -1,3 +1,4 @@
using Glamourer.Config;
using Glamourer.Designs.History; using Glamourer.Designs.History;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using Glamourer.Events; using Glamourer.Events;
@ -5,7 +6,6 @@ using Glamourer.GameData;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Services; using Glamourer.Services;
using ImSharp; using ImSharp;
using Luna;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -16,14 +16,14 @@ public class DesignEditor(
DesignChanged designChanged, DesignChanged designChanged,
CustomizeService customizations, CustomizeService customizations,
ItemManager items, ItemManager items,
Configuration.Configuration config) Configuration config)
: IDesignEditor : IDesignEditor
{ {
protected readonly DesignChanged DesignChanged = designChanged; protected readonly DesignChanged DesignChanged = designChanged;
protected readonly SaveService SaveService = saveService; protected readonly SaveService SaveService = saveService;
protected readonly ItemManager Items = items; protected readonly ItemManager Items = items;
protected readonly CustomizeService Customizations = customizations; protected readonly CustomizeService Customizations = customizations;
protected readonly Configuration.Configuration Config = config; protected readonly Configuration Config = config;
protected readonly Dictionary<Guid, DesignData> UndoStore = []; protected readonly Dictionary<Guid, DesignData> UndoStore = [];
private bool _forceFullItemOff; private bool _forceFullItemOff;

View file

@ -3,195 +3,64 @@ using Glamourer.Designs.History;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Luna; using Luna;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public sealed class DesignFileSystem : OtterGui.Filesystem.FileSystem<Design>, IDisposable, ISavable public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredService
{ {
private readonly DesignChanged _designChanged; private readonly DesignFileSystemSaver _saver;
private readonly DesignChanged _designChanged;
private readonly SaveService _saveService; public DesignFileSystem(Logger log, SaveService saveService, DesignStorage designs, DesignChanged designChanged)
private readonly DesignManager _designManager; : base("DesignFileSystem", log, true)
public DesignFileSystem(DesignManager designManager, SaveService saveService, DesignChanged designChanged)
{ {
_designManager = designManager;
_saveService = saveService;
_designChanged = designChanged; _designChanged = designChanged;
_designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystem); _saver = new DesignFileSystemSaver(log, this, saveService, designs);
Changed += OnChange;
Reload(); _saver.Load();
_designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystem);
} }
private void Reload() private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _)
{ {
if (Load(new FileInfo(_saveService.FileNames.DesignFileSystem), _designManager.Designs, DesignToIdentifier, DesignToName)) switch (type)
_saveService.ImmediateSave(this); {
case DesignChanged.Type.ReloadedAll: _saver.Load(); break;
case DesignChanged.Type.Created:
var parent = Root;
if (design.Path.Folder.Length > 0)
try
{
parent = FindOrCreateAllFolders(design.Path.Folder);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex,
$"Could not move design to {design.Path} because the folder could not be created.",
NotificationType.Error);
}
Glamourer.Log.Debug("Reloaded design filesystem."); var (data, _) = CreateDuplicateDataNode(parent, design.Path.SortName ?? design.Name, design);
Selection.Select(data);
break;
case DesignChanged.Type.Deleted:
if (design.Node is { } node)
{
if (node.Selected)
Selection.UnselectAll();
Delete(node);
}
break;
case DesignChanged.Type.Renamed when design.Path.SortName is null:
RenameWithDuplicates(design.Node!, design.Path.GetIntendedName(design.Name.Text));
break;
// TODO: Maybe add path changes?
}
} }
public void Dispose() public void Dispose()
{ {
_designChanged.Unsubscribe(OnDesignChange); _designChanged.Unsubscribe(OnDesignChanged);
}
public struct CreationDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Creation Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
}
public struct UpdateDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Update Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit));
}
public struct InverseCreationDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Creation Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
}
public struct InverseUpdateDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Update Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit));
}
private void OnChange(OtterGui.Filesystem.FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != OtterGui.Filesystem.FileSystemChangeType.Reload)
_saveService.QueueSave(this);
}
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? data)
{
switch (type)
{
case DesignChanged.Type.Created:
var parent = Root;
if ((data as CreationTransaction?)?.Path is { } path)
try
{
parent = FindOrCreateAllFolders(path);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.",
NotificationType.Error);
}
CreateDuplicateLeaf(parent, design.Name.Text, design);
return;
case DesignChanged.Type.Deleted:
if (TryGetValue(design, out var leaf1))
Delete(leaf1);
return;
case DesignChanged.Type.ReloadedAll:
Reload();
return;
case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName:
if (!TryGetValue(design, out var leaf2))
return;
var old = oldName.FixName();
if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
RenameWithDuplicates(leaf2, design.Name);
return;
}
}
// Used for saving and loading.
private static string DesignToIdentifier(Design design)
=> design.Identifier.ToString();
private static string DesignToName(Design design)
=> design.Name.Text.FixName();
private static bool DesignHasDefaultPath(Design design, string fullPath)
{
var regex = new Regex($@"^{Regex.Escape(DesignToName(design))}( \(\d+\))?$");
return regex.IsMatch(fullPath);
}
private static (string, bool) SaveDesign(Design design, string fullPath)
// Only save pairs with non-default paths.
=> DesignHasDefaultPath(design, fullPath)
? (string.Empty, false)
: (DesignToIdentifier(design), true);
internal static void MigrateOldPaths(SaveService saveService, Dictionary<string, string> oldPaths)
{
if (oldPaths.Count == 0)
return;
var file = saveService.FileNames.DesignFileSystem;
try
{
JObject jObject;
if (File.Exists(file))
{
var text = File.ReadAllText(file);
jObject = JObject.Parse(text);
var dict = jObject["Data"]?.ToObject<Dictionary<string, string>>();
if (dict != null)
foreach (var (key, value) in dict)
oldPaths.TryAdd(key, value);
jObject["Data"] = JToken.FromObject(oldPaths);
}
else
{
jObject = new JObject
{
["Data"] = JToken.FromObject(oldPaths),
["EmptyFolders"] = JToken.FromObject(Array.Empty<string>()),
};
}
var data = jObject.ToString(Formatting.Indented);
File.WriteAllText(file, data);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}");
}
}
public string ToFilePath(FilenameService fileNames)
=> fileNames.DesignFileSystem;
public void Save(StreamWriter writer)
{
SaveToFile(writer, SaveDesign, true);
} }
} }

View file

@ -0,0 +1,57 @@
using Glamourer.Services;
using Luna;
namespace Glamourer.Designs;
public sealed class DesignFileSystemSaver(Logger log, BaseFileSystem fileSystem, SaveService saveService, DesignStorage designs)
: FileSystemSaver<SaveService, FilenameService>(log, fileSystem, saveService)
{
protected override void SaveDataValue(IFileSystemValue value)
{
if (value is Design design)
SaveService.QueueSave(design);
}
protected override string LockedFile(FilenameService provider)
=> provider.FileSystemLockedNodes;
protected override string ExpandedFile(FilenameService provider)
=> provider.FileSystemExpandedFolders;
protected override string EmptyFoldersFile(FilenameService provider)
=> provider.FileSystemEmptyFolders;
protected override string SelectionFile(FilenameService provider)
=> provider.FileSystemSelectedNodes;
protected override string MigrationFile(FilenameService provider)
=> provider.MigrationDesignFileSystem;
protected override bool GetValueFromIdentifier(ReadOnlySpan<char> identifier, [NotNullWhen(true)] out IFileSystemValue? value)
{
if (!Guid.TryParse(identifier, out var guid))
{
value = null;
return false;
}
value = designs.FirstOrDefault(d => d.Identifier == guid);
return value is not null;
}
protected override void CreateDataNodes()
{
foreach (var design in designs)
{
try
{
var folder = design.Path.Folder.Length is 0 ? FileSystem.Root : FileSystem.FindOrCreateAllFolders(design.Path.Folder);
FileSystem.CreateDuplicateDataNode(folder, design.Path.SortName ?? design.Name, design);
}
catch (Exception ex)
{
Log.Error($"Could not create folder structure for design {design.Name} at path {design.Path.Folder}: {ex}");
}
}
}
}

View file

@ -1,4 +1,5 @@
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Config;
using Glamourer.Designs.History; using Glamourer.Designs.History;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using Glamourer.Events; using Glamourer.Events;
@ -21,7 +22,7 @@ public sealed class DesignManager : DesignEditor
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration.Configuration config) DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config)
: base(saveService, @event, customizations, items, config) : base(saveService, @event, customizations, items, config)
{ {
Designs = storage; Designs = storage;
@ -545,7 +546,6 @@ public sealed class DesignManager : DesignEditor
} }
} }
DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths);
Glamourer.Log.Information( Glamourer.Log.Information(
$"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs."); $"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs.");
} }

View file

@ -1,5 +1,6 @@
using Glamourer.Api.Enums; using Glamourer.Api.Enums;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Services; using Glamourer.Services;
@ -15,7 +16,7 @@ namespace Glamourer.Designs.Links;
public class DesignMerger( public class DesignMerger(
DesignManager designManager, DesignManager designManager,
CustomizeService customizeService, CustomizeService customizeService,
Configuration.Configuration config, Configuration config,
ItemUnlockManager itemUnlocks, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks) : IService CustomizeUnlockManager customizeUnlocks) : IService
{ {

View file

@ -0,0 +1,99 @@
using System.Collections.Frozen;
using Luna;
namespace Glamourer.Designs;
public readonly struct CreationDate : ISortMode
{
public static readonly CreationDate Instance = new();
public ReadOnlySpan<byte> Name
=> "Creation Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8;
public IEnumerable<IFileSystemNode> GetChildren(IFileSystemFolder f)
=> f.GetSubFolders().Cast<IFileSystemNode>().Concat(f.GetLeaves().OfType<IFileSystemData<Design>>().OrderBy(l => l.Value.CreationDate));
}
public readonly struct UpdateDate : ISortMode
{
public static readonly UpdateDate Instance = new();
public ReadOnlySpan<byte> Name
=> "Update Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8;
public IEnumerable<IFileSystemNode> GetChildren(IFileSystemFolder f)
=> f.GetSubFolders().Cast<IFileSystemNode>().Concat(f.GetLeaves().OfType<IFileSystemData<Design>>().OrderBy(l => l.Value.LastEdit));
}
public readonly struct InverseCreationDate : ISortMode
{
public static readonly InverseCreationDate Instance = new();
public ReadOnlySpan<byte> Name
=> "Creation Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8;
public IEnumerable<IFileSystemNode> GetChildren(IFileSystemFolder f)
=> f.GetSubFolders().Cast<IFileSystemNode>()
.Concat(f.GetLeaves().OfType<IFileSystemData<Design>>().OrderByDescending(l => l.Value.CreationDate));
}
public readonly struct InverseUpdateDate : ISortMode
{
public static readonly InverseUpdateDate Instance = new();
public ReadOnlySpan<byte> Name
=> "Update Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8;
public IEnumerable<IFileSystemNode> GetChildren(IFileSystemFolder f)
=> f.GetSubFolders().Cast<IFileSystemNode>()
.Concat(f.GetLeaves().OfType<IFileSystemData<Design>>().OrderByDescending(l => l.Value.LastEdit));
}
public static class SortModeExtensions
{
private static readonly FrozenDictionary<string, ISortMode> ValidSortModes = new Dictionary<string, ISortMode>
{
[nameof(ISortMode.FoldersFirst)] = ISortMode.FoldersFirst,
[nameof(ISortMode.Lexicographical)] = ISortMode.Lexicographical,
[nameof(CreationDate)] = ISortMode.CreationDate,
[nameof(InverseCreationDate)] = ISortMode.InverseCreationDate,
[nameof(UpdateDate)] = ISortMode.UpdateDate,
[nameof(InverseUpdateDate)] = ISortMode.InverseUpdateDate,
[nameof(ISortMode.InverseFoldersFirst)] = ISortMode.InverseFoldersFirst,
[nameof(ISortMode.InverseLexicographical)] = ISortMode.InverseLexicographical,
[nameof(ISortMode.FoldersLast)] = ISortMode.FoldersLast,
[nameof(ISortMode.InverseFoldersLast)] = ISortMode.InverseFoldersLast,
[nameof(ISortMode.InternalOrder)] = ISortMode.InternalOrder,
[nameof(ISortMode.InverseInternalOrder)] = ISortMode.InverseInternalOrder,
}.ToFrozenDictionary();
extension(ISortMode)
{
public static ISortMode CreationDate
=> CreationDate.Instance;
public static ISortMode InverseCreationDate
=> InverseCreationDate.Instance;
public static ISortMode UpdateDate
=> UpdateDate.Instance;
public static ISortMode InverseUpdateDate
=> InverseUpdateDate.Instance;
public static IReadOnlyDictionary<string, ISortMode> Valid
=> ValidSortModes;
}
}

View file

@ -1,9 +1,9 @@
 using Glamourer.Config;
using Luna; using Luna;
namespace Glamourer.Designs.Special; namespace Glamourer.Designs.Special;
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration.Configuration config) : IService public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService
{ {
private readonly Random _rng = new(); private readonly Random _rng = new();
private readonly WeakReference<Design> _lastDesign = new(null!, false); private readonly WeakReference<Design> _lastDesign = new(null!, false);

View file

@ -10,19 +10,19 @@ public interface IDesignPredicate
=> Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath); => Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath);
public IEnumerable<Design> Get(IEnumerable<Design> designs, DesignFileSystem fileSystem) public IEnumerable<Design> Get(IEnumerable<Design> designs, DesignFileSystem fileSystem)
=> designs.Select(d => Transform(d, fileSystem)) => designs.Select(Transform)
.Where(Invoke) .Where(Invoke)
.Select(t => t.Design); .Select(t => t.Design);
public static IEnumerable<Design> Get(IReadOnlyList<IDesignPredicate> predicates, IEnumerable<Design> designs, DesignFileSystem fileSystem) public static IEnumerable<Design> Get(IReadOnlyList<IDesignPredicate> predicates, IEnumerable<Design> designs, DesignFileSystem fileSystem)
=> predicates.Count > 0 => predicates.Count > 0
? designs.Select(d => Transform(d, fileSystem)) ? designs.Select(Transform)
.Where(t => predicates.Any(p => p.Invoke(t))) .Where(t => predicates.Any(p => p.Invoke(t)))
.Select(t => t.Design) .Select(t => t.Design)
: designs; : designs;
private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs) private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d)
=> (d, d.Name.Lower, d.Identifier.ToString(), fs.TryGetValue(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); => (d, d.Name.Lower, d.Identifier.ToString(), d.Path.CurrentPath.ToLowerInvariant());
} }
public static class RandomPredicate public static class RandomPredicate

View file

@ -1,6 +1,7 @@
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.History; using Glamourer.Designs.History;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Gui.Tabs.DesignTab;
using OtterGui.Classes; using OtterGui.Classes;
namespace Glamourer.Events; namespace Glamourer.Events;
@ -135,10 +136,13 @@ public sealed class DesignChanged()
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/> /// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
AutoDesignManager = 1, AutoDesignManager = 1,
/// <seealso cref="DesignFileSystem.OnDesignChange"/> /// <seealso cref="DesignFileSystem.OnDesignChanged"/>
DesignFileSystem = 0, DesignFileSystem = 0,
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/> /// <seealso cref="DesignHeader.OnDesignChanged"/>
DesignHeader = 0,
/// <seealso cref="DesignFileSystemDrawer.OnDesignChanged"/>
DesignFileSystemSelector = -1, DesignFileSystemSelector = -1,
/// <seealso cref="DesignComboBase.OnDesignChanged"/> /// <seealso cref="DesignComboBase.OnDesignChanged"/>

View file

@ -1,5 +1,6 @@
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Gui.Tabs.DesignTab;
using OtterGui.Classes; using OtterGui.Classes;
namespace Glamourer.Events; namespace Glamourer.Events;
@ -16,7 +17,7 @@ public sealed class TabSelected()
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnTabSelected"/> /// <seealso cref="DesignFileSystemDrawer.OnTabSelected"/>
DesignSelector = 0, DesignSelector = 0,
/// <seealso cref="Gui.MainWindow.OnTabSelected"/> /// <seealso cref="Gui.MainWindow.OnTabSelected"/>

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Api; using Glamourer.Api;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Interop; using Glamourer.Interop;
@ -61,7 +62,7 @@ public class Glamourer : IDalamudPlugin
public string GatherSupportInformation() public string GatherSupportInformation()
{ {
var sb = new StringBuilder(10240); var sb = new StringBuilder(10240);
var config = _services.GetService<Configuration.Configuration>(); var config = _services.GetService<Configuration>();
sb.AppendLine("**Settings**"); sb.AppendLine("**Settings**");
sb.Append($"> **`Plugin Version: `** {Version}\n"); sb.Append($"> **`Plugin Version: `** {Version}\n");
sb.Append($"> **`Commit Hash: `** {CommitHash}\n"); sb.Append($"> **`Commit Hash: `** {CommitHash}\n");

View file

@ -1,4 +1,4 @@
<Project Sdk="Dalamud.NET.Sdk/14.0.1"> <Project Sdk="Dalamud.NET.Sdk/14.0.2">
<PropertyGroup> <PropertyGroup>
<RootNamespace>Glamourer</RootNamespace> <RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName> <AssemblyName>Glamourer</AssemblyName>
@ -22,6 +22,11 @@
<EmbeddedResource Include="LegacyTattoo.raw" /> <EmbeddedResource Include="LegacyTattoo.raw" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<Use_DalamudPackager>false</Use_DalamudPackager>
<Use_Dalamud_ImGui>false</Use_Dalamud_ImGui>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" /> <ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
<ProjectReference Include="..\Luna\Luna\Luna.csproj" /> <ProjectReference Include="..\Luna\Luna\Luna.csproj" />
@ -31,7 +36,8 @@
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" /> <ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" /> <PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" />
<ProjectReference Include="..\Luna\Luna.Generators\Luna.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\Luna\Luna.Generators\Luna.Generators.csproj" OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -50,7 +56,8 @@
</ItemGroup> </ItemGroup>
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion"> <Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true"> <Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low"
ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess" /> <Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess" />
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" /> <Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" />
</Exec> </Exec>

View file

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=gui_005Ctabs_005Cdesigntab_005Cselector/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -1,3 +1,4 @@
using Glamourer.Config;
using ImSharp; using ImSharp;
namespace Glamourer.Gui; namespace Glamourer.Gui;
@ -83,6 +84,6 @@ public static class Colors
=> _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor; => _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor;
/// <summary> Set the configurable colors dictionary to a value. </summary> /// <summary> Set the configurable colors dictionary to a value. </summary>
public static void SetColors(Configuration.Configuration config) public static void SetColors(Configuration config)
=> _colors = config.Colors; => _colors = config.Colors;
} }

View file

@ -1,5 +1,5 @@
using System.Text.Unicode; using System.Text.Unicode;
using Glamourer.Configuration; using Glamourer.Config;
using ImSharp; using ImSharp;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;

View file

@ -1,6 +1,7 @@
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
@ -14,7 +15,7 @@ namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer( public partial class CustomizationDrawer(
ITextureProvider textures, ITextureProvider textures,
CustomizeService service, CustomizeService service,
Configuration.Configuration config, Configuration config,
FavoriteManager favorites, FavoriteManager favorites,
HeightService heightService) HeightService heightService)
: IDisposable : IDisposable
@ -81,7 +82,7 @@ public partial class CustomizationDrawer(
private CustomizeValue _currentByte = CustomizeValue.Zero; private CustomizeValue _currentByte = CustomizeValue.Zero;
private bool _currentApply; private bool _currentApply;
private int _currentCount; private int _currentCount;
private StringU8 _currentOption = StringU8.Empty; private StringU8 _currentOption = StringU8.Empty;
// Prepare a new customization option. // Prepare a new customization option.
private Im.IdDisposable SetId(CustomizeIndex index) private Im.IdDisposable SetId(CustomizeIndex index)

View file

@ -1,4 +1,5 @@
using Glamourer.Designs; using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.PalettePlus; using Glamourer.Interop.PalettePlus;
using Glamourer.State; using Glamourer.State;
@ -7,7 +8,7 @@ using Luna;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
public class CustomizeParameterDrawer(Configuration.Configuration config, PaletteImport import) : IService public class CustomizeParameterDrawer(Configuration config, PaletteImport import) : IService
{ {
private readonly Dictionary<Design, CustomizeParameterData> _lastData = []; private readonly Dictionary<Design, CustomizeParameterData> _lastData = [];
private StringU8 _paletteName = StringU8.Empty; private StringU8 _paletteName = StringU8.Empty;
@ -125,7 +126,7 @@ public class CustomizeParameterDrawer(Configuration.Configuration config, Palett
DrawColorFormatOptions(withApply); DrawColorFormatOptions(withApply);
var value = config.ShowColorConfig; var value = config.ShowColorConfig;
Im.Line.Same(); Im.Line.Same();
if (Im.Checkbox("Show Config"u8, ref value)) if (Im.Checkbox("Show Configuration"u8, ref value))
{ {
config.ShowColorConfig = value; config.ShowColorConfig = value;
config.Save(); config.Save();

View file

@ -9,7 +9,7 @@ using Luna;
namespace Glamourer.Gui; namespace Glamourer.Gui;
public abstract class DesignComboBase( public abstract class DesignComboBase(
Configuration.EphemeralConfig config, Config.EphemeralConfig config,
DesignManager designs, DesignManager designs,
DesignChanged designChanged, DesignChanged designChanged,
DesignColors designColors, DesignColors designColors,
@ -17,18 +17,18 @@ public abstract class DesignComboBase(
DesignFileSystem designFileSystem) DesignFileSystem designFileSystem)
: FilterComboBase<DesignComboBase.CacheItem>(new DesignFilter(), ConfigData.Default with { ComputeWidth = true }) : FilterComboBase<DesignComboBase.CacheItem>(new DesignFilter(), ConfigData.Default with { ComputeWidth = true })
{ {
protected readonly Configuration.EphemeralConfig Config = config; protected readonly Config.EphemeralConfig Config = config;
protected readonly DesignChanged DesignChanged = designChanged; protected readonly DesignChanged DesignChanged = designChanged;
protected readonly DesignColors DesignColors = designColors; protected readonly DesignColors DesignColors = designColors;
protected readonly DesignFileSystem DesignFileSystem = designFileSystem; protected readonly DesignFileSystem DesignFileSystem = designFileSystem;
protected readonly TabSelected TabSelected = tabSelected; protected readonly TabSelected TabSelected = tabSelected;
protected readonly DesignManager Designs = designs; protected readonly DesignManager Designs = designs;
protected IDesignStandIn? CurrentDesign; protected IDesignStandIn? CurrentDesign;
protected CacheItem CreateItem(IDesignStandIn design) protected CacheItem CreateItem(IDesignStandIn design)
{ {
var color = design is Design d1 ? DesignColors.GetColor(d1).ToVector() : ColorId.NormalDesign.Value().ToVector(); var color = design is Design d1 ? DesignColors.GetColor(d1).ToVector() : ColorId.NormalDesign.Value().ToVector();
var path = design is Design d2 && DesignFileSystem.TryGetValue(d2, out var leaf) ? leaf.FullName() : string.Empty; var path = design is Design d2 ? d2.Node!.FullPath : string.Empty;
var name = design.ResolveName(false); var name = design.ResolveName(false);
if (path == name) if (path == name)
path = string.Empty; path = string.Empty;
@ -118,7 +118,10 @@ public abstract class DesignComboBase(
protected override void ComputeWidth() protected override void ComputeWidth()
=> ComboWidth = UnfilteredItems.Max(d => ComboWidth = UnfilteredItems.Max(d
=> d.Name.Utf8.CalculateSize(false).X + d.FullPath.Utf8.CalculateSize(false).X + 2 * Im.Style.ItemSpacing.X + Im.Style.ScrollbarSize); => d.Name.Utf8.CalculateSize(false).X
+ d.FullPath.Utf8.CalculateSize(false).X
+ 2 * Im.Style.ItemSpacing.X
+ Im.Style.ScrollbarSize);
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
@ -203,7 +206,7 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService
} }
public QuickDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, public QuickDesignCombo(Config.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected,
DesignFileSystem designFileSystem, DesignManager designs) DesignFileSystem designFileSystem, DesignManager designs)
: base(config, designs, designChanged, designColors, tabSelected, designFileSystem) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem)
{ {
@ -264,7 +267,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable
{ {
public Design? NewSelection { get; private set; } public Design? NewSelection { get; private set; }
public LinkDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, public LinkDesignCombo(Config.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected,
DesignFileSystem designFileSystem, DesignManager designs) DesignFileSystem designFileSystem, DesignManager designs)
: base(config, designs, designChanged, designColors, tabSelected, designFileSystem) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem)
{ {
@ -295,7 +298,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable
} }
public sealed class RandomDesignCombo( public sealed class RandomDesignCombo(
Configuration.EphemeralConfig config, Config.EphemeralConfig config,
DesignManager designs, DesignManager designs,
DesignChanged designChanged, DesignChanged designChanged,
DesignColors designColors, DesignColors designColors,
@ -307,14 +310,10 @@ public sealed class RandomDesignCombo(
{ {
return exact.Which switch return exact.Which switch
{ {
RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value), RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value),
RandomPredicate.Exact.Type.Path => DesignFileSystem.Find(exact.Value.Text, out var c) && c is DesignFileSystem.Leaf l RandomPredicate.Exact.Type.Path => Designs.Designs.FirstOrDefault(d => d.Node!.FullPath == exact.Value.Text),
? l.Value RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) ? g : Guid.Empty),
: null, _ => null,
RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g)
? g
: Guid.Empty),
_ => null,
}; };
} }
@ -346,7 +345,7 @@ public sealed class SpecialDesignCombo : DesignComboBase, IUiService
private readonly CacheItem _revert; private readonly CacheItem _revert;
private readonly CacheItem _quick; private readonly CacheItem _quick;
public SpecialDesignCombo(Configuration.EphemeralConfig config, public SpecialDesignCombo(Config.EphemeralConfig config,
DesignManager designs, DesignManager designs,
DesignChanged designChanged, DesignChanged designChanged,
DesignColors designColors, DesignColors designColors,

View file

@ -2,6 +2,7 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
@ -33,19 +34,19 @@ public sealed class DesignQuickBar : Window, IDisposable
? WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing | WindowFlags.NoMove ? WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing | WindowFlags.NoMove
: WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing; : WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly QuickDesignCombo _designCombo; private readonly QuickDesignCombo _designCombo;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly IKeyState _keyState; private readonly IKeyState _keyState;
private readonly Im.ColorStyleDisposable _style = new(); private readonly Im.ColorStyleDisposable _style = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch; private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons; private int _numButtons;
private readonly StringBuilder _tooltipBuilder = new(512); private readonly StringBuilder _tooltipBuilder = new(512);
public DesignQuickBar(Configuration.Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState,
ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra)
: base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking) : base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking)
{ {

View file

@ -1,4 +1,5 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Gui.Materials; using Glamourer.Gui.Materials;
using Glamourer.Services; using Glamourer.Services;
@ -22,7 +23,7 @@ public class EquipmentDrawer
private readonly BonusItemCombo[] _bonusItemCombo; private readonly BonusItemCombo[] _bonusItemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo; private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly TextureService _textures; private readonly TextureService _textures;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly GPoseService _gPose; private readonly GPoseService _gPose;
private readonly AdvancedDyePopup _advancedDyes; private readonly AdvancedDyePopup _advancedDyes;
private readonly ItemCopyService _itemCopy; private readonly ItemCopyService _itemCopy;
@ -32,7 +33,7 @@ public class EquipmentDrawer
private EquipSlot _dragTarget; private EquipSlot _dragTarget;
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures,
Configuration.Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy)
{ {
_items = items; _items = items;
_textures = textures; _textures = textures;

View file

@ -1,17 +1,18 @@
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Config;
using ImSharp; using ImSharp;
namespace Glamourer.Gui; namespace Glamourer.Gui;
public class GenericPopupWindow : Luna.Window public class GenericPopupWindow : Luna.Window
{ {
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IClientState _state; private readonly IClientState _state;
public bool OpenFestivalPopup { get; internal set; } public bool OpenFestivalPopup { get; internal set; }
public GenericPopupWindow(Configuration.Configuration config, IClientState state, ICondition condition) public GenericPopupWindow(Configuration config, IClientState state, ICondition condition)
: base("Glamourer Popups", : base("Glamourer Popups",
WindowFlags.NoBringToFrontOnFocus WindowFlags.NoBringToFrontOnFocus
| WindowFlags.NoDecoration | WindowFlags.NoDecoration

View file

@ -1,3 +1,4 @@
using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -5,11 +6,11 @@ namespace Glamourer.Gui;
public class GlamourerChangelog public class GlamourerChangelog
{ {
public const int LastChangelogVersion = 0; public const int LastChangelogVersion = 0;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
public readonly Changelog Changelog; public readonly Changelog Changelog;
public GlamourerChangelog(Configuration.Configuration config) public GlamourerChangelog(Configuration config)
{ {
_config = config; _config = config;
Changelog = new Changelog("Glamourer Changelog", ConfigData, Save); Changelog = new Changelog("Glamourer Changelog", ConfigData, Save);

View file

@ -1,5 +1,6 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Glamourer.Config;
using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Gui.Tabs.UnlocksTab;
namespace Glamourer.Gui; namespace Glamourer.Gui;
@ -11,7 +12,7 @@ public class GlamourerWindowSystem : IDisposable
private readonly MainWindow _ui; private readonly MainWindow _ui;
public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups,
Configuration.Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick)
{ {
_uiBuilder = uiBuilder; _uiBuilder = uiBuilder;
_ui = ui; _ui = ui;

View file

@ -14,11 +14,11 @@ namespace Glamourer.Gui;
public sealed class MainTabBar : TabBar<MainTabType> public sealed class MainTabBar : TabBar<MainTabType>
{ {
private readonly Configuration.EphemeralConfig _config; private readonly Config.EphemeralConfig _config;
public readonly TabSelected Event; public readonly TabSelected Event;
public readonly SettingsTab Settings; public readonly SettingsTab Settings;
public MainTabBar(Logger log, Configuration.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, public MainTabBar(Logger log, Config.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs,
AutomationTab automation, UnlocksTab unlocks, NpcTab npcs, MessagesTab messages, DebugTab debug, TabSelected @event) AutomationTab automation, UnlocksTab unlocks, NpcTab npcs, MessagesTab messages, DebugTab debug, TabSelected @event)
: base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug) : base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug)
{ {

View file

@ -1,5 +1,6 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Config;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -8,20 +9,20 @@ namespace Glamourer.Gui;
public sealed class MainWindow : Window, IDisposable public sealed class MainWindow : Window, IDisposable
{ {
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly DesignQuickBar _quickBar; private readonly DesignQuickBar _quickBar;
private readonly MainTabBar _mainTabBar; private readonly MainTabBar _mainTabBar;
private bool _ignorePenumbra; private bool _ignorePenumbra;
public MainWindow(IDalamudPluginInterface pi, Configuration.Configuration config, PenumbraService penumbra, public MainWindow(IDalamudPluginInterface pi, Configuration config, PenumbraService penumbra,
MainTabBar mainTabBar, DesignQuickBar quickBar) MainTabBar mainTabBar, DesignQuickBar quickBar)
: base("GlamourerMainWindow") : base("GlamourerMainWindow")
{ {
pi.UiBuilder.DisableGposeUiHide = true; pi.UiBuilder.DisableGposeUiHide = true;
SizeConstraints = new WindowSizeConstraints SizeConstraints = new WindowSizeConstraints
{ {
MinimumSize = new Vector2(700, 675), MinimumSize = new Vector2(700, 675),
MaximumSize = new Vector2(3840, 2160), MaximumSize = new Vector2(3840, 2160),
}; };
_mainTabBar = mainTabBar; _mainTabBar = mainTabBar;

View file

@ -2,7 +2,7 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.State; using Glamourer.State;
@ -17,7 +17,7 @@ using Notification = Luna.Notification;
namespace Glamourer.Gui.Materials; namespace Glamourer.Gui.Materials;
public sealed unsafe class AdvancedDyePopup( public sealed unsafe class AdvancedDyePopup(
Configuration.Configuration config, Configuration config,
StateManager stateManager, StateManager stateManager,
LiveColorTablePreviewer preview, LiveColorTablePreviewer preview,
DirectXService directX) : IService DirectXService directX) : IService

View file

@ -1,4 +1,5 @@
using Glamourer.Designs; using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -7,7 +8,7 @@ using Penumbra.GameData.Files.MaterialStructs;
namespace Glamourer.Gui.Materials; namespace Glamourer.Gui.Materials;
public class MaterialDrawer(DesignManager designManager, Configuration.Configuration config) : IService public class MaterialDrawer(DesignManager designManager, Configuration config) : IService
{ {
public const float GlossWidth = 100; public const float GlossWidth = 100;
public const float SpecularStrengthWidth = 125; public const float SpecularStrengthWidth = 125;

View file

@ -1,7 +1,7 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.History; using Glamourer.Designs.History;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
@ -25,7 +25,7 @@ public sealed class ActorPanel : IPanel
private readonly CustomizationDrawer _customizationDrawer; private readonly CustomizationDrawer _customizationDrawer;
private readonly EquipmentDrawer _equipmentDrawer; private readonly EquipmentDrawer _equipmentDrawer;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly ImportService _importService; private readonly ImportService _importService;
@ -37,7 +37,7 @@ public sealed class ActorPanel : IPanel
CustomizationDrawer customizationDrawer, CustomizationDrawer customizationDrawer,
EquipmentDrawer equipmentDrawer, EquipmentDrawer equipmentDrawer,
AutoDesignApplier autoDesignApplier, AutoDesignApplier autoDesignApplier,
Configuration.Configuration config, Configuration config,
DesignConverter converter, DesignConverter converter,
ActorObjectManager objects, ActorObjectManager objects,
DesignManager designManager, DesignManager designManager,

View file

@ -14,7 +14,7 @@ public readonly struct ActorCacheItem(ActorIdentifier identifier, ActorData data
public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label)); public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label));
} }
public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Configuration.EphemeralConfig config) : IPanel public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Config.EphemeralConfig config) : IPanel
{ {
public ReadOnlySpan<byte> Id public ReadOnlySpan<byte> Id
=> "ActorSelector"u8; => "ActorSelector"u8;

View file

@ -1,4 +1,4 @@
using Glamourer.Configuration; using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;

View file

@ -6,10 +6,10 @@ namespace Glamourer.Gui.Tabs.ActorTab;
public sealed class ActorsHeader : SplitButtonHeader public sealed class ActorsHeader : SplitButtonHeader
{ {
private readonly ActorSelection _selection; private readonly ActorSelection _selection;
private readonly Configuration.EphemeralConfig _config; private readonly Config.EphemeralConfig _config;
public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save, public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save,
UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Configuration.EphemeralConfig config) UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Config.EphemeralConfig config)
{ {
_selection = selection; _selection = selection;
_config = config; _config = config;

View file

@ -1,4 +1,5 @@
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationButtons : ButtonFooter public sealed class AutomationButtons : ButtonFooter
{ {
public AutomationButtons(Configuration.Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects) public AutomationButtons(Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects)
{ {
Buttons.AddButton(new AddButton(objects, manager), 100); Buttons.AddButton(new AddButton(objects, manager), 100);
Buttons.AddButton(new DuplicateButton(selection, manager), 90); Buttons.AddButton(new DuplicateButton(selection, manager), 90);
@ -134,7 +135,7 @@ public sealed class AutomationButtons : ButtonFooter
} }
} }
private sealed class DeleteButton(AutomationSelection selection, Configuration.Configuration config, AutoDesignManager manager) private sealed class DeleteButton(AutomationSelection selection, Configuration config, AutoDesignManager manager)
: BaseIconButton<AwesomeIcon> : BaseIconButton<AwesomeIcon>
{ {
private bool _enabled; private bool _enabled;

View file

@ -1,13 +1,28 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationHeader(Configuration.Configuration config, AutomationSelection selection) : IHeader public sealed class AutomationHeader : SplitButtonHeader
{ {
public bool Collapsed private readonly Configuration _config;
=> false; private readonly AutomationSelection _selection;
public void Draw(Vector2 size) public AutomationHeader(Configuration config, AutomationSelection selection, IncognitoButton incognito)
=> ImEx.TextFramed(config.Ephemeral.IncognitoMode ? selection.Incognito : selection.Name, size with { Y = Im.Style.FrameHeight }); {
_config = config;
_selection = selection;
RightButtons.AddButton(incognito, 100);
}
public override void Draw(Vector2 size)
{
var color = ColorId.HeaderButtons.Value();
using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color);
base.Draw(size with { Y = Im.Style.FrameHeight });
}
public override ReadOnlySpan<byte> Text
=> _config.Ephemeral.IncognitoMode ? _selection.Incognito : _selection.Name;
} }

View file

@ -1,14 +1,15 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public class AutomationTab : TwoPanelLayout, ITab<MainTabType> public class AutomationTab : TwoPanelLayout, ITab<MainTabType>
{ {
private readonly Configuration.Configuration _config; private readonly Configuration _config;
public AutomationTab(AutomationFilter filter, SetSelector selector, SetPanel panel, AutomationButtons buttons, AutomationHeader header, public AutomationTab(AutomationFilter filter, SetSelector selector, SetPanel panel, AutomationButtons buttons, AutomationHeader header,
Configuration.Configuration config) Configuration config)
{ {
_config = config; _config = config;
LeftHeader = new FilterHeader<AutomationCacheItem>(filter, new StringU8("Filter..."u8)); LeftHeader = new FilterHeader<AutomationCacheItem>(filter, new StringU8("Filter..."u8));

View file

@ -1,5 +1,6 @@
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Special; using Glamourer.Designs.Special;
using Glamourer.Events; using Glamourer.Events;
@ -13,19 +14,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private AutoDesignSet? _set; private AutoDesignSet? _set;
private int _designIndex = -1; private int _designIndex = -1;
private readonly AutomationChanged _automationChanged; private readonly AutomationChanged _automationChanged;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
private readonly RandomDesignCombo _randomDesignCombo; private readonly RandomDesignCombo _randomDesignCombo;
private readonly AutomationSelection _selection; private readonly AutomationSelection _selection;
private readonly DesignStorage _designs; private readonly DesignStorage _designs;
private readonly DesignFileSystem _designFileSystem; private readonly DesignFileSystem _designFileSystem;
private string _newText = string.Empty; private string _newText = string.Empty;
private string? _newDefinition; private string? _newDefinition;
private Design? _newDesign; private Design? _newDesign;
public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration.Configuration config, AutoDesignManager autoDesignManager, public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager,
RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs) RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs)
{ {
_automationChanged = automationChanged; _automationChanged = automationChanged;
@ -268,19 +269,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
LookupTooltip(designs); LookupTooltip(designs);
} }
private void LookupTooltip(IEnumerable<Design> designs) private static void LookupTooltip(IEnumerable<Design> designs)
{ {
using var _ = Im.Tooltip.Begin(); using var _ = Im.Tooltip.Begin();
using var enumerator = designs.GetEnumerator(); using var enumerator = designs.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
Im.Text("Matches the following designs:"u8); Im.Text("Matches the following designs:"u8);
var name = _designFileSystem.TryGetValue(enumerator.Current, out var l) ? l.FullName() : enumerator.Current.Name.Text; var name = enumerator.Current.Path.CurrentPath;
Im.Separator(); Im.Separator();
Im.BulletText(name); Im.BulletText(name);
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text; name = enumerator.Current.Path.CurrentPath;
Im.BulletText(name); Im.BulletText(name);
} }

View file

@ -4,6 +4,7 @@ using Glamourer.Designs.Special;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -19,7 +20,7 @@ public class SetPanel(
CustomizeUnlockManager customizeUnlocks, CustomizeUnlockManager customizeUnlocks,
CustomizeService customizations, CustomizeService customizations,
IdentifierDrawer identifierDrawer, IdentifierDrawer identifierDrawer,
Configuration.Configuration config, Configuration config,
RandomRestrictionDrawer randomDrawer, RandomRestrictionDrawer randomDrawer,
AutomationSelection selection) : IPanel AutomationSelection selection) : IPanel
{ {

View file

@ -1,4 +1,5 @@
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Events; using Glamourer.Events;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -8,7 +9,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class SetSelector( public sealed class SetSelector(
AutomationSelection selection, AutomationSelection selection,
Configuration.Configuration config, Configuration config,
AutoDesignManager manager, AutoDesignManager manager,
AutomationFilter filter, AutomationFilter filter,
ActorObjectManager objects, ActorObjectManager objects,
@ -54,7 +55,8 @@ public sealed class SetSelector(
var identifier = config.Ephemeral.IncognitoMode ? item.IdentifierIncognito : item.IdentifierString; var identifier = config.Ephemeral.IncognitoMode ? item.IdentifierIncognito : item.IdentifierString;
var textSize = identifier.CalculateSize(); var textSize = identifier.CalculateSize();
var textColor = item.Set.Identifiers.Any(objects.ContainsKey) ? cache.AutomationAvailable : cache.AutomationUnavailable; var textColor = item.Set.Identifiers.Any(objects.ContainsKey) ? cache.AutomationAvailable : cache.AutomationUnavailable;
Im.Cursor.Position = new Vector2(Im.ContentRegion.Available.X - textSize.X - Im.Style.FramePadding.X, Im.Cursor.Y - Im.Style.TextHeightWithSpacing); Im.Cursor.Position = new Vector2(Im.ContentRegion.Available.X - textSize.X - Im.Style.FramePadding.X,
Im.Cursor.Y - Im.Style.TextHeightWithSpacing);
Im.Text(identifier, textColor); Im.Text(identifier, textColor);
} }

View file

@ -1,11 +1,12 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public sealed class DebugTab(ServiceManager manager) : ITab<MainTabType> public sealed class DebugTab(ServiceManager manager) : ITab<MainTabType>
{ {
private readonly Configuration.Configuration _config = manager.GetService<Configuration.Configuration>(); private readonly Configuration _config = manager.GetService<Configuration>();
public bool IsVisible public bool IsVisible
=> _config.DebugMode; => _config.DebugMode;

View file

@ -84,7 +84,7 @@ public sealed class DesignConverterPanel(DesignConverter designConverter) : IGam
Im.Text("JSON Parsing Successful!"u8); Im.Text("JSON Parsing Successful!"u8);
if (_tmpDesign is not null) if (_tmpDesign is not null)
DesignManagerPanel.DrawDesign(_tmpDesign, null); DesignManagerPanel.DrawDesign(_tmpDesign);
if (_clipboardProblem.Length > 0) if (_clipboardProblem.Length > 0)
{ {

View file

@ -24,7 +24,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
if (!t) if (!t)
continue; continue;
DrawDesign(design, designFileSystem); DrawDesign(design);
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize,
design.Application.Meta, design.Application.Meta,
design.WriteProtected()); design.WriteProtected());
@ -50,12 +50,12 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
var designs = designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray(); var designs = designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray();
foreach (var design in designs) foreach (var design in designs)
designManager.Delete(design); designManager.Delete(design);
if (designFileSystem.Find("Test Designs", out var path) && path is DesignFileSystem.Folder { TotalChildren: 0 }) if (designFileSystem.Find("Test Designs", out var path) && path is IFileSystemFolder { TotalDescendants: 0 })
designFileSystem.Delete(path); designFileSystem.Delete(path);
} }
} }
public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem) public static void DrawDesign(DesignBase design)
{ {
using var table = Im.Table.Begin("##equip"u8, 8, TableFlags.RowBackground | TableFlags.SizingFixedFit); using var table = Im.Table.Begin("##equip"u8, 8, TableFlags.RowBackground | TableFlags.SizingFixedFit);
if (design is Design d) if (design is Design d)
@ -70,8 +70,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
table.DrawDataPair("Identifier"u8, d.Identifier); table.DrawDataPair("Identifier"u8, d.Identifier);
table.NextRow(); table.NextRow();
table.DrawColumn("Design File System Path"u8); table.DrawColumn("Design File System Path"u8);
if (fileSystem is not null) table.DrawColumn(d.Path.CurrentPath);
table.DrawColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known"u8);
table.NextRow(); table.NextRow();
table.DrawDataPair("Creation"u8, d.CreationDate); table.DrawDataPair("Creation"u8, d.CreationDate);

View file

@ -1,10 +1,11 @@
using Glamourer.State; using Glamourer.Config;
using Glamourer.State;
using ImSharp; using ImSharp;
using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public sealed class FunPanel(FunModule funModule, Configuration.Configuration config) : IGameDataDrawer public sealed class FunPanel(FunModule funModule, Configuration config) : IGameDataDrawer
{ {
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Fun Module"u8; => "Fun Module"u8;

View file

@ -0,0 +1,55 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using Glamourer.State;
using ImSharp;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class ApplyCharacterButton(
DesignFileSystem fileSystem,
DesignManager manager,
ActorObjectManager objects,
StateManager stateManager,
DesignConverter converter) : BaseIconButton<AwesomeIcon>
{
private static readonly AwesomeIcon UserIcon = FontAwesomeIcon.UserEdit;
public override bool IsVisible
=> fileSystem.Selection.Selection is not null && objects.Player.Valid;
public override AwesomeIcon Icon
=> UserIcon;
public override bool Enabled
=> !((Design)fileSystem.Selection.Selection!.Value).WriteProtected();
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Overwrite this design with your character's current state."u8);
public override void OnClick()
{
var selection = (Design)fileSystem.Selection.Selection!.Value;
try
{
var (player, actor) = objects.PlayerData;
if (!player.IsValid || !actor.Valid || !stateManager.GetOrCreate(player, actor.Objects[0], out var state))
throw new Exception("No player state available.");
var design = converter.Convert(state, ApplicationRules.FromModifiers(state))
?? throw new Exception("The clipboard did not contain valid data.");
selection.GetMaterialDataRef().Clear();
manager.ApplyDesign(selection, design);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {selection.Name}.",
$"Could not apply player state to design {selection.Identifier}", NotificationType.Error, false);
}
}
}

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using ImSharp; using ImSharp;
@ -9,21 +9,19 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignDetailTab public class DesignDetailTab
{ {
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly DesignFileSystemSelector _selector; private readonly DesignFileSystem _fileSystem;
private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager;
private readonly DesignManager _manager; private readonly DesignColors _colors;
private readonly DesignColors _colors; private readonly DesignColorCombo _colorCombo;
private readonly DesignColorCombo _colorCombo;
private bool _editDescriptionMode; private bool _editDescriptionMode;
public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, public DesignDetailTab(SaveService saveService, DesignManager manager, DesignFileSystem fileSystem,
DesignColors colors, Configuration.Configuration config) DesignColors colors, Configuration config)
{ {
_saveService = saveService; _saveService = saveService;
_selector = selector;
_manager = manager; _manager = manager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_colors = colors; _colors = colors;
@ -42,6 +40,8 @@ public class DesignDetailTab
Im.Line.New(); Im.Line.New();
} }
private Design Selected
=> (Design) _fileSystem.Selection.Selection!.Value;
private void DrawDesignInfoTable() private void DrawDesignInfoTable()
{ {
@ -57,13 +57,13 @@ public class DesignDetailTab
table.NextColumn(); table.NextColumn();
var width = Im.ContentRegion.Available with { Y = 0 }; var width = Im.ContentRegion.Available with { Y = 0 };
Im.Item.SetNextWidth(width.X); Im.Item.SetNextWidth(width.X);
if (ImEx.InputOnDeactivation.Text("##Name"u8, _selector.Selected!.Name.Text, out string newName)) if (ImEx.InputOnDeactivation.Text("##Name"u8, Selected.Name.Text, out string newName))
_manager.Rename(_selector.Selected!, newName); _manager.Rename(Selected, newName);
var identifier = _selector.Selected!.Identifier.ToString(); var identifier = Selected.Identifier.ToString();
table.DrawFrameColumn("Unique Identifier"u8); table.DrawFrameColumn("Unique Identifier"u8);
table.NextColumn(); table.NextColumn();
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!); var fileName = _saveService.FileNames.DesignFile(Selected);
using (Im.Font.PushMono()) using (Im.Font.PushMono())
{ {
if (Im.Button(identifier, width)) if (Im.Button(identifier, width))
@ -87,10 +87,10 @@ public class DesignDetailTab
table.DrawFrameColumn("Full Selector Path"u8); table.DrawFrameColumn("Full Selector Path"u8);
table.NextColumn(); table.NextColumn();
Im.Item.SetNextWidth(width.X); Im.Item.SetNextWidth(width.X);
if (ImEx.InputOnDeactivation.Text("##Path"u8, _selector.SelectedLeaf!.FullName(), out string newPath)) if (ImEx.InputOnDeactivation.Text("##Path"u8, Selected.Path.CurrentPath, out string newPath))
try try
{ {
_fileSystem.RenameAndMove(_selector.SelectedLeaf, newPath); _fileSystem.RenameAndMove(Selected.Node!, newPath);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -99,56 +99,56 @@ public class DesignDetailTab
table.DrawFrameColumn("Quick Design Bar"u8); table.DrawFrameColumn("Quick Design Bar"u8);
table.NextColumn(); table.NextColumn();
if (Im.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign)) if (Im.RadioButton("Display##qdb"u8, Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, true); _manager.SetQuickDesign(Selected, true);
var hovered = Im.Item.Hovered(); var hovered = Im.Item.Hovered();
Im.Line.SameInner(); Im.Line.SameInner();
if (Im.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign)) if (Im.RadioButton("Hide##qdb"u8, !Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, false); _manager.SetQuickDesign(Selected, false);
if (hovered || Im.Item.Hovered()) if (hovered || Im.Item.Hovered())
Im.Tooltip.Set("Display or hide this design in your quick design bar."u8); Im.Tooltip.Set("Display or hide this design in your quick design bar."u8);
var forceRedraw = _selector.Selected!.ForcedRedraw; var forceRedraw = Selected.ForcedRedraw;
table.DrawFrameColumn("Force Redrawing"u8); table.DrawFrameColumn("Force Redrawing"u8);
table.NextColumn(); table.NextColumn();
if (Im.Checkbox("##ForceRedraw"u8, ref forceRedraw)) if (Im.Checkbox("##ForceRedraw"u8, ref forceRedraw))
_manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); _manager.ChangeForcedRedraw(Selected, forceRedraw);
Im.Tooltip.OnHover("Set this design to always force a redraw when it is applied through any means."u8); Im.Tooltip.OnHover("Set this design to always force a redraw when it is applied through any means."u8);
var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes; var resetAdvancedDyes = Selected.ResetAdvancedDyes;
table.DrawFrameColumn("Reset Advanced Dyes"u8); table.DrawFrameColumn("Reset Advanced Dyes"u8);
table.NextColumn(); table.NextColumn();
if (Im.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes)) if (Im.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes))
_manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); _manager.ChangeResetAdvancedDyes(Selected, resetAdvancedDyes);
Im.Tooltip.OnHover("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8); Im.Tooltip.OnHover("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8);
var resetTemporarySettings = _selector.Selected!.ResetTemporarySettings; var resetTemporarySettings = Selected.ResetTemporarySettings;
table.DrawFrameColumn("Reset Temporary Settings"u8); table.DrawFrameColumn("Reset Temporary Settings"u8);
table.NextColumn(); table.NextColumn();
if (Im.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) if (Im.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings))
_manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); _manager.ChangeResetTemporarySettings(Selected, resetTemporarySettings);
Im.Tooltip.OnHover( Im.Tooltip.OnHover(
"Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); "Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8);
table.DrawFrameColumn("Color"u8); table.DrawFrameColumn("Color"u8);
table.NextColumn(); table.NextColumn();
if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color, if (_colorCombo.Draw("##colorCombo"u8, Selected.Color.Length is 0 ? DesignColors.AutomaticName : Selected.Color,
"Associate a color with this design.\n"u8 "Associate a color with this design.\n"u8
+ "Right-Click to revert to automatic coloring.\n"u8 + "Right-Click to revert to automatic coloring.\n"u8
+ "Hold Control and scroll the mousewheel to scroll."u8, + "Hold Control and scroll the mousewheel to scroll."u8,
width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName)) width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName))
_manager.ChangeColor(_selector.Selected!, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName); _manager.ChangeColor(Selected, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName);
if (Im.Item.RightClicked()) if (Im.Item.RightClicked())
_manager.ChangeColor(_selector.Selected!, string.Empty); _manager.ChangeColor(Selected, string.Empty);
if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor)) if (_colors.TryGetValue(Selected.Color, out var currentColor))
{ {
Im.Line.Same(); Im.Line.Same();
if (DesignColorUi.DrawColorButton($"Color associated with {_selector.Selected!.Color}", currentColor, out var newColor)) if (DesignColorUi.DrawColorButton($"Color associated with {Selected.Color}", currentColor, out var newColor))
_colors.SetColor(_selector.Selected!.Color, newColor); _colors.SetColor(Selected.Color, newColor);
} }
else if (_selector.Selected!.Color.Length != 0) else if (Selected.Color.Length is not 0)
{ {
Im.Line.Same(); Im.Line.Same();
ImEx.Icon.Draw(LunaStyle.WarningIcon, _colors.MissingColor); ImEx.Icon.Draw(LunaStyle.WarningIcon, _colors.MissingColor);
@ -157,11 +157,11 @@ public class DesignDetailTab
table.DrawFrameColumn("Creation Date"u8); table.DrawFrameColumn("Creation Date"u8);
table.NextColumn(); table.NextColumn();
ImEx.TextFramed($"{_selector.Selected!.CreationDate.LocalDateTime:F}", width, 0); ImEx.TextFramed($"{Selected.CreationDate.LocalDateTime:F}", width, 0);
table.DrawFrameColumn("Last Update Date"u8); table.DrawFrameColumn("Last Update Date"u8);
table.NextColumn(); table.NextColumn();
ImEx.TextFramed($"{_selector.Selected!.LastEdit.LocalDateTime:F}", width, 0); ImEx.TextFramed($"{Selected.LastEdit.LocalDateTime:F}", width, 0);
table.DrawFrameColumn("Tags"u8); table.DrawFrameColumn("Tags"u8);
table.NextColumn(); table.NextColumn();
@ -170,26 +170,26 @@ public class DesignDetailTab
private void DrawTags() private void DrawTags()
{ {
var idx = TagButtons.Draw(StringU8.Empty, StringU8.Empty, _selector.Selected!.Tags, out var editedTag); var idx = TagButtons.Draw(StringU8.Empty, StringU8.Empty, Selected.Tags, out var editedTag);
if (idx < 0) if (idx < 0)
return; return;
if (idx < _selector.Selected!.Tags.Length) if (idx < Selected.Tags.Length)
{ {
if (editedTag.Length is 0) if (editedTag.Length is 0)
_manager.RemoveTag(_selector.Selected!, idx); _manager.RemoveTag(Selected, idx);
else else
_manager.RenameTag(_selector.Selected!, idx, editedTag); _manager.RenameTag(Selected, idx, editedTag);
} }
else else
{ {
_manager.AddTag(_selector.Selected!, editedTag); _manager.AddTag(Selected, editedTag);
} }
} }
private void DrawDescription() private void DrawDescription()
{ {
var desc = _selector.Selected!.Description; var desc = Selected.Description;
var size = Im.ContentRegion.Available with { Y = 12 * Im.Style.TextHeightWithSpacing }; var size = Im.ContentRegion.Available with { Y = 12 * Im.Style.TextHeightWithSpacing };
if (!_editDescriptionMode) if (!_editDescriptionMode)
{ {
@ -205,7 +205,7 @@ public class DesignDetailTab
else else
{ {
if (ImEx.InputOnDeactivation.MultiLine("##desc"u8, desc, out string newDescription, size)) if (ImEx.InputOnDeactivation.MultiLine("##desc"u8, desc, out string newDescription, size))
_manager.ChangeDescription(_selector.Selected!, newDescription); _manager.ChangeDescription(Selected, newDescription);
if (Im.Button("Stop Editing"u8)) if (Im.Button("Stop Editing"u8))
_editDescriptionMode = false; _editDescriptionMode = false;

View file

@ -1,405 +0,0 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services;
using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.Events;
using Glamourer.Services;
using Dalamud.Bindings.ImGui;
using ImSharp;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.FileSystem.Selector;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Text;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>, IPanel
{
private readonly DesignManager _designManager;
private readonly DesignChanged _event;
private readonly Configuration.Configuration _config;
private readonly DesignConverter _converter;
private readonly TabSelected _selectionEvent;
private readonly DesignColors _designColors;
private readonly DesignApplier _designApplier;
private string? _clipboardText;
private Design? _cloneDesign;
private string _newName = string.Empty;
public new DesignFileSystem.Leaf? SelectedLeaf
=> base.SelectedLeaf;
public record struct DesignState(Rgba32 Color)
{ }
protected override float CurrentWidth
=> _config.Ephemeral.CurrentDesignSelectorWidth * Im.Style.GlobalScale;
protected override float MinimumAbsoluteRemainder
=> 470 * Im.Style.GlobalScale;
protected override float MinimumScaling
=> _config.Ephemeral.DesignSelectorMinimumScale;
protected override float MaximumScaling
=> _config.Ephemeral.DesignSelectorMaximumScale;
protected override void SetSize(Vector2 size)
{
base.SetSize(size);
var adaptedSize = MathF.Round(size.X / Im.Style.GlobalScale);
if (adaptedSize == _config.Ephemeral.CurrentDesignSelectorWidth)
return;
_config.Ephemeral.CurrentDesignSelectorWidth = adaptedSize;
_config.Ephemeral.Save();
}
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event,
Configuration.Configuration config, DesignConverter converter, TabSelected selectionEvent, OtterGui.Log.Logger log, DesignColors designColors,
DesignApplier designApplier)
: base(fileSystem, keyState, log, allowMultipleSelection: true)
{
_designManager = designManager;
_event = @event;
_config = config;
_converter = converter;
_selectionEvent = selectionEvent;
_designColors = designColors;
_designApplier = designApplier;
_event.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystemSelector);
_selectionEvent.Subscribe(OnTabSelected, TabSelected.Priority.DesignSelector);
_designColors.ColorChanged += SetFilterDirty;
AddButton(NewDesignButton, 0);
AddButton(ImportDesignButton, 10);
AddButton(CloneDesignButton, 20);
AddButton(DeleteButton, 1000);
UnsubscribeRightClickLeaf(RenameLeaf);
SetRenameSearchPath(_config.ShowRename);
SetFilterTooltip();
if (_config.Ephemeral.SelectedDesign == Guid.Empty)
return;
var design = designManager.Designs.ByIdentifier(_config.Ephemeral.SelectedDesign);
if (design != null)
SelectByValue(design);
}
public void SetRenameSearchPath(RenameField value)
{
switch (value)
{
case RenameField.RenameSearchPath:
SubscribeRightClickLeaf(RenameLeafDesign, 1000);
UnsubscribeRightClickLeaf(RenameDesign);
break;
case RenameField.RenameData:
UnsubscribeRightClickLeaf(RenameLeafDesign);
SubscribeRightClickLeaf(RenameDesign, 1000);
break;
case RenameField.BothSearchPathPrio:
UnsubscribeRightClickLeaf(RenameLeafDesign);
UnsubscribeRightClickLeaf(RenameDesign);
SubscribeRightClickLeaf(RenameLeafDesign, 1001);
SubscribeRightClickLeaf(RenameDesign, 1000);
break;
case RenameField.BothDataPrio:
UnsubscribeRightClickLeaf(RenameLeafDesign);
UnsubscribeRightClickLeaf(RenameDesign);
SubscribeRightClickLeaf(RenameLeafDesign, 1000);
SubscribeRightClickLeaf(RenameDesign, 1001);
break;
default:
UnsubscribeRightClickLeaf(RenameLeafDesign);
UnsubscribeRightClickLeaf(RenameDesign);
break;
}
}
private void RenameLeafDesign(DesignFileSystem.Leaf leaf)
{
Im.Separator();
RenameLeaf(leaf);
}
private void RenameDesign(DesignFileSystem.Leaf leaf)
{
Im.Separator();
var currentName = leaf.Value.Name.Text;
if (ImGui.IsWindowAppearing())
ImGui.SetKeyboardFocusHere(0);
ImGui.TextUnformatted("Rename Design:");
if (Im.Input.Text("##RenameDesign"u8, ref currentName, StringU8.Empty, InputTextFlags.EnterReturnsTrue))
{
_designManager.Rename(leaf.Value, currentName);
ImGui.CloseCurrentPopup();
}
ImGuiUtil.HoverTooltip("Enter a new name here to rename the changed design.");
}
protected override void Select(FileSystem<Design>.Leaf? leaf, bool clear, in DesignState storage = default)
{
base.Select(leaf, clear, storage);
var id = SelectedLeaf?.Value.Identifier ?? Guid.Empty;
if (id != _config.Ephemeral.SelectedDesign)
{
_config.Ephemeral.SelectedDesign = id;
_config.Ephemeral.Save();
}
}
protected override void DrawPopups()
{
DrawNewDesignPopup();
}
protected override void DrawLeafName(FileSystem<Design>.Leaf leaf, in DesignState state, bool selected)
{
var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
var name = _config.Ephemeral.IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text;
using var color = ImGuiColor.Text.Push(state.Color);
using var _ = ImUtf8.TreeNode(name, flag);
if (_config.AllowDoubleClickToApply && ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
_designApplier.ApplyToPlayer(leaf.Value);
}
public override void Dispose()
{
base.Dispose();
_event.Unsubscribe(OnDesignChange);
_selectionEvent.Unsubscribe(OnTabSelected);
_designColors.ColorChanged -= SetFilterDirty;
}
public override ISortMode<Design> SortMode
=> _config.SortMode;
protected override uint ExpandedFolderColor
=> ColorId.FolderExpanded.Value().Color;
protected override uint CollapsedFolderColor
=> ColorId.FolderCollapsed.Value().Color;
protected override uint FolderLineColor
=> ColorId.FolderLine.Value().Color;
protected override bool FoldersDefaultOpen
=> _config.OpenFoldersByDefault;
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _)
{
switch (type)
{
case DesignChanged.Type.ReloadedAll:
case DesignChanged.Type.Renamed:
case DesignChanged.Type.AddedTag:
case DesignChanged.Type.ChangedTag:
case DesignChanged.Type.RemovedTag:
case DesignChanged.Type.AddedMod:
case DesignChanged.Type.RemovedMod:
case DesignChanged.Type.Created:
case DesignChanged.Type.Deleted:
case DesignChanged.Type.ApplyCustomize:
case DesignChanged.Type.ApplyEquip:
case DesignChanged.Type.ApplyStain:
case DesignChanged.Type.ApplyCrest:
case DesignChanged.Type.Customize:
case DesignChanged.Type.Equip:
case DesignChanged.Type.ChangedColor:
SetFilterDirty();
break;
}
}
private void NewDesignButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false,
true))
{
_cloneDesign = null;
_clipboardText = null;
ImGui.OpenPopup("##NewDesign");
}
}
private void ImportDesignButton(Vector2 size)
{
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, "Try to import a design from your clipboard.", false,
true))
return;
try
{
_cloneDesign = null;
_clipboardText = ImGui.GetClipboardText();
ImGui.OpenPopup("##NewDesign");
}
catch
{
Glamourer.Messager.NotificationMessage("Could not import data from clipboard.", NotificationType.Error, false);
}
}
private void CloneDesignButton(Vector2 size)
{
var tt = SelectedLeaf == null
? "No design selected."
: "Clone the currently selected design to a duplicate";
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
return;
_clipboardText = null;
_cloneDesign = Selected!;
ImGui.OpenPopup("##NewDesign");
}
private void DeleteButton(Vector2 size)
=> DeleteSelectionButton(size,
new OtterGui.Classes.DoubleModifier(new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier1),
new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier2)), "design", "designs", _designManager.Delete);
private void DrawNewDesignPopup()
{
if (!ImGuiUtil.OpenNameField("##NewDesign", ref _newName))
return;
if (_clipboardText != null)
{
var design = _converter.FromBase64(_clipboardText, true, true, out _);
if (design is Design d)
_designManager.CreateClone(d, _newName, true);
else if (design != null)
_designManager.CreateClone(design, _newName, true);
else
Glamourer.Messager.NotificationMessage("Could not create a design, clipboard did not contain valid design data.",
NotificationType.Error, false);
_clipboardText = null;
}
else if (_cloneDesign != null)
{
_designManager.CreateClone(_cloneDesign, _newName, true);
_cloneDesign = null;
}
else
{
_designManager.CreateEmpty(_newName, true);
}
_newName = string.Empty;
}
private void OnTabSelected(MainTabType type, Design? design)
{
if (type == MainTabType.Designs && design != null)
SelectByValue(design);
}
#region Filters
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _designFilter = LowerString.Empty;
private int _filterType = -1;
private void SetFilterTooltip()
{
FilterTooltip = "Filter designs for those where their full paths or names contain the given substring.\n"
+ "Enter m:[string] to filter for designs with with a mod association containing the string.\n"
+ "Enter t:[string] to filter for designs set to specific tags.\n"
+ "Enter c:[string] to filter for designs set to specific colors.\n"
+ "Enter i:[string] to filter for designs containing specific items.\n"
+ "Enter n:[string] to filter only for design names and no paths.\n\n"
+ "Use None as a placeholder value that only matches empty lists or names.";
}
/// <summary> Appropriately identify and set the string filter and its type. </summary>
protected override bool ChangeFilter(string filterValue)
{
(_designFilter, _filterType) = filterValue.Length switch
{
0 => (LowerString.Empty, -1),
> 1 when filterValue[1] == ':' =>
filterValue[0] switch
{
'n' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
'm' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2),
'M' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2),
't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4),
'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4),
'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5),
'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5),
_ => (new LowerString(filterValue), 0),
},
_ => (new LowerString(filterValue), 0),
};
return true;
}
private const int EmptyOffset = 128;
private static (LowerString, int) ParseFilter(string value, int id)
{
value = value[2..];
var lower = new LowerString(value);
return (lower, lower.Lower is "none" ? id + EmptyOffset : id);
}
/// <summary>
/// The overwritten filter method also computes the state.
/// Folders have default state and are filtered out on the direct string instead of the other options.
/// If any filter is set, they should be hidden by default unless their children are visible,
/// or they contain the path search string.
/// </summary>
protected override bool ApplyFiltersAndState(FileSystem<Design>.IPath path, out DesignState state)
{
if (path is DesignFileSystem.Folder f)
{
state = default;
return FilterValue.Length > 0 && !f.FullName().Contains(FilterValue, IgnoreCase);
}
return ApplyFiltersAndState((DesignFileSystem.Leaf)path, out state);
}
/// <summary> Apply the string filters. </summary>
private bool ApplyStringFilters(DesignFileSystem.Leaf leaf, Design design)
{
return _filterType switch
{
-1 => false,
0 => !(_designFilter.IsContained(leaf.FullName()) || design.Name.Contains(_designFilter)),
1 => !design.Name.Contains(_designFilter),
2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)),
3 => !design.Tags.Any(_designFilter.IsContained),
4 => !design.DesignData.ContainsName(_designFilter),
5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color),
2 + EmptyOffset => design.AssociatedMods.Count > 0,
3 + EmptyOffset => design.Tags.Length > 0,
_ => false, // Should never happen
};
}
/// <summary> Combined wrapper for handling all filters and setting state. </summary>
private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state)
{
state = new DesignState(_designColors.GetColor(leaf.Value));
return ApplyStringFilters(leaf, leaf.Value);
}
#endregion
public ReadOnlySpan<byte> Id
=> "DesignSelector"u8;
}

View file

@ -1,21 +1,107 @@
using Luna; using System.Security.AccessControl;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.Events;
using Glamourer.State;
using ImSharp;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignHeader : SplitButtonHeader public sealed class DesignHeader : SplitButtonHeader, IDisposable
{ {
public DesignHeader(DesignSelection selection, IncognitoButton incognito) private readonly DesignFileSystem _fileSystem;
private readonly DesignChanged _designChanged;
private readonly Configuration _config;
private StringU8 _header = new("No Selection"u8);
private StringU8 _incognito = new("No Selection"u8);
public DesignHeader(DesignFileSystem fileSystem, IncognitoButton incognito, DesignChanged designChanged, Configuration config,
DesignConverter converter, StateManager stateManager, EditorHistory history, DesignManager manager, ActorObjectManager objects)
{ {
RightButtons.AddButton(incognito, 50); _fileSystem = fileSystem;
RightButtons.AddButton(new LockedButton(selection), 100); _designChanged = designChanged;
_config = config;
LeftButtons.AddButton(new SetFromClipboardButton(fileSystem, converter, manager), 100);
LeftButtons.AddButton(new DesignUndoButton(fileSystem, manager), 90);
LeftButtons.AddButton(new ExportToClipboardButton(fileSystem, converter), 80);
LeftButtons.AddButton(new ApplyCharacterButton(fileSystem, manager, objects, stateManager, converter), 70);
LeftButtons.AddButton(new UndoButton(fileSystem, history), 60);
RightButtons.AddButton(incognito, 50);
RightButtons.AddButton(new LockedButton(fileSystem, manager), 100);
_fileSystem.Selection.Changed += OnSelectionChanged;
OnSelectionChanged();
designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignHeader);
} }
private sealed class LockedButton(DesignSelection selection) : BaseIconButton<AwesomeIcon> private void OnDesignChanged(DesignChanged.Type arg1, Design arg2, ITransaction? arg3)
{
if (arg1 is not DesignChanged.Type.Renamed)
return;
if (arg2 != _fileSystem.Selection.Selection?.Value)
return;
_header = new StringU8(arg2.Name.Text);
}
private void OnSelectionChanged()
{
if (_fileSystem.Selection.Selection?.GetValue<Design>() is { } selection)
{
_header = new StringU8(selection.Name.Text);
_incognito = new StringU8(selection.Incognito);
}
else if (_fileSystem.Selection.OrderedNodes.Count > 0)
{
_header = new StringU8($"{_fileSystem.Selection.OrderedNodes.Count} Objects Selected");
_incognito = _header;
}
else
{
_header = new StringU8("No Selection"u8);
_incognito = _header;
}
}
public override void Draw(Vector2 size)
{
var color = ColorId.HeaderButtons.Value();
using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color);
base.Draw(size with { Y = Im.Style.FrameHeight });
}
public override ReadOnlySpan<byte> Text
=> _config.Ephemeral.IncognitoMode ? _incognito : _header;
private sealed class LockedButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton<AwesomeIcon>
{ {
public override bool IsVisible public override bool IsVisible
=> selection.Design is not null; => fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon public override AwesomeIcon Icon
=> selection.Design!.WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon; => ((Design)fileSystem.Selection.Selection!.Value).WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(((Design)fileSystem.Selection.Selection!.Value).WriteProtected()
? "Make this design editable."u8
: "Write-protect this design."u8);
public override void OnClick()
=> manager.SetWriteProtection((Design)fileSystem.Selection.Selection!.Value,
!((Design)fileSystem.Selection.Selection!.Value).WriteProtected());
}
public void Dispose()
{
_fileSystem.Selection.Changed -= OnSelectionChanged;
_designChanged.Unsubscribe(OnDesignChanged);
} }
} }

View file

@ -1,6 +1,6 @@
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using ImSharp; using ImSharp;
@ -10,16 +10,19 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignLinkDrawer( public class DesignLinkDrawer(
DesignLinkManager linkManager, DesignLinkManager linkManager,
DesignFileSystemSelector selector, DesignFileSystem fileSystem,
LinkDesignCombo combo, LinkDesignCombo combo,
DesignColors colorManager, DesignColors colorManager,
Configuration.Configuration config) : IUiService Configuration config) : IUiService
{ {
private int _dragDropIndex = -1; private int _dragDropIndex = -1;
private LinkOrder _dragDropOrder = LinkOrder.None; private LinkOrder _dragDropOrder = LinkOrder.None;
private int _dragDropTargetIndex = -1; private int _dragDropTargetIndex = -1;
private LinkOrder _dragDropTargetOrder = LinkOrder.None; private LinkOrder _dragDropTargetOrder = LinkOrder.None;
private Design Selected
=> (Design)fileSystem.Selection.Selection!.Value;
public void Draw() public void Draw()
{ {
using var h = DesignPanelFlag.DesignLinks.Header(config); using var h = DesignPanelFlag.DesignLinks.Header(config);
@ -45,23 +48,23 @@ public class DesignLinkDrawer(
switch (_dragDropTargetOrder) switch (_dragDropTargetOrder)
{ {
case LinkOrder.Before: case LinkOrder.Before:
for (var i = selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i) for (var i = Selected.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i)
linkManager.MoveDesignLink(selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After); linkManager.MoveDesignLink(Selected, i, LinkOrder.Before, 0, LinkOrder.After);
break; break;
case LinkOrder.After: case LinkOrder.After:
for (var i = 0; i <= _dragDropTargetIndex; ++i) for (var i = 0; i <= _dragDropTargetIndex; ++i)
{ {
linkManager.MoveDesignLink(selector.Selected!, 0, LinkOrder.After, selector.Selected!.Links.Before.Count, linkManager.MoveDesignLink(Selected, 0, LinkOrder.After, Selected.Links.Before.Count,
LinkOrder.Before); LinkOrder.Before);
} }
break; break;
} }
else if (_dragDropTargetOrder is LinkOrder.Self) else if (_dragDropTargetOrder is LinkOrder.Self)
linkManager.MoveDesignLink(selector.Selected!, _dragDropIndex, _dragDropOrder, selector.Selected!.Links.Before.Count, linkManager.MoveDesignLink(Selected, _dragDropIndex, _dragDropOrder, Selected.Links.Before.Count,
LinkOrder.Before); LinkOrder.Before);
else else
linkManager.MoveDesignLink(selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); linkManager.MoveDesignLink(Selected, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
_dragDropIndex = -1; _dragDropIndex = -1;
_dragDropTargetIndex = -1; _dragDropTargetIndex = -1;
@ -81,9 +84,9 @@ public class DesignLinkDrawer(
6 * Im.Style.FrameHeight + 5 * Im.Style.ItemInnerSpacing.X); 6 * Im.Style.FrameHeight + 5 * Im.Style.ItemInnerSpacing.X);
using var style = ImStyleDouble.ItemSpacing.Push(Im.Style.ItemInnerSpacing); using var style = ImStyleDouble.ItemSpacing.Push(Im.Style.ItemInnerSpacing);
DrawSubList(table, selector.Selected!.Links.Before, LinkOrder.Before); DrawSubList(table, Selected.Links.Before, LinkOrder.Before);
DrawSelf(table); DrawSelf(table);
DrawSubList(table, selector.Selected!.Links.After, LinkOrder.After); DrawSubList(table, Selected.Links.After, LinkOrder.After);
DrawNew(table); DrawNew(table);
MoveLink(); MoveLink();
} }
@ -92,7 +95,7 @@ public class DesignLinkDrawer(
{ {
using var id = Im.Id.Push((int)LinkOrder.Self); using var id = Im.Id.Push((int)LinkOrder.Self);
table.NextColumn(); table.NextColumn();
var color = colorManager.GetColor(selector.Selected!); var color = colorManager.GetColor(Selected);
using (AwesomeIcon.Font.Push()) using (AwesomeIcon.Font.Push())
{ {
using var c = ImGuiColor.Text.Push(color); using var c = ImGuiColor.Text.Push(color);
@ -104,11 +107,11 @@ public class DesignLinkDrawer(
using (ImGuiColor.Text.Push(color)) using (ImGuiColor.Text.Push(color))
{ {
Im.Cursor.FrameAlign(); Im.Cursor.FrameAlign();
Im.Selectable(config.Ephemeral.IncognitoMode ? selector.Selected!.Incognito : selector.Selected!.Name.Text); Im.Selectable(config.Ephemeral.IncognitoMode ? Selected.Incognito : Selected.Name.Text);
} }
Im.Tooltip.OnHover("Current Design"u8); Im.Tooltip.OnHover("Current Design"u8);
DrawDragDrop(selector.Selected!, LinkOrder.Self, 0); DrawDragDrop(Selected, LinkOrder.Self, 0);
table.NextColumn(); table.NextColumn();
using (AwesomeIcon.Font.Push()) using (AwesomeIcon.Font.Push())
{ {
@ -144,7 +147,7 @@ public class DesignLinkDrawer(
DrawApplicationBoxes(i, order, flags); DrawApplicationBoxes(i, order, flags);
if (delete) if (delete)
linkManager.RemoveDesignLink(selector.Selected!, i--, order); linkManager.RemoveDesignLink(Selected, i--, order);
} }
} }
@ -164,11 +167,11 @@ public class DesignLinkDrawer(
} }
else else
{ {
canAddBefore = LinkContainer.CanAddLink(selector.Selected!, design, LinkOrder.Before, out var error); canAddBefore = LinkContainer.CanAddLink(Selected, design, LinkOrder.Before, out var error);
ttBefore = canAddBefore ttBefore = canAddBefore
? $"Add a link at the top of the list to {design.Name}." ? $"Add a link at the top of the list to {design.Name}."
: $"Can not add a link to {design.Name}:\n{error}"; : $"Can not add a link to {design.Name}:\n{error}";
canAddAfter = LinkContainer.CanAddLink(selector.Selected!, design, LinkOrder.After, out error); canAddAfter = LinkContainer.CanAddLink(Selected, design, LinkOrder.After, out error);
ttAfter = canAddAfter ttAfter = canAddAfter
? $"Add a link at the bottom of the list to {design.Name}." ? $"Add a link at the bottom of the list to {design.Name}."
: $"Can not add a link to {design.Name}:\n{error}"; : $"Can not add a link to {design.Name}:\n{error}";
@ -176,13 +179,13 @@ public class DesignLinkDrawer(
if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleUp.Icon(), ttBefore, !canAddBefore)) if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleUp.Icon(), ttBefore, !canAddBefore))
{ {
linkManager.AddDesignLink(selector.Selected!, design!, LinkOrder.Before); linkManager.AddDesignLink(Selected, design!, LinkOrder.Before);
linkManager.MoveDesignLink(selector.Selected!, selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before); linkManager.MoveDesignLink(Selected, Selected.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
} }
Im.Line.Same(); Im.Line.Same();
if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleDown.Icon(), ttAfter, !canAddAfter)) if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleDown.Icon(), ttAfter, !canAddAfter))
linkManager.AddDesignLink(selector.Selected!, design!, LinkOrder.After); linkManager.AddDesignLink(Selected, design!, LinkOrder.After);
} }
private void DrawDragDrop(Design design, LinkOrder order, int index) private void DrawDragDrop(Design design, LinkOrder order, int index)
@ -228,7 +231,7 @@ public class DesignLinkDrawer(
Im.Line.Same(); Im.Line.Same();
Box(4); Box(4);
if (newType != current) if (newType != current)
linkManager.ChangeApplicationType(selector.Selected!, idx, order, newType); linkManager.ChangeApplicationType(Selected, idx, order, newType);
return; return;
void Box(int i) void Box(int i)

View file

@ -1,51 +1,40 @@
using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Api.Enums; using Glamourer.Api.Enums;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
using Glamourer.Gui.Materials; using Glamourer.Gui.Materials;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.State; using Glamourer.State;
using Dalamud.Bindings.ImGui;
using Glamourer.Configuration;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using static Glamourer.Gui.Tabs.HeaderDrawer;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignPanel : IPanel public class DesignPanel : IPanel
{ {
private readonly FileDialogManager _fileDialog = new(); private readonly FileDialogManager _fileDialog = new();
private readonly DesignSelection _selection; private readonly CustomizationDrawer _customizationDrawer;
private readonly CustomizationDrawer _customizationDrawer; private readonly DesignFileSystem _fileSystem;
private readonly DesignManager _manager; private readonly DesignManager _manager;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly StateManager _state; private readonly StateManager _state;
private readonly EquipmentDrawer _equipmentDrawer; private readonly EquipmentDrawer _equipmentDrawer;
private readonly ModAssociationsTab _modAssociations; private readonly ModAssociationsTab _modAssociations;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly DesignDetailTab _designDetails; private readonly DesignDetailTab _designDetails;
private readonly ImportService _importService; private readonly ImportService _importService;
private readonly DesignConverter _converter; private readonly MultiDesignPanel _multiDesignPanel;
private readonly MultiDesignPanel _multiDesignPanel; private readonly CustomizeParameterDrawer _parameterDrawer;
private readonly CustomizeParameterDrawer _parameterDrawer; private readonly DesignLinkDrawer _designLinkDrawer;
private readonly DesignLinkDrawer _designLinkDrawer; private readonly MaterialDrawer _materials;
private readonly MaterialDrawer _materials;
private readonly EditorHistory _history;
private readonly Button[] _leftButtons;
private readonly Button[] _rightButtons;
public DesignPanel(CustomizationDrawer customizationDrawer, public DesignPanel(CustomizationDrawer customizationDrawer,
@ -54,7 +43,7 @@ public class DesignPanel : IPanel
StateManager state, StateManager state,
EquipmentDrawer equipmentDrawer, EquipmentDrawer equipmentDrawer,
ModAssociationsTab modAssociations, ModAssociationsTab modAssociations,
Configuration.Configuration config, Configuration config,
DesignDetailTab designDetails, DesignDetailTab designDetails,
DesignConverter converter, DesignConverter converter,
ImportService importService, ImportService importService,
@ -62,7 +51,7 @@ public class DesignPanel : IPanel
CustomizeParameterDrawer parameterDrawer, CustomizeParameterDrawer parameterDrawer,
DesignLinkDrawer designLinkDrawer, DesignLinkDrawer designLinkDrawer,
MaterialDrawer materials, MaterialDrawer materials,
EditorHistory history, DesignSelection selection) DesignFileSystem fileSystem)
{ {
_customizationDrawer = customizationDrawer; _customizationDrawer = customizationDrawer;
_manager = manager; _manager = manager;
@ -73,33 +62,16 @@ public class DesignPanel : IPanel
_config = config; _config = config;
_designDetails = designDetails; _designDetails = designDetails;
_importService = importService; _importService = importService;
_converter = converter;
_multiDesignPanel = multiDesignPanel; _multiDesignPanel = multiDesignPanel;
_parameterDrawer = parameterDrawer; _parameterDrawer = parameterDrawer;
_designLinkDrawer = designLinkDrawer; _designLinkDrawer = designLinkDrawer;
_materials = materials; _materials = materials;
_history = history; _fileSystem = fileSystem;
_selection = selection;
_leftButtons =
[
new SetFromClipboardButton(this),
new DesignUndoButton(this),
new ExportToClipboardButton(this),
new ApplyCharacterButton(this),
new UndoButton(this),
];
_rightButtons =
[
new LockButton(this),
//new IncognitoButton(_config),
];
} }
private void DrawHeader()
=> HeaderDrawer.Draw(SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons);
private string SelectionName private Design Selection
=> _selection.Design == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selection.Design.Incognito : _selection.Design.Name.Text; => (Design)_fileSystem.Selection.Selection!.Value;
private void DrawEquipment() private void DrawEquipment()
{ {
@ -109,22 +81,22 @@ public class DesignPanel : IPanel
_equipmentDrawer.Prepare(); _equipmentDrawer.Prepare();
var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selection.Design!.WriteProtected()); var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, Selection.WriteProtected());
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var data = EquipDrawData.FromDesign(_manager, _selection.Design!, slot); var data = EquipDrawData.FromDesign(_manager, Selection, slot);
_equipmentDrawer.DrawEquip(data); _equipmentDrawer.DrawEquip(data);
if (usedAllStain) if (usedAllStain)
_manager.ChangeStains(_selection.Design, slot, newAllStain); _manager.ChangeStains(Selection, slot, newAllStain);
} }
var mainhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.MainHand); var mainhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.MainHand);
var offhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.OffHand); var offhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.OffHand);
_equipmentDrawer.DrawWeapons(mainhand, offhand, true); _equipmentDrawer.DrawWeapons(mainhand, offhand, true);
foreach (var slot in BonusExtensions.AllFlags) foreach (var slot in BonusExtensions.AllFlags)
{ {
var data = BonusDrawData.FromDesign(_manager, _selection.Design!, slot); var data = BonusDrawData.FromDesign(_manager, Selection, slot);
_equipmentDrawer.DrawBonusItem(data); _equipmentDrawer.DrawBonusItem(data);
} }
@ -136,30 +108,30 @@ public class DesignPanel : IPanel
private void DrawEquipmentMetaToggles() private void DrawEquipmentMetaToggles()
{ {
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, Selection));
} }
Im.Line.Same(); Im.Line.Same();
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, Selection));
} }
Im.Line.Same(); Im.Line.Same();
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, Selection));
} }
Im.Line.Same(); Im.Line.Same();
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, Selection));
} }
} }
@ -169,25 +141,25 @@ public class DesignPanel : IPanel
return; return;
var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
using var h = Im.Tree.HeaderId(_selection.Design!.DesignData.ModelId is 0 using var h = Im.Tree.HeaderId(Selection.DesignData.ModelId is 0
? "Customization" ? "Customization"u8
: $"Customization (Model Id #{_selection.Design!.DesignData.ModelId})###Customization", : $"Customization (Model Id #{Selection.DesignData.ModelId})###Customization",
expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None);
if (!h) if (!h)
return; return;
if (_customizationDrawer.Draw(_selection.Design!.DesignData.Customize, _selection.Design.Application.Customize, if (_customizationDrawer.Draw(Selection.DesignData.Customize, Selection.Application.Customize,
_selection.Design!.WriteProtected(), false)) Selection.WriteProtected(), false))
foreach (var idx in CustomizeIndex.Values) foreach (var idx in CustomizeIndex.Values)
{ {
var flag = idx.ToFlag(); var flag = idx.ToFlag();
var newValue = _customizationDrawer.ChangeApply.HasFlag(flag); var newValue = _customizationDrawer.ChangeApply.HasFlag(flag);
_manager.ChangeApplyCustomize(_selection.Design, idx, newValue); _manager.ChangeApplyCustomize(Selection, idx, newValue);
if (_customizationDrawer.Changed.HasFlag(flag)) if (_customizationDrawer.Changed.HasFlag(flag))
_manager.ChangeCustomize(_selection.Design, idx, _customizationDrawer.Customize[idx]); _manager.ChangeCustomize(Selection, idx, _customizationDrawer.Customize[idx]);
} }
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selection.Design!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, Selection));
Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
} }
@ -197,7 +169,7 @@ public class DesignPanel : IPanel
if (!h) if (!h)
return; return;
_parameterDrawer.Draw(_manager, _selection.Design!); _parameterDrawer.Draw(_manager, Selection);
} }
private void DrawMaterialValues() private void DrawMaterialValues()
@ -206,52 +178,52 @@ public class DesignPanel : IPanel
if (!h) if (!h)
return; return;
_materials.Draw(_selection.Design!); _materials.Draw(Selection);
} }
private void DrawCustomizeApplication() private void DrawCustomizeApplication()
{ {
using var id = ImUtf8.PushId("Customizations"u8); using var id = Im.Id.Push("Customizations"u8);
var set = _selection.Design!.CustomizeSet; var set = Selection.CustomizeSet;
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType;
var flags = _selection.Design!.ApplyCustomizeExcludingBodyType == 0 ? 0 : var flags = Selection.ApplyCustomizeExcludingBodyType is 0 ? 0ul :
(_selection.Design!.ApplyCustomize & available) == available ? 3 : 1; (Selection.ApplyCustomize & available) == available ? 3ul : 1ul;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) if (Im.Checkbox("Apply All Customizations"u8, ref flags, 3ul))
{ {
var newFlags = flags == 3; var newFlags = flags is 3;
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, newFlags); _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, newFlags);
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, newFlags); _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, newFlags);
foreach (var index in CustomizationExtensions.AllBasic) foreach (var index in CustomizationExtensions.AllBasic)
_manager.ChangeApplyCustomize(_selection.Design!, index, newFlags); _manager.ChangeApplyCustomize(Selection, index, newFlags);
} }
var applyClan = _selection.Design!.DoApplyCustomize(CustomizeIndex.Clan); var applyClan = Selection.DoApplyCustomize(CustomizeIndex.Clan);
if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan)) if (Im.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan))
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, applyClan); _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, applyClan);
var applyGender = _selection.Design!.DoApplyCustomize(CustomizeIndex.Gender); var applyGender = Selection.DoApplyCustomize(CustomizeIndex.Gender);
if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender)) if (Im.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender))
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, applyGender); _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, applyGender);
foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable))
{ {
var apply = _selection.Design!.DoApplyCustomize(index); var apply = Selection.DoApplyCustomize(index);
if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) if (Im.Checkbox($"Apply {set.Option(index)}", ref apply))
_manager.ChangeApplyCustomize(_selection.Design!, index, apply); _manager.ChangeApplyCustomize(Selection, index, apply);
} }
} }
private void DrawCrestApplication() private void DrawCrestApplication()
{ {
using var id = ImUtf8.PushId("Crests"u8); using var id = Im.Id.Push("Crests"u8);
var flags = (uint)_selection.Design!.Application.Crest; var flags = (ulong)Selection.Application.Crest;
var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); var bigChange = Im.Checkbox("Apply All Crests"u8, ref flags, (ulong)CrestExtensions.AllRelevant);
foreach (var flag in CrestExtensions.AllRelevantSet) foreach (var flag in CrestExtensions.AllRelevantSet)
{ {
var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selection.Design!.DoApplyCrest(flag); var apply = bigChange ? ((CrestFlag)flags & flag) == flag : Selection.DoApplyCrest(flag);
if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) if (Im.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange)
_manager.ChangeApplyCrest(_selection.Design!, flag, apply); _manager.ChangeApplyCrest(Selection, flag, apply);
} }
} }
@ -261,63 +233,59 @@ public class DesignPanel : IPanel
if (!h) if (!h)
return; return;
using var disabled = Im.Disabled(_selection.Design!.WriteProtected()); using var disabled = Im.Disabled(Selection.WriteProtected());
DrawAllButtons(); DrawAllButtons();
using (var _ = ImUtf8.Group()) using (Im.Group())
{ {
DrawCustomizeApplication(); DrawCustomizeApplication();
ImUtf8.IconDummy(); Im.FrameDummy();
DrawCrestApplication(); DrawCrestApplication();
ImUtf8.IconDummy(); Im.FrameDummy();
DrawMetaApplication(); DrawMetaApplication();
} }
ImGui.SameLine(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X); Im.Line.Same(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X);
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots) void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots)
{ {
var flags = (uint)(allFlags & _selection.Design!.Application.Equip); var flags = (ulong)(allFlags & Selection.Application.Equip);
using var id = ImUtf8.PushId(label); using var id = Im.Id.Push(label);
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); var bigChange = Im.Checkbox($"Apply All {label}", ref flags, (ulong)allFlags);
if (stain) if (stain)
foreach (var slot in slots) foreach (var slot in slots)
{ {
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selection.Design!.DoApplyStain(slot); var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : Selection.DoApplyStain(slot);
if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) if (Im.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange)
_manager.ChangeApplyStains(_selection.Design!, slot, apply); _manager.ChangeApplyStains(Selection, slot, apply);
} }
else else
foreach (var slot in slots) foreach (var slot in slots)
{ {
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selection.Design!.DoApplyEquip(slot); var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : Selection.DoApplyEquip(slot);
if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) if (Im.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
_manager.ChangeApplyItem(_selection.Design!, slot, apply); _manager.ChangeApplyItem(Selection, slot, apply);
} }
} }
ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[] ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, [EquipSlot.MainHand, EquipSlot.OffHand]);
{
EquipSlot.MainHand,
EquipSlot.OffHand,
});
ImUtf8.IconDummy(); Im.FrameDummy();
ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
ImUtf8.IconDummy(); Im.FrameDummy();
ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
ImUtf8.IconDummy(); Im.FrameDummy();
ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true, ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true,
EquipSlotExtensions.FullSlots); EquipSlotExtensions.FullSlots);
ImUtf8.IconDummy(); Im.FrameDummy();
DrawParameterApplication(); DrawParameterApplication();
ImUtf8.IconDummy(); Im.FrameDummy();
DrawBonusSlotApplication(); DrawBonusSlotApplication();
} }
} }
@ -327,9 +295,9 @@ public class DesignPanel : IPanel
var enabled = _config.DeleteDesignModifier.IsActive(); var enabled = _config.DeleteDesignModifier.IsActive();
bool? equip = null; bool? equip = null;
bool? customize = null; bool? customize = null;
var size = new Vector2(210 * Im.Style.GlobalScale, 0); var size = ImEx.ScaledVectorX(210);
if (ImUtf8.ButtonEx("Disable Everything"u8, if (ImEx.Button("Disable Everything"u8, size,
"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8,
!enabled)) !enabled))
{ {
equip = false; equip = false;
@ -337,11 +305,11 @@ public class DesignPanel : IPanel
} }
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.ButtonEx("Enable Everything"u8, if (ImEx.Button("Enable Everything"u8, size,
"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, "Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8,
!enabled)) !enabled))
{ {
equip = true; equip = true;
@ -349,10 +317,10 @@ public class DesignPanel : IPanel
} }
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
if (ImUtf8.ButtonEx("Equipment Only"u8, if (ImEx.Button("Equipment Only"u8, size,
"Enable application of anything related to gear, disable anything that is not related to gear."u8, size, "Enable application of anything related to gear, disable anything that is not related to gear."u8,
!enabled)) !enabled))
{ {
equip = true; equip = true;
@ -360,11 +328,11 @@ public class DesignPanel : IPanel
} }
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.ButtonEx("Customization Only"u8, if (ImEx.Button("Customization Only"u8, size,
"Enable application of anything related to customization, disable anything that is not related to customization."u8, size, "Enable application of anything related to customization, disable anything that is not related to customization."u8,
!enabled)) !enabled))
{ {
equip = false; equip = false;
@ -372,85 +340,82 @@ public class DesignPanel : IPanel
} }
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
if (ImUtf8.ButtonEx("Default Application"u8, if (ImEx.Button("Default Application"u8, size,
"Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8,
size,
!enabled)) !enabled))
{ {
_manager.ChangeApplyMulti(_selection.Design!, true, true, true, false, true, true, false, true); _manager.ChangeApplyMulti(Selection, true, true, true, false, true, true, false, true);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, false); _manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, false);
} }
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, if (ImEx.Button("Disable Advanced"u8, size, "Disable all advanced dyes and customizations but keep everything else as is."u8, !enabled))
size, _manager.ChangeApplyMulti(Selection, null, null, null, false, null, null, false, null);
!enabled))
_manager.ChangeApplyMulti(_selection.Design!, null, null, null, false, null, null, false, null);
if (!enabled) if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
if (equip is null && customize is null) if (equip is null && customize is null)
return; return;
_manager.ChangeApplyMulti(_selection.Design!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, _manager.ChangeApplyMulti(Selection, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null,
equip, equip, equip); equip, equip, equip);
if (equip.HasValue) if (equip.HasValue)
{ {
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.HatState, equip.Value); _manager.ChangeApplyMeta(Selection, MetaIndex.HatState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.VisorState, equip.Value); _manager.ChangeApplyMeta(Selection, MetaIndex.VisorState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.WeaponState, equip.Value); _manager.ChangeApplyMeta(Selection, MetaIndex.WeaponState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.EarState, equip.Value); _manager.ChangeApplyMeta(Selection, MetaIndex.EarState, equip.Value);
} }
if (customize.HasValue) if (customize.HasValue)
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, customize.Value); _manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, customize.Value);
} }
private static readonly IReadOnlyList<string> MetaLabels = private static readonly IReadOnlyList<StringU8> MetaLabels =
[ [
"Apply Wetness", new("Apply Wetness"u8),
"Apply Hat Visibility", new("Apply Hat Visibility"u8),
"Apply Visor State", new("Apply Visor State"u8),
"Apply Weapon Visibility", new("Apply Weapon Visibility"u8),
"Apply Viera Ear Visibility", new("Apply Viera Ear Visibility"u8),
]; ];
private void DrawMetaApplication() private void DrawMetaApplication()
{ {
using var id = ImUtf8.PushId("Meta"); using var id = Im.Id.Push("Meta"u8);
const uint all = (uint)MetaExtensions.All; const ulong all = (ulong)MetaExtensions.All;
var flags = (uint)_selection.Design!.Application.Meta; var flags = (ulong)Selection.Application.Meta;
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var bigChange = Im.Checkbox("Apply All Meta Changes"u8, ref flags, all);
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels))
{ {
var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selection.Design!.DoApplyMeta(index); var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : Selection.DoApplyMeta(index);
if (ImUtf8.Checkbox(label, ref apply) || bigChange) if (Im.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyMeta(_selection.Design!, index, apply); _manager.ChangeApplyMeta(Selection, index, apply);
} }
} }
private static readonly IReadOnlyList<string> BonusSlotLabels = private static readonly IReadOnlyList<StringU8> BonusSlotLabels =
[ [
"Apply Facewear", new("Apply Facewear"u8),
]; ];
private void DrawBonusSlotApplication() private void DrawBonusSlotApplication()
{ {
using var id = ImUtf8.PushId("Bonus"u8); using var id = Im.Id.Push("Bonus"u8);
var flags = _selection.Design!.Application.BonusItem; var flags = Selection.Application.BonusItem;
var bigChange = BonusExtensions.AllFlags.Count > 1 && ImUtf8.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All); var bigChange = BonusExtensions.AllFlags.Count > 1 && Im.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All);
foreach (var (index, label) in BonusExtensions.AllFlags.Zip(BonusSlotLabels)) foreach (var (index, label) in BonusExtensions.AllFlags.Zip(BonusSlotLabels))
{ {
var apply = bigChange ? flags.HasFlag(index) : _selection.Design!.DoApplyBonusItem(index); var apply = bigChange ? flags.HasFlag(index) : Selection.DoApplyBonusItem(index);
if (ImUtf8.Checkbox(label, ref apply) || bigChange) if (Im.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyBonusItem(_selection.Design!, index, apply); _manager.ChangeApplyBonusItem(Selection, index, apply);
} }
} }
@ -458,69 +423,62 @@ public class DesignPanel : IPanel
private void DrawParameterApplication() private void DrawParameterApplication()
{ {
using var id = Im.Id.Push("Parameter"u8); using var id = Im.Id.Push("Parameter"u8);
var flags = (ulong)_selection.Design!.Application.Parameters; var flags = (ulong)Selection.Application.Parameters;
var bigChange = Im.Checkbox("Apply All Customize Parameters"u8, ref flags, (ulong)CustomizeParameterExtensions.All); var bigChange = Im.Checkbox("Apply All Customize Parameters"u8, ref flags, (ulong)CustomizeParameterExtensions.All);
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
{ {
var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selection.Design!.DoApplyParameter(flag); var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : Selection.DoApplyParameter(flag);
if (Im.Checkbox($"Apply {flag.ToNameU8()}", ref apply) || bigChange) if (Im.Checkbox($"Apply {flag.ToNameU8()}", ref apply) || bigChange)
_manager.ChangeApplyParameter(_selection.Design!, flag, apply); _manager.ChangeApplyParameter(Selection, flag, apply);
} }
} }
public ReadOnlySpan<byte> Id public ReadOnlySpan<byte> Id
=> "Designs"u8; => "DesignPanel"u8;
public void Draw() public void Draw()
{ {
using var group = ImUtf8.Group(); _importService.CreateDatSource();
//if (_selection.DesignPaths.Count > 1) if (_fileSystem.Selection.OrderedNodes.Count > 1)
if (false)
{ {
_multiDesignPanel.Draw(); _multiDesignPanel.Draw();
return;
} }
else
DrawPanel();
if (_fileSystem.Selection.Selection is null || Selection.WriteProtected())
return;
if (_importService.CreateDatTarget(out var dat))
{ {
DrawHeader(); _manager.ChangeCustomize(Selection, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]);
DrawPanel(); _manager.ChangeCustomize(Selection, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]);
foreach (var idx in CustomizationExtensions.AllBasic)
if (_selection.Design == null || _selection.Design.WriteProtected()) _manager.ChangeCustomize(Selection, idx, dat.Customize[idx]);
return; Glamourer.Messager.NotificationMessage(
$"Applied games .dat file {dat.Description} customizations to {Selection.Name}.", NotificationType.Success, false);
if (_importService.CreateDatTarget(out var dat)) }
{ else if (_importService.CreateCharaTarget(out var designBase, out var name))
_manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); {
_manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); _manager.ApplyDesign(Selection, designBase);
foreach (var idx in CustomizationExtensions.AllBasic) Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {Selection.Name}.",
_manager.ChangeCustomize(_selection.Design!, idx, dat.Customize[idx]); NotificationType.Success, false);
Glamourer.Messager.NotificationMessage(
$"Applied games .dat file {dat.Description} customizations to {_selection.Design.Name}.", NotificationType.Success, false);
}
else if (_importService.CreateCharaTarget(out var designBase, out var name))
{
_manager.ApplyDesign(_selection.Design!, designBase);
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selection.Design.Name}.",
NotificationType.Success, false);
}
} }
_importService.CreateDatSource();
} }
private void DrawPanel() private void DrawPanel()
{ {
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available); using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.ScrollY, Im.ContentRegion.Available);
if (!table || _selection.Design is null) if (!table || _fileSystem.Selection.Selection is null)
return; return;
ImGui.TableSetupScrollFreeze(0, 1); table.SetupScrollFreeze(0, 1);
ImGui.TableNextColumn(); table.NextColumn();
if (_selection.Design is null)
return;
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
DrawButtonRow(); DrawButtonRow();
ImGui.TableNextColumn(); table.NextColumn();
DrawCustomize(); DrawCustomize();
DrawEquipment(); DrawEquipment();
@ -547,15 +505,15 @@ public class DesignPanel : IPanel
private void DrawApplyToSelf() private void DrawApplyToSelf()
{ {
var (id, data) = _objects.PlayerData; var (id, data) = _objects.PlayerData;
if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, if (!ImEx.Button("Apply to Yourself"u8, Vector2.Zero,
"Apply the current design with its settings to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", "Apply the current design with its settings to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8,
!data.Valid)) !data.Valid))
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); _state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true });
} }
} }
@ -564,33 +522,33 @@ public class DesignPanel : IPanel
var (id, data) = _objects.TargetData; var (id, data) = _objects.TargetData;
var tt = id.IsValid var tt = id.IsValid
? data.Valid ? data.Valid
? "Apply the current design with its settings to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." ? "Apply the current design with its settings to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8
: "The current target can not be manipulated." : "The current target can not be manipulated."u8
: "No valid target selected."; : "No valid target selected."u8;
if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, !data.Valid))
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); _state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true });
} }
} }
private void DrawSaveToDat() private void DrawSaveToDat()
{ {
var verified = _importService.Verify(_selection.Design!.DesignData.Customize, out _); var verified = _importService.Verify(Selection.DesignData.Customize, out _);
var tt = verified var tt = verified
? "Export the currently configured customizations of this design to a character creation data file." ? "Export the currently configured customizations of this design to a character creation data file."u8
: "The current design contains customizations that can not be applied during character creation."; : "The current design contains customizations that can not be applied during character creation."u8;
var startPath = GetUserPath(); var startPath = GetUserPath();
if (startPath.Length == 0) if (startPath.Length is 0)
startPath = null; startPath = null;
if (ImGuiUtil.DrawDisabledButton("Export to Dat", Vector2.Zero, tt, !verified)) if (ImEx.Button("Export to Dat"u8, Vector2.Zero, tt, !verified))
_fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) => _fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) =>
{ {
if (v && _selection.Design != null) if (v && _fileSystem.Selection.Selection?.GetValue<Design>() is not null)
_importService.SaveDesignAsDat(path, _selection.Design!.DesignData.Customize, _selection.Design!.Name); _importService.SaveDesignAsDat(path, Selection.DesignData.Customize, Selection.Name);
}, startPath); }, startPath);
_fileDialog.Draw(); _fileDialog.Draw();
@ -598,165 +556,4 @@ public class DesignPanel : IPanel
private static unsafe string GetUserPath() private static unsafe string GetUserPath()
=> Framework.Instance()->UserPathString; => Framework.Instance()->UserPathString;
private sealed class LockButton(DesignPanel panel) : Button
{
public override bool Visible
=> panel._selection.Design != null;
protected override string Description
=> panel._selection.Design!.WriteProtected()
? "Make this design editable."
: "Write-protect this design.";
protected override FontAwesomeIcon Icon
=> panel._selection.Design!.WriteProtected()
? FontAwesomeIcon.Lock
: FontAwesomeIcon.LockOpen;
protected override void OnClick()
=> panel._manager.SetWriteProtection(panel._selection.Design!, !panel._selection.Design!.WriteProtected());
}
private sealed class SetFromClipboardButton(DesignPanel panel) : Button
{
public override bool Visible
=> panel._selection.Design != null;
protected override bool Disabled
=> panel._selection.Design?.WriteProtected() ?? true;
protected override string Description
=> "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Clipboard;
protected override void OnClick()
{
try
{
var text = ImGui.GetClipboardText();
var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool();
var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _)
?? throw new Exception("The clipboard did not contain valid data.");
panel._manager.ApplyDesign(panel._selection.Design!, design);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selection.Design!.Name}.",
$"Could not apply clipboard to design {panel._selection.Design!.Identifier}", NotificationType.Error, false);
}
}
}
private sealed class DesignUndoButton(DesignPanel panel) : Button
{
public override bool Visible
=> panel._selection.Design != null;
protected override bool Disabled
=> !panel._manager.CanUndo(panel._selection.Design) || (panel._selection.Design?.WriteProtected() ?? true);
protected override string Description
=> "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.SyncAlt;
protected override void OnClick()
{
try
{
panel._manager.UndoDesignChange(panel._selection.Design!);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selection.Design!.Name}.",
NotificationType.Error,
false);
}
}
}
private sealed class ExportToClipboardButton(DesignPanel panel) : Button
{
public override bool Visible
=> panel._selection.Design != null;
protected override string Description
=> "Copy the current design to your clipboard.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Copy;
protected override void OnClick()
{
try
{
var text = panel._converter.ShareBase64(panel._selection.Design!);
ImGui.SetClipboardText(text);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selection.Design!.Name} data to clipboard.",
$"Could not copy data from design {panel._selection.Design!.Identifier} to clipboard", NotificationType.Error, false);
}
}
}
private sealed class ApplyCharacterButton(DesignPanel panel) : Button
{
public override bool Visible
=> panel._selection.Design != null && panel._objects.Player.Valid;
protected override string Description
=> "Overwrite this design with your character's current state.";
protected override bool Disabled
=> panel._selection.Design?.WriteProtected() ?? true;
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.UserEdit;
protected override void OnClick()
{
try
{
var (player, actor) = panel._objects.PlayerData;
if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state))
throw new Exception("No player state available.");
var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state))
?? throw new Exception("The clipboard did not contain valid data.");
panel._selection.Design!.GetMaterialDataRef().Clear();
panel._manager.ApplyDesign(panel._selection.Design!, design);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selection.Design!.Name}.",
$"Could not apply player state to design {panel._selection.Design!.Identifier}", NotificationType.Error, false);
}
}
}
private sealed class UndoButton(DesignPanel panel) : Button
{
protected override string Description
=> "Undo the last change.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Undo;
public override bool Visible
=> panel._selection.Design != null;
protected override bool Disabled
=> (panel._selection.Design?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selection.Design);
protected override void OnClick()
=> panel._history.Undo(panel._selection.Design!);
}
} }

View file

@ -1,12 +0,0 @@
using Glamourer.Designs;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignSelection : IUiService, IDisposable
{
public Design? Design { get; private set; }
public void Dispose()
{ }
}

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop; using Glamourer.Interop;
using ImSharp; using ImSharp;
@ -7,36 +7,55 @@ using Luna;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignTab(DesignFileSystemSelector selector, DesignPanel panel, ImportService importService, DesignManager manager) public sealed class DesignTab : TwoPanelLayout, ITab<MainTabType>
: ITab<MainTabType>
{ {
public ReadOnlySpan<byte> Label private readonly ImportService _importService;
private readonly DesignManager _manager;
private readonly UiConfig _uiConfig;
public DesignTab(DesignFileSystemDrawer drawer, DesignPanel panel, ImportService importService, DesignManager manager, DesignFilter filter,
DesignHeader header, UiConfig uiConfig)
{
LeftHeader = drawer.Header;
LeftPanel = drawer;
LeftFooter = drawer.Footer;
RightHeader = header;
RightPanel = panel;
RightFooter = NopHeaderFooter.Instance;
_importService = importService;
_manager = manager;
_uiConfig = uiConfig;
}
public override ReadOnlySpan<byte> Label
=> "Designs"u8; => "Designs"u8;
public MainTabType Identifier public MainTabType Identifier
=> MainTabType.Designs; => MainTabType.Designs;
public void DrawContent() protected override void DrawLeftGroup(in TwoPanelWidth width)
{ {
selector.Draw(); base.DrawLeftGroup(in width);
if (importService.CreateCharaTarget(out var designBase, out var name)) if (_importService.CreateCharaTarget(out var designBase, out var name))
{ {
var newDesign = manager.CreateClone(designBase, name, true); var newDesign = _manager.CreateClone(designBase, name, true);
Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}", Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}",
NotificationType.Success, false); NotificationType.Success, false);
} }
_importService.CreateCharaSource();
Im.Line.Same();
panel.Draw();
importService.CreateCharaSource();
} }
//protected override void SetWidth(float width, ScalingMode mode) protected override float MinimumWidth
// => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode); => LeftFooter.MinimumWidth;
//
//protected override float MinimumWidth protected override float MaximumWidth
// => LeftFooter.MinimumWidth; => Im.Window.Width - 500 * Im.Style.GlobalScale;
//
//protected override float MaximumWidth protected override void SetWidth(float width, ScalingMode mode)
// => Im.Window.Width - 500 * Im.Style.GlobalScale; => _uiConfig.DesignsTabScale = new TwoPanelWidth(width, mode);
public void DrawContent()
=> Draw(_uiConfig.DesignsTabScale);
} }

View file

@ -0,0 +1,39 @@
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignUndoButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> LunaStyle.ResetIcon;
public override bool Enabled
=> !((Design)fileSystem.Selection.Selection!.Value).WriteProtected() && manager.CanUndo((Design)fileSystem.Selection.Selection!.Value);
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
"Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."u8);
public override void OnClick()
{
try
{
manager.UndoDesignChange((Design)fileSystem.Selection.Selection!.Value);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex,
$"Could not undo last changes to {((Design)fileSystem.Selection.Selection!.Value).Name}.",
NotificationType.Error, false);
}
}
}

View file

@ -0,0 +1,36 @@
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class ExportToClipboardButton(DesignFileSystem fileSystem, DesignConverter converter) : BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> LunaStyle.ToClipboardIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Copy the current design to your clipboard."u8);
public override void OnClick()
{
var design = (Design)fileSystem.Selection.Selection!.Value;
try
{
var text = converter.ShareBase64(design);
Im.Clipboard.Set(text);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {design.Name} data to clipboard.",
$"Could not copy data from design {design.Identifier} to clipboard", NotificationType.Error, false);
}
}
}

View file

@ -0,0 +1,28 @@
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class LockButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> ((Design)fileSystem.Selection.Selection!.Value).WriteProtected()
? LunaStyle.LockedIcon
: LunaStyle.UnlockedIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(((Design)fileSystem.Selection.Selection!.Value).WriteProtected()
? "Make this design editable."u8
: "Write-protect this design."u8);
public override void OnClick()
=> manager.SetWriteProtection((Design)fileSystem.Selection.Selection!.Value,
!((Design)fileSystem.Selection.Selection!.Value).WriteProtected());
}

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
@ -8,11 +8,14 @@ using Luna;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public class ModAssociationsTab(PenumbraService penumbra, DesignSelection selection, DesignManager manager, Configuration.Configuration config) public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystem fileSystem, DesignManager manager, Configuration config)
{ {
private readonly ModCombo _modCombo = new(penumbra, selection); private readonly ModCombo _modCombo = new(penumbra, fileSystem);
private (Mod, ModSettings)[]? _copy; private (Mod, ModSettings)[]? _copy;
private Design Selection
=> (Design)fileSystem.Selection.Selection!.Value;
public void Draw() public void Draw()
{ {
using var h = DesignPanelFlag.ModAssociations.Header(config); using var h = DesignPanelFlag.ModAssociations.Header(config);
@ -36,7 +39,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
{ {
var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0); var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0);
if (Im.Button("Copy All to Clipboard"u8, size)) if (Im.Button("Copy All to Clipboard"u8, size))
_copy = selection.Design!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); _copy = Selection.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray();
Im.Line.Same(); Im.Line.Same();
@ -45,7 +48,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
? $"Add {_copy.Length} mod association(s) from clipboard." ? $"Add {_copy.Length} mod association(s) from clipboard."
: "Copy some mod associations to the clipboard, first."u8, _copy is null)) : "Copy some mod associations to the clipboard, first."u8, _copy is null))
foreach (var (mod, setting) in _copy!) foreach (var (mod, setting) in _copy!)
manager.UpdateMod(selection.Design!, mod, setting); manager.UpdateMod(Selection, mod, setting);
Im.Line.Same(); Im.Line.Same();
@ -54,10 +57,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing."
: "Copy some mod associations to the clipboard, first."u8, _copy is null)) : "Copy some mod associations to the clipboard, first."u8, _copy is null))
{ {
while (selection.Design!.AssociatedMods.Count > 0) while (Selection.AssociatedMods.Count > 0)
manager.RemoveMod(selection.Design!, selection.Design!.AssociatedMods.Keys[0]); manager.RemoveMod(Selection, Selection.AssociatedMods.Keys[0]);
foreach (var (mod, setting) in _copy!) foreach (var (mod, setting) in _copy!)
manager.AddMod(selection.Design!, mod, setting); manager.AddMod(Selection, mod, setting);
} }
} }
@ -76,13 +79,13 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
var (id, name) = penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero, if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero,
$"Try to apply all associated mod settings to Penumbras current collection {name}", $"Try to apply all associated mod settings to Penumbras current collection {name}",
selection.Design!.AssociatedMods.Count is 0 || id == Guid.Empty)) Selection.AssociatedMods.Count is 0 || id == Guid.Empty))
ApplyAll(); ApplyAll();
} }
public void ApplyAll() public void ApplyAll()
{ {
foreach (var (mod, settings) in selection.Design!.AssociatedMods) foreach (var (mod, settings) in Selection.AssociatedMods)
penumbra.SetMod(mod, settings, StateSource.Manual, false); penumbra.SetMod(mod, settings, StateSource.Manual, false);
} }
@ -104,7 +107,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
Mod? removedMod = null; Mod? removedMod = null;
(Mod mod, ModSettings settings)? updatedMod = null; (Mod mod, ModSettings settings)? updatedMod = null;
foreach (var (idx, (mod, settings)) in selection.Design!.AssociatedMods.Index()) foreach (var (idx, (mod, settings)) in Selection.AssociatedMods.Index())
{ {
using var id = Im.Id.Push(idx); using var id = Im.Id.Push(idx);
DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp); DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp);
@ -117,10 +120,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
DrawNewModRow(table); DrawNewModRow(table);
if (removedMod.HasValue) if (removedMod.HasValue)
manager.RemoveMod(selection.Design!, removedMod.Value); manager.RemoveMod(Selection, removedMod.Value);
if (updatedMod.HasValue) if (updatedMod.HasValue)
manager.UpdateMod(selection.Design!, updatedMod.Value.mod, updatedMod.Value.settings); manager.UpdateMod(Selection, updatedMod.Value.mod, updatedMod.Value.settings);
} }
private void DrawAssociatedModRow(in Im.TableDisposable table, Mod mod, ModSettings settings, out Mod? removedMod, private void DrawAssociatedModRow(in Im.TableDisposable table, Mod mod, ModSettings settings, out Mod? removedMod,
@ -245,12 +248,12 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
table.NextColumn(); table.NextColumn();
var tt = currentDir.Length is 0 var tt = currentDir.Length is 0
? "Please select a mod first."u8 ? "Please select a mod first."u8
: selection.Design!.AssociatedMods.ContainsKey(new Mod(_modCombo.SelectionName, currentDir)) : Selection.AssociatedMods.ContainsKey(new Mod(_modCombo.SelectionName, currentDir))
? "The design already contains an association with the selected mod."u8 ? "The design already contains an association with the selected mod."u8
: StringU8.Empty; : StringU8.Empty;
if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0)) if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0))
manager.AddMod(selection.Design!, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings); manager.AddMod(Selection, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings);
table.NextColumn(); table.NextColumn();
_modCombo.Draw("##new"u8, Im.ContentRegion.Available.X); _modCombo.Draw("##new"u8, Im.ContentRegion.Available.X);
} }

View file

@ -1,9 +1,10 @@
using Glamourer.Interop.Penumbra; using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
using ImSharp; using ImSharp;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection) : FilterComboBase<ModCombo.CacheItem>(new ModFilter()) public sealed class ModCombo(PenumbraService penumbra, DesignFileSystem fileSystem) : FilterComboBase<ModCombo.CacheItem>(new ModFilter())
{ {
public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count) public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count)
{ {
@ -42,7 +43,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection
=> Im.Style.TextHeightWithSpacing; => Im.Style.TextHeightWithSpacing;
protected override IEnumerable<CacheItem> GetItems() protected override IEnumerable<CacheItem> GetItems()
=> penumbra.GetMods(selection.Design?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count)); => penumbra.GetMods(fileSystem.Selection.Selection?.GetValue<Design>()?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count));
protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected)
{ {

View file

@ -1,4 +1,5 @@
using Glamourer.Designs; using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -6,21 +7,21 @@ using Luna;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public class MultiDesignPanel( public class MultiDesignPanel(
DesignFileSystemSelector selector, DesignFileSystem fileSystem,
DesignManager editor, DesignManager editor,
DesignColors colors, DesignColors colors,
Configuration.Configuration config) Configuration config)
{ {
private readonly DesignColorCombo _colorCombo = new(colors, true); private readonly DesignColorCombo _colorCombo = new(colors, true);
public void Draw() public void Draw()
{ {
if (selector.SelectedPaths.Count == 0) if (fileSystem.Selection.OrderedNodes.Count is 0)
return; return;
var width = ImEx.ScaledVectorX(145); var width = ImEx.ScaledVectorX(145);
var treeNodePos = Im.Cursor.Position; var treeNodePos = Im.Cursor.Position;
_numDesigns = DrawDesignList(); DrawDesignList();
DrawCounts(treeNodePos); DrawCounts(treeNodePos);
var offset = DrawMultiTagger(width); var offset = DrawMultiTagger(width);
DrawMultiColor(width, offset); DrawMultiColor(width, offset);
@ -36,14 +37,15 @@ public class MultiDesignPanel(
private void DrawCounts(Vector2 treeNodePos) private void DrawCounts(Vector2 treeNodePos)
{ {
var startPos = Im.Cursor.Position; var startPos = Im.Cursor.Position;
var numFolders = selector.SelectedPaths.Count - _numDesigns; var numDesigns = fileSystem.Selection.DataNodes.Count;
var numFolders = fileSystem.Selection.Folders.Count;
Im.Cursor.Position = treeNodePos; Im.Cursor.Position = treeNodePos;
ImEx.TextRightAligned((_numDesigns, numFolders) switch ImEx.TextRightAligned((numDesigns, numFolders) switch
{ {
(0, 0) => StringU8.Empty, // should not happen (0, 0) => StringU8.Empty, // should not happen
( > 0, 0) => $"{_numDesigns} Designs", (> 0, 0) => $"{numDesigns} Designs",
(0, > 0) => $"{numFolders} Folders", (0, > 0) => $"{numFolders} Folders",
_ => $"{_numDesigns} Designs, {numFolders} Folders", _ => $"{numDesigns} Designs, {numFolders} Folders",
}); });
Im.Cursor.Position = startPos; Im.Cursor.Position = startPos;
} }
@ -59,10 +61,10 @@ public class MultiDesignPanel(
_numAdvancedDyes = 0; _numAdvancedDyes = 0;
} }
private bool CountLeaves(DesignFileSystem.IPath path) private void CountLeaves(IFileSystemNode path)
{ {
if (path is not DesignFileSystem.Leaf l) if (path is not IFileSystemData<Design> l)
return false; return;
if (l.Value.QuickDesign) if (l.Value.QuickDesign)
++_numQuickDesignEnabled; ++_numQuickDesignEnabled;
@ -79,55 +81,48 @@ public class MultiDesignPanel(
++_numDesignsWithAdvancedDyes; ++_numDesignsWithAdvancedDyes;
_numAdvancedDyes += l.Value.Materials.Count; _numAdvancedDyes += l.Value.Materials.Count;
} }
return true;
} }
private int DrawDesignList() private void DrawDesignList()
{ {
ResetCounts(); ResetCounts();
using var tree = Im.Tree.Node("Currently Selected Objects"u8, TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen); using var tree = Im.Tree.Node("Currently Selected Objects"u8, TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen);
Im.Separator(); Im.Separator();
if (!tree) if (!tree)
return selector.SelectedPaths.Count(CountLeaves); return;
var sizeType = new Vector2(Im.Style.FrameHeight); var sizeType = new Vector2(Im.Style.FrameHeight);
var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100; var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100;
var sizeMods = availableSizePercent * 35; var sizeMods = availableSizePercent * 35;
var sizeFolders = availableSizePercent * 65; var sizeFolders = availableSizePercent * 65;
var numDesigns = 0;
using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground)) using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground))
{ {
if (!table) if (!table)
return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); return;
table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X); table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X);
table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods); table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods);
table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders); table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders);
var i = 0; foreach (var (index, node) in fileSystem.Selection.OrderedNodes.Index())
foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p))
.OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase))
{ {
using var id = Im.Id.Push(i++); using var id = Im.Id.Push(index);
var (icon, text) = path is DesignFileSystem.Leaf l var (icon, text) = node is IFileSystemData<Design> l
? (LunaStyle.RemoveFileIcon, l.Value.Name.Text) ? (LunaStyle.RemoveFileIcon, l.Value.Name.Text)
: (LunaStyle.RemoveFolderIcon, string.Empty); : (LunaStyle.RemoveFolderIcon, string.Empty);
table.NextColumn(); table.NextColumn();
if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType)) if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType))
selector.RemovePathFromMultiSelection(path); fileSystem.Selection.RemoveFromSelection(node);
table.DrawFrameColumn(text); table.DrawFrameColumn(text);
table.DrawFrameColumn(fullName); table.DrawFrameColumn(node.FullPath);
if (CountLeaves(path)) CountLeaves(node);
++numDesigns;
} }
} }
Im.Separator(); Im.Separator();
return numDesigns;
} }
private string _tag = string.Empty; private string _tag = string.Empty;
@ -138,7 +133,6 @@ public class MultiDesignPanel(
private int _numDesignsResetDyes; private int _numDesignsResetDyes;
private int _numAdvancedDyes; private int _numAdvancedDyes;
private int _numDesignsWithAdvancedDyes; private int _numDesignsWithAdvancedDyes;
private int _numDesigns;
private readonly List<Design> _addDesigns = []; private readonly List<Design> _addDesigns = [];
private readonly List<(Design, int)> _removeDesigns = []; private readonly List<(Design, int)> _removeDesigns = [];
@ -153,23 +147,25 @@ public class MultiDesignPanel(
UpdateTagCache(); UpdateTagCache();
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button(_addDesigns.Count > 0 if (ImEx.Button(_addDesigns.Count > 0
? $"Add to {_addDesigns.Count} Designs" ? $"Add to {_addDesigns.Count} Designs"
: "Add"u8, width, _addDesigns.Count is 0 : "Add"u8, width, _addDesigns.Count is 0
? _tag.Length is 0 ? _tag.Length is 0
? "No tag specified."u8 ? "No tag specified."u8
: $"All designs selected already contain the tag \"{_tag}\"." : $"All designs selected already contain the tag \"{_tag}\"."
: $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}",
_addDesigns.Count is 0))
foreach (var design in _addDesigns) foreach (var design in _addDesigns)
editor.AddTag(design, _tag); editor.AddTag(design, _tag);
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button(_removeDesigns.Count > 0 if (ImEx.Button(_removeDesigns.Count > 0
? $"Remove from {_removeDesigns.Count} Designs" ? $"Remove from {_removeDesigns.Count} Designs"
: "Remove", width, _removeDesigns.Count is 0 : "Remove", width, _removeDesigns.Count is 0
? _tag.Length is 0 ? _tag.Length is 0
? "No tag specified."u8 ? "No tag specified."u8
: $"No selected design contains the tag \"{_tag}\" locally." : $"No selected design contains the tag \"{_tag}\" locally."
: $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}",
_removeDesigns.Count is 0))
foreach (var (design, index) in _removeDesigns) foreach (var (design, index) in _removeDesigns)
editor.RemoveTag(design, index); editor.RemoveTag(design, index);
Im.Separator(); Im.Separator();
@ -181,23 +177,21 @@ public class MultiDesignPanel(
ImEx.TextFrameAligned("Multi QDB:"u8); ImEx.TextFrameAligned("Multi QDB:"u8);
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
var diff = _numDesigns - _numQuickDesignEnabled; var diff = fileSystem.Selection.DataNodes.Count - _numQuickDesignEnabled;
if (ImEx.Button("Display Selected Designs in QDB"u8, buttonWidth, diff is 0 if (ImEx.Button("Display Selected Designs in QDB"u8, buttonWidth, diff is 0
? $"All {_numDesigns} selected designs are already displayed in the quick design bar." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already displayed in the quick design bar."
: $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs.", diff is 0)) : $"Display all {fileSystem.Selection.DataNodes.Count} selected designs in the quick design bar. Changes {diff} designs.",
{ diff is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
editor.SetQuickDesign(design.Value, true); editor.SetQuickDesign(design.GetValue<Design>()!, true);
}
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Hide Selected Designs in QDB"u8, buttonWidth, _numQuickDesignEnabled is 0 if (ImEx.Button("Hide Selected Designs in QDB"u8, buttonWidth, _numQuickDesignEnabled is 0
? $"All {_numDesigns} selected designs are already hidden in the quick design bar." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already hidden in the quick design bar."
: $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.", _numQuickDesignEnabled is 0)) : $"Hide all {fileSystem.Selection.DataNodes.Count} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.",
{ _numQuickDesignEnabled is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
editor.SetQuickDesign(design.Value, false); editor.SetQuickDesign(design.GetValue<Design>()!, false);
}
Im.Separator(); Im.Separator();
} }
@ -207,19 +201,20 @@ public class MultiDesignPanel(
ImEx.TextFrameAligned("Multi Lock:"u8); ImEx.TextFrameAligned("Multi Lock:"u8);
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
var diff = _numDesigns - _numDesignsLocked; var diff = fileSystem.Selection.DataNodes.Count - _numDesignsLocked;
if (ImEx.Button("Turn Write-Protected"u8, buttonWidth, diff is 0 if (ImEx.Button("Turn Write-Protected"u8, buttonWidth, diff is 0
? $"All {_numDesigns} selected designs are already write protected." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already write protected."
: $"Write-protect all {_numDesigns} designs. Changes {diff} designs.", diff is 0)) : $"Write-protect all {fileSystem.Selection.DataNodes.Count} designs. Changes {diff} designs.", diff is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
editor.SetWriteProtection(design.Value, true); editor.SetWriteProtection(design.GetValue<Design>()!, true);
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Remove Write-Protection"u8, buttonWidth, _numDesignsLocked is 0 if (ImEx.Button("Remove Write-Protection"u8, buttonWidth, _numDesignsLocked is 0
? $"None of the {_numDesigns} selected designs are write-protected." ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs are write-protected."
: $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs.", _numDesignsLocked is 0)) : $"Remove the write protection of the {fileSystem.Selection.DataNodes.Count} selected designs. Changes {_numDesignsLocked} designs.",
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) _numDesignsLocked is 0))
editor.SetWriteProtection(design.Value, false); foreach (var design in fileSystem.Selection.DataNodes)
editor.SetWriteProtection(design.GetValue<Design>()!, false);
Im.Separator(); Im.Separator();
} }
@ -228,19 +223,21 @@ public class MultiDesignPanel(
ImEx.TextFrameAligned("Settings:"u8); ImEx.TextFrameAligned("Settings:"u8);
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
var diff = _numDesigns - _numDesignsResetSettings; var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetSettings;
if (ImEx.Button("Set Reset Temp. Settings"u8, buttonWidth, diff is 0 if (ImEx.Button("Set Reset Temp. Settings"u8, buttonWidth, diff is 0
? $"All {_numDesigns} selected designs already reset temporary settings." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already reset temporary settings."
: $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs.", diff is 0)) : $"Make all {fileSystem.Selection.DataNodes.Count} selected designs reset temporary settings. Changes {diff} designs.",
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) diff is 0))
editor.ChangeResetTemporarySettings(design.Value, true); foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeResetTemporarySettings(design.GetValue<Design>()!, true);
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Remove Reset Temp. Settings"u8, buttonWidth, _numDesignsResetSettings is 0 if (ImEx.Button("Remove Reset Temp. Settings"u8, buttonWidth, _numDesignsResetSettings is 0
? $"None of the {_numDesigns} selected designs reset temporary settings." ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs reset temporary settings."
: $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs.", _numDesignsResetSettings is 0)) : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs.",
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) _numDesignsResetSettings is 0))
editor.ChangeResetTemporarySettings(design.Value, false); foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeResetTemporarySettings(design.GetValue<Design>()!, false);
Im.Separator(); Im.Separator();
} }
@ -249,19 +246,20 @@ public class MultiDesignPanel(
ImEx.TextFrameAligned("Adv. Dyes:"u8); ImEx.TextFrameAligned("Adv. Dyes:"u8);
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
var diff = _numDesigns - _numDesignsResetDyes; var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetDyes;
if (ImEx.Button("Set Reset Dyes"u8, buttonWidth, diff is 0 if (ImEx.Button("Set Reset Dyes"u8, buttonWidth, diff is 0
? $"All {_numDesigns} selected designs already reset advanced dyes." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already reset advanced dyes."
: $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs.", diff is 0)) : $"Make all {fileSystem.Selection.DataNodes.Count} selected designs reset advanced dyes. Changes {diff} designs.", diff is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeResetAdvancedDyes(design.Value, true); editor.ChangeResetAdvancedDyes(design.GetValue<Design>()!, true);
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Remove Reset Dyes"u8, buttonWidth, _numDesignsLocked is 0 if (ImEx.Button("Remove Reset Dyes"u8, buttonWidth, _numDesignsLocked is 0
? $"None of the {_numDesigns} selected designs reset advanced dyes." ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs reset advanced dyes."
: $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs.", _numDesignsResetDyes is 0)) : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs.",
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) _numDesignsResetDyes is 0))
editor.ChangeResetAdvancedDyes(design.Value, false); foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeResetAdvancedDyes(design.GetValue<Design>()!, false);
Im.Separator(); Im.Separator();
} }
@ -270,19 +268,20 @@ public class MultiDesignPanel(
ImEx.TextFrameAligned("Redrawing:"u8); ImEx.TextFrameAligned("Redrawing:"u8);
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
var diff = _numDesigns - _numDesignsForcedRedraw; var diff = fileSystem.Selection.DataNodes.Count - _numDesignsForcedRedraw;
if (ImEx.Button("Force Redraws"u8, buttonWidth, diff is 0 if (ImEx.Button("Force Redraws"u8, buttonWidth, diff is 0
? $"All {_numDesigns} selected designs already force redraws." ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already force redraws."
: $"Make all {_numDesigns} designs force redraws. Changes {diff} designs.", diff is 0)) : $"Make all {fileSystem.Selection.DataNodes.Count} designs force redraws. Changes {diff} designs.", diff is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeForcedRedraw(design.Value, true); editor.ChangeForcedRedraw(design.GetValue<Design>()!, true);
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Remove Forced Redraws"u8, buttonWidth, _numDesignsLocked is 0 if (ImEx.Button("Remove Forced Redraws"u8, buttonWidth, _numDesignsLocked is 0
? $"None of the {_numDesigns} selected designs force redraws." ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs force redraws."
: $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs.", _numDesignsForcedRedraw is 0)) : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs.",
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) _numDesignsForcedRedraw is 0))
editor.ChangeForcedRedraw(design.Value, false); foreach (var design in fileSystem.Selection.DataNodes)
editor.ChangeForcedRedraw(design.GetValue<Design>()!, false);
Im.Separator(); Im.Separator();
} }
@ -299,30 +298,28 @@ public class MultiDesignPanel(
UpdateColorCache(); UpdateColorCache();
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button(_addDesigns.Count > 0 if (ImEx.Button(_addDesigns.Count > 0
? $"Set for {_addDesigns.Count} Designs" ? $"Set for {_addDesigns.Count} Designs"
: "Set"u8, width, _addDesigns.Count is 0 : "Set"u8, width, _addDesigns.Count is 0
? _colorComboSelection switch ? _colorComboSelection switch
{ {
null => "No color specified."u8, null => "No color specified."u8,
DesignColors.AutomaticName => "Use the other button to set to automatic."u8, DesignColors.AutomaticName => "Use the other button to set to automatic."u8,
_ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".",
} }
: $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}",
{ _addDesigns.Count is 0))
foreach (var design in _addDesigns) foreach (var design in _addDesigns)
editor.ChangeColor(design, _colorComboSelection!); editor.ChangeColor(design, _colorComboSelection!);
}
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button(_removeDesigns.Count > 0 if (ImEx.Button(_removeDesigns.Count > 0
? $"Unset {_removeDesigns.Count} Designs" ? $"Unset {_removeDesigns.Count} Designs"
: "Unset"u8, width, _removeDesigns.Count is 0 : "Unset"u8, width, _removeDesigns.Count is 0
? "No selected design is set to a non-automatic color."u8 ? "No selected design is set to a non-automatic color."u8
: $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}",
{ _removeDesigns.Count is 0))
foreach (var (design, _) in _removeDesigns) foreach (var (design, _) in _removeDesigns)
editor.ChangeColor(design, string.Empty); editor.ChangeColor(design, string.Empty);
}
Im.Separator(); Im.Separator();
} }
@ -333,14 +330,15 @@ public class MultiDesignPanel(
Im.Line.Same(offset, Im.Style.ItemSpacing.X); Im.Line.Same(offset, Im.Style.ItemSpacing.X);
var enabled = config.DeleteDesignModifier.IsActive(); var enabled = config.DeleteDesignModifier.IsActive();
if (ImEx.Button("Delete All Advanced Dyes"u8, Im.ContentRegion.Available with { Y = 0 }, _numDesignsWithAdvancedDyes is 0 if (ImEx.Button("Delete All Advanced Dyes"u8, Im.ContentRegion.Available with { Y = 0 }, _numDesignsWithAdvancedDyes is 0
? "No selected designs contain any advanced dyes."u8 ? "No selected designs contain any advanced dyes."u8
: $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.", : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.",
!enabled || _numDesignsWithAdvancedDyes is 0)) !enabled || _numDesignsWithAdvancedDyes is 0))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var design in fileSystem.Selection.DataNodes)
{ {
while (design.Value.Materials.Count > 0) while (design.GetValue<Design>()!.Materials.Count > 0)
editor.ChangeMaterialValue(design.Value, MaterialValueIndex.FromKey(design.Value.Materials[0].Item1), null); editor.ChangeMaterialValue(design.GetValue<Design>()!,
MaterialValueIndex.FromKey(design.GetValue<Design>()!.Materials[0].Item1), null);
} }
if (!enabled && _numDesignsWithAdvancedDyes is not 0) if (!enabled && _numDesignsWithAdvancedDyes is not 0)
@ -359,8 +357,8 @@ public class MultiDesignPanel(
using (Im.Group()) using (Im.Group())
{ {
if (ImEx.Button("Disable Everything"u8, width, if (ImEx.Button("Disable Everything"u8, width,
_numDesigns > 0 fileSystem.Selection.DataNodes.Count > 0
? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {fileSystem.Selection.DataNodes.Count} designs."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
{ {
equip = false; equip = false;
@ -372,8 +370,8 @@ public class MultiDesignPanel(
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Enable Everything"u8, width, if (ImEx.Button("Enable Everything"u8, width,
_numDesigns > 0 fileSystem.Selection.DataNodes.Count > 0
? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {fileSystem.Selection.DataNodes.Count} designs."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
{ {
equip = true; equip = true;
@ -384,8 +382,8 @@ public class MultiDesignPanel(
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking.");
if (ImEx.Button("Equipment Only"u8, width, if (ImEx.Button("Equipment Only"u8, width,
_numDesigns > 0 fileSystem.Selection.DataNodes.Count > 0
? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {fileSystem.Selection.DataNodes.Count} designs."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
{ {
equip = true; equip = true;
@ -397,8 +395,8 @@ public class MultiDesignPanel(
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Customization Only"u8, width, if (ImEx.Button("Customization Only"u8, width,
_numDesigns > 0 fileSystem.Selection.DataNodes.Count > 0
? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {fileSystem.Selection.DataNodes.Count} designs."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
{ {
equip = false; equip = false;
@ -409,10 +407,10 @@ public class MultiDesignPanel(
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking.");
if (ImEx.Button("Default Application"u8, width, if (ImEx.Button("Default Application"u8, width,
_numDesigns > 0 fileSystem.Selection.DataNodes.Count > 0
? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." ? $"Set the application rules to the default values as if the {fileSystem.Selection.DataNodes.Count} were newly created,without any advanced features or wetness."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>().Select(l => l.Value)) foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue<Design>()!))
{ {
editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true);
editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); editor.ChangeApplyMeta(design, MetaIndex.Wetness, false);
@ -422,10 +420,10 @@ public class MultiDesignPanel(
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking.");
Im.Line.Same(); Im.Line.Same();
if (ImEx.Button("Disable Advanced"u8, width, _numDesigns > 0 if (ImEx.Button("Disable Advanced"u8, width, fileSystem.Selection.DataNodes.Count > 0
? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." ? $"Disable all advanced dyes and customizations but keep everything else as is for all {fileSystem.Selection.DataNodes.Count} designs."
: "No designs selected."u8, !enabled)) : "No designs selected."u8, !enabled))
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>().Select(l => l.Value)) foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue<Design>()!))
editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null);
if (!enabled) if (!enabled)
@ -436,7 +434,7 @@ public class MultiDesignPanel(
if (equip is null && customize is null) if (equip is null && customize is null)
return; return;
foreach (var design in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>().Select(l => l.Value)) foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue<Design>()!))
{ {
editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip,
equip); equip);
@ -459,13 +457,14 @@ public class MultiDesignPanel(
if (_tag.Length is 0) if (_tag.Length is 0)
return; return;
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var leaf in fileSystem.Selection.DataNodes)
{ {
var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); var design = leaf.GetValue<Design>()!;
var index = design.Tags.AsEnumerable().IndexOf(_tag);
if (index >= 0) if (index >= 0)
_removeDesigns.Add((leaf.Value, index)); _removeDesigns.Add((design, index));
else else
_addDesigns.Add(leaf.Value); _addDesigns.Add(design);
} }
} }
@ -474,12 +473,13 @@ public class MultiDesignPanel(
_addDesigns.Clear(); _addDesigns.Clear();
_removeDesigns.Clear(); _removeDesigns.Clear();
var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection; var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection;
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var leaf in fileSystem.Selection.DataNodes)
{ {
if (leaf.Value.Color.Length > 0) var design = leaf.GetValue<Design>()!;
_removeDesigns.Add((leaf.Value, 0)); if (design.Color.Length > 0)
if (selection != DesignColors.AutomaticName && leaf.Value.Color != selection) _removeDesigns.Add((design, 0));
_addDesigns.Add(leaf.Value); if (selection != DesignColors.AutomaticName && design.Color != selection)
_addDesigns.Add(design);
} }
} }
} }

View file

@ -0,0 +1,45 @@
using Glamourer.Config;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DeleteSelectionButton(DesignFileSystem fileSystem, DesignManager manager, Configuration config)
: BaseIconButton<AwesomeIcon>
{
/// <inheritdoc/>
public override AwesomeIcon Icon
=> LunaStyle.DeleteIcon;
/// <inheritdoc/>
public override bool HasTooltip
=> true;
/// <inheritdoc/>
public override void DrawTooltip()
{
var anySelected = fileSystem.Selection.DataNodes.Count > 0;
var modifier = Enabled;
Im.Text(anySelected
? "Delete the currently selected designs entirely from your drive\nThis can not be undone."u8
: "No designs selected."u8);
if (!modifier)
Im.Text($"\nHold {config.DeleteDesignModifier} while clicking to delete the designs.");
}
/// <inheritdoc/>
public override bool Enabled
=> config.DeleteDesignModifier.IsActive() && fileSystem.Selection.DataNodes.Count > 0;
/// <inheritdoc/>
public override void OnClick()
{
foreach (var node in fileSystem.Selection.DataNodes.ToArray())
{
if (node.GetValue<Design>() is { } design)
manager.Delete(design);
}
}
}

View file

@ -0,0 +1,112 @@
using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.Events;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignFileSystemCache : FileSystemCache<DesignFileSystemCache.DesignData>
{
public DesignFileSystemCache(DesignFileSystemDrawer parent)
: base(parent)
{
parent.DesignChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystemSelector);
parent.DesignColors.ColorChanged += OnColorChanged;
}
private void OnColorChanged()
{
foreach (var node in AllNodes.Values)
node.Dirty = true;
}
private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _2)
{
switch (type)
{
case DesignChanged.Type.Created:
case DesignChanged.Type.Deleted:
case DesignChanged.Type.ReloadedAll:
case DesignChanged.Type.Renamed:
case DesignChanged.Type.ChangedDescription:
case DesignChanged.Type.ChangedColor:
case DesignChanged.Type.AddedTag:
case DesignChanged.Type.RemovedTag:
case DesignChanged.Type.ChangedTag:
case DesignChanged.Type.AddedMod:
case DesignChanged.Type.RemovedMod:
case DesignChanged.Type.UpdatedMod:
case DesignChanged.Type.ChangedLink:
case DesignChanged.Type.Equip:
case DesignChanged.Type.BonusItem:
case DesignChanged.Type.Weapon:
VisibleDirty = true;
break;
}
if (design.Node is { } node && AllNodes.TryGetValue(node, out var cache))
cache.Dirty = true;
}
private new DesignFileSystemDrawer Parent
=> (DesignFileSystemDrawer)base.Parent;
public override void Update()
{
if (ColorsDirty)
{
CollapsedFolderColor = ColorId.FolderCollapsed.Value().ToVector();
ExpandedFolderColor = ColorId.FolderExpanded.Value().ToVector();
LineColor = ColorId.FolderLine.Value().ToVector();
Dirty &= ~IManagedCache.DirtyFlags.Colors;
OnColorChanged();
}
}
protected override DesignData ConvertNode(in IFileSystemNode node)
=> new((IFileSystemData<Design>)node);
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Parent.DesignChanged.Unsubscribe(OnDesignChanged);
Parent.DesignColors.ColorChanged -= OnColorChanged;
}
public sealed class DesignData(IFileSystemData<Design> node) : BaseFileSystemNodeCache<DesignData>
{
public readonly IFileSystemData<Design> Node = node;
public Vector4 Color;
public StringU8 Name = new(node.Value.Name.Text);
public StringU8 Incognito = new(node.Value.Incognito);
public override void Update(FileSystemCache cache, IFileSystemNode node)
{
var drawer = (DesignFileSystemDrawer)cache.Parent;
Color = drawer.DesignColors.GetColor(Node.Value).ToVector();
Name = new StringU8(Node.Value.Name.Text);
}
protected override void DrawInternal(FileSystemCache<DesignData> cache, IFileSystemNode node)
{
var c = (DesignFileSystemCache)cache;
using var color = ImGuiColor.Text.Push(Color);
using var id = Im.Id.Push(Node.Value.Index);
var flags = node.Selected ? TreeNodeFlags.NoTreePushOnOpen | TreeNodeFlags.Selected : TreeNodeFlags.NoTreePushOnOpen;
Im.Tree.Leaf(c.Parent.Config.Ephemeral.IncognitoMode ? Incognito : Name, flags);
CheckDoubleClick(c);
}
private void CheckDoubleClick(DesignFileSystemCache cache)
{
if (!cache.Parent.Config.AllowDoubleClickToApply)
return;
if (!Im.Item.Hovered())
return;
if (Im.Mouse.IsDoubleClicked(MouseButton.Left))
cache.Parent.DesignApplier.ApplyToPlayer(Node.Value);
}
}
}

View file

@ -0,0 +1,65 @@
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignFileSystemDrawer : FileSystemDrawer<DesignFileSystemCache.DesignData>, IDisposable
{
internal readonly Configuration Config;
internal readonly DesignApplier DesignApplier;
internal readonly DesignChanged DesignChanged;
internal readonly DesignColors DesignColors;
internal readonly DesignManager Manager;
public DesignFileSystemDrawer(DesignFileSystem fileSystem, DesignManager manager, DesignConverter converter, Configuration config,
DesignApplier designApplier, DesignChanged designChanged, DesignColors designColors)
: base(fileSystem, new DesignFilter())
{
Manager = manager;
Config = config;
DesignApplier = designApplier;
DesignChanged = designChanged;
DesignColors = designColors;
Footer.Buttons.AddButton(new NewDesignButton(manager), 1000);
Footer.Buttons.AddButton(new ImportDesignButton(converter, manager), 900);
Footer.Buttons.AddButton(new DuplicateDesignButton(fileSystem, manager), 800);
Footer.Buttons.AddButton(new DeleteSelectionButton(fileSystem, manager, config), -100);
SortMode = Config.SortMode;
OnRenameChanged(Config.ShowRename, default);
Config.OnRenameChanged += OnRenameChanged;
}
private void OnRenameChanged(RenameField newValue, RenameField _)
{
DataContext.RemoveButtons<MoveDesignInput>();
DataContext.RemoveButtons<RenameDesignInput>();
switch (newValue)
{
case RenameField.RenameSearchPath: DataContext.AddButton(new RenameDesignInput(this), -1000); break;
case RenameField.RenameData: DataContext.AddButton(new MoveDesignInput(this), -1000); break;
case RenameField.BothSearchPathPrio:
DataContext.AddButton(new RenameDesignInput(this), -1000);
DataContext.AddButton(new MoveDesignInput(this), -1001);
break;
case RenameField.BothDataPrio:
DataContext.AddButton(new RenameDesignInput(this), -1001);
DataContext.AddButton(new MoveDesignInput(this), -1000);
break;
}
}
public void Dispose()
{
Config.OnRenameChanged -= OnRenameChanged;
}
public override ReadOnlySpan<byte> Id
=> "Designs"u8;
protected override FileSystemCache<DesignFileSystemCache.DesignData> CreateCache()
=> new DesignFileSystemCache(this);
}

View file

@ -0,0 +1,150 @@
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignFilter : TokenizedFilter<DesignFilterTokenType, DesignFileSystemCache.DesignData, DesignFilterToken>,
IFileSystemFilter<DesignFileSystemCache.DesignData>, IUiService
{
protected override void DrawTooltip()
{
if (!Im.Item.Hovered())
return;
using var tt = Im.Tooltip.Begin();
var highlightColor = ColorId.EnabledAutoSet.Value().ToVector();
Im.Text("Filter designs for those where their full paths or names contain the given strings, split by spaces."u8);
ImEx.TextMultiColored("Enter "u8).Then("m:[string]"u8, highlightColor)
.Then(" to filter for designs with a mod association containing the string."u8).End();
ImEx.TextMultiColored("Enter "u8).Then("t:[string]"u8, highlightColor).Then(" to filter for designs set to specific tags."u8).End();
ImEx.TextMultiColored("Enter "u8).Then("c:[string]"u8, highlightColor)
.Then(" to filter for designs set to specific colors."u8).End();
ImEx.TextMultiColored("Enter "u8).Then("i:[string]"u8, highlightColor).Then(" to filter for designs containing specific items."u8)
.End();
ImEx.TextMultiColored("Enter "u8).Then("n:[string]"u8, highlightColor).Then(" to filter only for design names, ignoring the paths."u8)
.End();
ImEx.TextMultiColored("Enter "u8).Then("f:[string]"u8, highlightColor).Then(
" to filter for designs containing the text in name, path, description, tags, mod associations, colors or contained items."u8)
.End();
Im.Line.New();
ImEx.TextMultiColored("Use "u8).Then("None"u8, highlightColor).Then(" as a placeholder value that only matches empty lists or names."u8)
.End();
Im.Text("Regularly, a design has to match all supplied criteria separately."u8);
ImEx.TextMultiColored("Put a "u8).Then("'-'"u8, highlightColor)
.Then(" in front of a search token to search only for designs not matching the criterion."u8).End();
ImEx.TextMultiColored("Put a "u8).Then("'?'"u8, highlightColor)
.Then(" in front of a search token to search for designs matching at least one of the '?'-criteria."u8).End();
ImEx.TextMultiColored("Wrap spaces in "u8).Then("\"[string with space]\""u8, highlightColor)
.Then(" to match this exact combination of words."u8).End();
}
protected override bool Matches(in DesignFilterToken token, in DesignFileSystemCache.DesignData cacheItem)
=> token.Type switch
{
DesignFilterTokenType.Default => cacheItem.Node.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase)
|| cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.Mod => CheckMods(token.Needle, cacheItem),
DesignFilterTokenType.Tag => CheckTags(token.Needle, cacheItem),
DesignFilterTokenType.Color => cacheItem.Node.Value.Color.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.Item => cacheItem.Node.Value.DesignData.ContainsName(token.Needle),
DesignFilterTokenType.Name => cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.FullContext => CheckFullContext(token.Needle, cacheItem),
_ => true,
};
protected override bool MatchesNone(DesignFilterTokenType type, bool negated, in DesignFileSystemCache.DesignData cacheItem)
=> type switch
{
DesignFilterTokenType.Mod when negated => cacheItem.Node.Value.AssociatedMods.Count > 0,
DesignFilterTokenType.Mod => cacheItem.Node.Value.AssociatedMods.Count is 0,
DesignFilterTokenType.Tag when negated => cacheItem.Node.Value.Tags.Length > 0,
DesignFilterTokenType.Tag => cacheItem.Node.Value.Tags.Length is 0,
_ => true,
};
private static bool CheckMods(string needle, in DesignFileSystemCache.DesignData cacheItem)
=> cacheItem.Node.Value.AssociatedMods.Any(kvp => kvp.Key.Name.Contains(needle, StringComparison.OrdinalIgnoreCase));
private static bool CheckTags(string needle, in DesignFileSystemCache.DesignData cacheItem)
=> cacheItem.Node.Value.Tags.Any(t => t.Contains(needle, StringComparison.OrdinalIgnoreCase));
private static bool CheckFullContext(string needle, in DesignFileSystemCache.DesignData cacheItem)
{
if (needle.Length is 0)
return true;
if (cacheItem.Node.FullPath.Contains(needle, StringComparison.OrdinalIgnoreCase))
return true;
var design = cacheItem.Node.Value;
if (design.Name.Text.Contains(needle, StringComparison.OrdinalIgnoreCase))
return true;
if (design.Description.Contains(needle, StringComparison.OrdinalIgnoreCase))
return true;
if (CheckTags(needle, cacheItem))
return true;
if (design.Color.Contains(needle, StringComparison.OrdinalIgnoreCase))
return true;
if (CheckMods(needle, cacheItem))
return true;
if (design.DesignData.ContainsName(needle))
return true;
if (design.Identifier.ToString().Contains(needle, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
public bool WouldBeVisible(in FileSystemFolderCache folder)
{
switch (State)
{
case FilterState.NoFilters: return true;
case FilterState.NoMatches: return false;
}
foreach (var token in Forced)
{
if (token.Type switch
{
DesignFilterTokenType.Name => !folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.Default => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.FullContext => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
_ => true,
})
return false;
}
foreach (var token in Negated)
{
if (token.Type switch
{
DesignFilterTokenType.Name => folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.Default => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.FullContext => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
_ => false,
})
return false;
}
foreach (var token in General)
{
if (token.Type switch
{
DesignFilterTokenType.Name => folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.Default => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
DesignFilterTokenType.FullContext => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase),
_ => false,
})
return true;
}
return General.Count is 0;
}
}

View file

@ -0,0 +1,49 @@
using ImSharp;
namespace Glamourer.Gui.Tabs.DesignTab;
public enum DesignFilterTokenType
{
Default,
Mod,
Tag,
Color,
Item,
Name,
FullContext,
}
public readonly struct DesignFilterToken() : IFilterToken<DesignFilterTokenType, DesignFilterToken>
{
public string Needle { get; init; } = string.Empty;
public DesignFilterTokenType Type { get; init; }
public bool Contains(DesignFilterToken other)
{
if (Type != other.Type)
return false;
return Needle.Contains(other.Needle);
}
public static bool ConvertToken(char tokenCharacter, out DesignFilterTokenType type)
{
type = tokenCharacter switch
{
'm' or 'M' => DesignFilterTokenType.Mod,
'n' or 'N' => DesignFilterTokenType.Name,
't' or 'T' => DesignFilterTokenType.Tag,
'i' or 'I' => DesignFilterTokenType.Item,
'c' or 'C' => DesignFilterTokenType.Color,
'f' or 'F' => DesignFilterTokenType.FullContext,
_ => DesignFilterTokenType.Default,
};
return type is not DesignFilterTokenType.Default;
}
public static bool AllowsNone(DesignFilterTokenType type)
=> type is DesignFilterTokenType.Tag or DesignFilterTokenType.Mod;
public static void ProcessList(List<DesignFilterToken> list)
{ }
}

View file

@ -0,0 +1,38 @@
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DuplicateDesignButton(DesignFileSystem fileSystem, DesignManager designManager) : BaseIconButton<AwesomeIcon>
{
private readonly WeakReference<Design> _design = new(null!);
public override AwesomeIcon Icon
=> LunaStyle.DuplicateIcon;
public override bool HasTooltip
=> true;
public override bool Enabled
=> fileSystem.Selection.Selection is not null;
public override void DrawTooltip()
=> Im.Text(fileSystem.Selection.Selection is null ? "No design selected."u8 : "Clone the currently selected design to a duplicate."u8);
public override void OnClick()
{
_design.SetTarget(fileSystem.Selection.Selection?.GetValue<Design>()!);
Im.Popup.Open("##CloneDesign"u8);
}
protected override void PostDraw()
{
if (!InputPopup.OpenName("##CloneDesign"u8, out var newName))
return;
if (_design.TryGetTarget(out var design))
designManager.CreateClone(design, newName, true);
_design.SetTarget(null!);
}
}

View file

@ -0,0 +1,52 @@
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class ImportDesignButton(DesignConverter converter, DesignManager manager) : BaseIconButton<AwesomeIcon>
{
private string _clipboardText = string.Empty;
public override AwesomeIcon Icon
=> LunaStyle.ImportIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Try to import a design from your clipboard."u8);
public override void OnClick()
{
try
{
_clipboardText = Im.Clipboard.GetUtf16();
Im.Popup.Open("##ImportDesign"u8);
}
catch (Exception)
{
Glamourer.Messager.NotificationMessage("Could not import data from clipboard.", NotificationType.Error, false);
}
}
protected override void PostDraw()
{
if (!InputPopup.OpenName("##ImportDesign"u8, out var newName))
return;
if (_clipboardText.Length is 0)
return;
var design = converter.FromBase64(_clipboardText, true, true, out _);
if (design is Design d)
manager.CreateClone(d, newName, true);
else if (design is not null)
manager.CreateClone(design, newName, true);
else
Glamourer.Messager.NotificationMessage("Could not create a design, clipboard did not contain valid design data.",
NotificationType.Error, false);
_clipboardText = string.Empty;
}
}

View file

@ -0,0 +1,34 @@
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class MoveDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton<IFileSystemData>
{
/// <inheritdoc/>
public override ReadOnlySpan<byte> Label(in IFileSystemData _)
=> "##Move"u8;
/// <summary> Replaces the normal menu item handling for a text input, so the other fields are not used. </summary>
/// <inheritdoc/>
public override bool DrawMenuItem(in IFileSystemData data)
{
var currentPath = data.FullPath;
using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding);
MenuSeparator.DrawSeparator();
Im.Text("Move Design:"u8);
if (Im.Window.Appearing)
Im.Keyboard.SetFocusHere();
var ret = Im.Input.Text(Label(data), ref currentPath, flags: InputTextFlags.EnterReturnsTrue);
Im.Tooltip.OnHover(
"Enter a full path here to move the design or change its search path. Creates all required parent directories, if possible."u8);
if (!ret)
return false;
fileSystem.FileSystem.RenameAndMove(data, currentPath);
fileSystem.FileSystem.ExpandAllAncestors(data);
Im.Popup.CloseCurrent();
return ret;
}
}

View file

@ -0,0 +1,28 @@
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class NewDesignButton(DesignManager designManager) : BaseIconButton<AwesomeIcon>
{
public override AwesomeIcon Icon
=> LunaStyle.AddObjectIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Create a new design with default configuration."u8);
public override void OnClick()
=> Im.Popup.Open("##NewDesign"u8);
protected override void PostDraw()
{
if (!InputPopup.OpenName("##NewDesign"u8, out var newName))
return;
designManager.CreateEmpty(newName, true);
}
}

View file

@ -0,0 +1,34 @@
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class RenameDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton<IFileSystemData>
{
/// <inheritdoc/>
public override ReadOnlySpan<byte> Label(in IFileSystemData _)
=> "##Rename"u8;
/// <summary> Replaces the normal menu item handling for a text input, so the other fields are not used. </summary>
/// <inheritdoc/>
public override bool DrawMenuItem(in IFileSystemData data)
{
var design = (Design)data.Value;
var currentName = design.Name.Text;
using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding);
MenuSeparator.DrawSeparator();
Im.Text("Rename Design:"u8);
if (Im.Window.Appearing)
Im.Keyboard.SetFocusHere();
var ret = Im.Input.Text(Label(data), ref currentName, flags: InputTextFlags.EnterReturnsTrue);
Im.Tooltip.OnHover("Enter a new name here to rename the changed design."u8);
if (!ret)
return false;
fileSystem.Manager.Rename(design, currentName);
Im.Popup.CloseCurrent();
return ret;
}
}

View file

@ -0,0 +1,44 @@
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class SetFromClipboardButton(DesignFileSystem fileSystem, DesignConverter converter, DesignManager manager)
: BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> LunaStyle.FromClipboardIcon;
public override bool Enabled
=> !((Design)fileSystem.Selection.Selection!.Value).WriteProtected();
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
"Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8);
public override void OnClick()
{
try
{
var text = Im.Clipboard.GetUtf16();
var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool();
var design = converter.FromBase64(text, applyCustomize, applyEquip, out _)
?? throw new Exception("The clipboard did not contain valid data.");
manager.ApplyDesign((Design)fileSystem.Selection.Selection!.Value, design);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {((Design)fileSystem.Selection.Selection!.Value).Name}.",
$"Could not apply clipboard to design {((Design)fileSystem.Selection.Selection!.Value).Identifier}", NotificationType.Error,
false);
}
}
}

View file

@ -0,0 +1,27 @@
using Glamourer.Designs;
using Glamourer.Designs.History;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class UndoButton(DesignFileSystem fileSystem, EditorHistory history) : BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> LunaStyle.UndoIcon;
public override bool Enabled
=> !((Design)fileSystem.Selection.Selection!.Value).WriteProtected() && history.CanUndo((Design)fileSystem.Selection.Selection!.Value);
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Undo the last change."u8);
public override void OnClick()
=> history.Undo((Design)fileSystem.Selection.Selection!.Value);
}

View file

@ -1,80 +0,0 @@
using Dalamud.Interface;
using Dalamud.Bindings.ImGui;
using ImSharp;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui.Tabs;
public static class HeaderDrawer
{
public abstract class Button
{
protected abstract void OnClick();
protected virtual string Description
=> string.Empty;
protected virtual Rgba32 BorderColor
=> ColorId.HeaderButtons.Value();
protected virtual Rgba32 TextColor
=> ColorId.HeaderButtons.Value();
protected virtual FontAwesomeIcon Icon
=> FontAwesomeIcon.None;
protected virtual bool Disabled
=> false;
public virtual bool Visible
=> true;
public void Draw(float width)
{
if (!Visible)
return;
using var color = ImGuiColor.Border.Push(BorderColor)
.Push(ImGuiColor.Text, TextColor, TextColor.IsVisible);
if (ImGuiUtil.DrawDisabledButton(Icon.ToIconString(), new Vector2(width, Im.Style.FrameHeight), string.Empty, Disabled, true))
OnClick();
color.Pop();
ImGuiUtil.HoverTooltip(Description);
}
}
public static void Draw(string text, uint textColor, uint frameColor, Button[] leftButtons, Button[] rightButtons)
{
var width = Im.Style.FrameHeightWithSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0)
.Push(ImGuiStyleVar.FrameBorderSize, Im.Style.GlobalScale);
var leftButtonSize = 0f;
foreach (var button in leftButtons.Where(b => b.Visible))
{
button.Draw(width);
Im.Line.Same();
leftButtonSize += width;
}
var rightButtonSize = rightButtons.Count(b => b.Visible) * width;
var midSize = Im.ContentRegion.Available.X - rightButtonSize - Im.Style.GlobalScale;
style.Pop();
style.Push(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.5f + (rightButtonSize - leftButtonSize) / midSize, 0.5f));
if (textColor != 0)
ImGuiUtil.DrawTextButton(text, new Vector2(midSize, Im.Style.FrameHeight), frameColor, textColor);
else
ImGuiUtil.DrawTextButton(text, new Vector2(midSize, Im.Style.FrameHeight), frameColor);
style.Pop();
style.Push(ImGuiStyleVar.FrameBorderSize, Im.Style.GlobalScale);
foreach (var button in rightButtons.Where(b => b.Visible))
{
Im.Line.Same();
button.Draw(width);
}
}
}

View file

@ -1,9 +1,10 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs; namespace Glamourer.Gui.Tabs;
public sealed class IncognitoButton(Configuration.Configuration config) : BaseIconButton<AwesomeIcon>, IUiService public sealed class IncognitoButton(Configuration config) : BaseIconButton<AwesomeIcon>, IUiService
{ {
public override AwesomeIcon Icon public override AwesomeIcon Icon
=> config.Ephemeral.IncognitoMode => config.Ephemeral.IncognitoMode

View file

@ -1,5 +1,5 @@
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
@ -13,7 +13,7 @@ using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcPanel( public sealed class NpcPanel(
Configuration.Configuration config, Configuration config,
NpcSelection selection, NpcSelection selection,
CustomizationDrawer customizeDrawer, CustomizationDrawer customizeDrawer,
EquipmentDrawer equipmentDrawer, EquipmentDrawer equipmentDrawer,

View file

@ -1,4 +1,4 @@
using Glamourer.Configuration; using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;

View file

@ -1,16 +1,12 @@
using Dalamud.Interface; using Glamourer.Config;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui.Filesystem;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
namespace Glamourer.Gui.Tabs.SettingsTab; namespace Glamourer.Gui.Tabs.SettingsTab;
public class CodeDrawer(Configuration.Configuration config, CodeService codeService, FunModule funModule) : IUiService public class CodeDrawer(Configuration config, CodeService codeService, FunModule funModule) : IUiService
{ {
private static ReadOnlySpan<byte> Tooltip private static ReadOnlySpan<byte> Tooltip
=> "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8 => "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8
@ -41,7 +37,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
private void DrawCodeInput() private void DrawCodeInput()
{ {
var color = codeService.CheckCode(_currentCode).Item2 is not 0 ? ColorId.ActorAvailable : ColorId.ActorUnavailable; var color = codeService.CheckCode(_currentCode).Item2 is not 0 ? ColorId.ActorAvailable : ColorId.ActorUnavailable;
using var border = ImRaii.PushFrameBorder(Im.Style.GlobalScale, color.Value().Color, _currentCode.Length > 0); using var border = ImStyleBorder.Frame.Push(color.Value(), Im.Style.GlobalScale, _currentCode.Length > 0);
Im.Item.SetNextWidth(500 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X); Im.Item.SetNextWidth(500 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X);
if (Im.Input.Text("##Code"u8, ref _currentCode, "Enter Cheat Code..."u8, InputTextFlags.EnterReturnsTrue)) if (Im.Input.Text("##Code"u8, ref _currentCode, "Enter Cheat Code..."u8, InputTextFlags.EnterReturnsTrue))
{ {
@ -50,24 +46,22 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
} }
Im.Line.Same(); Im.Line.Same();
ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, ImGuiColor.TextDisabled.Get().Color); ImEx.Icon.Draw(LunaStyle.WarningIcon, ImGuiColor.TextDisabled.Get());
DrawTooltip(); DrawTooltip();
} }
private void DrawCopyButtons() private void DrawCopyButtons()
{ {
var buttonSize = new Vector2(250 * Im.Style.GlobalScale, 0); var buttonSize = ImEx.ScaledVectorX(250);
if (ImUtf8.Button("Who am I?!?"u8, buttonSize)) if (Im.Button("Who am I?!?"u8, buttonSize))
funModule.WhoAmI(); funModule.WhoAmI();
ImUtf8.HoverTooltip( Im.Tooltip.OnHover("Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
"Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.Button("Who is that!?!"u8, buttonSize)) if (Im.Button("Who is that!?!"u8, buttonSize))
funModule.WhoIsThat(); funModule.WhoIsThat();
ImUtf8.HoverTooltip( Im.Tooltip.OnHover("Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
"Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
} }
private CodeService.CodeFlag DrawCodes() private CodeService.CodeFlag DrawCodes()
@ -76,7 +70,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
CodeService.CodeFlag knownFlags = 0; CodeService.CodeFlag knownFlags = 0;
for (var i = 0; i < config.Codes.Count; ++i) for (var i = 0; i < config.Codes.Count; ++i)
{ {
using var id = ImUtf8.PushId(i); using var id = Im.Id.Push(i);
var (code, state) = config.Codes[i]; var (code, state) = config.Codes[i];
var (action, flag) = codeService.CheckCode(code); var (action, flag) = codeService.CheckCode(code);
if (flag is 0) if (flag is 0)
@ -84,8 +78,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
var data = CodeService.GetData(flag); var data = CodeService.GetData(flag);
if (ImUtf8.IconButton(FontAwesomeIcon.Trash, if (ImEx.Icon.Button(LunaStyle.DeleteIcon, $"Delete this cheat code.{(canDelete ? StringU8.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}",
$"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}",
disabled: !canDelete)) disabled: !canDelete))
{ {
action!(false); action!(false);
@ -95,7 +88,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
knownFlags |= flag; knownFlags |= flag;
Im.Line.SameInner(); Im.Line.SameInner();
if (ImUtf8.Checkbox("\0"u8, ref state)) if (Im.Checkbox(StringU8.Empty, ref state))
{ {
action!(state); action!(state);
codeService.SaveState(); codeService.SaveState();
@ -103,14 +96,14 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
var hovered = Im.Item.Hovered(); var hovered = Im.Item.Hovered();
Im.Line.Same(); Im.Line.Same();
ImUtf8.Selectable(code); Im.Selectable(code);
hovered |= Im.Item.Hovered(); hovered |= Im.Item.Hovered();
DrawSource(i, code); DrawSource(i, code);
DrawTarget(i); DrawTarget(i);
if (hovered) if (hovered)
{ {
using var tt = ImUtf8.Tooltip(); using var tt = Im.Tooltip.Begin();
ImUtf8.Text(data.Effect); Im.Text(data.Effect);
} }
} }
@ -119,22 +112,22 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
private void DrawSource(int idx, string code) private void DrawSource(int idx, string code)
{ {
using var source = ImUtf8.DragDropSource(); using var source = Im.DragDrop.Source();
if (!source) if (!source)
return; return;
if (!DragDropSource.SetPayload(DragDropLabel)) if (!source.SetPayload(DragDropLabel))
_dragCodeIdx = idx; _dragCodeIdx = idx;
ImUtf8.Text($"Dragging {code}..."); Im.Text($"Dragging {code}...");
} }
private void DrawTarget(int idx) private void DrawTarget(int idx)
{ {
using var target = ImUtf8.DragDropTarget(); using var target = Im.DragDrop.Target();
if (!target.IsDropping(DragDropLabel) || _dragCodeIdx == -1) if (!target.IsDropping(DragDropLabel) || _dragCodeIdx is -1)
return; return;
if (Extensions.Move(config.Codes, _dragCodeIdx, idx)) if (config.Codes.Move(_dragCodeIdx, idx))
codeService.SaveState(); codeService.SaveState();
_dragCodeIdx = -1; _dragCodeIdx = -1;
} }
@ -144,7 +137,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
if (knownFlags.HasFlag(CodeService.AllHintCodes)) if (knownFlags.HasFlag(CodeService.AllHintCodes))
return; return;
if (ImUtf8.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8)) if (Im.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8))
_showCodeHints = !_showCodeHints; _showCodeHints = !_showCodeHints;
if (!_showCodeHints) if (!_showCodeHints)
@ -162,23 +155,23 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
Im.Separator(); Im.Separator();
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
ImUtf8.Text(data.Effect); Im.Text(data.Effect);
using var indent = ImRaii.PushIndent(2); using var indent = Im.Indent(2);
using (ImUtf8.Group()) using (Im.Group())
{ {
ImUtf8.Text("Capitalized letters: "u8); Im.Text("Capitalized letters: "u8);
ImUtf8.Text("Punctuation: "u8); Im.Text("Punctuation: "u8);
} }
Im.Line.SameInner(); Im.Line.SameInner();
using (ImUtf8.Group()) using (Im.Group())
{ {
using var mono = Im.Font.PushMono(); using var mono = Im.Font.PushMono();
ImUtf8.Text($"{data.CapitalCount}"); Im.Text($"{data.CapitalCount}");
ImUtf8.Text($"{data.Punctuation}"); Im.Text($"{data.Punctuation}");
} }
ImUtf8.TextWrapped(data.Hint); Im.TextWrapped(data.Hint);
} }
} }
@ -189,7 +182,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
return; return;
Im.Window.SetNextSize(new Vector2(400, 0)); Im.Window.SetNextSize(new Vector2(400, 0));
using var tt = ImUtf8.Tooltip(); using var tt = Im.Tooltip.Begin();
ImUtf8.TextWrapped(Tooltip); Im.TextWrapped(Tooltip);
} }
} }

View file

@ -1,35 +1,82 @@
using Dalamud.Interface; using Glamourer.Config;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui.Widgets;
using Logger = OtterGui.Log.Logger;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.SettingsTab; namespace Glamourer.Gui.Tabs.SettingsTab;
public sealed class CollectionCombo(Configuration.Configuration config, PenumbraService penumbra, Logger log) public sealed class CollectionCombo(Configuration config, PenumbraService penumbra)
: FilterComboCache<(Guid Id, string IdShort, string Name)>( : FilterComboBase<CollectionCombo.CacheItem>(new CollectionFilter()), IUiService
() => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(),
MouseWheelType.Control, log), IUiService
{ {
protected override bool DrawSelectable(int globalIdx, bool selected) private Guid _selected = Guid.Empty;
public readonly struct CacheItem(Guid id, string name)
{
public readonly StringPair Name = new(name);
public readonly Guid Id = id;
public readonly StringU8 Incognito = id.ShortGuidU8();
public readonly StringU8 ShortId = new($"({id.ShortGuidU8()})");
}
protected override float ItemHeight
=> Im.Style.TextHeightWithSpacing;
protected override IEnumerable<CacheItem> GetItems()
=> penumbra.GetCollections().Select(kvp => new CacheItem(kvp.Key, kvp.Value));
public bool Draw(Utf8StringHandler<LabelStringHandlerBuffer> label, Utf8StringHandler<HintStringHandlerBuffer> preview, out string newName,
ref Guid id, float width)
{
_selected = id;
if (!base.Draw(label, preview, StringU8.Empty, width, out var ret))
{
newName = string.Empty;
return false;
}
newName = ret.Name.Utf16;
id = ret.Id;
return true;
}
protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected)
{ {
var (_, idShort, name) = Items[globalIdx];
if (config.Ephemeral.IncognitoMode) if (config.Ephemeral.IncognitoMode)
using (Im.Font.PushMono()) using (Im.Font.PushMono())
{ {
return Im.Selectable(idShort); return Im.Selectable(item.Incognito, selected);
} }
var ret = Im.Selectable(name, selected); var ret = Im.Selectable(item.Name.Utf8, selected);
Im.Line.Same(); Im.Line.Same();
using (Im.Font.PushMono()) using (Im.Font.PushMono())
{ {
using var color = ImGuiColor.Text.Push(ImGuiColor.TextDisabled.Get()); using var color = ImGuiColor.Text.Push(ImGuiColor.TextDisabled.Get());
ImEx.TextRightAligned($"({idShort})"); ImEx.TextRightAligned(item.ShortId);
} }
return ret; return ret;
} }
protected override bool IsSelected(CacheItem item, int globalIndex)
=> item.Id == _selected;
private sealed class CollectionFilter : Utf8FilterBase<CacheItem>
{
public override bool WouldBeVisible(in CacheItem item, int globalIndex)
=> base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Incognito);
protected override ReadOnlySpan<byte> ToFilterString(in CacheItem item, int globalIndex)
=> item.Name;
}
protected override FilterComboBaseCache<CacheItem> CreateCache()
=> new Cache(this);
private sealed class Cache(CollectionCombo parent) : FilterComboBaseCache<CacheItem>(parent)
{
protected override void ComputeWidth()
=> ComboWidth = AllItems.Max(i => i.Name.Utf8.CalculateSize().X + Im.Style.ItemSpacing.X * 2 + i.ShortId.CalculateSize().X);
}
} }

View file

@ -1,4 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Config;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using ImSharp; using ImSharp;
@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.SettingsTab;
public class CollectionOverrideDrawer( public class CollectionOverrideDrawer(
CollectionOverrideService collectionOverrides, CollectionOverrideService collectionOverrides,
Configuration.Configuration config, Configuration config,
ActorObjectManager objects, ActorObjectManager objects,
ActorManager actors, ActorManager actors,
PenumbraService penumbra, PenumbraService penumbra,
@ -58,18 +59,15 @@ public class CollectionOverrideDrawer(
DrawActorIdentifier(idx, actor); DrawActorIdentifier(idx, actor);
table.NextColumn(); table.NextColumn();
if (combo.Draw("##collection", name, "Select the overriding collection. Current GUID:", Im.ContentRegion.Available.X, if (combo.Draw("##collection"u8, name, out var newName, ref collection, Im.ContentRegion.Available.X))
Im.Style.TextHeight)) collectionOverrides.ChangeOverride(idx, collection, newName);
{
var (guid, _, newName) = combo.CurrentSelection;
collectionOverrides.ChangeOverride(idx, guid, newName);
}
if (Im.Item.Hovered()) if (Im.Item.Hovered())
{ {
using var tt = Im.Tooltip.Begin(); using var tt = Im.Tooltip.Begin();
using var font = Im.Font.PushMono(); Im.Text("Select the overriding collection. Current GUID:"u8);
Im.Text($" {collection}"); using var indent = Im.Indent();
ImEx.MonoText($"{collection}");
} }
table.NextColumn(); table.NextColumn();

View file

@ -2,7 +2,7 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Configuration; using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.DesignTab;
@ -15,8 +15,8 @@ using Luna;
namespace Glamourer.Gui.Tabs.SettingsTab; namespace Glamourer.Gui.Tabs.SettingsTab;
public sealed class SettingsTab( public sealed class SettingsTab(
Configuration.Configuration config, Configuration config,
DesignFileSystemSelector selector, DesignFileSystemDrawer drawer,
ContextMenuService contextMenuService, ContextMenuService contextMenuService,
IUiBuilder uiBuilder, IUiBuilder uiBuilder,
GlamourerChangelog changelog, GlamourerChangelog changelog,
@ -28,7 +28,8 @@ public sealed class SettingsTab(
Glamourer glamourer, Glamourer glamourer,
AutoDesignApplier autoDesignApplier, AutoDesignApplier autoDesignApplier,
AutoRedrawChanged autoRedraw, AutoRedrawChanged autoRedraw,
PcpService pcpService) PcpService pcpService,
IgnoredMods ignoredMods)
: ITab<MainTabType> : ITab<MainTabType>
{ {
private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray();
@ -61,6 +62,7 @@ public sealed class SettingsTab(
DrawInterfaceSettings(); DrawInterfaceSettings();
DrawColorSettings(); DrawColorSettings();
overrides.Draw(); overrides.Draw();
DrawIgnoredMods();
codeDrawer.Draw(); codeDrawer.Draw();
} }
@ -256,7 +258,7 @@ public sealed class SettingsTab(
Im.Line.New(); Im.Line.New();
Im.Text("Show the following panels in their respective tabs:"u8); Im.Text("Show the following panels in their respective tabs:"u8);
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
Configuration.DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v =>
{ {
config.HideDesignPanel = v; config.HideDesignPanel = v;
config.Save(); config.Save();
@ -280,8 +282,8 @@ public sealed class SettingsTab(
Checkbox("Show Unobtained Item Warnings"u8, Checkbox("Show Unobtained Item Warnings"u8,
"Show information whether you have unlocked all items and customizations in your automated design or not."u8, "Show information whether you have unlocked all items and customizations in your automated design or not."u8,
config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v);
Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, Checkbox("Show Color Display Configuration"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8,
config.ShowColorConfig, v => config.ShowColorConfig = v); config.ShowColorConfig, v => config.ShowColorConfig = v);
Checkbox("Show Palette+ Import Button"u8, Checkbox("Show Palette+ Import Button"u8,
"Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8,
config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v);
@ -376,7 +378,7 @@ public sealed class SettingsTab(
if (Im.Button("Import Palette+ to Designs"u8)) if (Im.Button("Import Palette+ to Designs"u8))
paletteImport.ImportDesigns(); paletteImport.ImportDesigns();
Im.Tooltip.OnHover( Im.Tooltip.OnHover(
$"Import all existing Palettes from your Palette+ Config into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); $"Import all existing Palettes from your Palette+ Configuration into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}");
} }
/// <summary> Draw the entire Color subsection. </summary> /// <summary> Draw the entire Color subsection. </summary>
@ -402,6 +404,7 @@ public sealed class SettingsTab(
continue; continue;
config.Colors[color] = newColor.Color; config.Colors[color] = newColor.Color;
CacheManager.Instance.SetColorsDirty();
config.Save(); config.Save();
} }
} }
@ -445,20 +448,20 @@ public sealed class SettingsTab(
using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name)) using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name))
{ {
if (combo) if (combo)
foreach (var val in Configuration.Configuration.Constants.ValidSortModes) foreach (var (_, value) in ISortMode.Valid)
{ {
if (Im.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) if (Im.Selectable(value.Name, value.GetType() == sortMode.GetType()) && value.GetType() != sortMode.GetType())
{ {
config.SortMode = val; config.SortMode = value;
selector.SetFilterDirty(); drawer.SortMode = value;
config.Save(); config.Save();
} }
Im.Tooltip.OnHover(val.Description); Im.Tooltip.OnHover(value.Description);
} }
} }
LunaStyle.DrawAlignedHelpMarkerLabel("Sort Mode"u8, "Choose the sort mode for the mod selector in the designs tab."u8); LunaStyle.DrawAlignedHelpMarkerLabel("Sort Mode"u8, "Choose the sort mode for the design selector in the designs tab."u8);
} }
private void DrawRenameSettings() private void DrawRenameSettings()
@ -472,7 +475,6 @@ public sealed class SettingsTab(
if (Im.Selectable(value.ToNameU8(), config.ShowRename == value)) if (Im.Selectable(value.ToNameU8(), config.ShowRename == value))
{ {
config.ShowRename = value; config.ShowRename = value;
selector.SetRenameSearchPath(value);
config.Save(); config.Save();
} }
@ -503,4 +505,46 @@ public sealed class SettingsTab(
LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"u8, LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"u8,
"Select how to display the height of characters in real-world units, if at all."u8); "Select how to display the height of characters in real-world units, if at all."u8);
} }
private string _newIgnoredMod = string.Empty;
private void DrawIgnoredMods()
{
using var header = Im.Tree.HeaderId("Ignored Mods"u8);
Im.Tooltip.OnHover("Add mods that are ignored for the 'modded' column in the Unlocks tab."u8);
if (!header)
return;
using var listBox = Im.ListBox.Begin("##box"u8, new Vector2(0.4f * Im.ContentRegion.Available.X, Im.Style.FrameHeightWithSpacing * 10));
if (!listBox)
return;
var delete = string.Empty;
using var alignment = ImStyleDouble.ButtonTextAlign.PushX(0);
foreach (var (idx, mod) in ignoredMods.Index())
{
using var id = Im.Id.Push(idx);
if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this ignored mod."u8))
delete = mod;
Im.Line.SameInner();
ImEx.TextFramed(mod, Im.ContentRegion.Available with { Y = Im.Style.FrameHeight});
}
if (delete.Length > 0)
ignoredMods.Remove(delete);
var tt = _newIgnoredMod.Length is 0 ? "Please enter a new mod name or mod directory to ignore."u8 :
ignoredMods.Contains(_newIgnoredMod) ? "This mod is already ignored."u8 :
"Ignore all mods with this name or directory in the Unlocks tab."u8;
if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt[0] is not (byte)'I'))
{
ignoredMods.Add(_newIgnoredMod);
_newIgnoredMod = string.Empty;
}
Im.Line.SameInner();
Im.Item.SetNextWidthFull();
Im.Input.Text("##newMod"u8, ref _newIgnoredMod, "Ignore this Mod..."u8);
}
} }

View file

@ -38,7 +38,8 @@ public readonly struct UnlockCacheItem(in EquipItem item, in EquipItem offhand,
public readonly StringPair OffhandModelString = offhand.Valid ? new StringPair(offhand.ModelString) : StringPair.Empty; public readonly StringPair OffhandModelString = offhand.Valid ? new StringPair(offhand.ModelString) : StringPair.Empty;
public readonly StringPair GauntletModelString = gauntlets.Valid ? new StringPair(gauntlets.ModelString) : StringPair.Empty; public readonly StringPair GauntletModelString = gauntlets.Valid ? new StringPair(gauntlets.ModelString) : StringPair.Empty;
public readonly StringPair RequiredLevel = new($"{item.Level.Value}"); public readonly StringPair RequiredLevel = new($"{item.Level.Value}");
public required (string, string)[] Mods { get; init; } public required (string, string)[] Mods { get; init; }
public int RelevantMods { get; init; }
public readonly JobFlag Jobs = jobs.Flags; public readonly JobFlag Jobs = jobs.Flags;
public readonly StringU8 JobText = jobs.Name.IsEmpty ? new StringU8($"Unknown {jobs.Id.Id}") : jobs.Name; public readonly StringU8 JobText = jobs.Name.IsEmpty ? new StringU8($"Unknown {jobs.Id.Id}") : jobs.Name;
public required bool Favorite { get; init; } public required bool Favorite { get; init; }

View file

@ -1,6 +1,7 @@
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility.Table; using Dalamud.Interface.Utility.Table;
using Glamourer.Config;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
@ -23,9 +24,10 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
private readonly FavoriteManager _favorites; private readonly FavoriteManager _favorites;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly ObjectUnlocked _unlockEvent; private readonly ObjectUnlocked _unlockEvent;
private readonly IgnoredMods _ignoredMods;
public UnlockTable(JobService jobs, ItemManager items, ItemUnlockManager unlocks, PenumbraChangedItemTooltip tooltip, public UnlockTable(JobService jobs, ItemManager items, ItemUnlockManager unlocks, PenumbraChangedItemTooltip tooltip,
ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures) ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures, IgnoredMods ignoredMods)
: base(new StringU8("Unlock Table"u8), new FavoriteColumn(favorites), new ModdedColumn(), new NameColumn(textures, tooltip), : base(new StringU8("Unlock Table"u8), new FavoriteColumn(favorites), new ModdedColumn(), new NameColumn(textures, tooltip),
new SlotColumn(), new TypeColumn(), new UnlockDateColumn(), new ItemIdColumn(), new ModelDataColumn(), new JobColumn(jobs), new SlotColumn(), new TypeColumn(), new UnlockDateColumn(), new ItemIdColumn(), new ModelDataColumn(), new JobColumn(jobs),
new RequiredLevelColumn(), new DyableColumn(), new CrestColumn(), new TradableColumn()) new RequiredLevelColumn(), new DyableColumn(), new CrestColumn(), new TradableColumn())
@ -36,6 +38,7 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
_unlockEvent = unlockEvent; _unlockEvent = unlockEvent;
_favorites = favorites; _favorites = favorites;
_penumbra = penumbra; _penumbra = penumbra;
_ignoredMods = ignoredMods;
Flags |= TableFlags.Hideable | TableFlags.Reorderable | TableFlags.Resizable; Flags |= TableFlags.Hideable | TableFlags.Reorderable | TableFlags.Resizable;
} }
@ -72,6 +75,7 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
UnlockTimestamp = unlocked, UnlockTimestamp = unlocked,
Mods = mods, Mods = mods,
Favorite = favorite, Favorite = favorite,
RelevantMods = mods.Count(m => !_ignoredMods.Contains(m.ModName) && !_ignoredMods.Contains(m.ModDirectory)),
}; };
} }
@ -94,24 +98,29 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
=> Im.Style.FrameHeightWithSpacing; => Im.Style.FrameHeightWithSpacing;
public override void DrawColumn(in UnlockCacheItem item, int globalIndex) public override void DrawColumn(in UnlockCacheItem item, int globalIndex)
{ => UiHelpers.DrawFavoriteStar(_favorites, item.Item);
Im.Cursor.FrameAlign();
UiHelpers.DrawFavoriteStar(_favorites, item.Item);
}
protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex)
=> item.Favorite; => item.Favorite;
} }
private sealed class ModdedColumn : YesNoColumn<UnlockCacheItem> private sealed class ModdedColumn : FlagColumn<ModdedColumn.Modded, UnlockCacheItem>
{ {
private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle; [Flags]
public enum Modded
{
Relevant = 1,
Ignored = 2,
None = 4,
}
private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle;
private static readonly AwesomeIcon Hollow = FontAwesomeIcon.DotCircle;
public ModdedColumn() public ModdedColumn()
{ {
Flags |= TableColumnFlags.NoResize; Flags |= TableColumnFlags.NoResize;
Label = new StringU8("M"); Label = new StringU8("M");
FilterLabel = new StringU8("Modded"u8);
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems) public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)
@ -124,8 +133,11 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
using (AwesomeIcon.Font.Push()) using (AwesomeIcon.Font.Push())
{ {
using var color = ImGuiColor.Text.Push(ColorId.ModdedItemMarker.Value()); var (color, text) = item.RelevantMods > 0
ImEx.TextCentered(Dot.Span); ? (ColorId.ModdedItemMarker.Value(), Dot)
: (ColorId.ModdedItemMarker.Value().HalfTransparent(), Hollow);
using var c = ImGuiColor.Text.Push(color);
Im.Text(text.Span);
} }
if (Im.Item.Hovered()) if (Im.Item.Hovered())
@ -137,11 +149,29 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
} }
} }
protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) protected override Modded GetValue(in UnlockCacheItem item, int globalIndex)
=> item.Mods.Length > 0; => item.RelevantMods > 0 ? Modded.Relevant : item.Mods.Length > 0 ? Modded.Ignored : Modded.None;
protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex)
=> StringU8.Empty;
protected override IReadOnlyList<(Modded Value, StringU8 Name)> EnumData
=>
[
(Modded.Relevant, new StringU8("Any Relevant Mods"u8)),
(Modded.Ignored, new StringU8("Only Ignored Mods"u8)),
(Modded.None, new StringU8("Unmodded"u8)),
];
public override int Compare(in UnlockCacheItem lhs, int lhsGlobalIndex, in UnlockCacheItem rhs, int rhsGlobalIndex) public override int Compare(in UnlockCacheItem lhs, int lhsGlobalIndex, in UnlockCacheItem rhs, int rhsGlobalIndex)
=> lhs.Mods.Length.CompareTo(rhs.Mods.Length); {
var relevant = lhs.RelevantMods.CompareTo(rhs.RelevantMods);
if (relevant is not 0)
return relevant;
return lhs.Mods.Length.CompareTo(rhs.Mods.Length);
}
} }
private sealed class NameColumn : TextColumn<UnlockCacheItem> private sealed class NameColumn : TextColumn<UnlockCacheItem>
@ -241,9 +271,9 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
{ {
public UnlockDateColumn() public UnlockDateColumn()
{ {
Flags &= ~TableColumnFlags.NoResize; Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Unlocked"u8); Label = new StringU8("Unlocked"u8);
FilterLabel = Label; FilterLabel = Label;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems) public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)

View file

@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.UnlocksTab;
public sealed class UnlocksTab : Window, ITab<MainTabType> public sealed class UnlocksTab : Window, ITab<MainTabType>
{ {
private readonly Configuration.EphemeralConfig _config; private readonly Config.EphemeralConfig _config;
private readonly UnlockOverview _overview; private readonly UnlockOverview _overview;
private readonly UnlockTable _table; private readonly UnlockTable _table;
public UnlocksTab(Configuration.EphemeralConfig config, UnlockOverview overview, UnlockTable table) public UnlocksTab(Config.EphemeralConfig config, UnlockOverview overview, UnlockTable table)
: base("Unlocked Equipment") : base("Unlocked Equipment")
{ {
_config = config; _config = config;

View file

@ -1,6 +1,7 @@
using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
@ -23,7 +24,7 @@ public class ContextMenuService : IDisposable
private readonly MenuItem _inventoryItem; private readonly MenuItem _inventoryItem;
public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration.Configuration config, public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration config,
IContextMenu context) IContextMenu context)
{ {
_contextMenu = context; _contextMenu = context;

View file

@ -1,5 +1,6 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
@ -24,7 +25,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []); private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []);
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra, public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra,
Configuration.Configuration config) Configuration config)
{ {
_stateManager = stateManager; _stateManager = stateManager;
_actors = actors; _actors = actors;

View file

@ -1,4 +1,5 @@
using Glamourer.Designs.Links; using Glamourer.Config;
using Glamourer.Designs.Links;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Luna; using Luna;
@ -7,14 +8,19 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Penumbra; namespace Glamourer.Interop.Penumbra;
public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration.Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) public class ModSettingApplier(
PenumbraService penumbra,
PenumbraAutoRedrawSkip autoRedrawSkip,
Configuration config,
ActorObjectManager objects,
CollectionOverrideService overrides)
: IService : IService
{ {
private readonly HashSet<Guid> _collectionTracker = []; private readonly HashSet<Guid> _collectionTracker = [];
public void HandleStateApplication(ActorState state, MergedDesign design, StateSource source, bool skipAutoRedraw, bool respectManual) public void HandleStateApplication(ActorState state, MergedDesign design, StateSource source, bool skipAutoRedraw, bool respectManual)
{ {
if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)
return; return;
if (!objects.TryGetValue(state.Identifier, out var data)) if (!objects.TryGetValue(state.Identifier, out var data))
@ -90,6 +96,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip
if (!respectManual && source.IsFixed()) if (!respectManual && source.IsFixed())
penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual); penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual);
} }
return index; return index;
} }
} }

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Api.Enums; using Glamourer.Api.Enums;
using Glamourer.Config;
using Glamourer.Designs.History; using Glamourer.Designs.History;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.State; using Glamourer.State;
@ -11,17 +12,17 @@ namespace Glamourer.Interop.Penumbra;
public class PenumbraAutoRedraw : IDisposable, IRequiredService public class PenumbraAutoRedraw : IDisposable, IRequiredService
{ {
private const int WaitFrames = 5; private const int WaitFrames = 5;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly StateManager _state; private readonly StateManager _state;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly StateChanged _stateChanged; private readonly StateChanged _stateChanged;
private readonly PenumbraAutoRedrawSkip _skip; private readonly PenumbraAutoRedrawSkip _skip;
public PenumbraAutoRedraw(PenumbraService penumbra, Configuration.Configuration config, StateManager state, ActorObjectManager objects, public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ActorObjectManager objects,
IFramework framework, IFramework framework,
StateChanged stateChanged, PenumbraAutoRedrawSkip skip) StateChanged stateChanged, PenumbraAutoRedrawSkip skip)
{ {

View file

@ -1,6 +1,7 @@
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Exceptions;
using Glamourer.Config;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.State; using Glamourer.State;
using Luna; using Luna;
@ -45,7 +46,7 @@ public class PenumbraService : IDisposable
private const string NameManual = "Glamourer (Manually)"; private const string NameManual = "Glamourer (Manually)";
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber; private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber; private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase; private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
@ -96,7 +97,7 @@ public class PenumbraService : IDisposable
public int CurrentMinor { get; private set; } public int CurrentMinor { get; private set; }
public DateTime AttachTime { get; private set; } public DateTime AttachTime { get; private set; }
public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration.Configuration config) public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config)
{ {
_pluginInterface = pi; _pluginInterface = pi;
_penumbraReloaded = penumbraReloaded; _penumbraReloaded = penumbraReloaded;

View file

@ -1,27 +1,9 @@
using Luna; using Luna;
using Backup = OtterGui.Classes.Backup;
using Logger = OtterGui.Log.Logger;
namespace Glamourer.Services; namespace Glamourer.Services;
public class BackupService : IAsyncService public class BackupService(Logger log, FilenameService provider) : BaseBackupService<FilenameService>(log, provider)
{ {
private readonly Logger _logger;
private readonly DirectoryInfo _configDirectory;
private readonly IReadOnlyList<FileInfo> _fileNames;
public BackupService(Logger logger, FilenameService fileNames)
{
_logger = logger;
_fileNames = GlamourerFiles(fileNames);
_configDirectory = new DirectoryInfo(fileNames.ConfigurationDirectory);
Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigurationDirectory), _fileNames));
}
/// <summary> Create a permanent backup with a given name for migrations. </summary>
public void CreateMigrationBackup(string name)
=> Backup.CreatePermanentBackup(_logger, _configDirectory, _fileNames, name);
/// <summary> Collect all relevant files for glamourer configuration. </summary> /// <summary> Collect all relevant files for glamourer configuration. </summary>
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames) private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
{ {
@ -29,7 +11,7 @@ public class BackupService : IAsyncService
{ {
new(fileNames.ConfigurationFile), new(fileNames.ConfigurationFile),
new(fileNames.UiConfiguration), new(fileNames.UiConfiguration),
new(fileNames.DesignFileSystem), new(fileNames.MigrationDesignFileSystem),
new(fileNames.MigrationDesignFile), new(fileNames.MigrationDesignFile),
new(fileNames.AutomationFile), new(fileNames.AutomationFile),
new(fileNames.UnlockFileCustomize), new(fileNames.UnlockFileCustomize),
@ -42,9 +24,4 @@ public class BackupService : IAsyncService
return list; return list;
} }
public Task Awaiter { get; }
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
} }

View file

@ -1,13 +1,13 @@
using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Services; namespace Glamourer.Services;
public class CodeService public class CodeService
{ {
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly SHA256 _hasher = SHA256.Create(); private readonly SHA256 _hasher = SHA256.Create();
[Flags] [Flags]
public enum CodeFlag : ulong public enum CodeFlag : ulong
@ -25,16 +25,17 @@ public class CodeService
OopsAuRa = 0x000400, OopsAuRa = 0x000400,
OopsHrothgar = 0x000800, OopsHrothgar = 0x000800,
OopsViera = 0x001000, OopsViera = 0x001000,
//Artisan = 0x002000, //Artisan = 0x002000,
SixtyThree = 0x004000, SixtyThree = 0x004000,
Shirts = 0x008000, Shirts = 0x008000,
World = 0x010000, World = 0x010000,
Elephants = 0x020000, Elephants = 0x020000,
Crown = 0x040000, Crown = 0x040000,
Dolphins = 0x080000, Dolphins = 0x080000,
Face = 0x100000, Face = 0x100000,
Manderville = 0x200000, Manderville = 0x200000,
Smiles = 0x400000, Smiles = 0x400000,
} }
public static readonly CodeFlag AllHintCodes = public static readonly CodeFlag AllHintCodes =
@ -87,7 +88,7 @@ public class CodeService
_ => Race.Unknown, _ => Race.Unknown,
}; };
public CodeService(Configuration.Configuration config) public CodeService(Configuration config)
{ {
_config = config; _config = config;
Load(); Load();
@ -253,4 +254,3 @@ public class CodeService
_ => (false, 0, string.Empty, string.Empty, string.Empty), _ => (false, 0, string.Empty, string.Empty, string.Empty),
}; };
} }

View file

@ -2,6 +2,7 @@
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Special; using Glamourer.Designs.Special;
using Glamourer.GameData; using Glamourer.GameData;
@ -24,27 +25,27 @@ public class CommandService : IDisposable, IApiService
private const string MainCommandString = "/glamourer"; private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour"; private const string ApplyCommandString = "/glamour";
private readonly ICommandManager _commands; private readonly ICommandManager _commands;
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
private readonly IChatGui _chat; private readonly IChatGui _chat;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
private readonly Configuration.Configuration _config; private readonly Configuration _config;
private readonly ModSettingApplier _modApplier; private readonly ModSettingApplier _modApplier;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly CustomizeService _customizeService; private readonly CustomizeService _customizeService;
private readonly DesignManager _designManager; private readonly DesignManager _designManager;
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly DesignResolver _resolver; private readonly DesignResolver _resolver;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects, public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects,
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration.Configuration config, ModSettingApplier modApplier, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier,
ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemDrawer designDrawer,
QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra)
{ {
_commands = commands; _commands = commands;

Some files were not shown because too many files have changed in this diff Show more