Merge branch 'luna' into luna-advdye-extensions

# Conflicts:
#	Glamourer/Designs/DesignEditor.cs
#	Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs
#	Glamourer/State/StateEditor.cs
This commit is contained in:
Ottermandias 2026-02-22 22:03:57 +01:00
commit 93ac50cbdc
171 changed files with 3709 additions and 3038 deletions

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

View file

@ -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

View file

@ -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<ActorState> 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);
}));
}

View file

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

View file

@ -1,4 +1,5 @@
using Glamourer.Api.Api;
using Glamourer.Config;
using Luna;
namespace Glamourer.Api;

View file

@ -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);
}

View file

@ -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<AutomationChanged.ChangeIdentifierArguments>().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<AutomationChanged.ChangeIdentifierArguments>().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);

View file

@ -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<AutoDesignSet>, IDisposable
public sealed class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDisposable, IService
{
public const int CurrentVersion = 1;
@ -77,7 +75,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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));
}
/// <remarks> Only used to move between sets. </remarks>
@ -275,8 +273,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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}.");
}
}
}

View file

@ -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<Design> leaf)
{
Glamourer.Messager.NotificationMessage($"Could not find design with path {design.Item1}, skipped fixed design.",
NotificationType.Warning);

View file

@ -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<Design> SortMode { get; set; } = ISortMode<Design>.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<ColorId, uint> 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<Design>[] ValidSortModes =
[
ISortMode<Design>.FoldersFirst,
ISortMode<Design>.Lexicographical,
new DesignFileSystem.CreationDate(),
new DesignFileSystem.InverseCreationDate(),
new DesignFileSystem.UpdateDate(),
new DesignFileSystem.InverseUpdateDate(),
ISortMode<Design>.InverseFoldersFirst,
ISortMode<Design>.InverseLexicographical,
ISortMode<Design>.FoldersLast,
ISortMode<Design>.InverseFoldersLast,
ISortMode<Design>.InternalOrder,
ISortMode<Design>.InverseInternalOrder,
];
}
/// <summary> Convert SortMode Types to their name. </summary>
private class SortModeConverter : JsonConverter<ISortMode<Design>>
private class SortModeConverter : JsonConverter<ISortMode>
{
public override void WriteJson(JsonWriter writer, ISortMode<Design>? value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer)
{
value ??= ISortMode<Design>.FoldersFirst;
value ??= ISortMode.FoldersFirst;
serializer.Serialize(writer, value.GetType().Name);
}
public override ISortMode<Design> ReadJson(JsonReader reader, Type objectType, ISortMode<Design>? existingValue,
bool hasExistingValue,
public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
var name = serializer.Deserialize<string>(reader);
if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode))
return existingValue ?? ISortMode<Design>.FoldersFirst;
if (serializer.Deserialize<string>(reader) is { } name)
return ISortMode.Valid.GetValueOrDefault(name, existingValue ?? ISortMode.FoldersFirst);
return mode;
return existingValue ?? ISortMode.FoldersFirst;
}
}
}

View file

@ -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;
}

View file

@ -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<DesignPanelFlag> 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

View file

@ -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)

View file

@ -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,
}

View file

@ -0,0 +1,84 @@
using Glamourer.Services;
using Luna;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Glamourer.Config;
public sealed class IgnoredMods : ConfigurationFile<FilenameService>, IReadOnlySet<string>
{
public override int CurrentVersion
=> 1;
private readonly HashSet<string> _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<string>().OfType<string>())
_ignoredMods.Add(value);
}
public override string ToFilePath(FilenameService fileNames)
=> fileNames.IgnoredModsFile;
public IEnumerator<string> 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<string> other)
=> _ignoredMods.IsProperSubsetOf(other);
public bool IsProperSupersetOf(IEnumerable<string> other)
=> _ignoredMods.IsProperSupersetOf(other);
public bool IsSubsetOf(IEnumerable<string> other)
=> _ignoredMods.IsSubsetOf(other);
public bool IsSupersetOf(IEnumerable<string> other)
=> _ignoredMods.IsSupersetOf(other);
public bool Overlaps(IEnumerable<string> other)
=> _ignoredMods.Overlaps(other);
public bool SetEquals(IEnumerable<string> other)
=> _ignoredMods.SetEquals(other);
}

View file

@ -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<FilenameService>
{
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;
}

View file

@ -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<Design>
{
#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<Design>? 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<Mod, ModSettings> 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<Guid>() ?? throw new ArgumentNullException("Identifier"),
Name = new LowerString(json["Name"]?.ToObject<string>() ?? throw new ArgumentNullException("Name")),
Name = json["Name"]?.ToObject<string>() ?? throw new ArgumentNullException("Name"),
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
Tags = ParseTags(json),
LastEdit = json["LastEdit"]?.ToObject<DateTimeOffset>() ?? 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>() ?? string.Empty;
design.Path.SortName = json["SortOrderName"]?.Value<string>()?.FixName();
design.SetWriteProtected(json["WriteProtected"]?.ToObject<bool>() ?? false);
LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
LoadEquip(items, json["Equipment"], design, design.Name, true);
@ -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;
}

View file

@ -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)

View file

@ -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<LabelStringHandlerBuffer> 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<string, Rgba32>
public sealed class DesignColors : ISavable, IReadOnlyDictionary<string, Rgba32>, IService
{
public const string AutomaticName = "Automatic";
public static readonly StringU8 AutomaticNameU8 = new("Automatic"u8);
@ -183,7 +76,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, Rgba32>
SaveAndInvoke();
}
public string ToFilename(FilenameService fileNames)
public string ToFilePath(FilenameService fileNames)
=> fileNames.DesignColorFile;
public void Save(StreamWriter writer)

View file

@ -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);
}

View file

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

View file

@ -1,3 +1,4 @@
using Glamourer.Config;
using Glamourer.Designs.History;
using Glamourer.Designs.Links;
using Glamourer.Events;
@ -5,7 +6,6 @@ using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Services;
using ImSharp;
using Luna;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}
/// <inheritdoc/>
@ -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)));
}

View file

@ -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<Design>, 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<Design>
{
public ReadOnlySpan<byte> Name
=> "Creation Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
}
public struct UpdateDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Update Date (Older First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit));
}
public struct InverseCreationDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Creation Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
}
public struct InverseUpdateDate : OtterGui.Filesystem.ISortMode<Design>
{
public ReadOnlySpan<byte> Name
=> "Update Date (Newer First)"u8;
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit));
}
private void OnChange(OtterGui.Filesystem.FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != OtterGui.Filesystem.FileSystemChangeType.Reload)
_saveService.QueueSave(this);
}
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? data)
{
switch (type)
{
case DesignChanged.Type.Created:
var parent = Root;
if ((data as CreationTransaction?)?.Path is { } path)
try
{
parent = FindOrCreateAllFolders(path);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.",
NotificationType.Error);
}
CreateDuplicateLeaf(parent, design.Name.Text, design);
return;
case DesignChanged.Type.Deleted:
if (TryGetValue(design, out var leaf1))
Delete(leaf1);
return;
case DesignChanged.Type.ReloadedAll:
Reload();
return;
case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName:
if (!TryGetValue(design, out var leaf2))
return;
var old = oldName.FixName();
if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
RenameWithDuplicates(leaf2, design.Name);
return;
}
}
// Used for saving and loading.
private static string DesignToIdentifier(Design design)
=> design.Identifier.ToString();
private static string DesignToName(Design design)
=> design.Name.Text.FixName();
private static bool DesignHasDefaultPath(Design design, string fullPath)
{
var regex = new Regex($@"^{Regex.Escape(DesignToName(design))}( \(\d+\))?$");
return regex.IsMatch(fullPath);
}
private static (string, bool) SaveDesign(Design design, string fullPath)
// Only save pairs with non-default paths.
=> DesignHasDefaultPath(design, fullPath)
? (string.Empty, false)
: (DesignToIdentifier(design), true);
internal static void MigrateOldPaths(SaveService saveService, Dictionary<string, string> oldPaths)
{
if (oldPaths.Count == 0)
return;
var file = saveService.FileNames.DesignFileSystem;
try
{
JObject jObject;
if (File.Exists(file))
{
var text = File.ReadAllText(file);
jObject = JObject.Parse(text);
var dict = jObject["Data"]?.ToObject<Dictionary<string, string>>();
if (dict != null)
foreach (var (key, value) in dict)
oldPaths.TryAdd(key, value);
jObject["Data"] = JToken.FromObject(oldPaths);
}
else
{
jObject = new JObject
{
["Data"] = JToken.FromObject(oldPaths),
["EmptyFolders"] = JToken.FromObject(Array.Empty<string>()),
};
}
var data = jObject.ToString(Formatting.Indented);
File.WriteAllText(file, data);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}");
}
}
public string ToFilename(FilenameService fileNames)
=> fileNames.DesignFileSystem;
public void Save(StreamWriter writer)
{
SaveToFile(writer, SaveDesign, true);
_tabSelected.Unsubscribe(OnTabSelected);
_designChanged.Unsubscribe(OnDesignChanged);
}
}

View file

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

View file

@ -1,4 +1,5 @@
using Dalamud.Utility;
using Glamourer.Config;
using Glamourer.Designs.History;
using Glamourer.Designs.Links;
using Glamourer.Events;
@ -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!));
}
/// <summary> Create a new temporary design without adding it to the manager. </summary>
@ -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
/// <summary> Rename a design. </summary>
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)));
}
/// <summary> Change the description of a design. </summary>
@ -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)));
}
/// <summary> Change the associated color of a design. </summary>
@ -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)));
}
/// <summary> Add a new tag to a design. The tags remain sorted. </summary>
@ -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)));
}
/// <summary> Remove a tag from a design by its index. </summary>
@ -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)));
}
/// <summary> Rename a tag from a design by its index. The tags stay sorted.</summary>
@ -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))));
}
/// <summary> Add an associated mod to a design. </summary>
@ -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)));
}
/// <summary> Remove an associated mod from a design. </summary>
@ -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)));
}
/// <summary> Add or update an associated mod to a design. </summary>
@ -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));
}
/// <summary> Set the quick design bar display status of a design. </summary>
@ -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));
}
/// <summary> Change whether to apply a specific customize value. </summary>
@ -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)));
}
/// <summary> Change whether to apply a specific equipment piece. </summary>
@ -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)));
}
/// <summary> Change whether to apply a specific equipment piece. </summary>
@ -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)));
}
/// <summary> Change whether to apply a specific stain. </summary>
@ -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)));
}
/// <summary> Change whether to apply a specific crest visibility. </summary>
@ -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)));
}
/// <summary> Change the application value of one of the meta flags. </summary>
@ -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)));
}
/// <summary> Change the application value of a customize parameter. </summary>
@ -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)));
}
/// <summary> Change multiple application values at once. </summary>
@ -511,7 +512,6 @@ public sealed class DesignManager : DesignEditor
{
var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile);
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(text) ?? new Dictionary<string, string>();
var migratedFileSystemPaths = new Dictionary<string, string>(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));
}
/// <summary> Split a given string into its folder path and its name, if <paramref name="handlePath"/> is true. </summary>

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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);
}
}

View file

@ -1,5 +1,6 @@
using Glamourer.Api.Enums;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Services;

View file

@ -1,6 +1,6 @@
using Glamourer.Automation;
using Luna;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
namespace Glamourer.Designs.Links;

View file

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

View file

@ -1,9 +1,9 @@

using Glamourer.Config;
using Luna;
namespace Glamourer.Designs.Special;
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService
public class RandomDesignGenerator(DesignStorage designs, Configuration config) : IService
{
private readonly Random _rng = new();
private readonly WeakReference<Design> _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<IDesignPredicate> 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()),
};
}

View file

@ -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<Design> Get(IEnumerable<Design> designs, DesignFileSystem fileSystem)
=> designs.Select(d => Transform(d, fileSystem))
IEnumerable<Design> Get(IEnumerable<Design> designs)
=> designs.Select(Transform)
.Where(Invoke)
.Select(t => t.Design);
public static IEnumerable<Design> Get(IReadOnlyList<IDesignPredicate> predicates, IEnumerable<Design> designs, DesignFileSystem fileSystem)
static IEnumerable<Design> Get(IReadOnlyList<IDesignPredicate> predicates, IEnumerable<Design> 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<string> data)
=> data.Any(t => t == value);
private static bool IsContained(string value, IEnumerable<string> 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<IDesignPredicate> GeneratePredicates(string restrictions)
{
if (restrictions.Length == 0)
if (restrictions.Length is 0)
return [];
List<IDesignPredicate> predicates = new(1);
@ -153,9 +151,9 @@ public static class RandomPredicate
public static string GeneratePredicateString(IReadOnlyCollection<IDesignPredicate> 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)}}}";

View file

@ -1,16 +1,16 @@
using OtterGui.Classes;
using Luna;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the auto-reload gear setting is changed in glamourer configuration.
/// </summary>
public sealed class AutoRedrawChanged()
: EventWrapper<bool, AutoRedrawChanged.Priority>(nameof(AutoRedrawChanged))
public sealed class AutoRedrawChanged(Logger log)
: EventBase<bool, AutoRedrawChanged.Priority>(nameof(AutoRedrawChanged), log)
{
public enum Priority
{
/// <seealso cref="Api.StateApi.OnGPoseChange"/>
StateApi = int.MinValue,
}
}
}

View file

@ -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;
/// <summary>
/// Triggered when an automated design is changed in any way.
/// <list type="number">
/// <item>Parameter is the type of the change </item>
/// <item>Parameter is the added or changed design set or null on deletion. </item>
/// <item>Parameter is additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class AutomationChanged()
: EventWrapper<AutomationChanged.Type, AutoDesignSet?, object?, AutomationChanged.Priority>(nameof(AutomationChanged))
/// <summary> Triggered when an automated design is changed in any way. </summary>
public sealed class AutomationChanged(Logger log)
: EventBase<AutomationChanged.Arguments, AutomationChanged.Priority>(nameof(AutomationChanged), log)
{
public enum Type
{
/// <summary> 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)]. </summary>
/// <summary> Add a new set. Names and identifiers do not have to be unique. It is not enabled by default. </summary>
AddedSet,
/// <summary> Delete a given set. Additional data is the index it got removed from [int].</summary>
/// <summary> Delete a given set. </summary>
DeletedSet,
/// <summary> Rename a given set. Names do not have to be unique. Additional data is the old name and the new name [(string, string)]. </summary>
/// <summary> Rename a given set. Names do not have to be unique. </summary>
RenamedSet,
/// <summary> 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)]. </summary>
/// <summary> Move a given set to a different position. </summary>
MovedSet,
/// <summary> 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?)]. </summary>
/// <summary> Change the identifier a given set is associated with to another one. </summary>
ChangeIdentifier,
/// <summary> Toggle the enabled state of a given set. Additional data is the thus disabled other set, if any [AutoDesignSet?]. </summary>
/// <summary> Toggle the enabled state of a given set. </summary>
ToggleSet,
/// <summary> Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. </summary>
/// <summary> Change the used base state of a given set. </summary>
ChangedBase,
/// <summary> Change the resetting of temporary settings for a given set. Additional data is the new value. </summary>
/// <summary> Change the resetting of temporary settings for a given set. </summary>
ChangedTemporarySettingsReset,
/// <summary> Add a new associated design to a given set. Additional data is the index it got added at [int]. </summary>
/// <summary> Add a new associated design to a given set. </summary>
AddedDesign,
/// <summary> Remove a given associated design from a given set. Additional data is the index it got removed from [int]. </summary>
/// <summary> Remove a given associated design from a given set. </summary>
DeletedDesign,
/// <summary> 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)]. </summary>
/// <summary> Move a given associated design in the list of a given set. </summary>
MovedDesign,
/// <summary> 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)]. </summary>
/// <summary> Change the linked design in an associated design for a given set. </summary>
ChangedDesign,
/// <summary> 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)]. </summary>
/// <summary> Change the job condition in an associated design for a given set. </summary>
ChangedConditions,
/// <summary> 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)]. </summary>
/// <summary> Change the application type in an associated design for a given set. </summary>
ChangedType,
/// <summary> 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)] </summary>
/// <summary> Change the additional data for a specific design type. </summary>
ChangedData,
}
@ -76,4 +72,92 @@ public sealed class AutomationChanged()
/// <seealso cref="Gui.Tabs.AutomationTab.RandomRestrictionDrawer.OnAutomationChange"/>
RandomRestrictionDrawer = -1,
}
/// <param name="Type"> The type of change. </param>
public record Arguments(Type Type, AutoDesignSet Set)
{
public T As<T>() where T : Arguments
=> (T)this;
}
/// <param name="Set"> The added set. </param>
/// <param name="Index"> The index the set was added at. </param>
/// <param name="Name"> The name of the added set. </param>
public sealed record AddedSetArguments(AutoDesignSet Set, int Index, string Name) : Arguments(Type.AddedSet, Set);
/// <param name="Set"> The deleted set. </param>
/// <param name="Index"> The index the set was deleted from. </param>
public sealed record DeletedSetArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedSet, Set);
/// <param name="Set"> The renamed set. </param>
/// <param name="OldName"> The old name of the set. </param>
/// <param name="NewName"> The new name of the set. </param>
public sealed record RenamedSetArguments(AutoDesignSet Set, string OldName, string NewName) : Arguments(Type.RenamedSet, Set);
/// <param name="Set"> The moved set. </param>
/// <param name="OldIndex"> The index the set was moved from. </param>
/// <param name="NewIndex"> The index the set was moved to. </param>
public sealed record MovedSetArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedSet, Set);
/// <param name="Set"> The set that got its associated identifiers changed. </param>
/// <param name="OldIdentifiers"> The prior identifiers that got removed. </param>
/// <param name="NewIdentifier"> The new identifiers associated with the set. </param>
/// <param name="DisabledSet"> A set that was associated with the new identifiers before and got disabled through this change, or null. </param>
public sealed record ChangeIdentifierArguments(
AutoDesignSet Set,
ActorIdentifier[] OldIdentifiers,
ActorIdentifier NewIdentifier,
AutoDesignSet? DisabledSet) : Arguments(Type.ChangeIdentifier, Set);
/// <param name="Set"> The set that got toggled on or off. </param>
/// <param name="DisabledSet"> A set with the same association that got disabled due to this change, or null. </param>
public sealed record ToggleSetArguments(AutoDesignSet Set, AutoDesignSet? DisabledSet) : Arguments(Type.ToggleSet, Set);
/// <param name="Set"> The set that changed its base state. </param>
/// <param name="OldBase"> The old base state of the set. </param>
/// <param name="NewBase"> The new base state of the set. </param>
public sealed record ChangedBaseArguments(AutoDesignSet Set, AutoDesignSet.Base OldBase, AutoDesignSet.Base NewBase) : Arguments(Type.ChangedBase, Set);
/// <param name="Set"> The set that changed whether it resets all temporary settings. </param>
/// <param name="NewValue"> The new state of resetting temporary settings. </param>
public sealed record ChangedTemporarySettingsResetArguments(AutoDesignSet Set, bool NewValue) : Arguments(Type.ChangedTemporarySettingsReset, Set);
/// <param name="Set"> The set that added a new design. </param>
/// <param name="Index"> The index the new design was added in the set. </param>
public sealed record AddedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.AddedDesign, Set);
/// <param name="Set"> The set that removed a design. </param>
/// <param name="Index"> The index the design was removed from. </param>
public sealed record DeletedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedDesign, Set);
/// <param name="Set"> The set that moved a design. </param>
/// <param name="OldIndex"> The index the design was moved from in the set. </param>
/// <param name="NewIndex"> The index the design was moved from to the set. </param>
public sealed record MovedDesignArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedDesign, Set);
/// <param name="Set"> The set that changed a design. </param>
/// <param name="DesignIndex"> The index of the changed design. </param>
/// <param name="OldDesign"> The design previously assigned to the set. </param>
/// <param name="NewDesign"> The design the old one was changed to. </param>
public sealed record ChangedDesignArguments(AutoDesignSet Set, int DesignIndex, IDesignStandIn OldDesign, IDesignStandIn NewDesign)
: Arguments(Type.ChangedDesign, Set);
/// <param name="Set"> The set that changed a job group condition. </param>
/// <param name="DesignIndex"> The index of the changed design. </param>
/// <param name="OldGroup"> The prior job condition for the design. </param>
/// <param name="NewGroup"> The new job condition for the design. </param>
public sealed record ChangedConditionsArguments(AutoDesignSet Set, int DesignIndex, JobGroup OldGroup, JobGroup NewGroup)
: Arguments(Type.ChangedConditions, Set);
/// <param name="Set"> The set that changed its application type. </param>
/// <param name="DesignIndex"> The index of the changed design. </param>
/// <param name="OldType"> The old application flags. </param>
/// <param name="NewType"> The new application flags. </param>
public sealed record ChangedTypeArguments(AutoDesignSet Set, int DesignIndex, ApplicationType OldType, ApplicationType NewType)
: Arguments(Type.ChangedType, Set);
/// <param name="Set"> The set that got a design data changed. </param>
/// <param name="DesignIndex"> The index of the changed design. </param>
/// <param name="NewData"> The new additional data for the changed design. </param>
public sealed record ChangedDataArguments(AutoDesignSet Set, int DesignIndex, object NewData) : Arguments(Type.ChangedData, Set);
}

View file

@ -1,25 +1,32 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a model flags a bonus slot for an update.
/// <list type="number">
/// <item>Parameter is the model with a flagged slot. </item>
/// <item>Parameter is the bonus slot changed. </item>
/// <item>Parameter is the model values to change the bonus piece to. </item>
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
/// </list>
/// </summary>
public sealed class BonusSlotUpdating()
: EventWrapperRef34<Model, BonusItemFlag, CharacterArmor, ulong, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating))
/// <summary> Triggered when a model flags a bonus slot for an update. </summary>
public sealed class BonusSlotUpdating(Logger log)
: EventBase<BonusSlotUpdating.Arguments, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnBonusSlotUpdating"/>
StateListener = 0,
}
public ref struct Arguments(Model model, BonusItemFlag flag, ref CharacterArmor armor, ref ulong returnValue)
{
/// <summary> The draw object with an updated bonus slot. </summary>
public readonly Model Model = model;
/// <summary> The updated slot. </summary>
public readonly BonusItemFlag Slot = flag;
/// <summary> The model data for the new bonus slot. This can be changed. </summary>
public ref CharacterArmor Armor = ref armor;
/// <summary> The return value of the event. If this is <see cref="ulong.MaxValue"/>, the original function will be called and its return value used, otherwise this value will be returned. </summary>
public ref ulong ReturnValue = ref returnValue;
}
}

View file

@ -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;
/// <summary>
/// Triggered when a Design is edited in any way.
/// <list type="number">
/// <item>Parameter is the type of the change </item>
/// <item>Parameter is the changed Design. </item>
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class DesignChanged()
: EventWrapper<DesignChanged.Type, Design, ITransaction?, DesignChanged.Priority>(nameof(DesignChanged))
/// <summary> Triggered when a Design is edited in any way. </summary>
public sealed class DesignChanged(Logger log)
: EventBase<DesignChanged.Arguments, DesignChanged.Priority>(nameof(DesignChanged), log)
{
public enum Type
{
@ -138,10 +132,13 @@ public sealed class DesignChanged()
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
AutoDesignManager = 1,
/// <seealso cref="DesignFileSystem.OnDesignChange"/>
/// <seealso cref="DesignFileSystem.OnDesignChanged"/>
DesignFileSystem = 0,
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
/// <seealso cref="DesignHeader.OnDesignChanged"/>
DesignHeader = 0,
/// <seealso cref="DesignFileSystemDrawer.OnDesignChanged"/>
DesignFileSystemSelector = -1,
/// <seealso cref="DesignComboBase.OnDesignChanged"/>
@ -150,4 +147,10 @@ public sealed class DesignChanged()
/// <seealso cref="EditorHistory.OnDesignChanged" />
EditorHistory = -1000,
}
/// <summary> Arguments for the DesignChanged event. </summary>
/// <param name="Type"> The type of changed. </param>
/// <param name="Design"> The changed design. </param>
/// <param name="Transaction"> A transaction with further data corresponding to the change. </param>
public readonly record struct Arguments(Type Type, Design Design, ITransaction? Transaction = null);
}

View file

@ -1,25 +1,32 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a model flags an equipment slot for an update.
/// <list type="number">
/// <item>Parameter is the model with a flagged slot. </item>
/// <item>Parameter is the equipment slot changed. </item>
/// <item>Parameter is the model values to change the equipment piece to. </item>
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
/// </list>
/// </summary>
public sealed class EquipSlotUpdating()
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, EquipSlotUpdating.Priority>(nameof(EquipSlotUpdating))
/// <summary> Triggered when a model flags an equipment slot for an update. </summary>
public sealed class EquipSlotUpdating(Logger log)
: EventBase<EquipSlotUpdating.Arguments, EquipSlotUpdating.Priority>(nameof(EquipSlotUpdating), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnEquipSlotUpdating"/>
StateListener = 0,
}
public ref struct Arguments(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
{
/// <summary> The draw object with an updated equipment slot. </summary>
public readonly Model Model = model;
/// <summary> The updated slot. </summary>
public readonly EquipSlot Slot = slot;
/// <summary> The model data for the new equipment slot. This can be changed. </summary>
public ref CharacterArmor Armor = ref armor;
/// <summary> The return value of the event. If this is <see cref="ulong.MaxValue"/>, the original function will be called and its return value used, otherwise this value will be returned. </summary>
public ref ulong ReturnValue = ref returnValue;
}
}

View file

@ -1,23 +1,23 @@
using OtterGui.Classes;
using Luna;
using Penumbra.String;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the player equips a gear set.
/// <list type="number">
/// <item>Parameter is the name of the gear set. </item>
/// <item>Parameter is the id of the gear set. </item>
/// <item>Parameter is the id of the prior gear set. </item>
/// <item>Parameter is the id of the associated glamour. </item>
/// <item>Parameter is the job id of the associated job. </item>
/// </list>
/// </summary>
public sealed class EquippedGearset()
: EventWrapper<string, int, int, byte, byte, EquippedGearset.Priority>(nameof(EquippedGearset))
/// <summary> Triggered when the player equips a gear set. </summary>
public sealed class EquippedGearset(Logger log)
: EventBase<EquippedGearset.Arguments, EquippedGearset.Priority>(nameof(EquippedGearset), log)
{
public enum Priority
{
/// <seealso cref="Automation.AutoDesignApplier.OnEquippedGearset"/>
AutoDesignApplier = 0,
}
/// <summary> Arguments for the EquippedGearset event. </summary>
/// <param name="Name"> The name of the equipped gear set. </param>
/// <param name="Id"> The ID of the equipped gear set.</param>
/// <param name="PriorId"> The ID of the gear set previously equipped.</param>
/// <param name="GlamourId"> The ID of the associated glamour plate. </param>
/// <param name="JobId"> The job ID of the associated job. </param>
public readonly record struct Arguments(ByteString Name, int Id, int PriorId, int GlamourId, byte JobId);
}

View file

@ -1,9 +1,9 @@
using Dalamud.Plugin.Services;
using OtterGui.Classes;
using Luna;
namespace Glamourer.Events;
public sealed class GPoseService : EventWrapper<bool, GPoseService.Priority>
public sealed class GPoseService : EventBase<bool, GPoseService.Priority>
{
private readonly IFramework _framework;
private readonly IClientState _state;
@ -19,8 +19,8 @@ public sealed class GPoseService : EventWrapper<bool, GPoseService.Priority>
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;

View file

@ -1,21 +1,23 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// 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.
/// <list type="number">
/// <item>The model draw object associated with the finished load (Also fired by other players on render) </item>
/// </list>
/// 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.
/// </summary>
public sealed class GearsetDataLoaded()
: EventWrapper<Actor, Model, GearsetDataLoaded.Priority>(nameof(GearsetDataLoaded))
public sealed class GearsetDataLoaded(Logger log)
: EventBase<GearsetDataLoaded.Arguments, GearsetDataLoaded.Priority>(nameof(GearsetDataLoaded), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnGearsetDataLoaded"/>
StateListener = 0,
}
/// <summary> Arguments for the GearsetDataLoaded event. </summary>
/// <param name="Actor"> The actor that loaded a gear set. </param>
/// <param name="Model"> The draw object that finished the load. </param>
public readonly record struct Arguments(Actor Actor, Model Model);
}

View file

@ -1,21 +1,24 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the visibility of head gear is changed.
/// <list type="number">
/// <item>Parameter is the actor with changed head gear visibility. </item>
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class HeadGearVisibilityChanged()
: EventWrapperRef2<Actor, bool, HeadGearVisibilityChanged.Priority>(nameof(HeadGearVisibilityChanged))
/// <summary> Triggered when the visibility of head gear is changed. </summary>
public sealed class HeadGearVisibilityChanged(Logger log)
: EventBase<HeadGearVisibilityChanged.Arguments, HeadGearVisibilityChanged.Priority>(nameof(HeadGearVisibilityChanged), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnHeadGearVisibilityChange"/>
StateListener = 0,
}
public ref struct Arguments(Actor actor, ref bool visible)
{
/// <summary> The actor whose head gear visibility changed. </summary>
public readonly Actor Actor = actor;
/// <summary> The new visibility state. </summary>
public ref bool Visible = ref visible;
}
}

View file

@ -1,4 +1,4 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -10,12 +10,14 @@ namespace Glamourer.Events;
/// <item>Parameter is an array of slots updated and corresponding item ids and stains. </item>
/// </list>
/// </summary>
public sealed class MovedEquipment()
: EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment))
public sealed class MovedEquipment(Logger log)
: EventBase<MovedEquipment.Arguments, MovedEquipment.Priority>(nameof(MovedEquipment), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnMovedEquipment"/>
StateListener = 0,
}
public readonly record struct Arguments(params (EquipSlot, uint, StainIds)[] Items);
}

View file

@ -1,17 +1,10 @@
using OtterGui.Classes;
using Luna;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a new item or customization is unlocked.
/// <list type="number">
/// <item>Parameter is the type of the unlocked object </item>
/// <item>Parameter is the id of the unlocked object. </item>
/// <item>Parameter is the timestamp of the unlock. </item>
/// </list>
/// </summary>
public sealed class ObjectUnlocked()
: EventWrapper<ObjectUnlocked.Type, uint, DateTimeOffset, ObjectUnlocked.Priority>(nameof(ObjectUnlocked))
/// <summary> Triggered when a new item or customization is unlocked. </summary>
public sealed class ObjectUnlocked(Logger log)
: EventBase<ObjectUnlocked.Arguments, ObjectUnlocked.Priority>(nameof(ObjectUnlocked), log), IRequiredService
{
public enum Type
{
@ -21,8 +14,15 @@ public sealed class ObjectUnlocked()
public enum Priority
{
/// <seealso cref="Gui.Tabs.UnlocksTab.UnlockTable.OnObjectUnlock"/>
/// <seealso cref="Gui.Tabs.UnlocksTab.UnlockTable.Cache.OnItemUnlock"/>
/// <remarks> Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework. </remarks>
UnlockTable = 0,
}
/// <summary> Arguments for the ObjectUnlocked event. </summary>
/// <param name="Type"> The type of the unlocked object. </param>
/// <param name="Id"> The ID of the unlocked object. </param>
/// <param name="Timestamp"> The timestamp of the unlock event.</param>
public readonly record struct Arguments(Type Type, uint Id, DateTimeOffset Timestamp);
}

View file

@ -1,22 +1,22 @@
using OtterGui.Classes;
using Luna;
namespace Glamourer.Events;
/// <summary>
/// Triggered when Penumbra is reloaded.
/// </summary>
public sealed class PenumbraReloaded()
: EventWrapper<PenumbraReloaded.Priority>(nameof(PenumbraReloaded))
public sealed class PenumbraReloaded(Logger log)
: EventBase<PenumbraReloaded.Priority>(nameof(PenumbraReloaded), log)
{
public enum Priority
{
/// <seealso cref="Interop.ChangeCustomizeService.Restore"/>
/// <seealso cref="global::Glamourer.Interop.ChangeCustomizeService.Restore"/>
ChangeCustomizeService = 0,
/// <seealso cref="Interop.VisorService.Restore"/>
/// <seealso cref="global::Glamourer.Interop.VisorService.Restore"/>
VisorService = 0,
/// <seealso cref="Interop.VieraEarService.Restore"/>
/// <seealso cref="global::Glamourer.Interop.VieraEarService.Restore"/>
VieraEarService = 0,
}
}

View file

@ -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;
/// <summary>
/// Triggered when a Design is edited in any way.
/// <list type="number">
/// <item>Parameter is the type of the change </item>
/// <item>Parameter is the changed saved state. </item>
/// <item>Parameter is the existing actors using this saved state. </item>
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class StateChanged()
: EventWrapper<StateChangeType, StateSource, ActorState, ActorData, ITransaction?, StateChanged.Priority>(nameof(StateChanged))
/// <summary> Triggered when a state changes in any way. </summary>
public sealed class StateChanged(Logger log)
: EventBase<StateChanged.Arguments, StateChanged.Priority>(nameof(StateChanged), log)
{
public enum Priority
{
/// <seealso cref="Api.StateApi.OnStateChanged" />
GlamourerIpc = int.MinValue,
/// <seealso cref="Interop.Penumbra.PenumbraAutoRedraw.OnStateChanged" />
/// <seealso cref="global::Glamourer.Interop.Penumbra.PenumbraAutoRedraw.OnStateChanged" />
PenumbraAutoRedraw = 0,
/// <seealso cref="EditorHistory.OnStateChanged" />
EditorHistory = -1000,
}
/// <summary> Arguments for the StateChanged event. </summary>
/// <param name="Type"> The type of change that occured. </param>
/// <param name="Source"> The source of the change. </param>
/// <param name="State"> The changed state. </param>
/// <param name="Actors"> Any currently affected actors. </param>
/// <param name="Transaction"> Additional data depending on the type of change. </param>
public readonly record struct Arguments(
StateChangeType Type,
StateSource Source,
ActorState State,
ActorData Actors,
ITransaction? Transaction = null);
}

View file

@ -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;
/// <summary>
/// Triggered when a set of grouped changes finishes being applied to a Glamourer state.
/// <list type="number">
/// <item>Parameter is the operation that finished updating the saved state. </item>
/// <item>Parameter is the existing actors using this saved state. </item>
/// </list>
/// </summary>
public sealed class StateFinalized()
: EventWrapper<StateFinalizationType, ActorData, StateFinalized.Priority>(nameof(StateFinalized))
/// <summary> Triggered when a set of grouped changes finishes being applied to a Glamourer state. </summary>
public sealed class StateFinalized(Logger log)
: EventBase<StateFinalized.Arguments, StateFinalized.Priority>(nameof(StateFinalized), log)
{
public enum Priority
{
/// <seealso cref="StateApi.OnStateFinalized"/>
StateApi = int.MinValue,
}
/// <summary> Arguments for the StateFinalized event. </summary>
/// <param name="Type"> The operation that finished updating the saved state. </param>
/// <param name="Actors"> The existing actors using this saved state at the moment. </param>
public readonly record struct Arguments(StateFinalizationType Type, ActorData Actors);
}

View file

@ -1,25 +1,24 @@
using Glamourer.Designs;
using Glamourer.Gui;
using OtterGui.Classes;
using Luna;
namespace Glamourer.Events;
/// <summary>
/// Triggered when an automated design is changed in any way.
/// <list type="number">
/// <item>Parameter is the tab to select. </item>
/// <item>Parameter is the design to select if the tab is the designs tab. </item>
/// </list>
/// </summary>
public sealed class TabSelected()
: EventWrapper<MainTabType, Design?, TabSelected.Priority>(nameof(TabSelected))
/// <summary> Triggered to select a tab or design. </summary>
public sealed class TabSelected(Logger log)
: EventBase<TabSelected.Arguments, TabSelected.Priority>(nameof(TabSelected), log)
{
public enum Priority
{
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnTabSelected"/>
/// <seealso cref="DesignFileSystem.OnTabSelected"/>
DesignSelector = 0,
/// <seealso cref="Gui.MainWindow.OnTabSelected"/>
/// <seealso cref="Gui.MainTabBar.OnEvent"/>
MainWindow = 1,
}
/// <summary> Arguments for the TabSelected event. </summary>
/// <param name="Type"> The tab to be selected. </param>
/// <param name="Design"> The design to be selected in the Designs tab. </param>
public readonly record struct Arguments(MainTabType Type, Design? Design = null);
}

View file

@ -1,22 +1,24 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the state of viera ear visibility for any draw object is changed.
/// <list type="number">
/// <item>Parameter is the model with a changed viera ear visibility state. </item>
/// <item>Parameter is the new state. </item>
/// <item>Parameter is whether to call the original function. </item>
/// </list>
/// </summary>
public sealed class VieraEarStateChanged()
: EventWrapperRef2<Actor, bool, VieraEarStateChanged.Priority>(nameof(VieraEarStateChanged))
/// <summary> Triggered when the state of viera ear visibility for any draw object is changed. </summary>
public sealed class VieraEarStateChanged(Logger log)
: EventBase<VieraEarStateChanged.Arguments, VieraEarStateChanged.Priority>(nameof(VieraEarStateChanged), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnVieraEarChange"/>
StateListener = 0,
}
public ref struct Arguments(Actor actor, ref bool state)
{
/// <summary> The actor with a changed viera ear visibility state. </summary>
public readonly Actor Actor = actor;
/// <summary> The new ear visibility state. </summary>
public ref bool State = ref state;
}
}

View file

@ -1,22 +1,27 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the state of a visor for any draw object is changed.
/// <list type="number">
/// <item>Parameter is the model with a changed visor state. </item>
/// <item>Parameter is the new state. </item>
/// <item>Parameter is whether to call the original function. </item>
/// </list>
/// </summary>
public sealed class VisorStateChanged()
: EventWrapperRef3<Model, bool, bool, VisorStateChanged.Priority>(nameof(VisorStateChanged))
/// <summary> Triggered when the state of a visor for any draw object is changed. </summary>
public sealed class VisorStateChanged(Logger log)
: EventBase<VisorStateChanged.Arguments, VisorStateChanged.Priority>(nameof(VisorStateChanged), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnVisorChange"/>
StateListener = 0,
}
}
public ref struct Arguments(Model model, bool visorState, ref bool value)
{
/// <summary> The model with a changed visor state. </summary>
public readonly Model Model = model;
/// <summary> The game's visor state. </summary>
public readonly bool NewVisorState = visorState;
/// <summary> The actual new value </summary>
public ref bool Value = ref value;
}
}

View file

@ -1,20 +1,13 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a model flags an equipment slot for an update.
/// <list type="number">
/// <item>Parameter is the actor that has its weapons changed. </item>
/// <item>Parameter is the equipment slot changed (Mainhand or Offhand). </item>
/// <item>Parameter is the model values to change the weapon to. </item>
/// </list>
/// </summary>
public sealed class WeaponLoading()
: EventWrapperRef3<Actor, EquipSlot, CharacterWeapon, WeaponLoading.Priority>(nameof(WeaponLoading))
/// <summary> Triggered when a model flags an equipment slot for an update. </summary>
public sealed class WeaponLoading(Logger log)
: EventBase<WeaponLoading.Arguments, WeaponLoading.Priority>(nameof(WeaponLoading), log)
{
public enum Priority
{
@ -24,4 +17,16 @@ public sealed class WeaponLoading()
/// <seealso cref="Automation.AutoDesignApplier.OnWeaponLoading"/>
AutoDesignApplier = -1,
}
public ref struct Arguments(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
{
/// <summary> The actor that has its weapons changed. </summary>
public readonly Actor Actor = actor;
/// <summary> The changed equipment slot (either Mainhand or Offhand). </summary>
public readonly EquipSlot Slot = slot;
/// <summary> The model data for the new weapon. </summary>
public ref CharacterWeapon Weapon = ref weapon;
}
}

View file

@ -1,20 +1,24 @@
using OtterGui.Classes;
using Luna;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the visibility of weapons is changed.
/// <list type="number">
/// <item>Parameter is the actor with changed weapon visibility. </item>
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class WeaponVisibilityChanged() : EventWrapperRef2<Actor, bool, WeaponVisibilityChanged.Priority>(nameof(WeaponVisibilityChanged))
/// <summary> Triggered when the visibility of weapons is changed. </summary>
public sealed class WeaponVisibilityChanged(Logger log)
: EventBase<WeaponVisibilityChanged.Arguments, WeaponVisibilityChanged.Priority>(nameof(WeaponVisibilityChanged), log)
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnWeaponVisibilityChange"/>
StateListener = 0,
}
public ref struct Arguments(Actor actor, ref bool value)
{
/// <summary> The actor with changed weapon visibility. </summary>
public readonly Actor Actor = actor;
/// <summary> The new state. </summary>
public ref bool Value = ref value;
}
}

View file

@ -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<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
static int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
{
var (val, idx) = list.Cast<CustomizeData?>().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v);
if (val == null)
var (idx, val) = list.Cast<CustomizeData?>().Index().FirstOrDefault(p => p.Item!.Value.Value == v);
if (val is null)
{
output = null;
return -1;

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
@ -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;

View file

@ -1,4 +1,4 @@
<Project Sdk="Dalamud.NET.Sdk/14.0.1">
<Project Sdk="Dalamud.NET.Sdk/14.0.2">
<PropertyGroup>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName>
@ -22,10 +22,14 @@
<EmbeddedResource Include="LegacyTattoo.raw" />
</ItemGroup>
<PropertyGroup>
<Use_DalamudPackager>false</Use_DalamudPackager>
<Use_Dalamud_ImGui>false</Use_Dalamud_ImGui>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
<ProjectReference Include="..\Luna\Luna\Luna.csproj" />
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />

View file

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

View file

@ -1,3 +1,4 @@
using Glamourer.Config;
using ImSharp;
namespace Glamourer.Gui;

View file

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

View file

@ -1,6 +1,7 @@
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.Unlocks;
@ -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)

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Interop.PalettePlus;
using Glamourer.State;
@ -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();

View file

@ -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<DesignComboBase.CacheItem>(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,

View file

@ -2,6 +2,7 @@
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
@ -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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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<MainTabType>
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)
{

View file

@ -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);
}
}

View file

@ -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;

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Interop.Material;
using ImSharp;
using Luna;

View file

@ -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,

View file

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

View file

@ -1,4 +1,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<MainTabType>
{
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<ActorCacheItem>(filter, new StringU8("Filter..."u8));
var footer = new ButtonFooter();
@ -29,9 +32,18 @@ public sealed class ActorTab : TwoPanelLayout, ITab<MainTabType>
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;
}

View file

@ -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;

View file

@ -1,4 +1,5 @@
using Glamourer.Automation;
using Glamourer.Config;
using ImSharp;
using Luna;
using Penumbra.GameData.Actors;

View file

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

View file

@ -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<AutomationChanged.MovedSetArguments>().NewIndex; break;
}
}

View file

@ -1,9 +1,10 @@
using ImSharp;
using Glamourer.Config;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab;
public class AutomationTab : TwoPanelLayout, ITab<MainTabType>
public sealed class AutomationTab : TwoPanelLayout, ITab<MainTabType>
{
private readonly Configuration _config;
@ -30,7 +31,11 @@ public class AutomationTab : TwoPanelLayout, ITab<MainTabType>
=> 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;
}

View file

@ -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);

View file

@ -1,5 +1,6 @@
using Dalamud.Interface;
using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;
@ -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<Design> designs)
private static void LookupTooltip(IEnumerable<Design> designs)
{
using var _ = Im.Tooltip.Begin();
using var enumerator = designs.GetEnumerator();
while (enumerator.MoveNext())
{
Im.Text("Matches the following designs:"u8);
var name = _designFileSystem.TryGetValue(enumerator.Current, out var l) ? l.FullName() : enumerator.Current.Name.Text;
var name = enumerator.Current.Path.CurrentPath;
Im.Separator();
Im.BulletText(name);
while (enumerator.MoveNext())
{
name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text;
name = enumerator.Current.Path.CurrentPath;
Im.BulletText(name);
}
@ -375,7 +374,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private void DrawTotalPreview(IReadOnlyList<IDesignPredicate> 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<AutomationChanged.DeletedDesignArguments>().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<AutomationChanged.MovedDesignArguments>();
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<AutomationChanged.ChangedDesignArguments>().DesignIndex == _designIndex:
Close();
break;
}

View file

@ -4,6 +4,7 @@ using Glamourer.Designs.Special;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Unlocks;
using Glamourer.Config;
using ImSharp;
using Luna;
using Penumbra.GameData.Enums;
@ -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())

View file

@ -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:

View file

@ -1,4 +1,5 @@
using ImSharp;
using Glamourer.Config;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.DebugTab;

View file

@ -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<byte> label, params IGameDataDrawer[] subTrees)
{
public string Label { get; } = label;
public StringU8 Label { get; } = new(label);
public IReadOnlyList<IGameDataDrawer> 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<ModelEvaluationPanel>(),
provider.GetRequiredService<ObjectManagerPanel>(),
provider.GetRequiredService<PenumbraPanel>(),
@ -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<DataServiceDiagnosticsDrawer>(),
provider.GetRequiredService<IdentificationDrawer>(),
provider.GetRequiredService<RestrictedGearDrawer>(),
@ -62,7 +61,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
public static DebugTabHeader CreateDesigns(IServiceProvider provider)
=> new
(
"Designs",
"Designs"u8,
provider.GetRequiredService<DesignManagerPanel>(),
provider.GetRequiredService<DesignConverterPanel>(),
provider.GetRequiredService<DesignTesterPanel>(),
@ -72,7 +71,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
public static DebugTabHeader CreateState(IServiceProvider provider)
=> new
(
"State",
"State"u8,
provider.GetRequiredService<ActiveStatePanel>(),
provider.GetRequiredService<RetainedStatePanel>(),
provider.GetRequiredService<FunPanel>()
@ -81,7 +80,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
public static DebugTabHeader CreateUnlocks(IServiceProvider provider)
=> new
(
"Unlocks",
"Unlocks"u8,
provider.GetRequiredService<CustomizationUnlockPanel>(),
provider.GetRequiredService<ItemUnlockPanel>(),
provider.GetRequiredService<UnlockableItemsPanel>(),

View file

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

View file

@ -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);

View file

@ -1,4 +1,5 @@
using Glamourer.State;
using Glamourer.Config;
using Glamourer.State;
using ImSharp;
using Penumbra.GameData.Gui.Debug;

View file

@ -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);
}

View file

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

View file

@ -1,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;

View file

@ -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<Design, DesignFileSystemSelector.DesignState>
{
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<Design>.Leaf? leaf, bool clear, in DesignState storage = default)
{
base.Select(leaf, clear, storage);
var id = SelectedLeaf?.Value.Identifier ?? Guid.Empty;
if (id != _config.Ephemeral.SelectedDesign)
{
_config.Ephemeral.SelectedDesign = id;
_config.Ephemeral.Save();
}
}
protected override void DrawPopups()
{
DrawNewDesignPopup();
}
protected override void DrawLeafName(FileSystem<Design>.Leaf leaf, in DesignState state, bool selected)
{
var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
var name = _config.Ephemeral.IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text;
using var color = ImGuiColor.Text.Push(state.Color);
using var _ = ImUtf8.TreeNode(name, flag);
if (_config.AllowDoubleClickToApply && ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
_designApplier.ApplyToPlayer(leaf.Value);
}
public override void Dispose()
{
base.Dispose();
_event.Unsubscribe(OnDesignChange);
_selectionEvent.Unsubscribe(OnTabSelected);
_designColors.ColorChanged -= SetFilterDirty;
}
public override ISortMode<Design> SortMode
=> _config.SortMode;
protected override uint ExpandedFolderColor
=> ColorId.FolderExpanded.Value().Color;
protected override uint CollapsedFolderColor
=> ColorId.FolderCollapsed.Value().Color;
protected override uint FolderLineColor
=> ColorId.FolderLine.Value().Color;
protected override bool FoldersDefaultOpen
=> _config.OpenFoldersByDefault;
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _)
{
switch (type)
{
case DesignChanged.Type.ReloadedAll:
case DesignChanged.Type.Renamed:
case DesignChanged.Type.AddedTag:
case DesignChanged.Type.ChangedTag:
case DesignChanged.Type.RemovedTag:
case DesignChanged.Type.AddedMod:
case DesignChanged.Type.RemovedMod:
case DesignChanged.Type.Created:
case DesignChanged.Type.Deleted:
case DesignChanged.Type.ApplyCustomize:
case DesignChanged.Type.ApplyEquip:
case DesignChanged.Type.ApplyStain:
case DesignChanged.Type.ApplyCrest:
case DesignChanged.Type.Customize:
case DesignChanged.Type.Equip:
case DesignChanged.Type.ChangedColor:
SetFilterDirty();
break;
}
}
private void NewDesignButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false,
true))
{
_cloneDesign = null;
_clipboardText = null;
ImGui.OpenPopup("##NewDesign");
}
}
private void ImportDesignButton(Vector2 size)
{
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, "Try to import a design from your clipboard.", false,
true))
return;
try
{
_cloneDesign = null;
_clipboardText = ImGui.GetClipboardText();
ImGui.OpenPopup("##NewDesign");
}
catch
{
Glamourer.Messager.NotificationMessage("Could not import data from clipboard.", NotificationType.Error, false);
}
}
private void CloneDesignButton(Vector2 size)
{
var tt = SelectedLeaf == null
? "No design selected."
: "Clone the currently selected design to a duplicate";
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
return;
_clipboardText = null;
_cloneDesign = Selected!;
ImGui.OpenPopup("##NewDesign");
}
private void DeleteButton(Vector2 size)
=> DeleteSelectionButton(size,
new OtterGui.Classes.DoubleModifier(new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier1),
new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier2)), "design", "designs", _designManager.Delete);
private void DrawNewDesignPopup()
{
if (!ImGuiUtil.OpenNameField("##NewDesign", ref _newName))
return;
if (_clipboardText != null)
{
var design = _converter.FromBase64(_clipboardText, true, true, out _);
if (design is Design d)
_designManager.CreateClone(d, _newName, true);
else if (design != null)
_designManager.CreateClone(design, _newName, true);
else
Glamourer.Messager.NotificationMessage("Could not create a design, clipboard did not contain valid design data.",
NotificationType.Error, false);
_clipboardText = null;
}
else if (_cloneDesign != null)
{
_designManager.CreateClone(_cloneDesign, _newName, true);
_cloneDesign = null;
}
else
{
_designManager.CreateEmpty(_newName, true);
}
_newName = string.Empty;
}
private void OnTabSelected(MainTabType type, Design? design)
{
if (type == MainTabType.Designs && design != null)
SelectByValue(design);
}
#region Filters
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _designFilter = LowerString.Empty;
private int _filterType = -1;
private void SetFilterTooltip()
{
FilterTooltip = "Filter designs for those where their full paths or names contain the given substring.\n"
+ "Enter m:[string] to filter for designs with with a mod association containing the string.\n"
+ "Enter t:[string] to filter for designs set to specific tags.\n"
+ "Enter c:[string] to filter for designs set to specific colors.\n"
+ "Enter i:[string] to filter for designs containing specific items.\n"
+ "Enter n:[string] to filter only for design names and no paths.\n\n"
+ "Use None as a placeholder value that only matches empty lists or names.";
}
/// <summary> Appropriately identify and set the string filter and its type. </summary>
protected override bool ChangeFilter(string filterValue)
{
(_designFilter, _filterType) = filterValue.Length switch
{
0 => (LowerString.Empty, -1),
> 1 when filterValue[1] == ':' =>
filterValue[0] switch
{
'n' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
'm' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2),
'M' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2),
't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4),
'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4),
'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5),
'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5),
_ => (new LowerString(filterValue), 0),
},
_ => (new LowerString(filterValue), 0),
};
return true;
}
private const int EmptyOffset = 128;
private static (LowerString, int) ParseFilter(string value, int id)
{
value = value[2..];
var lower = new LowerString(value);
return (lower, lower.Lower is "none" ? id + EmptyOffset : id);
}
/// <summary>
/// The overwritten filter method also computes the state.
/// Folders have default state and are filtered out on the direct string instead of the other options.
/// If any filter is set, they should be hidden by default unless their children are visible,
/// or they contain the path search string.
/// </summary>
protected override bool ApplyFiltersAndState(FileSystem<Design>.IPath path, out DesignState state)
{
if (path is DesignFileSystem.Folder f)
{
state = default;
return FilterValue.Length > 0 && !f.FullName().Contains(FilterValue, IgnoreCase);
}
return ApplyFiltersAndState((DesignFileSystem.Leaf)path, out state);
}
/// <summary> Apply the string filters. </summary>
private bool ApplyStringFilters(DesignFileSystem.Leaf leaf, Design design)
{
return _filterType switch
{
-1 => false,
0 => !(_designFilter.IsContained(leaf.FullName()) || design.Name.Contains(_designFilter)),
1 => !design.Name.Contains(_designFilter),
2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)),
3 => !design.Tags.Any(_designFilter.IsContained),
4 => !design.DesignData.ContainsName(_designFilter),
5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color),
2 + EmptyOffset => design.AssociatedMods.Count > 0,
3 + EmptyOffset => design.Tags.Length > 0,
_ => false, // Should never happen
};
}
/// <summary> Combined wrapper for handling all filters and setting state. </summary>
private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state)
{
state = new DesignState(_designColors.GetColor(leaf.Value));
return ApplyStringFilters(leaf, leaf.Value);
}
#endregion
}

View file

@ -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<Design>() 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<byte> Text
=> _config.Ephemeral.IncognitoMode ? _incognito : _header;
private sealed class LockedButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton<AwesomeIcon>
{
public override bool IsVisible
=> fileSystem.Selection.Selection is not null;
public override AwesomeIcon Icon
=> ((Design)fileSystem.Selection.Selection!.Value).WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(((Design)fileSystem.Selection.Selection!.Value).WriteProtected()
? "Make this design editable."u8
: "Write-protect this design."u8);
public override void OnClick()
=> manager.SetWriteProtection((Design)fileSystem.Selection.Selection!.Value,
!((Design)fileSystem.Selection.Selection!.Value).WriteProtected());
}
public void Dispose()
{
_fileSystem.Selection.Changed -= OnSelectionChanged;
_designChanged.Unsubscribe(OnDesignChanged);
}
}

View file

@ -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)

View file

@ -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<EquipSlot> 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<string> MetaLabels =
private static readonly IReadOnlyList<StringU8> MetaLabels =
[
"Apply Wetness",
"Apply Hat Visibility",
"Apply Visor State",
"Apply Weapon Visibility",
"Apply Viera Ear Visibility",
new("Apply Wetness"u8),
new("Apply Hat Visibility"u8),
new("Apply Visor State"u8),
new("Apply Weapon Visibility"u8),
new("Apply Viera Ear Visibility"u8),
];
private void DrawMetaApplication()
{
using var id = ImUtf8.PushId("Meta");
const uint all = (uint)MetaExtensions.All;
var flags = (uint)_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<string> BonusSlotLabels =
private static readonly IReadOnlyList<StringU8> BonusSlotLabels =
[
"Apply Facewear",
new("Apply Facewear"u8),
];
private void DrawBonusSlotApplication()
{
using var id = ImUtf8.PushId("Bonus"u8);
var flags = _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<byte> 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<Design>() 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!);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,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);
}
}

View file

@ -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<ModCombo.CacheItem>(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<LabelStringHandlerBuffer> 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<CacheItem> GetItems()
=> penumbra.GetMods(fileSystem.Selection.Selection?.GetValue<Design>()?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count));
protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected)
{
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("<None Enabled>"u8);
Im.Text("<None Enabled>"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<CacheItem>
{
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;
}
}

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