diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs index 71c1a225..b8710707 100644 --- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs @@ -30,7 +30,7 @@ public class ModPanelDescriptionTab( ImGui.Dummy(ImGuiHelpers.ScaledVector2(2)); ImGui.Dummy(ImGuiHelpers.ScaledVector2(2)); - var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Count > 0 + var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Enabled ? (true, ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.X + (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0)) : (false, 0); var tagIdx = _localTags.Draw("Local Tags: ", diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index 5b831a66..c3737b40 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -69,7 +69,7 @@ public class ModPanelEditTab( FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X); UiHelpers.DefaultLineSpace(); - var sharedTagsEnabled = predefinedTagManager.Count > 0; + var sharedTagsEnabled = predefinedTagManager.Enabled; var sharedTagButtonOffset = sharedTagsEnabled ? ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X : 0; var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags, out var editedTag, rightEndOffset: sharedTagButtonOffset); diff --git a/Penumbra/UI/ModsTab/MultiModPanel.cs b/Penumbra/UI/ModsTab/MultiModPanel.cs index 3eac972c..947ede14 100644 --- a/Penumbra/UI/ModsTab/MultiModPanel.cs +++ b/Penumbra/UI/ModsTab/MultiModPanel.cs @@ -2,6 +2,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using OtterGui.Extensions; +using OtterGui.Filesystem; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -10,7 +11,7 @@ using Penumbra.Mods.Manager; namespace Penumbra.UI.ModsTab; -public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor) : IUiService +public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService { public void Draw() { @@ -97,7 +98,12 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor) var width = ImGuiHelpers.ScaledVector2(150, 0); ImUtf8.TextFrameAligned("Multi Tagger:"u8); ImGui.SameLine(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); + + var predefinedTagsEnabled = tagManager.Enabled; + var inputWidth = predefinedTagsEnabled + ? ImGui.GetContentRegionAvail().X - 2 * width.X - 3 * ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() + : ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.SetNextItemWidth(inputWidth); ImUtf8.InputText("##tag"u8, ref _tag, "Local Tag Name..."u8); UpdateTagCache(); @@ -109,7 +115,7 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor) ? "No tag specified." : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data." : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name.Text))}"; - ImGui.SameLine(); + ImUtf8.SameLineInner(); if (ImUtf8.ButtonEx(label, tooltip, width, _addMods.Count == 0)) foreach (var mod in _addMods) editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag); @@ -122,10 +128,18 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor) ? "No tag specified." : $"No selected mod contains the tag \"{_tag}\" locally." : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name.Text))}"; - ImGui.SameLine(); + ImUtf8.SameLineInner(); if (ImUtf8.ButtonEx(label, tooltip, width, _removeMods.Count == 0)) foreach (var (mod, index) in _removeMods) editor.ChangeLocalTag(mod, index, string.Empty); + + if (predefinedTagsEnabled) + { + ImUtf8.SameLineInner(); + tagManager.DrawToggleButton(); + tagManager.DrawListMulti(selector.SelectedPaths.OfType().Select(l => l.Value)); + } + ImGui.Separator(); } diff --git a/Penumbra/UI/PredefinedTagManager.cs b/Penumbra/UI/PredefinedTagManager.cs index 7e268e8c..5a3a4b62 100644 --- a/Penumbra/UI/PredefinedTagManager.cs +++ b/Penumbra/UI/PredefinedTagManager.cs @@ -8,6 +8,8 @@ using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; +using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI.Classes; @@ -52,6 +54,9 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer jObj.WriteTo(jWriter); } + public bool Enabled + => Count > 0; + public void Save() => _saveService.DelaySave(this, TimeSpan.FromSeconds(5)); @@ -98,9 +103,9 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer } public void DrawAddFromSharedTagsAndUpdateTags(IReadOnlyCollection localTags, IReadOnlyCollection modTags, bool editLocal, - Mods.Mod mod) + Mod mod) { - DrawToggleButton(); + DrawToggleButtonTopRight(); if (!DrawList(localTags, modTags, editLocal, out var changedTag, out var index)) return; @@ -110,17 +115,22 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer _modManager.DataEditor.ChangeModTag(mod, index, changedTag); } - private void DrawToggleButton() + public void DrawToggleButton() { - ImGui.SameLine(ImGui.GetContentRegionMax().X - - ImGui.GetFrameHeight() - - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ItemInnerSpacing.X : 0)); using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), _isListOpen); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Tags.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Add Predefined Tags...", false, true)) _isListOpen = !_isListOpen; } + private void DrawToggleButtonTopRight() + { + ImGui.SameLine(ImGui.GetContentRegionMax().X + - ImGui.GetFrameHeight() + - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ItemInnerSpacing.X : 0)); + DrawToggleButton(); + } + private bool DrawList(IReadOnlyCollection localTags, IReadOnlyCollection modTags, bool editLocal, out string changedTag, out int changedIndex) { @@ -130,7 +140,7 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer if (!_isListOpen) return false; - ImGui.TextUnformatted("Predefined Tags"); + ImUtf8.Text("Predefined Tags"u8); ImGui.Separator(); var ret = false; @@ -155,6 +165,101 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer return ret; } + private readonly List _selectedMods = []; + private readonly List<(int Index, int DataIndex)> _countedMods = []; + + private void PrepareLists(IEnumerable selection) + { + _selectedMods.Clear(); + _selectedMods.AddRange(selection); + _countedMods.EnsureCapacity(_selectedMods.Count); + while (_countedMods.Count < _selectedMods.Count) + _countedMods.Add((-1, -1)); + } + + public void DrawListMulti(IEnumerable selection) + { + if (!_isListOpen) + return; + + ImUtf8.Text("Predefined Tags"u8); + PrepareLists(selection); + + _enabledColor = ColorId.PredefinedTagAdd.Value(); + _disabledColor = ColorId.PredefinedTagRemove.Value(); + using var color = new ImRaii.Color(); + foreach (var (tag, idx) in _predefinedTags.Keys.WithIndex()) + { + var alreadyContained = 0; + var inModData = 0; + var missing = 0; + + foreach (var (modIndex, mod) in _selectedMods.Index()) + { + var tagIdx = mod.LocalTags.IndexOf(tag); + if (tagIdx >= 0) + { + ++alreadyContained; + _countedMods[modIndex] = (tagIdx, -1); + } + else + { + var dataIdx = mod.ModTags.IndexOf(tag); + if (dataIdx >= 0) + { + ++inModData; + _countedMods[modIndex] = (-1, dataIdx); + } + else + { + ++missing; + _countedMods[modIndex] = (-1, -1); + } + } + } + + using var id = ImRaii.PushId(idx); + var buttonWidth = CalcTextButtonWidth(tag); + // Prevent adding a new tag past the right edge of the popup + if (buttonWidth + ImGui.GetStyle().ItemSpacing.X >= ImGui.GetContentRegionAvail().X) + ImGui.NewLine(); + + var (usedColor, disabled, tt) = (missing, alreadyContained) switch + { + (> 0, _) => (_enabledColor, false, + $"Add this tag to {missing} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), + (_, > 0) => (_disabledColor, false, + $"Remove this tag from {alreadyContained} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), + _ => (_disabledColor, true, "This tag is already present in the mod tags of all selected mods."), + }; + color.Push(ImGuiCol.Button, usedColor); + if (ImUtf8.ButtonEx(tag, tt, new Vector2(buttonWidth, 0), disabled)) + { + if (missing > 0) + foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods)) + { + if (localIdx >= 0) + continue; + + _modManager.DataEditor.ChangeLocalTag(mod, mod.LocalTags.Count, tag); + } + else + foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods)) + { + if (localIdx < 0) + continue; + + _modManager.DataEditor.ChangeLocalTag(mod, localIdx, string.Empty); + } + } + ImGui.SameLine(); + + color.Pop(); + } + + ImGui.NewLine(); + } + private bool DrawColoredButton(string buttonLabel, int index, int tagIdx, bool inOther) { using var id = ImRaii.PushId(index); diff --git a/repo.json b/repo.json index f6d69c8b..a452dc94 100644 --- a/repo.json +++ b/repo.json @@ -5,8 +5,8 @@ "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", - "AssemblyVersion": "1.5.0.5", - "TestingAssemblyVersion": "1.5.0.5", + "AssemblyVersion": "1.5.0.6", + "TestingAssemblyVersion": "1.5.0.6", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -18,9 +18,9 @@ "LoadPriority": 69420, "LoadRequiredState": 2, "LoadSync": true, - "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.5/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.5/Penumbra.zip", - "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.5/Penumbra.zip", + "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.6/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.6/Penumbra.zip", + "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.6/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } ]