diff --git a/Penumbra/Mods/Editor/ModNormalizer.cs b/Penumbra/Mods/Editor/ModNormalizer.cs index 437600c9..58e4fc08 100644 --- a/Penumbra/Mods/Editor/ModNormalizer.cs +++ b/Penumbra/Mods/Editor/ModNormalizer.cs @@ -1,4 +1,3 @@ -using System; using Dalamud.Interface.Internal.Notifications; using OtterGui; using OtterGui.Classes; @@ -279,19 +278,8 @@ public class ModNormalizer(ModManager _modManager, Configuration _config) private void ApplyRedirections() { foreach (var (group, groupIdx) in Mod.Groups.WithIndex()) - { - switch (group) - { - case SingleModGroup single: - foreach (var (option, optionIdx) in single.OptionData.WithIndex()) - _modManager.OptionEditor.SetFiles(option, _redirections[groupIdx + 1][optionIdx]); - break; - case MultiModGroup multi: - foreach (var (option, optionIdx) in multi.OptionData.WithIndex()) - _modManager.OptionEditor.SetFiles(option, _redirections[groupIdx + 1][optionIdx]); - break; - } - } + foreach (var (container, containerIdx) in group.DataContainers.WithIndex()) + _modManager.OptionEditor.SetFiles(container, _redirections[groupIdx + 1][containerIdx]); ++Step; } diff --git a/Penumbra/Mods/Groups/IModGroup.cs b/Penumbra/Mods/Groups/IModGroup.cs index ab367532..fcc8c093 100644 --- a/Penumbra/Mods/Groups/IModGroup.cs +++ b/Penumbra/Mods/Groups/IModGroup.cs @@ -5,6 +5,7 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; +using Penumbra.UI.ModsTab.Groups; namespace Penumbra.Mods.Groups; @@ -40,6 +41,8 @@ public interface IModGroup public int GetIndex(); + public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer); + public void AddData(Setting setting, Dictionary redirections, HashSet manipulations); public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index 173bf57e..d2c41f34 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -10,6 +10,8 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; +using Penumbra.UI.ModsTab; +using Penumbra.UI.ModsTab.Groups; using Penumbra.Util; namespace Penumbra.Mods.Groups; @@ -89,6 +91,9 @@ public class ImcModGroup(Mod mod) : IModGroup public int GetIndex() => ModGroup.GetIndex(this); + public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) + => new ImcModGroupEditDrawer(editDrawer, this); + private ushort GetCurrentMask(Setting setting) { var mask = DefaultEntry.AttributeMask; diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index f587fc8f..7fc9acb3 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -9,6 +9,7 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; +using Penumbra.UI.ModsTab.Groups; using Penumbra.Util; namespace Penumbra.Mods.Groups; @@ -107,6 +108,9 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup public int GetIndex() => ModGroup.GetIndex(this); + public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) + => new MultiModGroupEditDrawer(editDrawer, this); + public void AddData(Setting setting, Dictionary redirections, HashSet manipulations) { foreach (var (option, index) in OptionData.WithIndex().OrderByDescending(o => o.Value.Priority)) diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index 7a551322..4eec0746 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -7,6 +7,7 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; +using Penumbra.UI.ModsTab.Groups; using Penumbra.Util; namespace Penumbra.Mods.Groups; @@ -93,6 +94,9 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup public int GetIndex() => ModGroup.GetIndex(this); + public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) + => new SingleModGroupEditDrawer(editDrawer, this); + public void AddData(Setting setting, Dictionary redirections, HashSet manipulations) { if (OptionData.Count == 0) diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index 3c00dcc1..969ad3fa 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -37,8 +37,8 @@ public class ModGroupEditor( SingleModGroupEditor singleEditor, MultiModGroupEditor multiEditor, ImcModGroupEditor imcEditor, - CommunicatorService Communicator, - SaveService SaveService, + CommunicatorService communicator, + SaveService saveService, Configuration Config) : IService { public SingleModGroupEditor SingleEditor @@ -57,8 +57,8 @@ public class ModGroupEditor( return; group.DefaultSettings = defaultOption; - SaveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, group.Mod, group, null, null, -1); + saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, group.Mod, group, null, null, -1); } /// Rename an option group if possible. @@ -68,10 +68,10 @@ public class ModGroupEditor( if (oldName == newName || !VerifyFileName(group.Mod, group, newName, true)) return; - SaveService.ImmediateDelete(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.ImmediateDelete(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); group.Name = newName; - SaveService.ImmediateSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, group.Mod, group, null, null, -1); + saveService.ImmediateSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, group.Mod, group, null, null, -1); } /// Delete a given option group. Fires an event to prepare before actually deleting. @@ -79,22 +79,22 @@ public class ModGroupEditor( { var mod = group.Mod; var idx = group.GetIndex(); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, group, null, null, -1); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, group, null, null, -1); mod.Groups.RemoveAt(idx); - SaveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, null, null, null, idx); + saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, null, null, null, idx); } /// Move the index of a given option group. public void MoveModGroup(IModGroup group, int groupIdxTo) { - var mod = group.Mod; + var mod = group.Mod; var idxFrom = group.GetIndex(); if (!mod.Groups.Move(idxFrom, groupIdxTo)) return; - SaveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, group, null, null, idxFrom); + saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, group, null, null, idxFrom); } /// Change the internal priority of the given option group. @@ -104,8 +104,8 @@ public class ModGroupEditor( return; group.Priority = newPriority; - SaveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, group.Mod, group, null, null, -1); + saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, group.Mod, group, null, null, -1); } /// Change the description of the given option group. @@ -115,8 +115,8 @@ public class ModGroupEditor( return; group.Description = newDescription; - SaveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, group.Mod, group, null, null, -1); + saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, group.Mod, group, null, null, -1); } /// Rename the given option. @@ -126,8 +126,8 @@ public class ModGroupEditor( return; option.Name = newName; - SaveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); + saveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); } /// Change the description of the given option. @@ -137,8 +137,8 @@ public class ModGroupEditor( return; option.Description = newDescription; - SaveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); + saveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); } /// Set the meta manipulations for a given option. Replaces existing manipulations. @@ -148,10 +148,10 @@ public class ModGroupEditor( && subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m))) return; - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.Manipulations.SetTo(manipulations); - SaveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } /// Set the file redirections for a given option. Replaces existing redirections. @@ -160,10 +160,10 @@ public class ModGroupEditor( if (subMod.Files.SetEquals(replacements)) return; - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.Files.SetTo(replacements); - SaveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } /// Add additional file redirections to a given option, keeping already existing ones. Only fires an event if anything is actually added. @@ -173,8 +173,8 @@ public class ModGroupEditor( subMod.Files.AddFrom(additions); if (oldCount != subMod.Files.Count) { - SaveService.QueueSave(new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + saveService.QueueSave(new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } } @@ -184,10 +184,10 @@ public class ModGroupEditor( if (subMod.FileSwaps.SetEquals(swaps)) return; - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.FileSwaps.SetTo(swaps); - SaveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); + saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } /// Verify that a new option group name is unique in this mod. @@ -227,45 +227,45 @@ public class ModGroupEditor( => group switch { SingleModGroup s => SingleEditor.AddOption(s, option), - MultiModGroup m => MultiEditor.AddOption(m, option), - ImcModGroup i => ImcEditor.AddOption(i, option), - _ => null, + MultiModGroup m => MultiEditor.AddOption(m, option), + ImcModGroup i => ImcEditor.AddOption(i, option), + _ => null, }; public IModOption? AddOption(IModGroup group, string newName) => group switch { SingleModGroup s => SingleEditor.AddOption(s, newName), - MultiModGroup m => MultiEditor.AddOption(m, newName), - ImcModGroup i => ImcEditor.AddOption(i, newName), - _ => null, + MultiModGroup m => MultiEditor.AddOption(m, newName), + ImcModGroup i => ImcEditor.AddOption(i, newName), + _ => null, }; public IModGroup? AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync) => type switch { GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType), - GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), - GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, saveType), - _ => null, + GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), + GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, saveType), + _ => null, }; public (IModGroup?, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string name, SaveType saveType = SaveType.ImmediateSync) => type switch { GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType), - GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType), - GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType), - _ => (null, -1, false), + GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType), + GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType), + _ => (null, -1, false), }; public (IModOption?, int, bool) FindOrAddOption(IModGroup group, string name, SaveType saveType = SaveType.ImmediateSync) => group switch { SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType), - MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType), - ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType), - _ => (null, -1, false), + MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType), + ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType), + _ => (null, -1, false), }; public void MoveOption(IModOption option, int toIdx) diff --git a/Penumbra/UI/ModsTab/AddGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs similarity index 83% rename from Penumbra/UI/ModsTab/AddGroupDrawer.cs rename to Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs index 79c1bf9d..06cb4154 100644 --- a/Penumbra/UI/ModsTab/AddGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs @@ -12,25 +12,25 @@ using Penumbra.Mods.Manager; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.UI.Classes; -namespace Penumbra.UI.ModsTab; +namespace Penumbra.UI.ModsTab.Groups; public class AddGroupDrawer : IUiService { - private string _groupName = string.Empty; - private bool _groupNameValid = false; + private string _groupName = string.Empty; + private bool _groupNameValid = false; - private ImcManipulation _imcManip = new(EquipSlot.Head, 1, 1, new ImcEntry()); - private ImcEntry _defaultEntry; - private bool _imcFileExists; - private bool _entryExists; - private bool _entryInvalid; + private ImcManipulation _imcManip = new(EquipSlot.Head, 1, 1, new ImcEntry()); + private ImcEntry _defaultEntry; + private bool _imcFileExists; + private bool _entryExists; + private bool _entryInvalid; private readonly MetaFileManager _metaManager; - private readonly ModManager _modManager; + private readonly ModManager _modManager; public AddGroupDrawer(MetaFileManager metaManager, ModManager modManager) { _metaManager = metaManager; - _modManager = modManager; + _modManager = modManager; UpdateEntry(); } @@ -61,7 +61,7 @@ public class AddGroupDrawer : IUiService return; _modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _groupName); - _groupName = string.Empty; + _groupName = string.Empty; _groupNameValid = false; } @@ -74,7 +74,7 @@ public class AddGroupDrawer : IUiService return; _modManager.OptionEditor.AddModGroup(mod, GroupType.Multi, _groupName); - _groupName = string.Empty; + _groupName = string.Empty; _groupNameValid = false; } @@ -126,7 +126,7 @@ public class AddGroupDrawer : IUiService width, !_groupNameValid || _entryInvalid)) { _modManager.OptionEditor.ImcEditor.AddModGroup(mod, _groupName, _imcManip); - _groupName = string.Empty; + _groupName = string.Empty; _groupNameValid = false; } @@ -150,12 +150,12 @@ public class AddGroupDrawer : IUiService } catch (Exception) { - _defaultEntry = new ImcEntry(); + _defaultEntry = new ImcEntry(); _imcFileExists = false; - _entryExists = false; + _entryExists = false; } - _imcManip = _imcManip.Copy(_entryExists ? _defaultEntry : new ImcEntry()); + _imcManip = _imcManip.Copy(_entryExists ? _defaultEntry : new ImcEntry()); _entryInvalid = !_imcManip.Validate(true); } } diff --git a/Penumbra/UI/ModsTab/Groups/IModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/IModGroupEditDrawer.cs new file mode 100644 index 00000000..d7114147 --- /dev/null +++ b/Penumbra/UI/ModsTab/Groups/IModGroupEditDrawer.cs @@ -0,0 +1,6 @@ +namespace Penumbra.UI.ModsTab.Groups; + +public interface IModGroupEditDrawer +{ + public void Draw(); +} diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs new file mode 100644 index 00000000..2418c5cb --- /dev/null +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -0,0 +1,138 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Mods.Groups; +using Penumbra.Mods.Manager.OptionEditor; +using Penumbra.Mods.SubMods; + +namespace Penumbra.UI.ModsTab.Groups; + +public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGroup group) : IModGroupEditDrawer +{ + public void Draw() + { + using (ImUtf8.Group()) + { + ImUtf8.Text("Object Type"u8); + if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman) + ImUtf8.Text("Slot"u8); + ImUtf8.Text("Primary ID"); + if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory) + ImUtf8.Text("Secondary ID"); + ImUtf8.Text("Variant"u8); + + ImUtf8.TextFrameAligned("Material ID"u8); + ImUtf8.TextFrameAligned("Material Animation ID"u8); + ImUtf8.TextFrameAligned("Decal ID"u8); + ImUtf8.TextFrameAligned("VFX ID"u8); + ImUtf8.TextFrameAligned("Sound ID"u8); + ImUtf8.TextFrameAligned("Can Be Disabled"u8); + ImUtf8.TextFrameAligned("Default Attributes"u8); + } + + ImGui.SameLine(); + + var attributeCache = new ImcAttributeCache(group); + + using (ImUtf8.Group()) + { + ImUtf8.Text(group.ObjectType.ToName()); + if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman) + ImUtf8.Text(group.EquipSlot.ToName()); + ImUtf8.Text($"{group.PrimaryId.Id}"); + if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory) + ImUtf8.Text($"{group.SecondaryId.Id}"); + ImUtf8.Text($"{group.Variant.Id}"); + + ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialId}"); + ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialAnimationId}"); + ImUtf8.TextFrameAligned($"{group.DefaultEntry.DecalId}"); + ImUtf8.TextFrameAligned($"{group.DefaultEntry.VfxId}"); + ImUtf8.TextFrameAligned($"{group.DefaultEntry.SoundId}"); + + var canBeDisabled = group.CanBeDisabled; + if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled)) + editor.ModManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled, SaveType.Queue); + + var defaultDisabled = group.DefaultDisabled; + ImUtf8.SameLineInner(); + if (ImUtf8.Checkbox("##defaultDisabled"u8, ref defaultDisabled)) + editor.ModManager.OptionEditor.ChangeModGroupDefaultOption(group, + group.DefaultSettings.SetBit(ImcModGroup.DisabledIndex, defaultDisabled)); + + DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, group.DefaultEntry.AttributeMask, group); + } + + + foreach (var (option, optionIdx) in group.OptionData.WithIndex()) + { + using var id = ImRaii.PushId(optionIdx); + editor.DrawOptionPosition(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionDefaultMultiBehaviour(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionName(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDescription(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDelete(option); + + ImUtf8.SameLineInner(); + ImGui.Dummy(new Vector2(editor.PriorityWidth, 0)); + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + editor.OptionIdxSelectable.X + ImUtf8.ItemInnerSpacing.X * 2 + ImUtf8.FrameHeight); + DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, option.AttributeMask, option); + } + + DrawNewOption(attributeCache); + return; + + static void DrawAttributes(ImcModGroupEditor editor, in ImcAttributeCache cache, ushort mask, object data) + { + for (var i = 0; i < ImcEntry.NumAttributes; ++i) + { + using var id = ImRaii.PushId(i); + var value = (mask & 1 << i) != 0; + using (ImRaii.Disabled(!cache.CanChange(i))) + { + if (ImUtf8.Checkbox(TerminatedByteString.Empty, ref value)) + { + if (data is ImcModGroup g) + editor.ChangeDefaultAttribute(g, cache, i, value); + else + editor.ChangeOptionAttribute((ImcSubMod)data, cache, i, value); + } + } + + ImUtf8.HoverTooltip($"{(char)('A' + i)}"); + if (i != 9) + ImUtf8.SameLineInner(); + } + } + } + + private void DrawNewOption(in ImcAttributeCache cache) + { + if (cache.LowestUnsetMask == 0) + return; + + var name = editor.DrawNewOptionBase(group, group.Options.Count); + var validName = name.Length > 0; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName + ? "Add a new option to this group."u8 + : "Please enter a name for the new option."u8, !validName)) + { + editor.ModManager.OptionEditor.ImcEditor.AddOption(group, cache, name); + editor.NewOptionName = null; + } + } +} diff --git a/Penumbra/UI/ModsTab/ModGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs similarity index 85% rename from Penumbra/UI/ModsTab/ModGroupDrawer.cs rename to Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs index e9b0b396..dec77430 100644 --- a/Penumbra/UI/ModsTab/ModGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs @@ -11,7 +11,7 @@ using Penumbra.Mods.Groups; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; -namespace Penumbra.UI.ModsTab; +namespace Penumbra.UI.ModsTab.Groups; public sealed class ModGroupDrawer(Configuration config, CollectionManager collectionManager) : IUiService { @@ -63,8 +63,8 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle /// private void DrawSingleGroupCombo(IModGroup group, int groupIdx, Setting setting) { - using var id = ImRaii.PushId(groupIdx); - var selectedOption = setting.AsIndex; + using var id = ImRaii.PushId(groupIdx); + var selectedOption = setting.AsIndex; ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X * 3 / 4); var options = group.Options; using (var combo = ImRaii.Combo(string.Empty, options[selectedOption].Name)) @@ -97,10 +97,10 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle /// private void DrawSingleGroupRadio(IModGroup group, int groupIdx, Setting setting) { - using var id = ImRaii.PushId(groupIdx); - var selectedOption = setting.AsIndex; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); - var options = group.Options; + using var id = ImRaii.PushId(groupIdx); + var selectedOption = setting.AsIndex; + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var options = group.Options; DrawCollapseHandling(options, minWidth, DrawOptions); Widget.EndFramedGroup(); @@ -110,8 +110,8 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle { for (var idx = 0; idx < group.Options.Count; ++idx) { - using var i = ImRaii.PushId(idx); - var option = options[idx]; + using var i = ImRaii.PushId(idx); + var option = options[idx]; if (ImGui.RadioButton(option.Name, selectedOption == idx)) SetModSetting(group, groupIdx, Setting.Single(idx)); @@ -130,9 +130,9 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle /// private void DrawMultiGroup(IModGroup group, int groupIdx, Setting setting) { - using var id = ImRaii.PushId(groupIdx); - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); - var options = group.Options; + using var id = ImRaii.PushId(groupIdx); + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var options = group.Options; DrawCollapseHandling(options, minWidth, DrawOptions); Widget.EndFramedGroup(); @@ -147,9 +147,9 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle { for (var idx = 0; idx < options.Count; ++idx) { - using var i = ImRaii.PushId(idx); - var option = options[idx]; - var enabled = setting.HasFlag(idx); + using var i = ImRaii.PushId(idx); + var option = options[idx]; + var enabled = setting.HasFlag(idx); if (ImGui.Checkbox(option.Name, ref enabled)) SetModSetting(group, groupIdx, setting.SetBit(idx, enabled)); @@ -187,8 +187,8 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle } else { - var collapseId = ImGui.GetID("Collapse"); - var shown = ImGui.GetStateStorage().GetBool(collapseId, true); + var collapseId = ImGui.GetID("Collapse"); + var shown = ImGui.GetStateStorage().GetBool(collapseId, true); var buttonTextShow = $"Show {options.Count} Options"; var buttonTextHide = $"Hide {options.Count} Options"; var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X) @@ -204,7 +204,7 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle } - var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); + var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); var endPos = ImGui.GetCursorPos(); ImGui.SetCursorPos(pos); if (ImGui.Button(buttonTextHide, new Vector2(width, 0))) diff --git a/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs new file mode 100644 index 00000000..e7d70922 --- /dev/null +++ b/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs @@ -0,0 +1,360 @@ +using Dalamud.Interface; +using Dalamud.Interface.Internal.Notifications; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Text; +using OtterGui.Text.EndObjects; +using Penumbra.Mods; +using Penumbra.Mods.Groups; +using Penumbra.Mods.Manager; +using Penumbra.Mods.Manager.OptionEditor; +using Penumbra.Mods.Settings; +using Penumbra.Mods.SubMods; +using Penumbra.Services; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.ModsTab.Groups; + +public sealed class ModGroupEditDrawer( + ModManager modManager, + Configuration config, + FilenameService filenames, + DescriptionEditPopup descriptionPopup) : IUiService +{ + private static ReadOnlySpan DragDropLabel + => "##DragOption"u8; + + internal readonly ModManager ModManager = modManager; + internal readonly Queue ActionQueue = new(); + + internal Vector2 OptionIdxSelectable; + internal Vector2 AvailableWidth; + internal float PriorityWidth; + + internal string? NewOptionName; + private IModGroup? _newOptionGroup; + + private Vector2 _buttonSize; + private float _groupNameWidth; + private float _optionNameWidth; + private float _spacing; + private bool _deleteEnabled; + + private string? _currentGroupName; + private ModPriority? _currentGroupPriority; + private IModGroup? _currentGroupEdited; + private bool _isGroupNameValid = true; + + private IModGroup? _dragDropGroup; + private IModOption? _dragDropOption; + + public void Draw(Mod mod) + { + PrepareStyle(); + + using var id = ImUtf8.PushId("##GroupEdit"u8); + foreach (var (group, groupIdx) in mod.Groups.WithIndex()) + DrawGroup(group, groupIdx); + + while (ActionQueue.TryDequeue(out var action)) + action.Invoke(); + } + + private void DrawGroup(IModGroup group, int idx) + { + using var id = ImUtf8.PushId(idx); + using var frame = ImRaii.FramedGroup($"Group #{idx + 1}"); + DrawGroupNameRow(group, idx); + group.EditDrawer(this).Draw(); + } + + private void DrawGroupNameRow(IModGroup group, int idx) + { + DrawGroupName(group); + ImUtf8.SameLineInner(); + DrawGroupMoveButtons(group, idx); + ImUtf8.SameLineInner(); + DrawGroupOpenFile(group, idx); + ImUtf8.SameLineInner(); + DrawGroupDescription(group); + ImUtf8.SameLineInner(); + DrawGroupDelete(group); + ImUtf8.SameLineInner(); + DrawGroupPriority(group); + } + + private void DrawGroupName(IModGroup group) + { + var text = _currentGroupEdited == group ? _currentGroupName ?? group.Name : group.Name; + ImGui.SetNextItemWidth(_groupNameWidth); + using var border = ImRaii.PushFrameBorder(UiHelpers.ScaleX2, Colors.RegexWarningBorder, !_isGroupNameValid); + if (ImUtf8.InputText("##GroupName"u8, ref text)) + { + _currentGroupEdited = group; + _currentGroupName = text; + _isGroupNameValid = text == group.Name || ModGroupEditor.VerifyFileName(group.Mod, group, text, false); + } + + if (ImGui.IsItemDeactivated()) + { + if (_currentGroupName != null && _isGroupNameValid) + ModManager.OptionEditor.RenameModGroup(group, _currentGroupName); + _currentGroupName = null; + _currentGroupEdited = null; + _isGroupNameValid = true; + } + + var tt = _isGroupNameValid + ? "Change the Group name."u8 + : "Current name can not be used for this group."u8; + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tt); + } + + private void DrawGroupDelete(IModGroup group) + { + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, !_deleteEnabled)) + ActionQueue.Enqueue(() => ModManager.OptionEditor.DeleteModGroup(group)); + + if (_deleteEnabled) + ImUtf8.HoverTooltip("Delete this option group."u8); + else + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + $"Delete this option group.\nHold {config.DeleteModModifier} while clicking to delete."); + } + + private void DrawGroupPriority(IModGroup group) + { + var priority = _currentGroupEdited == group + ? (_currentGroupPriority ?? group.Priority).Value + : group.Priority.Value; + ImGui.SetNextItemWidth(PriorityWidth); + if (ImGui.InputInt("##GroupPriority", ref priority, 0, 0)) + { + _currentGroupEdited = group; + _currentGroupPriority = new ModPriority(priority); + } + + if (ImGui.IsItemDeactivated()) + { + if (_currentGroupPriority.HasValue) + ModManager.OptionEditor.ChangeGroupPriority(group, _currentGroupPriority.Value); + _currentGroupEdited = null; + _currentGroupPriority = null; + } + + ImGuiUtil.HoverTooltip("Group Priority"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DrawGroupDescription(IModGroup group) + { + if (ImUtf8.IconButton(FontAwesomeIcon.Edit, "Edit group description."u8)) + descriptionPopup.Open(group); + } + + private void DrawGroupMoveButtons(IModGroup group, int idx) + { + var isFirst = idx == 0; + if (ImUtf8.IconButton(FontAwesomeIcon.ArrowUp, isFirst)) + ActionQueue.Enqueue(() => ModManager.OptionEditor.MoveModGroup(group, idx - 1)); + + if (isFirst) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Can not move this group further upwards."u8); + else + ImUtf8.HoverTooltip($"Move this group up to group {idx}."); + + + ImUtf8.SameLineInner(); + var isLast = idx == group.Mod.Groups.Count - 1; + if (ImUtf8.IconButton(FontAwesomeIcon.ArrowDown, isLast)) + ActionQueue.Enqueue(() => ModManager.OptionEditor.MoveModGroup(group, idx + 1)); + + if (isLast) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Can not move this group further downwards."u8); + else + ImUtf8.HoverTooltip($"Move this group down to group {idx + 2}."); + } + + private void DrawGroupOpenFile(IModGroup group, int idx) + { + var fileName = filenames.OptionGroupFile(group.Mod, idx, config.ReplaceNonAsciiOnImport); + var fileExists = File.Exists(fileName); + if (ImUtf8.IconButton(FontAwesomeIcon.FileExport, !fileExists)) + try + { + Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true }); + } + catch (Exception e) + { + Penumbra.Messager.NotificationMessage(e, "Could not open editor.", NotificationType.Error); + } + + if (fileExists) + ImUtf8.HoverTooltip($"Open the {group.Name} json file in the text editor of your choice."); + else + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"The {group.Name} json file does not exist."); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionPosition(IModGroup group, IModOption option, int optionIdx) + { + ImGui.AlignTextToFramePadding(); + ImUtf8.Selectable($"Option #{optionIdx + 1}", false, size: OptionIdxSelectable); + Target(group, optionIdx); + Source(option); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionDefaultSingleBehaviour(IModGroup group, IModOption option, int optionIdx) + { + var isDefaultOption = group.DefaultSettings.AsIndex == optionIdx; + if (ImUtf8.RadioButton("##default"u8, isDefaultOption)) + ModManager.OptionEditor.ChangeModGroupDefaultOption(group, Setting.Single(optionIdx)); + ImUtf8.HoverTooltip($"Set {option.Name} as the default choice for this group."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionDefaultMultiBehaviour(IModGroup group, IModOption option, int optionIdx) + { + var isDefaultOption = group.DefaultSettings.HasFlag(optionIdx); + if (ImUtf8.Checkbox("##default"u8, ref isDefaultOption)) + ModManager.OptionEditor.ChangeModGroupDefaultOption(group, group.DefaultSettings.SetBit(optionIdx, isDefaultOption)); + ImUtf8.HoverTooltip($"{(isDefaultOption ? "Disable"u8 : "Enable"u8)} {option.Name} per default in this group."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionDescription(IModOption option) + { + if (ImUtf8.IconButton(FontAwesomeIcon.Edit, "Edit option description."u8)) + descriptionPopup.Open(option); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionPriority(MultiSubMod option) + { + var priority = option.Priority.Value; + ImGui.SetNextItemWidth(PriorityWidth); + if (ImUtf8.InputScalarOnDeactivated("##Priority"u8, ref priority)) + ModManager.OptionEditor.MultiEditor.ChangeOptionPriority(option, new ModPriority(priority)); + ImUtf8.HoverTooltip("Option priority inside the mod."u8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionName(IModOption option) + { + var name = option.Name; + ImGui.SetNextItemWidth(_optionNameWidth); + if (ImUtf8.InputTextOnDeactivated("##Name"u8, ref name)) + ModManager.OptionEditor.RenameOption(option, name); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void DrawOptionDelete(IModOption option) + { + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, !_deleteEnabled)) + ActionQueue.Enqueue(() => ModManager.OptionEditor.DeleteOption(option)); + + if (_deleteEnabled) + ImUtf8.HoverTooltip("Delete this option."u8); + else + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + $"Delete this option.\nHold {config.DeleteModModifier} while clicking to delete."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal string DrawNewOptionBase(IModGroup group, int count) + { + ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable); + Target(group, count); + + ImUtf8.SameLineInner(); + ImUtf8.IconDummy(); + + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(_optionNameWidth); + var newName = _newOptionGroup == group + ? NewOptionName ?? string.Empty + : string.Empty; + if (ImUtf8.InputText("##newOption"u8, ref newName, "Add new option..."u8)) + { + NewOptionName = newName; + _newOptionGroup = group; + } + + ImUtf8.SameLineInner(); + return newName; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Source(IModOption option) + { + if (option.Group is not ITexToolsGroup) + return; + + using var source = ImUtf8.DragDropSource(); + if (!source) + return; + + if (!DragDropSource.SetPayload(DragDropLabel)) + { + _dragDropGroup = option.Group; + _dragDropOption = option; + } + + ImGui.TextUnformatted($"Dragging option {option.Name} from group {option.Group.Name}..."); + } + + private void Target(IModGroup group, int optionIdx) + { + if (group is not ITexToolsGroup) + return; + + if (_dragDropGroup != group && _dragDropGroup != null && group is MultiModGroup { Options.Count: >= IModGroup.MaxMultiOptions }) + return; + + using var target = ImRaii.DragDropTarget(); + if (!target.Success || !DragDropTarget.CheckPayload(DragDropLabel)) + return; + + if (_dragDropGroup != null && _dragDropOption != null) + { + if (_dragDropGroup == group) + { + var sourceOption = _dragDropOption; + ActionQueue.Enqueue(() => ModManager.OptionEditor.MoveOption(sourceOption, optionIdx)); + } + else + { + // Move from one group to another by deleting, then adding, then moving the option. + var sourceOption = _dragDropOption; + ActionQueue.Enqueue(() => + { + ModManager.OptionEditor.DeleteOption(sourceOption); + if (ModManager.OptionEditor.AddOption(group, sourceOption) is { } newOption) + ModManager.OptionEditor.MoveOption(newOption, optionIdx); + }); + } + } + + _dragDropGroup = null; + _dragDropOption = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void PrepareStyle() + { + var totalWidth = 400f * ImUtf8.GlobalScale; + _buttonSize = new Vector2(ImUtf8.FrameHeight); + PriorityWidth = 50 * ImUtf8.GlobalScale; + AvailableWidth = new Vector2(totalWidth + 3 * _spacing + 2 * _buttonSize.X + PriorityWidth, 0); + _groupNameWidth = totalWidth - 3 * (_buttonSize.X + _spacing); + _spacing = ImGui.GetStyle().ItemInnerSpacing.X; + OptionIdxSelectable = ImUtf8.CalcTextSize("Option #88."u8); + _optionNameWidth = totalWidth - OptionIdxSelectable.X - _buttonSize.X - 2 * _spacing; + _deleteEnabled = config.DeleteModModifier.IsActive(); + } +} diff --git a/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs new file mode 100644 index 00000000..e6701a03 --- /dev/null +++ b/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs @@ -0,0 +1,63 @@ +using Dalamud.Interface; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.Mods.Groups; + +namespace Penumbra.UI.ModsTab.Groups; + +public readonly struct MultiModGroupEditDrawer(ModGroupEditDrawer editor, MultiModGroup group) : IModGroupEditDrawer +{ + public void Draw() + { + foreach (var (option, optionIdx) in group.OptionData.WithIndex()) + { + using var id = ImRaii.PushId(optionIdx); + editor.DrawOptionPosition(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionDefaultMultiBehaviour(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionName(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDescription(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDelete(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionPriority(option); + } + + DrawNewOption(); + DrawConvertButton(); + } + + private void DrawConvertButton() + { + var g = group; + var e = editor.ModManager.OptionEditor.MultiEditor; + if (ImUtf8.Button("Convert to Single Group"u8, editor.AvailableWidth)) + editor.ActionQueue.Enqueue(() => e.ChangeToSingle(g)); + } + + private void DrawNewOption() + { + var count = group.Options.Count; + if (count >= IModGroup.MaxMultiOptions) + return; + + var name = editor.DrawNewOptionBase(group, count); + + var validName = name.Length > 0; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName + ? "Add a new option to this group."u8 + : "Please enter a name for the new option."u8, !validName)) + { + editor.ModManager.OptionEditor.MultiEditor.AddOption(group, name); + editor.NewOptionName = null; + } + } +} diff --git a/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs new file mode 100644 index 00000000..75fbc63a --- /dev/null +++ b/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs @@ -0,0 +1,68 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.Mods.Groups; + +namespace Penumbra.UI.ModsTab.Groups; + +public readonly struct SingleModGroupEditDrawer(ModGroupEditDrawer editor, SingleModGroup group) : IModGroupEditDrawer +{ + public void Draw() + { + foreach (var (option, optionIdx) in group.OptionData.WithIndex()) + { + using var id = ImRaii.PushId(optionIdx); + editor.DrawOptionPosition(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionDefaultSingleBehaviour(group, option, optionIdx); + + ImUtf8.SameLineInner(); + editor.DrawOptionName(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDescription(option); + + ImUtf8.SameLineInner(); + editor.DrawOptionDelete(option); + + ImUtf8.SameLineInner(); + ImGui.Dummy(new Vector2(editor.PriorityWidth, 0)); + } + + DrawNewOption(); + DrawConvertButton(); + } + + private void DrawConvertButton() + { + var convertible = group.Options.Count <= IModGroup.MaxMultiOptions; + var g = group; + var e = editor.ModManager.OptionEditor.SingleEditor; + if (ImUtf8.ButtonEx("Convert to Multi Group", editor.AvailableWidth, !convertible)) + editor.ActionQueue.Enqueue(() => e.ChangeToMulti(g)); + if (!convertible) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + "Can not convert to multi group since maximum number of options is exceeded."u8); + } + + private void DrawNewOption() + { + var count = group.Options.Count; + if (count >= int.MaxValue) + return; + + var name = editor.DrawNewOptionBase(group, count); + + var validName = name.Length > 0; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName + ? "Add a new option to this group."u8 + : "Please enter a name for the new option."u8, !validName)) + { + editor.ModManager.OptionEditor.SingleEditor.AddOption(group, name); + editor.NewOptionName = null; + } + } +} diff --git a/Penumbra/UI/ModsTab/MetaManipulationDrawer.cs b/Penumbra/UI/ModsTab/MetaManipulationDrawer.cs new file mode 100644 index 00000000..1f2273b5 --- /dev/null +++ b/Penumbra/UI/ModsTab/MetaManipulationDrawer.cs @@ -0,0 +1,105 @@ +using ImGuiNET; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.Meta.Manipulations; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.ModsTab; + +public static class MetaManipulationDrawer +{ + public static bool DrawObjectType(ref ImcManipulation manip, float width = 110) + { + var ret = Combos.ImcType("##imcType", manip.ObjectType, out var type, width); + ImUtf8.HoverTooltip("Object Type"u8); + + if (ret) + { + var equipSlot = type switch + { + ObjectType.Equipment => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, + ObjectType.DemiHuman => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, + ObjectType.Accessory => manip.EquipSlot.IsAccessory() ? manip.EquipSlot : EquipSlot.Ears, + _ => EquipSlot.Unknown, + }; + manip = new ImcManipulation(type, manip.BodySlot, manip.PrimaryId, manip.SecondaryId == 0 ? 1 : manip.SecondaryId, + manip.Variant.Id, equipSlot, manip.Entry); + } + + return ret; + } + + public static bool DrawPrimaryId(ref ImcManipulation manip, float unscaledWidth = 80) + { + var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, manip.PrimaryId.Id, out var newId, 0, ushort.MaxValue, + manip.PrimaryId.Id <= 1); + ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8 + + "This should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, newId, manip.SecondaryId, manip.Variant.Id, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawSecondaryId(ref ImcManipulation manip, float unscaledWidth = 100) + { + var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, manip.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false); + ImUtf8.HoverTooltip("Secondary ID"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, newId, manip.Variant.Id, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawVariant(ref ImcManipulation manip, float unscaledWidth = 45) + { + var ret = IdInput("##imcVariant"u8, unscaledWidth, manip.Variant.Id, out var newId, 0, byte.MaxValue, false); + ImUtf8.HoverTooltip("Variant ID"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, (byte)newId, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawSlot(ref ImcManipulation manip, float unscaledWidth = 100) + { + bool ret; + EquipSlot slot; + switch (manip.ObjectType) + { + case ObjectType.Equipment: + case ObjectType.DemiHuman: + ret = Combos.EqpEquipSlot("##slot", manip.EquipSlot, out slot, unscaledWidth); + break; + case ObjectType.Accessory: + ret = Combos.AccessorySlot("##slot", manip.EquipSlot, out slot, unscaledWidth); + break; + default: return false; + } + + ImUtf8.HoverTooltip("Equip Slot"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, manip.Variant.Id, slot, + manip.Entry); + return ret; + } + + /// + /// A number input for ids with an optional max id of given width. + /// Returns true if newId changed against currentId. + /// + private static bool IdInput(ReadOnlySpan label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId, + bool border) + { + int tmp = currentId; + ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border); + using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border); + if (ImUtf8.InputScalar(label, ref tmp)) + tmp = Math.Clamp(tmp, minId, maxId); + + newId = (ushort)tmp; + return newId != currentId; + } +} diff --git a/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs deleted file mode 100644 index 4ef1577f..00000000 --- a/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs +++ /dev/null @@ -1,687 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; -using ImGuiNET; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Raii; -using OtterGui.Services; -using OtterGui.Text; -using OtterGui.Text.EndObjects; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods; -using Penumbra.Mods.Groups; -using Penumbra.Mods.Manager; -using Penumbra.Mods.Manager.OptionEditor; -using Penumbra.Mods.Settings; -using Penumbra.Mods.SubMods; -using Penumbra.Services; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.ModsTab; - -public static class MetaManipulationDrawer -{ - public static bool DrawObjectType(ref ImcManipulation manip, float width = 110) - { - var ret = Combos.ImcType("##imcType", manip.ObjectType, out var type, width); - ImUtf8.HoverTooltip("Object Type"u8); - - if (ret) - { - var equipSlot = type switch - { - ObjectType.Equipment => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, - ObjectType.DemiHuman => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, - ObjectType.Accessory => manip.EquipSlot.IsAccessory() ? manip.EquipSlot : EquipSlot.Ears, - _ => EquipSlot.Unknown, - }; - manip = new ImcManipulation(type, manip.BodySlot, manip.PrimaryId, manip.SecondaryId == 0 ? 1 : manip.SecondaryId, - manip.Variant.Id, equipSlot, manip.Entry); - } - - return ret; - } - - public static bool DrawPrimaryId(ref ImcManipulation manip, float unscaledWidth = 80) - { - var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, manip.PrimaryId.Id, out var newId, 0, ushort.MaxValue, - manip.PrimaryId.Id <= 1); - ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8 - + "This should generally not be left <= 1 unless you explicitly want that."u8); - if (ret) - manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, newId, manip.SecondaryId, manip.Variant.Id, manip.EquipSlot, - manip.Entry); - return ret; - } - - public static bool DrawSecondaryId(ref ImcManipulation manip, float unscaledWidth = 100) - { - var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, manip.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false); - ImUtf8.HoverTooltip("Secondary ID"u8); - if (ret) - manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, newId, manip.Variant.Id, manip.EquipSlot, - manip.Entry); - return ret; - } - - public static bool DrawVariant(ref ImcManipulation manip, float unscaledWidth = 45) - { - var ret = IdInput("##imcVariant"u8, unscaledWidth, manip.Variant.Id, out var newId, 0, byte.MaxValue, false); - ImUtf8.HoverTooltip("Variant ID"u8); - if (ret) - manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, (byte)newId, manip.EquipSlot, - manip.Entry); - return ret; - } - - public static bool DrawSlot(ref ImcManipulation manip, float unscaledWidth = 100) - { - bool ret; - EquipSlot slot; - switch (manip.ObjectType) - { - case ObjectType.Equipment: - case ObjectType.DemiHuman: - ret = Combos.EqpEquipSlot("##slot", manip.EquipSlot, out slot, unscaledWidth); - break; - case ObjectType.Accessory: - ret = Combos.AccessorySlot("##slot", manip.EquipSlot, out slot, unscaledWidth); - break; - default: return false; - } - - ImUtf8.HoverTooltip("Equip Slot"u8); - if (ret) - manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, manip.Variant.Id, slot, - manip.Entry); - return ret; - } - - /// - /// A number input for ids with an optional max id of given width. - /// Returns true if newId changed against currentId. - /// - private static bool IdInput(ReadOnlySpan label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId, - bool border) - { - int tmp = currentId; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border); - using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border); - if (ImUtf8.InputScalar(label, ref tmp)) - tmp = Math.Clamp(tmp, minId, maxId); - - newId = (ushort)tmp; - return newId != currentId; - } -} - -public sealed class ModGroupEditDrawer( - ModManager modManager, - Configuration config, - FilenameService filenames, - DescriptionEditPopup descriptionPopup) : IUiService -{ - private static ReadOnlySpan DragDropLabel - => "##DragOption"u8; - - private Vector2 _buttonSize; - private Vector2 _availableWidth; - private float _priorityWidth; - private float _groupNameWidth; - private float _optionNameWidth; - private float _spacing; - private Vector2 _optionIdxSelectable; - private bool _deleteEnabled; - - private string? _currentGroupName; - private ModPriority? _currentGroupPriority; - private IModGroup? _currentGroupEdited; - private bool _isGroupNameValid = true; - - private string? _newOptionName; - private IModGroup? _newOptionGroup; - private readonly Queue _actionQueue = new(); - - private IModGroup? _dragDropGroup; - private IModOption? _dragDropOption; - - public void Draw(Mod mod) - { - PrepareStyle(); - - using var id = ImUtf8.PushId("##GroupEdit"u8); - foreach (var (group, groupIdx) in mod.Groups.WithIndex()) - DrawGroup(group, groupIdx); - - while (_actionQueue.TryDequeue(out var action)) - action.Invoke(); - } - - private void DrawGroup(IModGroup group, int idx) - { - using var id = ImUtf8.PushId(idx); - using var frame = ImRaii.FramedGroup($"Group #{idx + 1}"); - DrawGroupNameRow(group, idx); - switch (group) - { - case SingleModGroup s: - DrawSingleGroup(s); - break; - case MultiModGroup m: - DrawMultiGroup(m); - break; - case ImcModGroup i: - DrawImcGroup(i); - break; - } - } - - private void DrawGroupNameRow(IModGroup group, int idx) - { - DrawGroupName(group); - ImUtf8.SameLineInner(); - DrawGroupMoveButtons(group, idx); - ImUtf8.SameLineInner(); - DrawGroupOpenFile(group, idx); - ImUtf8.SameLineInner(); - DrawGroupDescription(group); - ImUtf8.SameLineInner(); - DrawGroupDelete(group); - ImUtf8.SameLineInner(); - DrawGroupPriority(group); - } - - private void DrawGroupName(IModGroup group) - { - var text = _currentGroupEdited == group ? _currentGroupName ?? group.Name : group.Name; - ImGui.SetNextItemWidth(_groupNameWidth); - using var border = ImRaii.PushFrameBorder(UiHelpers.ScaleX2, Colors.RegexWarningBorder, !_isGroupNameValid); - if (ImUtf8.InputText("##GroupName"u8, ref text)) - { - _currentGroupEdited = group; - _currentGroupName = text; - _isGroupNameValid = text == group.Name || ModGroupEditor.VerifyFileName(group.Mod, group, text, false); - } - - if (ImGui.IsItemDeactivated()) - { - if (_currentGroupName != null && _isGroupNameValid) - modManager.OptionEditor.RenameModGroup(group, _currentGroupName); - _currentGroupName = null; - _currentGroupEdited = null; - _isGroupNameValid = true; - } - - var tt = _isGroupNameValid - ? "Change the Group name."u8 - : "Current name can not be used for this group."u8; - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tt); - } - - private void DrawGroupDelete(IModGroup group) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Trash, !_deleteEnabled)) - _actionQueue.Enqueue(() => modManager.OptionEditor.DeleteModGroup(group)); - - if (_deleteEnabled) - ImUtf8.HoverTooltip("Delete this option group."u8); - else - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, - $"Delete this option group.\nHold {config.DeleteModModifier} while clicking to delete."); - } - - private void DrawGroupPriority(IModGroup group) - { - var priority = _currentGroupEdited == group - ? (_currentGroupPriority ?? group.Priority).Value - : group.Priority.Value; - ImGui.SetNextItemWidth(_priorityWidth); - if (ImGui.InputInt("##GroupPriority", ref priority, 0, 0)) - { - _currentGroupEdited = group; - _currentGroupPriority = new ModPriority(priority); - } - - if (ImGui.IsItemDeactivated()) - { - if (_currentGroupPriority.HasValue) - modManager.OptionEditor.ChangeGroupPriority(group, _currentGroupPriority.Value); - _currentGroupEdited = null; - _currentGroupPriority = null; - } - - ImGuiUtil.HoverTooltip("Group Priority"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawGroupDescription(IModGroup group) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Edit, "Edit group description."u8)) - descriptionPopup.Open(group); - } - - private void DrawGroupMoveButtons(IModGroup group, int idx) - { - var isFirst = idx == 0; - if (ImUtf8.IconButton(FontAwesomeIcon.ArrowUp, isFirst)) - _actionQueue.Enqueue(() => modManager.OptionEditor.MoveModGroup(group, idx - 1)); - - if (isFirst) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Can not move this group further upwards."u8); - else - ImUtf8.HoverTooltip($"Move this group up to group {idx}."); - - - ImUtf8.SameLineInner(); - var isLast = idx == group.Mod.Groups.Count - 1; - if (ImUtf8.IconButton(FontAwesomeIcon.ArrowDown, isLast)) - _actionQueue.Enqueue(() => modManager.OptionEditor.MoveModGroup(group, idx + 1)); - - if (isLast) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Can not move this group further downwards."u8); - else - ImUtf8.HoverTooltip($"Move this group down to group {idx + 2}."); - } - - private void DrawGroupOpenFile(IModGroup group, int idx) - { - var fileName = filenames.OptionGroupFile(group.Mod, idx, config.ReplaceNonAsciiOnImport); - var fileExists = File.Exists(fileName); - if (ImUtf8.IconButton(FontAwesomeIcon.FileExport, !fileExists)) - try - { - Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true }); - } - catch (Exception e) - { - Penumbra.Messager.NotificationMessage(e, "Could not open editor.", NotificationType.Error); - } - - if (fileExists) - ImUtf8.HoverTooltip($"Open the {group.Name} json file in the text editor of your choice."); - else - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"The {group.Name} json file does not exist."); - } - - private void DrawSingleGroup(SingleModGroup group) - { - foreach (var (option, optionIdx) in group.OptionData.WithIndex()) - { - using var id = ImRaii.PushId(optionIdx); - DrawOptionPosition(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionDefaultSingleBehaviour(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionName(option); - - ImUtf8.SameLineInner(); - DrawOptionDescription(option); - - ImUtf8.SameLineInner(); - DrawOptionDelete(option); - - ImUtf8.SameLineInner(); - ImGui.Dummy(new Vector2(_priorityWidth, 0)); - } - - DrawNewOption(group); - var convertible = group.Options.Count <= IModGroup.MaxMultiOptions; - if (ImUtf8.ButtonEx("Convert to Multi Group", _availableWidth, !convertible)) - _actionQueue.Enqueue(() => modManager.OptionEditor.SingleEditor.ChangeToMulti(group)); - if (!convertible) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, - "Can not convert to multi group since maximum number of options is exceeded."u8); - } - - private void DrawMultiGroup(MultiModGroup group) - { - foreach (var (option, optionIdx) in group.OptionData.WithIndex()) - { - using var id = ImRaii.PushId(optionIdx); - DrawOptionPosition(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionDefaultMultiBehaviour(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionName(option); - - ImUtf8.SameLineInner(); - DrawOptionDescription(option); - - ImUtf8.SameLineInner(); - DrawOptionDelete(option); - - ImUtf8.SameLineInner(); - DrawOptionPriority(option); - } - - DrawNewOption(group); - if (ImUtf8.Button("Convert to Single Group"u8, _availableWidth)) - _actionQueue.Enqueue(() => modManager.OptionEditor.MultiEditor.ChangeToSingle(group)); - } - - private void DrawImcGroup(ImcModGroup group) - { - using (ImUtf8.Group()) - { - ImUtf8.Text("Object Type"u8); - if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman) - ImUtf8.Text("Slot"u8); - ImUtf8.Text("Primary ID"); - if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory) - ImUtf8.Text("Secondary ID"); - ImUtf8.Text("Variant"u8); - - ImUtf8.TextFrameAligned("Material ID"u8); - ImUtf8.TextFrameAligned("Material Animation ID"u8); - ImUtf8.TextFrameAligned("Decal ID"u8); - ImUtf8.TextFrameAligned("VFX ID"u8); - ImUtf8.TextFrameAligned("Sound ID"u8); - ImUtf8.TextFrameAligned("Can Be Disabled"u8); - ImUtf8.TextFrameAligned("Default Attributes"u8); - } - - ImGui.SameLine(); - - var attributeCache = new ImcAttributeCache(group); - - using (ImUtf8.Group()) - { - ImUtf8.Text(group.ObjectType.ToName()); - if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman) - ImUtf8.Text(group.EquipSlot.ToName()); - ImUtf8.Text($"{group.PrimaryId.Id}"); - if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory) - ImUtf8.Text($"{group.SecondaryId.Id}"); - ImUtf8.Text($"{group.Variant.Id}"); - - ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialId}"); - ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialAnimationId}"); - ImUtf8.TextFrameAligned($"{group.DefaultEntry.DecalId}"); - ImUtf8.TextFrameAligned($"{group.DefaultEntry.VfxId}"); - ImUtf8.TextFrameAligned($"{group.DefaultEntry.SoundId}"); - - var canBeDisabled = group.CanBeDisabled; - if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled)) - modManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled, SaveType.Queue); - - var defaultDisabled = group.DefaultDisabled; - ImUtf8.SameLineInner(); - if (ImUtf8.Checkbox("##defaultDisabled"u8, ref defaultDisabled)) - modManager.OptionEditor.ChangeModGroupDefaultOption(group, - group.DefaultSettings.SetBit(ImcModGroup.DisabledIndex, defaultDisabled)); - - DrawAttributes(modManager.OptionEditor.ImcEditor, attributeCache, group.DefaultEntry.AttributeMask, group); - } - - - foreach (var (option, optionIdx) in group.OptionData.WithIndex()) - { - using var id = ImRaii.PushId(optionIdx); - DrawOptionPosition(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionDefaultMultiBehaviour(group, option, optionIdx); - - ImUtf8.SameLineInner(); - DrawOptionName(option); - - ImUtf8.SameLineInner(); - DrawOptionDescription(option); - - ImUtf8.SameLineInner(); - DrawOptionDelete(option); - - ImUtf8.SameLineInner(); - ImGui.Dummy(new Vector2(_priorityWidth, 0)); - - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + _optionIdxSelectable.X + ImUtf8.ItemInnerSpacing.X * 2 + ImUtf8.FrameHeight); - DrawAttributes(modManager.OptionEditor.ImcEditor, attributeCache, option.AttributeMask, option); - } - - DrawNewOption(group, attributeCache); - return; - - static void DrawAttributes(ImcModGroupEditor editor, in ImcAttributeCache cache, ushort mask, object data) - { - for (var i = 0; i < ImcEntry.NumAttributes; ++i) - { - using var id = ImRaii.PushId(i); - var value = (mask & (1 << i)) != 0; - using (ImRaii.Disabled(!cache.CanChange(i))) - { - if (ImUtf8.Checkbox(TerminatedByteString.Empty, ref value)) - { - if (data is ImcModGroup g) - editor.ChangeDefaultAttribute(g, cache, i, value); - else - editor.ChangeOptionAttribute((ImcSubMod)data, cache, i, value); - } - } - - ImUtf8.HoverTooltip($"{(char)('A' + i)}"); - if (i != 9) - ImUtf8.SameLineInner(); - } - } - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionPosition(IModGroup group, IModOption option, int optionIdx) - { - ImGui.AlignTextToFramePadding(); - ImUtf8.Selectable($"Option #{optionIdx + 1}", false, size: _optionIdxSelectable); - Target(group, optionIdx); - Source(option); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionDefaultSingleBehaviour(IModGroup group, IModOption option, int optionIdx) - { - var isDefaultOption = group.DefaultSettings.AsIndex == optionIdx; - if (ImUtf8.RadioButton("##default"u8, isDefaultOption)) - modManager.OptionEditor.ChangeModGroupDefaultOption(group, Setting.Single(optionIdx)); - ImUtf8.HoverTooltip($"Set {option.Name} as the default choice for this group."); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionDefaultMultiBehaviour(IModGroup group, IModOption option, int optionIdx) - { - var isDefaultOption = group.DefaultSettings.HasFlag(optionIdx); - if (ImUtf8.Checkbox("##default"u8, ref isDefaultOption)) - modManager.OptionEditor.ChangeModGroupDefaultOption(group, group.DefaultSettings.SetBit(optionIdx, isDefaultOption)); - ImUtf8.HoverTooltip($"{(isDefaultOption ? "Disable"u8 : "Enable"u8)} {option.Name} per default in this group."); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionDescription(IModOption option) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Edit, "Edit option description."u8)) - descriptionPopup.Open(option); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionPriority(MultiSubMod option) - { - var priority = option.Priority.Value; - ImGui.SetNextItemWidth(_priorityWidth); - if (ImUtf8.InputScalarOnDeactivated("##Priority"u8, ref priority)) - modManager.OptionEditor.MultiEditor.ChangeOptionPriority(option, new ModPriority(priority)); - ImUtf8.HoverTooltip("Option priority inside the mod."u8); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionName(IModOption option) - { - var name = option.Name; - ImGui.SetNextItemWidth(_optionNameWidth); - if (ImUtf8.InputTextOnDeactivated("##Name"u8, ref name)) - modManager.OptionEditor.RenameOption(option, name); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawOptionDelete(IModOption option) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Trash, !_deleteEnabled)) - _actionQueue.Enqueue(() => modManager.OptionEditor.DeleteOption(option)); - - if (_deleteEnabled) - ImUtf8.HoverTooltip("Delete this option."u8); - else - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, - $"Delete this option.\nHold {config.DeleteModModifier} while clicking to delete."); - } - - private void DrawNewOption(SingleModGroup group) - { - var count = group.Options.Count; - if (count >= int.MaxValue) - return; - - var name = DrawNewOptionBase(group, count); - - var validName = name.Length > 0; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName - ? "Add a new option to this group."u8 - : "Please enter a name for the new option."u8, !validName)) - { - modManager.OptionEditor.SingleEditor.AddOption(group, name); - _newOptionName = null; - } - } - - private void DrawNewOption(MultiModGroup group) - { - var count = group.Options.Count; - if (count >= IModGroup.MaxMultiOptions) - return; - - var name = DrawNewOptionBase(group, count); - - var validName = name.Length > 0; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName - ? "Add a new option to this group."u8 - : "Please enter a name for the new option."u8, !validName)) - { - modManager.OptionEditor.MultiEditor.AddOption(group, name); - _newOptionName = null; - } - } - - private void DrawNewOption(ImcModGroup group, in ImcAttributeCache cache) - { - if (cache.LowestUnsetMask == 0) - return; - - var name = DrawNewOptionBase(group, group.Options.Count); - var validName = name.Length > 0; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName - ? "Add a new option to this group."u8 - : "Please enter a name for the new option."u8, !validName)) - { - modManager.OptionEditor.ImcEditor.AddOption(group, cache, name); - _newOptionName = null; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string DrawNewOptionBase(IModGroup group, int count) - { - ImUtf8.Selectable($"Option #{count + 1}", false, size: _optionIdxSelectable); - Target(group, count); - - ImUtf8.SameLineInner(); - ImUtf8.IconDummy(); - - ImUtf8.SameLineInner(); - ImGui.SetNextItemWidth(_optionNameWidth); - var newName = _newOptionGroup == group - ? _newOptionName ?? string.Empty - : string.Empty; - if (ImUtf8.InputText("##newOption"u8, ref newName, "Add new option..."u8)) - { - _newOptionName = newName; - _newOptionGroup = group; - } - - ImUtf8.SameLineInner(); - return newName; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Source(IModOption option) - { - if (option.Group is not ITexToolsGroup) - return; - - using var source = ImUtf8.DragDropSource(); - if (!source) - return; - - if (!DragDropSource.SetPayload(DragDropLabel)) - { - _dragDropGroup = option.Group; - _dragDropOption = option; - } - - ImGui.TextUnformatted($"Dragging option {option.Name} from group {option.Group.Name}..."); - } - - private void Target(IModGroup group, int optionIdx) - { - if (group is not ITexToolsGroup) - return; - - if (_dragDropGroup != group && _dragDropGroup != null && group is MultiModGroup { Options.Count: >= IModGroup.MaxMultiOptions }) - return; - - using var target = ImRaii.DragDropTarget(); - if (!target.Success || !DragDropTarget.CheckPayload(DragDropLabel)) - return; - - if (_dragDropGroup != null && _dragDropOption != null) - { - if (_dragDropGroup == group) - { - var sourceOption = _dragDropOption; - _actionQueue.Enqueue(() => modManager.OptionEditor.MoveOption(sourceOption, optionIdx)); - } - else - { - // Move from one group to another by deleting, then adding, then moving the option. - var sourceOption = _dragDropOption; - _actionQueue.Enqueue(() => - { - modManager.OptionEditor.DeleteOption(sourceOption); - if (modManager.OptionEditor.AddOption(group, sourceOption) is { } newOption) - modManager.OptionEditor.MoveOption(newOption, optionIdx); - }); - } - } - - _dragDropGroup = null; - _dragDropOption = null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void PrepareStyle() - { - var totalWidth = 400f * ImUtf8.GlobalScale; - _buttonSize = new Vector2(ImUtf8.FrameHeight); - _priorityWidth = 50 * ImUtf8.GlobalScale; - _availableWidth = new Vector2(totalWidth + 3 * _spacing + 2 * _buttonSize.X + _priorityWidth, 0); - _groupNameWidth = totalWidth - 3 * (_buttonSize.X + _spacing); - _spacing = ImGui.GetStyle().ItemInnerSpacing.X; - _optionIdxSelectable = ImUtf8.CalcTextSize("Option #88."u8); - _optionNameWidth = totalWidth - _optionIdxSelectable.X - _buttonSize.X - 2 * _spacing; - _deleteEnabled = config.DeleteModModifier.IsActive(); - } -} diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index b7951c49..125f539e 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -13,6 +13,7 @@ using Penumbra.Services; using Penumbra.UI.AdvancedWindow; using Penumbra.Mods.Settings; using Penumbra.Mods.Manager.OptionEditor; +using Penumbra.UI.ModsTab.Groups; namespace Penumbra.UI.ModsTab; diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index 107a2d04..7e3b8a95 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -9,6 +9,7 @@ using Penumbra.Collections.Manager; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.Mods.Settings; +using Penumbra.UI.ModsTab.Groups; namespace Penumbra.UI.ModsTab;