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

View file

@ -1,9 +1,10 @@
using Glamourer.Api.Api;
using Glamourer.Config;
using Luna;
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 CurrentApiVersionMinor = 7;

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events;
@ -16,22 +17,22 @@ namespace Glamourer.Automation;
public sealed class AutoDesignApplier : IDisposable
{
private readonly Configuration.Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly ActorObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly ActorObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
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,
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
{

View file

@ -47,7 +47,7 @@ public class FixedDesignMigrator(JobService jobs)
var set = autoManager[^1];
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.",
NotificationType.Warning);

View file

@ -7,14 +7,16 @@ using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Services;
using ImSharp;
using Luna;
using Luna.Generators;
using Newtonsoft.Json;
using OtterGui.Filesystem;
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]
public readonly EphemeralConfig Ephemeral;
@ -58,8 +60,11 @@ public class Configuration : IPluginConfiguration, ISavable
public DefaultDesignSettings DefaultDesignSettings { get; set; } = new();
public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre;
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre;
[ConfigProperty(EventName = "OnRenameChanged")]
private RenameField _showRename = RenameField.BothDataPrio;
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control);
@ -70,7 +75,7 @@ public class Configuration : IPluginConfiguration, ISavable
[JsonConverter(typeof(SortModeConverter))]
[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; } = [];
@ -80,7 +85,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool DebugMode { get; set; } = false;
#endif
public int Version { get; set; } = Constants.CurrentVersion;
public int Version { get; set; } = CurrentVersion;
public Dictionary<ColorId, uint> Colors { get; private set; }
= ColorId.Values.ToDictionary(c => c, c => c.Data().DefaultColor);
@ -142,45 +147,22 @@ public class Configuration : IPluginConfiguration, ISavable
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>
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);
}
public override ISortMode<Design> ReadJson(JsonReader reader, Type objectType, ISortMode<Design>? existingValue,
bool hasExistingValue,
public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
var name = serializer.Deserialize<string>(reader);
if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode))
return existingValue ?? ISortMode<Design>.FoldersFirst;
if (serializer.Deserialize<string>(reader) is { } name)
return ISortMode.Valid.GetValueOrDefault(name, existingValue ?? ISortMode.FoldersFirst);
return mode;
return existingValue ?? ISortMode.FoldersFirst;
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
using ImSharp;
using Luna.Generators;
namespace Glamourer.Configuration;
namespace Glamourer.Config;
[Flags]
[NamedEnum(Utf16: false)]
@ -40,9 +40,9 @@ public enum DesignPanelFlag : uint
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))
return default;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,195 +3,64 @@ using Glamourer.Designs.History;
using Glamourer.Events;
using Glamourer.Services;
using Luna;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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;
private readonly DesignManager _designManager;
public DesignFileSystem(DesignManager designManager, SaveService saveService, DesignChanged designChanged)
public DesignFileSystem(Logger log, SaveService saveService, DesignStorage designs, DesignChanged designChanged)
: base("DesignFileSystem", log, true)
{
_designManager = designManager;
_saveService = saveService;
_designChanged = designChanged;
_designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystem);
Changed += OnChange;
Reload();
_saver = new DesignFileSystemSaver(log, this, saveService, designs);
_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))
_saveService.ImmediateSave(this);
switch (type)
{
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()
{
_designChanged.Unsubscribe(OnDesignChange);
}
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);
_designChanged.Unsubscribe(OnDesignChanged);
}
}

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 Glamourer.Config;
using Glamourer.Designs.History;
using Glamourer.Designs.Links;
using Glamourer.Events;
@ -21,7 +22,7 @@ public sealed class DesignManager : DesignEditor
private readonly HumanModelList _humans;
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)
{
Designs = storage;
@ -545,7 +546,6 @@ public sealed class DesignManager : DesignEditor
}
}
DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths);
Glamourer.Log.Information(
$"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.Automation;
using Glamourer.Config;
using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Services;
@ -15,7 +16,7 @@ namespace Glamourer.Designs.Links;
public class DesignMerger(
DesignManager designManager,
CustomizeService customizeService,
Configuration.Configuration config,
Configuration config,
ItemUnlockManager itemUnlocks,
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;
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 WeakReference<Design> _lastDesign = new(null!, false);

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
@ -61,7 +62,7 @@ public class Glamourer : IDalamudPlugin
public string GatherSupportInformation()
{
var sb = new StringBuilder(10240);
var config = _services.GetService<Configuration.Configuration>();
var config = _services.GetService<Configuration>();
sb.AppendLine("**Settings**");
sb.Append($"> **`Plugin Version: `** {Version}\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>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName>
@ -22,6 +22,11 @@
<EmbeddedResource Include="LegacyTattoo.raw" />
</ItemGroup>
<PropertyGroup>
<Use_DalamudPackager>false</Use_DalamudPackager>
<Use_Dalamud_ImGui>false</Use_Dalamud_ImGui>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
<ProjectReference Include="..\Luna\Luna\Luna.csproj" />
@ -31,7 +36,8 @@
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<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>
@ -50,7 +56,8 @@
</ItemGroup>
<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="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" />
</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;
namespace Glamourer.Gui;
@ -83,6 +84,6 @@ public static class Colors
=> _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor;
/// <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;
}

View file

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

View file

@ -1,6 +1,7 @@
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.Unlocks;
@ -14,7 +15,7 @@ namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer(
ITextureProvider textures,
CustomizeService service,
Configuration.Configuration config,
Configuration config,
FavoriteManager favorites,
HeightService heightService)
: IDisposable
@ -81,7 +82,7 @@ public partial class CustomizationDrawer(
private CustomizeValue _currentByte = CustomizeValue.Zero;
private bool _currentApply;
private int _currentCount;
private StringU8 _currentOption = StringU8.Empty;
private StringU8 _currentOption = StringU8.Empty;
// Prepare a new customization option.
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.Interop.PalettePlus;
using Glamourer.State;
@ -7,7 +8,7 @@ using Luna;
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 StringU8 _paletteName = StringU8.Empty;
@ -125,7 +126,7 @@ public class CustomizeParameterDrawer(Configuration.Configuration config, Palett
DrawColorFormatOptions(withApply);
var value = config.ShowColorConfig;
Im.Line.Same();
if (Im.Checkbox("Show Config"u8, ref value))
if (Im.Checkbox("Show Configuration"u8, ref value))
{
config.ShowColorConfig = value;
config.Save();

View file

@ -9,7 +9,7 @@ using Luna;
namespace Glamourer.Gui;
public abstract class DesignComboBase(
Configuration.EphemeralConfig config,
Config.EphemeralConfig config,
DesignManager designs,
DesignChanged designChanged,
DesignColors designColors,
@ -17,18 +17,18 @@ public abstract class DesignComboBase(
DesignFileSystem designFileSystem)
: FilterComboBase<DesignComboBase.CacheItem>(new DesignFilter(), ConfigData.Default with { ComputeWidth = true })
{
protected readonly Configuration.EphemeralConfig Config = config;
protected readonly DesignChanged DesignChanged = designChanged;
protected readonly DesignColors DesignColors = designColors;
protected readonly DesignFileSystem DesignFileSystem = designFileSystem;
protected readonly TabSelected TabSelected = tabSelected;
protected readonly DesignManager Designs = designs;
protected IDesignStandIn? CurrentDesign;
protected readonly Config.EphemeralConfig Config = config;
protected readonly DesignChanged DesignChanged = designChanged;
protected readonly DesignColors DesignColors = designColors;
protected readonly DesignFileSystem DesignFileSystem = designFileSystem;
protected readonly TabSelected TabSelected = tabSelected;
protected readonly DesignManager Designs = designs;
protected IDesignStandIn? CurrentDesign;
protected CacheItem CreateItem(IDesignStandIn design)
{
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);
if (path == name)
path = string.Empty;
@ -118,7 +118,10 @@ public abstract class DesignComboBase(
protected override void ComputeWidth()
=> 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)
{
@ -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)
: 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 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)
: base(config, designs, designChanged, designColors, tabSelected, designFileSystem)
{
@ -295,7 +298,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable
}
public sealed class RandomDesignCombo(
Configuration.EphemeralConfig config,
Config.EphemeralConfig config,
DesignManager designs,
DesignChanged designChanged,
DesignColors designColors,
@ -307,14 +310,10 @@ public sealed class RandomDesignCombo(
{
return exact.Which switch
{
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
? l.Value
: null,
RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g)
? g
: Guid.Empty),
_ => null,
RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value),
RandomPredicate.Exact.Type.Path => Designs.Designs.FirstOrDefault(d => d.Node!.FullPath == exact.Value.Text),
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 _quick;
public SpecialDesignCombo(Configuration.EphemeralConfig config,
public SpecialDesignCombo(Config.EphemeralConfig config,
DesignManager designs,
DesignChanged designChanged,
DesignColors designColors,

View file

@ -2,6 +2,7 @@
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
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;
private readonly Configuration.Configuration _config;
private readonly QuickDesignCombo _designCombo;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly ActorObjectManager _objects;
private readonly PenumbraService _penumbra;
private readonly IKeyState _keyState;
private readonly Im.ColorStyleDisposable _style = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons;
private readonly StringBuilder _tooltipBuilder = new(512);
private readonly Configuration _config;
private readonly QuickDesignCombo _designCombo;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly ActorObjectManager _objects;
private readonly PenumbraService _penumbra;
private readonly IKeyState _keyState;
private readonly Im.ColorStyleDisposable _style = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons;
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)
: base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking)
{

View file

@ -1,4 +1,5 @@
using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.Events;
using Glamourer.Gui.Materials;
using Glamourer.Services;
@ -22,7 +23,7 @@ public class EquipmentDrawer
private readonly BonusItemCombo[] _bonusItemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly TextureService _textures;
private readonly Configuration.Configuration _config;
private readonly Configuration _config;
private readonly GPoseService _gPose;
private readonly AdvancedDyePopup _advancedDyes;
private readonly ItemCopyService _itemCopy;
@ -32,7 +33,7 @@ public class EquipmentDrawer
private EquipSlot _dragTarget;
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;
_textures = textures;

View file

@ -1,17 +1,18 @@
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Glamourer.Config;
using ImSharp;
namespace Glamourer.Gui;
public class GenericPopupWindow : Luna.Window
{
private readonly Configuration.Configuration _config;
private readonly ICondition _condition;
private readonly IClientState _state;
public bool OpenFestivalPopup { get; internal set; }
private readonly Configuration _config;
private readonly ICondition _condition;
private readonly IClientState _state;
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",
WindowFlags.NoBringToFrontOnFocus
| WindowFlags.NoDecoration

View file

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

View file

@ -1,5 +1,6 @@
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
using Glamourer.Config;
using Glamourer.Gui.Tabs.UnlocksTab;
namespace Glamourer.Gui;
@ -11,7 +12,7 @@ public class GlamourerWindowSystem : IDisposable
private readonly MainWindow _ui;
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;
_ui = ui;

View file

@ -14,11 +14,11 @@ namespace Glamourer.Gui;
public sealed class MainTabBar : TabBar<MainTabType>
{
private readonly Configuration.EphemeralConfig _config;
private readonly Config.EphemeralConfig _config;
public readonly TabSelected Event;
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)
: base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug)
{

View file

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

View file

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

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Material;
using ImSharp;
using Luna;
@ -7,7 +8,7 @@ using Penumbra.GameData.Files.MaterialStructs;
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 SpecularStrengthWidth = 125;

View file

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

View file

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

View file

@ -6,10 +6,10 @@ namespace Glamourer.Gui.Tabs.ActorTab;
public sealed class ActorsHeader : SplitButtonHeader
{
private readonly ActorSelection _selection;
private readonly Configuration.EphemeralConfig _config;
private readonly Config.EphemeralConfig _config;
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;
_config = config;

View file

@ -1,4 +1,5 @@
using Glamourer.Automation;
using Glamourer.Config;
using ImSharp;
using Luna;
using Penumbra.GameData.Actors;
@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab;
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 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>
{
private bool _enabled;

View file

@ -1,13 +1,28 @@
using ImSharp;
using Glamourer.Config;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationHeader(Configuration.Configuration config, AutomationSelection selection) : IHeader
public sealed class AutomationHeader : SplitButtonHeader
{
public bool Collapsed
=> false;
private readonly Configuration _config;
private readonly AutomationSelection _selection;
public void Draw(Vector2 size)
=> ImEx.TextFramed(config.Ephemeral.IncognitoMode ? selection.Incognito : selection.Name, size with { Y = Im.Style.FrameHeight });
public AutomationHeader(Configuration config, AutomationSelection selection, IncognitoButton incognito)
{
_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;
namespace Glamourer.Gui.Tabs.AutomationTab;
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,
Configuration.Configuration config)
Configuration config)
{
_config = config;
LeftHeader = new FilterHeader<AutomationCacheItem>(filter, new StringU8("Filter..."u8));

View file

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

View file

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

View file

@ -1,4 +1,5 @@
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Events;
using ImSharp;
using Luna;
@ -8,7 +9,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class SetSelector(
AutomationSelection selection,
Configuration.Configuration config,
Configuration config,
AutoDesignManager manager,
AutomationFilter filter,
ActorObjectManager objects,
@ -54,7 +55,8 @@ public sealed class SetSelector(
var identifier = config.Ephemeral.IncognitoMode ? item.IdentifierIncognito : item.IdentifierString;
var textSize = identifier.CalculateSize();
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);
}

View file

@ -1,11 +1,12 @@
using ImSharp;
using Glamourer.Config;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DebugTab;
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
=> _config.DebugMode;

View file

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

View file

@ -24,7 +24,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
if (!t)
continue;
DrawDesign(design, designFileSystem);
DrawDesign(design);
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize,
design.Application.Meta,
design.WriteProtected());
@ -50,12 +50,12 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
var designs = designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray();
foreach (var design in designs)
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);
}
}
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);
if (design is Design d)
@ -70,8 +70,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy
table.DrawDataPair("Identifier"u8, d.Identifier);
table.NextRow();
table.DrawColumn("Design File System Path"u8);
if (fileSystem is not null)
table.DrawColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known"u8);
table.DrawColumn(d.Path.CurrentPath);
table.NextRow();
table.DrawDataPair("Creation"u8, d.CreationDate);

View file

@ -1,10 +1,11 @@
using Glamourer.State;
using Glamourer.Config;
using Glamourer.State;
using ImSharp;
using Penumbra.GameData.Gui.Debug;
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
=> "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 Glamourer.Configuration;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Services;
using ImSharp;
@ -9,21 +9,19 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignDetailTab
{
private readonly SaveService _saveService;
private readonly Configuration.Configuration _config;
private readonly DesignFileSystemSelector _selector;
private readonly DesignFileSystem _fileSystem;
private readonly DesignManager _manager;
private readonly DesignColors _colors;
private readonly DesignColorCombo _colorCombo;
private readonly SaveService _saveService;
private readonly Configuration _config;
private readonly DesignFileSystem _fileSystem;
private readonly DesignManager _manager;
private readonly DesignColors _colors;
private readonly DesignColorCombo _colorCombo;
private bool _editDescriptionMode;
public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem,
DesignColors colors, Configuration.Configuration config)
public DesignDetailTab(SaveService saveService, DesignManager manager, DesignFileSystem fileSystem,
DesignColors colors, Configuration config)
{
_saveService = saveService;
_selector = selector;
_manager = manager;
_fileSystem = fileSystem;
_colors = colors;
@ -42,6 +40,8 @@ public class DesignDetailTab
Im.Line.New();
}
private Design Selected
=> (Design) _fileSystem.Selection.Selection!.Value;
private void DrawDesignInfoTable()
{
@ -57,13 +57,13 @@ public class DesignDetailTab
table.NextColumn();
var width = Im.ContentRegion.Available with { Y = 0 };
Im.Item.SetNextWidth(width.X);
if (ImEx.InputOnDeactivation.Text("##Name"u8, _selector.Selected!.Name.Text, out string newName))
_manager.Rename(_selector.Selected!, newName);
if (ImEx.InputOnDeactivation.Text("##Name"u8, Selected.Name.Text, out string newName))
_manager.Rename(Selected, newName);
var identifier = _selector.Selected!.Identifier.ToString();
var identifier = Selected.Identifier.ToString();
table.DrawFrameColumn("Unique Identifier"u8);
table.NextColumn();
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!);
var fileName = _saveService.FileNames.DesignFile(Selected);
using (Im.Font.PushMono())
{
if (Im.Button(identifier, width))
@ -87,10 +87,10 @@ public class DesignDetailTab
table.DrawFrameColumn("Full Selector Path"u8);
table.NextColumn();
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
{
_fileSystem.RenameAndMove(_selector.SelectedLeaf, newPath);
_fileSystem.RenameAndMove(Selected.Node!, newPath);
}
catch (Exception ex)
{
@ -99,56 +99,56 @@ public class DesignDetailTab
table.DrawFrameColumn("Quick Design Bar"u8);
table.NextColumn();
if (Im.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, true);
if (Im.RadioButton("Display##qdb"u8, Selected.QuickDesign))
_manager.SetQuickDesign(Selected, true);
var hovered = Im.Item.Hovered();
Im.Line.SameInner();
if (Im.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, false);
if (Im.RadioButton("Hide##qdb"u8, !Selected.QuickDesign))
_manager.SetQuickDesign(Selected, false);
if (hovered || Im.Item.Hovered())
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.NextColumn();
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);
var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes;
var resetAdvancedDyes = Selected.ResetAdvancedDyes;
table.DrawFrameColumn("Reset Advanced Dyes"u8);
table.NextColumn();
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);
var resetTemporarySettings = _selector.Selected!.ResetTemporarySettings;
var resetTemporarySettings = Selected.ResetTemporarySettings;
table.DrawFrameColumn("Reset Temporary Settings"u8);
table.NextColumn();
if (Im.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings))
_manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings);
_manager.ChangeResetTemporarySettings(Selected, resetTemporarySettings);
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);
table.DrawFrameColumn("Color"u8);
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
+ "Right-Click to revert to automatic coloring.\n"u8
+ "Hold Control and scroll the mousewheel to scroll."u8,
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())
_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();
if (DesignColorUi.DrawColorButton($"Color associated with {_selector.Selected!.Color}", currentColor, out var newColor))
_colors.SetColor(_selector.Selected!.Color, newColor);
if (DesignColorUi.DrawColorButton($"Color associated with {Selected.Color}", currentColor, out var newColor))
_colors.SetColor(Selected.Color, newColor);
}
else if (_selector.Selected!.Color.Length != 0)
else if (Selected.Color.Length is not 0)
{
Im.Line.Same();
ImEx.Icon.Draw(LunaStyle.WarningIcon, _colors.MissingColor);
@ -157,11 +157,11 @@ public class DesignDetailTab
table.DrawFrameColumn("Creation Date"u8);
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.NextColumn();
ImEx.TextFramed($"{_selector.Selected!.LastEdit.LocalDateTime:F}", width, 0);
ImEx.TextFramed($"{Selected.LastEdit.LocalDateTime:F}", width, 0);
table.DrawFrameColumn("Tags"u8);
table.NextColumn();
@ -170,26 +170,26 @@ public class DesignDetailTab
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)
return;
if (idx < _selector.Selected!.Tags.Length)
if (idx < Selected.Tags.Length)
{
if (editedTag.Length is 0)
_manager.RemoveTag(_selector.Selected!, idx);
_manager.RemoveTag(Selected, idx);
else
_manager.RenameTag(_selector.Selected!, idx, editedTag);
_manager.RenameTag(Selected, idx, editedTag);
}
else
{
_manager.AddTag(_selector.Selected!, editedTag);
_manager.AddTag(Selected, editedTag);
}
}
private void DrawDescription()
{
var desc = _selector.Selected!.Description;
var desc = Selected.Description;
var size = Im.ContentRegion.Available with { Y = 12 * Im.Style.TextHeightWithSpacing };
if (!_editDescriptionMode)
{
@ -205,7 +205,7 @@ public class DesignDetailTab
else
{
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))
_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;
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);
RightButtons.AddButton(new LockedButton(selection), 100);
_fileSystem = fileSystem;
_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
=> selection.Design is not null;
=> fileSystem.Selection.Selection is not null;
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 Glamourer.Automation;
using Glamourer.Configuration;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using ImSharp;
@ -10,16 +10,19 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignLinkDrawer(
DesignLinkManager linkManager,
DesignFileSystemSelector selector,
DesignFileSystem fileSystem,
LinkDesignCombo combo,
DesignColors colorManager,
Configuration.Configuration config) : IUiService
Configuration config) : IUiService
{
private int _dragDropIndex = -1;
private LinkOrder _dragDropOrder = LinkOrder.None;
private int _dragDropTargetIndex = -1;
private LinkOrder _dragDropTargetOrder = LinkOrder.None;
private Design Selected
=> (Design)fileSystem.Selection.Selection!.Value;
public void Draw()
{
using var h = DesignPanelFlag.DesignLinks.Header(config);
@ -45,23 +48,23 @@ public class DesignLinkDrawer(
switch (_dragDropTargetOrder)
{
case LinkOrder.Before:
for (var i = selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i)
linkManager.MoveDesignLink(selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After);
for (var i = Selected.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i)
linkManager.MoveDesignLink(Selected, i, LinkOrder.Before, 0, LinkOrder.After);
break;
case LinkOrder.After:
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);
}
break;
}
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);
else
linkManager.MoveDesignLink(selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
linkManager.MoveDesignLink(Selected, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
_dragDropIndex = -1;
_dragDropTargetIndex = -1;
@ -81,9 +84,9 @@ public class DesignLinkDrawer(
6 * Im.Style.FrameHeight + 5 * Im.Style.ItemInnerSpacing.X);
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);
DrawSubList(table, selector.Selected!.Links.After, LinkOrder.After);
DrawSubList(table, Selected.Links.After, LinkOrder.After);
DrawNew(table);
MoveLink();
}
@ -92,7 +95,7 @@ public class DesignLinkDrawer(
{
using var id = Im.Id.Push((int)LinkOrder.Self);
table.NextColumn();
var color = colorManager.GetColor(selector.Selected!);
var color = colorManager.GetColor(Selected);
using (AwesomeIcon.Font.Push())
{
using var c = ImGuiColor.Text.Push(color);
@ -104,11 +107,11 @@ public class DesignLinkDrawer(
using (ImGuiColor.Text.Push(color))
{
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);
DrawDragDrop(selector.Selected!, LinkOrder.Self, 0);
DrawDragDrop(Selected, LinkOrder.Self, 0);
table.NextColumn();
using (AwesomeIcon.Font.Push())
{
@ -144,7 +147,7 @@ public class DesignLinkDrawer(
DrawApplicationBoxes(i, order, flags);
if (delete)
linkManager.RemoveDesignLink(selector.Selected!, i--, order);
linkManager.RemoveDesignLink(Selected, i--, order);
}
}
@ -164,11 +167,11 @@ public class DesignLinkDrawer(
}
else
{
canAddBefore = LinkContainer.CanAddLink(selector.Selected!, design, LinkOrder.Before, out var error);
canAddBefore = LinkContainer.CanAddLink(Selected, design, LinkOrder.Before, out var error);
ttBefore = canAddBefore
? $"Add a link at the top of the list to {design.Name}."
: $"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
? $"Add a link at the bottom of the list to {design.Name}."
: $"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))
{
linkManager.AddDesignLink(selector.Selected!, design!, LinkOrder.Before);
linkManager.MoveDesignLink(selector.Selected!, selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
linkManager.AddDesignLink(Selected, design!, LinkOrder.Before);
linkManager.MoveDesignLink(Selected, Selected.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
}
Im.Line.Same();
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)
@ -228,7 +231,7 @@ public class DesignLinkDrawer(
Im.Line.Same();
Box(4);
if (newType != current)
linkManager.ChangeApplicationType(selector.Selected!, idx, order, newType);
linkManager.ChangeApplicationType(Selected, idx, order, newType);
return;
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 FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Api.Enums;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.GameData;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Materials;
using Glamourer.Interop;
using Glamourer.State;
using Dalamud.Bindings.ImGui;
using Glamourer.Configuration;
using ImSharp;
using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using static Glamourer.Gui.Tabs.HeaderDrawer;
namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignPanel : IPanel
{
private readonly FileDialogManager _fileDialog = new();
private readonly DesignSelection _selection;
private readonly CustomizationDrawer _customizationDrawer;
private readonly DesignManager _manager;
private readonly ActorObjectManager _objects;
private readonly StateManager _state;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly ModAssociationsTab _modAssociations;
private readonly Configuration.Configuration _config;
private readonly DesignDetailTab _designDetails;
private readonly ImportService _importService;
private readonly DesignConverter _converter;
private readonly MultiDesignPanel _multiDesignPanel;
private readonly CustomizeParameterDrawer _parameterDrawer;
private readonly DesignLinkDrawer _designLinkDrawer;
private readonly MaterialDrawer _materials;
private readonly EditorHistory _history;
private readonly Button[] _leftButtons;
private readonly Button[] _rightButtons;
private readonly FileDialogManager _fileDialog = new();
private readonly CustomizationDrawer _customizationDrawer;
private readonly DesignFileSystem _fileSystem;
private readonly DesignManager _manager;
private readonly ActorObjectManager _objects;
private readonly StateManager _state;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly ModAssociationsTab _modAssociations;
private readonly Configuration _config;
private readonly DesignDetailTab _designDetails;
private readonly ImportService _importService;
private readonly MultiDesignPanel _multiDesignPanel;
private readonly CustomizeParameterDrawer _parameterDrawer;
private readonly DesignLinkDrawer _designLinkDrawer;
private readonly MaterialDrawer _materials;
public DesignPanel(CustomizationDrawer customizationDrawer,
@ -54,7 +43,7 @@ public class DesignPanel : IPanel
StateManager state,
EquipmentDrawer equipmentDrawer,
ModAssociationsTab modAssociations,
Configuration.Configuration config,
Configuration config,
DesignDetailTab designDetails,
DesignConverter converter,
ImportService importService,
@ -62,7 +51,7 @@ public class DesignPanel : IPanel
CustomizeParameterDrawer parameterDrawer,
DesignLinkDrawer designLinkDrawer,
MaterialDrawer materials,
EditorHistory history, DesignSelection selection)
DesignFileSystem fileSystem)
{
_customizationDrawer = customizationDrawer;
_manager = manager;
@ -73,33 +62,16 @@ public class DesignPanel : IPanel
_config = config;
_designDetails = designDetails;
_importService = importService;
_converter = converter;
_multiDesignPanel = multiDesignPanel;
_parameterDrawer = parameterDrawer;
_designLinkDrawer = designLinkDrawer;
_materials = materials;
_history = history;
_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),
];
_fileSystem = fileSystem;
}
private void DrawHeader()
=> HeaderDrawer.Draw(SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons);
private string SelectionName
=> _selection.Design == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selection.Design.Incognito : _selection.Design.Name.Text;
private Design Selection
=> (Design)_fileSystem.Selection.Selection!.Value;
private void DrawEquipment()
{
@ -109,22 +81,22 @@ public class DesignPanel : IPanel
_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)
{
var data = EquipDrawData.FromDesign(_manager, _selection.Design!, slot);
var data = EquipDrawData.FromDesign(_manager, Selection, slot);
_equipmentDrawer.DrawEquip(data);
if (usedAllStain)
_manager.ChangeStains(_selection.Design, slot, newAllStain);
_manager.ChangeStains(Selection, slot, newAllStain);
}
var mainhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.MainHand);
var offhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.OffHand);
var mainhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.MainHand);
var offhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.OffHand);
_equipmentDrawer.DrawWeapons(mainhand, offhand, true);
foreach (var slot in BonusExtensions.AllFlags)
{
var data = BonusDrawData.FromDesign(_manager, _selection.Design!, slot);
var data = BonusDrawData.FromDesign(_manager, Selection, slot);
_equipmentDrawer.DrawBonusItem(data);
}
@ -136,30 +108,30 @@ public class DesignPanel : IPanel
private void DrawEquipmentMetaToggles()
{
using (var _ = ImRaii.Group())
using (Im.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, Selection));
}
Im.Line.Same();
using (var _ = ImRaii.Group())
using (Im.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, Selection));
}
Im.Line.Same();
using (var _ = ImRaii.Group())
using (Im.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selection.Design!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, Selection));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, Selection));
}
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;
var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
using var h = Im.Tree.HeaderId(_selection.Design!.DesignData.ModelId is 0
? "Customization"
: $"Customization (Model Id #{_selection.Design!.DesignData.ModelId})###Customization",
using var h = Im.Tree.HeaderId(Selection.DesignData.ModelId is 0
? "Customization"u8
: $"Customization (Model Id #{Selection.DesignData.ModelId})###Customization",
expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None);
if (!h)
return;
if (_customizationDrawer.Draw(_selection.Design!.DesignData.Customize, _selection.Design.Application.Customize,
_selection.Design!.WriteProtected(), false))
if (_customizationDrawer.Draw(Selection.DesignData.Customize, Selection.Application.Customize,
Selection.WriteProtected(), false))
foreach (var idx in CustomizeIndex.Values)
{
var flag = idx.ToFlag();
var newValue = _customizationDrawer.ChangeApply.HasFlag(flag);
_manager.ChangeApplyCustomize(_selection.Design, idx, newValue);
_manager.ChangeApplyCustomize(Selection, idx, newValue);
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));
}
@ -197,7 +169,7 @@ public class DesignPanel : IPanel
if (!h)
return;
_parameterDrawer.Draw(_manager, _selection.Design!);
_parameterDrawer.Draw(_manager, Selection);
}
private void DrawMaterialValues()
@ -206,52 +178,52 @@ public class DesignPanel : IPanel
if (!h)
return;
_materials.Draw(_selection.Design!);
_materials.Draw(Selection);
}
private void DrawCustomizeApplication()
{
using var id = ImUtf8.PushId("Customizations"u8);
var set = _selection.Design!.CustomizeSet;
using var id = Im.Id.Push("Customizations"u8);
var set = Selection.CustomizeSet;
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType;
var flags = _selection.Design!.ApplyCustomizeExcludingBodyType == 0 ? 0 :
(_selection.Design!.ApplyCustomize & available) == available ? 3 : 1;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
var flags = Selection.ApplyCustomizeExcludingBodyType is 0 ? 0ul :
(Selection.ApplyCustomize & available) == available ? 3ul : 1ul;
if (Im.Checkbox("Apply All Customizations"u8, ref flags, 3ul))
{
var newFlags = flags == 3;
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, newFlags);
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, newFlags);
var newFlags = flags is 3;
_manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, newFlags);
_manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, newFlags);
foreach (var index in CustomizationExtensions.AllBasic)
_manager.ChangeApplyCustomize(_selection.Design!, index, newFlags);
_manager.ChangeApplyCustomize(Selection, index, newFlags);
}
var applyClan = _selection.Design!.DoApplyCustomize(CustomizeIndex.Clan);
if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan))
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, applyClan);
var applyClan = Selection.DoApplyCustomize(CustomizeIndex.Clan);
if (Im.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan))
_manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, applyClan);
var applyGender = _selection.Design!.DoApplyCustomize(CustomizeIndex.Gender);
if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender))
_manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, applyGender);
var applyGender = Selection.DoApplyCustomize(CustomizeIndex.Gender);
if (Im.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender))
_manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, applyGender);
foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable))
{
var apply = _selection.Design!.DoApplyCustomize(index);
if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply))
_manager.ChangeApplyCustomize(_selection.Design!, index, apply);
var apply = Selection.DoApplyCustomize(index);
if (Im.Checkbox($"Apply {set.Option(index)}", ref apply))
_manager.ChangeApplyCustomize(Selection, index, apply);
}
}
private void DrawCrestApplication()
{
using var id = ImUtf8.PushId("Crests"u8);
var flags = (uint)_selection.Design!.Application.Crest;
var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant);
using var id = Im.Id.Push("Crests"u8);
var flags = (ulong)Selection.Application.Crest;
var bigChange = Im.Checkbox("Apply All Crests"u8, ref flags, (ulong)CrestExtensions.AllRelevant);
foreach (var flag in CrestExtensions.AllRelevantSet)
{
var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selection.Design!.DoApplyCrest(flag);
if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange)
_manager.ChangeApplyCrest(_selection.Design!, flag, apply);
var apply = bigChange ? ((CrestFlag)flags & flag) == flag : Selection.DoApplyCrest(flag);
if (Im.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange)
_manager.ChangeApplyCrest(Selection, flag, apply);
}
}
@ -261,63 +233,59 @@ public class DesignPanel : IPanel
if (!h)
return;
using var disabled = Im.Disabled(_selection.Design!.WriteProtected());
using var disabled = Im.Disabled(Selection.WriteProtected());
DrawAllButtons();
using (var _ = ImUtf8.Group())
using (Im.Group())
{
DrawCustomizeApplication();
ImUtf8.IconDummy();
Im.FrameDummy();
DrawCrestApplication();
ImUtf8.IconDummy();
Im.FrameDummy();
DrawMetaApplication();
}
ImGui.SameLine(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X);
using (var _ = ImRaii.Group())
Im.Line.Same(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X);
using (Im.Group())
{
void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots)
{
var flags = (uint)(allFlags & _selection.Design!.Application.Equip);
using var id = ImUtf8.PushId(label);
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags);
var flags = (ulong)(allFlags & Selection.Application.Equip);
using var id = Im.Id.Push(label);
var bigChange = Im.Checkbox($"Apply All {label}", ref flags, (ulong)allFlags);
if (stain)
foreach (var slot in slots)
{
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selection.Design!.DoApplyStain(slot);
if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange)
_manager.ChangeApplyStains(_selection.Design!, slot, apply);
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : Selection.DoApplyStain(slot);
if (Im.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange)
_manager.ChangeApplyStains(Selection, slot, apply);
}
else
foreach (var slot in slots)
{
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selection.Design!.DoApplyEquip(slot);
if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
_manager.ChangeApplyItem(_selection.Design!, slot, apply);
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : Selection.DoApplyEquip(slot);
if (Im.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
_manager.ChangeApplyItem(Selection, slot, apply);
}
}
ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[]
{
EquipSlot.MainHand,
EquipSlot.OffHand,
});
ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, [EquipSlot.MainHand, EquipSlot.OffHand]);
ImUtf8.IconDummy();
Im.FrameDummy();
ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
ImUtf8.IconDummy();
Im.FrameDummy();
ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
ImUtf8.IconDummy();
Im.FrameDummy();
ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true,
EquipSlotExtensions.FullSlots);
ImUtf8.IconDummy();
Im.FrameDummy();
DrawParameterApplication();
ImUtf8.IconDummy();
Im.FrameDummy();
DrawBonusSlotApplication();
}
}
@ -327,9 +295,9 @@ public class DesignPanel : IPanel
var enabled = _config.DeleteDesignModifier.IsActive();
bool? equip = null;
bool? customize = null;
var size = new Vector2(210 * Im.Style.GlobalScale, 0);
if (ImUtf8.ButtonEx("Disable Everything"u8,
"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size,
var size = ImEx.ScaledVectorX(210);
if (ImEx.Button("Disable Everything"u8, size,
"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8,
!enabled))
{
equip = false;
@ -337,11 +305,11 @@ public class DesignPanel : IPanel
}
if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same();
if (ImUtf8.ButtonEx("Enable Everything"u8,
"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size,
if (ImEx.Button("Enable Everything"u8, size,
"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8,
!enabled))
{
equip = true;
@ -349,10 +317,10 @@ public class DesignPanel : IPanel
}
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,
"Enable application of anything related to gear, disable anything that is not related to gear."u8, size,
if (ImEx.Button("Equipment Only"u8, size,
"Enable application of anything related to gear, disable anything that is not related to gear."u8,
!enabled))
{
equip = true;
@ -360,11 +328,11 @@ public class DesignPanel : IPanel
}
if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same();
if (ImUtf8.ButtonEx("Customization Only"u8,
"Enable application of anything related to customization, disable anything that is not related to customization."u8, size,
if (ImEx.Button("Customization Only"u8, size,
"Enable application of anything related to customization, disable anything that is not related to customization."u8,
!enabled))
{
equip = false;
@ -372,85 +340,82 @@ public class DesignPanel : IPanel
}
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,
size,
!enabled))
{
_manager.ChangeApplyMulti(_selection.Design!, true, true, true, false, true, true, false, true);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, false);
_manager.ChangeApplyMulti(Selection, true, true, true, false, true, true, false, true);
_manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, false);
}
if (!enabled)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking.");
Im.Line.Same();
if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8,
size,
!enabled))
_manager.ChangeApplyMulti(_selection.Design!, null, null, null, false, null, null, false, null);
if (ImEx.Button("Disable Advanced"u8, size, "Disable all advanced dyes and customizations but keep everything else as is."u8, !enabled))
_manager.ChangeApplyMulti(Selection, null, null, null, false, null, null, false, null);
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)
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);
if (equip.HasValue)
{
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.HatState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.VisorState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.WeaponState, equip.Value);
_manager.ChangeApplyMeta(_selection.Design!, MetaIndex.EarState, equip.Value);
_manager.ChangeApplyMeta(Selection, MetaIndex.HatState, equip.Value);
_manager.ChangeApplyMeta(Selection, MetaIndex.VisorState, equip.Value);
_manager.ChangeApplyMeta(Selection, MetaIndex.WeaponState, equip.Value);
_manager.ChangeApplyMeta(Selection, MetaIndex.EarState, equip.Value);
}
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",
"Apply Hat Visibility",
"Apply Visor State",
"Apply Weapon Visibility",
"Apply Viera Ear Visibility",
new("Apply Wetness"u8),
new("Apply Hat Visibility"u8),
new("Apply Visor State"u8),
new("Apply Weapon Visibility"u8),
new("Apply Viera Ear Visibility"u8),
];
private void DrawMetaApplication()
{
using var id = ImUtf8.PushId("Meta");
const uint all = (uint)MetaExtensions.All;
var flags = (uint)_selection.Design!.Application.Meta;
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
using var id = Im.Id.Push("Meta"u8);
const ulong all = (ulong)MetaExtensions.All;
var flags = (ulong)Selection.Application.Meta;
var bigChange = Im.Checkbox("Apply All Meta Changes"u8, ref flags, all);
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels))
{
var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selection.Design!.DoApplyMeta(index);
if (ImUtf8.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyMeta(_selection.Design!, index, apply);
var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : Selection.DoApplyMeta(index);
if (Im.Checkbox(label, ref apply) || bigChange)
_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()
{
using var id = ImUtf8.PushId("Bonus"u8);
var flags = _selection.Design!.Application.BonusItem;
var bigChange = BonusExtensions.AllFlags.Count > 1 && ImUtf8.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All);
using var id = Im.Id.Push("Bonus"u8);
var flags = Selection.Application.BonusItem;
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))
{
var apply = bigChange ? flags.HasFlag(index) : _selection.Design!.DoApplyBonusItem(index);
if (ImUtf8.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyBonusItem(_selection.Design!, index, apply);
var apply = bigChange ? flags.HasFlag(index) : Selection.DoApplyBonusItem(index);
if (Im.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyBonusItem(Selection, index, apply);
}
}
@ -458,69 +423,62 @@ public class DesignPanel : IPanel
private void DrawParameterApplication()
{
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);
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)
_manager.ChangeApplyParameter(_selection.Design!, flag, apply);
_manager.ChangeApplyParameter(Selection, flag, apply);
}
}
public ReadOnlySpan<byte> Id
=> "Designs"u8;
=> "DesignPanel"u8;
public void Draw()
{
using var group = ImUtf8.Group();
//if (_selection.DesignPaths.Count > 1)
if (false)
_importService.CreateDatSource();
if (_fileSystem.Selection.OrderedNodes.Count > 1)
{
_multiDesignPanel.Draw();
return;
}
else
DrawPanel();
if (_fileSystem.Selection.Selection is null || Selection.WriteProtected())
return;
if (_importService.CreateDatTarget(out var dat))
{
DrawHeader();
DrawPanel();
if (_selection.Design == null || _selection.Design.WriteProtected())
return;
if (_importService.CreateDatTarget(out var dat))
{
_manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]);
_manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]);
foreach (var idx in CustomizationExtensions.AllBasic)
_manager.ChangeCustomize(_selection.Design!, idx, dat.Customize[idx]);
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);
}
_manager.ChangeCustomize(Selection, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]);
_manager.ChangeCustomize(Selection, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]);
foreach (var idx in CustomizationExtensions.AllBasic)
_manager.ChangeCustomize(Selection, idx, dat.Customize[idx]);
Glamourer.Messager.NotificationMessage(
$"Applied games .dat file {dat.Description} customizations to {Selection.Name}.", NotificationType.Success, false);
}
else if (_importService.CreateCharaTarget(out var designBase, out var name))
{
_manager.ApplyDesign(Selection, designBase);
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {Selection.Name}.",
NotificationType.Success, false);
}
_importService.CreateDatSource();
}
private void DrawPanel()
{
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available);
if (!table || _selection.Design is null)
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.ScrollY, Im.ContentRegion.Available);
if (!table || _fileSystem.Selection.Selection is null)
return;
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableNextColumn();
if (_selection.Design is null)
return;
table.SetupScrollFreeze(0, 1);
table.NextColumn();
Im.Dummy(Vector2.Zero);
DrawButtonRow();
ImGui.TableNextColumn();
table.NextColumn();
DrawCustomize();
DrawEquipment();
@ -547,15 +505,15 @@ public class DesignPanel : IPanel
private void DrawApplyToSelf()
{
var (id, data) = _objects.PlayerData;
if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero,
"Apply the current design with its settings to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.",
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."u8,
!data.Valid))
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true });
using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true });
}
}
@ -564,33 +522,33 @@ public class DesignPanel : IPanel
var (id, data) = _objects.TargetData;
var tt = id.IsValid
? 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."
: "The current target can not be manipulated."
: "No valid target selected.";
if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !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."u8
: "The current target can not be manipulated."u8
: "No valid target selected."u8;
if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, !data.Valid))
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true });
using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true });
}
}
private void DrawSaveToDat()
{
var verified = _importService.Verify(_selection.Design!.DesignData.Customize, out _);
var verified = _importService.Verify(Selection.DesignData.Customize, out _);
var tt = verified
? "Export the currently configured customizations of this design to a character creation data file."
: "The current design contains customizations that can not be applied during character creation.";
? "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."u8;
var startPath = GetUserPath();
if (startPath.Length == 0)
if (startPath.Length is 0)
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) =>
{
if (v && _selection.Design != null)
_importService.SaveDesignAsDat(path, _selection.Design!.DesignData.Customize, _selection.Design!.Name);
if (v && _fileSystem.Selection.Selection?.GetValue<Design>() is not null)
_importService.SaveDesignAsDat(path, Selection.DesignData.Customize, Selection.Name);
}, startPath);
_fileDialog.Draw();
@ -598,165 +556,4 @@ public class DesignPanel : IPanel
private static unsafe string GetUserPath()
=> 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 Glamourer.Configuration;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop;
using ImSharp;
@ -7,36 +7,55 @@ using Luna;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignTab(DesignFileSystemSelector selector, DesignPanel panel, ImportService importService, DesignManager manager)
: ITab<MainTabType>
public sealed class DesignTab : TwoPanelLayout, 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;
public MainTabType Identifier
=> MainTabType.Designs;
public void DrawContent()
protected override void DrawLeftGroup(in TwoPanelWidth width)
{
selector.Draw();
if (importService.CreateCharaTarget(out var designBase, out var name))
base.DrawLeftGroup(in width);
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}",
NotificationType.Success, false);
}
Im.Line.Same();
panel.Draw();
importService.CreateCharaSource();
_importService.CreateCharaSource();
}
//protected override void SetWidth(float width, ScalingMode mode)
// => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode);
//
//protected override float MinimumWidth
// => LeftFooter.MinimumWidth;
//
//protected override float MaximumWidth
// => Im.Window.Width - 500 * Im.Style.GlobalScale;
protected override float MinimumWidth
=> LeftFooter.MinimumWidth;
protected override float MaximumWidth
=> Im.Window.Width - 500 * Im.Style.GlobalScale;
protected override void SetWidth(float width, ScalingMode mode)
=> _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 Glamourer.Configuration;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
@ -8,11 +8,14 @@ using Luna;
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 Design Selection
=> (Design)fileSystem.Selection.Selection!.Value;
public void Draw()
{
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);
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();
@ -45,7 +48,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
? $"Add {_copy.Length} mod association(s) from clipboard."
: "Copy some mod associations to the clipboard, first."u8, _copy is null))
foreach (var (mod, setting) in _copy!)
manager.UpdateMod(selection.Design!, mod, setting);
manager.UpdateMod(Selection, mod, setting);
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."
: "Copy some mod associations to the clipboard, first."u8, _copy is null))
{
while (selection.Design!.AssociatedMods.Count > 0)
manager.RemoveMod(selection.Design!, selection.Design!.AssociatedMods.Keys[0]);
while (Selection.AssociatedMods.Count > 0)
manager.RemoveMod(Selection, Selection.AssociatedMods.Keys[0]);
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;
if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero,
$"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();
}
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);
}
@ -104,7 +107,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
Mod? removedMod = 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);
DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp);
@ -117,10 +120,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
DrawNewModRow(table);
if (removedMod.HasValue)
manager.RemoveMod(selection.Design!, removedMod.Value);
manager.RemoveMod(Selection, removedMod.Value);
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,
@ -245,12 +248,12 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select
table.NextColumn();
var tt = currentDir.Length is 0
? "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
: StringU8.Empty;
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();
_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;
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)
{
@ -42,7 +43,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection
=> Im.Style.TextHeightWithSpacing;
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)
{

View file

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

View file

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

View file

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

View file

@ -1,16 +1,12 @@
using Dalamud.Interface;
using Glamourer.Config;
using Glamourer.Services;
using Glamourer.State;
using ImSharp;
using Luna;
using OtterGui.Filesystem;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
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
=> "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()
{
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);
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();
ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, ImGuiColor.TextDisabled.Get().Color);
ImEx.Icon.Draw(LunaStyle.WarningIcon, ImGuiColor.TextDisabled.Get());
DrawTooltip();
}
private void DrawCopyButtons()
{
var buttonSize = new Vector2(250 * Im.Style.GlobalScale, 0);
if (ImUtf8.Button("Who am I?!?"u8, buttonSize))
var buttonSize = ImEx.ScaledVectorX(250);
if (Im.Button("Who am I?!?"u8, buttonSize))
funModule.WhoAmI();
ImUtf8.HoverTooltip(
"Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
Im.Tooltip.OnHover("Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
Im.Line.Same();
if (ImUtf8.Button("Who is that!?!"u8, buttonSize))
if (Im.Button("Who is that!?!"u8, buttonSize))
funModule.WhoIsThat();
ImUtf8.HoverTooltip(
"Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
Im.Tooltip.OnHover("Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
}
private CodeService.CodeFlag DrawCodes()
@ -76,7 +70,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
CodeService.CodeFlag knownFlags = 0;
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 (action, flag) = codeService.CheckCode(code);
if (flag is 0)
@ -84,8 +78,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
var data = CodeService.GetData(flag);
if (ImUtf8.IconButton(FontAwesomeIcon.Trash,
$"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}",
if (ImEx.Icon.Button(LunaStyle.DeleteIcon, $"Delete this cheat code.{(canDelete ? StringU8.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}",
disabled: !canDelete))
{
action!(false);
@ -95,7 +88,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
knownFlags |= flag;
Im.Line.SameInner();
if (ImUtf8.Checkbox("\0"u8, ref state))
if (Im.Checkbox(StringU8.Empty, ref state))
{
action!(state);
codeService.SaveState();
@ -103,14 +96,14 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
var hovered = Im.Item.Hovered();
Im.Line.Same();
ImUtf8.Selectable(code);
Im.Selectable(code);
hovered |= Im.Item.Hovered();
DrawSource(i, code);
DrawTarget(i);
if (hovered)
{
using var tt = ImUtf8.Tooltip();
ImUtf8.Text(data.Effect);
using var tt = Im.Tooltip.Begin();
Im.Text(data.Effect);
}
}
@ -119,22 +112,22 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
private void DrawSource(int idx, string code)
{
using var source = ImUtf8.DragDropSource();
using var source = Im.DragDrop.Source();
if (!source)
return;
if (!DragDropSource.SetPayload(DragDropLabel))
if (!source.SetPayload(DragDropLabel))
_dragCodeIdx = idx;
ImUtf8.Text($"Dragging {code}...");
Im.Text($"Dragging {code}...");
}
private void DrawTarget(int idx)
{
using var target = ImUtf8.DragDropTarget();
if (!target.IsDropping(DragDropLabel) || _dragCodeIdx == -1)
using var target = Im.DragDrop.Target();
if (!target.IsDropping(DragDropLabel) || _dragCodeIdx is -1)
return;
if (Extensions.Move(config.Codes, _dragCodeIdx, idx))
if (config.Codes.Move(_dragCodeIdx, idx))
codeService.SaveState();
_dragCodeIdx = -1;
}
@ -144,7 +137,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
if (knownFlags.HasFlag(CodeService.AllHintCodes))
return;
if (ImUtf8.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8))
if (Im.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8))
_showCodeHints = !_showCodeHints;
if (!_showCodeHints)
@ -162,23 +155,23 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ
Im.Dummy(Vector2.Zero);
Im.Separator();
Im.Dummy(Vector2.Zero);
ImUtf8.Text(data.Effect);
using var indent = ImRaii.PushIndent(2);
using (ImUtf8.Group())
Im.Text(data.Effect);
using var indent = Im.Indent(2);
using (Im.Group())
{
ImUtf8.Text("Capitalized letters: "u8);
ImUtf8.Text("Punctuation: "u8);
Im.Text("Capitalized letters: "u8);
Im.Text("Punctuation: "u8);
}
Im.Line.SameInner();
using (ImUtf8.Group())
using (Im.Group())
{
using var mono = Im.Font.PushMono();
ImUtf8.Text($"{data.CapitalCount}");
ImUtf8.Text($"{data.Punctuation}");
Im.Text($"{data.CapitalCount}");
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;
Im.Window.SetNextSize(new Vector2(400, 0));
using var tt = ImUtf8.Tooltip();
ImUtf8.TextWrapped(Tooltip);
using var tt = Im.Tooltip.Begin();
Im.TextWrapped(Tooltip);
}
}

View file

@ -1,35 +1,82 @@
using Dalamud.Interface;
using Glamourer.Config;
using Glamourer.Interop.Penumbra;
using ImSharp;
using Luna;
using OtterGui.Widgets;
using Logger = OtterGui.Log.Logger;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.SettingsTab;
public sealed class CollectionCombo(Configuration.Configuration config, PenumbraService penumbra, Logger log)
: FilterComboCache<(Guid Id, string IdShort, string Name)>(
() => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(),
MouseWheelType.Control, log), IUiService
public sealed class CollectionCombo(Configuration config, PenumbraService penumbra)
: FilterComboBase<CollectionCombo.CacheItem>(new CollectionFilter()), 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)
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();
using (Im.Font.PushMono())
{
using var color = ImGuiColor.Text.Push(ImGuiColor.TextDisabled.Get());
ImEx.TextRightAligned($"({idShort})");
ImEx.TextRightAligned(item.ShortId);
}
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 Glamourer.Config;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using ImSharp;
@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.SettingsTab;
public class CollectionOverrideDrawer(
CollectionOverrideService collectionOverrides,
Configuration.Configuration config,
Configuration config,
ActorObjectManager objects,
ActorManager actors,
PenumbraService penumbra,
@ -58,18 +59,15 @@ public class CollectionOverrideDrawer(
DrawActorIdentifier(idx, actor);
table.NextColumn();
if (combo.Draw("##collection", name, "Select the overriding collection. Current GUID:", Im.ContentRegion.Available.X,
Im.Style.TextHeight))
{
var (guid, _, newName) = combo.CurrentSelection;
collectionOverrides.ChangeOverride(idx, guid, newName);
}
if (combo.Draw("##collection"u8, name, out var newName, ref collection, Im.ContentRegion.Available.X))
collectionOverrides.ChangeOverride(idx, collection, newName);
if (Im.Item.Hovered())
{
using var tt = Im.Tooltip.Begin();
using var font = Im.Font.PushMono();
Im.Text($" {collection}");
using var tt = Im.Tooltip.Begin();
Im.Text("Select the overriding collection. Current GUID:"u8);
using var indent = Im.Indent();
ImEx.MonoText($"{collection}");
}
table.NextColumn();

View file

@ -2,7 +2,7 @@
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Configuration;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Tabs.DesignTab;
@ -15,8 +15,8 @@ using Luna;
namespace Glamourer.Gui.Tabs.SettingsTab;
public sealed class SettingsTab(
Configuration.Configuration config,
DesignFileSystemSelector selector,
Configuration config,
DesignFileSystemDrawer drawer,
ContextMenuService contextMenuService,
IUiBuilder uiBuilder,
GlamourerChangelog changelog,
@ -28,7 +28,8 @@ public sealed class SettingsTab(
Glamourer glamourer,
AutoDesignApplier autoDesignApplier,
AutoRedrawChanged autoRedraw,
PcpService pcpService)
PcpService pcpService,
IgnoredMods ignoredMods)
: ITab<MainTabType>
{
private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray();
@ -61,6 +62,7 @@ public sealed class SettingsTab(
DrawInterfaceSettings();
DrawColorSettings();
overrides.Draw();
DrawIgnoredMods();
codeDrawer.Draw();
}
@ -256,7 +258,7 @@ public sealed class SettingsTab(
Im.Line.New();
Im.Text("Show the following panels in their respective tabs:"u8);
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.Save();
@ -280,8 +282,8 @@ public sealed class SettingsTab(
Checkbox("Show Unobtained Item Warnings"u8,
"Show information whether you have unlocked all items and customizations in your automated design or not."u8,
config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v);
Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8,
config.ShowColorConfig, v => config.ShowColorConfig = v);
Checkbox("Show Color Display Configuration"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8,
config.ShowColorConfig, v => config.ShowColorConfig = v);
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,
config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v);
@ -376,7 +378,7 @@ public sealed class SettingsTab(
if (Im.Button("Import Palette+ to Designs"u8))
paletteImport.ImportDesigns();
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>
@ -402,6 +404,7 @@ public sealed class SettingsTab(
continue;
config.Colors[color] = newColor.Color;
CacheManager.Instance.SetColorsDirty();
config.Save();
}
}
@ -445,20 +448,20 @@ public sealed class SettingsTab(
using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name))
{
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;
selector.SetFilterDirty();
config.SortMode = value;
drawer.SortMode = value;
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()
@ -472,7 +475,6 @@ public sealed class SettingsTab(
if (Im.Selectable(value.ToNameU8(), config.ShowRename == value))
{
config.ShowRename = value;
selector.SetRenameSearchPath(value);
config.Save();
}
@ -503,4 +505,46 @@ public sealed class SettingsTab(
LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"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 GauntletModelString = gauntlets.Valid ? new StringPair(gauntlets.ModelString) : StringPair.Empty;
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 StringU8 JobText = jobs.Name.IsEmpty ? new StringU8($"Unknown {jobs.Id.Id}") : jobs.Name;
public required bool Favorite { get; init; }

View file

@ -1,6 +1,7 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Table;
using Glamourer.Config;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
@ -23,9 +24,10 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
private readonly FavoriteManager _favorites;
private readonly PenumbraService _penumbra;
private readonly ObjectUnlocked _unlockEvent;
private readonly IgnoredMods _ignoredMods;
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),
new SlotColumn(), new TypeColumn(), new UnlockDateColumn(), new ItemIdColumn(), new ModelDataColumn(), new JobColumn(jobs),
new RequiredLevelColumn(), new DyableColumn(), new CrestColumn(), new TradableColumn())
@ -36,6 +38,7 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
_unlockEvent = unlockEvent;
_favorites = favorites;
_penumbra = penumbra;
_ignoredMods = ignoredMods;
Flags |= TableFlags.Hideable | TableFlags.Reorderable | TableFlags.Resizable;
}
@ -72,6 +75,7 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
UnlockTimestamp = unlocked,
Mods = mods,
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;
public override void DrawColumn(in UnlockCacheItem item, int globalIndex)
{
Im.Cursor.FrameAlign();
UiHelpers.DrawFavoriteStar(_favorites, item.Item);
}
=> UiHelpers.DrawFavoriteStar(_favorites, item.Item);
protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex)
=> 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()
{
Flags |= TableColumnFlags.NoResize;
Label = new StringU8("M");
FilterLabel = new StringU8("Modded"u8);
Flags |= TableColumnFlags.NoResize;
Label = new StringU8("M");
}
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)
@ -124,8 +133,11 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
using (AwesomeIcon.Font.Push())
{
using var color = ImGuiColor.Text.Push(ColorId.ModdedItemMarker.Value());
ImEx.TextCentered(Dot.Span);
var (color, text) = item.RelevantMods > 0
? (ColorId.ModdedItemMarker.Value(), Dot)
: (ColorId.ModdedItemMarker.Value().HalfTransparent(), Hollow);
using var c = ImGuiColor.Text.Push(color);
Im.Text(text.Span);
}
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)
=> item.Mods.Length > 0;
protected override Modded GetValue(in UnlockCacheItem item, int globalIndex)
=> 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)
=> 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>
@ -241,9 +271,9 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>
{
public UnlockDateColumn()
{
Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Unlocked"u8);
FilterLabel = Label;
Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Unlocked"u8);
FilterLabel = Label;
}
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>
{
private readonly Configuration.EphemeralConfig _config;
private readonly Config.EphemeralConfig _config;
private readonly UnlockOverview _overview;
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")
{
_config = config;

View file

@ -1,6 +1,7 @@
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
@ -23,7 +24,7 @@ public class ContextMenuService : IDisposable
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)
{
_contextMenu = context;

View file

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

View file

@ -1,4 +1,5 @@
using Glamourer.Designs.Links;
using Glamourer.Config;
using Glamourer.Designs.Links;
using Glamourer.Services;
using Glamourer.State;
using Luna;
@ -7,14 +8,19 @@ using Penumbra.GameData.Structs;
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
{
private readonly HashSet<Guid> _collectionTracker = [];
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;
if (!objects.TryGetValue(state.Identifier, out var data))
@ -90,6 +96,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip
if (!respectManual && source.IsFixed())
penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual);
}
return index;
}
}

View file

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

View file

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

View file

@ -1,27 +1,9 @@
using Luna;
using Backup = OtterGui.Classes.Backup;
using Logger = OtterGui.Log.Logger;
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>
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
{
@ -29,7 +11,7 @@ public class BackupService : IAsyncService
{
new(fileNames.ConfigurationFile),
new(fileNames.UiConfiguration),
new(fileNames.DesignFileSystem),
new(fileNames.MigrationDesignFileSystem),
new(fileNames.MigrationDesignFile),
new(fileNames.AutomationFile),
new(fileNames.UnlockFileCustomize),
@ -42,9 +24,4 @@ public class BackupService : IAsyncService
return list;
}
public Task Awaiter { get; }
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
}

View file

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

View file

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

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