From b748e349170b3b08bbfaaa1badc36e7ed93edc62 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Jun 2023 01:31:51 +0200 Subject: [PATCH] Add ChangedItemDrawer and move it. --- Penumbra/Services/ServiceManager.cs | 3 +- Penumbra/UI/ChangedItemDrawer.cs | 255 ++++++++++++++++++ .../UI/ModsTab/ModPanelChangedItemsTab.cs | 11 +- Penumbra/UI/Tabs/ChangedItemsTab.cs | 19 +- Penumbra/UI/UiHelpers.cs | 62 ----- 5 files changed, 271 insertions(+), 79 deletions(-) create mode 100644 Penumbra/UI/ChangedItemDrawer.cs diff --git a/Penumbra/Services/ServiceManager.cs b/Penumbra/Services/ServiceManager.cs index 3b9e3e31..1ab1f313 100644 --- a/Penumbra/Services/ServiceManager.cs +++ b/Penumbra/Services/ServiceManager.cs @@ -161,7 +161,8 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddModEditor(this IServiceCollection services) => services.AddSingleton() diff --git a/Penumbra/UI/ChangedItemDrawer.cs b/Penumbra/UI/ChangedItemDrawer.cs new file mode 100644 index 00000000..69c2bc51 --- /dev/null +++ b/Penumbra/UI/ChangedItemDrawer.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Data; +using Dalamud.Interface; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using ImGuiNET; +using ImGuiScene; +using Lumina.Data.Parsing; +using Lumina.Excel.GeneratedSheets; +using OtterGui; +using OtterGui.Raii; +using Penumbra.Api.Enums; +using Penumbra.GameData.Enums; +using Penumbra.Services; +using Penumbra.UI.Classes; + +namespace Penumbra.UI; + +public class ChangedItemDrawer : IDisposable +{ + private const EquipSlot MonsterSlot = (EquipSlot)100; + private const EquipSlot DemihumanSlot = (EquipSlot)101; + private const EquipSlot CustomizationSlot = (EquipSlot)102; + private const EquipSlot ActionSlot = (EquipSlot)103; + + private readonly CommunicatorService _communicator; + private readonly Dictionary _icons; + + public ChangedItemDrawer(UiBuilder uiBuilder, DataManager gameData, CommunicatorService communicator) + { + _icons = CreateEquipSlotIcons(uiBuilder, gameData); + _communicator = communicator; + } + + public void Dispose() + { + foreach (var wrap in _icons.Values.Distinct()) + wrap.Dispose(); + _icons.Clear(); + } + + /// Apply Changed Item Counters to the Name if necessary. + public static string ChangedItemName(string name, object? data) + => data is int counter ? $"{counter} Files Manipulating {name}s" : name; + + /// Add filterable information to the string. + public static string ChangedItemFilterName(string name, object? data) + => data switch + { + int counter => $"{counter} Files Manipulating {name}s", + Item it => $"{name}\0{((EquipSlot)it.EquipSlotCategory.Row).ToName()}\0{(GetChangedItemObject(it, out var t) ? t : string.Empty)}", + ModelChara m => $"{name}\0{(GetChangedItemObject(m, out var t) ? t : string.Empty)}", + _ => name, + }; + + /// + /// Draw a changed item, invoking the Api-Events for clicks and tooltips. + /// Also draw the item Id in grey if requested. + /// + public void DrawChangedItem(string name, object? data, bool drawId) + { + name = ChangedItemName(name, data); + DrawCategoryIcon(name, data); + ImGui.SameLine(); + var ret = ImGui.Selectable(name) ? MouseButton.Left : MouseButton.None; + ret = ImGui.IsItemClicked(ImGuiMouseButton.Right) ? MouseButton.Right : ret; + ret = ImGui.IsItemClicked(ImGuiMouseButton.Middle) ? MouseButton.Middle : ret; + + if (ret != MouseButton.None) + _communicator.ChangedItemClick.Invoke(ret, data); + + if (_communicator.ChangedItemHover.HasTooltip && ImGui.IsItemHovered()) + { + // We can not be sure that any subscriber actually prints something in any case. + // Circumvent ugly blank tooltip with less-ugly useless tooltip. + using var tt = ImRaii.Tooltip(); + using var group = ImRaii.Group(); + _communicator.ChangedItemHover.Invoke(data); + group.Dispose(); + if (ImGui.GetItemRectSize() == Vector2.Zero) + ImGui.TextUnformatted("No actions available."); + } + + if (!drawId || !GetChangedItemObject(data, out var text)) + return; + + ImGui.SameLine(ImGui.GetContentRegionAvail().X); + ImGuiUtil.RightJustify(text, ColorId.ItemId.Value()); + } + + private void DrawCategoryIcon(string name, object? obj) + { + var height = ImGui.GetTextLineHeight(); + var slot = EquipSlot.Unknown; + var desc = string.Empty; + if (obj is Item it) + { + slot = (EquipSlot)it.EquipSlotCategory.Row; + desc = slot.ToName(); + } + else if (obj is ModelChara m) + { + (slot, desc) = (CharacterBase.ModelType)m.Type switch + { + CharacterBase.ModelType.DemiHuman => (DemihumanSlot, "Demi-Human"), + CharacterBase.ModelType.Monster => (MonsterSlot, "Monster"), + _ => (EquipSlot.Unknown, string.Empty), + }; + } + else if (name.StartsWith("Action: ")) + { + (slot, desc) = (ActionSlot, "Action"); + } + else if (name.StartsWith("Customization: ")) + { + (slot, desc) = (CustomizationSlot, "Customization"); + } + + if (!_icons.TryGetValue(slot, out var icon)) + { + ImGui.Dummy(new Vector2(height)); + return; + } + + ImGui.Image(icon.ImGuiHandle, new Vector2(height)); + if (ImGui.IsItemHovered() && icon.Height > height) + { + using var tt = ImRaii.Tooltip(); + ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height)); + ImGui.SameLine(); + ImGuiUtil.DrawTextButton(desc, new Vector2(0, icon.Height), 0); + } + } + + /// Return more detailed object information in text, if it exists. + public static bool GetChangedItemObject(object? obj, out string text) + { + switch (obj) + { + case Item it: + var quad = (Quad)it.ModelMain; + text = quad.C == 0 ? $"({quad.A}-{quad.B})" : $"({quad.A}-{quad.B}-{quad.C})"; + return true; + case ModelChara m: + text = $"({((CharacterBase.ModelType)m.Type).ToName()} {m.Model}-{m.Base}-{m.Variant})"; + return true; + default: + text = string.Empty; + return false; + } + } + + private static Dictionary CreateEquipSlotIcons(UiBuilder uiBuilder, DataManager gameData) + { + using var equipTypeIcons = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); + + if (!equipTypeIcons.Valid) + return new Dictionary(); + + var dict = new Dictionary(12); + + // Weapon + var tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 0); + if (tex != null) + { + dict.Add(EquipSlot.MainHand, tex); + dict.Add(EquipSlot.BothHand, tex); + } + + // Hat + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 1); + if (tex != null) + dict.Add(EquipSlot.Head, tex); + + // Body + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 2); + if (tex != null) + { + dict.Add(EquipSlot.Body, tex); + dict.Add(EquipSlot.BodyHands, tex); + dict.Add(EquipSlot.BodyHandsLegsFeet, tex); + dict.Add(EquipSlot.BodyLegsFeet, tex); + dict.Add(EquipSlot.ChestHands, tex); + dict.Add(EquipSlot.FullBody, tex); + dict.Add(EquipSlot.HeadBody, tex); + } + + // Hands + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 3); + if (tex != null) + dict.Add(EquipSlot.Hands, tex); + + // Pants + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 5); + if (tex != null) + { + dict.Add(EquipSlot.Legs, tex); + dict.Add(EquipSlot.LegsFeet, tex); + } + + // Shoes + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 6); + if (tex != null) + dict.Add(EquipSlot.Feet, tex); + + // Offhand + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 7); + if (tex != null) + dict.Add(EquipSlot.OffHand, tex); + + // Earrings + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 8); + if (tex != null) + dict.Add(EquipSlot.Ears, tex); + + // Necklace + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 9); + if (tex != null) + dict.Add(EquipSlot.Neck, tex); + + // Bracelet + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 10); + if (tex != null) + dict.Add(EquipSlot.Wrists, tex); + + // Ring + tex = equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 11); + if (tex != null) + dict.Add(EquipSlot.RFinger, tex); + + // Monster + tex = gameData.GetImGuiTexture("ui/icon/062000/062042_hr1.tex"); + if (tex != null) + dict.Add(MonsterSlot, tex); + + // Demihuman + tex = gameData.GetImGuiTexture("ui/icon/062000/062041_hr1.tex"); + if (tex != null) + dict.Add(DemihumanSlot, tex); + + // Customization + tex = gameData.GetImGuiTexture("ui/icon/062000/062043_hr1.tex"); + if (tex != null) + dict.Add(CustomizationSlot, tex); + + // Action + tex = gameData.GetImGuiTexture("ui/icon/062000/062001_hr1.tex"); + if (tex != null) + dict.Add(ActionSlot, tex); + + return dict; + } +} diff --git a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs index ae500b1d..b2b75d8b 100644 --- a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs @@ -6,7 +6,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Widgets; -using Penumbra.Api; using Penumbra.Services; namespace Penumbra.UI.ModsTab; @@ -14,15 +13,15 @@ namespace Penumbra.UI.ModsTab; public class ModPanelChangedItemsTab : ITab { private readonly ModFileSystemSelector _selector; - private readonly CommunicatorService _communicator; + private readonly ChangedItemDrawer _drawer; public ReadOnlySpan Label => "Changed Items"u8; - public ModPanelChangedItemsTab(ModFileSystemSelector selector, CommunicatorService communicator) + public ModPanelChangedItemsTab(ModFileSystemSelector selector, ChangedItemDrawer drawer) { - _selector = selector; - _communicator = communicator; + _selector = selector; + _drawer = drawer; } public bool IsVisible @@ -36,6 +35,6 @@ public class ModPanelChangedItemsTab : ITab var zipList = ZipList.FromSortedList((SortedList)_selector.Selected!.ChangedItems); var height = ImGui.GetTextLineHeight(); - ImGuiClip.ClippedDraw(zipList, kvp => UiHelpers.DrawChangedItem(_communicator, kvp.Item1, kvp.Item2, true), height); + ImGuiClip.ClippedDraw(zipList, kvp => _drawer.DrawChangedItem(kvp.Item1, kvp.Item2, true), height); } } diff --git a/Penumbra/UI/Tabs/ChangedItemsTab.cs b/Penumbra/UI/Tabs/ChangedItemsTab.cs index b7880d0c..bc371010 100644 --- a/Penumbra/UI/Tabs/ChangedItemsTab.cs +++ b/Penumbra/UI/Tabs/ChangedItemsTab.cs @@ -9,7 +9,6 @@ using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.Collections.Manager; using Penumbra.Mods; -using Penumbra.Services; using Penumbra.UI.Classes; namespace Penumbra.UI.Tabs; @@ -17,14 +16,14 @@ namespace Penumbra.UI.Tabs; public class ChangedItemsTab : ITab { private readonly CollectionManager _collectionManager; - private readonly CommunicatorService _communicator; + private readonly ChangedItemDrawer _drawer; private readonly CollectionSelectHeader _collectionHeader; - public ChangedItemsTab(CollectionManager collectionManager, CommunicatorService communicator, CollectionSelectHeader collectionHeader) + public ChangedItemsTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader, ChangedItemDrawer drawer) { _collectionManager = collectionManager; - _communicator = communicator; _collectionHeader = collectionHeader; + _drawer = drawer; } public ReadOnlySpan Label @@ -34,7 +33,7 @@ public class ChangedItemsTab : ITab private LowerString _changedItemModFilter = LowerString.Empty; public void DrawContent() - { + { _collectionHeader.Draw(true); var varWidth = DrawFilters(); using var child = ImRaii.Child("##changedItemsChild", -Vector2.One); @@ -49,8 +48,8 @@ public class ChangedItemsTab : ITab const ImGuiTableColumnFlags flags = ImGuiTableColumnFlags.NoResize | ImGuiTableColumnFlags.WidthFixed; ImGui.TableSetupColumn("items", flags, 400 * UiHelpers.Scale); - ImGui.TableSetupColumn("mods", flags, varWidth - 120 * UiHelpers.Scale); - ImGui.TableSetupColumn("id", flags, 120 * UiHelpers.Scale); + ImGui.TableSetupColumn("mods", flags, varWidth - 130 * UiHelpers.Scale); + ImGui.TableSetupColumn("id", flags, 130 * UiHelpers.Scale); var items = _collectionManager.Active.Current.ChangedItems; var rest = _changedItemFilter.IsEmpty && _changedItemModFilter.IsEmpty @@ -76,7 +75,7 @@ public class ChangedItemsTab : ITab /// Apply the current filters. private bool FilterChangedItem(KeyValuePair, object?)> item) => (_changedItemFilter.IsEmpty - || UiHelpers.ChangedItemName(item.Key, item.Value.Item2) + || ChangedItemDrawer.ChangedItemFilterName(item.Key, item.Value.Item2) .Contains(_changedItemFilter.Lower, StringComparison.OrdinalIgnoreCase)) && (_changedItemModFilter.IsEmpty || item.Value.Item1.Any(m => m.Name.Contains(_changedItemModFilter))); @@ -84,7 +83,7 @@ public class ChangedItemsTab : ITab private void DrawChangedItemColumn(KeyValuePair, object?)> item) { ImGui.TableNextColumn(); - UiHelpers.DrawChangedItem(_communicator, item.Key, item.Value.Item2, false); + _drawer.DrawChangedItem(item.Key, item.Value.Item2, false); ImGui.TableNextColumn(); if (item.Value.Item1.Count > 0) { @@ -94,7 +93,7 @@ public class ChangedItemsTab : ITab } ImGui.TableNextColumn(); - if (!UiHelpers.GetChangedItemObject(item.Value.Item2, out var text)) + if (!ChangedItemDrawer.GetChangedItemObject(item.Value.Item2, out var text)) return; using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); diff --git a/Penumbra/UI/UiHelpers.cs b/Penumbra/UI/UiHelpers.cs index 944223f0..f46eac35 100644 --- a/Penumbra/UI/UiHelpers.cs +++ b/Penumbra/UI/UiHelpers.cs @@ -3,17 +3,10 @@ using System.IO; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using ImGuiNET; -using Lumina.Data.Parsing; -using Lumina.Excel.GeneratedSheets; using OtterGui; using OtterGui.Raii; -using Penumbra.Api; -using Penumbra.Api.Enums; -using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; -using Penumbra.Services; using Penumbra.String; using Penumbra.UI.Classes; @@ -53,61 +46,6 @@ public static class UiHelpers ImGui.SetTooltip("Click to copy to clipboard."); } - /// Apply Changed Item Counters to the Name if necessary. - public static string ChangedItemName(string name, object? data) - => data is int counter ? $"{counter} Files Manipulating {name}s" : name; - - /// - /// Draw a changed item, invoking the Api-Events for clicks and tooltips. - /// Also draw the item Id in grey if requested. - /// - public static void DrawChangedItem(CommunicatorService communicator, string name, object? data, bool drawId) - { - name = ChangedItemName(name, data); - var ret = ImGui.Selectable(name) ? MouseButton.Left : MouseButton.None; - ret = ImGui.IsItemClicked(ImGuiMouseButton.Right) ? MouseButton.Right : ret; - ret = ImGui.IsItemClicked(ImGuiMouseButton.Middle) ? MouseButton.Middle : ret; - - if (ret != MouseButton.None) - communicator.ChangedItemClick.Invoke(ret, data); - - if (communicator.ChangedItemHover.HasTooltip && ImGui.IsItemHovered()) - { - // We can not be sure that any subscriber actually prints something in any case. - // Circumvent ugly blank tooltip with less-ugly useless tooltip. - using var tt = ImRaii.Tooltip(); - using var group = ImRaii.Group(); - communicator.ChangedItemHover.Invoke(data); - group.Dispose(); - if (ImGui.GetItemRectSize() == Vector2.Zero) - ImGui.TextUnformatted("No actions available."); - } - - if (!drawId || !GetChangedItemObject(data, out var text)) - return; - - ImGui.SameLine(ImGui.GetContentRegionAvail().X); - ImGuiUtil.RightJustify(text, ColorId.ItemId.Value()); - } - - /// Return more detailed object information in text, if it exists. - public static bool GetChangedItemObject(object? obj, out string text) - { - switch (obj) - { - case Item it: - var quad = (Quad)it.ModelMain; - text = quad.C == 0 ? $"({quad.A}-{quad.B})" : $"({quad.A}-{quad.B}-{quad.C})"; - return true; - case ModelChara m: - text = $"({((CharacterBase.ModelType)m.Type).ToName()} {m.Model}-{m.Base}-{m.Variant})"; - return true; - default: - text = string.Empty; - return false; - } - } - /// Draw a button to open the official discord server. /// The desired width of the button. public static void DrawDiscordButton(float width)