diff --git a/Glamourer.Api b/Glamourer.Api index 51b3c72..941dc7e 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 51b3c72e91816af0002dd543d64944e777b246ba +Subproject commit 941dc7e1da694127a4405f4888ae162133131268 diff --git a/Glamourer.sln b/Glamourer.sln index 2eb4741..a791f23 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11415.281 d18.3 +VisualStudioVersion = 18.3.11415.281 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}" ProjectSection(SolutionItems) = preProject @@ -20,8 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumb EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.Api", "Glamourer.Api\Glamourer.Api.csproj", "{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luna", "Luna\Luna\Luna.csproj", "{DEA936D7-1386-55A1-7451-E0C240F56E9D}" @@ -68,14 +66,6 @@ Global {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64 {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|x64.ActiveCfg = Release|x64 {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|x64.Build.0 = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|x64.ActiveCfg = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|x64.Build.0 = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|x64.ActiveCfg = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|x64.Build.0 = Release|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index f8ac724..5adf76e 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -2,7 +2,6 @@ using Glamourer.Designs; using Glamourer.State; using Luna; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -73,26 +72,26 @@ public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, A [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal static void Lock(ActorState state, uint key, ApplyFlag flags) { - if ((flags & ApplyFlag.Lock) != 0 && key != 0) + if ((flags & ApplyFlag.Lock) is not 0 && key is not 0) state.Lock(key); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal IEnumerable FindStates(string objectName) { - if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString)) + if (objectName.Length is 0 || !ByteString.FromString(objectName, out var byteString)) return []; return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString) - .Concat(ArrayExtensions.SelectWhere(objects - .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString), kvp => - { - if (stateManager.ContainsKey(kvp.Key)) - return (false, null); + .Concat(objects + .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString).SelectWhere(kvp => + { + if (stateManager.ContainsKey(kvp.Key)) + return (false, null); - var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); - return (ret, state); - })); + var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); + return (ret, state); + })); } diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index d1f08e7..c3ec325 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -2,7 +2,6 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; -using ImSharp; using Luna; using Newtonsoft.Json.Linq; @@ -12,21 +11,19 @@ public class DesignsApi( ApiHelpers helpers, DesignManager designs, StateManager stateManager, - DesignFileSystem fileSystem, DesignColors color, DesignConverter converter) : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() - => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name); public Dictionary 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, 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) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index e4ca224..5ff5d56 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -1,4 +1,5 @@ using Glamourer.Api.Api; +using Glamourer.Config; using Luna; namespace Glamourer.Api; diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index f400aa8..1986925 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -2,7 +2,6 @@ using Glamourer.Api.Enums; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.State; using Luna; @@ -143,10 +142,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); var states = _helpers.FindExistingStates(playerName); - var any = false; + var any = false; var anyReapplied = false; foreach (var state in states) { @@ -154,7 +153,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) continue; - anyReapplied = true; + anyReapplied = true; anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success; } @@ -227,13 +226,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock) { var args = ApiHelpers.Args("Index", objectIndex, "Key", key); - isLocked = false; + isLocked = false; canUnlock = true; if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); if (state is null) - return ApiHelpers.Return(GlamourerApiEc.Success, args); - isLocked = state.IsLocked; + return ApiHelpers.Return(GlamourerApiEc.Success, args); + + isLocked = state.IsLocked; canUnlock = state.CanUnlock(key); return ApiHelpers.Return(GlamourerApiEc.Success, args); } @@ -423,29 +423,31 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable }; } - private void OnAutoRedrawChange(bool autoReload) + private void OnAutoRedrawChange(in bool autoReload) => AutoReloadGearChanged?.Invoke(autoReload); - private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) + private void OnStateChanged(in StateChanged.Arguments arguments) { - Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateChanged != null) - foreach (var actor in actors.Objects) + Glamourer.Log.Excessive( + $"[OnStateChanged] State Changed with Type {arguments.Type} [Affecting {arguments.Actors.ToLazyString("nothing")}.]"); + if (StateChanged is not null) + foreach (var actor in arguments.Actors.Objects) StateChanged.Invoke(actor.Address); - if (StateChangedWithType != null) - foreach (var actor in actors.Objects) - StateChangedWithType.Invoke(actor.Address, type); + if (StateChangedWithType is not null) + foreach (var actor in arguments.Actors.Objects) + StateChangedWithType.Invoke(actor.Address, arguments.Type); } - private void OnStateFinalized(StateFinalizationType type, ActorData actors) + private void OnStateFinalized(in StateFinalized.Arguments arguments) { - Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateFinalized != null) - foreach (var actor in actors.Objects) - StateFinalized.Invoke(actor.Address, type); + Glamourer.Log.Verbose( + $"[OnStateUpdated] State Updated with Type {arguments.Type}. [Affecting {arguments.Actors.ToLazyString("nothing")}.]"); + if (StateFinalized is not null) + foreach (var actor in arguments.Actors.Objects) + StateFinalized.Invoke(actor.Address, arguments.Type); } - private void OnGPoseChange(bool gPose) + private void OnGPoseChange(in bool gPose) => GPoseChanged?.Invoke(gPose); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index a61a004..a41760e 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,11 +1,13 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Material; using Glamourer.State; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -14,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Automation; -public sealed class AutoDesignApplier : IDisposable +public sealed class AutoDesignApplier : IDisposable, IRequiredService { private readonly Configuration _config; private readonly AutoDesignManager _manager; @@ -71,38 +73,38 @@ public sealed class AutoDesignApplier : IDisposable _jobs.JobChanged -= OnJobChange; } - private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + private void OnWeaponLoading(in WeaponLoading.Arguments arguments) { if (!_jobChangeState.HasState || !_config.EnableAutoDesigns) return; - var id = actor.GetIdentifier(_actors); + var id = arguments.Actor.GetIdentifier(_actors); if (id == _jobChangeState.Identifier) { var state = _jobChangeState.State!; - var current = state.BaseData.Item(slot); - switch (slot) + var current = state.BaseData.Item(arguments.Slot); + switch (arguments.Slot) { case EquipSlot.MainHand: { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) + if (_jobChangeState.TryGetValue(current.Type, arguments.Actor.Job, false, out var data)) { Glamourer.Log.Verbose( - $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); + $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{arguments.Actor.Address:X}."); _state.ChangeItem(state, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2)); - weapon = state.ModelData.Weapon(EquipSlot.MainHand); + arguments.Weapon = state.ModelData.Weapon(EquipSlot.MainHand); } break; } case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand(): { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) + if (_jobChangeState.TryGetValue(current.Type, arguments.Actor.Job, false, out var data)) { Glamourer.Log.Verbose( - $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); + $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{arguments.Actor.Address:X}."); _state.ChangeItem(state, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2)); - weapon = state.ModelData.Weapon(EquipSlot.OffHand); + arguments.Weapon = state.ModelData.Weapon(EquipSlot.OffHand); } _jobChangeState.Reset(); @@ -116,22 +118,22 @@ public sealed class AutoDesignApplier : IDisposable } } - private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? bonusData) + private void OnAutomationChange(in AutomationChanged.Arguments arguments) { - if (!_config.EnableAutoDesigns || set == null) + if (!_config.EnableAutoDesigns) return; - switch (type) + switch (arguments.Type) { - case AutomationChanged.Type.ToggleSet when !set.Enabled: - case AutomationChanged.Type.DeletedDesign when set.Enabled: - // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. - RemoveOld(set.Identifiers); + // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. + case AutomationChanged.Type.ToggleSet when arguments.Set.Enabled: + case AutomationChanged.Type.DeletedDesign when arguments.Set.Enabled: + RemoveOld(arguments.Set.Identifiers); break; - case AutomationChanged.Type.ChangeIdentifier when set.Enabled: + case AutomationChanged.Type.ChangeIdentifier + when arguments.As().Set is { Enabled: true } set: // Remove fixed state from the old identifiers assigned and the old enabled set, if any. - var (oldIds, _, _) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; - RemoveOld(oldIds); + RemoveOld(arguments.As().OldIdentifiers); ApplyNew(set); // Does not need to disable oldSet because same identifiers. break; case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers. @@ -142,7 +144,7 @@ public sealed class AutoDesignApplier : IDisposable case AutomationChanged.Type.ChangedConditions: case AutomationChanged.Type.ChangedType: case AutomationChanged.Type.ChangedData: - ApplyNew(set); + ApplyNew(arguments.Set); break; } @@ -302,7 +304,7 @@ public sealed class AutoDesignApplier : IDisposable mergedDesign.ResetTemporarySettings = true; } - _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange)); forcedRedraw = mergedDesign.ForcedRedraw; } @@ -344,7 +346,7 @@ public sealed class AutoDesignApplier : IDisposable internal static int NewGearsetId = -1; - private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) + private void OnEquippedGearset(in EquippedGearset.Arguments arguments) { if (!_config.EnableAutoDesigns) return; @@ -356,9 +358,9 @@ public sealed class AutoDesignApplier : IDisposable if (!GetPlayerSet(player, out var set) || !_state.TryGetValue(player, out var state)) return; - var respectManual = prior == id; - NewGearsetId = id; - Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, prior == id, out var forcedRedraw); + var respectManual = arguments.PriorId == arguments.Id; + NewGearsetId = arguments.Id; + Reduce(data.Objects[0], state, set, respectManual, arguments.JobId != state.LastJob, arguments.PriorId == arguments.Id, out var forcedRedraw); NewGearsetId = -1; foreach (var actor in data.Objects) _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index a7e63bd..53a0a92 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,14 +1,12 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,7 +14,7 @@ using Luna; namespace Glamourer.Automation; -public class AutoDesignManager : ISavable, IReadOnlyList, IDisposable +public sealed class AutoDesignManager : ISavable, IReadOnlyList, IDisposable, IService { public const int CurrentVersion = 1; @@ -77,7 +75,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _data.Add(newSet); Save(); Glamourer.Log.Debug($"Created new design set for {newSet.Identifiers[0].Incognito(null)}."); - _event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name)); + _event.Invoke(new AutomationChanged.AddedSetArguments(newSet, _data.Count - 1, name)); } public void DuplicateDesignSet(AutoDesignSet set) @@ -102,7 +100,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Duplicated new design set for {newSet.Identifiers[0].Incognito(null)} with {newSet.Designs.Count} auto designs from existing set."); - _event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name)); + _event.Invoke(new AutomationChanged.AddedSetArguments(newSet, _data.Count - 1, name)); } public void DeleteDesignSet(int whichSet) @@ -121,12 +119,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _data.RemoveAt(whichSet); Save(); Glamourer.Log.Debug($"Deleted design set {whichSet + 1}."); - _event.Invoke(AutomationChanged.Type.DeletedSet, set, whichSet); + _event.Invoke(new AutomationChanged.DeletedSetArguments(set, whichSet)); } public void Rename(int whichSet, string newName) { - if (whichSet >= _data.Count || whichSet < 0 || newName.Length == 0) + if (whichSet >= _data.Count || whichSet < 0 || newName.Length is 0) return; var set = _data[whichSet]; @@ -137,7 +135,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.Name = newName; Save(); Glamourer.Log.Debug($"Renamed design set {whichSet + 1} from {old} to {newName}."); - _event.Invoke(AutomationChanged.Type.RenamedSet, set, (old, newName)); + _event.Invoke(new AutomationChanged.RenamedSetArguments(set, old, newName)); } @@ -148,7 +146,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Moved design set {whichSet + 1} to position {toWhichSet + 1}."); - _event.Invoke(AutomationChanged.Type.MovedSet, _data[toWhichSet], (whichSet, toWhichSet)); + _event.Invoke(new AutomationChanged.MovedSetArguments(_data[toWhichSet], whichSet, toWhichSet)); } public void ChangeIdentifier(int whichSet, ActorIdentifier to) @@ -180,7 +178,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed Identifier of design set {whichSet + 1} from {old[0].Incognito(null)} to {to.Incognito(null)}."); - _event.Invoke(AutomationChanged.Type.ChangeIdentifier, set, (old, to, oldEnabled)); + _event.Invoke(new AutomationChanged.ChangeIdentifierArguments(set, old, to, oldEnabled)); } public void SetState(int whichSet, bool value) @@ -214,7 +212,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed enabled state of design set {whichSet + 1} to {value}."); - _event.Invoke(AutomationChanged.Type.ToggleSet, set, oldEnabled); + _event.Invoke(new AutomationChanged.ToggleSetArguments(set, oldEnabled)); } public void ChangeBaseState(int whichSet, AutoDesignSet.Base newBase) @@ -230,7 +228,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.BaseState = newBase; Save(); Glamourer.Log.Debug($"Changed base state of set {whichSet + 1} from {old} to {newBase}."); - _event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); + _event.Invoke(new AutomationChanged.ChangedBaseArguments(set, old, newBase)); } public void ChangeResetSettings(int whichSet, bool newValue) @@ -246,12 +244,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.ResetTemporarySettings = newValue; Save(); Glamourer.Log.Debug($"Changed resetting of temporary settings of set {whichSet + 1} from {old} to {newValue}."); - _event.Invoke(AutomationChanged.Type.ChangedTemporarySettingsReset, set, newValue); + _event.Invoke(new AutomationChanged.ChangedTemporarySettingsResetArguments(set, newValue)); } public void AddDesign(AutoDesignSet set, IDesignStandIn design) { - var newDesign = new AutoDesign() + var newDesign = new AutoDesign { Design = design, Type = ApplicationType.All, @@ -261,7 +259,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Added new associated design {design.ResolveName(true)} as design {set.Designs.Count} to design set."); - _event.Invoke(AutomationChanged.Type.AddedDesign, set, set.Designs.Count - 1); + _event.Invoke(new AutomationChanged.AddedDesignArguments(set, set.Designs.Count - 1)); } /// Only used to move between sets. @@ -275,8 +273,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos from.Designs.RemoveAt(idx); Save(); Glamourer.Log.Debug($"Moved design {idx} from design set {from.Name} to design set {to.Name}."); - _event.Invoke(AutomationChanged.Type.AddedDesign, to, to.Designs.Count - 1); - _event.Invoke(AutomationChanged.Type.DeletedDesign, from, idx); + _event.Invoke(new AutomationChanged.AddedDesignArguments(to, to.Designs.Count - 1)); + _event.Invoke(new AutomationChanged.DeletedDesignArguments(from, idx)); } public void DeleteDesign(AutoDesignSet set, int which) @@ -287,7 +285,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.Designs.RemoveAt(which); Save(); Glamourer.Log.Debug($"Removed associated design {which + 1} from design set."); - _event.Invoke(AutomationChanged.Type.DeletedDesign, set, which); + _event.Invoke(new AutomationChanged.DeletedDesignArguments(set, which)); } public void MoveDesign(AutoDesignSet set, int from, int to) @@ -297,7 +295,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Moved design {from + 1} to {to + 1} in design set."); - _event.Invoke(AutomationChanged.Type.MovedDesign, set, (from, to)); + _event.Invoke(new AutomationChanged.MovedDesignArguments(set, from, to)); } public void ChangeDesign(AutoDesignSet set, int which, IDesignStandIn newDesign) @@ -314,7 +312,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Changed linked design from {old.ResolveName(true)} to {newDesign.ResolveName(true)} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedDesign, set, (which, old, newDesign)); + _event.Invoke(new AutomationChanged.ChangedDesignArguments(set, which, old, newDesign)); } public void ChangeJobCondition(AutoDesignSet set, int which, JobGroup jobs) @@ -331,7 +329,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.Jobs = jobs; Save(); Glamourer.Log.Debug($"Changed job condition from {old.Id} to {jobs.Id} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs)); + _event.Invoke(new AutomationChanged.ChangedConditionsArguments(set, which, old, jobs)); } public void ChangeGearsetCondition(AutoDesignSet set, int which, short index) @@ -347,7 +345,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.GearsetIndex = index; Save(); Glamourer.Log.Debug($"Changed gearset condition from {old} to {index} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); + _event.Invoke(new AutomationChanged.ChangedConditionsArguments(set, which, default, default)); } public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType) @@ -364,7 +362,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.Type = applicationType; Save(); Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType)); + _event.Invoke(new AutomationChanged.ChangedTypeArguments(set, which, old, applicationType)); } public void ChangeData(AutoDesignSet set, int which, object data) @@ -378,10 +376,10 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed additional design data for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedData, set, (which, data)); + _event.Invoke(new AutomationChanged.ChangedDataArguments(set, which, data)); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.AutomationFile; public void Save(StreamWriter writer) @@ -397,7 +395,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos foreach (var set in _data) array.Add(set.Serialize()); - return new JObject() + return new JObject { ["Version"] = CurrentVersion, ["Data"] = array, @@ -424,9 +422,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Glamourer.Messager.NotificationMessage("Failure to load automated designs: No valid version available.", NotificationType.Error); break; - case 1: - LoadV1(obj["Data"]); - break; + case 1: LoadV1(obj["Data"]); break; } } catch (Exception ex) @@ -638,17 +634,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos } } - private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _) + private void OnDesignChange(in DesignChanged.Arguments arguments) { - if (type is not DesignChanged.Type.Deleted) + if (arguments.Type is not DesignChanged.Type.Deleted) return; - foreach (var (set, idx) in this.WithIndex()) + foreach (var (idx, set) in this.Index()) { var deleted = 0; for (var i = 0; i < set.Designs.Count; ++i) { - if (set.Designs[i].Design != design) + if (set.Designs[i].Design != arguments.Design) continue; DeleteDesign(set, i--); @@ -657,7 +653,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos if (deleted > 0) Glamourer.Log.Information( - $"Removed {deleted} automated designs from automated design set {idx} due to deletion of {design.Incognito}."); + $"Removed {deleted} automated designs from automated design set {idx} due to deletion of {arguments.Design.Incognito}."); } } } diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 1621588..7344c27 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -9,7 +9,7 @@ using Penumbra.String; namespace Glamourer.Automation; -public class FixedDesignMigrator(JobService jobs) +public sealed class FixedDesignMigrator(JobService jobs) : IRequiredService { private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; @@ -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 leaf) { Glamourer.Messager.NotificationMessage($"Could not find design with path {design.Item1}, skipped fixed design.", NotificationType.Warning); diff --git a/Glamourer/Configuration.cs b/Glamourer/Config/Configuration.cs similarity index 66% rename from Glamourer/Configuration.cs rename to Glamourer/Config/Configuration.cs index 0ceb056..8b80527 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Config/Configuration.cs @@ -6,53 +6,23 @@ using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Services; using ImSharp; -using Newtonsoft.Json; -using OtterGui.Filesystem; using Luna; using Luna.Generators; +using Newtonsoft.Json; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer; +namespace Glamourer.Config; -[TooltipEnum] -public enum HeightDisplayType +public sealed partial class Configuration : IPluginConfiguration, ISavable, IService { - [Tooltip("Do Not Display")] - None, + public const int CurrentVersion = 9; - [Tooltip("Centimetres (000.0 cm)")] - Centimetre, - - [Tooltip("Metres (0.00 m)")] - Metre, - - [Tooltip("Inches (00.0 in)")] - Wrong, - - [Tooltip("Feet (0'00'')")] - WrongFoot, - - [Tooltip("Corgis (0.0 Corgis)")] - Corgi, - - [Tooltip("Olympic-size swimming Pools (0.000 Pools)")] - OlympicPool, -} - -public class DefaultDesignSettings -{ - public bool AlwaysForceRedrawing = false; - public bool ResetAdvancedDyes = false; - public bool ShowQuickDesignBar = true; - public bool ResetTemporarySettings = false; - public bool Locked = false; -} - -public class Configuration : IPluginConfiguration, ISavable -{ [JsonIgnore] public readonly EphemeralConfig Ephemeral; + [JsonIgnore] + public readonly UiConfig Ui; + public bool AttachToPcp { get; set; } = true; public bool UseRestrictedGearProtection { get; set; } = false; public bool OpenFoldersByDefault { get; set; } = false; @@ -91,8 +61,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); @@ -103,7 +76,7 @@ public class Configuration : IPluginConfiguration, ISavable [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] - public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; + public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; public List<(string Code, bool Enabled)> Codes { get; set; } = []; @@ -113,7 +86,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 Colors { get; private set; } = ColorId.Values.ToDictionary(c => c, c => c.Data().DefaultColor); @@ -121,10 +94,11 @@ public class Configuration : IPluginConfiguration, ISavable [JsonIgnore] private readonly SaveService _saveService; - public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral) + public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral, UiConfig ui) { _saveService = saveService; Ephemeral = ephemeral; + Ui = ui; Load(migrator); } @@ -133,13 +107,13 @@ public class Configuration : IPluginConfiguration, ISavable private void Load(ConfigMigrationService migrator) { - if (!File.Exists(_saveService.FileNames.ConfigFile)) + if (!File.Exists(_saveService.FileNames.ConfigurationFile)) return; - if (File.Exists(_saveService.FileNames.ConfigFile)) + if (File.Exists(_saveService.FileNames.ConfigurationFile)) try { - var text = File.ReadAllText(_saveService.FileNames.ConfigFile); + var text = File.ReadAllText(_saveService.FileNames.ConfigurationFile); JsonConvert.PopulateObject(text, this, new JsonSerializerSettings { Error = HandleDeserializationError, @@ -163,8 +137,8 @@ public class Configuration : IPluginConfiguration, ISavable } } - public string ToFilename(FilenameService fileNames) - => fileNames.ConfigFile; + public string ToFilePath(FilenameService fileNames) + => fileNames.ConfigurationFile; public void Save(StreamWriter writer) { @@ -174,45 +148,22 @@ public class Configuration : IPluginConfiguration, ISavable serializer.Serialize(jWriter, this); } - public static class Constants - { - public const int CurrentVersion = 8; - - public static readonly ISortMode[] ValidSortModes = - [ - ISortMode.FoldersFirst, - ISortMode.Lexicographical, - new DesignFileSystem.CreationDate(), - new DesignFileSystem.InverseCreationDate(), - new DesignFileSystem.UpdateDate(), - new DesignFileSystem.InverseUpdateDate(), - ISortMode.InverseFoldersFirst, - ISortMode.InverseLexicographical, - ISortMode.FoldersLast, - ISortMode.InverseFoldersLast, - ISortMode.InternalOrder, - ISortMode.InverseInternalOrder, - ]; - } - /// Convert SortMode Types to their name. - private class SortModeConverter : JsonConverter> + private class SortModeConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer) { - value ??= ISortMode.FoldersFirst; + value ??= ISortMode.FoldersFirst; serializer.Serialize(writer, value.GetType().Name); } - public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, - bool hasExistingValue, + public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, bool hasExistingValue, JsonSerializer serializer) { - var name = serializer.Deserialize(reader); - if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode)) - return existingValue ?? ISortMode.FoldersFirst; + if (serializer.Deserialize(reader) is { } name) + return ISortMode.Valid.GetValueOrDefault(name, existingValue ?? ISortMode.FoldersFirst); - return mode; + return existingValue ?? ISortMode.FoldersFirst; } } } diff --git a/Glamourer/Config/DefaultDesignSettings.cs b/Glamourer/Config/DefaultDesignSettings.cs new file mode 100644 index 0000000..b96cf85 --- /dev/null +++ b/Glamourer/Config/DefaultDesignSettings.cs @@ -0,0 +1,10 @@ +namespace Glamourer.Config; + +public class DefaultDesignSettings +{ + public bool AlwaysForceRedrawing = false; + public bool ResetAdvancedDyes = false; + public bool ShowQuickDesignBar = true; + public bool ResetTemporarySettings = false; + public bool Locked = false; +} diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/Config/DesignPanelFlag.cs similarity index 91% rename from Glamourer/DesignPanelFlag.cs rename to Glamourer/Config/DesignPanelFlag.cs index a5a353a..c3d1915 100644 --- a/Glamourer/DesignPanelFlag.cs +++ b/Glamourer/Config/DesignPanelFlag.cs @@ -1,7 +1,7 @@ -using Luna.Generators; -using ImSharp; +using ImSharp; +using Luna.Generators; -namespace Glamourer; +namespace Glamourer.Config; [Flags] [NamedEnum(Utf16: false)] @@ -40,7 +40,7 @@ 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, Configuration config) { @@ -55,7 +55,6 @@ public static partial class DesignPanelFlagExtensions Action setterExpand) { var checkBoxWidth = Math.Max(Im.Style.FrameHeight, Expand.CalculateSize().X); - var test = DesignPanelFlag.AdvancedCustomizations.ToNameU8(); var textWidth = AdvancedCustomizations_Name__GenU8.CalculateSize().X; var tableSize = 2 * (textWidth + 2 * checkBoxWidth) + 10 * Im.Style.CellPadding.X diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/Config/EphemeralConfig.cs similarity index 74% rename from Glamourer/EphemeralConfig.cs rename to Glamourer/Config/EphemeralConfig.cs index fd76817..6218e83 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/Config/EphemeralConfig.cs @@ -6,28 +6,23 @@ using Luna.Generators; using Newtonsoft.Json; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer; +namespace Glamourer.Config; -public partial class EphemeralConfig : ISavable +public partial class EphemeralConfig : ISavable, IService { - public int Version { get; set; } = Configuration.Constants.CurrentVersion; + public int Version { get; set; } = Configuration.CurrentVersion; [ConfigProperty] private bool _incognitoMode; public bool UnlockDetailMode { get; set; } = true; - public bool ShowDesignQuickBar { get; set; } = false; - public bool LockDesignQuickBar { get; set; } = false; - public bool LockMainWindow { get; set; } = false; + public bool ShowDesignQuickBar { get; set; } + public bool LockDesignQuickBar { get; set; } + public bool LockMainWindow { get; set; } public MainTabType SelectedMainTab { get; set; } = MainTabType.Settings; - public Guid SelectedDesign { get; set; } = Guid.Empty; public Guid SelectedQuickDesign { get; set; } = Guid.Empty; public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; - public float CurrentDesignSelectorWidth { get; set; } = 200f; - public float DesignSelectorMinimumScale { get; set; } = 0.1f; - public float DesignSelectorMaximumScale { get; set; } = 0.5f; - [JsonIgnore] private readonly SaveService _saveService; @@ -69,7 +64,7 @@ public partial class EphemeralConfig : ISavable } } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.EphemeralConfigFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Config/HeightDisplayType.cs b/Glamourer/Config/HeightDisplayType.cs new file mode 100644 index 0000000..f16fdec --- /dev/null +++ b/Glamourer/Config/HeightDisplayType.cs @@ -0,0 +1,28 @@ +using Luna.Generators; + +namespace Glamourer.Config; + +[TooltipEnum] +public enum HeightDisplayType +{ + [Tooltip("Do Not Display")] + None, + + [Tooltip("Centimetres (000.0 cm)")] + Centimetre, + + [Tooltip("Metres (0.00 m)")] + Metre, + + [Tooltip("Inches (00.0 in)")] + Wrong, + + [Tooltip("Feet (0'00'')")] + WrongFoot, + + [Tooltip("Corgis (0.0 Corgis)")] + Corgi, + + [Tooltip("Olympic-size swimming Pools (0.000 Pools)")] + OlympicPool, +} diff --git a/Glamourer/Config/IgnoredMods.cs b/Glamourer/Config/IgnoredMods.cs new file mode 100644 index 0000000..d123108 --- /dev/null +++ b/Glamourer/Config/IgnoredMods.cs @@ -0,0 +1,84 @@ +using Glamourer.Services; +using Luna; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Config; + +public sealed class IgnoredMods : ConfigurationFile, IReadOnlySet +{ + public override int CurrentVersion + => 1; + + private readonly HashSet _ignoredMods = []; + + public IgnoredMods(SaveService saveService, MessageService messageService) + : base(saveService, messageService) + { + Load(); + } + + protected override void AddData(JsonTextWriter j) + { + j.WritePropertyName("IgnoredMods"); + j.WriteStartArray(); + foreach (var mod in _ignoredMods) + j.WriteValue(mod); + j.WriteEndArray(); + } + + protected override void LoadData(JObject j) + { + _ignoredMods.Clear(); + if (j["IgnoredMods"] is not JArray arr) + return; + + foreach (var value in arr.Values().OfType()) + _ignoredMods.Add(value); + } + + public override string ToFilePath(FilenameService fileNames) + => fileNames.IgnoredModsFile; + + public IEnumerator GetEnumerator() + => _ignoredMods.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _ignoredMods.Count; + + public void Add(string mod) + { + if (_ignoredMods.Add(mod)) + Save(); + } + + public void Remove(string mod) + { + if (_ignoredMods.Remove(mod)) + Save(); + } + + public bool Contains(string item) + => _ignoredMods.Contains(item); + + public bool IsProperSubsetOf(IEnumerable other) + => _ignoredMods.IsProperSubsetOf(other); + + public bool IsProperSupersetOf(IEnumerable other) + => _ignoredMods.IsProperSupersetOf(other); + + public bool IsSubsetOf(IEnumerable other) + => _ignoredMods.IsSubsetOf(other); + + public bool IsSupersetOf(IEnumerable other) + => _ignoredMods.IsSupersetOf(other); + + public bool Overlaps(IEnumerable other) + => _ignoredMods.Overlaps(other); + + public bool SetEquals(IEnumerable other) + => _ignoredMods.SetEquals(other); +} diff --git a/Glamourer/Config/UiConfig.cs b/Glamourer/Config/UiConfig.cs new file mode 100644 index 0000000..e76266a --- /dev/null +++ b/Glamourer/Config/UiConfig.cs @@ -0,0 +1,50 @@ +using Glamourer.Services; +using Luna; +using Luna.Generators; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Config; + +public sealed partial class UiConfig : ConfigurationFile +{ + public UiConfig(SaveService saveService, MessageService messageService) + : base(saveService, messageService) + { + Load(); + } + + [ConfigProperty] + private TwoPanelWidth _actorsTabScale = new(250, ScalingMode.Absolute); + + [ConfigProperty] + private TwoPanelWidth _designsTabScale = new(0.3f, ScalingMode.Percentage); + + [ConfigProperty] + private TwoPanelWidth _automationTabScale = new(0.3f, ScalingMode.Percentage); + + [ConfigProperty] + private TwoPanelWidth _npcTabScale = new(250, ScalingMode.Absolute); + + public override int CurrentVersion + => 1; + + protected override void AddData(JsonTextWriter j) + { + ActorsTabScale.WriteJson(j, "ActorsTab"); + DesignsTabScale.WriteJson(j, "DesignsTab"); + AutomationTabScale.WriteJson(j, "AutomationTab"); + NpcTabScale.WriteJson(j, "NpcTab"); + } + + protected override void LoadData(JObject j) + { + _actorsTabScale = TwoPanelWidth.ReadJson(j, "ActorsTab", new TwoPanelWidth(250, ScalingMode.Absolute)); + _designsTabScale = TwoPanelWidth.ReadJson(j, "DesignsTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); + _automationTabScale = TwoPanelWidth.ReadJson(j, "AutomationTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); + _npcTabScale = TwoPanelWidth.ReadJson(j, "NpcTab", new TwoPanelWidth(250, ScalingMode.Absolute)); + } + + public override string ToFilePath(FilenameService fileNames) + => fileNames.UiConfiguration; +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9ceb8c0..9b123d0 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -7,15 +7,13 @@ using Glamourer.Services; using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Classes; using Penumbra.GameData.Structs; using Luna; using Notification = Luna.Notification; -using SaveType = OtterGui.Classes.SaveType; namespace Glamourer.Designs; -public sealed class Design : DesignBase, ISavable, IDesignStandIn +public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemValue { #region Data @@ -45,9 +43,10 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public new const int FileVersion = 2; public Guid Identifier { get; internal init; } + public IFileSystemData? Node { get; set; } public DateTimeOffset CreationDate { get; internal init; } public DateTimeOffset LastEdit { get; internal set; } - public LowerString Name { get; internal set; } = LowerString.Empty; + public string Name { get; internal set; } = string.Empty; public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } @@ -58,6 +57,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string Color { get; internal set; } = string.Empty; public SortedList AssociatedMods { get; private set; } = []; public LinkContainer Links { get; private set; } = []; + public DataPath Path { get; } = new(); public string Incognito => Identifier.ToString()[..8]; @@ -70,7 +70,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region IDesignStandIn public string ResolveName(bool incognito) - => incognito ? Incognito : Name.Text; + => incognito ? Incognito : Name; public string SerializeName() => Identifier.ToString(); @@ -108,7 +108,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["Identifier"] = Identifier, ["CreationDate"] = CreationDate, ["LastEdit"] = LastEdit, - ["Name"] = Name.Text, + ["Name"] = Name, ["Description"] = Description, ["ForcedRedraw"] = ForcedRedraw, ["ResetAdvancedDyes"] = ResetAdvancedDyes, @@ -125,6 +125,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; } @@ -193,7 +199,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn var hasNegativeGloss = false; var hasNonPositiveGloss = false; var specularLarger = 0; - foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + foreach (var (_, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) { hasNegativeGloss |= value.Value.GlossStrength < 0; hasNonPositiveGloss |= value.Value.GlossStrength <= 0; @@ -244,7 +250,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn { CreationDate = creationDate, Identifier = json["Identifier"]?.ToObject() ?? throw new ArgumentNullException("Identifier"), - Name = new LowerString(json["Name"]?.ToObject() ?? throw new ArgumentNullException("Name")), + Name = json["Name"]?.ToObject() ?? throw new ArgumentNullException("Name"), Description = json["Description"]?.ToObject() ?? string.Empty, Tags = ParseTags(json), LastEdit = json["LastEdit"]?.ToObject() ?? creationDate, @@ -252,6 +258,9 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn }; if (design.LastEdit < creationDate) design.LastEdit = creationDate; + design.Path.Folder = json["FileSystemFolder"]?.Value() ?? string.Empty; + design.Path.SortName = json["SortOrderName"]?.Value()?.FixName(); + design.SetWriteProtected(json["WriteProtected"]?.ToObject() ?? false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); @@ -329,21 +338,25 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region ISavable - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.DesignFile(this); public void Save(StreamWriter writer) { - using var j = new JsonTextWriter(writer) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; var obj = JsonSerialize(); obj.WriteTo(j); } public string LogName(string fileName) - => Path.GetFileNameWithoutExtension(fileName); + => System.IO.Path.GetFileNameWithoutExtension(fileName); #endregion + + string IFileSystemValue.Identifier + => Identifier.ToString(); + + public string DisplayName + => Name; } diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 8cd137f..dda16b0 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,7 +1,5 @@ using Glamourer.Api.Enums; using Glamourer.Services; -using OtterGui; -using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -107,7 +105,7 @@ public class DesignBase64Migration } data.Customize = *(CustomizeArray*)(ptr + 4); - foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + foreach (var (idx, slot) in EquipSlotExtensions.EqdpSlots.Index()) { var mdl = eq[idx]; var item = items.Identify(slot, mdl.Set, mdl.Variant); @@ -121,7 +119,7 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].Skeleton.Id == 0 + var main = cur[0].Skeleton.Id is 0 ? items.DefaultSword : items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant); if (!main.Valid) diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index b5b2c4b..44be523 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -8,114 +8,7 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Designs; -public class DesignColorUi(DesignColors colors, Configuration config) -{ - private string _newName = string.Empty; - - public void Draw() - { - using var table = Im.Table.Begin("designColors"u8, 3, TableFlags.RowBackground); - if (!table) - return; - - var changeString = string.Empty; - Rgba32? changeValue = null; - - table.SetupColumn("##Delete"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); - table.SetupColumn("##Select"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); - table.SetupColumn("Color Name"u8, TableColumnFlags.WidthStretch); - - table.HeaderRow(); - - table.NextColumn(); - if (ImEx.Icon.Button(LunaStyle.RefreshIcon, "Revert the color used for missing design colors to its default."u8, - colors.MissingColor == DesignColors.MissingColorDefault)) - { - changeString = DesignColors.MissingColorName; - changeValue = DesignColors.MissingColorDefault; - } - - table.NextColumn(); - if (DrawColorButton(DesignColors.MissingColorNameU8, colors.MissingColor, out var newColor)) - { - changeString = DesignColors.MissingColorName; - changeValue = newColor; - } - - table.NextColumn(); - Im.Cursor.X += Im.Style.FramePadding.X; - Im.Text(DesignColors.MissingColorNameU8); - Im.Tooltip.OnHover("This color is used when the color specified in a design is not available."u8); - - var disabled = !config.DeleteDesignModifier.IsActive(); - foreach (var (idx, (name, color)) in colors.Index()) - { - using var id = Im.Id.Push(idx); - table.NextColumn(); - - if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this color. This does not remove it from designs using it."u8, disabled)) - { - changeString = name; - changeValue = null; - } - - if (disabled) - Im.Tooltip.OnHover($"\nHold {config.DeleteDesignModifier} to delete."); - - table.NextColumn(); - if (DrawColorButton(name, color, out newColor)) - { - changeString = name; - changeValue = newColor; - } - - table.NextColumn(); - Im.Cursor.X += Im.Style.FramePadding.X; - Im.Text(name); - } - - table.NextColumn(); - (var tt, disabled) = _newName.Length == 0 - ? ("Specify a name for a new color first.", true) - : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName - ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) - : colors.ContainsKey(_newName) - ? ($"The color {_newName} already exists, please choose a different name.", true) - : ($"Add a new color {_newName} to your list.", false); - if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, disabled)) - { - changeString = _newName; - changeValue = 0xFFFFFFFF; - } - - table.NextColumn(); - table.NextColumn(); - Im.Item.SetNextWidth(Im.ContentRegion.Available.X); - if (Im.Input.Text("##newDesignColor"u8, ref _newName, "New Color Name..."u8, InputTextFlags.EnterReturnsTrue)) - { - changeString = _newName; - changeValue = 0xFFFFFFFF; - } - - if (changeString.Length > 0) - { - if (!changeValue.HasValue) - colors.DeleteColor(changeString); - else - colors.SetColor(changeString, changeValue.Value); - } - } - - public static bool DrawColorButton(Utf8StringHandler tooltip, Rgba32 color, out Rgba32 newColor) - { - var ret = Im.Color.Editor(tooltip, ref color, ColorEditorFlags.AlphaPreviewHalf | ColorEditorFlags.NoInputs); - Im.Tooltip.OnHover(ref tooltip); - newColor = color; - return ret; - } -} - -public class DesignColors : ISavable, IReadOnlyDictionary +public sealed class DesignColors : ISavable, IReadOnlyDictionary, IService { public const string AutomaticName = "Automatic"; public static readonly StringU8 AutomaticNameU8 = new("Automatic"u8); @@ -183,7 +76,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary SaveAndInvoke(); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.DesignColorFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 058b023..46ee6d3 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -3,6 +3,7 @@ using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; +using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; @@ -12,13 +13,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter( +public sealed class DesignConverter( SaveService saveService, - ItemManager _items, - DesignManager _designs, - CustomizeService _customize, - HumanModelList _humans, - DesignLinkLoader _linkLoader) + ItemManager items, + DesignManager designs, + CustomizeService customizeService, + HumanModelList humans, + DesignLinkLoader linkLoader) : IService { public const byte Version = 6; @@ -54,9 +55,9 @@ public class DesignConverter( public DesignBase Convert(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { - var design = _designs.CreateTemporary(); + var design = designs.CreateTemporary(); rules.Apply(design); - design.SetDesignData(_customize, data); + design.SetDesignData(customizeService, data); if (rules.Materials) ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip); return design; @@ -70,8 +71,8 @@ public class DesignConverter( try { var ret = jObject["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObject) - : DesignBase.LoadDesignBase(_customize, _items, jObject); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObject) + : DesignBase.LoadDesignBase(customizeService, items, jObject); if (!customize) ret.Application.RemoveCustomize(); @@ -101,14 +102,14 @@ public class DesignConverter( case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj1) - : DesignBase.LoadDesignBase(_customize, _items, jObj1); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj1) + : DesignBase.LoadDesignBase(customizeService, items, jObj1); break; case 1: case 2: case 4: - ret = _designs.CreateTemporary(); - ret.MigrateBase64(_customize, _items, _humans, base64); + ret = designs.CreateTemporary(); + ret.MigrateBase64(customizeService, items, humans, base64); break; case 3: { @@ -116,8 +117,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } case 5: @@ -127,8 +128,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } case 6: @@ -137,8 +138,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } @@ -177,7 +178,7 @@ public class DesignConverter( { var index = (int)slot.ToIndex(); var armor = armors[index]; - var item = _items.Identify(slot, armor.Set, armor.Variant); + var item = items.Identify(slot, armor.Set, armor.Variant); if (!item.Valid) { if (!skipWarnings) @@ -188,20 +189,20 @@ public class DesignConverter( yield return (slot, item, armor.Stains); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); + var mh = items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); if (!skipWarnings && !mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); - mh = _items.DefaultSword; + mh = items.DefaultSword; } yield return (EquipSlot.MainHand, mh, mainhand.Stains); - var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); + var oh = items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!skipWarnings && !oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); - oh = _items.GetDefaultOffhand(mh); + oh = items.GetDefaultOffhand(mh); if (!oh.Valid) oh = ItemManager.NothingItem(FullEquipType.Shield); } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index c7ca8e5..5ace949 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -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) { diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index c6fbe86..da112a3 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -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; @@ -75,7 +75,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed customize {idx.ToName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value))); } /// @@ -91,7 +91,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize))); } /// @@ -106,7 +106,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new))); } /// @@ -130,9 +130,9 @@ public class DesignEditor( SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Weapon, design, new WeaponTransaction(currentMain, currentOff, currentGauntlets, item, newOff ?? currentOff, - newGauntlets ?? currentGauntlets)); + newGauntlets ?? currentGauntlets))); return; } case EquipSlot.OffHand: @@ -150,8 +150,8 @@ public class DesignEditor( SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, - new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Weapon, design, + new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets))); return; } default: @@ -167,7 +167,7 @@ public class DesignEditor( Glamourer.Log.Debug( $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item))); return; } } @@ -187,7 +187,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {slot} bonus item to {item}."); - DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item))); } /// @@ -204,7 +204,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); - DesignChanged.Invoke(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains))); } /// @@ -227,7 +227,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); - DesignChanged.Invoke(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest))); } /// @@ -240,7 +240,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value))); } public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert) @@ -253,7 +253,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}"); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert))); } public void ChangeMaterialMode(Design design, MaterialValueIndex index, ColorRow.Mode mode) @@ -267,7 +267,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed advanced dye value for {index} from {oldMode} to {mode} mode."); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.MaterialMode, design, new MaterialModeTransaction(index, oldMode, mode)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.MaterialMode, design, new MaterialModeTransaction(index, oldMode, mode))); } public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row, ColorRow.Mode? mode = null) @@ -307,8 +307,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.DelaySave(design); - DesignChanged.Invoke(DesignChanged.Type.Material, design, - new MaterialTransaction(index, oldValue.Value, row, mode.HasValue ? oldValue.Mode : null, mode)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row, mode.HasValue ? oldValue.Mode : null, mode))); } public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) @@ -321,7 +320,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}."); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value))); } diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 159da19..c2dc048 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,197 +1,75 @@ using Dalamud.Interface.ImGuiNotification; -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, IDisposable, ISavable +public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredService { - private readonly DesignChanged _designChanged; + private readonly DesignFileSystemSaver _saver; + private readonly DesignChanged _designChanged; + private readonly TabSelected _tabSelected; - 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, TabSelected tabSelected) + : base("DesignFileSystem", log, true) { - _designManager = designManager; - _saveService = saveService; _designChanged = designChanged; - _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystem); - Changed += OnChange; - Reload(); + _tabSelected = tabSelected; + _saver = new DesignFileSystemSaver(log, this, saveService, designs); + + _saver.Load(); + _designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystem); + _tabSelected.Subscribe(OnTabSelected, TabSelected.Priority.DesignSelector); } - private void Reload() + private void OnTabSelected(in TabSelected.Arguments arguments) { - if (Load(new FileInfo(_saveService.FileNames.DesignFileSystem), _designManager.Designs, DesignToIdentifier, DesignToName)) - _saveService.ImmediateSave(this); + if (arguments.Design?.Node is { } node) + Selection.Select(node); + } - Glamourer.Log.Debug("Reloaded design filesystem."); + private void OnDesignChanged(in DesignChanged.Arguments arguments) + { + switch (arguments.Type) + { + case DesignChanged.Type.ReloadedAll: _saver.Load(); break; + case DesignChanged.Type.Created: + var parent = Root; + if (arguments.Design.Path.Folder.Length > 0) + try + { + parent = FindOrCreateAllFolders(arguments.Design.Path.Folder); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, + $"Could not move design to {arguments.Design.Path} because the folder could not be created.", + NotificationType.Error); + } + + var (data, _) = CreateDuplicateDataNode(parent, arguments.Design.Path.SortName ?? arguments.Design.Name, arguments.Design); + Selection.Select(data); + break; + case DesignChanged.Type.Deleted: + if (arguments.Design.Node is { } node) + { + if (node.Selected) + Selection.UnselectAll(); + Delete(node); + } + + break; + case DesignChanged.Type.Renamed when arguments.Design.Path.SortName is null: + RenameWithDuplicates(arguments.Design.Node!, arguments.Design.Path.GetIntendedName(arguments.Design.Name)); + break; + // TODO: Maybe add path changes? + } } public void Dispose() { - _designChanged.Unsubscribe(OnDesignChange); - } - - public struct CreationDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Creation Date (Older First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate)); - } - - public struct UpdateDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Update Date (Older First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit)); - } - - public struct InverseCreationDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Creation Date (Newer First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate)); - } - - public struct InverseUpdateDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Update Date (Newer First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().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 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>(); - 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()), - }; - } - - 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 ToFilename(FilenameService fileNames) - => fileNames.DesignFileSystem; - - public void Save(StreamWriter writer) - { - SaveToFile(writer, SaveDesign, true); + _tabSelected.Unsubscribe(OnTabSelected); + _designChanged.Unsubscribe(OnDesignChanged); } } diff --git a/Glamourer/Designs/DesignFileSystemSaver.cs b/Glamourer/Designs/DesignFileSystemSaver.cs new file mode 100644 index 0000000..9899b28 --- /dev/null +++ b/Glamourer/Designs/DesignFileSystemSaver.cs @@ -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(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 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}"); + } + } + } +} diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 9c9122b..5e12b57 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,4 +1,5 @@ using Dalamud.Utility; +using Glamourer.Config; using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; @@ -6,7 +7,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; -using OtterGui.Extensions; +using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; @@ -15,7 +16,7 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public sealed class DesignManager : DesignEditor +public sealed class DesignManager : DesignEditor, IService { public readonly DesignStorage Designs; private readonly HumanModelList _humans; @@ -88,7 +89,7 @@ public sealed class DesignManager : DesignEditor Glamourer.Log.Information( $"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); - DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ReloadedAll, null!)); } /// Create a new temporary design without adding it to the manager. @@ -115,7 +116,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -140,7 +141,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -161,7 +162,7 @@ public sealed class DesignManager : DesignEditor Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -172,7 +173,7 @@ public sealed class DesignManager : DesignEditor --d.Index; Designs.RemoveAt(design.Index); SaveService.ImmediateDelete(design); - DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Deleted, design)); } #endregion @@ -182,7 +183,7 @@ public sealed class DesignManager : DesignEditor /// Rename a design. public void Rename(Design design, string newName) { - var oldName = design.Name.Text; + var oldName = design.Name; if (oldName == newName) return; @@ -190,7 +191,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName))); } /// Change the description of a design. @@ -204,7 +205,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed description of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description))); } /// Change the associated color of a design. @@ -218,7 +219,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor))); } /// Add a new tag to a design. The tags remain sorted. @@ -232,7 +233,7 @@ public sealed class DesignManager : DesignEditor var idx = design.Tags.AsEnumerable().IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx))); } /// Remove a tag from a design by its index. @@ -246,7 +247,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx))); } /// Rename a tag from a design by its index. The tags stay sorted. @@ -261,8 +262,8 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, - new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag))); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedTag, design, + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag)))); } /// Add an associated mod to a design. @@ -274,7 +275,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings))); } /// Remove an associated mod from a design. @@ -286,7 +287,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings))); } /// Add or update an associated mod to a design. @@ -299,12 +300,12 @@ public sealed class DesignManager : DesignEditor if (hasOldSettings) { Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings))); } else { Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings))); } } @@ -316,7 +317,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected."); - DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.WriteProtection, design)); } /// Set the quick design bar display status of a design. @@ -329,7 +330,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar."); - DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.QuickDesignBar, design)); } #endregion @@ -344,7 +345,7 @@ public sealed class DesignManager : DesignEditor design.ForcedRedraw = forcedRedraw; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? string.Empty : "not")} force redraws."); - DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ForceRedraw, design)); } public void ChangeResetAdvancedDyes(Design design, bool resetAdvancedDyes) @@ -355,7 +356,7 @@ public sealed class DesignManager : DesignEditor design.ResetAdvancedDyes = resetAdvancedDyes; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? string.Empty : "not")} reset advanced dyes."); - DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ResetAdvancedDyes, design)); } public void ChangeResetTemporarySettings(Design design, bool resetTemporarySettings) @@ -366,7 +367,7 @@ public sealed class DesignManager : DesignEditor design.ResetTemporarySettings = resetTemporarySettings; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(resetTemporarySettings ? string.Empty : "not")} reset temporary settings."); - DesignChanged.Invoke(DesignChanged.Type.ResetTemporarySettings, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ResetTemporarySettings, design)); } /// Change whether to apply a specific customize value. @@ -378,7 +379,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of customization {idx.ToName()} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value))); } /// Change whether to apply a specific equipment piece. @@ -390,7 +391,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value))); } /// Change whether to apply a specific equipment piece. @@ -402,7 +403,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value))); } /// Change whether to apply a specific stain. @@ -414,7 +415,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value))); } /// Change whether to apply a specific crest visibility. @@ -426,7 +427,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value))); } /// Change the application value of one of the meta flags. @@ -438,7 +439,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value))); } /// Change the application value of a customize parameter. @@ -450,7 +451,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value))); } /// Change multiple application values at once. @@ -511,7 +512,6 @@ public sealed class DesignManager : DesignEditor { var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile); var dict = JsonConvert.DeserializeObject>(text) ?? new Dictionary(); - var migratedFileSystemPaths = new Dictionary(dict.Count); foreach (var (name, base64) in dict) { try @@ -528,7 +528,6 @@ public sealed class DesignManager : DesignEditor if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); - migratedFileSystemPaths.Add(design.Identifier.ToString(), name); ++successes; } else @@ -545,7 +544,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."); } @@ -632,7 +630,7 @@ public sealed class DesignManager : DesignEditor if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design)); } /// Split a given string into its folder path and its name, if is true. diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 2676a84..4fc418b 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -1,8 +1,6 @@ -using Glamourer.Api.Enums; using Glamourer.Events; using Glamourer.State; using Luna; -using Penumbra.GameData.Interop; namespace Glamourer.Designs.History; @@ -171,21 +169,21 @@ public class EditorHistory : IDisposable, IService } - private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData actors, ITransaction? data) + private void OnStateChanged(in StateChanged.Arguments arguments) { - if (_undoMode || source is not StateSource.Manual) + if (_undoMode || arguments.Source is not StateSource.Manual) return; - if (data is not null) - AddStateTransaction(state, data); + if (arguments.Transaction is not null) + AddStateTransaction(arguments.State, arguments.Transaction); } - private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? data) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { if (_undoMode) return; - if (data is not null) - AddDesignTransaction(design, data); + if (arguments.Transaction is not null) + AddDesignTransaction(arguments.Design, arguments.Transaction); } } diff --git a/Glamourer/Designs/History/Transaction.cs b/Glamourer/Designs/History/Transaction.cs index 47b10bf..234c7bb 100644 --- a/Glamourer/Designs/History/Transaction.cs +++ b/Glamourer/Designs/History/Transaction.cs @@ -10,7 +10,7 @@ public interface ITransaction public void Revert(IDesignEditor editor, object data); } -public readonly record struct CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New) +public record CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -20,7 +20,7 @@ public readonly record struct CustomizeTransaction(CustomizeIndex Slot, Customiz => editor.ChangeCustomize(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New) +public record EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -30,7 +30,7 @@ public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, Cu => editor.ChangeEntireCustomize(data, Old, Apply, ApplySettings.Manual); } -public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New) +public record EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -40,7 +40,7 @@ public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, Eq => editor.ChangeItem(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New) +public record BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -50,7 +50,7 @@ public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem => editor.ChangeBonusItem(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct WeaponTransaction( +public record WeaponTransaction( EquipItem OldMain, EquipItem OldOff, EquipItem OldGauntlets, @@ -72,7 +72,7 @@ public readonly record struct WeaponTransaction( } } -public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, StainIds New) +public record StainTransaction(EquipSlot Slot, StainIds Old, StainIds New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -82,7 +82,7 @@ public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, Sta => editor.ChangeStains(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool New) +public record CrestTransaction(CrestFlag Slot, bool Old, bool New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -92,7 +92,7 @@ public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool Ne => editor.ChangeCrest(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New) +public record ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -102,7 +102,7 @@ public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, => editor.ChangeCustomizeParameter(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct MetaTransaction(MetaIndex Slot, bool Old, bool New) +public record MetaTransaction(MetaIndex Slot, bool Old, bool New) : ITransaction { public ITransaction? Merge(ITransaction older) diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs index efed8ab..a5833e6 100644 --- a/Glamourer/Designs/Links/DesignLinkManager.cs +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -1,5 +1,4 @@ using Glamourer.Automation; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using Luna; @@ -32,7 +31,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void AddDesignLink(Design parent, Design child, LinkOrder order) @@ -43,7 +42,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void RemoveDesignLink(Design parent, int idx, LinkOrder order) @@ -54,7 +53,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void ChangeApplicationType(Design parent, int idx, LinkOrder order, ApplicationType applicationType) @@ -64,22 +63,23 @@ public sealed class DesignLinkManager : IService, IDisposable return; _saveService.QueueSave(parent); - Glamourer.Log.Debug($"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + Glamourer.Log.Debug( + $"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}."); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } - private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type is not DesignChanged.Type.Deleted) + if (arguments.Type is not DesignChanged.Type.Deleted) return; foreach (var design in _storage) { - if (!design.Links.Remove(deletedDesign)) + if (!design.Links.Remove(arguments.Design)) continue; design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); + Glamourer.Log.Debug($"Removed {arguments.Design.Identifier} from {design.Identifier} links due to deletion."); _saveService.QueueSave(design); } } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 927a7d7..a75c7cb 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,5 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs index 6cfc121..10e2416 100644 --- a/Glamourer/Designs/Links/LinkContainer.cs +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -1,6 +1,6 @@ using Glamourer.Automation; +using Luna; using Newtonsoft.Json.Linq; -using OtterGui.Filesystem; namespace Glamourer.Designs.Links; diff --git a/Glamourer/Designs/SortMode.cs b/Glamourer/Designs/SortMode.cs new file mode 100644 index 0000000..db746b2 --- /dev/null +++ b/Glamourer/Designs/SortMode.cs @@ -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 Name + => "Creation Date (Older First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast().Concat(f.GetLeaves().OfType>().OrderBy(l => l.Value.CreationDate)); +} + +public readonly struct UpdateDate : ISortMode +{ + public static readonly UpdateDate Instance = new(); + + public ReadOnlySpan Name + => "Update Date (Older First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast().Concat(f.GetLeaves().OfType>().OrderBy(l => l.Value.LastEdit)); +} + +public readonly struct InverseCreationDate : ISortMode +{ + public static readonly InverseCreationDate Instance = new(); + + public ReadOnlySpan Name + => "Creation Date (Newer First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast() + .Concat(f.GetLeaves().OfType>().OrderByDescending(l => l.Value.CreationDate)); +} + +public readonly struct InverseUpdateDate : ISortMode +{ + public static readonly InverseUpdateDate Instance = new(); + + public ReadOnlySpan Name + => "Update Date (Newer First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast() + .Concat(f.GetLeaves().OfType>().OrderByDescending(l => l.Value.LastEdit)); +} + +public static class SortModeExtensions +{ + private static readonly FrozenDictionary ValidSortModes = new Dictionary + { + [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 Valid + => ValidSortModes; + } +} diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index c5d9124..e45122a 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -1,9 +1,9 @@ - +using Glamourer.Config; using Luna; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService +public class RandomDesignGenerator(DesignStorage designs, Configuration config) : IService { private readonly Random _rng = new(); private readonly WeakReference _lastDesign = new(null!, false); @@ -34,7 +34,7 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS => Design(designs); public Design? Design(IDesignPredicate predicate) - => Design(predicate.Get(designs, fileSystem).ToList()); + => Design(predicate.Get(designs).ToList()); public Design? Design(IReadOnlyList predicates) { @@ -42,7 +42,7 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS { 0 => Design(), 1 => Design(predicates[0]), - _ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()), + _ => Design(IDesignPredicate.Get(predicates, designs).ToList()), }; } diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs index ae05f8f..9039eaa 100644 --- a/Glamourer/Designs/Special/RandomPredicate.cs +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -1,61 +1,59 @@ -using OtterGui.Classes; - -namespace Glamourer.Designs.Special; +namespace Glamourer.Designs.Special; public interface IDesignPredicate { - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath); + bool Invoke(Design design, string name, string identifier, string path); - public bool Invoke((Design Design, string LowerName, string Identifier, string LowerPath) args) - => Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath); + bool Invoke((Design Design, string Name, string Identifier, string Path) args) + => Invoke(args.Design, args.Name, args.Identifier, args.Path); - public IEnumerable Get(IEnumerable designs, DesignFileSystem fileSystem) - => designs.Select(d => Transform(d, fileSystem)) + IEnumerable Get(IEnumerable designs) + => designs.Select(Transform) .Where(Invoke) .Select(t => t.Design); - public static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs, DesignFileSystem fileSystem) + static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs) => 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 Name, string Identifier, string Path) Transform(Design d) + => (d, d.Name, d.Identifier.ToString(), d.Path.CurrentPath); } public static class RandomPredicate { public readonly struct StartsWith(string value) : IDesignPredicate { - public LowerString Value { get; } = value; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) - => lowerPath.StartsWith(Value.Lower); + public bool Invoke(Design design, string name, string identifier, string path) + => path.StartsWith(Value, StringComparison.OrdinalIgnoreCase); public override string ToString() - => $"/{Value.Text}"; + => $"/{Value}"; } public readonly struct Contains(string value) : IDesignPredicate { - public LowerString Value { get; } = value; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + public bool Invoke(Design design, string name, string identifier, string path) { - if (lowerName.Contains(Value.Lower)) + if (name.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; - if (identifier.Contains(Value.Lower)) + if (identifier.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; - if (lowerPath.Contains(Value.Lower)) + if (path.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; return false; } public override string ToString() - => Value.Text; + => Value; } public readonly struct Exact(Exact.Type type, string value) : IDesignPredicate @@ -69,25 +67,25 @@ public static class RandomPredicate Color, } - public Type Which { get; } = type; - public LowerString Value { get; } = value; + public Type Which { get; } = type; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + public bool Invoke(Design design, string name, string identifier, string path) => Which switch { - Type.Name => lowerName == Value.Lower, - Type.Path => lowerPath == Value.Lower, - Type.Identifier => identifier == Value.Lower, + Type.Name => string.Equals(name, Value, StringComparison.OrdinalIgnoreCase), + Type.Path => string.Equals(path, Value, StringComparison.OrdinalIgnoreCase), + Type.Identifier => string.Equals(identifier, Value, StringComparison.OrdinalIgnoreCase), Type.Tag => IsContained(Value, design.Tags), - Type.Color => design.Color == Value, + Type.Color => string.Equals(design.Color, Value, StringComparison.OrdinalIgnoreCase), _ => false, }; - private static bool IsContained(LowerString value, IEnumerable data) - => data.Any(t => t == value); + private static bool IsContained(string value, IEnumerable data) + => data.Any(t => string.Equals(t, value, StringComparison.OrdinalIgnoreCase)); public override string ToString() - => $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value.Text}\""; + => $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value}\""; } public static IDesignPredicate CreateSinglePredicate(string restriction) @@ -125,7 +123,7 @@ public static class RandomPredicate public static List GeneratePredicates(string restrictions) { - if (restrictions.Length == 0) + if (restrictions.Length is 0) return []; List predicates = new(1); @@ -153,9 +151,9 @@ public static class RandomPredicate public static string GeneratePredicateString(IReadOnlyCollection predicates) { - if (predicates.Count == 0) + if (predicates.Count is 0) return string.Empty; - if (predicates.Count == 1) + if (predicates.Count is 1) return predicates.First()!.ToString()!; return $"{{{string.Join("; ", predicates)}}}"; diff --git a/Glamourer/Events/AutoRedrawChanged.cs b/Glamourer/Events/AutoRedrawChanged.cs index a8dd03a..5f9eebb 100644 --- a/Glamourer/Events/AutoRedrawChanged.cs +++ b/Glamourer/Events/AutoRedrawChanged.cs @@ -1,16 +1,16 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; /// /// Triggered when the auto-reload gear setting is changed in glamourer configuration. /// -public sealed class AutoRedrawChanged() - : EventWrapper(nameof(AutoRedrawChanged)) +public sealed class AutoRedrawChanged(Logger log) + : EventBase(nameof(AutoRedrawChanged), log) { public enum Priority { /// StateApi = int.MinValue, } -} \ No newline at end of file +} diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 9ee3e1b..67c2c76 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -1,64 +1,60 @@ using Glamourer.Automation; -using OtterGui.Classes; +using Glamourer.Designs; +using Luna; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when an automated design is changed in any way. -/// -/// Parameter is the type of the change -/// Parameter is the added or changed design set or null on deletion. -/// Parameter is additional data depending on the type of change. -/// -/// -public sealed class AutomationChanged() - : EventWrapper(nameof(AutomationChanged)) +/// Triggered when an automated design is changed in any way. +public sealed class AutomationChanged(Logger log) + : EventBase(nameof(AutomationChanged), log) { public enum Type { - /// Add a new set. Names and identifiers do not have to be unique. It is not enabled by default. Additional data is the index it gets added at and the name [(int, string)]. + /// Add a new set. Names and identifiers do not have to be unique. It is not enabled by default. AddedSet, - /// Delete a given set. Additional data is the index it got removed from [int]. + /// Delete a given set. DeletedSet, - /// Rename a given set. Names do not have to be unique. Additional data is the old name and the new name [(string, string)]. + /// Rename a given set. Names do not have to be unique. RenamedSet, - /// Move a given set to a different position. Additional data is the old index of the set and the new index of the set [(int, int)]. + /// Move a given set to a different position. MovedSet, - /// Change the identifier a given set is associated with to another one. Additional data is the old identifier and the new one, and a potentially disabled other design set. [(ActorIdentifier[], ActorIdentifier, AutoDesignSet?)]. + /// Change the identifier a given set is associated with to another one. ChangeIdentifier, - /// Toggle the enabled state of a given set. Additional data is the thus disabled other set, if any [AutoDesignSet?]. + /// Toggle the enabled state of a given set. ToggleSet, - /// Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. + /// Change the used base state of a given set. ChangedBase, - /// Change the resetting of temporary settings for a given set. Additional data is the new value. + /// Change the resetting of temporary settings for a given set. ChangedTemporarySettingsReset, - /// Add a new associated design to a given set. Additional data is the index it got added at [int]. + /// Add a new associated design to a given set. AddedDesign, - /// Remove a given associated design from a given set. Additional data is the index it got removed from [int]. + /// Remove a given associated design from a given set. DeletedDesign, - /// Move a given associated design in the list of a given set. Additional data is the index that got moved and the index it got moved to [(int, int)]. + /// Move a given associated design in the list of a given set. MovedDesign, - /// Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, IDesignStandIn, IDesignStandIn)]. + /// Change the linked design in an associated design for a given set. ChangedDesign, - /// Change the job condition in an associated design for a given set. Additional data is the index of the changed associated design, the old job group and the new job group [(int, JobGroup, JobGroup)]. + /// Change the job condition in an associated design for a given set. ChangedConditions, - /// Change the application type in an associated design for a given set. Additional data is the index of the changed associated design, the old type and the new type. [(int, AutoDesign.Type, AutoDesign.Type)]. + /// Change the application type in an associated design for a given set. ChangedType, - /// Change the additional data for a specific design type. Additional data is the index of the changed associated design and the new data. [(int, object)] + /// Change the additional data for a specific design type. ChangedData, } @@ -76,4 +72,92 @@ public sealed class AutomationChanged() /// RandomRestrictionDrawer = -1, } + + /// The type of change. + public record Arguments(Type Type, AutoDesignSet Set) + { + public T As() where T : Arguments + => (T)this; + } + + /// The added set. + /// The index the set was added at. + /// The name of the added set. + public sealed record AddedSetArguments(AutoDesignSet Set, int Index, string Name) : Arguments(Type.AddedSet, Set); + + /// The deleted set. + /// The index the set was deleted from. + public sealed record DeletedSetArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedSet, Set); + + /// The renamed set. + /// The old name of the set. + /// The new name of the set. + public sealed record RenamedSetArguments(AutoDesignSet Set, string OldName, string NewName) : Arguments(Type.RenamedSet, Set); + + /// The moved set. + /// The index the set was moved from. + /// The index the set was moved to. + public sealed record MovedSetArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedSet, Set); + + /// The set that got its associated identifiers changed. + /// The prior identifiers that got removed. + /// The new identifiers associated with the set. + /// A set that was associated with the new identifiers before and got disabled through this change, or null. + public sealed record ChangeIdentifierArguments( + AutoDesignSet Set, + ActorIdentifier[] OldIdentifiers, + ActorIdentifier NewIdentifier, + AutoDesignSet? DisabledSet) : Arguments(Type.ChangeIdentifier, Set); + + /// The set that got toggled on or off. + /// A set with the same association that got disabled due to this change, or null. + public sealed record ToggleSetArguments(AutoDesignSet Set, AutoDesignSet? DisabledSet) : Arguments(Type.ToggleSet, Set); + + /// The set that changed its base state. + /// The old base state of the set. + /// The new base state of the set. + public sealed record ChangedBaseArguments(AutoDesignSet Set, AutoDesignSet.Base OldBase, AutoDesignSet.Base NewBase) : Arguments(Type.ChangedBase, Set); + + /// The set that changed whether it resets all temporary settings. + /// The new state of resetting temporary settings. + public sealed record ChangedTemporarySettingsResetArguments(AutoDesignSet Set, bool NewValue) : Arguments(Type.ChangedTemporarySettingsReset, Set); + + /// The set that added a new design. + /// The index the new design was added in the set. + public sealed record AddedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.AddedDesign, Set); + + /// The set that removed a design. + /// The index the design was removed from. + public sealed record DeletedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedDesign, Set); + + /// The set that moved a design. + /// The index the design was moved from in the set. + /// The index the design was moved from to the set. + public sealed record MovedDesignArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedDesign, Set); + + /// The set that changed a design. + /// The index of the changed design. + /// The design previously assigned to the set. + /// The design the old one was changed to. + public sealed record ChangedDesignArguments(AutoDesignSet Set, int DesignIndex, IDesignStandIn OldDesign, IDesignStandIn NewDesign) + : Arguments(Type.ChangedDesign, Set); + + /// The set that changed a job group condition. + /// The index of the changed design. + /// The prior job condition for the design. + /// The new job condition for the design. + public sealed record ChangedConditionsArguments(AutoDesignSet Set, int DesignIndex, JobGroup OldGroup, JobGroup NewGroup) + : Arguments(Type.ChangedConditions, Set); + + /// The set that changed its application type. + /// The index of the changed design. + /// The old application flags. + /// The new application flags. + public sealed record ChangedTypeArguments(AutoDesignSet Set, int DesignIndex, ApplicationType OldType, ApplicationType NewType) + : Arguments(Type.ChangedType, Set); + + /// The set that got a design data changed. + /// The index of the changed design. + /// The new additional data for the changed design. + public sealed record ChangedDataArguments(AutoDesignSet Set, int DesignIndex, object NewData) : Arguments(Type.ChangedData, Set); } diff --git a/Glamourer/Events/BonusSlotUpdating.cs b/Glamourer/Events/BonusSlotUpdating.cs index 3f6e761..b796744 100644 --- a/Glamourer/Events/BonusSlotUpdating.cs +++ b/Glamourer/Events/BonusSlotUpdating.cs @@ -1,25 +1,32 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags a bonus slot for an update. -/// -/// Parameter is the model with a flagged slot. -/// Parameter is the bonus slot changed. -/// Parameter is the model values to change the bonus piece to. -/// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. -/// -/// -public sealed class BonusSlotUpdating() - : EventWrapperRef34(nameof(BonusSlotUpdating)) +/// Triggered when a model flags a bonus slot for an update. +public sealed class BonusSlotUpdating(Logger log) + : EventBase(nameof(BonusSlotUpdating), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Model model, BonusItemFlag flag, ref CharacterArmor armor, ref ulong returnValue) + { + /// The draw object with an updated bonus slot. + public readonly Model Model = model; + + /// The updated slot. + public readonly BonusItemFlag Slot = flag; + + /// The model data for the new bonus slot. This can be changed. + public ref CharacterArmor Armor = ref armor; + + /// The return value of the event. If this is , the original function will be called and its return value used, otherwise this value will be returned. + public ref ulong ReturnValue = ref returnValue; + } } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index b6e708e..b8cc7b0 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,20 +1,14 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui; -using OtterGui.Classes; +using Glamourer.Gui.Tabs.DesignTab; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a Design is edited in any way. -/// -/// Parameter is the type of the change -/// Parameter is the changed Design. -/// Parameter is any additional data depending on the type of change. -/// -/// -public sealed class DesignChanged() - : EventWrapper(nameof(DesignChanged)) +/// Triggered when a Design is edited in any way. +public sealed class DesignChanged(Logger log) + : EventBase(nameof(DesignChanged), log) { public enum Type { @@ -138,10 +132,13 @@ public sealed class DesignChanged() /// AutoDesignManager = 1, - /// + /// DesignFileSystem = 0, - /// + /// + DesignHeader = 0, + + /// DesignFileSystemSelector = -1, /// @@ -150,4 +147,10 @@ public sealed class DesignChanged() /// EditorHistory = -1000, } + + /// Arguments for the DesignChanged event. + /// The type of changed. + /// The changed design. + /// A transaction with further data corresponding to the change. + public readonly record struct Arguments(Type Type, Design Design, ITransaction? Transaction = null); } diff --git a/Glamourer/Events/EquipSlotUpdating.cs b/Glamourer/Events/EquipSlotUpdating.cs index a2daf85..da2face 100644 --- a/Glamourer/Events/EquipSlotUpdating.cs +++ b/Glamourer/Events/EquipSlotUpdating.cs @@ -1,25 +1,32 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags an equipment slot for an update. -/// -/// Parameter is the model with a flagged slot. -/// Parameter is the equipment slot changed. -/// Parameter is the model values to change the equipment piece to. -/// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. -/// -/// -public sealed class EquipSlotUpdating() - : EventWrapperRef34(nameof(EquipSlotUpdating)) +/// Triggered when a model flags an equipment slot for an update. +public sealed class EquipSlotUpdating(Logger log) + : EventBase(nameof(EquipSlotUpdating), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) + { + /// The draw object with an updated equipment slot. + public readonly Model Model = model; + + /// The updated slot. + public readonly EquipSlot Slot = slot; + + /// The model data for the new equipment slot. This can be changed. + public ref CharacterArmor Armor = ref armor; + + /// The return value of the event. If this is , the original function will be called and its return value used, otherwise this value will be returned. + public ref ulong ReturnValue = ref returnValue; + } } \ No newline at end of file diff --git a/Glamourer/Events/EquippedGearset.cs b/Glamourer/Events/EquippedGearset.cs index c4252eb..012b59f 100644 --- a/Glamourer/Events/EquippedGearset.cs +++ b/Glamourer/Events/EquippedGearset.cs @@ -1,23 +1,23 @@ -using OtterGui.Classes; +using Luna; +using Penumbra.String; namespace Glamourer.Events; -/// -/// Triggered when the player equips a gear set. -/// -/// Parameter is the name of the gear set. -/// Parameter is the id of the gear set. -/// Parameter is the id of the prior gear set. -/// Parameter is the id of the associated glamour. -/// Parameter is the job id of the associated job. -/// -/// -public sealed class EquippedGearset() - : EventWrapper(nameof(EquippedGearset)) +/// Triggered when the player equips a gear set. +public sealed class EquippedGearset(Logger log) + : EventBase(nameof(EquippedGearset), log) { public enum Priority { /// AutoDesignApplier = 0, } + + /// Arguments for the EquippedGearset event. + /// The name of the equipped gear set. + /// The ID of the equipped gear set. + /// The ID of the gear set previously equipped. + /// The ID of the associated glamour plate. + /// The job ID of the associated job. + public readonly record struct Arguments(ByteString Name, int Id, int PriorId, int GlamourId, byte JobId); } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index 44421a0..08a75a6 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -1,9 +1,9 @@ using Dalamud.Plugin.Services; -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -public sealed class GPoseService : EventWrapper +public sealed class GPoseService : EventBase { private readonly IFramework _framework; private readonly IClientState _state; @@ -19,8 +19,8 @@ public sealed class GPoseService : EventWrapper public bool InGPose { get; private set; } - public GPoseService(IFramework framework, IClientState state) - : base(nameof(GPoseService)) + public GPoseService(IFramework framework, IClientState state, Logger log) + : base(nameof(GPoseService), log) { _framework = framework; _state = state; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 620bdab..31daae1 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -1,21 +1,23 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; /// -/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) -/// This defines an endpoint for when the gameState is updated. -/// -/// The model draw object associated with the finished load (Also fired by other players on render) -/// +/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) +/// This defines an endpoint for when the gameState is updated. /// -public sealed class GearsetDataLoaded() - : EventWrapper(nameof(GearsetDataLoaded)) +public sealed class GearsetDataLoaded(Logger log) + : EventBase(nameof(GearsetDataLoaded), log) { public enum Priority { /// StateListener = 0, } + + /// Arguments for the GearsetDataLoaded event. + /// The actor that loaded a gear set. + /// The draw object that finished the load. + public readonly record struct Arguments(Actor Actor, Model Model); } diff --git a/Glamourer/Events/HeadGearVisibilityChanged.cs b/Glamourer/Events/HeadGearVisibilityChanged.cs index d8f722b..eaf067c 100644 --- a/Glamourer/Events/HeadGearVisibilityChanged.cs +++ b/Glamourer/Events/HeadGearVisibilityChanged.cs @@ -1,21 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the visibility of head gear is changed. -/// -/// Parameter is the actor with changed head gear visibility. -/// Parameter is the new state. -/// -/// -public sealed class HeadGearVisibilityChanged() - : EventWrapperRef2(nameof(HeadGearVisibilityChanged)) +/// Triggered when the visibility of head gear is changed. +public sealed class HeadGearVisibilityChanged(Logger log) + : EventBase(nameof(HeadGearVisibilityChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool visible) + { + /// The actor whose head gear visibility changed. + public readonly Actor Actor = actor; + + /// The new visibility state. + public ref bool Visible = ref visible; + } } diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs index 9d24a03..6f2952c 100644 --- a/Glamourer/Events/MovedEquipment.cs +++ b/Glamourer/Events/MovedEquipment.cs @@ -1,4 +1,4 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -10,12 +10,14 @@ namespace Glamourer.Events; /// Parameter is an array of slots updated and corresponding item ids and stains. /// /// -public sealed class MovedEquipment() - : EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment)) +public sealed class MovedEquipment(Logger log) + : EventBase(nameof(MovedEquipment), log) { public enum Priority { /// StateListener = 0, } + + public readonly record struct Arguments(params (EquipSlot, uint, StainIds)[] Items); } diff --git a/Glamourer/Events/ObjectUnlocked.cs b/Glamourer/Events/ObjectUnlocked.cs index b15acd2..e2d103c 100644 --- a/Glamourer/Events/ObjectUnlocked.cs +++ b/Glamourer/Events/ObjectUnlocked.cs @@ -1,17 +1,10 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a new item or customization is unlocked. -/// -/// Parameter is the type of the unlocked object -/// Parameter is the id of the unlocked object. -/// Parameter is the timestamp of the unlock. -/// -/// -public sealed class ObjectUnlocked() - : EventWrapper(nameof(ObjectUnlocked)) +/// Triggered when a new item or customization is unlocked. +public sealed class ObjectUnlocked(Logger log) + : EventBase(nameof(ObjectUnlocked), log), IRequiredService { public enum Type { @@ -21,8 +14,15 @@ public sealed class ObjectUnlocked() public enum Priority { - /// + /// /// Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework. UnlockTable = 0, } + + + /// Arguments for the ObjectUnlocked event. + /// The type of the unlocked object. + /// The ID of the unlocked object. + /// The timestamp of the unlock event. + public readonly record struct Arguments(Type Type, uint Id, DateTimeOffset Timestamp); } diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 0975670..0546daa 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -1,22 +1,22 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; /// /// Triggered when Penumbra is reloaded. /// -public sealed class PenumbraReloaded() - : EventWrapper(nameof(PenumbraReloaded)) +public sealed class PenumbraReloaded(Logger log) + : EventBase(nameof(PenumbraReloaded), log) { public enum Priority { - /// + /// ChangeCustomizeService = 0, - /// + /// VisorService = 0, - /// + /// VieraEarService = 0, } } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 2bcc6fe..a1bf6df 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -1,33 +1,37 @@ using Glamourer.Api.Enums; using Glamourer.Designs.History; -using Glamourer.Interop.Structs; using Glamourer.State; -using OtterGui.Classes; using Penumbra.GameData.Interop; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a Design is edited in any way. -/// -/// Parameter is the type of the change -/// Parameter is the changed saved state. -/// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. -/// -/// -public sealed class StateChanged() - : EventWrapper(nameof(StateChanged)) +/// Triggered when a state changes in any way. +public sealed class StateChanged(Logger log) + : EventBase(nameof(StateChanged), log) { public enum Priority { /// GlamourerIpc = int.MinValue, - /// + /// PenumbraAutoRedraw = 0, /// EditorHistory = -1000, } + + /// Arguments for the StateChanged event. + /// The type of change that occured. + /// The source of the change. + /// The changed state. + /// Any currently affected actors. + /// Additional data depending on the type of change. + public readonly record struct Arguments( + StateChangeType Type, + StateSource Source, + ActorState State, + ActorData Actors, + ITransaction? Transaction = null); } diff --git a/Glamourer/Events/StateFinalized.cs b/Glamourer/Events/StateFinalized.cs index 0ccaa8b..8944835 100644 --- a/Glamourer/Events/StateFinalized.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -1,24 +1,22 @@ using Glamourer.Api; using Glamourer.Api.Enums; -using Glamourer.Interop.Structs; -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. -/// -/// Parameter is the operation that finished updating the saved state. -/// Parameter is the existing actors using this saved state. -/// -/// -public sealed class StateFinalized() - : EventWrapper(nameof(StateFinalized)) +/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. +public sealed class StateFinalized(Logger log) + : EventBase(nameof(StateFinalized), log) { public enum Priority { /// StateApi = int.MinValue, } + + /// Arguments for the StateFinalized event. + /// The operation that finished updating the saved state. + /// The existing actors using this saved state at the moment. + public readonly record struct Arguments(StateFinalizationType Type, ActorData Actors); } diff --git a/Glamourer/Events/TabSelected.cs b/Glamourer/Events/TabSelected.cs index 8dcf917..1b81d46 100644 --- a/Glamourer/Events/TabSelected.cs +++ b/Glamourer/Events/TabSelected.cs @@ -1,25 +1,24 @@ using Glamourer.Designs; using Glamourer.Gui; -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when an automated design is changed in any way. -/// -/// Parameter is the tab to select. -/// Parameter is the design to select if the tab is the designs tab. -/// -/// -public sealed class TabSelected() - : EventWrapper(nameof(TabSelected)) +/// Triggered to select a tab or design. +public sealed class TabSelected(Logger log) + : EventBase(nameof(TabSelected), log) { public enum Priority { - /// + /// DesignSelector = 0, - /// + /// MainWindow = 1, } + + /// Arguments for the TabSelected event. + /// The tab to be selected. + /// The design to be selected in the Designs tab. + public readonly record struct Arguments(MainTabType Type, Design? Design = null); } diff --git a/Glamourer/Events/VieraEarStateChanged.cs b/Glamourer/Events/VieraEarStateChanged.cs index 65730b8..5390469 100644 --- a/Glamourer/Events/VieraEarStateChanged.cs +++ b/Glamourer/Events/VieraEarStateChanged.cs @@ -1,22 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the state of viera ear visibility for any draw object is changed. -/// -/// Parameter is the model with a changed viera ear visibility state. -/// Parameter is the new state. -/// Parameter is whether to call the original function. -/// -/// -public sealed class VieraEarStateChanged() - : EventWrapperRef2(nameof(VieraEarStateChanged)) +/// Triggered when the state of viera ear visibility for any draw object is changed. +public sealed class VieraEarStateChanged(Logger log) + : EventBase(nameof(VieraEarStateChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool state) + { + /// The actor with a changed viera ear visibility state. + public readonly Actor Actor = actor; + + /// The new ear visibility state. + public ref bool State = ref state; + } } diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index 03b7336..b070bda 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -1,22 +1,27 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the state of a visor for any draw object is changed. -/// -/// Parameter is the model with a changed visor state. -/// Parameter is the new state. -/// Parameter is whether to call the original function. -/// -/// -public sealed class VisorStateChanged() - : EventWrapperRef3(nameof(VisorStateChanged)) +/// Triggered when the state of a visor for any draw object is changed. +public sealed class VisorStateChanged(Logger log) + : EventBase(nameof(VisorStateChanged), log) { public enum Priority { /// StateListener = 0, } -} \ No newline at end of file + + public ref struct Arguments(Model model, bool visorState, ref bool value) + { + /// The model with a changed visor state. + public readonly Model Model = model; + + /// The game's visor state. + public readonly bool NewVisorState = visorState; + + /// The actual new value + public ref bool Value = ref value; + } +} diff --git a/Glamourer/Events/WeaponLoading.cs b/Glamourer/Events/WeaponLoading.cs index fda0b2f..fe29702 100644 --- a/Glamourer/Events/WeaponLoading.cs +++ b/Glamourer/Events/WeaponLoading.cs @@ -1,20 +1,13 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags an equipment slot for an update. -/// -/// Parameter is the actor that has its weapons changed. -/// Parameter is the equipment slot changed (Mainhand or Offhand). -/// Parameter is the model values to change the weapon to. -/// -/// -public sealed class WeaponLoading() - : EventWrapperRef3(nameof(WeaponLoading)) +/// Triggered when a model flags an equipment slot for an update. +public sealed class WeaponLoading(Logger log) + : EventBase(nameof(WeaponLoading), log) { public enum Priority { @@ -24,4 +17,16 @@ public sealed class WeaponLoading() /// AutoDesignApplier = -1, } + + public ref struct Arguments(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + { + /// The actor that has its weapons changed. + public readonly Actor Actor = actor; + + /// The changed equipment slot (either Mainhand or Offhand). + public readonly EquipSlot Slot = slot; + + /// The model data for the new weapon. + public ref CharacterWeapon Weapon = ref weapon; + } } diff --git a/Glamourer/Events/WeaponVisibilityChanged.cs b/Glamourer/Events/WeaponVisibilityChanged.cs index f75fa68..5683336 100644 --- a/Glamourer/Events/WeaponVisibilityChanged.cs +++ b/Glamourer/Events/WeaponVisibilityChanged.cs @@ -1,20 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the visibility of weapons is changed. -/// -/// Parameter is the actor with changed weapon visibility. -/// Parameter is the new state. -/// -/// -public sealed class WeaponVisibilityChanged() : EventWrapperRef2(nameof(WeaponVisibilityChanged)) +/// Triggered when the visibility of weapons is changed. +public sealed class WeaponVisibilityChanged(Logger log) + : EventBase(nameof(WeaponVisibilityChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool value) + { + /// The actor with changed weapon visibility. + public readonly Actor Actor = actor; + + /// The new state. + public ref bool Value = ref value; + } } diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index abcc90d..c388a5f 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,5 +1,4 @@ using ImSharp; -using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; @@ -139,10 +138,10 @@ public class CustomizeSet _ => Invalid(out custom), }; - int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) + static int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) { - var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v); - if (val == null) + var (idx, val) = list.Cast().Index().FirstOrDefault(p => p.Item!.Value.Value == v); + if (val is null) { output = null; return -1; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 754914e..fc5d67d 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -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; @@ -9,7 +10,7 @@ using Glamourer.State; using ImSharp; using Luna; using Penumbra.GameData.Interop; -using Logger = OtterGui.Log.Logger; +using Vortice.Direct3D11.Debug; namespace Glamourer; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 4bd1c25..7ce1901 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer @@ -22,10 +22,14 @@ + + false + false + + - diff --git a/Glamourer/Glamourer.csproj.DotSettings b/Glamourer/Glamourer.csproj.DotSettings new file mode 100644 index 0000000..9252e38 --- /dev/null +++ b/Glamourer/Glamourer.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 12c14f8..a1e9780 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,3 +1,4 @@ +using Glamourer.Config; using ImSharp; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index cd7f077..4665880 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,4 +1,5 @@ using System.Text.Unicode; +using Glamourer.Config; using ImSharp; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 89e5688..287176b 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -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; @@ -11,13 +12,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer( +public sealed partial class CustomizationDrawer( ITextureProvider textures, CustomizeService service, Configuration config, FavoriteManager favorites, HeightService heightService) - : IDisposable + : IDisposable, IUiService { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(textures); @@ -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) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 07bb9e0..e9004ac 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.PalettePlus; using Glamourer.State; @@ -125,7 +126,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import 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(); diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 36cc5fa..f19ea5c 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -1,6 +1,5 @@ using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using ImSharp; @@ -9,7 +8,7 @@ using Luna; namespace Glamourer.Gui; public abstract class DesignComboBase( - EphemeralConfig config, + Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -17,18 +16,18 @@ public abstract class DesignComboBase( DesignFileSystem designFileSystem) : FilterComboBase(new DesignFilter(), ConfigData.Default with { ComputeWidth = true }) { - protected readonly 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; @@ -54,7 +53,7 @@ public abstract class DesignComboBase( if (CurrentDesign is Design design) { if (Im.Item.RightClicked() && Im.Io.KeyControl) - TabSelected.Invoke(MainTabType.Designs, design); + TabSelected.Invoke(new TabSelected.Arguments(MainTabType.Designs, design)); Im.Tooltip.OnHover("Control + Right-Click to move to design."u8); } else @@ -81,7 +80,7 @@ public abstract class DesignComboBase( Im.Text("Currently resolving to "u8); using var color = ImGuiColor.Text.Push(DesignColors.GetColor(linkedDesign)); Im.Line.NoSpacing(); - Im.Text(linkedDesign.Name.Text); + Im.Text(linkedDesign.Name); } else { @@ -118,7 +117,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) { @@ -130,9 +132,9 @@ public abstract class DesignComboBase( private void OnDesignColorChanged() => Dirty |= IManagedCache.DirtyFlags.Custom; - private void OnDesignChanged(DesignChanged.Type type, Design? _1, ITransaction? _2 = null) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type switch + if (arguments.Type switch { DesignChanged.Type.Created => true, DesignChanged.Type.Renamed => true, @@ -203,7 +205,7 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService } - public QuickDesignCombo(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) { @@ -212,18 +214,18 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService DesignChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignCombo); } - private void OnDesignChanged(DesignChanged.Type type, Design changedDesign, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - switch (type) + switch (arguments.Type) { case DesignChanged.Type.Created: // If the quick design bar has no selection, select the new design if it supports the bar. - if (QuickDesign is null && changedDesign.QuickDesign) - QuickDesign = changedDesign; + if (QuickDesign is null && arguments.Design.QuickDesign) + QuickDesign = arguments.Design; break; case DesignChanged.Type.Deleted: // If the deleted design was selected, select the first design that supports the bar, if any. - if (QuickDesign == changedDesign) + if (QuickDesign == arguments.Design) QuickDesign = Designs.Designs.FirstOrDefault(d => d.QuickDesign); break; case DesignChanged.Type.ReloadedAll: @@ -232,10 +234,10 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService break; case DesignChanged.Type.QuickDesignBar: // If the quick design support of a design was changed, select the new design if the bar has no selection and the design now supports it, - if (QuickDesign is null && changedDesign.QuickDesign) - QuickDesign = changedDesign; + if (QuickDesign is null && arguments.Design.QuickDesign) + QuickDesign = arguments.Design; // or select the first design that supports the bar, if any, if the support was removed from the currently selected design. - else if (QuickDesign == changedDesign && !changedDesign.QuickDesign) + else if (QuickDesign == arguments.Design && !arguments.Design.QuickDesign) QuickDesign = Designs.Designs.FirstOrDefault(d => d.QuickDesign); break; } @@ -264,7 +266,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable { public Design? NewSelection { get; private set; } - public LinkDesignCombo(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) { @@ -287,15 +289,16 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable public void Dispose() => DesignChanged.Unsubscribe(OnDesignChanged); - private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type is DesignChanged.Type.Deleted && design == NewSelection || type is DesignChanged.Type.ReloadedAll) + if (arguments.Type is DesignChanged.Type.Deleted && arguments.Design == NewSelection + || arguments.Type is DesignChanged.Type.ReloadedAll) NewSelection = null; } } public sealed class RandomDesignCombo( - EphemeralConfig config, + Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -307,21 +310,17 @@ 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), + RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value, out var g) ? g : Guid.Empty), + _ => null, }; } public bool Draw(RandomPredicate.Exact exact, [NotNullWhen(true)] out Design? newDesign, float width) { var design = GetDesign(exact); - if (Draw(StringU8.Empty, design?.ResolveName(Config.IncognitoMode) ?? $"Not Found [{exact.Value.Text}]", StringU8.Empty, width, + if (Draw(StringU8.Empty, design?.ResolveName(Config.IncognitoMode) ?? $"Not Found [{exact.Value}]", StringU8.Empty, width, out var newItem) && newItem.Design is Design d) { @@ -346,7 +345,7 @@ public sealed class SpecialDesignCombo : DesignComboBase, IUiService private readonly CacheItem _revert; private readonly CacheItem _quick; - public SpecialDesignCombo(EphemeralConfig config, + public SpecialDesignCombo(Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 9e9fa80..e5623cc 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -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; @@ -24,6 +25,7 @@ public enum QdbButtons ReapplyAutomation = 0x40, ResetSettings = 0x80, RevertAdvancedCustomization = 0x100, + ToggleMainWindow = 0x200, } public sealed class DesignQuickBar : Window, IDisposable @@ -45,6 +47,8 @@ public sealed class DesignQuickBar : Window, IDisposable private int _numButtons; private readonly StringBuilder _tooltipBuilder = new(512); + public event Action? ToggleMainWindow; + public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking) @@ -77,38 +81,41 @@ public sealed class DesignQuickBar : Window, IDisposable public override void PreDraw() { Flags = GetFlags; - UpdateWidth(); _style.Push(ImStyleDouble.WindowPadding, new Vector2(Im.Style.GlobalScale * 4)) .Push(ImStyleSingle.WindowBorderThickness, 0); _style.Push(ImGuiColor.WindowBackground, ColorId.QuickDesignBg.Value()) .Push(ImGuiColor.Button, ColorId.QuickDesignButton.Value()) .Push(ImGuiColor.FrameBackground, ColorId.QuickDesignFrame.Value()); + + UpdateWidth(); } public override void PostDraw() => _style.Dispose(); - public void DrawAtEnd(float yPos) + public void DrawAtEnd(float yPos, bool mainWindow) { - var width = UpdateWidth(); + var numButtons = CalculateButtonCount(mainWindow); + var width = CalculateWidth(numButtons, mainWindow); Im.Cursor.Position = new Vector2(Im.ContentRegion.Maximum.X - width, yPos - Im.Style.GlobalScale); - Draw(); + Draw(Im.ContentRegion.Available.X, numButtons, mainWindow); } public override void Draw() - => Draw(Im.ContentRegion.Available.X); + => Draw(Im.ContentRegion.Available.X, _numButtons, false); - private void Draw(float width) + private void Draw(float width, int numButtons, bool mainWindow) { using var group = Im.Group(); var spacing = Im.Style.ItemInnerSpacing; using var style = ImStyleDouble.ItemSpacing.Push(spacing); var buttonSize = new Vector2(Im.Style.FrameHeight); PrepareButtons(); + DrawToggleMainWindowButton(buttonSize, mainWindow); if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) { - var comboSize = width - _numButtons * (buttonSize.X + spacing.X); + var comboSize = width - numButtons * (buttonSize.X + spacing.X); _designCombo.Draw(StringU8.Empty, comboSize); Im.Line.Same(); DrawApplyButton(buttonSize); @@ -485,6 +492,16 @@ public sealed class DesignQuickBar : Window, IDisposable } } + private void DrawToggleMainWindowButton(Vector2 buttonSize, bool mainWindow) + { + if (mainWindow || !_config.QdbButtons.HasFlag(QdbButtons.ToggleMainWindow)) + return; + + if (ImEx.Icon.Button(FontAwesomeIcon.TheaterMasks.Icon(), "Toggle Glamourer's main window."u8, ToggleMainWindow is null, buttonSize)) + ToggleMainWindow?.Invoke(); + Im.Line.Same(); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(AwesomeIcon icon, Vector2 buttonSize, int available) { var enumerator = _tooltipBuilder.GetChunks(); @@ -516,44 +533,51 @@ public sealed class DesignQuickBar : Window, IDisposable return _keyState[key.Hotkey] && key.Modifiers.IsActive(); } - private float UpdateWidth() + private int CalculateButtonCount(bool mainWindow) { - _numButtons = 0; + var numButtons = 0; if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll)) - ++_numButtons; + ++numButtons; if (_config.EnableAutoDesigns) { if (_config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation)) - ++_numButtons; + ++numButtons; } if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) - ++_numButtons; + ++numButtons; if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) - { - ++_numButtons; - Size = new Vector2((7 + _numButtons) * Im.Style.FrameHeight + _numButtons * Im.Style.ItemInnerSpacing.X, - Im.Style.FrameHeight); - } - else - { - Size = new Vector2( - _numButtons * Im.Style.FrameHeight - + (_numButtons - 1) * Im.Style.ItemInnerSpacing.X - + Im.Style.WindowPadding.X * 2, - Im.Style.FrameHeight); - } + ++numButtons; + if (!mainWindow && _config.QdbButtons.HasFlag(QdbButtons.ToggleMainWindow)) + ++numButtons; - return Size.Value.X; + return numButtons; + } + + private float CalculateWidth(int numButtons, bool mainWindow) + { + var content = _config.QdbButtons.HasFlag(QdbButtons.ApplyDesign) + ? (7 + numButtons) * Im.Style.FrameHeight + numButtons * Im.Style.ItemInnerSpacing.X + : numButtons * Im.Style.FrameHeight + (numButtons - 1) * Im.Style.ItemInnerSpacing.X; + var padding = mainWindow ? 0 : Im.Style.WindowPadding.X * 2; + + return content + padding; + } + + private void UpdateWidth() + { + _numButtons = CalculateButtonCount(false); + var width = CalculateWidth(_numButtons, false); + Size = new Vector2(width, Im.Style.FrameHeight); } } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 720174a..5c874bd 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Config; using Glamourer.Events; using Glamourer.Gui.Materials; using Glamourer.Services; @@ -11,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public class EquipmentDrawer +public sealed class EquipmentDrawer : IUiService { private const float DefaultWidth = 280; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index fa3c93f..ab53622 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,10 +1,12 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Config; using ImSharp; +using Luna; namespace Glamourer.Gui; -public class GenericPopupWindow : Luna.Window +public sealed class GenericPopupWindow : Window { private readonly Configuration _config; private readonly ICondition _condition; diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 0c285c3..b0195a4 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -1,9 +1,10 @@ +using Glamourer.Config; using ImSharp; using Luna; namespace Glamourer.Gui; -public class GlamourerChangelog +public sealed class GlamourerChangelog : IUiService { public const int LastChangelogVersion = 0; private readonly Configuration _config; diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 76f107e..9ed596e 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -1,10 +1,12 @@ using Dalamud.Interface; -using Dalamud.Interface.Windowing; +using Glamourer.Config; using Glamourer.Gui.Tabs.UnlocksTab; +using Luna; +using WindowSystem = Dalamud.Interface.Windowing.WindowSystem; namespace Glamourer.Gui; -public class GlamourerWindowSystem : IDisposable +public sealed class GlamourerWindowSystem : IDisposable, IUiService { private readonly WindowSystem _windowSystem = new("Glamourer"); private readonly IUiBuilder _uiBuilder; diff --git a/Glamourer/Gui/MainTabBar.cs b/Glamourer/Gui/MainTabBar.cs index 2b2b398..4abf279 100644 --- a/Glamourer/Gui/MainTabBar.cs +++ b/Glamourer/Gui/MainTabBar.cs @@ -1,4 +1,4 @@ -using Glamourer.Designs; +using Glamourer.Config; using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; @@ -30,8 +30,8 @@ public sealed class MainTabBar : TabBar NextTab = _config.SelectedMainTab; } - private void OnEvent(MainTabType tab, Design? _) - => NextTab = tab; + private void OnEvent(in TabSelected.Arguments arguments) + => NextTab = arguments.Type; private void OnTabSelected(in MainTabType arguments) { diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 49341eb..0e67519 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; +using Glamourer.Config; using Glamourer.Interop.Penumbra; using ImSharp; using Luna; @@ -21,7 +22,7 @@ public sealed class MainWindow : Window, IDisposable pi.UiBuilder.DisableGposeUiHide = true; SizeConstraints = new WindowSizeConstraints { - MinimumSize = new Vector2(700, 675), + MinimumSize = new Vector2(700, 675), MaximumSize = new Vector2(3840, 2160), }; _mainTabBar = mainTabBar; @@ -32,6 +33,7 @@ public sealed class MainWindow : Window, IDisposable IsOpen = _config.OpenWindowAtStart; _penumbra.DrawSettingsSection += _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + _quickBar.ToggleMainWindow += Toggle; } public void OpenSettings() @@ -49,7 +51,10 @@ public sealed class MainWindow : Window, IDisposable } public void Dispose() - => _penumbra.DrawSettingsSection -= _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + { + _penumbra.DrawSettingsSection -= _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + _quickBar.ToggleMainWindow -= Toggle; + } public override void Draw() { @@ -74,7 +79,7 @@ public sealed class MainWindow : Window, IDisposable { _mainTabBar.Draw(); if (_config.ShowQuickBarInTabs) - _quickBar.DrawAtEnd(yPos); + _quickBar.DrawAtEnd(yPos, true); } } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 74a41c4..f234313 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -2,6 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 9187649..79a669a 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.Interop.Material; using ImSharp; using Luna; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index eff2c5a..f0f5be4 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui.Customization; @@ -19,18 +20,18 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorPanel : IPanel { - private readonly ActorSelection _selection; - private readonly StateManager _stateManager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly ActorObjectManager _objects; - private readonly ImportService _importService; - private readonly DictModelChara _modelChara; - private readonly CustomizeParameterDrawer _parameterDrawer; - private readonly AdvancedDyePopup _advancedDyes; + private readonly ActorSelection _selection; + private readonly StateManager _stateManager; + private readonly CustomizationDrawer _customizationDrawer; + private readonly EquipmentDrawer _equipmentDrawer; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly Configuration _config; + private readonly DesignConverter _converter; + private readonly ActorObjectManager _objects; + private readonly ImportService _importService; + private readonly DictModelChara _modelChara; + private readonly CustomizeParameterDrawer _parameterDrawer; + private readonly AdvancedDyePopup _advancedDyes; public ActorPanel(StateManager stateManager, CustomizationDrawer customizationDrawer, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index c9ed4b8..d4f3f9c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -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, EphemeralConfig config) : IPanel +public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Config.EphemeralConfig config) : IPanel { public ReadOnlySpan Id => "ActorSelector"u8; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index f6f0c73..ed97ddc 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,4 +1,5 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.ActorTab; @@ -6,11 +7,13 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorTab : TwoPanelLayout, ITab { private readonly ActorSelection _selection; + private readonly UiConfig _uiConfig; public ActorTab(ActorSelector selector, ActorPanel panel, ActorFilter filter, SelectPlayerButton selectPlayer, - SelectTargetButton selectTarget, ActorsHeader header, ActorSelection selection) + SelectTargetButton selectTarget, ActorsHeader header, ActorSelection selection, UiConfig uiConfig) { _selection = selection; + _uiConfig = uiConfig; LeftPanel = selector; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); var footer = new ButtonFooter(); @@ -29,9 +32,18 @@ public sealed class ActorTab : TwoPanelLayout, ITab public void DrawContent() { _selection.Update(); - Draw(TwoPanelWidth.IndeterminateRelative); + Draw(_uiConfig.ActorsTabScale); } + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode); + + protected override float MinimumWidth + => LeftHeader.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; + public MainTabType Identifier => MainTabType.Actors; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs index d3bb802..4386d14 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorsHeader : SplitButtonHeader { - private readonly ActorSelection _selection; - private readonly EphemeralConfig _config; + private readonly ActorSelection _selection; + private readonly Config.EphemeralConfig _config; public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save, - UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, EphemeralConfig config) + UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Config.EphemeralConfig config) { _selection = selection; _config = config; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs index 8cc1dfe..8c0d1b6 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Config; using ImSharp; using Luna; using Penumbra.GameData.Actors; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs index f0c4cb1..93c5dbb 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs @@ -1,13 +1,28 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; -public sealed class AutomationHeader(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 Text + => _config.Ephemeral.IncognitoMode ? _selection.Incognito : _selection.Name; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs index 5682895..89d520d 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs @@ -13,7 +13,7 @@ public sealed class AutomationSelection : IUiService, IDisposable public int DraggedDesignIndex = -1; - public AutoDesignSet? Set { get; private set; } + public AutoDesignSet? Set { get; private set; } public int Index { get; private set; } = -1; public StringU8 Name { get; private set; } = NoSelection; public StringU8 Incognito { get; private set; } = NoSelection; @@ -29,16 +29,16 @@ public sealed class AutomationSelection : IUiService, IDisposable _automationChanged.Unsubscribe(OnAutomationChanged); } - private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChanged(in AutomationChanged.Arguments arguments) { - if (set != Set) + if (arguments.Set != Set) return; - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: Update(null); break; - case AutomationChanged.Type.RenamedSet: Name = new StringU8(set!.Name); break; - case AutomationChanged.Type.MovedSet: Index = (((int, int))data!).Item2; break; + case AutomationChanged.Type.RenamedSet: Name = new StringU8(arguments.Set!.Name); break; + case AutomationChanged.Type.MovedSet: Index = arguments.As().NewIndex; break; } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 559d3f7..4f70ab8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -1,9 +1,10 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; -public class AutomationTab : TwoPanelLayout, ITab +public sealed class AutomationTab : TwoPanelLayout, ITab { private readonly Configuration _config; @@ -30,7 +31,11 @@ public class AutomationTab : TwoPanelLayout, ITab => MainTabType.Automation; public void DrawContent() - { - Draw(TwoPanelWidth.IndeterminateRelative); - } + => Draw(_config.Ui.AutomationTabScale); + + protected override float MinimumWidth + => LeftFooter.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index c808ba9..f86c942 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,5 +1,6 @@ using Dalamud.Game.ClientState.Objects.Enums; using ImSharp; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Gui; @@ -7,13 +8,13 @@ using Penumbra.String; namespace Glamourer.Gui.Tabs.AutomationTab; -public class IdentifierDrawer( +public sealed class IdentifierDrawer( ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc, - HumanModelList humans) + HumanModelList humans) : IUiService { private readonly WorldCombo _worldCombo = new(dictWorld); private readonly HumanNpcCombo _humanNpcCombo = new(bNpcNames, dictModelChara, humans, bNpc); diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index df651b5..fc23aba 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -1,5 +1,6 @@ using Dalamud.Interface; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; @@ -19,21 +20,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable 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 config, AutoDesignManager autoDesignManager, - RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs) + RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignStorage designs) { _automationChanged = automationChanged; _config = config; _autoDesignManager = autoDesignManager; _randomDesignCombo = randomDesignCombo; _selection = selection; - _designFileSystem = designFileSystem; _designs = designs; _automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer); } @@ -168,7 +167,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable { ImEx.TextFrameAligned("that contain"u8); table.NextColumn(); - var data = contains.Value.Text; + var data = contains.Value; Im.Item.SetNextWidthFull(); if (Im.Input.Text("##match"u8, ref data, "Name, Path, or Identifier Contains..."u8)) { @@ -185,7 +184,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable { ImEx.TextFrameAligned("whose path starts with"u8); table.NextColumn(); - var data = startsWith.Value.Text; + var data = startsWith.Value; Im.Item.SetNextWidthFull(); if (Im.Input.Text("##startsWith"u8, ref data, "Path Starts With..."u8)) { @@ -203,7 +202,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable ImEx.TextFrameAligned("that contain the tag"u8); table.NextColumn(); Im.Item.SetNextWidthFull(); - var data = exact.Value.Text; + var data = exact.Value; if (Im.Input.Text("##color"u8, ref data, "Contained tag..."u8)) { if (data.Length is 0) @@ -220,7 +219,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable ImEx.TextFrameAligned("that are set to the color"u8); table.NextColumn(); Im.Item.SetNextWidthFull(); - var data = exact.Value.Text; + var data = exact.Value; if (Im.Input.Text("##color"u8, ref data, "Assigned Color is..."u8)) { if (data.Length is 0) @@ -264,23 +263,23 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable if (!Im.Item.Hovered()) return; - var designs = predicate.Get(_designs, _designFileSystem); + var designs = predicate.Get(_designs); LookupTooltip(designs); } - private void LookupTooltip(IEnumerable designs) + private static void LookupTooltip(IEnumerable 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); } @@ -375,7 +374,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private void DrawTotalPreview(IReadOnlyList list) { - var designs = IDesignPredicate.Get(list, _designs, _designFileSystem).ToList(); + var designs = IDesignPredicate.Get(list, _designs).ToList(); Im.Button(designs.Count > 0 ? $"All Restrictions Combined Match {designs.Count} Designs" : "None of the Restrictions Matches Any Designs"u8, Im.ContentRegion.Available with { Y = 0 }); @@ -413,26 +412,27 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable DrawManualInput(list); } - private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChange(in AutomationChanged.Arguments arguments) { - if (set != _set || _set is null) + if (arguments.Set != _set || _set is null) return; - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: - case AutomationChanged.Type.DeletedDesign when data is int index && _designIndex == index: + case AutomationChanged.Type.DeletedDesign when arguments.As().Index == _designIndex: Close(); break; - case AutomationChanged.Type.MovedDesign when data is (int from, int to): - if (_designIndex == from) - _designIndex = to; - else if (_designIndex < from && _designIndex > to) + case AutomationChanged.Type.MovedDesign: + var data = arguments.As(); + if (_designIndex == data.OldIndex) + _designIndex = data.NewIndex; + else if (_designIndex < data.OldIndex && _designIndex > data.NewIndex) _designIndex++; - else if (_designIndex > to && _designIndex < from) + else if (_designIndex > data.NewIndex && _designIndex < data.OldIndex) _designIndex--; break; - case AutomationChanged.Type.ChangedDesign when data is (int index, IDesignStandIn _, IDesignStandIn _) && index == _designIndex: + case AutomationChanged.Type.ChangedDesign when arguments.As().DesignIndex == _designIndex: Close(); break; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index cd676b1..c6026a8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -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; @@ -11,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.AutomationTab; -public class SetPanel( +public sealed class SetPanel( AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, @@ -33,8 +34,7 @@ public class SetPanel( public void Draw() { - using var child = Im.Child.Begin("##Panel"u8, Im.ContentRegion.Available, true); - if (!child || selection.Index < 0) + if (selection.Index < 0) return; using (Im.Group()) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 595882f..64b3f5d 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Events; using ImSharp; using Luna; @@ -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); } @@ -123,9 +125,9 @@ public sealed class SetSelector( base.Dispose(disposing); } - private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChanged(in AutomationChanged.Arguments arguments) { - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: case AutomationChanged.Type.AddedSet: diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index 21ae0fd..24b6fa6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,4 +1,5 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index d6f2369..e627395 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,19 +1,18 @@ using Glamourer.Gui.Tabs.DebugTab.IpcTester; using ImSharp; using Microsoft.Extensions.DependencyInjection; -using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) +public class DebugTabHeader(ReadOnlySpan label, params IGameDataDrawer[] subTrees) { - public string Label { get; } = label; + public StringU8 Label { get; } = new(label); public IReadOnlyList SubTrees { get; } = subTrees; public void Draw() { - using var h = ImRaii.CollapsingHeader(Label); + using var h = Im.Tree.HeaderId(Label); if (!h) return; @@ -32,7 +31,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateInterop(IServiceProvider provider) => new ( - "Interop", + "Interop"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -46,7 +45,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateGameData(IServiceProvider provider) => new ( - "Game Data", + "Game Data"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -62,7 +61,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateDesigns(IServiceProvider provider) => new ( - "Designs", + "Designs"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -72,7 +71,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateState(IServiceProvider provider) => new ( - "State", + "State"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService() @@ -81,7 +80,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateUnlocks(IServiceProvider provider) => new ( - "Unlocks", + "Unlocks"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index ef79666..ea8e97d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -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) { diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 7da78dd..79fe516 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -20,11 +20,11 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy foreach (var (idx, design) in designManager.Designs.Index()) { using var id = Im.Id.Push(idx); - using var t = Im.Tree.Node(design.Name.Text); + using var t = Im.Tree.Node(design.Name); 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,18 +50,18 @@ 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) { table.DrawColumn("Name"u8); - table.DrawColumn(d.Name.Text); + table.DrawColumn(d.Name); table.DrawColumn($"({d.Index})"); table.DrawColumn("Description (Hover)"u8); Im.Tooltip.OnHover(d.Description); @@ -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); diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index 0bf64bb..07071ef 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,4 +1,5 @@ -using Glamourer.State; +using Glamourer.Config; +using Glamourer.State; using ImSharp; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 40a73d1..7ca377d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -1,5 +1,5 @@ using Glamourer.State; -using OtterGui.Raii; +using ImSharp; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; @@ -17,7 +17,7 @@ public sealed class RetainedStatePanel(StateManager stateManager, ActorObjectMan { foreach (var (identifier, state) in stateManager.Where(kvp => !objectManager.ContainsKey(kvp.Key))) { - using var t = ImRaii.TreeNode(identifier.ToString()); + using var t = Im.Tree.Node($"{identifier}"); if (t) ActiveStatePanel.DrawState(stateManager, ActorData.Invalid, state); } diff --git a/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs b/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs new file mode 100644 index 0000000..bb728dc --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs @@ -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 +{ + 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); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index f69d02b..6316092 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,28 +1,28 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Config; using Glamourer.Designs; +using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Services; using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignDetailTab +public sealed class DesignDetailTab : IUiService { - private readonly SaveService _saveService; - private readonly 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, + public DesignDetailTab(SaveService saveService, DesignManager manager, DesignFileSystem fileSystem, DesignColors colors, Configuration config) { _saveService = saveService; - _selector = selector; _manager = manager; _fileSystem = fileSystem; _colors = colors; @@ -41,6 +41,8 @@ public class DesignDetailTab Im.Line.New(); } + private Design Selected + => (Design) _fileSystem.Selection.Selection!.Value; private void DrawDesignInfoTable() { @@ -56,13 +58,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, 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)) @@ -86,10 +88,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) { @@ -98,56 +100,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); @@ -156,11 +158,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(); @@ -169,26 +171,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) { @@ -204,7 +206,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; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs deleted file mode 100644 index ca6974a..0000000 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ /dev/null @@ -1,402 +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 -{ - private readonly DesignManager _designManager; - private readonly DesignChanged _event; - private readonly 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 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.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.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 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."; - } - - /// Appropriately identify and set the string filter and its type. - 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); - } - - /// - /// 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. - /// - protected override bool ApplyFiltersAndState(FileSystem.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); - } - - /// Apply the string filters. - 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 - }; - } - - /// Combined wrapper for handling all filters and setting state. - private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) - { - state = new DesignState(_designColors.GetColor(leaf.Value)); - return ApplyStringFilters(leaf, leaf.Value); - } - - #endregion -} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs new file mode 100644 index 0000000..b38a2b5 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs @@ -0,0 +1,106 @@ +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, IDisposable +{ + 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) + { + _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 void OnDesignChanged(in DesignChanged.Arguments arguments) + { + if (arguments.Type is not DesignChanged.Type.Renamed) + return; + + if (arguments.Design != _fileSystem.Selection.Selection?.Value) + return; + + _header = new StringU8(arguments.Design.Name); + } + + private void OnSelectionChanged() + { + if (_fileSystem.Selection.Selection?.GetValue() is { } selection) + { + _header = new StringU8(selection.Name); + _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 Text + => _config.Ephemeral.IncognitoMode ? _incognito : _header; + + private sealed class LockedButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton + { + 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()); + } + + public void Dispose() + { + _fileSystem.Selection.Changed -= OnSelectionChanged; + _designChanged.Unsubscribe(OnDesignChanged); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 7cfc964..1cf485e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -1,5 +1,6 @@ using Dalamud.Interface; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using ImSharp; @@ -9,7 +10,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignLinkDrawer( DesignLinkManager linkManager, - DesignFileSystemSelector selector, + DesignFileSystem fileSystem, LinkDesignCombo combo, DesignColors colorManager, Configuration config) : IUiService @@ -19,6 +20,9 @@ public class DesignLinkDrawer( 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); @@ -44,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; @@ -80,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(); } @@ -91,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); @@ -103,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); } Im.Tooltip.OnHover("Current Design"u8); - DrawDragDrop(selector.Selected!, LinkOrder.Self, 0); + DrawDragDrop(Selected, LinkOrder.Self, 0); table.NextColumn(); using (AwesomeIcon.Font.Push()) { @@ -133,7 +137,7 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(colorManager.GetColor(design))) { Im.Cursor.FrameAlign(); - Im.Selectable(config.Ephemeral.IncognitoMode ? design.Incognito : design.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? design.Incognito : design.Name); } DrawDragDrop(design, order, i); @@ -143,7 +147,7 @@ public class DesignLinkDrawer( DrawApplicationBoxes(i, order, flags); if (delete) - linkManager.RemoveDesignLink(selector.Selected!, i--, order); + linkManager.RemoveDesignLink(Selected, i--, order); } } @@ -163,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}"; @@ -175,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) @@ -227,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) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 7da344b..f04beed 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,34 +1,28 @@ -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 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 +public class DesignPanel : IPanel { private readonly FileDialogManager _fileDialog = new(); - private readonly DesignFileSystemSelector _selector; private readonly CustomizationDrawer _customizationDrawer; + private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager; private readonly ActorObjectManager _objects; private readonly StateManager _state; @@ -37,18 +31,13 @@ public class DesignPanel private readonly 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; - public DesignPanel(DesignFileSystemSelector selector, - CustomizationDrawer customizationDrawer, + public DesignPanel(CustomizationDrawer customizationDrawer, DesignManager manager, ActorObjectManager objects, StateManager state, @@ -62,9 +51,8 @@ public class DesignPanel CustomizeParameterDrawer parameterDrawer, DesignLinkDrawer designLinkDrawer, MaterialDrawer materials, - EditorHistory history) + DesignFileSystem fileSystem) { - _selector = selector; _customizationDrawer = customizationDrawer; _manager = manager; _objects = objects; @@ -74,32 +62,16 @@ public class DesignPanel _config = config; _designDetails = designDetails; _importService = importService; - _converter = converter; _multiDesignPanel = multiDesignPanel; _parameterDrawer = parameterDrawer; _designLinkDrawer = designLinkDrawer; _materials = materials; - _history = history; - _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 - => _selector.Selected == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text; + private Design Selection + => (Design)_fileSystem.Selection.Selection!.Value; private void DrawEquipment() { @@ -109,22 +81,22 @@ public class DesignPanel _equipmentDrawer.Prepare(); - var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected()); + var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, Selection.WriteProtected()); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); + var data = EquipDrawData.FromDesign(_manager, Selection, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _manager.ChangeStains(_selector.Selected, slot, newAllStain); + _manager.ChangeStains(Selection, slot, newAllStain); } - var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); - var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, 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, _selector.Selected!, slot); + var data = BonusDrawData.FromDesign(_manager, Selection, slot); _equipmentDrawer.DrawBonusItem(data); } @@ -136,30 +108,30 @@ public class DesignPanel private void DrawEquipmentMetaToggles() { - using (var _ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); + 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, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); + 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, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); + 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, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, Selection)); } } @@ -169,25 +141,25 @@ public class DesignPanel return; var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); - using var h = Im.Tree.HeaderId(_selector.Selected!.DesignData.ModelId is 0 - ? "Customization" - : $"Customization (Model Id #{_selector.Selected!.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(_selector.Selected!.DesignData.Customize, _selector.Selected.Application.Customize, - _selector.Selected!.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(_selector.Selected, idx, newValue); + _manager.ChangeApplyCustomize(Selection, idx, newValue); if (_customizationDrawer.Changed.HasFlag(flag)) - _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); + _manager.ChangeCustomize(Selection, idx, _customizationDrawer.Customize[idx]); } - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, Selection)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); } @@ -197,7 +169,7 @@ public class DesignPanel if (!h) return; - _parameterDrawer.Draw(_manager, _selector.Selected!); + _parameterDrawer.Draw(_manager, Selection); } private void DrawMaterialValues() @@ -206,52 +178,52 @@ public class DesignPanel if (!h) return; - _materials.Draw(_selector.Selected!); + _materials.Draw(Selection); } private void DrawCustomizeApplication() { - using var id = ImUtf8.PushId("Customizations"u8); - var set = _selector.Selected!.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 = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : - (_selector.Selected!.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(_selector.Selected!, CustomizeIndex.Clan, newFlags); - _manager.ChangeApplyCustomize(_selector.Selected!, 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(_selector.Selected!, index, newFlags); + _manager.ChangeApplyCustomize(Selection, index, newFlags); } - var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan)) - _manager.ChangeApplyCustomize(_selector.Selected!, 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 = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender)) - _manager.ChangeApplyCustomize(_selector.Selected!, 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 = _selector.Selected!.DoApplyCustomize(index); - if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) - _manager.ChangeApplyCustomize(_selector.Selected!, 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)_selector.Selected!.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 : _selector.Selected!.DoApplyCrest(flag); - if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) - _manager.ChangeApplyCrest(_selector.Selected!, 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 if (!h) return; - using var disabled = Im.Disabled(_selector.Selected!.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 slots) { - var flags = (uint)(allFlags & _selector.Selected!.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()) : _selector.Selected!.DoApplyStain(slot); - if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) - _manager.ChangeApplyStains(_selector.Selected!, 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()) : _selector.Selected!.DoApplyEquip(slot); - if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) - _manager.ChangeApplyItem(_selector.Selected!, 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 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 } 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 } 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 } 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 } 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(_selector.Selected!, true, true, true, false, true, true, false, true); - _manager.ChangeApplyMeta(_selector.Selected!, 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(_selector.Selected!, 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(_selector.Selected!, 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(_selector.Selected!, MetaIndex.HatState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, 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(_selector.Selected!, MetaIndex.Wetness, customize.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, customize.Value); } - private static readonly IReadOnlyList MetaLabels = + private static readonly IReadOnlyList 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)_selector.Selected!.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()) : _selector.Selected!.DoApplyMeta(index); - if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, 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 BonusSlotLabels = + private static readonly IReadOnlyList BonusSlotLabels = [ - "Apply Facewear", + new("Apply Facewear"u8), ]; private void DrawBonusSlotApplication() { - using var id = ImUtf8.PushId("Bonus"u8); - var flags = _selector.Selected!.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) : _selector.Selected!.DoApplyBonusItem(index); - if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyBonusItem(_selector.Selected!, index, apply); + var apply = bigChange ? flags.HasFlag(index) : Selection.DoApplyBonusItem(index); + if (Im.Checkbox(label, ref apply) || bigChange) + _manager.ChangeApplyBonusItem(Selection, index, apply); } } @@ -458,65 +423,62 @@ public class DesignPanel private void DrawParameterApplication() { using var id = Im.Id.Push("Parameter"u8); - var flags = (ulong)_selector.Selected!.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) : _selector.Selected!.DoApplyParameter(flag); + var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : Selection.DoApplyParameter(flag); if (Im.Checkbox($"Apply {flag.ToNameU8()}", ref apply) || bigChange) - _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); + _manager.ChangeApplyParameter(Selection, flag, apply); } } + public ReadOnlySpan Id + => "DesignPanel"u8; + public void Draw() { - using var group = ImUtf8.Group(); - if (_selector.SelectedPaths.Count > 1) + _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 (_selector.Selected == null || _selector.Selected.WriteProtected()) - return; - - if (_importService.CreateDatTarget(out var dat)) - { - _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); - _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); - foreach (var idx in CustomizationExtensions.AllBasic) - _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); - Glamourer.Messager.NotificationMessage( - $"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); - } - else if (_importService.CreateCharaTarget(out var designBase, out var name)) - { - _manager.ApplyDesign(_selector.Selected!, designBase); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.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 || _selector.Selected 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 (_selector.Selected is null) - return; + table.SetupScrollFreeze(0, 1); + table.NextColumn(); Im.Dummy(Vector2.Zero); DrawButtonRow(); - ImGui.TableNextColumn(); + table.NextColumn(); DrawCustomize(); DrawEquipment(); @@ -543,15 +505,15 @@ public class DesignPanel 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 _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); + using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); + _state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -560,33 +522,33 @@ public class DesignPanel 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 _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, 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(_selector.Selected!.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 && _selector.Selected != null) - _importService.SaveDesignAsDat(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); + if (v && _fileSystem.Selection.Selection?.GetValue() is not null) + _importService.SaveDesignAsDat(path, Selection.DesignData.Customize, Selection.Name); }, startPath); _fileDialog.Draw(); @@ -594,164 +556,4 @@ public class DesignPanel private static unsafe string GetUserPath() => Framework.Instance()->UserPathString; - - - private sealed class LockButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override string Description - => panel._selector.Selected!.WriteProtected() - ? "Make this design editable." - : "Write-protect this design."; - - protected override FontAwesomeIcon Icon - => panel._selector.Selected!.WriteProtected() - ? FontAwesomeIcon.Lock - : FontAwesomeIcon.LockOpen; - - protected override void OnClick() - => panel._manager.SetWriteProtection(panel._selector.Selected!, !panel._selector.Selected!.WriteProtected()); - } - - private sealed class SetFromClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override bool Disabled - => panel._selector.Selected?.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._selector.Selected!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selector.Selected!.Name}.", - $"Could not apply clipboard to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false); - } - } - } - - private sealed class DesignUndoButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override bool Disabled - => !panel._manager.CanUndo(panel._selector.Selected) || (panel._selector.Selected?.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._selector.Selected!); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selector.Selected!.Name}.", - NotificationType.Error, - false); - } - } - } - - private sealed class ExportToClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != 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._selector.Selected!); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selected!.Name} data to clipboard.", - $"Could not copy data from design {panel._selector.Selected!.Identifier} to clipboard", NotificationType.Error, false); - } - } - } - - private sealed class ApplyCharacterButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null && panel._objects.Player.Valid; - - protected override string Description - => "Overwrite this design with your character's current state."; - - protected override bool Disabled - => panel._selector.Selected?.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._selector.Selected!.GetMaterialDataRef().Clear(); - panel._manager.ApplyDesign(panel._selector.Selected!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selector.Selected!.Name}.", - $"Could not apply player state to design {panel._selector.Selected!.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._selector.Selected != null; - - protected override bool Disabled - => (panel._selector.Selected?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selector.Selected); - - protected override void OnClick() - => panel._history.Undo(panel._selector.Selected!); - } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 1f018e0..839c392 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop; using ImSharp; @@ -6,27 +7,55 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignTab(DesignFileSystemSelector selector, DesignPanel panel, ImportService importService, DesignManager manager) - : ITab +public sealed class DesignTab : TwoPanelLayout, ITab { - public ReadOnlySpan 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 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 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); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs b/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs new file mode 100644 index 0000000..4242b0a --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs @@ -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 +{ + 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); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs b/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs new file mode 100644 index 0000000..990a3f2 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs @@ -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 +{ + 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); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/LockButton.cs b/Glamourer/Gui/Tabs/DesignTab/LockButton.cs new file mode 100644 index 0000000..62225b8 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/LockButton.cs @@ -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 +{ + 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()); +} diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 95c3153..ac8a326 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -7,11 +8,14 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager, Configuration config) +public sealed class ModAssociationsTab(PenumbraService penumbra, DesignFileSystem fileSystem, DesignManager manager, Configuration config) : IUiService { - private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log, selector); + 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); @@ -35,7 +39,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect { 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 = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + _copy = Selection.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); Im.Line.Same(); @@ -44,7 +48,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ? $"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(selector.Selected!, mod, setting); + manager.UpdateMod(Selection, mod, setting); Im.Line.Same(); @@ -53,10 +57,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." : "Copy some mod associations to the clipboard, first."u8, _copy is null)) { - while (selector.Selected!.AssociatedMods.Count > 0) - manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]); + while (Selection.AssociatedMods.Count > 0) + manager.RemoveMod(Selection, Selection.AssociatedMods.Keys[0]); foreach (var (mod, setting) in _copy!) - manager.AddMod(selector.Selected!, mod, setting); + manager.AddMod(Selection, mod, setting); } } @@ -75,13 +79,13 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect 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}", - selector.Selected!.AssociatedMods.Count is 0 || id == Guid.Empty)) + Selection.AssociatedMods.Count is 0 || id == Guid.Empty)) ApplyAll(); } public void ApplyAll() { - foreach (var (mod, settings) in selector.Selected!.AssociatedMods) + foreach (var (mod, settings) in Selection.AssociatedMods) penumbra.SetMod(mod, settings, StateSource.Manual, false); } @@ -103,7 +107,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect Mod? removedMod = null; (Mod mod, ModSettings settings)? updatedMod = null; - foreach (var (idx, (mod, settings)) in selector.Selected!.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); @@ -116,10 +120,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect DrawNewModRow(table); if (removedMod.HasValue) - manager.RemoveMod(selector.Selected!, removedMod.Value); + manager.RemoveMod(Selection, removedMod.Value); if (updatedMod.HasValue) - manager.UpdateMod(selector.Selected!, 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, @@ -240,18 +244,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawNewModRow(in Im.TableDisposable table) { - var currentName = _modCombo.CurrentSelection.Mod.Name; + var currentDir = _modCombo.Selection; table.NextColumn(); - var tt = string.IsNullOrEmpty(currentName) + var tt = currentDir.Length is 0 ? "Please select a mod first."u8 - : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) + : 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(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); + manager.AddMod(Selection, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings); table.NextColumn(); - _modCombo.Draw("##new", string.IsNullOrEmpty(currentName) ? "Select new Mod..." : currentName, string.Empty, - Im.ContentRegion.Available.X, Im.Style.TextHeight); + _modCombo.Draw("##new"u8, Im.ContentRegion.Available.X); } } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index a0b805e..98575a9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,95 +1,123 @@ -using Dalamud.Interface.Utility; +using Glamourer.Designs; using Glamourer.Interop.Penumbra; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Classes; -using OtterGui.Log; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Widgets; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings, int Count)> +public sealed class ModCombo(PenumbraService penumbra, DesignFileSystem fileSystem) : FilterComboBase(new ModFilter()) { - public ModCombo(PenumbraService penumbra, Logger log, DesignFileSystemSelector selector) - : base(() => penumbra.GetMods(selector.Selected?.FilteredItemNames.ToArray() ?? []), MouseWheelType.None, log) - => SearchByParts = false; - - protected override string ToString((Mod Mod, ModSettings Settings, int Count) obj) - => obj.Mod.Name; - - protected override bool IsVisible(int globalIndex, LowerString filter) - => filter.IsContained(Items[globalIndex].Mod.Name) || filter.IsContained(Items[globalIndex].Mod.DirectoryName); - - protected override bool DrawSelectable(int globalIdx, bool selected) + public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count) { - using var id = ImUtf8.PushId(globalIdx); - var (mod, settings, count) = Items[globalIdx]; - bool ret; - var color = settings.Enabled + public readonly StringPair Name = new(mod.Name); + public readonly StringPair Directory = new(mod.DirectoryName); + public readonly ModSettings Settings = settings; + public readonly int Count = count; + + public readonly Vector4 Color = settings.Enabled ? count > 0 - ? ColorId.ContainsItemsEnabled.Value() - : ImGuiColor.Text.Get() + ? ColorId.ContainsItemsEnabled.Value().ToVector() + : Im.Style[ImGuiColor.Text] : count > 0 - ? ColorId.ContainsItemsDisabled.Value() - : ImGuiColor.TextDisabled.Get(); - using (ImGuiColor.Text.Push(color)) + ? ColorId.ContainsItemsDisabled.Value().ToVector() + : Im.Style[ImGuiColor.TextDisabled]; + + public readonly bool DifferingNames = string.Equals(mod.Name, mod.DirectoryName, StringComparison.CurrentCultureIgnoreCase); + } + + public StringPair SelectionName { get; private set; } = new("Select new Mod...", new StringU8("Select new Mod..."u8)); + public string Selection { get; private set; } = string.Empty; + public ModSettings Settings { get; private set; } = ModSettings.Empty; + + public bool Draw(Utf8StringHandler label, float previewWidth) + { + if (!Draw(label, SelectionName.Utf8, StringU8.Empty, previewWidth, out var newItem)) + return false; + + SelectionName = newItem.Name; + Selection = newItem.Directory.Utf16; + Settings = newItem.Settings; + return true; + } + + protected override float ItemHeight + => Im.Style.TextHeightWithSpacing; + + protected override IEnumerable GetItems() + => penumbra.GetMods(fileSystem.Selection.Selection?.GetValue()?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count)); + + protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) + { + bool ret; + using (ImGuiColor.Text.Push(item.Color)) { - ret = ImUtf8.Selectable(mod.Name, selected); + ret = Im.Selectable(item.Name.Utf8, selected); } - if (ImGui.IsItemHovered()) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * Im.Style.GlobalScale); - using var tt = ImUtf8.Tooltip(); - var namesDifferent = mod.Name != mod.DirectoryName; - Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0)); - using (ImUtf8.Group()) - { - if (namesDifferent) - ImUtf8.Text("Directory Name"u8); - ImUtf8.Text("Enabled"u8); - ImUtf8.Text("Priority"u8); - ImUtf8.Text("Affected Design Items"u8); - DrawSettingsLeft(settings); - } - - ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale)); - using (ImUtf8.Group()) - { - if (namesDifferent) - ImUtf8.Text(mod.DirectoryName); - ImUtf8.Text($"{settings.Enabled}"); - ImUtf8.Text($"{settings.Priority}"); - ImUtf8.Text($"{count}"); - DrawSettingsRight(settings); - } - } + if (Im.Item.Hovered()) + DrawTooltip(item); return ret; } - public static void DrawSettingsLeft(ModSettings settings) + private static void DrawTooltip(in CacheItem item) + { + using var style = ImStyleSingle.PopupBorderThickness.Push(2 * Im.Style.GlobalScale); + using var tt = Im.Tooltip.Begin(); + + Im.Dummy(ImEx.ScaledVectorX(300)); + using (Im.Group()) + { + if (item.DifferingNames) + Im.Text("Directory Name"u8); + Im.Text("Enabled"u8); + Im.Text("Priority"u8); + Im.Text("Affected Design Items"u8); + DrawSettingsLeft(item.Settings); + } + + Im.Line.Same(Math.Max(Im.Item.Size.X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale)); + using (Im.Group()) + { + if (item.DifferingNames) + Im.Text(item.Directory.Utf8); + Im.Text($"{item.Settings.Enabled}"); + Im.Text($"{item.Settings.Priority}"); + Im.Text($"{item.Count}"); + DrawSettingsRight(item.Settings); + } + } + + public static void DrawSettingsLeft(in ModSettings settings) { foreach (var setting in settings.Settings) { - ImUtf8.Text(setting.Key); + Im.Text(setting.Key); for (var i = 1; i < setting.Value.Count; ++i) Im.Line.New(); } } - public static void DrawSettingsRight(ModSettings settings) + public static void DrawSettingsRight(in ModSettings settings) { foreach (var setting in settings.Settings) { if (setting.Value.Count == 0) - ImUtf8.Text(""u8); + Im.Text(""u8); else foreach (var option in setting.Value) - ImUtf8.Text(option); + Im.Text(option); } } + + protected override bool IsSelected(CacheItem item, int globalIndex) + => Selection.Equals(item.Directory.Utf16, StringComparison.OrdinalIgnoreCase); + + private sealed class ModFilter : TextFilterBase + { + public override bool WouldBeVisible(in CacheItem item, int globalIndex) + => base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Directory.Utf16); + + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Name.Utf16; + } } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index c7ca7c6..4d1c483 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,40 +1,27 @@ -using Dalamud.Interface; -using Dalamud.Interface.Utility; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Material; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Extensions; -using OtterGui.Raii; -using OtterGui.Text; -using static Glamourer.Gui.Tabs.HeaderDrawer; +using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel( - DesignFileSystemSelector selector, +public sealed class MultiDesignPanel( + DesignFileSystem fileSystem, DesignManager editor, DesignColors colors, - Configuration config) + Configuration config) : IUiService { - private readonly Button[] _leftButtons = []; - private readonly Button[] _rightButtons = []; //[new IncognitoButton(config)]; - private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() { - if (selector.SelectedPaths.Count == 0) + if (fileSystem.Selection.OrderedNodes.Count is 0) return; - HeaderDrawer.Draw(string.Empty, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); - using var child = ImUtf8.Child("##MultiPanel"u8, default, true); - if (!child) - return; - - var width = ImGuiHelpers.ScaledVector2(145, 0); - var treeNodePos = ImGui.GetCursorPos(); - _numDesigns = DrawDesignList(); + var width = ImEx.ScaledVectorX(145); + var treeNodePos = Im.Cursor.Position; + DrawDesignList(); DrawCounts(treeNodePos); var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); @@ -49,18 +36,18 @@ public class MultiDesignPanel( private void DrawCounts(Vector2 treeNodePos) { - var startPos = ImGui.GetCursorPos(); - var numFolders = selector.SelectedPaths.Count - _numDesigns; - var text = (_numDesigns, numFolders) switch + var startPos = Im.Cursor.Position; + var numDesigns = fileSystem.Selection.DataNodes.Count; + var numFolders = fileSystem.Selection.Folders.Count; + Im.Cursor.Position = treeNodePos; + ImEx.TextRightAligned((numDesigns, numFolders) switch { - (0, 0) => string.Empty, // should not happen - (> 0, 0) => $"{_numDesigns} Designs", + (0, 0) => StringU8.Empty, // should not happen + (> 0, 0) => $"{numDesigns} Designs", (0, > 0) => $"{numFolders} Folders", - _ => $"{_numDesigns} Designs, {numFolders} Folders", - }; - ImGui.SetCursorPos(treeNodePos); - ImUtf8.TextRightAligned(text); - ImGui.SetCursorPos(startPos); + _ => $"{numDesigns} Designs, {numFolders} Folders", + }); + Im.Cursor.Position = startPos; } private void ResetCounts() @@ -74,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 l) + return; if (l.Value.QuickDesign) ++_numQuickDesignEnabled; @@ -94,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 - ? (FontAwesomeIcon.FileCircleMinus, l.Value.Name.Text) - : (FontAwesomeIcon.FolderMinus, string.Empty); - ImGui.TableNextColumn(); - if (ImUtf8.IconButton(icon, "Remove from selection."u8, sizeType)) - selector.RemovePathFromMultiSelection(path); + using var id = Im.Id.Push(index); + var (icon, text) = node is IFileSystemData l + ? (LunaStyle.RemoveFileIcon, l.Value.Name) + : (LunaStyle.RemoveFolderIcon, string.Empty); + table.NextColumn(); + if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType)) + fileSystem.Selection.RemoveFromSelection(node); - ImUtf8.DrawFrameColumn(text); - ImUtf8.DrawFrameColumn(fullName); + table.DrawFrameColumn(text); + table.DrawFrameColumn(node.FullPath); - if (CountLeaves(path)) - ++numDesigns; + CountLeaves(node); } } Im.Separator(); - return numDesigns; } private string _tag = string.Empty; @@ -153,42 +133,39 @@ public class MultiDesignPanel( private int _numDesignsResetDyes; private int _numAdvancedDyes; private int _numDesignsWithAdvancedDyes; - private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; private float DrawMultiTagger(Vector2 width) { - ImUtf8.TextFrameAligned("Multi Tagger:"u8); + ImEx.TextFrameAligned("Multi Tagger:"u8); Im.Line.Same(); - var offset = ImGui.GetItemRectSize().X + Im.Style.WindowPadding.X; - ImGui.SetNextItemWidth(Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X)); - ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); + var offset = Im.Item.Size.X + Im.Style.WindowPadding.X; + Im.Item.SetNextWidth(Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X)); + Im.Input.Text("##tag"u8, ref _tag, "Tag Name..."u8); UpdateTagCache(); - var label = _addDesigns.Count > 0 - ? $"Add to {_addDesigns.Count} Designs" - : "Add"; - var tooltip = _addDesigns.Count == 0 - ? _tag.Length == 0 - ? "No tag specified." - : $"All designs selected already contain the tag \"{_tag}\"." - : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; Im.Line.Same(); - if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) + 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))}", + _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.AddTag(design, _tag); - label = _removeDesigns.Count > 0 - ? $"Remove from {_removeDesigns.Count} Designs" - : "Remove"; - tooltip = _removeDesigns.Count == 0 - ? _tag.Length == 0 - ? "No tag specified." - : $"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))}"; Im.Line.Same(); - if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) + 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))}", + _removeDesigns.Count is 0)) foreach (var (design, index) in _removeDesigns) editor.RemoveTag(design, index); Im.Separator(); @@ -197,121 +174,114 @@ public class MultiDesignPanel( private void DrawMultiQuickDesignBar(float offset) { - ImUtf8.TextFrameAligned("Multi QDB:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + 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 tt = diff == 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."; - if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) - { - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetQuickDesign(design.Value, true); - } + var diff = fileSystem.Selection.DataNodes.Count - _numQuickDesignEnabled; + if (ImEx.Button("Display Selected Designs in QDB"u8, buttonWidth, diff is 0 + ? $"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()!, true); Im.Line.Same(); - tt = _numQuickDesignEnabled == 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."; - if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) - { - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetQuickDesign(design.Value, false); - } + if (ImEx.Button("Hide Selected Designs in QDB"u8, buttonWidth, _numQuickDesignEnabled is 0 + ? $"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()!, false); Im.Separator(); } private void DrawMultiLock(float offset) { - ImUtf8.TextFrameAligned("Multi Lock:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + 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 tt = diff == 0 - ? $"All {_numDesigns} selected designs are already write protected." - : $"Write-protect all {_numDesigns} designs. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Turn Write-Protected"u8, tt, buttonWidth, diff == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetWriteProtection(design.Value, true); + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsLocked; + if (ImEx.Button("Turn Write-Protected"u8, buttonWidth, diff is 0 + ? $"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()!, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs are write-protected." - : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs."; - if (ImUtf8.ButtonEx("Remove Write-Protection"u8, tt, buttonWidth, _numDesignsLocked == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetWriteProtection(design.Value, false); + if (ImEx.Button("Remove Write-Protection"u8, buttonWidth, _numDesignsLocked is 0 + ? $"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()!, false); Im.Separator(); } private void DrawMultiResetSettings(float offset) { - ImUtf8.TextFrameAligned("Settings:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + 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 tt = diff == 0 - ? $"All {_numDesigns} selected designs already reset temporary settings." - : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Temp. Settings"u8, tt, buttonWidth, diff == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetTemporarySettings(design.Value, true); + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetSettings; + if (ImEx.Button("Set Reset Temp. Settings"u8, buttonWidth, diff is 0 + ? $"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()!, true); Im.Line.Same(); - tt = _numDesignsResetSettings == 0 - ? $"None of the {_numDesigns} selected designs reset temporary settings." - : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; - if (ImUtf8.ButtonEx("Remove Reset Temp. Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetTemporarySettings(design.Value, false); + if (ImEx.Button("Remove Reset Temp. Settings"u8, buttonWidth, _numDesignsResetSettings is 0 + ? $"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()!, false); Im.Separator(); } private void DrawMultiResetDyes(float offset) { - ImUtf8.TextFrameAligned("Adv. Dyes:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + 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 tt = diff == 0 - ? $"All {_numDesigns} selected designs already reset advanced dyes." - : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Dyes"u8, tt, buttonWidth, diff == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetAdvancedDyes(design.Value, true); + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetDyes; + if (ImEx.Button("Set Reset Dyes"u8, buttonWidth, diff is 0 + ? $"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()!, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs reset advanced dyes." - : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs."; - if (ImUtf8.ButtonEx("Remove Reset Dyes"u8, tt, buttonWidth, _numDesignsResetDyes == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetAdvancedDyes(design.Value, false); + if (ImEx.Button("Remove Reset Dyes"u8, buttonWidth, _numDesignsLocked is 0 + ? $"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()!, false); Im.Separator(); } private void DrawMultiForceRedraw(float offset) { - ImUtf8.TextFrameAligned("Redrawing:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + 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 tt = diff == 0 - ? $"All {_numDesigns} selected designs already force redraws." - : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Force Redraws"u8, tt, buttonWidth, diff == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeForcedRedraw(design.Value, true); + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsForcedRedraw; + if (ImEx.Button("Force Redraws"u8, buttonWidth, diff is 0 + ? $"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()!, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs force redraws." - : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs."; - if (ImUtf8.ButtonEx("Remove Forced Redraws"u8, tt, buttonWidth, _numDesignsForcedRedraw == 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeForcedRedraw(design.Value, false); + if (ImEx.Button("Remove Forced Redraws"u8, buttonWidth, _numDesignsLocked is 0 + ? $"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()!, false); Im.Separator(); } @@ -319,157 +289,152 @@ public class MultiDesignPanel( private void DrawMultiColor(Vector2 width, float offset) { - ImUtf8.TextFrameAligned("Multi Colors:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Multi Colors:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); if (_colorCombo.Draw("##color"u8, _colorComboSelection, "Select a design color."u8, Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), out var newSelection)) _colorComboSelection = newSelection; UpdateColorCache(); - var label = _addDesigns.Count > 0 - ? $"Set for {_addDesigns.Count} Designs" - : "Set"; - var tooltip = _addDesigns.Count is 0 - ? _colorComboSelection switch - { - null => "No color specified.", - DesignColors.AutomaticName => "Use the other button to set to automatic.", - _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", - } - : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; Im.Line.Same(); - if (ImEx.Button(label, width, tooltip, _addDesigns.Count is 0)) - { + 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))}", + _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.ChangeColor(design, _colorComboSelection!); - } - label = _removeDesigns.Count > 0 - ? $"Unset {_removeDesigns.Count} Designs" - : "Unset"; - tooltip = _removeDesigns.Count == 0 - ? "No selected design is set to a non-automatic color." - : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; Im.Line.Same(); - if (ImEx.Button(label, width, tooltip, _removeDesigns.Count is 0)) - { + 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))}", + _removeDesigns.Count is 0)) foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); - } Im.Separator(); } private void DrawAdvancedButtons(float offset) { - ImUtf8.TextFrameAligned("Delete Adv."u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Delete Adv."u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var enabled = config.DeleteDesignModifier.IsActive(); - var tt = _numDesignsWithAdvancedDyes is 0 - ? "No selected designs contain any advanced dyes." - : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs."; - if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, tt, new Vector2(Im.ContentRegion.Available.X, 0), + if (ImEx.Button("Delete All Advanced Dyes"u8, Im.ContentRegion.Available with { Y = 0 }, _numDesignsWithAdvancedDyes is 0 + ? "No selected designs contain any advanced dyes."u8 + : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.", !enabled || _numDesignsWithAdvancedDyes is 0)) - foreach (var design in selector.SelectedPaths.OfType()) + 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()!.Materials.Count > 0) + editor.ChangeMaterialValue(design.GetValue()!, + MaterialValueIndex.FromKey(design.GetValue()!.Materials[0].Item1), null); } if (!enabled && _numDesignsWithAdvancedDyes is not 0) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); Im.Separator(); } private void DrawApplicationButtons(float offset) { - ImUtf8.TextFrameAligned("Application"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Application"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var width = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var enabled = config.DeleteDesignModifier.IsActive(); bool? equip = null; bool? customize = null; - var group = ImUtf8.Group(); - if (ImUtf8.ButtonEx("Disable Everything"u8, - _numDesigns > 0 - ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) + using (Im.Group()) { - equip = false; - customize = false; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - Im.Line.Same(); - if (ImUtf8.ButtonEx("Enable Everything"u8, - _numDesigns > 0 - ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = true; - customize = true; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - if (ImUtf8.ButtonEx("Equipment Only"u8, - _numDesigns > 0 - ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = true; - customize = false; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - Im.Line.Same(); - if (ImUtf8.ButtonEx("Customization Only"u8, - _numDesigns > 0 - ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = false; - customize = true; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - if (ImUtf8.ButtonEx("Default Application"u8, - _numDesigns > 0 - ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." - : "No designs selected.", width, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + if (ImEx.Button("Disable Everything"u8, width, + 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)) { - editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); - editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + equip = false; + customize = false; } - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - Im.Line.Same(); - if (ImUtf8.ButtonEx("Disable Advanced"u8, _numDesigns > 0 - ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) - editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + Im.Line.Same(); + if (ImEx.Button("Enable Everything"u8, width, + 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; + customize = true; + } - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImEx.Button("Equipment Only"u8, width, + 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; + customize = false; + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + Im.Line.Same(); + if (ImEx.Button("Customization Only"u8, width, + 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; + customize = true; + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImEx.Button("Default Application"u8, width, + 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 fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) + { + editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); + editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + Im.Line.Same(); + 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 fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) + editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + } - group.Dispose(); Im.Separator(); if (equip is null && customize is null) return; - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) { editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); @@ -489,16 +454,17 @@ public class MultiDesignPanel( { _addDesigns.Clear(); _removeDesigns.Clear(); - if (_tag.Length == 0) + if (_tag.Length is 0) return; - foreach (var leaf in selector.SelectedPaths.OfType()) + foreach (var leaf in fileSystem.Selection.DataNodes) { - var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); + var design = leaf.GetValue()!; + 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); } } @@ -507,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()) + 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()!; + if (design.Color.Length > 0) + _removeDesigns.Add((design, 0)); + if (selection != DesignColors.AutomaticName && design.Color != selection) + _addDesigns.Add(design); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs new file mode 100644 index 0000000..c35a57b --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs @@ -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 +{ + /// + public override AwesomeIcon Icon + => LunaStyle.DeleteIcon; + + /// + public override bool HasTooltip + => true; + + /// + 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."); + } + + /// + public override bool Enabled + => config.DeleteDesignModifier.IsActive() && fileSystem.Selection.DataNodes.Count > 0; + + /// + public override void OnClick() + { + foreach (var node in fileSystem.Selection.DataNodes.ToArray()) + { + if (node.GetValue() is { } design) + manager.Delete(design); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs new file mode 100644 index 0000000..4bbc7d6 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs @@ -0,0 +1,111 @@ +using Glamourer.Designs; +using Glamourer.Events; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignFileSystemCache : FileSystemCache +{ + 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(in DesignChanged.Arguments arguments) + { + switch (arguments.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 (arguments.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)node); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + Parent.DesignChanged.Unsubscribe(OnDesignChanged); + Parent.DesignColors.ColorChanged -= OnColorChanged; + } + + public sealed class DesignData(IFileSystemData node) : BaseFileSystemNodeCache + { + public readonly IFileSystemData Node = node; + public Vector4 Color; + public StringU8 Name = new(node.Value.Name); + 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); + } + + protected override void DrawInternal(FileSystemCache 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); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs new file mode 100644 index 0000000..9bb29da --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs @@ -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, 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(); + DataContext.RemoveButtons(); + 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 Id + => "Designs"u8; + + protected override FileSystemCache CreateCache() + => new DesignFileSystemCache(this); +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs new file mode 100644 index 0000000..2fa7ef8 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs @@ -0,0 +1,150 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignFilter : TokenizedFilter, + IFileSystemFilter, 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.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.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.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; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs new file mode 100644 index 0000000..2df214f --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs @@ -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 +{ + 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 list) + { } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs new file mode 100644 index 0000000..e1e4405 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs @@ -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 +{ + private readonly WeakReference _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()!); + 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!); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs new file mode 100644 index 0000000..cc5f03f --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs @@ -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 +{ + 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; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs new file mode 100644 index 0000000..ca86538 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs @@ -0,0 +1,34 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class MoveDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton +{ + /// + public override ReadOnlySpan Label(in IFileSystemData _) + => "##Move"u8; + + /// Replaces the normal menu item handling for a text input, so the other fields are not used. + /// + 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; + } +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs new file mode 100644 index 0000000..258e077 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs @@ -0,0 +1,28 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class NewDesignButton(DesignManager designManager) : BaseIconButton +{ + 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); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs new file mode 100644 index 0000000..45832bb --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs @@ -0,0 +1,34 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class RenameDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton +{ + /// + public override ReadOnlySpan Label(in IFileSystemData _) + => "##Rename"u8; + + /// Replaces the normal menu item handling for a text input, so the other fields are not used. + /// + public override bool DrawMenuItem(in IFileSystemData data) + { + var design = (Design)data.Value; + var currentName = design.Name; + 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; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs b/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs new file mode 100644 index 0000000..0e67431 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs @@ -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 +{ + 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); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs b/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs new file mode 100644 index 0000000..889893a --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs @@ -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 +{ + 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); +} diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs deleted file mode 100644 index 6025b0c..0000000 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/IncognitoButton.cs b/Glamourer/Gui/Tabs/IncognitoButton.cs index 427d388..46f3394 100644 --- a/Glamourer/Gui/Tabs/IncognitoButton.cs +++ b/Glamourer/Gui/Tabs/IncognitoButton.cs @@ -1,4 +1,5 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs; diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index 4caa246..247ee5a 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -3,12 +3,13 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Services; using ImSharp; +using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Glamourer.Gui.Tabs.NpcTab; -public class LocalNpcAppearanceData : ISavable +public sealed class LocalNpcAppearanceData : ISavable, IUiService { private readonly DesignColors _colors; @@ -93,7 +94,7 @@ public class LocalNpcAppearanceData : ISavable public event Action DataChanged = null!; - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.NpcAppearanceFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index f26e1a4..a574471 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,8 +1,10 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.State; using ImSharp; using Luna; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 27d8233..4b420c6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,12 +1,16 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.NpcTab; public sealed class NpcTab : TwoPanelLayout, ITab { - public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header) + private readonly UiConfig _uiConfig; + + public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header, UiConfig uiConfig) { + _uiConfig = uiConfig; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); LeftPanel = selector; LeftFooter = NopHeaderFooter.Instance; @@ -22,5 +26,14 @@ public sealed class NpcTab : TwoPanelLayout, ITab => MainTabType.Npcs; public void DrawContent() - => Draw(TwoPanelWidth.IndeterminateRelative); + => Draw(_uiConfig.NpcTabScale); + + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.NpcTabScale = new TwoPanelWidth(width, mode); + + protected override float MinimumWidth + => LeftHeader.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index de40e4d..9beb9ff 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -1,12 +1,8 @@ -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; @@ -41,7 +37,7 @@ public class CodeDrawer(Configuration config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule } 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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 config, CodeService codeService, FunModule 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); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs index 78987e2..a8ebfb2 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -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 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(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 GetItems() + => penumbra.GetCollections().Select(kvp => new CacheItem(kvp.Key, kvp.Value)); + + public bool Draw(Utf8StringHandler label, Utf8StringHandler 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 + { + public override bool WouldBeVisible(in CacheItem item, int globalIndex) + => base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Incognito); + + protected override ReadOnlySpan ToFilterString(in CacheItem item, int globalIndex) + => item.Name; + } + + protected override FilterComboBaseCache CreateCache() + => new Cache(this); + + private sealed class Cache(CollectionCombo parent) : FilterComboBaseCache(parent) + { + protected override void ComputeWidth() + => ComboWidth = AllItems.Max(i => i.Name.Utf8.CalculateSize().X + Im.Style.ItemSpacing.X * 2 + i.ShortId.CalculateSize().X); + } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 0a1620e..dcbae3c 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,4 +1,5 @@ using Dalamud.Interface; +using Glamourer.Config; using Glamourer.Interop.Penumbra; using Glamourer.Services; using ImSharp; @@ -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(); diff --git a/Glamourer/Gui/Tabs/SettingsTab/DesignColorUi.cs b/Glamourer/Gui/Tabs/SettingsTab/DesignColorUi.cs new file mode 100644 index 0000000..ff2a19c --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab/DesignColorUi.cs @@ -0,0 +1,113 @@ +using Glamourer.Config; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.SettingsTab; + +public sealed class DesignColorUi(DesignColors colors, Configuration config) : IUiService +{ + private string _newName = string.Empty; + + public void Draw() + { + using var table = Im.Table.Begin("designColors"u8, 3, TableFlags.RowBackground); + if (!table) + return; + + var changeString = string.Empty; + Rgba32? changeValue = null; + + table.SetupColumn("##Delete"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); + table.SetupColumn("##Select"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); + table.SetupColumn("Color Name"u8, TableColumnFlags.WidthStretch); + + table.HeaderRow(); + + table.NextColumn(); + if (ImEx.Icon.Button(LunaStyle.RefreshIcon, "Revert the color used for missing design colors to its default."u8, + colors.MissingColor == DesignColors.MissingColorDefault)) + { + changeString = DesignColors.MissingColorName; + changeValue = DesignColors.MissingColorDefault; + } + + table.NextColumn(); + if (DrawColorButton(DesignColors.MissingColorNameU8, colors.MissingColor, out var newColor)) + { + changeString = DesignColors.MissingColorName; + changeValue = newColor; + } + + table.NextColumn(); + Im.Cursor.X += Im.Style.FramePadding.X; + Im.Text(DesignColors.MissingColorNameU8); + Im.Tooltip.OnHover("This color is used when the color specified in a design is not available."u8); + + var disabled = !config.DeleteDesignModifier.IsActive(); + foreach (var (idx, (name, color)) in colors.Index()) + { + using var id = Im.Id.Push(idx); + table.NextColumn(); + + if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this color. This does not remove it from designs using it."u8, disabled)) + { + changeString = name; + changeValue = null; + } + + if (disabled) + Im.Tooltip.OnHover($"\nHold {config.DeleteDesignModifier} to delete."); + + table.NextColumn(); + if (DrawColorButton(name, color, out newColor)) + { + changeString = name; + changeValue = newColor; + } + + table.NextColumn(); + Im.Cursor.X += Im.Style.FramePadding.X; + Im.Text(name); + } + + table.NextColumn(); + (var tt, disabled) = _newName.Length == 0 + ? ("Specify a name for a new color first.", true) + : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName + ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) + : colors.ContainsKey(_newName) + ? ($"The color {_newName} already exists, please choose a different name.", true) + : ($"Add a new color {_newName} to your list.", false); + if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, disabled)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + table.NextColumn(); + table.NextColumn(); + Im.Item.SetNextWidth(Im.ContentRegion.Available.X); + if (Im.Input.Text("##newDesignColor"u8, ref _newName, "New Color Name..."u8, InputTextFlags.EnterReturnsTrue)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + if (changeString.Length > 0) + { + if (!changeValue.HasValue) + colors.DeleteColor(changeString); + else + colors.SetColor(changeString, changeValue.Value); + } + } + + public static bool DrawColorButton(Utf8StringHandler tooltip, Rgba32 color, out Rgba32 newColor) + { + var ret = Im.Color.Editor(tooltip, ref color, ColorEditorFlags.AlphaPreviewHalf | ColorEditorFlags.NoInputs); + Im.Tooltip.OnHover(ref tooltip); + newColor = color; + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 249f3bc..1577ea9 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using Dalamud.Plugin.Services; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Tabs.DesignTab; @@ -15,7 +16,7 @@ namespace Glamourer.Gui.Tabs.SettingsTab; public sealed class SettingsTab( Configuration config, - DesignFileSystemSelector selector, + DesignFileSystemDrawer drawer, ContextMenuService contextMenuService, IUiBuilder uiBuilder, GlamourerChangelog changelog, @@ -27,7 +28,8 @@ public sealed class SettingsTab( Glamourer glamourer, AutoDesignApplier autoDesignApplier, AutoRedrawChanged autoRedraw, - PcpService pcpService) + PcpService pcpService, + IgnoredMods ignoredMods) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -60,6 +62,7 @@ public sealed class SettingsTab( DrawInterfaceSettings(); DrawColorSettings(); overrides.Draw(); + DrawIgnoredMods(); codeDrawer.Draw(); } @@ -280,8 +283,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); @@ -302,6 +305,7 @@ public sealed class SettingsTab( private readonly (StringU8, QdbButtons)[] _columns = [ + (new StringU8("Toggle Main Window"u8), QdbButtons.ToggleMainWindow), (new StringU8("Apply Design"u8), QdbButtons.ApplyDesign), (new StringU8("Revert All"u8), QdbButtons.RevertAll), (new StringU8("Revert to Auto"u8), QdbButtons.RevertAutomation), @@ -325,7 +329,7 @@ public sealed class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var numColumns = 9 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 10 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); Im.Line.New(); Im.Text("Show the Following Buttons in the Quick Design Bar:"u8); Im.Dummy(Vector2.Zero); @@ -376,7 +380,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)}"); } /// Draw the entire Color subsection. @@ -402,6 +406,7 @@ public sealed class SettingsTab( continue; config.Colors[color] = newColor.Color; + CacheManager.Instance.SetColorsDirty(); config.Save(); } } @@ -445,20 +450,20 @@ public sealed class SettingsTab( using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name)) { if (combo) - foreach (var val in 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 +477,6 @@ public sealed class SettingsTab( if (Im.Selectable(value.ToNameU8(), config.ShowRename == value)) { config.ShowRename = value; - selector.SetRenameSearchPath(value); config.Save(); } @@ -504,6 +508,48 @@ public sealed class SettingsTab( "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); + } + private void DrawRoughnessSettings() { Im.Item.SetNextWidthScaled(300); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs index e59e8d0..fb305c6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs @@ -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; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 7d645a9..2002604 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.UnlocksTab; -public class UnlockOverview( +public sealed class UnlockOverview( ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, @@ -21,7 +21,7 @@ public class UnlockOverview( CodeService codes, JobService jobs, FavoriteManager favorites, - PenumbraService penumbra) + PenumbraService penumbra) : IUiService { private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index f6c747c..961f65f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -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; @@ -15,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.UnlocksTab; -public sealed class UnlockTable : TableBase +public sealed class UnlockTable : TableBase, IUiService { private readonly JobService _jobs; private readonly ItemManager _items; @@ -23,9 +24,10 @@ public sealed class UnlockTable : TableBase 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 _unlockEvent = unlockEvent; _favorites = favorites; _penumbra = penumbra; + _ignoredMods = ignoredMods; Flags |= TableFlags.Hideable | TableFlags.Reorderable | TableFlags.Resizable; } @@ -72,6 +75,7 @@ public sealed class UnlockTable : TableBase 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 => 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 + private sealed class ModdedColumn : FlagColumn { - 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 allItems) @@ -124,8 +133,11 @@ public sealed class UnlockTable : TableBase 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 } } - 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 @@ -241,9 +271,9 @@ public sealed class UnlockTable : TableBase { 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 allItems) @@ -524,16 +554,17 @@ public sealed class UnlockTable : TableBase } } - private void OnItemUnlock(ObjectUnlocked.Type type, uint id, DateTimeOffset timestamp) + private void OnItemUnlock(in ObjectUnlocked.Arguments arguments) { - if (type is not ObjectUnlocked.Type.Item) + if (arguments.Type is not ObjectUnlocked.Type.Item) return; FilterDirty = true; SortDirty = true; + var id = arguments.Id; var idx = UnfilteredItems.IndexOf(i => i.Item.ItemId == id); if (idx >= 0) - UpdateSingleItem(idx, UnfilteredItems[idx] with { UnlockTimestamp = timestamp }, false); + UpdateSingleItem(idx, UnfilteredItems[idx] with { UnlockTimestamp = arguments.Timestamp }, false); } public override void Update() diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 62a0b41..3574be3 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public sealed class UnlocksTab : Window, ITab { - private readonly EphemeralConfig _config; - private readonly UnlockOverview _overview; - private readonly UnlockTable _table; + private readonly Config.EphemeralConfig _config; + private readonly UnlockOverview _overview; + private readonly UnlockTable _table; - public UnlocksTab(EphemeralConfig config, UnlockOverview overview, UnlockTable table) + public UnlocksTab(Config.EphemeralConfig config, UnlockOverview overview, UnlockTable table) : base("Unlocked Equipment") { _config = config; diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 495d69c..06acd2f 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,7 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; @@ -13,13 +13,19 @@ namespace Glamourer.Interop; /// Changes in Race, body type or Gender are probably ignored. /// This operates on draw objects, not game objects. /// -public unsafe class ChangeCustomizeService : EventWrapperRef2 +public sealed unsafe class ChangeCustomizeService : EventBase { - private readonly PenumbraReloaded _penumbraReloaded; - private readonly IGameInteropProvider _interop; + private readonly PenumbraReloaded _penumbraReloaded; + private readonly IGameInteropProvider _interop; private readonly delegate* unmanaged _original; - private readonly Post _postEvent = new(); - + private readonly Post _postEvent; + + public ref struct Arguments(Model model, ref CustomizeArray customize) + { + public readonly Model Model = model; + public ref CustomizeArray Customize = ref customize; + } + /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. public static readonly InMethodChecker InUpdate = new(); @@ -30,8 +36,8 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) + public void Subscribe(InAction action, Post.Priority priority) => _postEvent.Subscribe(action, priority); - public void Unsubscribe(Action action) + public void Unsubscribe(InAction action) => _postEvent.Unsubscribe(action); - public sealed class Post() : EventWrapper(nameof(ChangeCustomizeService) + '.' + nameof(Post)) + public sealed class Post(Logger log) : EventBase(nameof(ChangeCustomizeService) + '.' + nameof(Post), log) { public enum Priority { diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 1f85612..be728b5 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -1,16 +1,18 @@ 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; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class ContextMenuService : IDisposable +public sealed class ContextMenuService : IDisposable, IRequiredService { public const int ChatLogContextItemId = 0x958; diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 2b55f94..6e56f49 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,24 +2,16 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Classes; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -/// -/// Triggered when the crest visibility is updated on a model. -/// -/// Parameter is the model with an update. -/// Parameter is the equipment slot changed. -/// Parameter is whether the crest will be shown. -/// -/// -public sealed unsafe class CrestService : EventWrapperRef3 +/// Triggered when the crest visibility is updated on a model. +public sealed unsafe class CrestService : EventBase { public enum Priority { @@ -27,8 +19,20 @@ public sealed unsafe class CrestService : EventWrapperRef3 The game object with a crest update. + public readonly Actor Actor = actor; + + /// The equipment slot changed. + public readonly CrestFlag Slot = slot; + + /// The new value. + public ref bool Value = ref value; + } + + public CrestService(IGameInteropProvider interop, Logger log) + : base(nameof(CrestService), log) { interop.InitializeFromAttributes(this); _humanSetFreeCompanyCrestVisibleOnSlot = @@ -76,7 +80,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 886fb58..2aa3a06 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -19,10 +19,12 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); + _equipGearsetHook = + interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, + EquipGearSetDetour); _moveItemHook.Enable(); _equipGearsetHook.Enable(); @@ -36,14 +38,14 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook = null!; + private readonly Hook _equipGearsetHook; private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset((int)gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new EquippedGearset.Arguments(new ByteString(set->Name), (int)gearsetId, prior, glamourPlateId, set->ClassJob)); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { @@ -111,7 +113,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService Add(EquipSlot.LFinger, ref entry->Items[12]); } - _movedItemsEvent.Invoke(_itemList.ToArray()); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(_itemList.ToArray())); } return ret; @@ -130,25 +132,15 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); - if (ret == 0) + if (ret is 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _movedItemsEvent.Invoke(new[] - { - source, - target, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(source, target)); else - _movedItemsEvent.Invoke(new[] - { - source, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(source)); else if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _movedItemsEvent.Invoke(new[] - { - target, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(target)); } return ret; diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 1797809..5315e35 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -2,6 +2,7 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Luna; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Interop; @@ -9,7 +10,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class JobService : IDisposable +public sealed class JobService : IDisposable, IRequiredService { private readonly nint _characterDataOffset; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 8cc7bdd..c342912 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -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; @@ -36,11 +37,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable public void Dispose() => _event.Unsubscribe(OnPrepareColorSet); - private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainIds stain, ref nint ret) + private void OnPrepareColorSet(in PrepareColorSet.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(characterBase); - var validType = FindType(characterBase, actor, out var type); - var (slotId, materialId) = FindMaterial(characterBase, material); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); + var validType = FindType(arguments.Model.AsCharacterBase, actor, out var type); + var (slotId, materialId) = FindMaterial(arguments.Model.AsCharacterBase, arguments.Handle); if (!validType || type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0 @@ -54,19 +55,19 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (values.Length == 0) return; - if (!PrepareColorSet.TryGetColorTable(material, stain, out var baseColorSet)) + if (!PrepareColorSet.TryGetColorTable(arguments.Handle, arguments.Ids, out var baseColorSet)) return; var drawData = type switch { - MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId), - _ => GetTempSlot((Weapon*)characterBase), + MaterialValueIndex.DrawObjectType.Human => GetTempSlot(arguments.Model.AsHuman, (HumanSlot)slotId), + _ => GetTempSlot(arguments.Model.AsWeapon), }; - var mode = PrepareColorSet.GetMode(material); + var mode = PrepareColorSet.GetMode(arguments.Handle); UpdateMaterialValues(state, values, drawData, ref baseColorSet, mode); if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) - ret = (nint)texture; + arguments.ReturnValue = (nint)texture; } /// Update and apply the glamourer state of an actor according to the application sources when updated by the game. diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 83c8b9b..eac45b3 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -3,7 +3,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Luna; -using OtterGui.Classes; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; @@ -13,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; public sealed unsafe class PrepareColorSet - : EventWrapperPtr12Ref34, IHookService + : EventBase, IHookService { private readonly UpdateColorSets _updateColorSets; @@ -23,11 +22,19 @@ public sealed unsafe class PrepareColorSet MaterialManager = 0, } - public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets) - : base("Prepare Color Set ") + public ref struct Arguments(Model model, MaterialResourceHandle* handle, ref StainIds ids, ref nint returnValue) + { + public readonly Model Model = model; + public readonly MaterialResourceHandle* Handle = handle; + public ref StainIds Ids = ref ids; + public ref nint ReturnValue = ref returnValue; + } + + public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets, Logger log) + : base("Prepare Color Set", log) { _updateColorSets = updateColorSets; - _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); } private readonly Task> _task; @@ -64,7 +71,7 @@ public sealed unsafe class PrepareColorSet var ret = nint.Zero; var stainIds = new StainIds(stainId1, stainId2); - Invoke(characterBase.AsCharacterBase, material, ref stainIds, ref ret); + Invoke(new Arguments(characterBase.AsCharacterBase, material, ref stainIds, ref ret)); if (ret != nint.Zero) return (Texture*)ret; @@ -74,7 +81,7 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) + if (material->DataSet is null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) { table = default; return false; @@ -83,10 +90,10 @@ public sealed unsafe class PrepareColorSet var newTable = *(ColorTable.Table*)material->DataSet; if (GetDyeTable(material, out var dyeTable)) { - if (stainIds.Stain1.Id != 0) + if (stainIds.Stain1.Id is not 0) material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); - if (stainIds.Stain2.Id != 0) + if (stainIds.Stain2.Id is not 0) material->ReadStainingTemplate(dyeTable, stainIds.Stain2.Id, (Half*)&newTable, 1); } diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 6225986..7baa255 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -2,11 +2,12 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public unsafe class MetaService : IDisposable +public sealed unsafe class MetaService : IDisposable, IRequiredService { private readonly HeadGearVisibilityChanged _headGearEvent; private readonly WeaponVisibilityChanged _weaponEvent; @@ -75,8 +76,8 @@ public unsafe class MetaService : IDisposable } Actor actor = drawData->OwnerObject; - var v = value == 0; - _headGearEvent.Invoke(actor, ref v); + var v = value is 0; + _headGearEvent.Invoke(new HeadGearVisibilityChanged.Arguments(actor, ref v)); value = (byte)(v ? 0 : 1); Glamourer.Log.Verbose($"[MetaService] Hide Hat triggered with 0x{(nint)drawData:X} {id} {value} for {actor.Utf8Name}."); _hideHatGearHook.Original(drawData, id, value); @@ -85,8 +86,8 @@ public unsafe class MetaService : IDisposable private void HideWeaponsDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var v = value == 0; - _weaponEvent.Invoke(actor, ref v); + var v = value is 0; + _weaponEvent.Invoke(new WeaponVisibilityChanged.Arguments(actor, ref v)); Glamourer.Log.Verbose($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _hideWeaponsHook.Original(drawData, (byte)(v ? 0 : 1)); } @@ -94,8 +95,8 @@ public unsafe class MetaService : IDisposable private void ToggleVisorDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var v = value != 0; - _visorEvent.Invoke(actor.Model, true, ref v); + var v = value is not 0; + _visorEvent.Invoke(new VisorStateChanged.Arguments(actor.Model, true, ref v)); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _toggleVisorHook.Original(drawData, (byte)(v ? 1 : 0)); } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index c8c40db..153ed0f 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -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 config, ActorObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier( + PenumbraService penumbra, + PenumbraAutoRedrawSkip autoRedrawSkip, + Configuration config, + ActorObjectManager objects, + CollectionOverrideService overrides) : IService { private readonly HashSet _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; } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index c480249..546400d 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,6 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; -using Glamourer.Designs.History; +using Glamourer.Config; using Glamourer.Events; using Glamourer.State; using Luna; @@ -9,7 +9,7 @@ using Penumbra.GameData.Interop; namespace Glamourer.Interop.Penumbra; -public class PenumbraAutoRedraw : IDisposable, IRequiredService +public sealed class PenumbraAutoRedraw : IDisposable, IRequiredService { private const int WaitFrames = 5; private readonly Configuration _config; @@ -45,13 +45,13 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } private readonly ConcurrentQueue<(ActorState, Action, int)> _actions = []; - private readonly OtterGui.Classes.ConcurrentSet _skips = []; + private readonly ConcurrentSet _skips = []; private DateTime _frame; - private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData _1, ITransaction? _2) + private void OnStateChanged(in StateChanged.Arguments arguments) { - if (type is StateChangeType.Design && source.IsIpc()) - _skips.TryAdd(state); + if (arguments.Type is StateChangeType.Design && arguments.Source.IsIpc()) + _skips.TryAdd(arguments.State); } private void OnFramework(IFramework _) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index b198f55..382960d 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -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; @@ -34,7 +35,7 @@ public readonly record struct ModSettings(Dictionary> Setti => new(); } -public class PenumbraService : IDisposable +public sealed class PenumbraService : IDisposable, IService { public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraFeatureVersion = 13; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 2a89a25..92acb83 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -6,6 +6,7 @@ using Penumbra.GameData; using Penumbra.GameData.Interop; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.State; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -13,7 +14,7 @@ using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; namespace Glamourer.Interop; -public unsafe class ScalingService : IDisposable +public sealed unsafe class ScalingService : IDisposable, IRequiredService { private readonly ActorManager _actors; private readonly StateManager _state; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 3ef99d9..4a9dd95 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Network; using Glamourer.Events; +using Luna; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -12,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public unsafe class UpdateSlotService : IDisposable +public sealed unsafe class UpdateSlotService : IDisposable, IRequiredService { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; @@ -97,18 +98,18 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; - EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + EquipSlotUpdatingEvent.Invoke(new EquipSlotUpdating.Arguments(drawObject, slot, ref *data, ref returnValue)); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + return returnValue is ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; - BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + BonusSlotUpdatingEvent.Invoke(new BonusSlotUpdating.Arguments(drawObject, slot, ref *data, ref returnValue)); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + return returnValue is ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) @@ -120,7 +121,7 @@ public unsafe class UpdateSlotService : IDisposable { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); var drawObject = drawDataContainer->OwnerObject->DrawObject; - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject, drawObject); + GearsetDataLoadedEvent.Invoke(new GearsetDataLoaded.Arguments(drawDataContainer->OwnerObject, drawObject)); Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs index a6afd1d..c909486 100644 --- a/Glamourer/Interop/VieraEarService.cs +++ b/Glamourer/Interop/VieraEarService.cs @@ -2,12 +2,13 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public unsafe class VieraEarService : IDisposable +public unsafe sealed class VieraEarService : IDisposable, IRequiredService { private readonly PenumbraReloaded _penumbra; private readonly IGameInteropProvider _interop; @@ -57,10 +58,10 @@ public unsafe class VieraEarService : IDisposable private void SetupVieraEarDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var originalOn = value != 0; + var originalOn = value is not 0; var on = originalOn; // Invoke an event that can change the requested value - Event.Invoke(actor, ref on); + Event.Invoke(new VieraEarStateChanged.Arguments(actor, ref on)); Glamourer.Log.Verbose( $"[SetVieraEarState] Invoked from game on 0x{actor.Address:X} switching to {on} (original {originalOn} from {value})."); diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 83262e4..b13287c 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -2,12 +2,13 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public class VisorService : IDisposable +public sealed class VisorService : IDisposable, IRequiredService { private readonly PenumbraReloaded _penumbra; private readonly IGameInteropProvider _interop; @@ -58,11 +59,11 @@ public class VisorService : IDisposable private void SetupVisorDetour(nint human, ushort modelId, byte value) { - var originalOn = value != 0; + var originalOn = value is not 0; var on = originalOn; // Invoke an event that can change the requested value // and also control whether the function should be called at all. - Event.Invoke(human, false, ref on); + Event.Invoke(new VisorStateChanged.Arguments(human, false, ref on)); Glamourer.Log.Verbose( $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn} from {value} with {modelId})."); diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 54f318b..a5e1ee1 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -2,13 +2,14 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public unsafe class WeaponService : IDisposable +public sealed unsafe class WeaponService : IDisposable, IRequiredService { private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); @@ -59,17 +60,17 @@ public unsafe class WeaponService : IDisposable var tmpWeapon = weapon; // First call the regular function. if (equipSlot is not EquipSlot.Unknown) - _event.Invoke(actor, equipSlot, ref tmpWeapon); + _event.Invoke(new WeaponLoading.Arguments(actor, equipSlot, ref tmpWeapon)); // Sage hack for weapons appearing in animations? // Check for weapon value 0 for certain cases (e.g. carbuncles transforming to humans) because that breaks some stuff (weapon hiding?) otherwise. - else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0) - _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); + else if (weaponValue == actor.GetMainhand().Value && weaponValue is not 0) + _event.Invoke(new WeaponLoading.Arguments(actor, EquipSlot.MainHand, ref tmpWeapon)); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4, unk5); if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.Skeleton.Id == 0) + if (tmpWeapon.Skeleton.Id is 0) tmpWeapon.Stains = StainIds.None; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4, unk5); } diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index e9b8d9d..fdd3f90 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,34 +1,17 @@ using Luna; -using Backup = OtterGui.Classes.Backup; -using Logger = OtterGui.Log.Logger; namespace Glamourer.Services; -public class BackupService : IAsyncService +public sealed class BackupService(Logger log, FilenameService provider) : BaseBackupService(log, provider) { - private readonly Logger _logger; - private readonly DirectoryInfo _configDirectory; - private readonly IReadOnlyList _fileNames; - - public BackupService(Logger logger, FilenameService fileNames) - { - _logger = logger; - _fileNames = GlamourerFiles(fileNames); - _configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); - Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames)); - } - - /// Create a permanent backup with a given name for migrations. - public void CreateMigrationBackup(string name) - => Backup.CreatePermanentBackup(_logger, _configDirectory, _fileNames, name); - /// Collect all relevant files for glamourer configuration. private static IReadOnlyList GlamourerFiles(FilenameService fileNames) { var list = new List(16) { - new(fileNames.ConfigFile), - new(fileNames.DesignFileSystem), + new(fileNames.ConfigurationFile), + new(fileNames.UiConfiguration), + new(fileNames.MigrationDesignFileSystem), new(fileNames.MigrationDesignFile), new(fileNames.AutomationFile), new(fileNames.UnlockFileCustomize), @@ -41,9 +24,4 @@ public class BackupService : IAsyncService return list; } - - public Task Awaiter { get; } - - public bool Finished - => Awaiter.IsCompletedSuccessfully; } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 783a03d..0616fd4 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -1,10 +1,11 @@ +using Glamourer.Config; using ImSharp; using Luna; using Penumbra.GameData.Enums; namespace Glamourer.Services; -public class CodeService +public sealed class CodeService : IService { private readonly Configuration _config; private readonly SHA256 _hasher = SHA256.Create(); @@ -25,16 +26,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 = @@ -253,4 +255,3 @@ public class CodeService _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } - diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 0a5c97b..b754fa5 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,12 +1,9 @@ -using Dalamud.Interface.ImGuiNotification; using Glamourer.Interop.Penumbra; using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; -using Extensions = OtterGui.Filesystem.Extensions; using Notification = Luna.Notification; namespace Glamourer.Services; @@ -31,7 +28,7 @@ public sealed class CollectionOverrideService : IService, ISavable if (!identifier.IsValid) identifier = _actors.FromObject(actor.AsObject, out _, true, true, true); - return ArrayExtensions.FindFirst(_overrides, p => p.Actor.Matches(identifier), out var ret) + return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret) ? (ret.CollectionId, ret.DisplayName, true) : (_penumbra.GetActorCollection(actor, out var name), name, false); } @@ -41,7 +38,7 @@ public sealed class CollectionOverrideService : IService, ISavable public IReadOnlyList<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> Overrides => _overrides; - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.CollectionOverrideFile; public void AddOverride(IEnumerable identifiers, Guid collectionId, string displayName) @@ -78,7 +75,7 @@ public sealed class CollectionOverrideService : IService, ISavable if (idx < 0 || idx >= _overrides.Count) return; - if (newCollectionId == Guid.Empty || newDisplayName.Length == 0) + if (newCollectionId == Guid.Empty || newDisplayName.Length is 0) return; var current = _overrides[idx]; @@ -106,7 +103,7 @@ public sealed class CollectionOverrideService : IService, ISavable public void MoveOverride(int idxFrom, int idxTo) { - if (!Extensions.Move(_overrides, idxFrom, idxTo)) + if (!_overrides.Move(idxFrom, idxTo)) return; Glamourer.Log.Debug($"Moved collection override {idxFrom + 1} to {idxTo + 1}."); @@ -192,7 +189,7 @@ public sealed class CollectionOverrideService : IService, ISavable public void Save(StreamWriter writer) { - var jObj = new JObject() + var jObj = new JObject { ["Version"] = Version, ["Overrides"] = SerializeOverrides(), diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e078557..575ea6f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -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; @@ -15,7 +16,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using SeStringBuilderExtensions = OtterGui.Classes.SeStringBuilderExtensions; namespace Glamourer.Services; @@ -44,7 +44,7 @@ public class CommandService : IDisposable, IApiService 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 config, ModSettingApplier modApplier, - ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, + ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemDrawer designDrawer, QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) { _commands = commands; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index a0a3914..1c6dcf6 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Gui; using ImSharp; using Luna; @@ -6,7 +7,7 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Services; -public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) +public sealed class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) : IRequiredService { private Configuration _config = null!; private JObject _data = null!; @@ -14,22 +15,35 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator public void Migrate(Configuration config) { _config = config; - if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(saveService.FileNames.ConfigFile)) + if (config.Version >= Configuration.CurrentVersion || !File.Exists(saveService.FileNames.ConfigurationFile)) { AddColors(config, false); return; } - _data = JObject.Parse(File.ReadAllText(saveService.FileNames.ConfigFile)); + _data = JObject.Parse(File.ReadAllText(saveService.FileNames.ConfigurationFile)); MigrateV1To2(); MigrateV2To4(); MigrateV4To5(); MigrateV5To6(); MigrateV6To7(); MigrateV7To8(); + MigrateV8To9(); AddColors(config, true); } + private void MigrateV8To9() + { + if (_config.Version > 8) + return; + + backupService.CreateMigrationBackup("pre_filesystem_update", saveService.FileNames.MigrationDesignFileSystem); + _config.Version = 9; + _config.Ephemeral.Version = 9; + _config.Save(); + _config.Ephemeral.Save(); + } + private void MigrateV7To8() { if (_config.Version > 7) @@ -60,7 +74,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Version = 6; } - // Ephemeral Config. + // Ephemeral Configuration. private void MigrateV4To5() { if (_config.Version > 4) @@ -71,7 +85,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Ephemeral.ShowDesignQuickBar = _data["ShowDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.ShowDesignQuickBar; _config.Ephemeral.LockDesignQuickBar = _data["LockDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.LockDesignQuickBar; _config.Ephemeral.LockMainWindow = _data["LockMainWindow"]?.ToObject() ?? _config.Ephemeral.LockMainWindow; - _config.Ephemeral.SelectedMainTab = _data["SelectedTab"]?.ToObject() ?? _config.Ephemeral.SelectedMainTab; + _config.Ephemeral.SelectedMainTab = _data["SelectedTab"]?.ToObject() ?? _config.Ephemeral.SelectedMainTab; _config.Ephemeral.LastSeenVersion = _data["LastSeenVersion"]?.ToObject() ?? _config.Ephemeral.LastSeenVersion; _config.Version = 5; _config.Ephemeral.Version = 5; @@ -83,7 +97,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator if (_config.Version > 1) return; - backupService.CreateMigrationBackup("pre_v1_to_v2_migration"); + backupService.CreateMigrationBackup("pre_v1_to_v2_migration", saveService.FileNames.MigrationDesignFile); fixedDesignMigrator.Migrate(_data["FixedDesigns"]); _config.Version = 2; var customizationColor = _data["CustomizationColor"]?.ToObject() ?? ColorId.CustomizationDesign.Data().DefaultColor; diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs index ef1e3b9..9ce09b0 100644 --- a/Glamourer/Services/DesignResolver.cs +++ b/Glamourer/Services/DesignResolver.cs @@ -3,14 +3,13 @@ using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Gui; -using Glamourer.Gui.Tabs.DesignTab; using Luna; using ImSharp; namespace Glamourer.Services; public class DesignResolver( - DesignFileSystemSelector designSelector, + DesignFileSystem fileSystem, QuickDesignCombo quickDesignCombo, DesignConverter converter, DesignManager manager, @@ -66,8 +65,8 @@ public class DesignResolver( [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GetSelectedDesign(ref DesignBase? design, ref SeString? error) { - design = designSelector.Selected; - if (design != null) + design = (Design?)fileSystem.Selection.Selection?.Value; + if (design is not null) return true; error = "You do not have selected any design in the Designs Tab."; @@ -143,11 +142,10 @@ public class DesignResolver( } else { - var lower = argument.ToLowerInvariant(); // Search for design by name and partial identifier. - design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(lower)); + design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(argument)); // Search for design by path, if nothing was found. - if (design == null && designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf) + if (design is null && designFileSystem.Find(argument, out var child) && child is IFileSystemData leaf) design = leaf.Value; } @@ -160,13 +158,13 @@ public class DesignResolver( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Func MatchNameAndIdentifier(string lower) + private static Func MatchNameAndIdentifier(string text) { // Check for names and identifiers, prefer names - if (lower.Length > 3) - return d => d.Name.Lower == lower || d.Identifier.ToString().StartsWith(lower); + if (text.Length > 3) + return d => string.Equals(d.Name, text, StringComparison.OrdinalIgnoreCase) || d.Identifier.ToString().StartsWith(text, StringComparison.OrdinalIgnoreCase); // Check only for names. - return d => d.Name.Lower == lower; + return d => string.Equals(d.Name, text, StringComparison.OrdinalIgnoreCase); } } diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index cd25c64..e59d97c 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -1,40 +1,29 @@ using Dalamud.Plugin; using Glamourer.Designs; +using Luna; namespace Glamourer.Services; -public class FilenameService +public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) { - public readonly string ConfigDirectory; - public readonly string ConfigFile; - public readonly string DesignFileSystem; - public readonly string MigrationDesignFile; - public readonly string DesignDirectory; - public readonly string AutomationFile; - public readonly string UnlockFileCustomize; - public readonly string UnlockFileItems; - public readonly string FavoriteFile; - public readonly string DesignColorFile; - public readonly string EphemeralConfigFile; - public readonly string NpcAppearanceFile; - public readonly string CollectionOverrideFile; - - public FilenameService(IDalamudPluginInterface pi) - { - ConfigDirectory = pi.ConfigDirectory.FullName; - ConfigFile = pi.ConfigFile.FullName; - AutomationFile = Path.Combine(ConfigDirectory, "automation.json"); - DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json"); - MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json"); - UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json"); - UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); - DesignDirectory = Path.Combine(ConfigDirectory, "designs"); - FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); - DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); - EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); - NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); - CollectionOverrideFile = Path.Combine(ConfigDirectory, "collection_overrides.json"); - } + public readonly string MigrationDesignFileSystem = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json"); + public readonly string MigrationDesignFile = Path.Combine(pi.ConfigDirectory.FullName, "Designs.json"); + public readonly string DesignDirectory = Path.Combine(pi.ConfigDirectory.FullName, "designs"); + public readonly string AutomationFile = Path.Combine(pi.ConfigDirectory.FullName, "automation.json"); + public readonly string IgnoredModsFile = Path.Combine(pi.ConfigDirectory.FullName, "ignored_mods.json"); + public readonly string UnlockFileCustomize = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_customize.json"); + public readonly string UnlockFileItems = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_items.json"); + public readonly string FavoriteFile = Path.Combine(pi.ConfigDirectory.FullName, "favorites.json"); + public readonly string DesignColorFile = Path.Combine(pi.ConfigDirectory.FullName, "design_colors.json"); + public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json"); + public readonly string NpcAppearanceFile = Path.Combine(pi.ConfigDirectory.FullName, "npc_appearance_data.json"); + public readonly string CollectionOverrideFile = Path.Combine(pi.ConfigDirectory.FullName, "collection_overrides.json"); + public readonly string UiConfiguration = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json"); + public readonly string FileSystemFolder = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem"); + public readonly string FileSystemEmptyFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "empty_folders.json"); + public readonly string FileSystemExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "expanded_folders.json"); + public readonly string FileSystemLockedNodes = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "locked_nodes.json"); + public readonly string FileSystemSelectedNodes = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "selected_nodes.json"); public IEnumerable Designs() { @@ -50,4 +39,23 @@ public class FilenameService public string DesignFile(Design design) => DesignFile(design.Identifier.ToString()); + + public override List GetBackupFiles() + { + var list = new List(16) + { + new(ConfigurationFile), + new(AutomationFile), + new(IgnoredModsFile), + new(UnlockFileCustomize), + new(UnlockFileItems), + new(FavoriteFile), + new(DesignColorFile), + new(FileSystemEmptyFolders), + new(FileSystemLockedNodes), + }; + // Do not back up expanded folders, selected nodes, ui configuration or ephemeral config. + list.AddRange(Designs()); + return list; + } } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index a885b54..b81e1b1 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,6 +1,8 @@ using Dalamud.Plugin.Services; +using Glamourer.Config; using Lumina.Excel; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -9,7 +11,7 @@ using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Services; -public class ItemManager +public sealed class ItemManager : IService { public const string Nothing = EquipItem.Nothing; public const string SmallClothesNpc = "Smallclothes (NPC)"; diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs index 6711ce3..cc8a54d 100644 --- a/Glamourer/Services/PcpService.cs +++ b/Glamourer/Services/PcpService.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; using Luna; diff --git a/Glamourer/Services/SaveService.cs b/Glamourer/Services/SaveService.cs index 2a96755..ac0ad8f 100644 --- a/Glamourer/Services/SaveService.cs +++ b/Glamourer/Services/SaveService.cs @@ -1,17 +1,11 @@ -using OtterGui.Classes; -using OtterGui.Log; +using Luna; namespace Glamourer.Services; /// /// Any file type that we want to save via SaveService. /// -public interface ISavable : ISavable -{ } +public interface ISavable : ISavable; -public sealed class SaveService : SaveServiceBase -{ - public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) - : base(log, framework, fileNames) - { } -} +public sealed class SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) + : BaseSaveService(log, framework, fileNames), IService; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index f9e63f9..e06cdb4 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,36 +1,12 @@ using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Api.Api; -using Glamourer.Automation; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Gui; -using Glamourer.Gui.Customization; -using Glamourer.Gui.Equipment; -using Glamourer.Gui.Tabs; -using Glamourer.Gui.Tabs.ActorTab; -using Glamourer.Gui.Tabs.AutomationTab; -using Glamourer.Gui.Tabs.DebugTab; -using Glamourer.Gui.Tabs.DesignTab; -using Glamourer.Gui.Tabs.NpcTab; -using Glamourer.Gui.Tabs.SettingsTab; -using Glamourer.Gui.Tabs.UnlocksTab; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; -using Glamourer.State; -using Glamourer.Unlocks; using Luna; using Microsoft.Extensions.DependencyInjection; -using OtterGui.Classes; -using OtterGui.Raii; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; -using Penumbra.GameData.DataContainers; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using FrameworkManager = OtterGui.Classes.FrameworkManager; -using Logger = OtterGui.Log.Logger; -using MessageService = Luna.MessageService; namespace Glamourer.Services; @@ -38,146 +14,19 @@ public static class StaticServiceManager { public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger log, Glamourer glamourer) { - EventWrapperBase.ChangeLogger(log); - var services = new ServiceManager(new Luna.Logger("Glamourer")) + var services = new ServiceManager(log) .AddExistingService(log) - .AddMeta() - .AddInterop() - .AddEvents() - .AddData() - .AddDesigns() - .AddState() - .AddUi() + .AddSingleton() + .AddSingleton() + .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddExistingService(glamourer); - DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); - services.AddIServices(typeof(ImRaii).Assembly); services.AddIServices(typeof(ServiceManager).Assembly); - services.AddIServices(typeof(Glamourer).Assembly); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(p => p.GetRequiredService()); + DalamudServices.AddServices(services, pi); + services.BuildProvider(); return services; } - - private static ServiceManager AddMeta(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddEvents(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddData(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddInterop(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddDesigns(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddState(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddUi(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); } diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 635584f..ba11c9a 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,41 +1,56 @@ using Dalamud.Interface; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using ImSharp; using Luna; -using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) - : TextureCache(dataManager, textureProvider), IDisposable +public sealed class TextureService(IUiBuilder uiBuilder, ITextureProvider textureProvider) + : IDisposable, IUiService { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); + public IDalamudTextureWrap? LoadIcon(uint iconId) + { + var icon = textureProvider.GetFromGameIcon(new GameIconLookup(iconId)); + if (!icon.TryGetWrap(out var wrap, out _)) + return null; + + return wrap; + } + + public bool TryLoadIcon(uint iconId, [NotNullWhen(true)] out IDalamudTextureWrap? wrap) + { + wrap = LoadIcon(iconId); + return wrap is not null; + } + public (ImTextureId, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) { - if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) + if (item.IconId.Id is not 0 && TryLoadIcon(item.IconId.Id, out var ret)) return (ret.Id, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); - return idx < 12 && _slotIcons[idx] != null + return idx < 12 && _slotIcons[idx] is not null ? (_slotIcons[idx]!.Id, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) : (default, Vector2.Zero, true); } public (ImTextureId, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) { - if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) + if (item.IconId.Id is not 0 && TryLoadIcon(item.IconId.Id, out var ret)) return (ret.Id, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); - if (idx == uint.MaxValue) + if (idx is uint.MaxValue) return (default, Vector2.Zero, true); idx += 12; - return idx < 13 && _slotIcons[idx] != null + return idx < 13 && _slotIcons[idx] is not null ? (_slotIcons[idx]!.Id, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) : (default, Vector2.Zero, true); } @@ -72,7 +87,7 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage SetIcon(EquipSlot.RFinger, 28); SetIcon(EquipSlot.MainHand, 17); SetIcon(EquipSlot.OffHand, 18); - Set(BonusItemFlag.Glasses.ToName(), (int) BonusItemFlag.Glasses.ToIndex() + 12, 55); + Set(BonusItemFlag.Glasses.ToName(), (int)BonusItemFlag.Glasses.ToIndex() + 12, 55); ret[EquipSlot.LFinger.ToIndex()] = ret[EquipSlot.RFinger.ToIndex()]; return ret; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index fbb6a55..e129376 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui; @@ -13,7 +14,7 @@ using Luna; namespace Glamourer.State; -public unsafe class FunModule : IDisposable +public sealed unsafe class FunModule : IDisposable, IRequiredService { public enum FestivalType { diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 1c41769..ee94743 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -5,6 +5,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; using ImSharp; +using Luna; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,7 +17,7 @@ public class InternalStateEditor( HumanModelList humans, ItemManager items, GPoseService gPose, - ICondition condition) + ICondition condition) : IService { /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. @@ -168,7 +169,7 @@ public class InternalStateEditor( public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, out StainIds oldStains, uint key = 0) { - oldItem = state.ModelData.Item(slot); + oldItem = state.ModelData.Item(slot); oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 9800445..75992f2 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -5,6 +5,7 @@ using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; +using Luna; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -17,22 +18,22 @@ namespace Glamourer.State; /// It handles applying those changes as well as redrawing the actor if necessary. /// public class StateApplier( - UpdateSlotService _updateSlot, - VisorService _visor, - WeaponService _weapon, - ChangeCustomizeService _changeCustomize, - ItemManager _items, - PenumbraService _penumbra, - MetaService _metaService, - ActorObjectManager _objects, - CrestService _crests, - DirectXService _directX) + UpdateSlotService updateSlot, + VisorService visor, + WeaponService weaponService, + ChangeCustomizeService changeCustomize, + ItemManager items, + PenumbraService penumbra, + MetaService metaService, + ActorObjectManager objects, + CrestService crests, + DirectXService directX) : IRequiredService { /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) { foreach (var actor in data.Objects) - _penumbra.RedrawObject(actor, RedrawType.Redraw); + penumbra.RedrawObject(actor, RedrawType.Redraw); } /// @@ -60,17 +61,17 @@ public class StateApplier( var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize); if (!flags.RequiresRedraw() || !mdl.IsHuman) { - _changeCustomize.UpdateCustomize(mdl, customize); + changeCustomize.UpdateCustomize(mdl, customize); } - else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene) + else if (data.Objects.Count > 1 && objects.IsInGPose && !actor.IsGPoseOrCutscene) { var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize; *mdlCustomize = customize; - _penumbra.RedrawObject(actor, RedrawType.AfterGPose); + penumbra.RedrawObject(actor, RedrawType.AfterGPose); } else { - _penumbra.RedrawObject(actor, RedrawType.Redraw); + penumbra.RedrawObject(actor, RedrawType.Redraw); } } } @@ -104,12 +105,12 @@ public class StateApplier( if (checkRestrictions) { var customize = mdl.GetCustomize(); - var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); - _updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem); + var (_, resolvedItem) = items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); + updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem); } else { - _updateSlot.UpdateEquipSlot(actor.Model, slot, armor); + updateSlot.UpdateEquipSlot(actor.Model, slot, armor); } } } @@ -134,7 +135,7 @@ public class StateApplier( if (!mdl.IsHuman) continue; - _updateSlot.UpdateBonusSlot(actor.Model, slot, item); + updateSlot.UpdateBonusSlot(actor.Model, slot, item); } } @@ -164,15 +165,15 @@ public class StateApplier( { case < 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _updateSlot.UpdateStain(actor.Model, slot, stains); + updateSlot.UpdateStain(actor.Model, slot, stains); break; case 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.MainHand, stains); + weaponService.LoadStain(actor, EquipSlot.MainHand, stains); break; case 11: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.OffHand, stains); + weaponService.LoadStain(actor, EquipSlot.OffHand, stains); break; } } @@ -217,7 +218,7 @@ public class StateApplier( { var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); + weaponService.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); } /// Apply a weapon to the offhand. @@ -225,7 +226,7 @@ public class StateApplier( { stains = weapon.PrimaryId.Id == 0 ? StainIds.None : stains; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); + weaponService.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); } /// Change a meta state. @@ -242,24 +243,24 @@ public class StateApplier( case MetaIndex.HatState: { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetHatState(actor, value); + metaService.SetHatState(actor, value); return; } case MetaIndex.WeaponState: { // Only apply to the GPose character because otherwise we get some weird incompatibility when leaving GPose. - if (_objects.IsInGPose) + if (objects.IsInGPose) foreach (var actor in data.Objects.Where(a => a.IsGPoseOrCutscene)) - _metaService.SetWeaponState(actor, value); + metaService.SetWeaponState(actor, value); else foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetWeaponState(actor, value); + metaService.SetWeaponState(actor, value); return; } case MetaIndex.VisorState: { foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _visor.SetVisorState(actor.Model, value); + visor.SetVisorState(actor.Model, value); return; } case MetaIndex.EarState: @@ -286,7 +287,7 @@ public class StateApplier( public void ChangeCrests(ActorData data, CrestFlag flags) { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _crests.UpdateCrests(actor, flags); + crests.UpdateCrests(actor, flags); } /// @@ -337,7 +338,7 @@ public class StateApplier( value.Model.Apply(ref baseTable[MaterialValueIndex.FromKey(index).RowIndex], mode); } - _directX.ReplaceColorTable(texture, baseTable); + directX.ReplaceColorTable(texture, baseTable); } } @@ -370,7 +371,7 @@ public class StateApplier( foreach (var (key, value) in values) value.Model.Apply(ref table[key.RowIndex], mode); - _directX.ReplaceColorTable(texture, table); + directX.ReplaceColorTable(texture, table); } } } @@ -421,5 +422,5 @@ public class StateApplier( } public ActorData GetData(ActorState state) - => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; + => objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ad77853..0652cc4 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,4 +1,5 @@ using Glamourer.Api.Enums; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Designs.Links; @@ -6,7 +7,6 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -43,8 +43,8 @@ public class StateEditor( var actors = Applier.ForceRedraw(state, source.RequiresChange()); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateFinalized.Invoke(StateFinalizationType.ModelChange, actors); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Model, source, state, actors)); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.ModelChange, actors)); } /// @@ -57,7 +57,7 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {idx.ToName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value))); } /// @@ -70,8 +70,8 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, - new EntireCustomizeTransaction(applied, old, customizeInput)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.EntireCustomize, settings.Source, state, actors, + new EntireCustomizeTransaction(applied, old, customizeInput))); } /// @@ -95,21 +95,21 @@ public class StateEditor( if (type is StateChangeType.Equip) { - StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, new EquipTransaction(slot, old, item))); } else if (slot is EquipSlot.MainHand) { var oldOff = state.ModelData.Item(EquipSlot.OffHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(old, oldOff, oldGauntlets, item, oldOff, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item, oldOff, oldGauntlets))); } else { var oldMain = state.ModelData.Item(EquipSlot.MainHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item, oldGauntlets))); } } @@ -122,7 +122,7 @@ public class StateEditor( var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item))); } /// @@ -157,24 +157,24 @@ public class StateEditor( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { - StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value))); } else if (slot is EquipSlot.MainHand) { var oldOff = state.ModelData.Item(EquipSlot.OffHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(old, oldOff, oldGauntlets, item!.Value, oldOff, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item!.Value, oldOff, oldGauntlets))); } else { var oldMain = state.ModelData.Item(EquipSlot.MainHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item!.Value, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item!.Value, oldGauntlets))); } - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, oldStains, stains!.Value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, oldStains, stains!.Value))); } /// @@ -187,7 +187,7 @@ public class StateEditor( var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains))); } /// @@ -200,7 +200,7 @@ public class StateEditor( var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Crest, settings.Source, state, actors, new CrestTransaction(slot, old, crest)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Crest, settings.Source, state, actors, new CrestTransaction(slot, old, crest))); } /// @@ -218,7 +218,7 @@ public class StateEditor( var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {flag} in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, new ParameterTransaction(flag, old, @new)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Parameter, settings.Source, state, actors, new ParameterTransaction(flag, old, @new))); } public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings) @@ -230,8 +230,8 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, - new MaterialTransaction(index, oldValue, newValue.Game, null, null)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.MaterialValue, settings.Source, state, actors, + new MaterialTransaction(index, oldValue, newValue.Game, null, null))); } public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) @@ -243,8 +243,7 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, true); Glamourer.Log.Verbose( $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, - new MaterialTransaction(index, null, null, null, null)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null, null, null))); } /// @@ -257,7 +256,7 @@ public class StateEditor( var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value))); } /// @@ -422,9 +421,9 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors)); // FIXME: maybe later if (settings.IsFinal) - StateFinalized.Invoke(StateFinalizationType.DesignApplied, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.DesignApplied, actors)); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 241a0b1..0991e62 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -15,6 +15,8 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using Glamourer.Api.Enums; +using Glamourer.Config; +using Luna; namespace Glamourer.State; @@ -23,7 +25,7 @@ namespace Glamourer.State; /// it always updates the base state for existing states, /// and either discards the changes or updates the model state too. /// -public class StateListener : IDisposable +public sealed class StateListener : IDisposable, IRequiredService { private readonly Configuration _config; private readonly ActorManager _actors; @@ -155,12 +157,12 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private void OnCustomizeChange(Model model, ref CustomizeArray customize) + private void OnCustomizeChange(in ChangeCustomizeService.Arguments arguments) { - if (!model.IsHuman) + if (!arguments.Model.IsHuman) return; - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -168,7 +170,7 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out _customizeState)) return; - UpdateCustomize(actor, _customizeState, ref customize, false); + UpdateCustomize(actor, _customizeState, ref arguments.Customize, false); } private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) @@ -214,9 +216,9 @@ public class StateListener : IDisposable /// A draw model loads a new equipment piece. /// Update base data, apply or update model data, and protect against restricted gear. /// - private void OnEquipSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) + private void OnEquipSlotUpdating(in EquipSlotUpdating.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -227,66 +229,66 @@ public class StateListener : IDisposable if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { - HandleEquipSlot(actor, state, slot, ref armor); - locked = state.Sources[slot, false] is StateSource.IpcFixed; + HandleEquipSlot(actor, state, arguments.Slot, ref arguments.Armor); + locked = state.Sources[arguments.Slot, false] is StateSource.IpcFixed; } - _funModule.ApplyFunToSlot(actor, ref armor, slot); + _funModule.ApplyFunToSlot(actor, ref arguments.Armor, arguments.Slot); if (!_config.UseRestrictedGearProtection || locked) return; - var customize = model.GetCustomize(); - (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); + var customize = arguments.Model.GetCustomize(); + (_, arguments.Armor) = _items.RestrictedGear.ResolveRestricted(arguments.Armor, arguments.Slot, customize.Race, customize.Gender); } - private void OnBonusSlotUpdating(Model model, BonusItemFlag slot, ref CharacterArmor item, ref ulong returnValue) + private void OnBonusSlotUpdating(in BonusSlotUpdating.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) - switch (UpdateBaseData(actor, state, slot, item)) + switch (UpdateBaseData(actor, state, arguments.Slot, arguments.Armor)) { // Base data changed equipment while actors were not there. // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (!state.Sources[slot].IsFixed()) - _manager.ChangeBonusItem(state, slot, state.BaseData.BonusItem(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot].IsFixed()) + _manager.ChangeBonusItem(state, arguments.Slot, state.BaseData.BonusItem(arguments.Slot), ApplySettings.Game); else apply = true; if (apply) - item = state.ModelData.BonusItem(slot).Armor(); + arguments.Armor = state.ModelData.BonusItem(arguments.Slot).Armor(); break; // Use current model data. - case UpdateState.NoChange: item = state.ModelData.BonusItem(slot).Armor(); break; + case UpdateState.NoChange: arguments.Armor = state.ModelData.BonusItem(arguments.Slot).Armor(); break; case UpdateState.Transformed: break; } } - private void OnGearsetDataLoaded(Actor actor, Model model) + private void OnGearsetDataLoaded(in GearsetDataLoaded.Arguments arguments) { - if (!actor.Valid || _condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (!arguments.Actor.Valid || _condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // ensure actor and state are valid. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); + _stateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Gearset, actors)); } - private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) + private void OnMovedEquipment(in MovedEquipment.Arguments arguments) { var (identifier, objects) = _objects.PlayerData; if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var state)) return; - foreach (var (slot, item, stain) in items) + foreach (var (slot, item, stain) in arguments.Items) { var currentItem = state.BaseData.Item(slot); var model = slot is EquipSlot.MainHand or EquipSlot.OffHand @@ -335,75 +337,75 @@ public class StateListener : IDisposable /// Update base data, apply or update model data. /// Verify consistent weapon types. /// - private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + private void OnWeaponLoading(in WeaponLoading.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand - && weapon.Variant == 0 - && weapon.Weapon.Id != 0 - && _fistOffhands.TryGetValue(actor, out var lastFistOffhand)) + if (arguments.Slot is EquipSlot.OffHand + && arguments.Weapon.Variant.Id is 0 + && arguments.Weapon.Weapon.Id is not 0 + && _fistOffhands.TryGetValue(arguments.Actor, out var lastFistOffhand)) { - Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); - weapon = lastFistOffhand; + Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{arguments.Actor.Address:X}."); + arguments.Weapon = lastFistOffhand; } - if (!actor.Identifier(_actors, out var identifier) + if (!arguments.Actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; var apply = false; - switch (UpdateBaseData(actor, state, slot, weapon)) + switch (UpdateBaseData(arguments.Actor, state, arguments.Slot, arguments.Weapon)) { // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (!state.Sources[slot, false].IsFixed()) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot, false].IsFixed()) + _manager.ChangeItem(state, arguments.Slot, state.BaseData.Item(arguments.Slot), ApplySettings.Game); else apply = true; - if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot, true].IsFixed()) + _manager.ChangeStains(state, arguments.Slot, state.BaseData.Stain(arguments.Slot), ApplySettings.Game); else apply = true; break; case UpdateState.NoChange: apply = true; break; } - var baseType = slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; - var modelType = state.ModelData.Item(slot).Type; + var baseType = arguments.Slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; + var modelType = state.ModelData.Item(arguments.Slot).Type; if (apply) { // Only allow overwriting identical weapons var canApply = baseType == modelType - || _gPose.InGPose && actor.IsGPoseOrCutscene; - var newWeapon = state.ModelData.Weapon(slot); + || _gPose.InGPose && arguments.Actor.IsGPoseOrCutscene; + var newWeapon = state.ModelData.Weapon(arguments.Slot); if (canApply) { - weapon = newWeapon; + arguments.Weapon = newWeapon; } else { - if (weapon.Skeleton.Id != 0) - weapon = weapon.With(newWeapon.Stains); + if (arguments.Weapon.Skeleton.Id is not 0) + arguments.Weapon = arguments.Weapon.With(newWeapon.Stains); // Force unlock if necessary. - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); + _manager.ChangeItem(state, arguments.Slot, state.BaseData.Item(arguments.Slot), ApplySettings.Game with { Key = state.Combination }); } } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) + if (arguments.Slot is EquipSlot.MainHand && arguments.Weapon.Skeleton.Id is > 1600 and < 1651) { - lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, - weapon.Stains); - _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + lastFistOffhand = new CharacterWeapon((PrimaryId)(arguments.Weapon.Skeleton.Id + 50), arguments.Weapon.Weapon, arguments.Weapon.Variant, + arguments.Weapon.Stains); + _fistOffhands[arguments.Actor] = lastFistOffhand; + Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{arguments.Actor.Address:X}."); } - _funModule.ApplyFunToWeapon(actor, ref weapon, slot); + _funModule.ApplyFunToWeapon(arguments.Actor, ref arguments.Weapon, arguments.Slot); } /// Update base data for a single changed equipment slot. @@ -526,26 +528,26 @@ public class StateListener : IDisposable } } - private void OnCrestChange(Actor actor, CrestFlag slot, ref bool value) + private void OnCrestChange(in CrestService.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors, out var identifier) + if (!arguments.Actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; - switch (UpdateBaseCrest(actor, state, slot, value)) + switch (UpdateBaseCrest(arguments.Actor, state, arguments.Slot, arguments.Value)) { case UpdateState.Change: - if (!state.Sources[slot].IsFixed()) - _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot].IsFixed()) + _manager.ChangeCrest(state, arguments.Slot, state.BaseData.Crest(arguments.Slot), ApplySettings.Game); else - value = state.ModelData.Crest(slot); + arguments.Value = state.ModelData.Crest(arguments.Slot); break; case UpdateState.NoChange: case UpdateState.HatHack: - value = state.ModelData.Crest(slot); + arguments.Value = state.ModelData.Crest(arguments.Slot); break; case UpdateState.Transformed: break; } @@ -674,7 +676,7 @@ public class StateListener : IDisposable } /// Handle visor state changes made by the game. - private unsafe void OnVisorChange(Model model, bool game, ref bool value) + private unsafe void OnVisorChange(in VisorStateChanged.Arguments arguments) { // Skip updates when in customize update. if (ChangeCustomizeService.InUpdate.InMethod) @@ -683,14 +685,14 @@ public class StateListener : IDisposable // Find appropriate actor and state. // We do not need to handle fixed designs, // since a fixed design would already have established state-tracking. - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (!actor.IsCharacter) return; // Only actually change anything if the actor state changed, // when equipping headgear the method is called with the current draw object state, // which corrupts Glamourer's assumed game state otherwise. - if (!game && actor.AsCharacter->DrawData.IsVisorToggled != value) + if (!arguments.NewVisorState && actor.AsCharacter->DrawData.IsVisorToggled != arguments.Value) return; if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -703,24 +705,24 @@ public class StateListener : IDisposable return; // Update visor base state. - if (state.BaseData.SetVisor(value)) + if (state.BaseData.SetVisor(arguments.Value)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.VisorState].IsFixed()) - value = state.ModelData.IsVisorToggled(); + arguments.Value = state.ModelData.IsVisorToggled(); else - _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.VisorState, arguments.Value, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsVisorToggled(); + arguments.Value = state.ModelData.IsVisorToggled(); } } /// Handle visor state changes made by the game. - private void OnVieraEarChange(Actor actor, ref bool value) + private void OnVieraEarChange(in VieraEarStateChanged.Arguments arguments) { // Value is inverted compared to our own handling. @@ -728,98 +730,98 @@ public class StateListener : IDisposable if (ChangeCustomizeService.InUpdate.InMethod) return; - if (!actor.IsCharacter) + if (!arguments.Actor.IsCharacter) return; - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update visor base state. - if (state.BaseData.SetEarsVisible(!value)) + if (state.BaseData.SetEarsVisible(!arguments.State)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.EarState].IsFixed()) - value = !state.ModelData.AreEarsVisible(); + arguments.State = !state.ModelData.AreEarsVisible(); else - _manager.ChangeMetaState(state, MetaIndex.EarState, !value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.EarState, !arguments.State, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = !state.ModelData.AreEarsVisible(); + arguments.State = !state.ModelData.AreEarsVisible(); } } /// Handle Hat Visibility changes. These act on the game object. - private void OnHeadGearVisibilityChange(Actor actor, ref bool value) + private void OnHeadGearVisibilityChange(in HeadGearVisibilityChanged.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Find appropriate state. // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update hat visibility state. - if (state.BaseData.SetHatVisible(value)) + if (state.BaseData.SetHatVisible(arguments.Visible)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.HatState].IsFixed()) - value = state.ModelData.IsHatVisible(); + arguments.Visible = state.ModelData.IsHatVisible(); else - _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.HatState, arguments.Visible, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsHatVisible(); + arguments.Visible = state.ModelData.IsHatVisible(); } } /// Handle Weapon Visibility changes. These act on the game object. - private void OnWeaponVisibilityChange(Actor actor, ref bool value) + private void OnWeaponVisibilityChange(in WeaponVisibilityChanged.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Find appropriate state. // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update weapon visibility state. - if (state.BaseData.SetWeaponVisible(value)) + if (state.BaseData.SetWeaponVisible(arguments.Value)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.WeaponState].IsFixed()) - value = state.ModelData.IsWeaponVisible(); + arguments.Value = state.ModelData.IsWeaponVisible(); else - _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.WeaponState, arguments.Value, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsWeaponVisible(); + arguments.Value = state.ModelData.IsWeaponVisible(); } } @@ -893,9 +895,9 @@ public class StateListener : IDisposable ApplyParameters(_creatingState, drawObject); } - private void OnCustomizeChanged(Model model) + private void OnCustomizeChanged(in Model model) { - if (_customizeState == null) + if (_customizeState is null) { var actor = _penumbra.GameObjectFromDrawObject(model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 633c444..df2029b 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; @@ -10,6 +11,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using ImSharp; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -33,7 +35,7 @@ public sealed class StateManager( ModSettingApplier modApplier, GPoseService gPose) : StateEditor(editor, applier, changeEvent, finalizeEvent, jobChange, config, items, merger, modApplier, gPose), - IReadOnlyDictionary + IReadOnlyDictionary, IService { private readonly Dictionary _states = []; @@ -278,10 +280,10 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // only invoke if we define this reset call as the final call in our state update. if (isFinal) - StateFinalized.Invoke(StateFinalizationType.Revert, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Revert, objects)); } public void ResetAdvancedDyes(ActorState state, StateSource source, uint key = 0) @@ -303,9 +305,9 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset advanced dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, objects)); } public void ResetAdvancedCustomizations(ActorState state, StateSource source, uint key = 0) @@ -326,9 +328,9 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, objects)); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -341,21 +343,21 @@ public sealed class StateManager( foreach (var flag in CustomizeParameterExtensions.AllFlags) state.Sources[flag] = StateSource.Game; - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) { - actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + data = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); foreach (var (idx, mat) in state.Materials.Values) - Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game); + Applier.ChangeMaterialValue(state, data, MaterialValueIndex.FromKey(idx), mat.Game); } state.Materials.Clear(); Glamourer.Log.Verbose( - $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, data)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, data)); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -368,13 +370,13 @@ public sealed class StateManager( state.ModelData.ModelId = state.BaseData.ModelId; state.ModelData.Customize = state.BaseData.Customize; - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) - actors = Applier.ChangeCustomize(state, true); + data = Applier.ChangeCustomize(state, true); Glamourer.Log.Verbose( - $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertCustomize, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertCustomize, data)); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -400,32 +402,32 @@ public sealed class StateManager( state.ModelData.SetBonusItem(slot, state.BaseData.BonusItem(slot)); } - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) { - actors = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); + data = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) { - Applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), + Applier.ChangeArmor(data, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); } foreach (var slot in BonusExtensions.AllFlags) { var item = state.ModelData.BonusItem(slot); - Applier.ChangeBonusItem(actors, slot, item.PrimaryId, item.Variant); + Applier.ChangeBonusItem(data, slot, item.PrimaryId, item.Variant); } - var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; + var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? data.OnlyGPose() : data; Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); - var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; + var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? data.OnlyGPose() : data; Applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); } Glamourer.Log.Verbose( - $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertEquipment, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertEquipment, data)); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -516,9 +518,9 @@ public sealed class StateManager( forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reapply, source, state, data)); if (isFinal) - StateFinalized.Invoke(StateFinalizationType.Reapply, data); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Reapply, data)); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -537,9 +539,9 @@ public sealed class StateManager( forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reapply, source, state, data)); // invoke the automation update based on what reset is. - StateFinalized.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); + StateFinalized.Invoke(new StateFinalized.Arguments(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data)); } public void DeleteState(ActorIdentifier identifier) diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 6fcdde0..66d2d67 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -7,6 +7,7 @@ using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -14,7 +15,7 @@ using StringU8 = ImSharp.StringU8; namespace Glamourer.Unlocks; -public class CustomizeUnlockManager : IDisposable, ISavable +public sealed class CustomizeUnlockManager : IDisposable, ISavable, IRequiredService { private readonly SaveService _saveService; private readonly IClientState _clientState; @@ -78,7 +79,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable _unlocked.TryAdd(pair.Data, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); time = DateTimeOffset.UtcNow; - _event.Invoke(ObjectUnlocked.Type.Customization, pair.Data, time); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, pair.Data, time)); Save(); return true; } @@ -87,7 +88,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable public unsafe bool IsUnlockedGame(uint dataId) { var instance = UIState.Instance(); - if (instance == null) + if (instance is null) return false; return UIState.Instance()->IsUnlockLinkUnlocked(dataId); @@ -101,7 +102,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable Glamourer.Log.Debug("[UnlockManager] Scanning for new unlocked customizations."); var instance = UIState.Instance(); - if (instance == null) + if (instance is null) return; try @@ -112,7 +113,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable { if (instance->IsUnlockLinkUnlocked(id) && _unlocked.TryAdd(id, time)) { - _event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time)); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time))); ++count; } } @@ -139,7 +140,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable _setUnlockLinkValueHook.Original(uiState, data, value); try { - if (value == 0) + if (value is 0) return; var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -148,7 +149,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable if (id != data || !_unlocked.TryAdd(id, time)) continue; - _event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time)); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time))); Save(); break; } @@ -159,7 +160,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable } } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.UnlockFileCustomize; public void Save() @@ -169,7 +170,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => UnlockDictionaryHelpers.Save(writer, Unlocked); private void Load() - => UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), + => UnlockDictionaryHelpers.Load(ToFilePath(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), "customization"); /// Create a list of all unlockable hairstyles and face paints. diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 6caa6a1..87ce720 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Unlocks; -public sealed class FavoriteManager : ISavable +public sealed class FavoriteManager : ISavable, IService { public readonly record struct FavoriteHairStyle(Gender Gender, SubRace Race, CustomizeIndex Type, CustomizeValue Id) { @@ -94,7 +94,7 @@ public sealed class FavoriteManager : ISavable Save(); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.FavoriteFile; private void Save() diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 6708267..fbc35c9 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -11,13 +12,13 @@ using Cabinet = Lumina.Excel.Sheets.Cabinet; namespace Glamourer.Unlocks; -public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary +public sealed class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary, IService { - private readonly SaveService _saveService; - private readonly ItemManager _items; - private readonly IClientState _clientState; - private readonly IFramework _framework; - private readonly ObjectUnlocked _event; + private readonly SaveService _saveService; + private readonly ItemManager _items; + private readonly IClientState _clientState; + private readonly IFramework _framework; + private readonly ObjectUnlocked _event; private readonly ObjectIdentification _identifier; private readonly Dictionary _unlocked = new(); @@ -100,12 +101,13 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary= (uint) _items.ItemSheet.Count) + if (itemId.Id >= (uint)_items.ItemSheet.Count) { time = DateTimeOffset.MinValue; return true; @@ -210,7 +212,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary fileNames.UnlockFileItems; public void Save() @@ -265,7 +267,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _items.ItemData.TryGetValue(id, EquipSlot.MainHand, out _), "item"); UpdateModels(version); } @@ -296,13 +298,13 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary