diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 708d935..fd76817 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -2,23 +2,27 @@ using Glamourer.Gui; using Glamourer.Services; using Luna; +using Luna.Generators; using Newtonsoft.Json; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Glamourer; -public class EphemeralConfig : ISavable +public partial class EphemeralConfig : ISavable { - public int Version { get; set; } = Configuration.Constants.CurrentVersion; - public bool IncognitoMode { get; set; } = false; - public bool UnlockDetailMode { get; set; } = true; - public bool ShowDesignQuickBar { get; set; } = false; - public bool LockDesignQuickBar { get; set; } = false; - public bool LockMainWindow { get; set; } = false; - public MainTabType SelectedMainTab { get; set; } = MainTabType.Settings; - public Guid SelectedDesign { get; set; } = Guid.Empty; - public Guid SelectedQuickDesign { get; set; } = Guid.Empty; - public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; + public int Version { get; set; } = Configuration.Constants.CurrentVersion; + + [ConfigProperty] + private bool _incognitoMode; + + public bool UnlockDetailMode { get; set; } = true; + public bool ShowDesignQuickBar { get; set; } = false; + public bool LockDesignQuickBar { get; set; } = false; + public bool LockMainWindow { get; set; } = false; + public MainTabType SelectedMainTab { get; set; } = MainTabType.Settings; + public Guid SelectedDesign { get; set; } = Guid.Empty; + public Guid SelectedQuickDesign { get; set; } = Guid.Empty; + public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; public float CurrentDesignSelectorWidth { get; set; } = 200f; public float DesignSelectorMinimumScale { get; set; } = 0.1f; @@ -70,9 +74,9 @@ public class EphemeralConfig : ISavable public void Save(StreamWriter writer) { - using var jWriter = new JsonTextWriter(writer); + using var jWriter = new JsonTextWriter(writer); jWriter.Formatting = Formatting.Indented; - var serializer = new JsonSerializer { Formatting = Formatting.Indented }; + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index f75b04e..13bb831 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -46,7 +46,7 @@ public partial class CustomizationDrawer } if (hasIcon) - Im.Tooltip.ImageOnHover(wrap!.Id, wrap.Size); + Im.Tooltip.ImageOnHover(wrap!.Id, wrap!.Size); Im.Line.Same(); using (Im.Group()) @@ -221,7 +221,7 @@ public partial class CustomizationDrawer } if (hasIcon) - Im.Tooltip.ImageOnHover(wrap!.Id, wrap.Size); + Im.Tooltip.ImageOnHover(wrap!.Id, wrap!.Size); if (idx % 4 is not 3) Im.Line.Same(); } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 9270578..0277f0e 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -92,9 +92,6 @@ public abstract class DesignComboBase : FilterComboCache Config.IncognitoMode; - void IDisposable.Dispose() { DesignChanged.Unsubscribe(OnDesignChanged); @@ -168,7 +165,7 @@ public abstract class DesignComboBase : FilterComboCache obj) - => obj.Item1.ResolveName(Incognito); + => obj.Item1.ResolveName(Config.IncognitoMode); protected override float GetFilterWidth() => InnerWidth - 2 * Im.Style.FramePadding.X; @@ -294,7 +291,7 @@ public abstract class DesignCombo : DesignComboBase => CurrentSelection?.Item1; public void Draw(float width) - => Draw(Design, Design?.ResolveName(Incognito) ?? string.Empty, width); + => Draw(Design, Design?.ResolveName(Config.IncognitoMode) ?? string.Empty, width); } public sealed class QuickDesignCombo : DesignCombo @@ -392,11 +389,11 @@ public sealed class RandomDesignCombo( public bool Draw(RandomPredicate.Exact exact, float width) { var design = GetDesign(exact); - return Draw(design, design?.ResolveName(Incognito) ?? $"Not Found [{exact.Value.Text}]", width); + return Draw(design, design?.ResolveName(Config.IncognitoMode) ?? $"Not Found [{exact.Value.Text}]", width); } public bool Draw(IDesignStandIn? design, float width) - => Draw(design, design?.ResolveName(Incognito) ?? string.Empty, width); + => Draw(design, design?.ResolveName(Config.IncognitoMode) ?? string.Empty, width); } public sealed class SpecialDesignCombo( @@ -419,7 +416,7 @@ public sealed class SpecialDesignCombo( { public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex) { - if (!Draw(design?.Design, design?.Design.ResolveName(Incognito), Im.ContentRegion.Available.X)) + if (!Draw(design?.Design, design?.Design.ResolveName(Config.IncognitoMode), Im.ContentRegion.Available.X)) return; if (autoDesignIndex >= 0) diff --git a/Glamourer/Gui/Equipment/BaseItemCombo.cs b/Glamourer/Gui/Equipment/BaseItemCombo.cs new file mode 100644 index 0000000..d7b5fdb --- /dev/null +++ b/Glamourer/Gui/Equipment/BaseItemCombo.cs @@ -0,0 +1,119 @@ +using Glamourer.Services; +using Glamourer.Unlocks; +using ImSharp; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public abstract class BaseItemCombo(FavoriteManager favorites, ItemManager items) : FilterComboBase(new ItemFilter()) +{ + public abstract StringU8 Label { get; } + + protected readonly FavoriteManager Favorites = favorites; + protected readonly ItemManager Items = items; + protected EquipItem CurrentItem; + protected PrimaryId CustomSetId; + protected SecondaryId CustomWeaponId; + protected Variant CustomVariant; + + public bool Draw(in EquipItem item, out EquipItem newItem, float width) + { + using var id = Im.Id.Push(Label); + CurrentItem = item; + CustomVariant = 0; + if (Draw(StringU8.Empty, item.Name, StringU8.Empty, width, out var cache)) + { + newItem = cache.Item; + return true; + } + + if (CustomVariant.Id is not 0 && Identify(out newItem)) + return true; + + newItem = item; + return false; + } + + public readonly struct CacheItem(EquipItem item) : IDisposable + { + public readonly EquipItem Item = item; + public readonly StringPair Name = new(item.Name); + public readonly SizedStringPair Model = new($"({item.PrimaryId.Id}-{item.Variant.Id})"); + + public void Dispose() + => Model.Dispose(); + } + + protected sealed class ItemFilter : PartwiseFilterBase + { + public override bool WouldBeVisible(in CacheItem item, int globalIndex) + => base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Model.Utf16); + + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Name.Utf16; + } + + protected override FilterComboBaseCache CreateCache() + => new Cache(this); + + protected sealed class Cache(FilterComboBase parent) : FilterComboBaseCache(parent) + { + private static EquipItem _longestItem; + + protected override void ComputeWidth() + { + if (!_longestItem.Valid) + { + var data = ((BaseItemCombo)Parent).Items.ItemData; + _longestItem = data.AllItems(true).Concat(data.AllItems(false)) + .MaxBy(i => Im.Font.CalculateSize($"{i.Item2.Name} ({i.Item2.ModelString})").X).Item2; + } + + ComboWidth = Im.Font.CalculateSize($"{_longestItem.Name} ({_longestItem.Name})").X + + Im.Style.FrameHeight + + Im.Style.ItemSpacing.X * 3; + } + } + + protected override float ItemHeight + => Im.Style.FrameHeightWithSpacing; + + protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) + { + UiHelpers.DrawFavoriteStar(Favorites, item.Item); + Im.Line.Same(); + var ret = Im.Selectable(item.Name.Utf8, selected); + Im.Line.Same(); + using var color = ImGuiColor.Text.Push(Rgba32.Gray); + ImEx.TextRightAligned(item.Model); + return ret; + } + + protected override void EnterPressed() + { + if (!Im.Io.KeyControl) + return; + + var split = ((ItemFilter)Filter).Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + switch (split.Length) + { + case 2 when ushort.TryParse(split[0], out var setId) && byte.TryParse(split[1], out var variant): + CustomSetId = setId; + CustomVariant = variant; + break; + case 3 when ushort.TryParse(split[0], out var setId) + && ushort.TryParse(split[1], out var weaponId) + && byte.TryParse(split[2], out var variant): + CustomSetId = setId; + CustomWeaponId = weaponId; + CustomVariant = variant; + break; + default: return; + } + } + + protected abstract bool Identify(out EquipItem item); + + protected override bool IsSelected(CacheItem item, int globalIndex) + => item.Item.Id == CurrentItem.Id; +} diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index 5d486e9..f25eafd 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -1,66 +1,32 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; using ImSharp; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Extensions; -using OtterGui.Log; -using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Addon = Lumina.Excel.Sheets.Addon; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Equipment; -public sealed class BonusItemCombo2(IDataManager gameData, ItemManager items, FavoriteManager favorites, BonusItemFlag slot) - : ImSharp.FilterComboBase(new ItemFilter()) +public sealed class BonusItemCombo(FavoriteManager favorites, ItemManager items, IDataManager gameData, BonusItemFlag slot) + : BaseItemCombo(favorites, items) { - public readonly StringU8 Label = GetLabel(gameData, slot); - public readonly BonusItemFlag Slot = slot; + public override StringU8 Label { get; } = GetLabel(gameData, slot); + public readonly BonusItemFlag Slot = slot; - public readonly struct CacheItem(EquipItem item) : IDisposable + protected override bool Identify(out EquipItem item) { - public readonly EquipItem Item = item; - public readonly StringPair Name = new(item.Name); - public readonly SizedString Model = new($"({item.PrimaryId.Id}-{item.Variant.Id})"); - - public void Dispose() - => Model.Dispose(); - } - - private sealed class ItemFilter : PartwiseFilterBase - { - protected override string ToFilterString(in CacheItem item, int globalIndex) - => item.Name.Utf16; + item = Items.Identify(Slot, CustomSetId, CustomVariant); + return true; } protected override IEnumerable GetItems() { var nothing = EquipItem.BonusItemNothing(Slot); - return items.ItemData.ByType[Slot.ToEquipType()].OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing) + return Items.ItemData.ByType[Slot.ToEquipType()].OrderByDescending(Favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing) .Select(i => new CacheItem(i)); } - protected override float ItemHeight - => Im.Style.TextHeightWithSpacing; - - protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) - { - UiHelpers.DrawFavoriteStar(favorites, item.Item); - Im.Line.Same(); - var ret = Im.Selectable(item.Name.Utf8, selected); - Im.Line.Same(); - using var color = ImGuiColor.Text.Push(Rgba32.Gray); - ImEx.TextRightAligned(item.Model); - return ret; - } - - protected override bool IsSelected(CacheItem item, int globalIndex) - => throw new NotImplementedException(); - private static StringU8 GetLabel(IDataManager gameData, BonusItemFlag slot) { var sheet = gameData.GetExcelSheet()!; @@ -74,109 +40,3 @@ public sealed class BonusItemCombo2(IDataManager gameData, ItemManager items, Fa }; } } - -public sealed class BonusItemCombo : FilterComboCache -{ - private readonly FavoriteManager _favorites; - public readonly string Label; - private CustomItemId _currentItem; - private float _innerWidth; - - public PrimaryId CustomSetId { get; private set; } - public Variant CustomVariant { get; private set; } - - public BonusItemCombo(IDataManager gameData, ItemManager items, BonusItemFlag slot, Logger log, FavoriteManager favorites) - : base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log) - { - _favorites = favorites; - Label = GetLabel(gameData, slot); - _currentItem = 0; - SearchByParts = true; - } - - protected override void DrawList(float width, float itemHeight) - { - base.DrawList(width, itemHeight); - if (NewSelection != null && Items.Count > NewSelection.Value) - CurrentSelection = Items[NewSelection.Value]; - } - - protected override int UpdateCurrentSelected(int currentSelected) - { - if (CurrentSelection.Id == _currentItem) - return currentSelected; - - CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem); - CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; - return base.UpdateCurrentSelected(CurrentSelectionIdx); - } - - public bool Draw(string previewName, BonusItemId previewIdx, float width, float innerWidth) - { - _innerWidth = innerWidth; - _currentItem = previewIdx; - CustomVariant = 0; - return Draw($"##{Label}", previewName, string.Empty, width, Im.Style.TextHeightWithSpacing); - } - - protected override float GetFilterWidth() - => _innerWidth - 2 * Im.Style.FramePadding.X; - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var obj = Items[globalIdx]; - var name = ToString(obj); - if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx) - { - CurrentSelectionIdx = -1; - _currentItem = obj.Id; - CurrentSelection = default; - } - - Im.Line.Same(); - var ret = ImGui.Selectable(name, selected); - Im.Line.Same(); - using var color = ImGuiColor.Text.Push(0xFF808080); - ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.Variant.Id})"); - return ret; - } - - protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); - - protected override string ToString(EquipItem obj) - => obj.Name; - - private static string GetLabel(IDataManager gameData, BonusItemFlag slot) - { - var sheet = gameData.GetExcelSheet()!; - - return slot switch - { - BonusItemFlag.Glasses => sheet.TryGetRow(16050, out var text) ? text.Text.ToString() : "Facewear", - BonusItemFlag.UnkSlot => sheet.TryGetRow(16051, out var text) ? text.Text.ToString() : "Facewear", - - _ => string.Empty, - }; - } - - private static List GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot) - { - var nothing = EquipItem.BonusItemNothing(slot); - return items.ItemData.ByType[slot.ToEquipType()].OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList(); - } - - protected override void OnClosePopup() - { - // If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that. - if (!Im.Io.KeyControl) - return; - - var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant)) - return; - - CustomSetId = setId; - CustomVariant = variant; - } -} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 67967e8..720174a 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -5,7 +5,6 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImSharp; using Luna; -using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -19,7 +18,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; private readonly DictStain _stainData; - private readonly ItemCombo[] _itemCombo; + private readonly EquipCombo[] _equipCombo; private readonly BonusItemCombo[] _bonusItemCombo; private readonly Dictionary _weaponCombo; private readonly TextureService _textures; @@ -28,9 +27,6 @@ public class EquipmentDrawer private readonly AdvancedDyePopup _advancedDyes; private readonly ItemCopyService _itemCopy; - private float _requiredComboWidthUnscaled; - private float _requiredComboWidth; - private Stain? _draggedStain; private EquipItemSlotCache _draggedItem; private EquipSlot _dragTarget; @@ -46,18 +42,16 @@ public class EquipmentDrawer _itemCopy = itemCopy; _stainData = items.Stains; _stainCombo = new GlamourerColorCombo(_stainData, favorites); - _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); - _bonusItemCombo = BonusExtensions.AllFlags.Select(f => new BonusItemCombo(gameData, items, f, Glamourer.Log, favorites)).ToArray(); + _equipCombo = EquipSlotExtensions.EqdpSlots.Select(e => new EquipCombo(favorites, items, gameData, e)).ToArray(); + _bonusItemCombo = BonusExtensions.AllFlags.Select(f => new BonusItemCombo(favorites, items, gameData, f)).ToArray(); _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in FullEquipType.Values) { - if (type.ToSlot() is EquipSlot.MainHand) - _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites)); - else if (type.ToSlot() is EquipSlot.OffHand) - _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites)); + if (type.ToSlot() is EquipSlot.MainHand or EquipSlot.OffHand) + _weaponCombo.TryAdd(type, new WeaponCombo(favorites, items, type)); } - _weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log, favorites)); + _weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(favorites, items, FullEquipType.Unknown)); } private Vector2 _iconSize; @@ -66,15 +60,8 @@ public class EquipmentDrawer public void Prepare() { - _iconSize = new Vector2(2 * Im.Style.FrameHeight + Im.Style.ItemSpacing.Y); - _comboLength = DefaultWidth * Im.Style.GlobalScale; - if (_requiredComboWidthUnscaled is 0) - _requiredComboWidthUnscaled = _items.ItemData.AllItems(true) - .Concat(_items.ItemData.AllItems(false)) - .Max(i => Im.Font.CalculateSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) - / Im.Style.GlobalScale; - - _requiredComboWidth = _requiredComboWidthUnscaled * Im.Style.GlobalScale; + _iconSize = new Vector2(2 * Im.Style.FrameHeight + Im.Style.ItemSpacing.Y); + _comboLength = DefaultWidth * Im.Style.GlobalScale; _advancedMaterialColor = ColorId.AdvancedDyeActive.Value(); _dragTarget = EquipSlot.Unknown; } @@ -194,11 +181,11 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } if (VerifyRestrictedGear(equipDrawData)) - label += " (Restricted)"; + label += " (Restricted)"u8; DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } @@ -216,7 +203,7 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); @@ -236,11 +223,11 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } if (allWeapons) - mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; + mainhandLabel = new StringU8($"{mainhandLabel} ({mainhand.CurrentItem.Type.ToName()})"); WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel); if (offhand.CurrentItem.Type is FullEquipType.Unknown) @@ -258,7 +245,7 @@ public class EquipmentDrawer } else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel); @@ -292,7 +279,7 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } if (VerifyRestrictedGear(equipDrawData)) @@ -316,7 +303,7 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); @@ -339,7 +326,7 @@ public class EquipmentDrawer } WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel, - allWeapons ? mainhand.CurrentItem.Type.ToName() : null); + allWeapons ? new StringU8(mainhand.CurrentItem.Type.ToName()) : null); DrawStain(mainhand, false); if (mainhand.DisplayApplication) @@ -349,7 +336,7 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } } @@ -379,7 +366,7 @@ public class EquipmentDrawer } else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : ColorParameter.Default); } } } @@ -434,23 +421,20 @@ public class EquipmentDrawer } } - private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) + private void DrawItem(in EquipDrawData data, out StringU8 label, bool small, bool clear, bool open) { Debug.Assert(data.Slot.IsEquipment() || data.Slot.IsAccessory(), $"Called {nameof(DrawItem)} on {data.Slot}."); - var combo = _itemCombo[data.Slot.ToIndex()]; + var combo = _equipCombo[data.Slot.ToIndex()]; label = combo.Label; if (!data.Locked && open) UiHelpers.OpenCombo($"##{combo.Label}"); using var disabled = Im.Disabled(data.Locked); - var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - Im.Style.FrameHeight : _comboLength, - _requiredComboWidth); + var change = combo.Draw(data.CurrentItem, out var newItem, small ? _comboLength - Im.Style.FrameHeight : _comboLength); DrawGearDragDrop(data); if (change) - data.SetItem(combo.CurrentSelection); - else if (combo.CustomVariant.Id > 0) - data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + data.SetItem(newItem); _itemCopy.HandleCopyPaste(data); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), @@ -458,7 +442,7 @@ public class EquipmentDrawer data.SetItem(item); } - private void DrawBonusItem(in BonusDrawData data, out string label, bool small, bool clear, bool open) + private void DrawBonusItem(in BonusDrawData data, out StringU8 label, bool small, bool clear, bool open) { var combo = _bonusItemCombo[data.Slot.ToIndex()]; label = combo.Label; @@ -466,21 +450,17 @@ public class EquipmentDrawer UiHelpers.OpenCombo($"##{combo.Label}"); using var disabled = Im.Disabled(data.Locked); - var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, - small ? _comboLength - Im.Style.FrameHeight : _comboLength, - _requiredComboWidth); + var change = combo.Draw(data.CurrentItem, out var newItem, small ? _comboLength - Im.Style.FrameHeight : _comboLength); if (Im.Item.Hovered() && Im.Io.KeyControl) { if (Im.Keyboard.IsPressed(Key.C)) - _itemCopy.Copy(combo.CurrentSelection); + _itemCopy.Copy(newItem); else if (Im.Keyboard.IsPressed(Key.V)) _itemCopy.Paste(data.Slot.ToEquipType(), data.SetItem); } if (change) - data.SetItem(combo.CurrentSelection); - else if (combo.CustomVariant.Id > 0) - data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + data.SetItem(newItem); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), out var item)) @@ -563,12 +543,12 @@ public class EquipmentDrawer return clicked && valid; } - private void DrawMainhand(ref EquipDrawData mainhand, ref EquipDrawData offhand, out string label, bool drawAll, bool small, + private void DrawMainhand(ref EquipDrawData mainhand, ref EquipDrawData offhand, out StringU8 label, bool drawAll, bool small, bool open) { if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : mainhand.CurrentItem.Type, out var combo)) { - label = string.Empty; + label = StringU8.Empty; return; } @@ -580,11 +560,8 @@ public class EquipmentDrawer { if (!mainhand.Locked && open) UiHelpers.OpenCombo($"##{label}"); - if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - Im.Style.FrameHeight : _comboLength, - _requiredComboWidth)) - changedItem = combo.CurrentSelection; - else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) - changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); + if (combo.Draw(mainhand.CurrentItem, out var newItem, small ? _comboLength - Im.Style.FrameHeight : _comboLength)) + changedItem = newItem; _itemCopy.HandleCopyPaste(mainhand); DrawGearDragDrop(mainhand); @@ -610,11 +587,11 @@ public class EquipmentDrawer "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } - private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) + private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out StringU8 label, bool small, bool clear, bool open) { if (!_weaponCombo.TryGetValue(offhand.CurrentItem.Type, out var combo)) { - label = string.Empty; + label = StringU8.Empty; return; } @@ -624,11 +601,8 @@ public class EquipmentDrawer using var disabled = Im.Disabled(locked); if (!locked && open) UiHelpers.OpenCombo($"##{combo.Label}"); - if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - Im.Style.FrameHeight : _comboLength, - _requiredComboWidth)) - offhand.SetItem(combo.CurrentSelection); - else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) - offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); + if (combo.Draw(offhand.CurrentItem, out var newItem, small ? _comboLength - Im.Style.FrameHeight : _comboLength)) + offhand.SetItem(newItem); _itemCopy.HandleCopyPaste(offhand); DrawGearDragDrop(offhand); @@ -664,8 +638,9 @@ public class EquipmentDrawer #endregion - private void WeaponHelpMarker(bool hasAdvancedDyes, string label, string? type = null) + private void WeaponHelpMarker(bool hasAdvancedDyes, StringU8 label, StringU8? type = null) { + Im.Line.SameInner(); LunaStyle.DrawAlignedHelpMarker( "Changing weapons to weapons of different types can cause crashes, freezes, soft- and hard locks and cheating, "u8 + "thus it is only allowed to change weapons to other weapons of the same type."u8); @@ -680,7 +655,7 @@ public class EquipmentDrawer } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private void DrawEquipLabel(bool hasAdvancedDyes, string label) + private void DrawEquipLabel(bool hasAdvancedDyes, StringU8 label) { Im.Line.Same(); using (ImGuiColor.Text.Push(_advancedMaterialColor, hasAdvancedDyes)) diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 13d36f4..c7dbc3c 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -9,7 +9,7 @@ namespace Glamourer.Gui.Equipment; public sealed class GlamourerColorCombo(DictStain stains, FavoriteManager favorites) : FilterComboColors { protected override float AdditionalSpace - => AwesomeIcon.Font.CalculateTextSize(LunaStyle.FavoriteIcon.Span).X + 4 * Im.Style.GlobalScale; + => AwesomeIcon.Font.CalculateTextSize(LunaStyle.FavoriteIcon.Span).X + 8 * Im.Style.GlobalScale; protected override bool DrawItem(in Item item, int globalIndex, bool selected) { @@ -17,7 +17,7 @@ public sealed class GlamourerColorCombo(DictStain stains, FavoriteManager favori Im.Dummy(AwesomeIcon.Font.CalculateTextSize(LunaStyle.FavoriteIcon.Span)); else UiHelpers.DrawFavoriteStar(favorites, item.Id); - Im.Line.Same(0, 4 * Im.Style.GlobalScale); + Im.Line.Same(0, 8 * Im.Style.GlobalScale); var buttonWidth = Im.ContentRegion.Available.X; var totalWidth = Im.ContentRegion.Maximum.X; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 25cd74a..47bc5c6 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,135 +1,54 @@ using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using Dalamud.Bindings.ImGui; using ImSharp; using Lumina.Excel.Sheets; -using OtterGui.Classes; -using OtterGui.Extensions; -using OtterGui.Log; -using OtterGui.Text; -using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Equipment; -public sealed class ItemCombo : FilterComboCache +public sealed class EquipCombo(FavoriteManager favorites, ItemManager items, IDataManager gameData, EquipSlot slot) + : BaseItemCombo(favorites, items) { - private readonly FavoriteManager _favorites; - public readonly string Label; - private ItemId _currentItem; - private float _innerWidth; + public override StringU8 Label { get; } = GetLabel(gameData, slot); + public readonly EquipSlot Slot = slot; - public PrimaryId CustomSetId { get; private set; } - public Variant CustomVariant { get; private set; } - - public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) - : base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log) + protected override bool Identify(out EquipItem item) { - _favorites = favorites; - Label = GetLabel(gameData, slot); - _currentItem = ItemManager.NothingId(slot); - SearchByParts = true; + item = Items.Identify(Slot, CustomSetId, CustomVariant); + return true; } - protected override void DrawList(float width, float itemHeight) + protected override IEnumerable GetItems() { - base.DrawList(width, itemHeight); - if (NewSelection != null && Items.Count > NewSelection.Value) - CurrentSelection = Items[NewSelection.Value]; + var nothing = ItemManager.NothingItem(Slot); + if (!Items.ItemData.ByType.TryGetValue(Slot.ToEquipType(), out var list)) + return [new CacheItem(nothing)]; + + var enumerable = list.AsEnumerable(); + if (Slot.IsEquipment()) + enumerable = enumerable.Append(ItemManager.SmallClothesItem(Slot)); + return enumerable.OrderByDescending(Favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).Select(e => new CacheItem(e)); } - protected override int UpdateCurrentSelected(int currentSelected) - { - if (CurrentSelection.ItemId == _currentItem) - return currentSelected; - - CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); - CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; - return base.UpdateCurrentSelected(CurrentSelectionIdx); - } - - public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) - { - _innerWidth = innerWidth; - _currentItem = previewIdx; - CustomVariant = 0; - return Draw($"##{Label}", previewName, string.Empty, width, Im.Style.TextHeightWithSpacing); - } - - protected override float GetFilterWidth() - => _innerWidth - 2 * Im.Style.FramePadding.X; - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var obj = Items[globalIdx]; - var name = ToString(obj); - if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx) - { - CurrentSelectionIdx = -1; - _currentItem = obj.ItemId; - CurrentSelection = default; - } - - Im.Line.Same(); - var ret = ImGui.Selectable(name, selected); - Im.Line.Same(); - using var color = ImGuiColor.Text.Push(0xFF808080); - ImUtf8.TextRightAligned($"({obj.PrimaryId.Id}-{obj.Variant.Id})"); - return ret; - } - - protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); - - protected override string ToString(EquipItem obj) - => obj.Name; - - private static string GetLabel(IDataManager gameData, EquipSlot slot) + private static StringU8 GetLabel(IDataManager gameData, EquipSlot slot) { var sheet = gameData.GetExcelSheet(); return slot switch { - EquipSlot.Head => sheet.TryGetRow(740, out var text) ? text.Text.ToString() : "Head", - EquipSlot.Body => sheet.TryGetRow(741, out var text) ? text.Text.ToString() : "Body", - EquipSlot.Hands => sheet.TryGetRow(742, out var text) ? text.Text.ToString() : "Hands", - EquipSlot.Legs => sheet.TryGetRow(744, out var text) ? text.Text.ToString() : "Legs", - EquipSlot.Feet => sheet.TryGetRow(745, out var text) ? text.Text.ToString() : "Feet", - EquipSlot.Ears => sheet.TryGetRow(746, out var text) ? text.Text.ToString() : "Ears", - EquipSlot.Neck => sheet.TryGetRow(747, out var text) ? text.Text.ToString() : "Neck", - EquipSlot.Wrists => sheet.TryGetRow(748, out var text) ? text.Text.ToString() : "Wrists", - EquipSlot.RFinger => sheet.TryGetRow(749, out var text) ? text.Text.ToString() : "Right Ring", - EquipSlot.LFinger => sheet.TryGetRow(750, out var text) ? text.Text.ToString() : "Left Ring", - _ => string.Empty, + EquipSlot.Head => sheet.TryGetRow(740, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Head"u8), + EquipSlot.Body => sheet.TryGetRow(741, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Body"u8), + EquipSlot.Hands => sheet.TryGetRow(742, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Hands"u8), + EquipSlot.Legs => sheet.TryGetRow(744, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Legs"u8), + EquipSlot.Feet => sheet.TryGetRow(745, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Feet"u8), + EquipSlot.Ears => sheet.TryGetRow(746, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Ears"u8), + EquipSlot.Neck => sheet.TryGetRow(747, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Neck"u8), + EquipSlot.Wrists => sheet.TryGetRow(748, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Wrists"u8), + EquipSlot.RFinger => sheet.TryGetRow(749, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Right Ring"u8), + EquipSlot.LFinger => sheet.TryGetRow(750, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Left Ring"u8), + _ => StringU8.Empty, }; } - - private static List GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) - { - var nothing = ItemManager.NothingItem(slot); - if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list)) - return [nothing]; - - var enumerable = list.AsEnumerable(); - if (slot.IsEquipment()) - enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot)); - return enumerable.OrderByDescending(favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).ToList(); - } - - protected override void OnClosePopup() - { - // If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that. - if (!Im.Io.KeyControl) - return; - - var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant)) - return; - - CustomSetId = setId; - CustomVariant = variant; - } } diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index dd2c1fb..761863e 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,115 +1,30 @@ using Glamourer.Services; using Glamourer.Unlocks; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Classes; -using OtterGui.Extensions; -using OtterGui.Log; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Widgets; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; -using Luna; namespace Glamourer.Gui.Equipment; -public sealed class WeaponCombo : FilterComboCache +public sealed class WeaponCombo(FavoriteManager favorites, ItemManager items, FullEquipType slot) + : BaseItemCombo(favorites, items) { - private readonly FavoriteManager _favorites; - public readonly string Label; - private ItemId _currentItem; - private float _innerWidth; + public override StringU8 Label { get; } = GetLabel(slot); + public readonly FullEquipType Slot = slot; - public PrimaryId CustomSetId { get; private set; } - public SecondaryId CustomWeaponId { get; private set; } - public Variant CustomVariant { get; private set; } - - public WeaponCombo(ItemManager items, FullEquipType type, OtterGui.Log.Logger log, FavoriteManager favorites) - : base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log) + protected override bool Identify(out EquipItem item) { - _favorites = favorites; - Label = GetLabel(type); - SearchByParts = true; - } - - protected override void DrawList(float width, float itemHeight) - { - base.DrawList(width, itemHeight); - if (NewSelection != null && Items.Count > NewSelection.Value) - CurrentSelection = Items[NewSelection.Value]; - } - - protected override int UpdateCurrentSelected(int currentSelected) - { - if (CurrentSelection.ItemId == _currentItem) - return currentSelected; - - CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); - CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; - return base.UpdateCurrentSelected(CurrentSelectionIdx); - } - - public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) - { - _innerWidth = innerWidth; - _currentItem = previewIdx; - CustomVariant = 0; - return Draw($"##{Label}", previewName, string.Empty, width, Im.Style.TextHeightWithSpacing); - } - - protected override float GetFilterWidth() - => _innerWidth - 2 * Im.Style.FramePadding.X; - - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var obj = Items[globalIdx]; - var name = ToString(obj); - if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx) + if (Slot is not FullEquipType.Unknown && ItemData.ConvertWeaponId(CustomSetId) != CurrentItem.Type) { - CurrentSelectionIdx = -1; - _currentItem = obj.ItemId; - CurrentSelection = default; + item = default; + return false; } - - Im.Line.Same(); - var ret = ImGui.Selectable(name, selected); - Im.Line.Same(); - using var color = ImGuiColor.Text.Push(0xFF808080); - ImUtf8.TextRightAligned($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant.Id})"); - return ret; + item = Items.Identify(Slot.ToSlot(), CustomSetId, CustomWeaponId, CustomVariant); + return true; } - protected override void OnClosePopup() - { - // If holding control while the popup closes, try to parse the input as a full tuple of set id, weapon id and variant, and set a custom item for that. - if (!Im.Io.KeyControl) - return; - - var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if (split.Length != 3 - || !ushort.TryParse(split[0], out var setId) - || !ushort.TryParse(split[1], out var weaponId) - || !byte.TryParse(split[2], out var variant)) - return; - - CustomSetId = setId; - CustomWeaponId = weaponId; - CustomVariant = variant; - } - - protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); - - protected override string ToString(EquipItem obj) - => obj.Name; - - private static string GetLabel(FullEquipType type) - => type.IsUnknown() ? "Mainhand" : type.ToName(); - - private static IReadOnlyList GetWeapons(FavoriteManager favorites, ItemManager items, FullEquipType type) + private static IReadOnlyList GetItems(FavoriteManager favorites, ItemManager items, FullEquipType type) { if (type is FullEquipType.Unknown) { @@ -131,4 +46,30 @@ public sealed class WeaponCombo : FilterComboCache return [.. list.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)]; } -} + + protected override IEnumerable GetItems() + { + if (Slot is FullEquipType.Unknown) + { + var enumerable = Array.Empty().AsEnumerable(); + foreach (var t in FullEquipType.Values.Where(e => e.ToSlot() is EquipSlot.MainHand)) + { + if (Items.ItemData.ByType.TryGetValue(t, out var l)) + enumerable = enumerable.Concat(l); + } + + return enumerable.OrderByDescending(Favorites.Contains).ThenBy(e => e.Name).Select(e => new CacheItem(e)); + } + + if (!Items.ItemData.ByType.TryGetValue(Slot, out var list)) + return []; + + IEnumerable ret = list.OrderByDescending(Favorites.Contains).ThenBy(e => e.Name); + if (Slot.AllowsNothing()) + ret = ret.Prepend(ItemManager.NothingItem(Slot)); + return ret.Select(e => new CacheItem(e)); + } + + private static StringU8 GetLabel(FullEquipType type) + => type.IsUnknown() ? new StringU8("Mainhand"u8) : new StringU8(type.ToName()); +} \ No newline at end of file diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 455cefd..bdbaa16 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -45,13 +45,13 @@ public sealed unsafe class AdvancedDyePopup( return true; } - public void DrawButton(EquipSlot slot, Rgba32 color) + public void DrawButton(EquipSlot slot, ColorParameter color) => DrawButton(MaterialValueIndex.FromSlot(slot), color); - public void DrawButton(BonusItemFlag slot, Rgba32 color) + public void DrawButton(BonusItemFlag slot, ColorParameter color) => DrawButton(MaterialValueIndex.FromSlot(slot), color); - private void DrawButton(MaterialValueIndex index, Rgba32 color) + private void DrawButton(MaterialValueIndex index, ColorParameter color) { if (config.HideDesignPanel.HasFlag(DesignPanelFlag.AdvancedDyes)) return; @@ -62,7 +62,7 @@ public sealed unsafe class AdvancedDyePopup( var (textColor, buttonColor) = isOpen ? (ColorId.HeaderButtons.Value(), ImGuiColor.ButtonActive.Get()) - : (color, 0u); + : (color, ColorParameter.Default); using (ImStyleBorder.Frame.Push(textColor, 2 * Im.Style.GlobalScale, isOpen)) { diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorFilter.cs b/Glamourer/Gui/Tabs/ActorTab/ActorFilter.cs new file mode 100644 index 0000000..e03c1cf --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/ActorFilter.cs @@ -0,0 +1,100 @@ +using Dalamud.Plugin.Services; +using ImSharp; +using Luna; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class ActorFilter : TextFilterBase, IUiService +{ + private readonly IPlayerState _playerState; + + private enum FilterMethod + { + Player, + Owned, + Npc, + Retainer, + Special, + Homeworld, + Text, + Empty, + }; + + private FilterMethod _method = FilterMethod.Empty; + + public ActorFilter(IPlayerState playerState) + { + _playerState = playerState; + FilterChanged += () => + { + _method = Text switch + { + "" => FilterMethod.Empty, + "

" or "

" => FilterMethod.Player, + "" or "" => FilterMethod.Owned, + "" or "" => FilterMethod.Npc, + "" or "" => FilterMethod.Retainer, + "" or "" => FilterMethod.Special, + "" or "" => FilterMethod.Homeworld, + _ => FilterMethod.Text, + }; + }; + } + + public override bool DrawFilter(ReadOnlySpan label, Vector2 availableRegion) + { + var ret = base.DrawFilter(label, availableRegion); + + if (!Im.Item.Hovered()) + return ret; + + using var tt = Im.Tooltip.Begin(); + Im.Text("Filter for names containing the input."u8); + Im.Dummy(new Vector2(0, Im.Style.TextHeight / 2)); + Im.Text("Special filters are:"u8); + var color = ColorId.HeaderButtons.Value(); + Im.Text("

"u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only player characters."u8); + + + Im.Text(""u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only owned game objects."u8); + + Im.Text(""u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only NPCs."u8); + + Im.Text(""u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only retainers."u8); + + Im.Text(""u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only special screen characters."u8); + + Im.Text(""u8, color); + Im.Line.NoSpacing(); + Im.Text(": show only players from your world."u8); + return ret; + } + + protected override string ToFilterString(in ActorCacheItem item, int globalIndex) + => item.DisplayText.Utf16; + + public override bool WouldBeVisible(in ActorCacheItem item, int globalIndex) + => _method switch + { + FilterMethod.Player => item.Identifier.Type is IdentifierType.Player, + FilterMethod.Owned => item.Identifier.Type is IdentifierType.Owned, + FilterMethod.Npc => item.Identifier.Type is IdentifierType.Npc, + FilterMethod.Retainer => item.Identifier.Type is IdentifierType.Retainer, + FilterMethod.Special => item.Identifier.Type is IdentifierType.Special, + FilterMethod.Homeworld => item.Identifier.Type is IdentifierType.Player + && item.Identifier.HomeWorld == _playerState.HomeWorld.RowId, + FilterMethod.Text => base.WouldBeVisible(item, globalIndex), + _ => true, + }; +} diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index d1fe172..b4db897 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,8 +1,4 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Game.ClientState.Conditions; -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin.Services; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; using Glamourer.Designs; @@ -14,20 +10,16 @@ using Glamourer.Interop; using Glamourer.State; using ImSharp; using Luna; -using OtterGui; -using OtterGui.Raii; -using OtterGui.Text; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using TextStringHandlerBuffer = OtterGui.Text.HelperObjects.TextStringHandlerBuffer; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel +public sealed class ActorPanel : IPanel { - private readonly ActorSelector _selector; + private readonly ActorSelection _selection; private readonly StateManager _stateManager; private readonly CustomizationDrawer _customizationDrawer; private readonly EquipmentDrawer _equipmentDrawer; @@ -35,18 +27,12 @@ public class ActorPanel private readonly Configuration _config; private readonly DesignConverter _converter; private readonly ActorObjectManager _objects; - private readonly DesignManager _designManager; private readonly ImportService _importService; - private readonly ICondition _conditions; private readonly DictModelChara _modelChara; private readonly CustomizeParameterDrawer _parameterDrawer; private readonly AdvancedDyePopup _advancedDyes; - private readonly EditorHistory _editorHistory; - private readonly HeaderDrawer.Button[] _leftButtons; - private readonly HeaderDrawer.Button[] _rightButtons; - public ActorPanel(ActorSelector selector, - StateManager stateManager, + public ActorPanel(StateManager stateManager, CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, AutoDesignApplier autoDesignApplier, @@ -55,13 +41,11 @@ public class ActorPanel ActorObjectManager objects, DesignManager designManager, ImportService importService, - ICondition conditions, DictModelChara modelChara, CustomizeParameterDrawer parameterDrawer, AdvancedDyePopup advancedDyes, - EditorHistory editorHistory) + EditorHistory editorHistory, ActorSelection selection) { - _selector = selector; _stateManager = stateManager; _customizationDrawer = customizationDrawer; _equipmentDrawer = equipmentDrawer; @@ -69,61 +53,40 @@ public class ActorPanel _config = config; _converter = converter; _objects = objects; - _designManager = designManager; _importService = importService; - _conditions = conditions; _modelChara = modelChara; _parameterDrawer = parameterDrawer; _advancedDyes = advancedDyes; - _editorHistory = editorHistory; - _leftButtons = - [ - new SetFromClipboardButton(this), - new ExportToClipboardButton(this), - new SaveAsDesignButton(this), - new UndoButton(this), - ]; - _rightButtons = - [ - new LockedButton(this), - new HeaderDrawer.IncognitoButton(_config), - ]; + _selection = selection; } - - private ActorIdentifier _identifier; - private string _actorName = string.Empty; - private Actor _actor = Actor.Null; - private ActorData _data; - private ActorState? _state; - private bool _lockedRedraw; - private CustomizeFlag CustomizeApplicationFlags - => _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant; + => _selection.LockedRedraw + ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired + : CustomizeFlagExtensions.AllRelevant; + + public ReadOnlySpan Id + => "ActorPanel"u8; public void Draw() { - using var group = ImRaii.Group(); - (_identifier, _data) = _selector.Selection; - _lockedRedraw = _identifier.Type is IdentifierType.Special || _objects.IsInLobby - || _conditions[ConditionFlag.OccupiedInCutSceneEvent]; - (_actorName, _actor) = GetHeaderName(); - DrawHeader(); DrawPanel(); - if (_state is not { IsLocked: false }) + if (_selection.State is not { IsLocked: false }) return; if (_importService.CreateDatTarget(out var dat)) { - _stateManager.ChangeEntireCustomize(_state!, dat.Customize, CustomizeApplicationFlags, ApplySettings.Manual); - Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.", + _stateManager.ChangeEntireCustomize(_selection.State!, dat.Customize, CustomizeApplicationFlags, ApplySettings.Manual); + Glamourer.Messager.NotificationMessage( + $"Applied games .dat file {dat.Description} customizations to {_selection.State.Identifier}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { - _stateManager.ApplyDesign(_state!, designBase, ApplySettings.Manual); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, + _stateManager.ApplyDesign(_selection.State!, designBase, ApplySettings.Manual); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selection.State.Identifier}.", + NotificationType.Success, false); } @@ -131,39 +94,19 @@ public class ActorPanel _importService.CreateCharaSource(); } - private void DrawHeader() - { - var textColor = !_identifier.IsValid ? ImGuiColor.Text.Get() : - _data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value(); - HeaderDrawer.Draw(_actorName, textColor.Color, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); - - SaveDesignDrawPopup(); - } - - private (string, Actor) GetHeaderName() - { - if (!_identifier.IsValid) - return ("No Selection", Actor.Null); - - if (_data.Valid) - return (_selector.IncognitoMode ? _identifier.Incognito(_data.Label) : _data.Label, _data.Objects[0]); - - return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null); - } - private unsafe void DrawPanel() { - using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available); - if (!table || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) + using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.ScrollY, Im.ContentRegion.Available); + if (!table || _selection.State is null) return; table.SetupScrollFreeze(0, 1); table.NextColumn(); Im.Dummy(Vector2.Zero); - var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; + var transformationId = _selection.Actor.IsCharacter ? _selection.Actor.AsCharacter->CharacterData.TransformationId : 0; if (transformationId is not 0) - ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", - -Vector2.UnitX, Colors.SelectedRed); + ImEx.TextFramed($"Currently transformed to Transformation {transformationId}.", Im.ContentRegion.Available with { Y = 0 }, + Colors.SelectedRed); DrawApplyToSelf(); Im.Line.Same(); @@ -173,12 +116,12 @@ public class ActorPanel table.NextColumn(); using var disabled = Im.Disabled(transformationId is not 0); - if (_state.ModelData.IsHuman) + if (_selection.State.ModelData.IsHuman) DrawHumanPanel(); else DrawMonsterPanel(); - if (_data.Objects.Count > 0) - _advancedDyes.Draw(_data.Objects.Last(), _state); + if (_selection.Data.Objects.Count > 0) + _advancedDyes.Draw(_selection.Data.Objects.Last(), _selection.State); } private void DrawHumanPanel() @@ -194,18 +137,19 @@ public class ActorPanel if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) return; - var header = _state!.ModelData.ModelId == 0 - ? "Customization" - : $"Customization (Model Id #{_state.ModelData.ModelId})###Customization"; - var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); - using var h = Im.Tree.HeaderId(header, expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = Im.Tree.HeaderId(_selection.State!.ModelData.ModelId is 0 + ? "Customization"u8 + : $"Customization (Model Id #{_selection.State.ModelData.ModelId})###Customization", + expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); if (!h) return; - if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) - _stateManager.ChangeEntireCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, ApplySettings.Manual); + if (_customizationDrawer.Draw(_selection.State!.ModelData.Customize, _selection.State.IsLocked, _selection.LockedRedraw)) + _stateManager.ChangeEntireCustomize(_selection.State, _customizationDrawer.Customize, _customizationDrawer.Changed, + ApplySettings.Manual); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _selection.State)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); } @@ -217,22 +161,22 @@ public class ActorPanel _equipmentDrawer.Prepare(); - var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked); + var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selection.State!.IsLocked); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var data = EquipDrawData.FromState(_stateManager, _state!, slot); + var data = EquipDrawData.FromState(_stateManager, _selection.State!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _stateManager.ChangeStains(_state, slot, newAllStain, ApplySettings.Manual); + _stateManager.ChangeStains(_selection.State, slot, newAllStain, ApplySettings.Manual); } - var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); - var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); + var mainhand = EquipDrawData.FromState(_stateManager, _selection.State, EquipSlot.MainHand); + var offhand = EquipDrawData.FromState(_stateManager, _selection.State, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); foreach (var slot in BonusExtensions.AllFlags) { - var data = BonusDrawData.FromState(_stateManager, _state!, slot); + var data = BonusDrawData.FromState(_stateManager, _selection.State!, slot); _equipmentDrawer.DrawBonusItem(data); } @@ -248,7 +192,7 @@ public class ActorPanel if (!h) return; - _parameterDrawer.Draw(_stateManager, _state!); + _parameterDrawer.Draw(_stateManager, _selection.State!); } private unsafe void DrawDebugData() @@ -260,63 +204,64 @@ public class ActorPanel if (!h) return; - using var t = Im.Table.Begin("table"u8, 2, TableFlags.SizingFixedFit); - if (!t) + using var table = Im.Table.Begin("table"u8, 2, TableFlags.SizingFixedFit); + if (!table) return; - ImUtf8.DrawTableColumn("Object Index"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->ObjectIndex))}"); - ImUtf8.DrawTableColumn("Name ID"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->GetNameId()))}"); - ImUtf8.DrawTableColumn("Base ID"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->BaseId))}"); - ImUtf8.DrawTableColumn("Entity ID"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->EntityId))}"); - ImUtf8.DrawTableColumn("Owner ID"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->OwnerId))}"); - ImUtf8.DrawTableColumn("Game Object ID"u8); - DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->GetGameObjectId().ObjectId))}"); + table.DrawColumn("Object Index"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->ObjectIndex))); + table.DrawColumn("Name ID"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->GetNameId()))); + table.DrawColumn("Base ID"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->BaseId))); + table.DrawColumn("Entity ID"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->EntityId))); + table.DrawColumn("Owner ID"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->OwnerId))); + table.DrawColumn("Game Object ID"u8); + DrawCopyColumn(table, StringU8.Join(", "u8, _selection.Data.Objects.Select(d => d.AsObject->GetGameObjectId().ObjectId))); + return; - static void DrawCopyColumn(ref OtterGui.Text.HelperObjects.Utf8StringHandler text) + static void DrawCopyColumn(Im.TableDisposable table, Utf8StringHandler text) { - ImUtf8.DrawTableColumn(ref text); + table.DrawColumn(ref text); if (Im.Item.RightClicked()) - ImUtf8.SetClipboardText(TextStringHandlerBuffer.Span); + Im.Clipboard.Set(ref text); } } private void DrawEquipmentMetaToggles() { - using (_ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _selection.State!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _selection.State!)); } Im.Line.Same(); - using (_ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _selection.State!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _selection.State!)); } Im.Line.Same(); - using (_ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _selection.State!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _selection.State!)); } Im.Line.Same(); - using (_ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.EarState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.EarState, _stateManager, _selection.State!)); } } private void DrawMonsterPanel() { - var names = _modelChara[_state!.ModelData.ModelId]; + var names = _modelChara[_selection.State!.ModelData.ModelId]; var turnHuman = Im.Button("Turn Human"u8); Im.Separator(); using (Im.ListBox.Begin("##MonsterList"u8, Im.ContentRegion.Available with { Y = 10 * Im.Style.TextHeightWithSpacing })) @@ -324,15 +269,14 @@ public class ActorPanel if (names.Count is 0) Im.Text("Unknown Monster"u8); else - ImGuiClip.ClippedDraw(names, p => Im.Text($"{p.Name} ({p.Kind.ToName()} #{p.Id})"), - Im.Style.TextHeightWithSpacing); + Im.ListClipper.Draw(names, p => Im.Text($"{p.Name} ({p.Kind.ToName()} #{p.Id})"), Im.Style.TextHeightWithSpacing); } Im.Separator(); Im.Text("Customization Data"u8); using (Im.Font.PushMono()) { - foreach (var b in _state.ModelData.Customize) + foreach (var b in _selection.State.ModelData.Customize) { using (Im.Group()) { @@ -353,7 +297,7 @@ public class ActorPanel Im.Text("Equipment Data"u8); using (Im.Font.PushMono()) { - foreach (var b in _state.ModelData.GetEquipmentBytes()) + foreach (var b in _selection.State.ModelData.GetEquipmentBytes()) { using (Im.Group()) { @@ -371,65 +315,53 @@ public class ActorPanel } if (turnHuman) - _stateManager.TurnHuman(_state, StateSource.Manual); + _stateManager.TurnHuman(_selection.State, StateSource.Manual); } - private string _newName = string.Empty; - private DesignBase? _newDesign; - - private void SaveDesignDrawPopup() - { - if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName)) - return; - - if (_newDesign != null && _newName.Length > 0) - _designManager.CreateClone(_newDesign, _newName, true); - _newDesign = null; - _newName = string.Empty; - } private void RevertButtons() { - if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", - _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual, isFinal: true); + if (ImEx.Button("Revert to Game"u8, Vector2.Zero, "Revert the character to its actual state in the game."u8, + _selection.State!.IsLocked)) + _stateManager.ResetState(_selection.State!, StateSource.Manual, isFinal: true); Im.Line.Same(); - if (ImGuiUtil.DrawDisabledButton("Reapply Automation", Vector2.Zero, - "Reapply the current automation state for the character on top of its current state..", - !_config.EnableAutoDesigns || _state!.IsLocked)) + if (ImEx.Button("Reapply Automation"u8, Vector2.Zero, + "Reapply the current automation state for the character on top of its current state.."u8, + !_config.EnableAutoDesigns || _selection.State!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, false, out var forcedRedraw); - _stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(_selection.Actor, _selection.Identifier, _selection.State!, false, false, + out var forcedRedraw); + _stateManager.ReapplyAutomationState(_selection.Actor, forcedRedraw, false, StateSource.Manual); } Im.Line.Same(); - if (ImGuiUtil.DrawDisabledButton("Revert to Automation", Vector2.Zero, - "Try to revert the character to the state it would have using automated designs.", - !_config.EnableAutoDesigns || _state!.IsLocked)) + if (ImEx.Button("Revert to Automation"u8, Vector2.Zero, + "Try to revert the character to the state it would have using automated designs."u8, + !_config.EnableAutoDesigns || _selection.State!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, false, out var forcedRedraw); - _stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(_selection.Actor, _selection.Identifier, _selection.State!, true, false, out var forcedRedraw); + _stateManager.ReapplyAutomationState(_selection.Actor, forcedRedraw, true, StateSource.Manual); } Im.Line.Same(); - if (ImGuiUtil.DrawDisabledButton("Reapply", Vector2.Zero, - "Try to reapply the configured state if something went wrong. Should generally not be necessary.", - _state!.IsLocked)) - _stateManager.ReapplyState(_actor, false, StateSource.Manual, true); + if (ImEx.Button("Reapply"u8, Vector2.Zero, + "Try to reapply the configured state if something went wrong. Should generally not be necessary."u8, + _selection.State!.IsLocked)) + _stateManager.ReapplyState(_selection.Actor, false, StateSource.Manual, true); } private void DrawApplyToSelf() { var (id, data) = _objects.PlayerData; - if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, - "Apply the current state to your own character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", - !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) + if (!ImEx.Button("Apply to Yourself"u8, Vector2.Zero, + "Apply the current state to your own character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, + !data.Valid || id == _selection.Identifier || _selection.State!.ModelData.ModelId is not 0)) return; if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), + _stateManager.ApplyDesign(state, _converter.Convert(_selection.State!, ApplicationRules.FromModifiers(_selection.State!)), ApplySettings.Manual with { IsFinal = true }); } @@ -438,136 +370,15 @@ public class ActorPanel var (id, data) = _objects.TargetData; var tt = id.IsValid ? data.Valid - ? "Apply the current state to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." - : "The current target can not be manipulated." - : "No valid target selected."; - if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, - !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) + ? "Apply the current state to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8 + : "The current target can not be manipulated."u8 + : "No valid target selected."u8; + if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, + !data.Valid || id == _selection.Identifier || _selection.State!.ModelData.ModelId is not 0)) return; if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), + _stateManager.ApplyDesign(state, _converter.Convert(_selection.State!, ApplicationRules.FromModifiers(_selection.State!)), ApplySettings.Manual with { IsFinal = true }); } - - - private sealed class SetFromClipboardButton(ActorPanel panel) - : HeaderDrawer.Button - { - protected override string Description - => "Try to apply a design from your clipboard.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Clipboard; - - public override bool Visible - => panel._state != null; - - protected override bool Disabled - => panel._state?.IsLocked ?? true; - - protected override void OnClick() - { - try - { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var text = ImGui.GetClipboardText(); - var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { IsFinal = true }); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._identifier}.", - $"Could not apply clipboard to design {panel._identifier.Incognito(null)}", NotificationType.Error, false); - } - } - } - - private sealed class ExportToClipboardButton(ActorPanel panel) : HeaderDrawer.Button - { - protected override string Description - => "Copy the current design to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Copy; - - public override bool Visible - => panel._state?.ModelData.ModelId == 0; - - protected override void OnClick() - { - try - { - var text = panel._converter.ShareBase64(panel._state!, ApplicationRules.FromModifiers(panel._state!)); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._identifier} data to clipboard.", - $"Could not copy data from design {panel._identifier.Incognito(null)} to clipboard", NotificationType.Error); - } - } - } - - private sealed class SaveAsDesignButton(ActorPanel panel) : HeaderDrawer.Button - { - protected override string Description - => "Save the current state as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Save; - - public override bool Visible - => panel._state?.ModelData.ModelId == 0; - - protected override void OnClick() - { - ImGui.OpenPopup("Save as Design"); - panel._newName = panel._state!.Identifier.ToName(); - panel._newDesign = panel._converter.Convert(panel._state, ApplicationRules.FromModifiers(panel._state)); - } - } - - private sealed class UndoButton(ActorPanel panel) : HeaderDrawer.Button - { - protected override string Description - => "Undo the last change."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Undo; - - public override bool Visible - => panel._state != null; - - protected override bool Disabled - => (panel._state?.IsLocked ?? true) || !panel._editorHistory.CanUndo(panel._state); - - protected override void OnClick() - => panel._editorHistory.Undo(panel._state!); - } - - private sealed class LockedButton(ActorPanel panel) : HeaderDrawer.Button - { - protected override string Description - => "The current state of this actor is locked by external tools."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Lock; - - public override bool Visible - => panel._state?.IsLocked ?? false; - - protected override bool Disabled - => true; - - protected override Rgba32 BorderColor - => ColorId.ActorUnavailable.Value(); - - protected override Rgba32 TextColor - => ColorId.ActorUnavailable.Value(); - - protected override void OnClick() - { } - } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelection.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelection.cs new file mode 100644 index 0000000..82f6388 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelection.cs @@ -0,0 +1,64 @@ +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Plugin.Services; +using Glamourer.State; +using ImSharp; +using Luna; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class ActorSelection(StateManager manager, ActorObjectManager objects, ICondition conditions) : IUiService +{ + private static readonly StringU8 NoSelection = new("No Selection"u8); + + public ActorIdentifier Identifier { get; private set; } + public ActorState? State { get; private set; } + public StringU8 ActorName { get; private set; } = NoSelection; + public StringU8 IncognitoName { get; private set; } = NoSelection; + public ActorData Data { get; private set; } = ActorData.Invalid; + public Actor Actor { get; private set; } = Actor.Null; + public bool LockedRedraw { get; private set; } = false; + + public void Select(ActorIdentifier identifier, ActorData data) + { + Identifier = identifier.CreatePermanent(); + if (Identifier.IsValid) + { + ActorName = new StringU8(data.Label); + IncognitoName = new StringU8(Identifier.Incognito(data.Label)); + State = data.Valid && manager.GetOrCreate(Identifier, data.Objects[0], out var s) ? s : null; + } + else + { + ActorName = NoSelection; + IncognitoName = NoSelection; + } + } + + public void Update() + { + if (Identifier.IsValid) + { + if (objects.TryGetValue(Identifier, out var data)) + { + Data = data; + Actor = Data.Objects[0]; + } + else + { + Data = ActorData.Invalid; + Actor = Actor.Null; + } + + LockedRedraw = Identifier.Type is IdentifierType.Special || objects.IsInLobby || conditions[ConditionFlag.OccupiedInCutSceneEvent]; + } + else + { + Data = ActorData.Invalid; + Actor = Actor.Null; + LockedRedraw = false; + } + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 4a9d7d5..c9ed4b8 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,135 +1,68 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using Glamourer.Interop.Penumbra; using ImSharp; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Raii; -using OtterGui.Text; +using Luna; using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorSelector(ActorObjectManager objects, ActorManager actors, EphemeralConfig config) +public readonly struct ActorCacheItem(ActorIdentifier identifier, ActorData data) { - private ActorIdentifier _identifier = ActorIdentifier.Invalid; + public readonly ActorIdentifier Identifier = identifier; + public readonly ActorData Data = data; + public readonly StringPair DisplayText = new(data.Label); + public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label)); +} - public bool IncognitoMode +public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, EphemeralConfig config) : IPanel +{ + public ReadOnlySpan Id + => "ActorSelector"u8; + + public void Draw() { - get => config.IncognitoMode; - set + Im.Cursor.Y += Im.Style.FramePadding.Y; + var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new ActorSelectorCache(objects, filter, penumbra)); + using var clip = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing); + foreach (var actor in clip.Iterate(cache)) { - config.IncognitoMode = value; - config.Save(); + Im.Cursor.X += Im.Style.FramePadding.X; + var selected = actor.Identifier.Equals(selection.Identifier); + if (Im.Selectable(config.IncognitoMode ? actor.IncognitoText : actor.DisplayText.Utf8, selected) && !selected) + selection.Select(actor.Identifier, actor.Data); } } - private LowerString _actorFilter = LowerString.Empty; - private Vector2 _defaultItemSpacing; - private WorldId _world; - private float _width; - - public (ActorIdentifier Identifier, ActorData Data) Selection - => objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); - - public bool HasSelection - => _identifier.IsValid; - - public void Draw(float width) + private sealed class ActorSelectorCache : BasicFilterCache { - _width = width; - using var group = Im.Group(); - _defaultItemSpacing = Im.Style.ItemSpacing; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .Push(ImGuiStyleVar.FrameRounding, 0); - ImGui.SetNextItemWidth(_width); - LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64); - if (ImGui.IsItemHovered()) + private readonly ActorObjectManager _objects; + private readonly PenumbraService _penumbra; + + public ActorSelectorCache(ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra) + : base(filter) { - using var tt = ImUtf8.Tooltip(); - ImUtf8.Text("Filter for names containing the input."u8); - Im.Dummy(new Vector2(0, Im.Style.TextHeight / 2)); - ImUtf8.Text("Special filters are:"u8); - var color = ColorId.HeaderButtons.Value(); - Im.Text("

"u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only player characters."u8); - - Im.Text(""u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only owned game objects."u8); - - Im.Text(""u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only NPCs."u8); - - Im.Text(""u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only retainers."u8); - - Im.Text(""u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only special screen characters."u8); - - Im.Text(""u8, color); - ImGui.SameLine(0, 0); - ImUtf8.Text(": show only players from your world."u8); + _objects = objects; + _penumbra = penumbra; + _objects.Objects.OnUpdateRequired += OnUpdateRequired; + _penumbra.CreatedCharacterBase += OnCreatedCharacterBase; } - DrawSelector(); - DrawSelectionButtons(); - } + ///

Update actors when models are created since visible models are required. + private void OnCreatedCharacterBase(nint _1, Guid _2, IntPtr _3) + => Dirty |= IManagedCache.DirtyFlags.Custom; - private void DrawSelector() - { - using var child = ImUtf8.Child("##Selector"u8, new Vector2(_width, -Im.Style.FrameHeight), true); - if (!child) - return; + /// Update actors when anything changes in the object table. + private void OnUpdateRequired() + => Dirty |= IManagedCache.DirtyFlags.Custom; - _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); - var skips = ImGuiClip.GetNecessarySkips(Im.Style.TextHeight); - var remainder = ImGuiClip.FilteredClippedDraw(objects.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, - DrawSelectable); - ImGuiClip.DrawEndDummy(remainder, Im.Style.TextHeight); - } - - private bool CheckFilter(KeyValuePair pair) - => _actorFilter.Lower switch + protected override void Dispose(bool disposing) { - "" => true, - "

" => pair.Key.Type is IdentifierType.Player, - "" => pair.Key.Type is IdentifierType.Owned, - "" => pair.Key.Type is IdentifierType.Npc, - "" => pair.Key.Type is IdentifierType.Retainer, - "" => pair.Key.Type is IdentifierType.Special, - "" => pair.Key.Type is IdentifierType.Player && pair.Key.HomeWorld == _world, - _ => _actorFilter.IsContained(pair.Value.Label), - }; + base.Dispose(disposing); + _objects.Objects.OnUpdateRequired -= OnUpdateRequired; + _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; + } - private void DrawSelectable(KeyValuePair pair) - { - var equals = pair.Key.Equals(_identifier); - if (ImUtf8.Selectable(IncognitoMode ? pair.Key.Incognito(pair.Value.Label) : pair.Value.Label, equals) && !equals) - _identifier = pair.Key.CreatePermanent(); - } - - private void DrawSelectionButtons() - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .Push(ImGuiStyleVar.FrameRounding, 0); - var buttonWidth = new Vector2(_width / 2, 0); - - if (ImUtf8.IconButton(FontAwesomeIcon.UserCircle, "Select the local player character."u8, buttonWidth, !objects.Player)) - _identifier = objects.Player.GetIdentifier(actors); - - Im.Line.Same(); - var (id, data) = objects.TargetData; - var tt = data.Valid ? $"Select the current target {id} in the list." : - id.IsValid ? $"The target {id} is not in the list." : "No target selected."; - if (ImUtf8.IconButton(FontAwesomeIcon.HandPointer, tt, buttonWidth, objects.IsInGPose || !data.Valid)) - _identifier = id; + protected override IEnumerable GetItems() + => _objects.Where(p => p.Value.Objects.Any(a => a.Model)).Select(a => new ActorCacheItem(a.Key, a.Value)); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 84b25cb..f6f0c73 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -3,18 +3,35 @@ using Luna; namespace Glamourer.Gui.Tabs.ActorTab; -public sealed class ActorTab(ActorSelector selector, ActorPanel panel) : ITab +public sealed class ActorTab : TwoPanelLayout, ITab { - public ReadOnlySpan Label - => "Actors"u8; + private readonly ActorSelection _selection; - public MainTabType Identifier - => MainTabType.Actors; + public ActorTab(ActorSelector selector, ActorPanel panel, ActorFilter filter, SelectPlayerButton selectPlayer, + SelectTargetButton selectTarget, ActorsHeader header, ActorSelection selection) + { + _selection = selection; + LeftPanel = selector; + LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); + var footer = new ButtonFooter(); + footer.Buttons.AddButton(selectPlayer, 100); + footer.Buttons.AddButton(selectTarget, 0); + LeftFooter = footer; + + RightHeader = header; + RightPanel = panel; + RightFooter = NopHeaderFooter.Instance; + } + + public override ReadOnlySpan Label + => "Actors"u8; public void DrawContent() { - selector.Draw(200 * Im.Style.GlobalScale); - Im.Line.Same(); - panel.Draw(); + _selection.Update(); + Draw(TwoPanelWidth.IndeterminateRelative); } + + public MainTabType Identifier + => MainTabType.Actors; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs new file mode 100644 index 0000000..d3bb802 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs @@ -0,0 +1,38 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class ActorsHeader : SplitButtonHeader +{ + private readonly ActorSelection _selection; + private readonly EphemeralConfig _config; + + public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save, + UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, EphemeralConfig config) + { + _selection = selection; + _config = config; + LeftButtons.AddButton(setFromClipboard, 100); + LeftButtons.AddButton(exportToClipboard, 90); + LeftButtons.AddButton(save, 80); + LeftButtons.AddButton(undo, 70); + + RightButtons.AddButton(locked, 100); + RightButtons.AddButton(incognito, 90); + } + + public override ReadOnlySpan Text + => _config.IncognitoMode ? _selection.IncognitoName : _selection.ActorName; + + public override ColorParameter TextColor + => _selection.State is null ? ColorParameter.Default : + _selection.Data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value(); + + public override void Draw(Vector2 size) + { + var color = ColorId.HeaderButtons.Value(); + using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color); + base.Draw(size with { Y = Im.Style.FrameHeight }); + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/ExportToClipboardButton.cs b/Glamourer/Gui/Tabs/ActorTab/ExportToClipboardButton.cs new file mode 100644 index 0000000..4eec14f --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/ExportToClipboardButton.cs @@ -0,0 +1,41 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class ExportToClipboardButton(ActorSelection selection, DesignConverter converter) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => LunaStyle.ToClipboardIcon; + + public override bool IsVisible + => selection.State?.ModelData.ModelId is 0; + + public override bool Enabled + => !(selection.State?.IsLocked ?? true); + + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text( + "Copy the current design to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."u8); + + public override void OnClick() + { + try + { + var text = converter.ShareBase64(selection.State!, ApplicationRules.FromModifiers(selection.State!)); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {selection.Identifier} data to clipboard.", + $"Could not copy data from design {selection.Identifier.Incognito(null)} to clipboard", NotificationType.Error); + } + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/LockedButton.cs b/Glamourer/Gui/Tabs/ActorTab/LockedButton.cs new file mode 100644 index 0000000..ac9d7d7 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/LockedButton.cs @@ -0,0 +1,32 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class LockedButton(ActorSelection selection) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => LunaStyle.LockedIcon; + + public override bool IsVisible + => selection.State?.IsLocked ?? false; + + public override bool Enabled + => true; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("The current state of this actor is locked by external tools."u8); + + protected override void PreDraw() + { + var color = ColorId.ActorAvailable.Value(); + ImGuiColor.Border.Push(color) + .Push(ImGuiColor.Text, color); + } + + protected override void PostDraw() + => Im.ColorDisposable.PopUnsafe(2); +} diff --git a/Glamourer/Gui/Tabs/ActorTab/SaveAsDesignButton.cs b/Glamourer/Gui/Tabs/ActorTab/SaveAsDesignButton.cs new file mode 100644 index 0000000..036c948 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/SaveAsDesignButton.cs @@ -0,0 +1,47 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class SaveAsDesignButton(ActorSelection selection, DesignConverter converter, DesignManager designManager) + : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => LunaStyle.SaveIcon; + + public override bool IsVisible + => selection.State?.ModelData.ModelId is 0; + + public override bool Enabled + => !(selection.State?.IsLocked ?? true); + + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text( + "Save the current state as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."u8); + + private string _newName = string.Empty; + private DesignBase? _newDesign; + + public override void OnClick() + { + Im.Popup.Open("Save as Design"u8); + _newName = selection.State!.Identifier.ToName(); + _newDesign = converter.Convert(selection.State, ApplicationRules.FromModifiers(selection.State)); + } + + protected override void PostDraw() + { + if (!InputPopup.Open("Save as Design"u8, _newName, out var newName, "Enter Design Name..."u8)) + return; + + if (_newDesign is not null && newName.Length > 0) + designManager.CreateClone(_newDesign, newName, true); + _newDesign = null; + _newName = string.Empty; + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/SelectPlayerButton.cs b/Glamourer/Gui/Tabs/ActorTab/SelectPlayerButton.cs new file mode 100644 index 0000000..f136fcf --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/SelectPlayerButton.cs @@ -0,0 +1,27 @@ +using Dalamud.Interface; +using ImSharp; +using Luna; +using Penumbra.GameData.Interop; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class SelectPlayerButton(ActorObjectManager objects, ActorSelection selection) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => FontAwesomeIcon.UserCircle; + + public override void DrawTooltip() + => Im.Text("Select the local player character."u8); + + public override bool HasTooltip + => true; + + public override bool Enabled + => objects.Player; + + public override void OnClick() + { + var (identifier, data) = objects.PlayerData; + selection.Select(identifier, data); + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/SelectTargetButton.cs b/Glamourer/Gui/Tabs/ActorTab/SelectTargetButton.cs new file mode 100644 index 0000000..c4cd087 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/SelectTargetButton.cs @@ -0,0 +1,35 @@ +using Dalamud.Interface; +using ImSharp; +using Luna; +using Penumbra.GameData.Interop; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class SelectTargetButton(ActorObjectManager objects, ActorSelection selection) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => FontAwesomeIcon.HandPointer; + + public override void DrawTooltip() + { + var (id, data) = objects.TargetData; + if (data.Valid) + Im.Text($"Select the current target {id} in the list."); + else if (id.IsValid) + Im.Text($"The target {id} is not in the list."); + else + Im.Text("No target selected."u8); + } + + public override bool HasTooltip + => true; + + public override bool Enabled + => objects.IsInGPose || !objects.TargetData.Data.Valid; + + public override void OnClick() + { + var (identifier, data) = objects.TargetData; + selection.Select(identifier, data); + } +} diff --git a/Glamourer/Gui/Tabs/ActorTab/SetFromClipboardButton.cs b/Glamourer/Gui/Tabs/ActorTab/SetFromClipboardButton.cs new file mode 100644 index 0000000..6084e49 --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/SetFromClipboardButton.cs @@ -0,0 +1,44 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using Glamourer.State; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class SetFromClipboardButton(ActorSelection selection, DesignConverter converter, StateManager stateManager) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => LunaStyle.FromClipboardIcon; + + public override bool IsVisible + => selection.State is not null; + + public override bool Enabled + => !(selection.State?.IsLocked ?? true); + + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Try to apply a design from your clipboard.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8); + + public override void OnClick() + { + try + { + var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var text = ImGui.GetClipboardText(); + var design = converter.FromBase64(text, applyCustomize, applyGear, out _) + ?? throw new Exception("The clipboard did not contain valid data."); + stateManager.ApplyDesign(selection.State!, design, ApplySettings.ManualWithLinks with { IsFinal = true }); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {selection.Identifier}.", + $"Could not apply clipboard to design {selection.Identifier.Incognito(null)}", NotificationType.Error, false); + } + } +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/ActorTab/UndoButton.cs b/Glamourer/Gui/Tabs/ActorTab/UndoButton.cs new file mode 100644 index 0000000..35cb22a --- /dev/null +++ b/Glamourer/Gui/Tabs/ActorTab/UndoButton.cs @@ -0,0 +1,26 @@ +using Glamourer.Designs.History; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.ActorTab; + +public sealed class UndoButton(ActorSelection selection, EditorHistory editorHistory) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => LunaStyle.UndoIcon; + + public override bool IsVisible + => selection.State is not null; + + public override bool Enabled + => !(selection.State?.IsLocked ?? true) && editorHistory.CanUndo(selection.State); + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Undo the last change."u8); + + public override void OnClick() + => editorHistory.Undo(selection.State!); +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 5a7b3dc..4561f40 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -27,7 +27,7 @@ public class SetPanel( RandomRestrictionDrawer randomDrawer) { private readonly JobGroupCombo _jobGroupCombo = new(manager, jobs, Glamourer.Log); - private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(config)]; + private readonly HeaderDrawer.Button[] _rightButtons = []; // [new IncognitoButton(config)]; private string? _tempName; private int _dragIndex = -1; @@ -96,7 +96,7 @@ public class SetPanel( Im.Dummy(Vector2.Zero); var name = _tempName ?? Selection.Name; - var flags = selector.IncognitoMode ? InputTextFlags.ReadOnly | InputTextFlags.Password : InputTextFlags.None; + var flags = config.Ephemeral.IncognitoMode ? InputTextFlags.ReadOnly | InputTextFlags.Password : InputTextFlags.None; Im.Item.SetNextWidthScaled(330); if (Im.Input.Text("Rename Set##Name"u8, ref name, StringU8.Empty, flags)) _tempName = name; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 34fde5b..65d91a0 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -25,16 +25,6 @@ public class SetSelector : IDisposable public AutoDesignSet? Selection { get; private set; } public int SelectionIndex { get; private set; } = -1; - public bool IncognitoMode - { - get => _config.Ephemeral.IncognitoMode; - set - { - _config.Ephemeral.IncognitoMode = value; - _config.Ephemeral.Save(); - } - } - private int _dragIndex = -1; private Action? _endAction; @@ -58,7 +48,7 @@ public class SetSelector : IDisposable => GetSetName(Selection, SelectionIndex); public string GetSetName(AutoDesignSet? set, int index) - => set == null ? "No Selection" : IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name; + => set == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name; private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data) { @@ -200,7 +190,7 @@ public class SetSelector : IDisposable DrawDragDrop(pair.Set, pair.Index); var text = pair.Set.Identifiers[0].ToString(); - if (IncognitoMode) + if (_config.Ephemeral.IncognitoMode) text = pair.Set.Identifiers[0].Incognito(text); var textSize = ImGui.CalcTextSize(text); var textColor = pair.Set.Identifiers.Any(_objects.ContainsKey) ? ColorId.AutomationActorAvailable : ColorId.AutomationActorUnavailable; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 00b0723..ca6974a 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -32,16 +32,6 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _config.Ephemeral.IncognitoMode; - set - { - _config.Ephemeral.IncognitoMode = value; - _config.Ephemeral.Save(); - } - } - public new DesignFileSystem.Leaf? SelectedLeaf => base.SelectedLeaf; @@ -175,7 +165,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector.Leaf leaf, in DesignState state, bool selected) { var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; - var name = IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text; + var name = _config.Ephemeral.IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text; using var color = ImGuiColor.Text.Push(state.Color); using var _ = ImUtf8.TreeNode(name, flag); if (_config.AllowDoubleClickToApply && ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index a334172..4c22df1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -103,7 +103,7 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(color)) { Im.Cursor.FrameAlign(); - Im.Selectable(selector.IncognitoMode ? selector.Selected!.Incognito : selector.Selected!.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? selector.Selected!.Incognito : selector.Selected!.Name.Text); } Im.Tooltip.OnHover("Current Design"u8); @@ -133,7 +133,7 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(colorManager.GetColor(design))) { Im.Cursor.FrameAlign(); - Im.Selectable(selector.IncognitoMode ? design.Incognito : design.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? design.Incognito : design.Name.Text); } DrawDragDrop(design, order, i); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 464dd19..7da344b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -91,7 +91,7 @@ public class DesignPanel _rightButtons = [ new LockButton(this), - new IncognitoButton(_config), + //new IncognitoButton(_config), ]; } @@ -99,7 +99,7 @@ public class DesignPanel => HeaderDrawer.Draw(SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); private string SelectionName - => _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text; + => _selector.Selected == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text; private void DrawEquipment() { diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 8ab07a6..17d6c9f 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -18,7 +18,7 @@ public class MultiDesignPanel( Configuration config) { private readonly Button[] _leftButtons = []; - private readonly Button[] _rightButtons = [new IncognitoButton(config)]; + private readonly Button[] _rightButtons = []; //[new IncognitoButton(config)]; private readonly DesignColorCombo _colorCombo = new(colors, true); diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index 255f83e..6025b0c 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -44,39 +44,6 @@ public static class HeaderDrawer } } - public sealed class IncognitoButton(Configuration config) : Button - { - protected override string Description - { - get - { - var hold = config.IncognitoModifier.IsActive(); - return (config.Ephemeral.IncognitoMode, hold) - switch - { - (true, true) => "Toggle incognito mode off.", - (false, true) => "Toggle incognito mode on.", - (true, false) => $"Toggle incognito mode off.\n\nHold {config.IncognitoModifier} while clicking to toggle.", - (false, false) => $"Toggle incognito mode on.\n\nHold {config.IncognitoModifier} while clicking to toggle.", - }; - } - } - - protected override FontAwesomeIcon Icon - => config.Ephemeral.IncognitoMode - ? FontAwesomeIcon.EyeSlash - : FontAwesomeIcon.Eye; - - protected override void OnClick() - { - if (!config.IncognitoModifier.IsActive()) - return; - - config.Ephemeral.IncognitoMode = !config.Ephemeral.IncognitoMode; - config.Ephemeral.Save(); - } - } - public static void Draw(string text, uint textColor, uint frameColor, Button[] leftButtons, Button[] rightButtons) { var width = Im.Style.FrameHeightWithSpacing; @@ -110,4 +77,4 @@ public static class HeaderDrawer button.Draw(width); } } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/IncognitoButton.cs b/Glamourer/Gui/Tabs/IncognitoButton.cs new file mode 100644 index 0000000..427d388 --- /dev/null +++ b/Glamourer/Gui/Tabs/IncognitoButton.cs @@ -0,0 +1,29 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs; + +public sealed class IncognitoButton(Configuration config) : BaseIconButton, IUiService +{ + public override AwesomeIcon Icon + => config.Ephemeral.IncognitoMode + ? LunaStyle.IncognitoOn + : LunaStyle.IncognitoOff; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + { + var hold = config.IncognitoModifier.IsActive(); + Im.Text(config.Ephemeral.IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8); + if (!hold) + Im.Text($"\nHold {config.IncognitoModifier} while clicking to toggle."); + } + + public override void OnClick() + { + if (config.IncognitoModifier.IsActive()) + config.Ephemeral.IncognitoMode = !config.Ephemeral.IncognitoMode; + } +} diff --git a/Luna b/Luna index 6235cc8..ab62092 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit 6235cc8b4d0196c2545bf39834265fb4e0939b08 +Subproject commit ab620925015405133533c8811793f64082804d59 diff --git a/Penumbra.GameData b/Penumbra.GameData index bdbf134..ab77b93 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bdbf134934238c7af53aa8625467bd5ad580d6f6 +Subproject commit ab77b934eecc097ca1bf0d6243c8cd0dd4ffa155