From 6f219b092935c0abda50eaab4cde8d878e68a259 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Feb 2026 14:50:25 +0100 Subject: [PATCH 1/8] Current design state. --- .../DesignTab/DesignFileSystemSelector.cs | 5 +- Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs | 21 + Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 476 +++++++++--------- .../Gui/Tabs/DesignTab/DesignSelection.cs | 12 + Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 138 ++--- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 377 +++++++------- Luna | 2 +- 7 files changed, 524 insertions(+), 507 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index ca6974a..2eb220a 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -18,7 +18,7 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignFileSystemSelector : FileSystemSelector +public sealed class DesignFileSystemSelector : FileSystemSelector, IPanel { private readonly DesignManager _designManager; private readonly DesignChanged _event; @@ -399,4 +399,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Id + => "DesignSelector"u8; } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs new file mode 100644 index 0000000..b8baa4d --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs @@ -0,0 +1,21 @@ +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignHeader : SplitButtonHeader +{ + public DesignHeader(DesignSelection selection, IncognitoButton incognito) + { + RightButtons.AddButton(incognito, 50); + RightButtons.AddButton(new LockedButton(selection), 100); + } + + private sealed class LockedButton(DesignSelection selection) : BaseIconButton + { + public override bool IsVisible + => selection.Design is not null; + + public override AwesomeIcon Icon + => selection.Design!.WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 7da344b..082cb71 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -24,10 +24,10 @@ using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignPanel +public class DesignPanel : IPanel { private readonly FileDialogManager _fileDialog = new(); - private readonly DesignFileSystemSelector _selector; + private readonly DesignSelection _selection; private readonly CustomizationDrawer _customizationDrawer; private readonly DesignManager _manager; private readonly ActorObjectManager _objects; @@ -47,8 +47,7 @@ public class DesignPanel private readonly Button[] _rightButtons; - public DesignPanel(DesignFileSystemSelector selector, - CustomizationDrawer customizationDrawer, + public DesignPanel(CustomizationDrawer customizationDrawer, DesignManager manager, ActorObjectManager objects, StateManager state, @@ -62,9 +61,8 @@ public class DesignPanel CustomizeParameterDrawer parameterDrawer, DesignLinkDrawer designLinkDrawer, MaterialDrawer materials, - EditorHistory history) + EditorHistory history, DesignSelection selection) { - _selector = selector; _customizationDrawer = customizationDrawer; _manager = manager; _objects = objects; @@ -80,6 +78,7 @@ public class DesignPanel _designLinkDrawer = designLinkDrawer; _materials = materials; _history = history; + _selection = selection; _leftButtons = [ new SetFromClipboardButton(this), @@ -99,7 +98,7 @@ public class DesignPanel => HeaderDrawer.Draw(SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); private string SelectionName - => _selector.Selected == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text; + => _selection.Design == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selection.Design.Incognito : _selection.Design.Name.Text; private void DrawEquipment() { @@ -109,22 +108,22 @@ public class DesignPanel _equipmentDrawer.Prepare(); - var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected()); + var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selection.Design!.WriteProtected()); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); + var data = EquipDrawData.FromDesign(_manager, _selection.Design!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _manager.ChangeStains(_selector.Selected, slot, newAllStain); + _manager.ChangeStains(_selection.Design, slot, newAllStain); } - var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); - var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand); + var mainhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.MainHand); + var offhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, true); foreach (var slot in BonusExtensions.AllFlags) { - var data = BonusDrawData.FromDesign(_manager, _selector.Selected!, slot); + var data = BonusDrawData.FromDesign(_manager, _selection.Design!, slot); _equipmentDrawer.DrawBonusItem(data); } @@ -138,28 +137,28 @@ public class DesignPanel { using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selection.Design!)); } Im.Line.Same(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selection.Design!)); } Im.Line.Same(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selection.Design!)); } Im.Line.Same(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selection.Design!)); } } @@ -169,25 +168,25 @@ public class DesignPanel return; var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); - using var h = Im.Tree.HeaderId(_selector.Selected!.DesignData.ModelId is 0 + using var h = Im.Tree.HeaderId(_selection.Design!.DesignData.ModelId is 0 ? "Customization" - : $"Customization (Model Id #{_selector.Selected!.DesignData.ModelId})###Customization", + : $"Customization (Model Id #{_selection.Design!.DesignData.ModelId})###Customization", expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); if (!h) return; - if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.Application.Customize, - _selector.Selected!.WriteProtected(), false)) + if (_customizationDrawer.Draw(_selection.Design!.DesignData.Customize, _selection.Design.Application.Customize, + _selection.Design!.WriteProtected(), false)) foreach (var idx in CustomizeIndex.Values) { var flag = idx.ToFlag(); var newValue = _customizationDrawer.ChangeApply.HasFlag(flag); - _manager.ChangeApplyCustomize(_selector.Selected, idx, newValue); + _manager.ChangeApplyCustomize(_selection.Design, idx, newValue); if (_customizationDrawer.Changed.HasFlag(flag)) - _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); + _manager.ChangeCustomize(_selection.Design, idx, _customizationDrawer.Customize[idx]); } - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selection.Design!)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); } @@ -197,7 +196,7 @@ public class DesignPanel if (!h) return; - _parameterDrawer.Draw(_manager, _selector.Selected!); + _parameterDrawer.Draw(_manager, _selection.Design!); } private void DrawMaterialValues() @@ -206,52 +205,52 @@ public class DesignPanel if (!h) return; - _materials.Draw(_selector.Selected!); + _materials.Draw(_selection.Design!); } private void DrawCustomizeApplication() { using var id = ImUtf8.PushId("Customizations"u8); - var set = _selector.Selected!.CustomizeSet; + var set = _selection.Design!.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; - var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : - (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + var flags = _selection.Design!.ApplyCustomizeExcludingBodyType == 0 ? 0 : + (_selection.Design!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) { var newFlags = flags == 3; - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); + _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, newFlags); + _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, newFlags); foreach (var index in CustomizationExtensions.AllBasic) - _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); + _manager.ChangeApplyCustomize(_selection.Design!, index, newFlags); } - var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); + var applyClan = _selection.Design!.DoApplyCustomize(CustomizeIndex.Clan); if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); + _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, applyClan); - var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); + var applyGender = _selection.Design!.DoApplyCustomize(CustomizeIndex.Gender); if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); + _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, applyGender); foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) { - var apply = _selector.Selected!.DoApplyCustomize(index); + var apply = _selection.Design!.DoApplyCustomize(index); if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) - _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); + _manager.ChangeApplyCustomize(_selection.Design!, index, apply); } } private void DrawCrestApplication() { using var id = ImUtf8.PushId("Crests"u8); - var flags = (uint)_selector.Selected!.Application.Crest; + var flags = (uint)_selection.Design!.Application.Crest; var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { - var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selection.Design!.DoApplyCrest(flag); if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) - _manager.ChangeApplyCrest(_selector.Selected!, flag, apply); + _manager.ChangeApplyCrest(_selection.Design!, flag, apply); } } @@ -261,7 +260,7 @@ public class DesignPanel if (!h) return; - using var disabled = Im.Disabled(_selector.Selected!.WriteProtected()); + using var disabled = Im.Disabled(_selection.Design!.WriteProtected()); DrawAllButtons(); @@ -279,22 +278,22 @@ public class DesignPanel { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(allFlags & _selector.Selected!.Application.Equip); + var flags = (uint)(allFlags & _selection.Design!.Application.Equip); using var id = ImUtf8.PushId(label); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { - var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot); + var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selection.Design!.DoApplyStain(slot); if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) - _manager.ChangeApplyStains(_selector.Selected!, slot, apply); + _manager.ChangeApplyStains(_selection.Design!, slot, apply); } else foreach (var slot in slots) { - var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot); + var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selection.Design!.DoApplyEquip(slot); if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) - _manager.ChangeApplyItem(_selector.Selected!, slot, apply); + _manager.ChangeApplyItem(_selection.Design!, slot, apply); } } @@ -379,8 +378,8 @@ public class DesignPanel size, !enabled)) { - _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); + _manager.ChangeApplyMulti(_selection.Design!, true, true, true, false, true, true, false, true); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, false); } if (!enabled) @@ -390,7 +389,7 @@ public class DesignPanel if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, size, !enabled)) - _manager.ChangeApplyMulti(_selector.Selected!, null, null, null, false, null, null, false, null); + _manager.ChangeApplyMulti(_selection.Design!, null, null, null, false, null, null, false, null); if (!enabled) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); @@ -398,18 +397,18 @@ public class DesignPanel if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, + _manager.ChangeApplyMulti(_selection.Design!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.EarState, equip.Value); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.HatState, equip.Value); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.VisorState, equip.Value); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.WeaponState, equip.Value); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.EarState, equip.Value); } if (customize.HasValue) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, customize.Value); + _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, customize.Value); } private static readonly IReadOnlyList MetaLabels = @@ -425,14 +424,14 @@ public class DesignPanel { using var id = ImUtf8.PushId("Meta"); const uint all = (uint)MetaExtensions.All; - var flags = (uint)_selector.Selected!.Application.Meta; + var flags = (uint)_selection.Design!.Application.Meta; var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) { - var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); + var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selection.Design!.DoApplyMeta(index); if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, index, apply); + _manager.ChangeApplyMeta(_selection.Design!, index, apply); } } @@ -444,13 +443,13 @@ public class DesignPanel private void DrawBonusSlotApplication() { using var id = ImUtf8.PushId("Bonus"u8); - var flags = _selector.Selected!.Application.BonusItem; + var flags = _selection.Design!.Application.BonusItem; var bigChange = BonusExtensions.AllFlags.Count > 1 && ImUtf8.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All); foreach (var (index, label) in BonusExtensions.AllFlags.Zip(BonusSlotLabels)) { - var apply = bigChange ? flags.HasFlag(index) : _selector.Selected!.DoApplyBonusItem(index); + var apply = bigChange ? flags.HasFlag(index) : _selection.Design!.DoApplyBonusItem(index); if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyBonusItem(_selector.Selected!, index, apply); + _manager.ChangeApplyBonusItem(_selection.Design!, index, apply); } } @@ -458,20 +457,23 @@ public class DesignPanel private void DrawParameterApplication() { using var id = Im.Id.Push("Parameter"u8); - var flags = (ulong)_selector.Selected!.Application.Parameters; + var flags = (ulong)_selection.Design!.Application.Parameters; var bigChange = Im.Checkbox("Apply All Customize Parameters"u8, ref flags, (ulong)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { - var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); + var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selection.Design!.DoApplyParameter(flag); if (Im.Checkbox($"Apply {flag.ToNameU8()}", ref apply) || bigChange) - _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); + _manager.ChangeApplyParameter(_selection.Design!, flag, apply); } } + public ReadOnlySpan Id + => "Designs"u8; + public void Draw() { using var group = ImUtf8.Group(); - if (_selector.SelectedPaths.Count > 1) + if (_selection.DesignPaths.Count > 1) { _multiDesignPanel.Draw(); } @@ -480,22 +482,22 @@ public class DesignPanel DrawHeader(); DrawPanel(); - if (_selector.Selected == null || _selector.Selected.WriteProtected()) + if (_selection.Design == null || _selection.Design.WriteProtected()) return; if (_importService.CreateDatTarget(out var dat)) { - _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); - _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); + _manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); + _manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); foreach (var idx in CustomizationExtensions.AllBasic) - _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); + _manager.ChangeCustomize(_selection.Design!, idx, dat.Customize[idx]); Glamourer.Messager.NotificationMessage( - $"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); + $"Applied games .dat file {dat.Description} customizations to {_selection.Design.Name}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { - _manager.ApplyDesign(_selector.Selected!, designBase); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", + _manager.ApplyDesign(_selection.Design!, designBase); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selection.Design.Name}.", NotificationType.Success, false); } } @@ -506,12 +508,12 @@ public class DesignPanel private void DrawPanel() { using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available); - if (!table || _selector.Selected is null) + if (!table || _selection.Design is null) return; ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); - if (_selector.Selected is null) + if (_selection.Design is null) return; Im.Dummy(Vector2.Zero); @@ -550,8 +552,8 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); + using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); + _state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -568,14 +570,14 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); + using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); + _state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } private void DrawSaveToDat() { - var verified = _importService.Verify(_selector.Selected!.DesignData.Customize, out _); + var verified = _importService.Verify(_selection.Design!.DesignData.Customize, out _); var tt = verified ? "Export the currently configured customizations of this design to a character creation data file." : "The current design contains customizations that can not be applied during character creation."; @@ -585,8 +587,8 @@ public class DesignPanel if (ImGuiUtil.DrawDisabledButton("Export to Dat", Vector2.Zero, tt, !verified)) _fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) => { - if (v && _selector.Selected != null) - _importService.SaveDesignAsDat(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); + if (v && _selection.Design != null) + _importService.SaveDesignAsDat(path, _selection.Design!.DesignData.Customize, _selection.Design!.Name); }, startPath); _fileDialog.Draw(); @@ -595,163 +597,163 @@ public class DesignPanel private static unsafe string GetUserPath() => Framework.Instance()->UserPathString; +} - private sealed class LockButton(DesignPanel panel) : Button +private sealed class LockButton(DesignPanel panel) : Button +{ + public override bool Visible + => panel._selection.Design != null; + + protected override string Description + => panel._selection.Design!.WriteProtected() + ? "Make this design editable." + : "Write-protect this design."; + + protected override FontAwesomeIcon Icon + => panel._selection.Design!.WriteProtected() + ? FontAwesomeIcon.Lock + : FontAwesomeIcon.LockOpen; + + protected override void OnClick() + => panel._manager.SetWriteProtection(panel._selection.Design!, !panel._selection.Design!.WriteProtected()); +} + +private sealed class SetFromClipboardButton(DesignPanel panel) : Button +{ + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => panel._selection.Design?.WriteProtected() ?? true; + + protected override string Description + => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Clipboard; + + protected override void OnClick() { - public override bool Visible - => panel._selector.Selected != null; - - protected override string Description - => panel._selector.Selected!.WriteProtected() - ? "Make this design editable." - : "Write-protect this design."; - - protected override FontAwesomeIcon Icon - => panel._selector.Selected!.WriteProtected() - ? FontAwesomeIcon.Lock - : FontAwesomeIcon.LockOpen; - - protected override void OnClick() - => panel._manager.SetWriteProtection(panel._selector.Selected!, !panel._selector.Selected!.WriteProtected()); - } - - private sealed class SetFromClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override bool Disabled - => panel._selector.Selected?.WriteProtected() ?? true; - - protected override string Description - => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Clipboard; - - protected override void OnClick() + try { - try - { - var text = ImGui.GetClipboardText(); - var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._manager.ApplyDesign(panel._selector.Selected!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selector.Selected!.Name}.", - $"Could not apply clipboard to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false); - } + var text = ImGui.GetClipboardText(); + var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._manager.ApplyDesign(panel._selection.Design!, design); } - } - - private sealed class DesignUndoButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override bool Disabled - => !panel._manager.CanUndo(panel._selector.Selected) || (panel._selector.Selected?.WriteProtected() ?? true); - - protected override string Description - => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.SyncAlt; - - protected override void OnClick() + catch (Exception ex) { - try - { - panel._manager.UndoDesignChange(panel._selector.Selected!); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selector.Selected!.Name}.", - NotificationType.Error, - false); - } + Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selection.Design!.Name}.", + $"Could not apply clipboard to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); } } - - private sealed class ExportToClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null; - - protected override string Description - => "Copy the current design to your clipboard."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Copy; - - protected override void OnClick() - { - try - { - var text = panel._converter.ShareBase64(panel._selector.Selected!); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selected!.Name} data to clipboard.", - $"Could not copy data from design {panel._selector.Selected!.Identifier} to clipboard", NotificationType.Error, false); - } - } - } - - private sealed class ApplyCharacterButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selector.Selected != null && panel._objects.Player.Valid; - - protected override string Description - => "Overwrite this design with your character's current state."; - - protected override bool Disabled - => panel._selector.Selected?.WriteProtected() ?? true; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.UserEdit; - - protected override void OnClick() - { - try - { - var (player, actor) = panel._objects.PlayerData; - if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) - throw new Exception("No player state available."); - - var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._selector.Selected!.GetMaterialDataRef().Clear(); - panel._manager.ApplyDesign(panel._selector.Selected!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selector.Selected!.Name}.", - $"Could not apply player state to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false); - } - } - } - - private sealed class UndoButton(DesignPanel panel) : Button - { - protected override string Description - => "Undo the last change."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Undo; - - public override bool Visible - => panel._selector.Selected != null; - - protected override bool Disabled - => (panel._selector.Selected?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selector.Selected); - - protected override void OnClick() - => panel._history.Undo(panel._selector.Selected!); - } } + +private sealed class DesignUndoButton(DesignPanel panel) : Button +{ + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => !panel._manager.CanUndo(panel._selection.Design) || (panel._selection.Design?.WriteProtected() ?? true); + + protected override string Description + => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.SyncAlt; + + protected override void OnClick() + { + try + { + panel._manager.UndoDesignChange(panel._selection.Design!); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selection.Design!.Name}.", + NotificationType.Error, + false); + } + } +} + +private sealed class ExportToClipboardButton(DesignPanel panel) : Button +{ + public override bool Visible + => panel._selection.Design != null; + + protected override string Description + => "Copy the current design to your clipboard."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Copy; + + protected override void OnClick() + { + try + { + var text = panel._converter.ShareBase64(panel._selection.Design!); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selection.Design!.Name} data to clipboard.", + $"Could not copy data from design {panel._selection.Design!.Identifier} to clipboard", NotificationType.Error, false); + } + } +} + +private sealed class ApplyCharacterButton(DesignPanel panel) : Button +{ + public override bool Visible + => panel._selection.Design != null && panel._objects.Player.Valid; + + protected override string Description + => "Overwrite this design with your character's current state."; + + protected override bool Disabled + => panel._selection.Design?.WriteProtected() ?? true; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.UserEdit; + + protected override void OnClick() + { + try + { + var (player, actor) = panel._objects.PlayerData; + if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) + throw new Exception("No player state available."); + + var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._selection.Design!.GetMaterialDataRef().Clear(); + panel._manager.ApplyDesign(panel._selection.Design!, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selection.Design!.Name}.", + $"Could not apply player state to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); + } + } +} + +private sealed class UndoButton(DesignPanel panel) : Button +{ + protected override string Description + => "Undo the last change."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Undo; + + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => (panel._selection.Design?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selection.Design); + + protected override void OnClick() + => panel._history.Undo(panel._selection.Design!); +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs b/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs new file mode 100644 index 0000000..f070208 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs @@ -0,0 +1,12 @@ +using Glamourer.Designs; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignSelection : IUiService, IDisposable +{ + public Design? Design { get; private set; } + + public void Dispose() + { } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index a0b805e..96bdb2e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,95 +1,107 @@ -using Dalamud.Interface.Utility; -using Glamourer.Interop.Penumbra; -using Dalamud.Bindings.ImGui; +using Glamourer.Interop.Penumbra; using ImSharp; -using OtterGui.Classes; -using OtterGui.Log; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Widgets; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings, int Count)> +public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection) : ImSharp.FilterComboBase(new ModFilter()) { - public ModCombo(PenumbraService penumbra, Logger log, DesignFileSystemSelector selector) - : base(() => penumbra.GetMods(selector.Selected?.FilteredItemNames.ToArray() ?? []), MouseWheelType.None, log) - => SearchByParts = false; - - protected override string ToString((Mod Mod, ModSettings Settings, int Count) obj) - => obj.Mod.Name; - - protected override bool IsVisible(int globalIndex, LowerString filter) - => filter.IsContained(Items[globalIndex].Mod.Name) || filter.IsContained(Items[globalIndex].Mod.DirectoryName); - - protected override bool DrawSelectable(int globalIdx, bool selected) + public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count) { - using var id = ImUtf8.PushId(globalIdx); - var (mod, settings, count) = Items[globalIdx]; - bool ret; - var color = settings.Enabled + public readonly StringPair Name = new(mod.Name); + public readonly StringPair Directory = new(mod.DirectoryName); + public readonly ModSettings Settings = settings; + public readonly int Count = count; + + public readonly Vector4 Color = settings.Enabled ? count > 0 - ? ColorId.ContainsItemsEnabled.Value() - : ImGuiColor.Text.Get() + ? ColorId.ContainsItemsEnabled.Value().ToVector() + : Im.Style[ImGuiColor.Text] : count > 0 - ? ColorId.ContainsItemsDisabled.Value() - : ImGuiColor.TextDisabled.Get(); - using (ImGuiColor.Text.Push(color)) + ? ColorId.ContainsItemsDisabled.Value().ToVector() + : Im.Style[ImGuiColor.TextDisabled]; + + public readonly bool DifferingNames = string.Equals(mod.Name, mod.DirectoryName, StringComparison.CurrentCultureIgnoreCase); + } + + protected override float ItemHeight + => Im.Style.TextHeightWithSpacing; + + protected override IEnumerable GetItems() + => penumbra.GetMods(selection.Design?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count)); + + protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) + { + bool ret; + using (ImGuiColor.Text.Push(item.Color)) { - ret = ImUtf8.Selectable(mod.Name, selected); + ret = Im.Selectable(item.Name.Utf8, selected); } - if (ImGui.IsItemHovered()) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * Im.Style.GlobalScale); - using var tt = ImUtf8.Tooltip(); - var namesDifferent = mod.Name != mod.DirectoryName; - Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0)); - using (ImUtf8.Group()) - { - if (namesDifferent) - ImUtf8.Text("Directory Name"u8); - ImUtf8.Text("Enabled"u8); - ImUtf8.Text("Priority"u8); - ImUtf8.Text("Affected Design Items"u8); - DrawSettingsLeft(settings); - } - - ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale)); - using (ImUtf8.Group()) - { - if (namesDifferent) - ImUtf8.Text(mod.DirectoryName); - ImUtf8.Text($"{settings.Enabled}"); - ImUtf8.Text($"{settings.Priority}"); - ImUtf8.Text($"{count}"); - DrawSettingsRight(settings); - } - } + if (Im.Item.Hovered()) + DrawTooltip(item); return ret; } - public static void DrawSettingsLeft(ModSettings settings) + private static void DrawTooltip(in CacheItem item) + { + using var style = ImStyleSingle.PopupBorderThickness.Push(2 * Im.Style.GlobalScale); + using var tt = Im.Tooltip.Begin(); + + Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0)); + using (Im.Group()) + { + if (item.DifferingNames) + Im.Text("Directory Name"u8); + Im.Text("Enabled"u8); + Im.Text("Priority"u8); + Im.Text("Affected Design Items"u8); + DrawSettingsLeft(item.Settings); + } + + Im.Line.Same(Math.Max(Im.Item.Size.X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale)); + using (Im.Group()) + { + if (item.DifferingNames) + Im.Text(item.Directory.Utf8); + Im.Text($"{item.Settings.Enabled}"); + Im.Text($"{item.Settings.Priority}"); + Im.Text($"{item.Count}"); + DrawSettingsRight(item.Settings); + } + } + + private static void DrawSettingsLeft(in ModSettings settings) { foreach (var setting in settings.Settings) { - ImUtf8.Text(setting.Key); + Im.Text(setting.Key); for (var i = 1; i < setting.Value.Count; ++i) Im.Line.New(); } } - public static void DrawSettingsRight(ModSettings settings) + private static void DrawSettingsRight(in ModSettings settings) { foreach (var setting in settings.Settings) { if (setting.Value.Count == 0) - ImUtf8.Text(""u8); + Im.Text(""u8); else foreach (var option in setting.Value) - ImUtf8.Text(option); + Im.Text(option); } } + + protected override bool IsSelected(CacheItem item, int globalIndex) + => throw new NotImplementedException(); + + private sealed class ModFilter : TextFilterBase + { + public override bool WouldBeVisible(in CacheItem item, int globalIndex) + => base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Directory.Utf16); + + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Name.Utf16; + } } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index c7ca7c6..06588fc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,13 +1,7 @@ -using Dalamud.Interface; -using Dalamud.Interface.Utility; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Interop.Material; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Extensions; -using OtterGui.Raii; -using OtterGui.Text; -using static Glamourer.Gui.Tabs.HeaderDrawer; +using Luna; namespace Glamourer.Gui.Tabs.DesignTab; @@ -17,9 +11,6 @@ public class MultiDesignPanel( DesignColors colors, Configuration config) { - private readonly Button[] _leftButtons = []; - private readonly Button[] _rightButtons = []; //[new IncognitoButton(config)]; - private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() @@ -27,13 +18,8 @@ public class MultiDesignPanel( if (selector.SelectedPaths.Count == 0) return; - HeaderDrawer.Draw(string.Empty, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); - using var child = ImUtf8.Child("##MultiPanel"u8, default, true); - if (!child) - return; - - var width = ImGuiHelpers.ScaledVector2(145, 0); - var treeNodePos = ImGui.GetCursorPos(); + var width = ImEx.ScaledVectorX(145); + var treeNodePos = Im.Cursor.Position; _numDesigns = DrawDesignList(); DrawCounts(treeNodePos); var offset = DrawMultiTagger(width); @@ -49,18 +35,17 @@ public class MultiDesignPanel( private void DrawCounts(Vector2 treeNodePos) { - var startPos = ImGui.GetCursorPos(); + var startPos = Im.Cursor.Position; var numFolders = selector.SelectedPaths.Count - _numDesigns; - var text = (_numDesigns, numFolders) switch + Im.Cursor.Position = treeNodePos; + ImEx.TextRightAligned((_numDesigns, numFolders) switch { - (0, 0) => string.Empty, // should not happen - (> 0, 0) => $"{_numDesigns} Designs", - (0, > 0) => $"{numFolders} Folders", - _ => $"{_numDesigns} Designs, {numFolders} Folders", - }; - ImGui.SetCursorPos(treeNodePos); - ImUtf8.TextRightAligned(text); - ImGui.SetCursorPos(startPos); + (0, 0) => StringU8.Empty, // should not happen + ( > 0, 0) => $"{_numDesigns} Designs", + (0, > 0) => $"{numFolders} Folders", + _ => $"{_numDesigns} Designs, {numFolders} Folders", + }); + Im.Cursor.Position = startPos; } private void ResetCounts() @@ -127,14 +112,14 @@ public class MultiDesignPanel( { using var id = Im.Id.Push(i++); var (icon, text) = path is DesignFileSystem.Leaf l - ? (FontAwesomeIcon.FileCircleMinus, l.Value.Name.Text) - : (FontAwesomeIcon.FolderMinus, string.Empty); - ImGui.TableNextColumn(); - if (ImUtf8.IconButton(icon, "Remove from selection."u8, sizeType)) + ? (LunaStyle.RemoveFileIcon, l.Value.Name.Text) + : (LunaStyle.RemoveFolderIcon, string.Empty); + table.NextColumn(); + if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType)) selector.RemovePathFromMultiSelection(path); - ImUtf8.DrawFrameColumn(text); - ImUtf8.DrawFrameColumn(fullName); + table.DrawFrameColumn(text); + table.DrawFrameColumn(fullName); if (CountLeaves(path)) ++numDesigns; @@ -159,36 +144,32 @@ public class MultiDesignPanel( private float DrawMultiTagger(Vector2 width) { - ImUtf8.TextFrameAligned("Multi Tagger:"u8); + ImEx.TextFrameAligned("Multi Tagger:"u8); Im.Line.Same(); - var offset = ImGui.GetItemRectSize().X + Im.Style.WindowPadding.X; - ImGui.SetNextItemWidth(Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X)); - ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); + var offset = Im.Item.Size.X + Im.Style.WindowPadding.X; + Im.Item.SetNextWidth(Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X)); + Im.Input.Text("##tag"u8, ref _tag, "Tag Name..."u8); UpdateTagCache(); - var label = _addDesigns.Count > 0 - ? $"Add to {_addDesigns.Count} Designs" - : "Add"; - var tooltip = _addDesigns.Count == 0 - ? _tag.Length == 0 - ? "No tag specified." - : $"All designs selected already contain the tag \"{_tag}\"." - : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; Im.Line.Same(); - if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) + if (ImEx.Button(_addDesigns.Count > 0 + ? $"Add to {_addDesigns.Count} Designs" + : "Add"u8, width, _addDesigns.Count is 0 + ? _tag.Length is 0 + ? "No tag specified."u8 + : $"All designs selected already contain the tag \"{_tag}\"." + : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.AddTag(design, _tag); - label = _removeDesigns.Count > 0 - ? $"Remove from {_removeDesigns.Count} Designs" - : "Remove"; - tooltip = _removeDesigns.Count == 0 - ? _tag.Length == 0 - ? "No tag specified." - : $"No selected design contains the tag \"{_tag}\" locally." - : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; Im.Line.Same(); - if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) + if (ImEx.Button(_removeDesigns.Count > 0 + ? $"Remove from {_removeDesigns.Count} Designs" + : "Remove", width, _removeDesigns.Count is 0 + ? _tag.Length is 0 + ? "No tag specified."u8 + : $"No selected design contains the tag \"{_tag}\" locally." + : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) foreach (var (design, index) in _removeDesigns) editor.RemoveTag(design, index); Im.Separator(); @@ -197,24 +178,22 @@ public class MultiDesignPanel( private void DrawMultiQuickDesignBar(float offset) { - ImUtf8.TextFrameAligned("Multi QDB:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Multi QDB:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numQuickDesignEnabled; - var tt = diff == 0 - ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." - : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) + if (ImEx.Button("Display Selected Designs in QDB"u8, buttonWidth, diff is 0 + ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." + : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs.", diff is 0)) { foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, true); } Im.Line.Same(); - tt = _numQuickDesignEnabled == 0 - ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." - : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; - if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) + if (ImEx.Button("Hide Selected Designs in QDB"u8, buttonWidth, _numQuickDesignEnabled is 0 + ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." + : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.", _numQuickDesignEnabled is 0)) { foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, false); @@ -225,22 +204,20 @@ public class MultiDesignPanel( private void DrawMultiLock(float offset) { - ImUtf8.TextFrameAligned("Multi Lock:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Multi Lock:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numDesignsLocked; - var tt = diff == 0 - ? $"All {_numDesigns} selected designs are already write protected." - : $"Write-protect all {_numDesigns} designs. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Turn Write-Protected"u8, tt, buttonWidth, diff == 0)) + if (ImEx.Button("Turn Write-Protected"u8, buttonWidth, diff is 0 + ? $"All {_numDesigns} selected designs are already write protected." + : $"Write-protect all {_numDesigns} designs. Changes {diff} designs.", diff is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.SetWriteProtection(design.Value, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs are write-protected." - : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs."; - if (ImUtf8.ButtonEx("Remove Write-Protection"u8, tt, buttonWidth, _numDesignsLocked == 0)) + if (ImEx.Button("Remove Write-Protection"u8, buttonWidth, _numDesignsLocked is 0 + ? $"None of the {_numDesigns} selected designs are write-protected." + : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs.", _numDesignsLocked is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.SetWriteProtection(design.Value, false); Im.Separator(); @@ -248,22 +225,20 @@ public class MultiDesignPanel( private void DrawMultiResetSettings(float offset) { - ImUtf8.TextFrameAligned("Settings:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Settings:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numDesignsResetSettings; - var tt = diff == 0 - ? $"All {_numDesigns} selected designs already reset temporary settings." - : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Temp. Settings"u8, tt, buttonWidth, diff == 0)) + if (ImEx.Button("Set Reset Temp. Settings"u8, buttonWidth, diff is 0 + ? $"All {_numDesigns} selected designs already reset temporary settings." + : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs.", diff is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, true); Im.Line.Same(); - tt = _numDesignsResetSettings == 0 - ? $"None of the {_numDesigns} selected designs reset temporary settings." - : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; - if (ImUtf8.ButtonEx("Remove Reset Temp. Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) + if (ImEx.Button("Remove Reset Temp. Settings"u8, buttonWidth, _numDesignsResetSettings is 0 + ? $"None of the {_numDesigns} selected designs reset temporary settings." + : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs.", _numDesignsResetSettings is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, false); Im.Separator(); @@ -271,22 +246,20 @@ public class MultiDesignPanel( private void DrawMultiResetDyes(float offset) { - ImUtf8.TextFrameAligned("Adv. Dyes:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Adv. Dyes:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numDesignsResetDyes; - var tt = diff == 0 - ? $"All {_numDesigns} selected designs already reset advanced dyes." - : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Dyes"u8, tt, buttonWidth, diff == 0)) + if (ImEx.Button("Set Reset Dyes"u8, buttonWidth, diff is 0 + ? $"All {_numDesigns} selected designs already reset advanced dyes." + : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs.", diff is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetAdvancedDyes(design.Value, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs reset advanced dyes." - : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs."; - if (ImUtf8.ButtonEx("Remove Reset Dyes"u8, tt, buttonWidth, _numDesignsResetDyes == 0)) + if (ImEx.Button("Remove Reset Dyes"u8, buttonWidth, _numDesignsLocked is 0 + ? $"None of the {_numDesigns} selected designs reset advanced dyes." + : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs.", _numDesignsResetDyes is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetAdvancedDyes(design.Value, false); Im.Separator(); @@ -294,22 +267,20 @@ public class MultiDesignPanel( private void DrawMultiForceRedraw(float offset) { - ImUtf8.TextFrameAligned("Redrawing:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Redrawing:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numDesignsForcedRedraw; - var tt = diff == 0 - ? $"All {_numDesigns} selected designs already force redraws." - : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Force Redraws"u8, tt, buttonWidth, diff == 0)) + if (ImEx.Button("Force Redraws"u8, buttonWidth, diff is 0 + ? $"All {_numDesigns} selected designs already force redraws." + : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs.", diff is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeForcedRedraw(design.Value, true); Im.Line.Same(); - tt = _numDesignsLocked == 0 - ? $"None of the {_numDesigns} selected designs force redraws." - : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs."; - if (ImUtf8.ButtonEx("Remove Forced Redraws"u8, tt, buttonWidth, _numDesignsForcedRedraw == 0)) + if (ImEx.Button("Remove Forced Redraws"u8, buttonWidth, _numDesignsLocked is 0 + ? $"None of the {_numDesigns} selected designs force redraws." + : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs.", _numDesignsForcedRedraw is 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeForcedRedraw(design.Value, false); Im.Separator(); @@ -319,39 +290,35 @@ public class MultiDesignPanel( private void DrawMultiColor(Vector2 width, float offset) { - ImUtf8.TextFrameAligned("Multi Colors:"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Multi Colors:"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); if (_colorCombo.Draw("##color"u8, _colorComboSelection, "Select a design color."u8, Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), out var newSelection)) _colorComboSelection = newSelection; UpdateColorCache(); - var label = _addDesigns.Count > 0 - ? $"Set for {_addDesigns.Count} Designs" - : "Set"; - var tooltip = _addDesigns.Count is 0 - ? _colorComboSelection switch - { - null => "No color specified.", - DesignColors.AutomaticName => "Use the other button to set to automatic.", - _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", - } - : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; Im.Line.Same(); - if (ImEx.Button(label, width, tooltip, _addDesigns.Count is 0)) + if (ImEx.Button(_addDesigns.Count > 0 + ? $"Set for {_addDesigns.Count} Designs" + : "Set"u8, width, _addDesigns.Count is 0 + ? _colorComboSelection switch + { + null => "No color specified."u8, + DesignColors.AutomaticName => "Use the other button to set to automatic."u8, + _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", + } + : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) { foreach (var design in _addDesigns) editor.ChangeColor(design, _colorComboSelection!); } - label = _removeDesigns.Count > 0 - ? $"Unset {_removeDesigns.Count} Designs" - : "Unset"; - tooltip = _removeDesigns.Count == 0 - ? "No selected design is set to a non-automatic color." - : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; Im.Line.Same(); - if (ImEx.Button(label, width, tooltip, _removeDesigns.Count is 0)) + if (ImEx.Button(_removeDesigns.Count > 0 + ? $"Unset {_removeDesigns.Count} Designs" + : "Unset"u8, width, _removeDesigns.Count is 0 + ? "No selected design is set to a non-automatic color."u8 + : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) { foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); @@ -362,13 +329,12 @@ public class MultiDesignPanel( private void DrawAdvancedButtons(float offset) { - ImUtf8.TextFrameAligned("Delete Adv."u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Delete Adv."u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var enabled = config.DeleteDesignModifier.IsActive(); - var tt = _numDesignsWithAdvancedDyes is 0 - ? "No selected designs contain any advanced dyes." - : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs."; - if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, tt, new Vector2(Im.ContentRegion.Available.X, 0), + if (ImEx.Button("Delete All Advanced Dyes"u8, Im.ContentRegion.Available with { Y = 0 }, _numDesignsWithAdvancedDyes is 0 + ? "No selected designs contain any advanced dyes."u8 + : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.", !enabled || _numDesignsWithAdvancedDyes is 0)) foreach (var design in selector.SelectedPaths.OfType()) @@ -378,93 +344,94 @@ public class MultiDesignPanel( } if (!enabled && _numDesignsWithAdvancedDyes is not 0) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); Im.Separator(); } private void DrawApplicationButtons(float offset) { - ImUtf8.TextFrameAligned("Application"u8); - ImGui.SameLine(offset, Im.Style.ItemSpacing.X); + ImEx.TextFrameAligned("Application"u8); + Im.Line.Same(offset, Im.Style.ItemSpacing.X); var width = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); var enabled = config.DeleteDesignModifier.IsActive(); bool? equip = null; bool? customize = null; - var group = ImUtf8.Group(); - if (ImUtf8.ButtonEx("Disable Everything"u8, - _numDesigns > 0 - ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) + using (Im.Group()) { - equip = false; - customize = false; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - Im.Line.Same(); - if (ImUtf8.ButtonEx("Enable Everything"u8, - _numDesigns > 0 - ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = true; - customize = true; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - if (ImUtf8.ButtonEx("Equipment Only"u8, - _numDesigns > 0 - ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = true; - customize = false; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - Im.Line.Same(); - if (ImUtf8.ButtonEx("Customization Only"u8, - _numDesigns > 0 - ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - { - equip = false; - customize = true; - } - - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - - if (ImUtf8.ButtonEx("Default Application"u8, - _numDesigns > 0 - ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." - : "No designs selected.", width, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + if (ImEx.Button("Disable Everything"u8, width, + _numDesigns > 0 + ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected."u8, !enabled)) { - editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); - editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + equip = false; + customize = false; } - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); - Im.Line.Same(); - if (ImUtf8.ButtonEx("Disable Advanced"u8, _numDesigns > 0 - ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." - : "No designs selected.", width, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) - editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + Im.Line.Same(); + if (ImEx.Button("Enable Everything"u8, width, + _numDesigns > 0 + ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected."u8, !enabled)) + { + equip = true; + customize = true; + } - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImEx.Button("Equipment Only"u8, width, + _numDesigns > 0 + ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." + : "No designs selected."u8, !enabled)) + { + equip = true; + customize = false; + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + Im.Line.Same(); + if (ImEx.Button("Customization Only"u8, width, + _numDesigns > 0 + ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." + : "No designs selected."u8, !enabled)) + { + equip = false; + customize = true; + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImEx.Button("Default Application"u8, width, + _numDesigns > 0 + ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." + : "No designs selected."u8, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + { + editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); + editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + } + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + Im.Line.Same(); + if (ImEx.Button("Disable Advanced"u8, width, _numDesigns > 0 + ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." + : "No designs selected."u8, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + + if (!enabled) + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + } - group.Dispose(); Im.Separator(); if (equip is null && customize is null) return; @@ -489,7 +456,7 @@ public class MultiDesignPanel( { _addDesigns.Clear(); _removeDesigns.Clear(); - if (_tag.Length == 0) + if (_tag.Length is 0) return; foreach (var leaf in selector.SelectedPaths.OfType()) diff --git a/Luna b/Luna index e80bdf5..0ee15af 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit e80bdf5c10c6c30efccdff7d1adcb4a638eba4df +Subproject commit 0ee15af5c45b6c72df25b972e48b9a57347c25a5 From 766fe42598df483dc5417de2283dd67077b8992c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Feb 2026 15:02:15 +0100 Subject: [PATCH 2/8] Fix crashes in rare cases due to non-permanent identifiers. --- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 7d132a1..9b66ada 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -122,13 +122,13 @@ public class ActorSelector(ActorObjectManager objects, ActorManager actors, Ephe 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); + _identifier = objects.Player.GetIdentifier(actors).CreatePermanent(); ImGui.SameLine(); 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; + _identifier = id.CreatePermanent(); } } From 6b7039f743ce987ffd63c4e8dd62b1faad6f7f3d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 18 Feb 2026 18:04:58 +0100 Subject: [PATCH 3/8] Move some services to Luna, move config files. Add and use UiConfig. --- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 26 +- Glamourer/Automation/AutoDesignManager.cs | 2 +- .../{ => Configuration}/Configuration.cs | 55 +-- .../Configuration/DefaultDesignSettings.cs | 10 + .../{ => Configuration}/DesignPanelFlag.cs | 9 +- .../{ => Configuration}/EphemeralConfig.cs | 4 +- Glamourer/Configuration/HeightDisplayType.cs | 28 ++ Glamourer/Configuration/UiConfig.cs | 50 +++ Glamourer/Designs/Design.cs | 11 +- Glamourer/Designs/DesignColors.cs | 4 +- Glamourer/Designs/DesignEditor.cs | 4 +- Glamourer/Designs/DesignFileSystem.cs | 2 +- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Designs/Links/DesignMerger.cs | 2 +- .../Designs/Special/RandomDesignGenerator.cs | 2 +- Glamourer/Glamourer.cs | 2 +- Glamourer/Gui/Colors.cs | 2 +- .../CustomizationDrawer.Simple.cs | 1 + .../Gui/Customization/CustomizationDrawer.cs | 2 +- .../Customization/CustomizeParameterDrawer.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 24 +- Glamourer/Gui/DesignQuickBar.cs | 24 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 +- Glamourer/Gui/GenericPopupWindow.cs | 10 +- Glamourer/Gui/GlamourerChangelog.cs | 8 +- Glamourer/Gui/GlamourerWindowSystem.cs | 2 +- Glamourer/Gui/MainTabBar.cs | 8 +- Glamourer/Gui/MainWindow.cs | 12 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 3 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 27 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 18 +- Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs | 6 +- .../Tabs/AutomationTab/AutomationButtons.cs | 4 +- .../Tabs/AutomationTab/AutomationHeader.cs | 2 +- .../Gui/Tabs/AutomationTab/AutomationTab.cs | 14 +- .../AutomationTab/RandomRestrictionDrawer.cs | 16 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 5 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 17 +- .../DesignTab/DesignFileSystemSelector.cs | 16 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 3 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 319 +++++++++--------- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 10 + .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 36 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 25 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/IncognitoButton.cs | 2 +- .../Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 3 +- Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 19 +- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 2 +- .../Gui/Tabs/SettingsTab/CollectionCombo.cs | 2 +- .../SettingsTab/CollectionOverrideDrawer.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 7 +- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 8 +- Glamourer/Interop/ContextMenuService.cs | 2 +- Glamourer/Interop/Material/MaterialManager.cs | 2 +- .../Interop/Penumbra/ModSettingApplier.cs | 2 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 18 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 4 +- Glamourer/Services/BackupService.cs | 7 +- Glamourer/Services/CodeService.cs | 6 +- .../Services/CollectionOverrideService.cs | 2 +- Glamourer/Services/CommandService.cs | 34 +- Glamourer/Services/ConfigMigrationService.cs | 12 +- Glamourer/Services/FilenameService.cs | 54 ++- Glamourer/Services/ItemManager.cs | 4 +- Glamourer/Services/PcpService.cs | 14 +- Glamourer/Services/SaveService.cs | 14 +- Glamourer/Services/ServiceManager.cs | 4 +- Glamourer/State/FunModule.cs | 28 +- Glamourer/State/StateEditor.cs | 14 +- Glamourer/State/StateListener.cs | 52 +-- Glamourer/State/StateManager.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 6 +- Glamourer/Unlocks/FavoriteManager.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 4 +- Luna | 2 +- 83 files changed, 643 insertions(+), 540 deletions(-) rename Glamourer/{ => Configuration}/Configuration.cs (86%) create mode 100644 Glamourer/Configuration/DefaultDesignSettings.cs rename Glamourer/{ => Configuration}/DesignPanelFlag.cs (93%) rename Glamourer/{ => Configuration}/EphemeralConfig.cs (94%) create mode 100644 Glamourer/Configuration/HeightDisplayType.cs create mode 100644 Glamourer/Configuration/UiConfig.cs diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index e4ca224..c176a30 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -3,7 +3,7 @@ using Luna; namespace Glamourer.Api; -public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +public class GlamourerApi(Configuration.Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; public const int CurrentApiVersionMinor = 7; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index a61a004..71b76a3 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -16,22 +16,22 @@ namespace Glamourer.Automation; public sealed class AutoDesignApplier : IDisposable { - private readonly Configuration _config; - private readonly AutoDesignManager _manager; - private readonly StateManager _state; - private readonly JobService _jobs; - private readonly EquippedGearset _equippedGearset; - private readonly ActorManager _actors; - private readonly AutomationChanged _event; - private readonly ActorObjectManager _objects; - private readonly WeaponLoading _weapons; - private readonly HumanModelList _humans; - private readonly DesignMerger _designMerger; - private readonly IClientState _clientState; + private readonly Configuration.Configuration _config; + private readonly AutoDesignManager _manager; + private readonly StateManager _state; + private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly ActorObjectManager _objects; + private readonly WeaponLoading _weapons; + private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; + private readonly IClientState _clientState; private readonly JobChangeState _jobChangeState; - public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, + public AutoDesignApplier(Configuration.Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) { diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index a7e63bd..eb76bd0 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -381,7 +381,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedData, set, (which, data)); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.AutomationFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration/Configuration.cs similarity index 86% rename from Glamourer/Configuration.cs rename to Glamourer/Configuration/Configuration.cs index 219e8cf..417850f 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration/Configuration.cs @@ -6,53 +6,21 @@ using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Services; using ImSharp; +using Luna; using Newtonsoft.Json; using OtterGui.Filesystem; -using Luna; -using Luna.Generators; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer; - -[TooltipEnum] -public enum HeightDisplayType -{ - [Tooltip("Do Not Display")] - None, - - [Tooltip("Centimetres (000.0 cm)")] - Centimetre, - - [Tooltip("Metres (0.00 m)")] - Metre, - - [Tooltip("Inches (00.0 in)")] - Wrong, - - [Tooltip("Feet (0'00'')")] - WrongFoot, - - [Tooltip("Corgis (0.0 Corgis)")] - Corgi, - - [Tooltip("Olympic-size swimming Pools (0.000 Pools)")] - OlympicPool, -} - -public class DefaultDesignSettings -{ - public bool AlwaysForceRedrawing = false; - public bool ResetAdvancedDyes = false; - public bool ShowQuickDesignBar = true; - public bool ResetTemporarySettings = false; - public bool Locked = false; -} +namespace Glamourer.Configuration; public class Configuration : IPluginConfiguration, ISavable { [JsonIgnore] public readonly EphemeralConfig Ephemeral; + [JsonIgnore] + public readonly UiConfig Ui; + public bool AttachToPcp { get; set; } = true; public bool UseRestrictedGearProtection { get; set; } = false; public bool OpenFoldersByDefault { get; set; } = false; @@ -120,10 +88,11 @@ public class Configuration : IPluginConfiguration, ISavable [JsonIgnore] private readonly SaveService _saveService; - public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral) + public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral, UiConfig ui) { _saveService = saveService; Ephemeral = ephemeral; + Ui = ui; Load(migrator); } @@ -132,13 +101,13 @@ public class Configuration : IPluginConfiguration, ISavable private void Load(ConfigMigrationService migrator) { - if (!File.Exists(_saveService.FileNames.ConfigFile)) + if (!File.Exists(_saveService.FileNames.ConfigurationFile)) return; - if (File.Exists(_saveService.FileNames.ConfigFile)) + if (File.Exists(_saveService.FileNames.ConfigurationFile)) try { - var text = File.ReadAllText(_saveService.FileNames.ConfigFile); + var text = File.ReadAllText(_saveService.FileNames.ConfigurationFile); JsonConvert.PopulateObject(text, this, new JsonSerializerSettings { Error = HandleDeserializationError, @@ -162,8 +131,8 @@ public class Configuration : IPluginConfiguration, ISavable } } - public string ToFilename(FilenameService fileNames) - => fileNames.ConfigFile; + public string ToFilePath(FilenameService fileNames) + => fileNames.ConfigurationFile; public void Save(StreamWriter writer) { diff --git a/Glamourer/Configuration/DefaultDesignSettings.cs b/Glamourer/Configuration/DefaultDesignSettings.cs new file mode 100644 index 0000000..4ed7dfe --- /dev/null +++ b/Glamourer/Configuration/DefaultDesignSettings.cs @@ -0,0 +1,10 @@ +namespace Glamourer.Configuration; + +public class DefaultDesignSettings +{ + public bool AlwaysForceRedrawing = false; + public bool ResetAdvancedDyes = false; + public bool ShowQuickDesignBar = true; + public bool ResetTemporarySettings = false; + public bool Locked = false; +} diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/Configuration/DesignPanelFlag.cs similarity index 93% rename from Glamourer/DesignPanelFlag.cs rename to Glamourer/Configuration/DesignPanelFlag.cs index a5a353a..c381222 100644 --- a/Glamourer/DesignPanelFlag.cs +++ b/Glamourer/Configuration/DesignPanelFlag.cs @@ -1,7 +1,7 @@ -using Luna.Generators; -using ImSharp; +using ImSharp; +using Luna.Generators; -namespace Glamourer; +namespace Glamourer.Configuration; [Flags] [NamedEnum(Utf16: false)] @@ -42,7 +42,7 @@ public static partial class DesignPanelFlagExtensions { private static readonly StringU8 Expand = new("Expand"u8); - public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config) + public static Im.HeaderDisposable Header(this DesignPanelFlag flag, global::Glamourer.Configuration.Configuration config) { if (config.HideDesignPanel.HasFlag(flag)) return default; @@ -55,7 +55,6 @@ public static partial class DesignPanelFlagExtensions Action setterExpand) { var checkBoxWidth = Math.Max(Im.Style.FrameHeight, Expand.CalculateSize().X); - var test = DesignPanelFlag.AdvancedCustomizations.ToNameU8(); var textWidth = AdvancedCustomizations_Name__GenU8.CalculateSize().X; var tableSize = 2 * (textWidth + 2 * checkBoxWidth) + 10 * Im.Style.CellPadding.X diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/Configuration/EphemeralConfig.cs similarity index 94% rename from Glamourer/EphemeralConfig.cs rename to Glamourer/Configuration/EphemeralConfig.cs index fd76817..ebf1c75 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/Configuration/EphemeralConfig.cs @@ -6,7 +6,7 @@ using Luna.Generators; using Newtonsoft.Json; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer; +namespace Glamourer.Configuration; public partial class EphemeralConfig : ISavable { @@ -69,7 +69,7 @@ public partial class EphemeralConfig : ISavable } } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.EphemeralConfigFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Configuration/HeightDisplayType.cs b/Glamourer/Configuration/HeightDisplayType.cs new file mode 100644 index 0000000..5ff79e0 --- /dev/null +++ b/Glamourer/Configuration/HeightDisplayType.cs @@ -0,0 +1,28 @@ +using Luna.Generators; + +namespace Glamourer.Configuration; + +[TooltipEnum] +public enum HeightDisplayType +{ + [Tooltip("Do Not Display")] + None, + + [Tooltip("Centimetres (000.0 cm)")] + Centimetre, + + [Tooltip("Metres (0.00 m)")] + Metre, + + [Tooltip("Inches (00.0 in)")] + Wrong, + + [Tooltip("Feet (0'00'')")] + WrongFoot, + + [Tooltip("Corgis (0.0 Corgis)")] + Corgi, + + [Tooltip("Olympic-size swimming Pools (0.000 Pools)")] + OlympicPool, +} diff --git a/Glamourer/Configuration/UiConfig.cs b/Glamourer/Configuration/UiConfig.cs new file mode 100644 index 0000000..c1ee1b4 --- /dev/null +++ b/Glamourer/Configuration/UiConfig.cs @@ -0,0 +1,50 @@ +using Glamourer.Services; +using Luna; +using Luna.Generators; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Configuration; + +public sealed partial class UiConfig : ConfigurationFile +{ + public UiConfig(SaveService saveService, MessageService messageService) + : base(saveService, messageService) + { + Load(); + } + + [ConfigProperty] + private TwoPanelWidth _actorsTabScale = new(250, ScalingMode.Absolute); + + [ConfigProperty] + private TwoPanelWidth _designsTabScale = new(0.3f, ScalingMode.Percentage); + + [ConfigProperty] + private TwoPanelWidth _automationTabScale = new(0.3f, ScalingMode.Percentage); + + [ConfigProperty] + private TwoPanelWidth _npcTabScale = new(250, ScalingMode.Absolute); + + public override int CurrentVersion + => 1; + + protected override void AddData(JsonTextWriter j) + { + ActorsTabScale.WriteJson(j, "ActorsTab"); + DesignsTabScale.WriteJson(j, "DesignsTab"); + AutomationTabScale.WriteJson(j, "AutomationTab"); + NpcTabScale.WriteJson(j, "NpcTab"); + } + + protected override void LoadData(JObject j) + { + _actorsTabScale = TwoPanelWidth.ReadJson(j, "ActorsTab", new TwoPanelWidth(250, ScalingMode.Absolute)); + _designsTabScale = TwoPanelWidth.ReadJson(j, "DesignsTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); + _automationTabScale = TwoPanelWidth.ReadJson(j, "AutomationTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); + _npcTabScale = TwoPanelWidth.ReadJson(j, "NpcTab", new TwoPanelWidth(250, ScalingMode.Absolute)); + } + + public override string ToFilePath(FilenameService fileNames) + => fileNames.UiConfiguration; +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9ceb8c0..69f4404 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -11,7 +11,6 @@ using OtterGui.Classes; using Penumbra.GameData.Structs; using Luna; using Notification = Luna.Notification; -using SaveType = OtterGui.Classes.SaveType; namespace Glamourer.Designs; @@ -232,7 +231,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn Glamourer.Messager.AddMessage(new Notification( $"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}", NotificationType.Info)); - saveService.Save(SaveType.ImmediateSync, design); + saveService.Save(Luna.SaveType.ImmediateSync, design); } } @@ -329,15 +328,13 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region ISavable - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.DesignFile(this); public void Save(StreamWriter writer) { - using var j = new JsonTextWriter(writer) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; var obj = JsonSerialize(); obj.WriteTo(j); } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index b5b2c4b..b9200eb 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Designs; -public class DesignColorUi(DesignColors colors, Configuration config) +public class DesignColorUi(DesignColors colors, Configuration.Configuration config) { private string _newName = string.Empty; @@ -183,7 +183,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary SaveAndInvoke(); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.DesignColorFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index e5c0357..828ed43 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -16,14 +16,14 @@ public class DesignEditor( DesignChanged designChanged, CustomizeService customizations, ItemManager items, - Configuration config) + Configuration.Configuration config) : IDesignEditor { protected readonly DesignChanged DesignChanged = designChanged; protected readonly SaveService SaveService = saveService; protected readonly ItemManager Items = items; protected readonly CustomizeService Customizations = customizations; - protected readonly Configuration Config = config; + protected readonly Configuration.Configuration Config = config; protected readonly Dictionary UndoStore = []; private bool _forceFullItemOff; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 159da19..9aa5fb8 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -187,7 +187,7 @@ public sealed class DesignFileSystem : OtterGui.Filesystem.FileSystem, I } } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.DesignFileSystem; public void Save(StreamWriter writer) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 9c9122b..2e1a1f9 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -21,7 +21,7 @@ public sealed class DesignManager : DesignEditor private readonly HumanModelList _humans; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, - DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) + DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration.Configuration config) : base(saveService, @event, customizations, items, config) { Designs = storage; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 927a7d7..709ae47 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -15,7 +15,7 @@ namespace Glamourer.Designs.Links; public class DesignMerger( DesignManager designManager, CustomizeService customizeService, - Configuration config, + Configuration.Configuration config, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks) : IService { diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index c5d9124..ab736f4 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -3,7 +3,7 @@ using Luna; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration.Configuration config) : IService { private readonly Random _rng = new(); private readonly WeakReference _lastDesign = new(null!, false); diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 754914e..c059b4f 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -61,7 +61,7 @@ public class Glamourer : IDalamudPlugin public string GatherSupportInformation() { var sb = new StringBuilder(10240); - var config = _services.GetService(); + var config = _services.GetService(); sb.AppendLine("**Settings**"); sb.Append($"> **`Plugin Version: `** {Version}\n"); sb.Append($"> **`Commit Hash: `** {CommitHash}\n"); diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 12c14f8..dab72db 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -83,6 +83,6 @@ public static class Colors => _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor; /// Set the configurable colors dictionary to a value. - public static void SetColors(Configuration config) + public static void SetColors(Configuration.Configuration config) => _colors = config.Colors; } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index cd7f077..dd8a272 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,4 +1,5 @@ using System.Text.Unicode; +using Glamourer.Configuration; using ImSharp; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 89e5688..d6ebf5d 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -14,7 +14,7 @@ namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( ITextureProvider textures, CustomizeService service, - Configuration config, + Configuration.Configuration config, FavoriteManager favorites, HeightService heightService) : IDisposable diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 07bb9e0..65a4e7d 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -7,7 +7,7 @@ using Luna; namespace Glamourer.Gui.Customization; -public class CustomizeParameterDrawer(Configuration config, PaletteImport import) : IService +public class CustomizeParameterDrawer(Configuration.Configuration config, PaletteImport import) : IService { private readonly Dictionary _lastData = []; private StringU8 _paletteName = StringU8.Empty; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 36cc5fa..cead406 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -9,7 +9,7 @@ using Luna; namespace Glamourer.Gui; public abstract class DesignComboBase( - EphemeralConfig config, + Configuration.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -17,13 +17,13 @@ public abstract class DesignComboBase( DesignFileSystem designFileSystem) : FilterComboBase(new DesignFilter(), ConfigData.Default with { ComputeWidth = true }) { - protected readonly EphemeralConfig Config = config; - protected readonly DesignChanged DesignChanged = designChanged; - protected readonly DesignColors DesignColors = designColors; - protected readonly DesignFileSystem DesignFileSystem = designFileSystem; - protected readonly TabSelected TabSelected = tabSelected; - protected readonly DesignManager Designs = designs; - protected IDesignStandIn? CurrentDesign; + protected readonly Configuration.EphemeralConfig Config = config; + protected readonly DesignChanged DesignChanged = designChanged; + protected readonly DesignColors DesignColors = designColors; + protected readonly DesignFileSystem DesignFileSystem = designFileSystem; + protected readonly TabSelected TabSelected = tabSelected; + protected readonly DesignManager Designs = designs; + protected IDesignStandIn? CurrentDesign; protected CacheItem CreateItem(IDesignStandIn design) { @@ -203,7 +203,7 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService } - public QuickDesignCombo(EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, + public QuickDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, DesignFileSystem designFileSystem, DesignManager designs) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem) { @@ -264,7 +264,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable { public Design? NewSelection { get; private set; } - public LinkDesignCombo(EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, + public LinkDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, DesignFileSystem designFileSystem, DesignManager designs) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem) { @@ -295,7 +295,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable } public sealed class RandomDesignCombo( - EphemeralConfig config, + Configuration.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -346,7 +346,7 @@ public sealed class SpecialDesignCombo : DesignComboBase, IUiService private readonly CacheItem _revert; private readonly CacheItem _quick; - public SpecialDesignCombo(EphemeralConfig config, + public SpecialDesignCombo(Configuration.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 9e9fa80..cd76118 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -33,19 +33,19 @@ public sealed class DesignQuickBar : Window, IDisposable ? WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing | WindowFlags.NoMove : WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing; - private readonly Configuration _config; - private readonly QuickDesignCombo _designCombo; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ActorObjectManager _objects; - private readonly PenumbraService _penumbra; - private readonly IKeyState _keyState; - private readonly Im.ColorStyleDisposable _style = new(); - private DateTime _keyboardToggle = DateTime.UnixEpoch; - private int _numButtons; - private readonly StringBuilder _tooltipBuilder = new(512); + private readonly Configuration.Configuration _config; + private readonly QuickDesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ActorObjectManager _objects; + private readonly PenumbraService _penumbra; + private readonly IKeyState _keyState; + private readonly Im.ColorStyleDisposable _style = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); - public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, + public DesignQuickBar(Configuration.Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking) { diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 720174a..217d8c4 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -22,7 +22,7 @@ public class EquipmentDrawer private readonly BonusItemCombo[] _bonusItemCombo; private readonly Dictionary _weaponCombo; private readonly TextureService _textures; - private readonly Configuration _config; + private readonly Configuration.Configuration _config; private readonly GPoseService _gPose; private readonly AdvancedDyePopup _advancedDyes; private readonly ItemCopyService _itemCopy; @@ -32,7 +32,7 @@ public class EquipmentDrawer private EquipSlot _dragTarget; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, - Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) + Configuration.Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) { _items = items; _textures = textures; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index fa3c93f..cb13a18 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -6,12 +6,12 @@ namespace Glamourer.Gui; public class GenericPopupWindow : Luna.Window { - private readonly Configuration _config; - private readonly ICondition _condition; - private readonly IClientState _state; - public bool OpenFestivalPopup { get; internal set; } + private readonly Configuration.Configuration _config; + private readonly ICondition _condition; + private readonly IClientState _state; + public bool OpenFestivalPopup { get; internal set; } - public GenericPopupWindow(Configuration config, IClientState state, ICondition condition) + public GenericPopupWindow(Configuration.Configuration config, IClientState state, ICondition condition) : base("Glamourer Popups", WindowFlags.NoBringToFrontOnFocus | WindowFlags.NoDecoration diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 0c285c3..a30dce6 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui; public class GlamourerChangelog { - public const int LastChangelogVersion = 0; - private readonly Configuration _config; - public readonly Changelog Changelog; + public const int LastChangelogVersion = 0; + private readonly Configuration.Configuration _config; + public readonly Changelog Changelog; - public GlamourerChangelog(Configuration config) + public GlamourerChangelog(Configuration.Configuration config) { _config = config; Changelog = new Changelog("Glamourer Changelog", ConfigData, Save); diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 76f107e..6379a4c 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -11,7 +11,7 @@ public class GlamourerWindowSystem : IDisposable private readonly MainWindow _ui; public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, - Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) + Configuration.Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; _ui = ui; diff --git a/Glamourer/Gui/MainTabBar.cs b/Glamourer/Gui/MainTabBar.cs index 2b2b398..0862faa 100644 --- a/Glamourer/Gui/MainTabBar.cs +++ b/Glamourer/Gui/MainTabBar.cs @@ -14,11 +14,11 @@ namespace Glamourer.Gui; public sealed class MainTabBar : TabBar { - private readonly EphemeralConfig _config; - public readonly TabSelected Event; - public readonly SettingsTab Settings; + private readonly Configuration.EphemeralConfig _config; + public readonly TabSelected Event; + public readonly SettingsTab Settings; - public MainTabBar(Logger log, EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, + public MainTabBar(Logger log, Configuration.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, AutomationTab automation, UnlocksTab unlocks, NpcTab npcs, MessagesTab messages, DebugTab debug, TabSelected @event) : base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug) { diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 49341eb..d1d5e3b 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -8,13 +8,13 @@ namespace Glamourer.Gui; public sealed class MainWindow : Window, IDisposable { - private readonly Configuration _config; - private readonly PenumbraService _penumbra; - private readonly DesignQuickBar _quickBar; - private readonly MainTabBar _mainTabBar; - private bool _ignorePenumbra; + private readonly Configuration.Configuration _config; + private readonly PenumbraService _penumbra; + private readonly DesignQuickBar _quickBar; + private readonly MainTabBar _mainTabBar; + private bool _ignorePenumbra; - public MainWindow(IDalamudPluginInterface pi, Configuration config, PenumbraService penumbra, + public MainWindow(IDalamudPluginInterface pi, Configuration.Configuration config, PenumbraService penumbra, MainTabBar mainTabBar, DesignQuickBar quickBar) : base("GlamourerMainWindow") { diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index bdbaa16..20d65dc 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -2,6 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; @@ -16,7 +17,7 @@ using Notification = Luna.Notification; namespace Glamourer.Gui.Materials; public sealed unsafe class AdvancedDyePopup( - Configuration config, + Configuration.Configuration config, StateManager stateManager, LiveColorTablePreviewer preview, DirectXService directX) : IService diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 766ff20..db9f8ce 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Files.MaterialStructs; namespace Glamourer.Gui.Materials; -public class MaterialDrawer(DesignManager designManager, Configuration config) : IService +public class MaterialDrawer(DesignManager designManager, Configuration.Configuration config) : IService { public const float GlossWidth = 100; public const float SpecularStrengthWidth = 125; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index eff2c5a..0e8ef3c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui.Customization; @@ -19,24 +20,24 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorPanel : IPanel { - private readonly ActorSelection _selection; - private readonly StateManager _stateManager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly ActorObjectManager _objects; - private readonly ImportService _importService; - private readonly DictModelChara _modelChara; - private readonly CustomizeParameterDrawer _parameterDrawer; - private readonly AdvancedDyePopup _advancedDyes; + private readonly ActorSelection _selection; + private readonly StateManager _stateManager; + private readonly CustomizationDrawer _customizationDrawer; + private readonly EquipmentDrawer _equipmentDrawer; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly Configuration.Configuration _config; + private readonly DesignConverter _converter; + private readonly ActorObjectManager _objects; + private readonly ImportService _importService; + private readonly DictModelChara _modelChara; + private readonly CustomizeParameterDrawer _parameterDrawer; + private readonly AdvancedDyePopup _advancedDyes; public ActorPanel(StateManager stateManager, CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, AutoDesignApplier autoDesignApplier, - Configuration config, + Configuration.Configuration config, DesignConverter converter, ActorObjectManager objects, DesignManager designManager, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index c9ed4b8..82d54ff 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -14,7 +14,7 @@ public readonly struct ActorCacheItem(ActorIdentifier identifier, ActorData data public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label)); } -public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, EphemeralConfig config) : IPanel +public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Configuration.EphemeralConfig config) : IPanel { public ReadOnlySpan Id => "ActorSelector"u8; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index f6f0c73..c250d7a 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,4 +1,5 @@ -using ImSharp; +using Glamourer.Configuration; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.ActorTab; @@ -6,11 +7,13 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorTab : TwoPanelLayout, ITab { private readonly ActorSelection _selection; + private readonly UiConfig _uiConfig; public ActorTab(ActorSelector selector, ActorPanel panel, ActorFilter filter, SelectPlayerButton selectPlayer, - SelectTargetButton selectTarget, ActorsHeader header, ActorSelection selection) + SelectTargetButton selectTarget, ActorsHeader header, ActorSelection selection, UiConfig uiConfig) { _selection = selection; + _uiConfig = uiConfig; LeftPanel = selector; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); var footer = new ButtonFooter(); @@ -29,9 +32,18 @@ public sealed class ActorTab : TwoPanelLayout, ITab public void DrawContent() { _selection.Update(); - Draw(TwoPanelWidth.IndeterminateRelative); + Draw(_uiConfig.ActorsTabScale); } + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode); + + protected override float MinimumWidth + => LeftHeader.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; + public MainTabType Identifier => MainTabType.Actors; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs index d3bb802..907c1f4 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorsHeader : SplitButtonHeader { - private readonly ActorSelection _selection; - private readonly EphemeralConfig _config; + private readonly ActorSelection _selection; + private readonly Configuration.EphemeralConfig _config; public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save, - UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, EphemeralConfig config) + UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Configuration.EphemeralConfig config) { _selection = selection; _config = config; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs index 8cc1dfe..6ef6583 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs @@ -10,7 +10,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab; public sealed class AutomationButtons : ButtonFooter { - public AutomationButtons(Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects) + public AutomationButtons(Configuration.Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects) { Buttons.AddButton(new AddButton(objects, manager), 100); Buttons.AddButton(new DuplicateButton(selection, manager), 90); @@ -134,7 +134,7 @@ public sealed class AutomationButtons : ButtonFooter } } - private sealed class DeleteButton(AutomationSelection selection, Configuration config, AutoDesignManager manager) + private sealed class DeleteButton(AutomationSelection selection, Configuration.Configuration config, AutoDesignManager manager) : BaseIconButton { private bool _enabled; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs index f0c4cb1..f5381e7 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs @@ -3,7 +3,7 @@ using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; -public sealed class AutomationHeader(Configuration config, AutomationSelection selection) : IHeader +public sealed class AutomationHeader(Configuration.Configuration config, AutomationSelection selection) : IHeader { public bool Collapsed => false; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 559d3f7..a2c601b 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -5,10 +5,10 @@ namespace Glamourer.Gui.Tabs.AutomationTab; public class AutomationTab : TwoPanelLayout, ITab { - private readonly Configuration _config; + private readonly Configuration.Configuration _config; public AutomationTab(AutomationFilter filter, SetSelector selector, SetPanel panel, AutomationButtons buttons, AutomationHeader header, - Configuration config) + Configuration.Configuration config) { _config = config; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); @@ -30,7 +30,11 @@ public class AutomationTab : TwoPanelLayout, ITab => MainTabType.Automation; public void DrawContent() - { - Draw(TwoPanelWidth.IndeterminateRelative); - } + => Draw(_config.Ui.AutomationTabScale); + + protected override float MinimumWidth + => LeftFooter.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index df651b5..42cdc9c 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -13,19 +13,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private AutoDesignSet? _set; private int _designIndex = -1; - private readonly AutomationChanged _automationChanged; - private readonly Configuration _config; - private readonly AutoDesignManager _autoDesignManager; - private readonly RandomDesignCombo _randomDesignCombo; - private readonly AutomationSelection _selection; - private readonly DesignStorage _designs; - private readonly DesignFileSystem _designFileSystem; + private readonly AutomationChanged _automationChanged; + private readonly Configuration.Configuration _config; + private readonly AutoDesignManager _autoDesignManager; + private readonly RandomDesignCombo _randomDesignCombo; + private readonly AutomationSelection _selection; + private readonly DesignStorage _designs; + private readonly DesignFileSystem _designFileSystem; private string _newText = string.Empty; private string? _newDefinition; private Design? _newDesign; - public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager, + public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration.Configuration config, AutoDesignManager autoDesignManager, RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs) { _automationChanged = automationChanged; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index cd676b1..bbd2fe5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -19,7 +19,7 @@ public class SetPanel( CustomizeUnlockManager customizeUnlocks, CustomizeService customizations, IdentifierDrawer identifierDrawer, - Configuration config, + Configuration.Configuration config, RandomRestrictionDrawer randomDrawer, AutomationSelection selection) : IPanel { @@ -33,8 +33,7 @@ public class SetPanel( public void Draw() { - using var child = Im.Child.Begin("##Panel"u8, Im.ContentRegion.Available, true); - if (!child || selection.Index < 0) + if (selection.Index < 0) return; using (Im.Group()) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 595882f..e61fdbc 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -8,7 +8,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab; public sealed class SetSelector( AutomationSelection selection, - Configuration config, + Configuration.Configuration config, AutoDesignManager manager, AutomationFilter filter, ActorObjectManager objects, diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index 21ae0fd..0e298bd 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -5,7 +5,7 @@ namespace Glamourer.Gui.Tabs.DebugTab; public sealed class DebugTab(ServiceManager manager) : ITab { - private readonly Configuration _config = manager.GetService(); + private readonly Configuration.Configuration _config = manager.GetService(); public bool IsVisible => _config.DebugMode; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index 0bf64bb..10c5b1d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -4,7 +4,7 @@ using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public sealed class FunPanel(FunModule funModule, Configuration config) : IGameDataDrawer +public sealed class FunPanel(FunModule funModule, Configuration.Configuration config) : IGameDataDrawer { public ReadOnlySpan Label => "Fun Module"u8; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index f69d02b..3c5e6c0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Services; using ImSharp; @@ -8,18 +9,18 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignDetailTab { - private readonly SaveService _saveService; - private readonly Configuration _config; - private readonly DesignFileSystemSelector _selector; - private readonly DesignFileSystem _fileSystem; - private readonly DesignManager _manager; - private readonly DesignColors _colors; - private readonly DesignColorCombo _colorCombo; + private readonly SaveService _saveService; + private readonly Configuration.Configuration _config; + private readonly DesignFileSystemSelector _selector; + private readonly DesignFileSystem _fileSystem; + private readonly DesignManager _manager; + private readonly DesignColors _colors; + private readonly DesignColorCombo _colorCombo; private bool _editDescriptionMode; public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, - DesignColors colors, Configuration config) + DesignColors colors, Configuration.Configuration config) { _saveService = saveService; _selector = selector; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 2eb220a..33e35e2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -20,13 +20,13 @@ namespace Glamourer.Gui.Tabs.DesignTab; public sealed class DesignFileSystemSelector : FileSystemSelector, IPanel { - private readonly DesignManager _designManager; - private readonly DesignChanged _event; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly TabSelected _selectionEvent; - private readonly DesignColors _designColors; - private readonly DesignApplier _designApplier; + private readonly DesignManager _designManager; + private readonly DesignChanged _event; + private readonly Configuration.Configuration _config; + private readonly DesignConverter _converter; + private readonly TabSelected _selectionEvent; + private readonly DesignColors _designColors; + private readonly DesignApplier _designApplier; private string? _clipboardText; private Design? _cloneDesign; @@ -62,7 +62,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector 1) + //if (_selection.DesignPaths.Count > 1) + if (false) { _multiDesignPanel.Draw(); } @@ -597,163 +599,164 @@ public class DesignPanel : IPanel private static unsafe string GetUserPath() => Framework.Instance()->UserPathString; -} -private sealed class LockButton(DesignPanel panel) : Button -{ - public override bool Visible - => panel._selection.Design != null; - protected override string Description - => panel._selection.Design!.WriteProtected() - ? "Make this design editable." - : "Write-protect this design."; - - protected override FontAwesomeIcon Icon - => panel._selection.Design!.WriteProtected() - ? FontAwesomeIcon.Lock - : FontAwesomeIcon.LockOpen; - - protected override void OnClick() - => panel._manager.SetWriteProtection(panel._selection.Design!, !panel._selection.Design!.WriteProtected()); -} - -private sealed class SetFromClipboardButton(DesignPanel panel) : Button -{ - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => panel._selection.Design?.WriteProtected() ?? true; - - protected override string Description - => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Clipboard; - - protected override void OnClick() + private sealed class LockButton(DesignPanel panel) : Button { - try + public override bool Visible + => panel._selection.Design != null; + + protected override string Description + => panel._selection.Design!.WriteProtected() + ? "Make this design editable." + : "Write-protect this design."; + + protected override FontAwesomeIcon Icon + => panel._selection.Design!.WriteProtected() + ? FontAwesomeIcon.Lock + : FontAwesomeIcon.LockOpen; + + protected override void OnClick() + => panel._manager.SetWriteProtection(panel._selection.Design!, !panel._selection.Design!.WriteProtected()); + } + + private sealed class SetFromClipboardButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => panel._selection.Design?.WriteProtected() ?? true; + + protected override string Description + => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Clipboard; + + protected override void OnClick() { - var text = ImGui.GetClipboardText(); - var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._manager.ApplyDesign(panel._selection.Design!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selection.Design!.Name}.", - $"Could not apply clipboard to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); + try + { + var text = ImGui.GetClipboardText(); + var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._manager.ApplyDesign(panel._selection.Design!, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selection.Design!.Name}.", + $"Could not apply clipboard to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); + } } } -} -private sealed class DesignUndoButton(DesignPanel panel) : Button -{ - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => !panel._manager.CanUndo(panel._selection.Design) || (panel._selection.Design?.WriteProtected() ?? true); - - protected override string Description - => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.SyncAlt; - - protected override void OnClick() + private sealed class DesignUndoButton(DesignPanel panel) : Button { - try + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => !panel._manager.CanUndo(panel._selection.Design) || (panel._selection.Design?.WriteProtected() ?? true); + + protected override string Description + => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.SyncAlt; + + protected override void OnClick() { - panel._manager.UndoDesignChange(panel._selection.Design!); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selection.Design!.Name}.", - NotificationType.Error, - false); + try + { + panel._manager.UndoDesignChange(panel._selection.Design!); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selection.Design!.Name}.", + NotificationType.Error, + false); + } } } -} -private sealed class ExportToClipboardButton(DesignPanel panel) : Button -{ - public override bool Visible - => panel._selection.Design != null; - - protected override string Description - => "Copy the current design to your clipboard."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Copy; - - protected override void OnClick() + private sealed class ExportToClipboardButton(DesignPanel panel) : Button { - try + public override bool Visible + => panel._selection.Design != null; + + protected override string Description + => "Copy the current design to your clipboard."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Copy; + + protected override void OnClick() { - var text = panel._converter.ShareBase64(panel._selection.Design!); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selection.Design!.Name} data to clipboard.", - $"Could not copy data from design {panel._selection.Design!.Identifier} to clipboard", NotificationType.Error, false); + try + { + var text = panel._converter.ShareBase64(panel._selection.Design!); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selection.Design!.Name} data to clipboard.", + $"Could not copy data from design {panel._selection.Design!.Identifier} to clipboard", NotificationType.Error, false); + } } } -} -private sealed class ApplyCharacterButton(DesignPanel panel) : Button -{ - public override bool Visible - => panel._selection.Design != null && panel._objects.Player.Valid; - - protected override string Description - => "Overwrite this design with your character's current state."; - - protected override bool Disabled - => panel._selection.Design?.WriteProtected() ?? true; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.UserEdit; - - protected override void OnClick() + private sealed class ApplyCharacterButton(DesignPanel panel) : Button { - try - { - var (player, actor) = panel._objects.PlayerData; - if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) - throw new Exception("No player state available."); + public override bool Visible + => panel._selection.Design != null && panel._objects.Player.Valid; - var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._selection.Design!.GetMaterialDataRef().Clear(); - panel._manager.ApplyDesign(panel._selection.Design!, design); - } - catch (Exception ex) + protected override string Description + => "Overwrite this design with your character's current state."; + + protected override bool Disabled + => panel._selection.Design?.WriteProtected() ?? true; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.UserEdit; + + protected override void OnClick() { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selection.Design!.Name}.", - $"Could not apply player state to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); + try + { + var (player, actor) = panel._objects.PlayerData; + if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) + throw new Exception("No player state available."); + + var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._selection.Design!.GetMaterialDataRef().Clear(); + panel._manager.ApplyDesign(panel._selection.Design!, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selection.Design!.Name}.", + $"Could not apply player state to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); + } } } + + private sealed class UndoButton(DesignPanel panel) : Button + { + protected override string Description + => "Undo the last change."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Undo; + + public override bool Visible + => panel._selection.Design != null; + + protected override bool Disabled + => (panel._selection.Design?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selection.Design); + + protected override void OnClick() + => panel._history.Undo(panel._selection.Design!); + } } - -private sealed class UndoButton(DesignPanel panel) : Button -{ - protected override string Description - => "Undo the last change."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Undo; - - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => (panel._selection.Design?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selection.Design); - - protected override void OnClick() - => panel._history.Undo(panel._selection.Design!); -} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 1f018e0..4bef23b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Interop; using ImSharp; @@ -29,4 +30,13 @@ public sealed class DesignTab(DesignFileSystemSelector selector, DesignPanel pan panel.Draw(); importService.CreateCharaSource(); } + + //protected override void SetWidth(float width, ScalingMode mode) + // => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode); + // + //protected override float MinimumWidth + // => LeftFooter.MinimumWidth; + // + //protected override float MaximumWidth + // => Im.Window.Width - 500 * Im.Style.GlobalScale; } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 95c3153..cfde48b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -7,9 +8,9 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager, Configuration config) +public class ModAssociationsTab(PenumbraService penumbra, DesignSelection selection, DesignManager manager, Configuration.Configuration config) { - private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log, selector); + private readonly ModCombo _modCombo = new(penumbra, selection); private (Mod, ModSettings)[]? _copy; public void Draw() @@ -35,7 +36,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect { var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0); if (Im.Button("Copy All to Clipboard"u8, size)) - _copy = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + _copy = selection.Design!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); Im.Line.Same(); @@ -44,7 +45,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ? $"Add {_copy.Length} mod association(s) from clipboard." : "Copy some mod associations to the clipboard, first."u8, _copy is null)) foreach (var (mod, setting) in _copy!) - manager.UpdateMod(selector.Selected!, mod, setting); + manager.UpdateMod(selection.Design!, mod, setting); Im.Line.Same(); @@ -53,10 +54,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." : "Copy some mod associations to the clipboard, first."u8, _copy is null)) { - while (selector.Selected!.AssociatedMods.Count > 0) - manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]); + while (selection.Design!.AssociatedMods.Count > 0) + manager.RemoveMod(selection.Design!, selection.Design!.AssociatedMods.Keys[0]); foreach (var (mod, setting) in _copy!) - manager.AddMod(selector.Selected!, mod, setting); + manager.AddMod(selection.Design!, mod, setting); } } @@ -75,13 +76,13 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect var (id, name) = penumbra.CurrentCollection; if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero, $"Try to apply all associated mod settings to Penumbras current collection {name}", - selector.Selected!.AssociatedMods.Count is 0 || id == Guid.Empty)) + selection.Design!.AssociatedMods.Count is 0 || id == Guid.Empty)) ApplyAll(); } public void ApplyAll() { - foreach (var (mod, settings) in selector.Selected!.AssociatedMods) + foreach (var (mod, settings) in selection.Design!.AssociatedMods) penumbra.SetMod(mod, settings, StateSource.Manual, false); } @@ -103,7 +104,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect Mod? removedMod = null; (Mod mod, ModSettings settings)? updatedMod = null; - foreach (var (idx, (mod, settings)) in selector.Selected!.AssociatedMods.Index()) + foreach (var (idx, (mod, settings)) in selection.Design!.AssociatedMods.Index()) { using var id = Im.Id.Push(idx); DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp); @@ -116,10 +117,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect DrawNewModRow(table); if (removedMod.HasValue) - manager.RemoveMod(selector.Selected!, removedMod.Value); + manager.RemoveMod(selection.Design!, removedMod.Value); if (updatedMod.HasValue) - manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); + manager.UpdateMod(selection.Design!, updatedMod.Value.mod, updatedMod.Value.settings); } private void DrawAssociatedModRow(in Im.TableDisposable table, Mod mod, ModSettings settings, out Mod? removedMod, @@ -240,18 +241,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawNewModRow(in Im.TableDisposable table) { - var currentName = _modCombo.CurrentSelection.Mod.Name; + var currentDir = _modCombo.Selection; table.NextColumn(); - var tt = string.IsNullOrEmpty(currentName) + var tt = currentDir.Length is 0 ? "Please select a mod first."u8 - : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) + : selection.Design!.AssociatedMods.ContainsKey(new Mod(_modCombo.SelectionName, currentDir)) ? "The design already contains an association with the selected mod."u8 : StringU8.Empty; if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0)) - manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); + manager.AddMod(selection.Design!, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings); table.NextColumn(); - _modCombo.Draw("##new", string.IsNullOrEmpty(currentName) ? "Select new Mod..." : currentName, string.Empty, - Im.ContentRegion.Available.X, Im.Style.TextHeight); + _modCombo.Draw("##new"u8, Im.ContentRegion.Available.X); } } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 96bdb2e..58f5adb 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -3,7 +3,7 @@ using ImSharp; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection) : ImSharp.FilterComboBase(new ModFilter()) +public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection) : FilterComboBase(new ModFilter()) { public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count) { @@ -23,6 +23,21 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection public readonly bool DifferingNames = string.Equals(mod.Name, mod.DirectoryName, StringComparison.CurrentCultureIgnoreCase); } + public StringPair SelectionName { get; private set; } = new("Select new Mod...", new StringU8("Select new Mod..."u8)); + public string Selection { get; private set; } = string.Empty; + public ModSettings Settings { get; private set; } = ModSettings.Empty; + + public bool Draw(Utf8StringHandler label, float previewWidth) + { + if (!Draw(label, SelectionName.Utf8, StringU8.Empty, previewWidth, out var newItem)) + return false; + + SelectionName = newItem.Name; + Selection = newItem.Directory.Utf16; + Settings = newItem.Settings; + return true; + } + protected override float ItemHeight => Im.Style.TextHeightWithSpacing; @@ -48,7 +63,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection using var style = ImStyleSingle.PopupBorderThickness.Push(2 * Im.Style.GlobalScale); using var tt = Im.Tooltip.Begin(); - Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0)); + Im.Dummy(ImEx.ScaledVectorX(300)); using (Im.Group()) { if (item.DifferingNames) @@ -71,7 +86,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection } } - private static void DrawSettingsLeft(in ModSettings settings) + public static void DrawSettingsLeft(in ModSettings settings) { foreach (var setting in settings.Settings) { @@ -81,7 +96,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection } } - private static void DrawSettingsRight(in ModSettings settings) + public static void DrawSettingsRight(in ModSettings settings) { foreach (var setting in settings.Settings) { @@ -94,7 +109,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection } protected override bool IsSelected(CacheItem item, int globalIndex) - => throw new NotImplementedException(); + => Selection.Equals(item.Directory.Utf16, StringComparison.OrdinalIgnoreCase); private sealed class ModFilter : TextFilterBase { diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 06588fc..472f571 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -9,7 +9,7 @@ public class MultiDesignPanel( DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, - Configuration config) + Configuration.Configuration config) { private readonly DesignColorCombo _colorCombo = new(colors, true); diff --git a/Glamourer/Gui/Tabs/IncognitoButton.cs b/Glamourer/Gui/Tabs/IncognitoButton.cs index 427d388..a7f3eb9 100644 --- a/Glamourer/Gui/Tabs/IncognitoButton.cs +++ b/Glamourer/Gui/Tabs/IncognitoButton.cs @@ -3,7 +3,7 @@ using Luna; namespace Glamourer.Gui.Tabs; -public sealed class IncognitoButton(Configuration config) : BaseIconButton, IUiService +public sealed class IncognitoButton(Configuration.Configuration config) : BaseIconButton, IUiService { public override AwesomeIcon Icon => config.Ephemeral.IncognitoMode diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index 4caa246..63fcf3c 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -93,7 +93,7 @@ public class LocalNpcAppearanceData : ISavable public event Action DataChanged = null!; - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.NpcAppearanceFile; public void Save(StreamWriter writer) diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index f26e1a4..4b0daa6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,4 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; @@ -12,7 +13,7 @@ using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.NpcTab; public sealed class NpcPanel( - Configuration config, + Configuration.Configuration config, NpcSelection selection, CustomizationDrawer customizeDrawer, EquipmentDrawer equipmentDrawer, diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 27d8233..60c64a9 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,12 +1,16 @@ -using ImSharp; +using Glamourer.Configuration; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.NpcTab; public sealed class NpcTab : TwoPanelLayout, ITab { - public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header) + private readonly UiConfig _uiConfig; + + public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header, UiConfig uiConfig) { + _uiConfig = uiConfig; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); LeftPanel = selector; LeftFooter = NopHeaderFooter.Instance; @@ -22,5 +26,14 @@ public sealed class NpcTab : TwoPanelLayout, ITab => MainTabType.Npcs; public void DrawContent() - => Draw(TwoPanelWidth.IndeterminateRelative); + => Draw(_uiConfig.NpcTabScale); + + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.NpcTabScale = new TwoPanelWidth(width, mode); + + protected override float MinimumWidth + => LeftHeader.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index de40e4d..ca27f95 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -10,7 +10,7 @@ using OtterGui.Text.EndObjects; namespace Glamourer.Gui.Tabs.SettingsTab; -public class CodeDrawer(Configuration config, CodeService codeService, FunModule funModule) : IUiService +public class CodeDrawer(Configuration.Configuration config, CodeService codeService, FunModule funModule) : IUiService { private static ReadOnlySpan Tooltip => "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8 diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs index 78987e2..99b91a7 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -8,7 +8,7 @@ using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Tabs.SettingsTab; -public sealed class CollectionCombo(Configuration config, PenumbraService penumbra, Logger log) +public sealed class CollectionCombo(Configuration.Configuration config, PenumbraService penumbra, Logger log) : FilterComboCache<(Guid Id, string IdShort, string Name)>( () => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(), MouseWheelType.Control, log), IUiService diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 0a1620e..a98eb71 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -10,7 +10,7 @@ namespace Glamourer.Gui.Tabs.SettingsTab; public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, - Configuration config, + Configuration.Configuration config, ActorObjectManager objects, ActorManager actors, PenumbraService penumbra, diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index de30188..97fa1e3 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using Dalamud.Plugin.Services; using Glamourer.Automation; +using Glamourer.Configuration; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Tabs.DesignTab; @@ -14,7 +15,7 @@ using Luna; namespace Glamourer.Gui.Tabs.SettingsTab; public sealed class SettingsTab( - Configuration config, + Configuration.Configuration config, DesignFileSystemSelector selector, ContextMenuService contextMenuService, IUiBuilder uiBuilder, @@ -255,7 +256,7 @@ public sealed class SettingsTab( Im.Line.New(); Im.Text("Show the following panels in their respective tabs:"u8); Im.Dummy(Vector2.Zero); - DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => + Configuration.DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => { config.HideDesignPanel = v; config.Save(); @@ -444,7 +445,7 @@ public sealed class SettingsTab( using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name)) { if (combo) - foreach (var val in Configuration.Constants.ValidSortModes) + foreach (var val in Configuration.Configuration.Constants.ValidSortModes) { if (Im.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) { diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 62a0b41..3be59f7 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public sealed class UnlocksTab : Window, ITab { - private readonly EphemeralConfig _config; - private readonly UnlockOverview _overview; - private readonly UnlockTable _table; + private readonly Configuration.EphemeralConfig _config; + private readonly UnlockOverview _overview; + private readonly UnlockTable _table; - public UnlocksTab(EphemeralConfig config, UnlockOverview overview, UnlockTable table) + public UnlocksTab(Configuration.EphemeralConfig config, UnlockOverview overview, UnlockTable table) : base("Unlocked Equipment") { _config = config; diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 1f85612..32260ec 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -23,7 +23,7 @@ public class ContextMenuService : IDisposable private readonly MenuItem _inventoryItem; - public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration config, + public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration.Configuration config, IContextMenu context) { _contextMenu = context; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 3b99e2c..d8c6ce9 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -24,7 +24,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private readonly ThreadLocal> _deleteList = new(() => []); public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra, - Configuration config) + Configuration.Configuration config) { _stateManager = stateManager; _actors = actors; diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index c8c40db..d12b2be 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration.Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index c480249..406e830 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -11,17 +11,17 @@ namespace Glamourer.Interop.Penumbra; public class PenumbraAutoRedraw : IDisposable, IRequiredService { - private const int WaitFrames = 5; - private readonly Configuration _config; - private readonly PenumbraService _penumbra; - private readonly StateManager _state; - private readonly ActorObjectManager _objects; - private readonly IFramework _framework; - private readonly StateChanged _stateChanged; - private readonly PenumbraAutoRedrawSkip _skip; + private const int WaitFrames = 5; + private readonly Configuration.Configuration _config; + private readonly PenumbraService _penumbra; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; + private readonly IFramework _framework; + private readonly StateChanged _stateChanged; + private readonly PenumbraAutoRedrawSkip _skip; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ActorObjectManager objects, + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration.Configuration config, StateManager state, ActorObjectManager objects, IFramework framework, StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index b198f55..6759375 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -45,7 +45,7 @@ public class PenumbraService : IDisposable private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; - private readonly Configuration _config; + private readonly Configuration.Configuration _config; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; @@ -96,7 +96,7 @@ public class PenumbraService : IDisposable public int CurrentMinor { get; private set; } public DateTime AttachTime { get; private set; } - public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config) + public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration.Configuration config) { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index e9b8d9d..3beac71 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -14,8 +14,8 @@ public class BackupService : IAsyncService { _logger = logger; _fileNames = GlamourerFiles(fileNames); - _configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); - Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames)); + _configDirectory = new DirectoryInfo(fileNames.ConfigurationDirectory); + Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigurationDirectory), _fileNames)); } /// Create a permanent backup with a given name for migrations. @@ -27,7 +27,8 @@ public class BackupService : IAsyncService { var list = new List(16) { - new(fileNames.ConfigFile), + new(fileNames.ConfigurationFile), + new(fileNames.UiConfiguration), new(fileNames.DesignFileSystem), new(fileNames.MigrationDesignFile), new(fileNames.AutomationFile), diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 783a03d..bd1f9c3 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -6,8 +6,8 @@ namespace Glamourer.Services; public class CodeService { - private readonly Configuration _config; - private readonly SHA256 _hasher = SHA256.Create(); + private readonly Configuration.Configuration _config; + private readonly SHA256 _hasher = SHA256.Create(); [Flags] public enum CodeFlag : ulong @@ -87,7 +87,7 @@ public class CodeService _ => Race.Unknown, }; - public CodeService(Configuration config) + public CodeService(Configuration.Configuration config) { _config = config; Load(); diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 0a5c97b..164e4b1 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -41,7 +41,7 @@ public sealed class CollectionOverrideService : IService, ISavable public IReadOnlyList<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> Overrides => _overrides; - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.CollectionOverrideFile; public void AddOverride(IEnumerable identifiers, Guid collectionId, string displayName) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e078557..8a7c086 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -24,26 +24,26 @@ public class CommandService : IDisposable, IApiService private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ActorObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; - private readonly CustomizeService _customizeService; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignResolver _resolver; - private readonly PenumbraService _penumbra; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly Configuration.Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly CustomizeService _customizeService; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, - DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration.Configuration config, ModSettingApplier modApplier, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) { diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index a0a3914..dc6291f 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -8,19 +8,19 @@ namespace Glamourer.Services; public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) { - private Configuration _config = null!; - private JObject _data = null!; + private Configuration.Configuration _config = null!; + private JObject _data = null!; - public void Migrate(Configuration config) + public void Migrate(Configuration.Configuration config) { _config = config; - if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(saveService.FileNames.ConfigFile)) + if (config.Version >= Configuration.Configuration.Constants.CurrentVersion || !File.Exists(saveService.FileNames.ConfigurationFile)) { AddColors(config, false); return; } - _data = JObject.Parse(File.ReadAllText(saveService.FileNames.ConfigFile)); + _data = JObject.Parse(File.ReadAllText(saveService.FileNames.ConfigurationFile)); MigrateV1To2(); MigrateV2To4(); MigrateV4To5(); @@ -103,7 +103,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Codes = _config.Codes.DistinctBy(c => c.Code).ToList(); } - private static void AddColors(Configuration config, bool forceSave) + private static void AddColors(Configuration.Configuration config, bool forceSave) { var save = false; foreach (var color in ColorId.Values) diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index cd25c64..60f1773 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -1,40 +1,28 @@ using Dalamud.Plugin; using Glamourer.Designs; +using Luna; namespace Glamourer.Services; -public class FilenameService +public class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) { - public readonly string ConfigDirectory; - public readonly string ConfigFile; - public readonly string DesignFileSystem; - public readonly string MigrationDesignFile; - public readonly string DesignDirectory; - public readonly string AutomationFile; - public readonly string UnlockFileCustomize; - public readonly string UnlockFileItems; - public readonly string FavoriteFile; - public readonly string DesignColorFile; - public readonly string EphemeralConfigFile; - public readonly string NpcAppearanceFile; - public readonly string CollectionOverrideFile; - - public FilenameService(IDalamudPluginInterface pi) - { - ConfigDirectory = pi.ConfigDirectory.FullName; - ConfigFile = pi.ConfigFile.FullName; - AutomationFile = Path.Combine(ConfigDirectory, "automation.json"); - DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json"); - MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json"); - UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json"); - UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); - DesignDirectory = Path.Combine(ConfigDirectory, "designs"); - FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); - DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); - EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); - NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); - CollectionOverrideFile = Path.Combine(ConfigDirectory, "collection_overrides.json"); - } + public readonly string DesignFileSystem = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json"); + public readonly string MigrationDesignFile = Path.Combine(pi.ConfigDirectory.FullName, "Designs.json"); + public readonly string DesignDirectory = Path.Combine(pi.ConfigDirectory.FullName, "designs"); + public readonly string AutomationFile = Path.Combine(pi.ConfigDirectory.FullName, "automation.json"); + public readonly string UnlockFileCustomize = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_customize.json"); + public readonly string UnlockFileItems = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_items.json"); + public readonly string FavoriteFile = Path.Combine(pi.ConfigDirectory.FullName, "favorites.json"); + public readonly string DesignColorFile = Path.Combine(pi.ConfigDirectory.FullName, "design_colors.json"); + public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json"); + public readonly string NpcAppearanceFile = Path.Combine(pi.ConfigDirectory.FullName, "npc_appearance_data.json"); + public readonly string CollectionOverrideFile = Path.Combine(pi.ConfigDirectory.FullName, "collection_overrides.json"); + public readonly string UiConfiguration = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json"); + public readonly string FileSystemFolder = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem"); + public readonly string FileSystemEmptyFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "empty_folders.json"); + public readonly string FileSystemExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "expanded_folders.json"); + public readonly string FileSystemLockedNodes = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "locked_nodes.json"); + public readonly string FileSystemSelectedNodes = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "selected_nodes.json"); public IEnumerable Designs() { @@ -50,4 +38,8 @@ public class FilenameService public string DesignFile(Design design) => DesignFile(design.Identifier.ToString()); + + // TODO + public override List GetBackupFiles() + => []; } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index a885b54..d26a7f3 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -15,7 +15,7 @@ public class ItemManager public const string SmallClothesNpc = "Smallclothes (NPC)"; public const ushort SmallClothesNpcModel = 9903; - private readonly Configuration _config; + private readonly Configuration.Configuration _config; public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; @@ -26,7 +26,7 @@ public class ItemManager public readonly EquipItem DefaultSword; - public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, + public ItemManager(Configuration.Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems) { _config = config; diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs index 6711ce3..639026a 100644 --- a/Glamourer/Services/PcpService.cs +++ b/Glamourer/Services/PcpService.cs @@ -10,14 +10,14 @@ namespace Glamourer.Services; public class PcpService : IRequiredService { - private readonly Configuration _config; - private readonly PenumbraService _penumbra; - private readonly ActorObjectManager _objects; - private readonly StateManager _state; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; + private readonly Configuration.Configuration _config; + private readonly PenumbraService _penumbra; + private readonly ActorObjectManager _objects; + private readonly StateManager _state; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; - public PcpService(Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, + public PcpService(Configuration.Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, DesignConverter designConverter, DesignManager designManager) { _config = config; diff --git a/Glamourer/Services/SaveService.cs b/Glamourer/Services/SaveService.cs index 2a96755..1ccaaea 100644 --- a/Glamourer/Services/SaveService.cs +++ b/Glamourer/Services/SaveService.cs @@ -1,17 +1,11 @@ -using OtterGui.Classes; -using OtterGui.Log; +using Luna; namespace Glamourer.Services; /// /// Any file type that we want to save via SaveService. /// -public interface ISavable : ISavable -{ } +public interface ISavable : ISavable; -public sealed class SaveService : SaveServiceBase -{ - public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) - : base(log, framework, fileNames) - { } -} +public sealed class SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) + : BaseSaveService(log, framework, fileNames); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index f9e63f9..e65f7ea 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -73,8 +73,8 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index fbb6a55..6199d33 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -23,19 +23,19 @@ public unsafe class FunModule : IDisposable AprilFirst, } - private readonly WorldSets _worldSets = new(); - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly Configuration _config; - private readonly CodeService _codes; - private readonly Random _rng; - private readonly GenericPopupWindow _popupWindow; - private readonly StateManager _stateManager; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; - private readonly ActorObjectManager _objects; - private readonly NpcCustomizeSet _npcs; - private readonly StainId[] _stains; + private readonly WorldSets _worldSets = new(); + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly Configuration.Configuration _config; + private readonly CodeService _codes; + private readonly Random _rng; + private readonly GenericPopupWindow _popupWindow; + private readonly StateManager _stateManager; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + private readonly ActorObjectManager _objects; + private readonly NpcCustomizeSet _npcs; + private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; private FunEquipSet? _festivalSet; @@ -66,7 +66,7 @@ public unsafe class FunModule : IDisposable internal void ResetFestival() => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); - public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, + public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration.Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ActorObjectManager objects, DesignConverter designConverter, DesignManager designManager, NpcCustomizeSet npcs) { diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index cbce7d5..305575f 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -20,18 +20,18 @@ public class StateEditor( StateChanged stateChanged, StateFinalized stateFinalized, JobChangeState jobChange, - Configuration config, + Configuration.Configuration config, ItemManager items, DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) : IDesignEditor { - protected readonly InternalStateEditor Editor = editor; - protected readonly StateApplier Applier = applier; - protected readonly StateChanged StateChanged = stateChanged; - protected readonly StateFinalized StateFinalized = stateFinalized; - protected readonly Configuration Config = config; - protected readonly ItemManager Items = items; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateFinalized StateFinalized = stateFinalized; + protected readonly Configuration.Configuration Config = config; + protected readonly ItemManager Items = items; /// Turn an actor to. public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 241a0b1..85878ed 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -25,31 +25,31 @@ namespace Glamourer.State; /// public class StateListener : IDisposable { - private readonly Configuration _config; - private readonly ActorManager _actors; - private readonly ActorObjectManager _objects; - private readonly StateManager _manager; - private readonly StateApplier _applier; - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly PenumbraService _penumbra; - private readonly EquipSlotUpdating _equipSlotUpdating; - private readonly BonusSlotUpdating _bonusSlotUpdating; - private readonly GearsetDataLoaded _gearsetDataLoaded; - private readonly WeaponLoading _weaponLoading; - private readonly HeadGearVisibilityChanged _headGearVisibility; - private readonly VisorStateChanged _visorState; - private readonly VieraEarStateChanged _vieraEarState; - private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateFinalized _stateFinalized; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly FunModule _funModule; - private readonly HumanModelList _humans; - private readonly MovedEquipment _movedEquipment; - private readonly GPoseService _gPose; - private readonly ChangeCustomizeService _changeCustomizeService; - private readonly CrestService _crestService; - private readonly ICondition _condition; + private readonly Configuration.Configuration _config; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _manager; + private readonly StateApplier _applier; + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly PenumbraService _penumbra; + private readonly EquipSlotUpdating _equipSlotUpdating; + private readonly BonusSlotUpdating _bonusSlotUpdating; + private readonly GearsetDataLoaded _gearsetDataLoaded; + private readonly WeaponLoading _weaponLoading; + private readonly HeadGearVisibilityChanged _headGearVisibility; + private readonly VisorStateChanged _visorState; + private readonly VieraEarStateChanged _vieraEarState; + private readonly WeaponVisibilityChanged _weaponVisibility; + private readonly StateFinalized _stateFinalized; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly FunModule _funModule; + private readonly HumanModelList _humans; + private readonly MovedEquipment _movedEquipment; + private readonly GPoseService _gPose; + private readonly ChangeCustomizeService _changeCustomizeService; + private readonly CrestService _crestService; + private readonly ICondition _condition; private readonly Dictionary _fistOffhands = []; @@ -58,7 +58,7 @@ public class StateListener : IDisposable private ActorState? _creatingState; private ActorState? _customizeState; - public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, + public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration.Configuration config, EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 633c444..c0fffab 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -27,7 +27,7 @@ public sealed class StateManager( InternalStateEditor editor, HumanModelList humans, IClientState clientState, - Configuration config, + Configuration.Configuration config, JobChangeState jobChange, DesignMerger merger, ModSettingApplier modApplier, diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 6fcdde0..71e99b3 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -139,7 +139,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable _setUnlockLinkValueHook.Original(uiState, data, value); try { - if (value == 0) + if (value is 0) return; var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -159,7 +159,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable } } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.UnlockFileCustomize; public void Save() @@ -169,7 +169,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => UnlockDictionaryHelpers.Save(writer, Unlocked); private void Load() - => UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), + => UnlockDictionaryHelpers.Load(ToFilePath(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), "customization"); /// Create a list of all unlockable hairstyles and face paints. diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 6caa6a1..1f6f3f5 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -94,7 +94,7 @@ public sealed class FavoriteManager : ISavable Save(); } - public string ToFilename(FilenameService fileNames) + public string ToFilePath(FilenameService fileNames) => fileNames.FavoriteFile; private void Save() diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 6708267..6f8e097 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -254,7 +254,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary fileNames.UnlockFileItems; public void Save() @@ -265,7 +265,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _items.ItemData.TryGetValue(id, EquipSlot.MainHand, out _), "item"); UpdateModels(version); } diff --git a/Luna b/Luna index 0ee15af..bff400e 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit 0ee15af5c45b6c72df25b972e48b9a57347c25a5 +Subproject commit bff400eb44aeded41fb6b1b3d35948881b578737 From 85bd351d9b5f22ee54077b8724e50175c924a5ee Mon Sep 17 00:00:00 2001 From: Exter-N Date: Thu, 19 Feb 2026 22:22:02 +0100 Subject: [PATCH 4/8] Add a Toggle Main Window button to the QDB --- Glamourer/Gui/DesignQuickBar.cs | 85 ++++++++++++------- Glamourer/Gui/MainWindow.cs | 8 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index cd76118..a0a6ddc 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -24,6 +24,7 @@ public enum QdbButtons ReapplyAutomation = 0x40, ResetSettings = 0x80, RevertAdvancedCustomization = 0x100, + ToggleMainWindow = 0x200, } public sealed class DesignQuickBar : Window, IDisposable @@ -45,6 +46,8 @@ public sealed class DesignQuickBar : Window, IDisposable private int _numButtons; private readonly StringBuilder _tooltipBuilder = new(512); + public event Action? ToggleMainWindow; + public DesignQuickBar(Configuration.Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking) @@ -77,38 +80,41 @@ public sealed class DesignQuickBar : Window, IDisposable public override void PreDraw() { Flags = GetFlags; - UpdateWidth(); _style.Push(ImStyleDouble.WindowPadding, new Vector2(Im.Style.GlobalScale * 4)) .Push(ImStyleSingle.WindowBorderThickness, 0); _style.Push(ImGuiColor.WindowBackground, ColorId.QuickDesignBg.Value()) .Push(ImGuiColor.Button, ColorId.QuickDesignButton.Value()) .Push(ImGuiColor.FrameBackground, ColorId.QuickDesignFrame.Value()); + + UpdateWidth(); } public override void PostDraw() => _style.Dispose(); - public void DrawAtEnd(float yPos) + public void DrawAtEnd(float yPos, bool mainWindow) { - var width = UpdateWidth(); + var numButtons = CalculateButtonCount(mainWindow); + var width = CalculateWidth(numButtons, mainWindow); Im.Cursor.Position = new Vector2(Im.ContentRegion.Maximum.X - width, yPos - Im.Style.GlobalScale); - Draw(); + Draw(Im.ContentRegion.Available.X, numButtons, mainWindow); } public override void Draw() - => Draw(Im.ContentRegion.Available.X); + => Draw(Im.ContentRegion.Available.X, _numButtons, false); - private void Draw(float width) + private void Draw(float width, int numButtons, bool mainWindow) { using var group = Im.Group(); var spacing = Im.Style.ItemInnerSpacing; using var style = ImStyleDouble.ItemSpacing.Push(spacing); var buttonSize = new Vector2(Im.Style.FrameHeight); PrepareButtons(); + DrawToggleMainWindowButton(buttonSize, mainWindow); if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) { - var comboSize = width - _numButtons * (buttonSize.X + spacing.X); + var comboSize = width - numButtons * (buttonSize.X + spacing.X); _designCombo.Draw(StringU8.Empty, comboSize); Im.Line.Same(); DrawApplyButton(buttonSize); @@ -485,6 +491,16 @@ public sealed class DesignQuickBar : Window, IDisposable } } + private void DrawToggleMainWindowButton(Vector2 buttonSize, bool mainWindow) + { + if (mainWindow || !_config.QdbButtons.HasFlag(QdbButtons.ToggleMainWindow)) + return; + + if (ImEx.Icon.Button(FontAwesomeIcon.TheaterMasks.Icon(), "Toggle Glamourer's main window."u8, ToggleMainWindow is null, buttonSize)) + ToggleMainWindow?.Invoke(); + Im.Line.Same(); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(AwesomeIcon icon, Vector2 buttonSize, int available) { var enumerator = _tooltipBuilder.GetChunks(); @@ -516,44 +532,51 @@ public sealed class DesignQuickBar : Window, IDisposable return _keyState[key.Hotkey] && key.Modifiers.IsActive(); } - private float UpdateWidth() + private int CalculateButtonCount(bool mainWindow) { - _numButtons = 0; + var numButtons = 0; if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll)) - ++_numButtons; + ++numButtons; if (_config.EnableAutoDesigns) { if (_config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation)) - ++_numButtons; + ++numButtons; } if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) - ++_numButtons; + ++numButtons; if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) - ++_numButtons; + ++numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) - { - ++_numButtons; - Size = new Vector2((7 + _numButtons) * Im.Style.FrameHeight + _numButtons * Im.Style.ItemInnerSpacing.X, - Im.Style.FrameHeight); - } - else - { - Size = new Vector2( - _numButtons * Im.Style.FrameHeight - + (_numButtons - 1) * Im.Style.ItemInnerSpacing.X - + Im.Style.WindowPadding.X * 2, - Im.Style.FrameHeight); - } + ++numButtons; + if (!mainWindow && _config.QdbButtons.HasFlag(QdbButtons.ToggleMainWindow)) + ++numButtons; - return Size.Value.X; + return numButtons; + } + + private float CalculateWidth(int numButtons, bool mainWindow) + { + var content = _config.QdbButtons.HasFlag(QdbButtons.ApplyDesign) + ? (7 + numButtons) * Im.Style.FrameHeight + numButtons * Im.Style.ItemInnerSpacing.X + : numButtons * Im.Style.FrameHeight + (numButtons - 1) * Im.Style.ItemInnerSpacing.X; + var padding = mainWindow ? 0 : Im.Style.WindowPadding.X * 2; + + return content + padding; + } + + private void UpdateWidth() + { + _numButtons = CalculateButtonCount(false); + var width = CalculateWidth(_numButtons, false); + Size = new Vector2(width, Im.Style.FrameHeight); } } diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d1d5e3b..038c336 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -32,6 +32,7 @@ public sealed class MainWindow : Window, IDisposable IsOpen = _config.OpenWindowAtStart; _penumbra.DrawSettingsSection += _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + _quickBar.ToggleMainWindow += Toggle; } public void OpenSettings() @@ -49,7 +50,10 @@ public sealed class MainWindow : Window, IDisposable } public void Dispose() - => _penumbra.DrawSettingsSection -= _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + { + _penumbra.DrawSettingsSection -= _mainTabBar.Settings.DrawPenumbraIntegrationSettings; + _quickBar.ToggleMainWindow -= Toggle; + } public override void Draw() { @@ -74,7 +78,7 @@ public sealed class MainWindow : Window, IDisposable { _mainTabBar.Draw(); if (_config.ShowQuickBarInTabs) - _quickBar.DrawAtEnd(yPos); + _quickBar.DrawAtEnd(yPos, true); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 97fa1e3..90cc17e 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -302,6 +302,7 @@ public sealed class SettingsTab( private readonly (StringU8, QdbButtons)[] _columns = [ + (new StringU8("Toggle Main Window"u8), QdbButtons.ToggleMainWindow), (new StringU8("Apply Design"u8), QdbButtons.ApplyDesign), (new StringU8("Revert All"u8), QdbButtons.RevertAll), (new StringU8("Revert to Auto"u8), QdbButtons.RevertAutomation), @@ -325,7 +326,7 @@ public sealed class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var numColumns = 9 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 10 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); Im.Line.New(); Im.Text("Show the Following Buttons in the Quick Design Bar:"u8); Im.Dummy(Vector2.Zero); From 04a8449967eb3de0463c16ed575e07f133f28514 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 Feb 2026 01:23:35 +0100 Subject: [PATCH 5/8] Add Ignored Mods. --- Glamourer/Config/IgnoredMods.cs | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Glamourer/Config/IgnoredMods.cs diff --git a/Glamourer/Config/IgnoredMods.cs b/Glamourer/Config/IgnoredMods.cs new file mode 100644 index 0000000..d123108 --- /dev/null +++ b/Glamourer/Config/IgnoredMods.cs @@ -0,0 +1,84 @@ +using Glamourer.Services; +using Luna; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Config; + +public sealed class IgnoredMods : ConfigurationFile, IReadOnlySet +{ + public override int CurrentVersion + => 1; + + private readonly HashSet _ignoredMods = []; + + public IgnoredMods(SaveService saveService, MessageService messageService) + : base(saveService, messageService) + { + Load(); + } + + protected override void AddData(JsonTextWriter j) + { + j.WritePropertyName("IgnoredMods"); + j.WriteStartArray(); + foreach (var mod in _ignoredMods) + j.WriteValue(mod); + j.WriteEndArray(); + } + + protected override void LoadData(JObject j) + { + _ignoredMods.Clear(); + if (j["IgnoredMods"] is not JArray arr) + return; + + foreach (var value in arr.Values().OfType()) + _ignoredMods.Add(value); + } + + public override string ToFilePath(FilenameService fileNames) + => fileNames.IgnoredModsFile; + + public IEnumerator GetEnumerator() + => _ignoredMods.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _ignoredMods.Count; + + public void Add(string mod) + { + if (_ignoredMods.Add(mod)) + Save(); + } + + public void Remove(string mod) + { + if (_ignoredMods.Remove(mod)) + Save(); + } + + public bool Contains(string item) + => _ignoredMods.Contains(item); + + public bool IsProperSubsetOf(IEnumerable other) + => _ignoredMods.IsProperSubsetOf(other); + + public bool IsProperSupersetOf(IEnumerable other) + => _ignoredMods.IsProperSupersetOf(other); + + public bool IsSubsetOf(IEnumerable other) + => _ignoredMods.IsSubsetOf(other); + + public bool IsSupersetOf(IEnumerable other) + => _ignoredMods.IsSupersetOf(other); + + public bool Overlaps(IEnumerable other) + => _ignoredMods.Overlaps(other); + + public bool SetEquals(IEnumerable other) + => _ignoredMods.SetEquals(other); +} From b88bba1a8758ea69a4aa6b3fad32761fa09fc744 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 Feb 2026 01:31:30 +0100 Subject: [PATCH 6/8] Current state. --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 7 +- Glamourer/Api/GlamourerApi.cs | 3 +- Glamourer/Automation/AutoDesignApplier.cs | 27 +- Glamourer/Automation/FixedDesignMigrator.cs | 2 +- .../Configuration.cs | 56 +- .../DefaultDesignSettings.cs | 2 +- .../DesignPanelFlag.cs | 6 +- .../EphemeralConfig.cs | 4 +- .../HeightDisplayType.cs | 2 +- .../{Configuration => Config}/UiConfig.cs | 2 +- Glamourer/Designs/Design.cs | 21 +- Glamourer/Designs/DesignColors.cs | 3 +- Glamourer/Designs/DesignData.cs | 5 +- Glamourer/Designs/DesignEditor.cs | 6 +- Glamourer/Designs/DesignFileSystem.cs | 219 ++----- Glamourer/Designs/DesignFileSystemSaver.cs | 57 ++ Glamourer/Designs/DesignManager.cs | 4 +- Glamourer/Designs/Links/DesignMerger.cs | 3 +- Glamourer/Designs/SortMode.cs | 99 +++ .../Designs/Special/RandomDesignGenerator.cs | 4 +- Glamourer/Designs/Special/RandomPredicate.cs | 8 +- Glamourer/Events/DesignChanged.cs | 8 +- Glamourer/Events/TabSelected.cs | 3 +- Glamourer/Glamourer.cs | 3 +- Glamourer/Glamourer.csproj | 13 +- Glamourer/Glamourer.csproj.DotSettings | 2 + Glamourer/Gui/Colors.cs | 3 +- .../CustomizationDrawer.Simple.cs | 2 +- .../Gui/Customization/CustomizationDrawer.cs | 5 +- .../Customization/CustomizeParameterDrawer.cs | 7 +- Glamourer/Gui/DesignCombo.cs | 43 +- Glamourer/Gui/DesignQuickBar.cs | 25 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 5 +- Glamourer/Gui/GenericPopupWindow.cs | 11 +- Glamourer/Gui/GlamourerChangelog.cs | 9 +- Glamourer/Gui/GlamourerWindowSystem.cs | 3 +- Glamourer/Gui/MainTabBar.cs | 4 +- Glamourer/Gui/MainWindow.cs | 15 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 4 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 5 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 6 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs | 4 +- .../Tabs/AutomationTab/AutomationButtons.cs | 5 +- .../Tabs/AutomationTab/AutomationHeader.cs | 27 +- .../Gui/Tabs/AutomationTab/AutomationTab.cs | 7 +- .../AutomationTab/RandomRestrictionDrawer.cs | 23 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 3 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 6 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 5 +- .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 2 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 9 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 5 +- .../Tabs/DesignTab/ApplyCharacterButton.cs | 55 ++ .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 86 +-- .../DesignTab/DesignFileSystemSelector.cs | 405 ------------ Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs | 102 ++- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 43 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 581 ++++++------------ .../Gui/Tabs/DesignTab/DesignSelection.cs | 12 - Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 59 +- .../Gui/Tabs/DesignTab/DesignUndoButton.cs | 39 ++ .../Tabs/DesignTab/ExportToClipboardButton.cs | 36 ++ Glamourer/Gui/Tabs/DesignTab/LockButton.cs | 28 + .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 33 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 7 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 276 ++++----- .../Selector/DeleteSelectionButton.cs | 45 ++ .../Selector/DesignFileSystemCache.cs | 112 ++++ .../Selector/DesignFileSystemDrawer.cs | 65 ++ .../Tabs/DesignTab/Selector/DesignFilter.cs | 150 +++++ .../DesignTab/Selector/DesignFilterToken.cs | 49 ++ .../Selector/DuplicateDesignButton.cs | 38 ++ .../DesignTab/Selector/ImportDesignButton.cs | 52 ++ .../DesignTab/Selector/MoveDesignInput.cs | 34 + .../DesignTab/Selector/NewDesignButton.cs | 28 + .../DesignTab/Selector/RenameDesignInput.cs | 34 + .../Tabs/DesignTab/SetFromClipboardButton.cs | 44 ++ Glamourer/Gui/Tabs/DesignTab/UndoButton.cs | 27 + Glamourer/Gui/Tabs/HeaderDrawer.cs | 80 --- Glamourer/Gui/Tabs/IncognitoButton.cs | 5 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 73 +-- .../Gui/Tabs/SettingsTab/CollectionCombo.cs | 73 ++- .../SettingsTab/CollectionOverrideDrawer.cs | 18 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 74 ++- .../Gui/Tabs/UnlocksTab/UnlockCacheItem.cs | 3 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 66 +- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 4 +- Glamourer/Interop/ContextMenuService.cs | 3 +- Glamourer/Interop/Material/MaterialManager.cs | 3 +- .../Interop/Penumbra/ModSettingApplier.cs | 13 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 19 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 5 +- Glamourer/Services/BackupService.cs | 27 +- Glamourer/Services/CodeService.cs | 28 +- Glamourer/Services/CommandService.cs | 37 +- Glamourer/Services/ConfigMigrationService.cs | 31 +- Glamourer/Services/DesignResolver.cs | 9 +- Glamourer/Services/FilenameService.cs | 22 +- Glamourer/Services/ItemManager.cs | 5 +- Glamourer/Services/PcpService.cs | 17 +- Glamourer/Services/ServiceManager.cs | 7 +- Glamourer/State/FunModule.cs | 29 +- Glamourer/State/StateEditor.cs | 16 +- Glamourer/State/StateListener.cs | 53 +- Glamourer/State/StateManager.cs | 3 +- Glamourer/packages.lock.json | 6 - Luna | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 115 files changed, 2225 insertions(+), 1776 deletions(-) rename Glamourer/{Configuration => Config}/Configuration.cs (75%) rename Glamourer/{Configuration => Config}/DefaultDesignSettings.cs (85%) rename Glamourer/{Configuration => Config}/DesignPanelFlag.cs (93%) rename Glamourer/{Configuration => Config}/EphemeralConfig.cs (93%) rename Glamourer/{Configuration => Config}/HeightDisplayType.cs (87%) rename Glamourer/{Configuration => Config}/UiConfig.cs (95%) create mode 100644 Glamourer/Designs/DesignFileSystemSaver.cs create mode 100644 Glamourer/Designs/SortMode.cs create mode 100644 Glamourer/Glamourer.csproj.DotSettings create mode 100644 Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs delete mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs delete mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/LockButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/UndoButton.cs delete mode 100644 Glamourer/Gui/Tabs/HeaderDrawer.cs diff --git a/Glamourer.Api b/Glamourer.Api index 51b3c72..941dc7e 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 51b3c72e91816af0002dd543d64944e777b246ba +Subproject commit 941dc7e1da694127a4405f4888ae162133131268 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index d1f08e7..d2da5b1 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -2,7 +2,6 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; -using ImSharp; using Luna; using Newtonsoft.Json.Linq; @@ -12,7 +11,6 @@ public class DesignsApi( ApiHelpers helpers, DesignManager designs, StateManager stateManager, - DesignFileSystem fileSystem, DesignColors color, DesignConverter converter) : IGlamourerApiDesigns, IApiService @@ -21,12 +19,11 @@ public class DesignsApi( => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); public Dictionary GetDesignListExtended() - => fileSystem.ToDictionary(kvp => kvp.Key.Identifier, - kvp => (kvp.Key.Name.Text, kvp.Value.FullName(), color.GetColor(kvp.Key).Color, kvp.Key.QuickDesign)); + => designs.Designs.ToDictionary(d => d.Identifier, d => (d.DisplayName, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign)); public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) => designs.Designs.ByIdentifier(designId) is { } d - ? (d.Name.Text, fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d).Color, d.QuickDesign) + ? (d.Name.Text, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign) : (string.Empty, string.Empty, 0, false); public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index c176a30..5ff5d56 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -1,9 +1,10 @@ using Glamourer.Api.Api; +using Glamourer.Config; using Luna; namespace Glamourer.Api; -public class GlamourerApi(Configuration.Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; public const int CurrentApiVersionMinor = 7; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 71b76a3..f88fd69 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; @@ -16,22 +17,22 @@ namespace Glamourer.Automation; public sealed class AutoDesignApplier : IDisposable { - private readonly Configuration.Configuration _config; - private readonly AutoDesignManager _manager; - private readonly StateManager _state; - private readonly JobService _jobs; - private readonly EquippedGearset _equippedGearset; - private readonly ActorManager _actors; - private readonly AutomationChanged _event; - private readonly ActorObjectManager _objects; - private readonly WeaponLoading _weapons; - private readonly HumanModelList _humans; - private readonly DesignMerger _designMerger; - private readonly IClientState _clientState; + private readonly Configuration _config; + private readonly AutoDesignManager _manager; + private readonly StateManager _state; + private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly ActorObjectManager _objects; + private readonly WeaponLoading _weapons; + private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; + private readonly IClientState _clientState; private readonly JobChangeState _jobChangeState; - public AutoDesignApplier(Configuration.Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, + public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) { diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 1621588..3340457 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -47,7 +47,7 @@ public class FixedDesignMigrator(JobService jobs) var set = autoManager[^1]; foreach (var design in data.AsEnumerable().Reverse()) { - if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf) + if (!designFileSystem.Find(design.Item1, out var child) || child is not IFileSystemData leaf) { Glamourer.Messager.NotificationMessage($"Could not find design with path {design.Item1}, skipped fixed design.", NotificationType.Warning); diff --git a/Glamourer/Configuration/Configuration.cs b/Glamourer/Config/Configuration.cs similarity index 75% rename from Glamourer/Configuration/Configuration.cs rename to Glamourer/Config/Configuration.cs index 417850f..31452bc 100644 --- a/Glamourer/Configuration/Configuration.cs +++ b/Glamourer/Config/Configuration.cs @@ -7,14 +7,16 @@ using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Services; using ImSharp; using Luna; +using Luna.Generators; using Newtonsoft.Json; -using OtterGui.Filesystem; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer.Configuration; +namespace Glamourer.Config; -public class Configuration : IPluginConfiguration, ISavable +public sealed partial class Configuration : IPluginConfiguration, ISavable { + public const int CurrentVersion = 9; + [JsonIgnore] public readonly EphemeralConfig Ephemeral; @@ -58,8 +60,11 @@ public class Configuration : IPluginConfiguration, ISavable public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); - public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; - public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; + public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; + + [ConfigProperty(EventName = "OnRenameChanged")] + private RenameField _showRename = RenameField.BothDataPrio; + public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control); @@ -70,7 +75,7 @@ public class Configuration : IPluginConfiguration, ISavable [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] - public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; + public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; public List<(string Code, bool Enabled)> Codes { get; set; } = []; @@ -80,7 +85,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool DebugMode { get; set; } = false; #endif - public int Version { get; set; } = Constants.CurrentVersion; + public int Version { get; set; } = CurrentVersion; public Dictionary Colors { get; private set; } = ColorId.Values.ToDictionary(c => c, c => c.Data().DefaultColor); @@ -142,45 +147,22 @@ public class Configuration : IPluginConfiguration, ISavable serializer.Serialize(jWriter, this); } - public static class Constants - { - public const int CurrentVersion = 8; - - public static readonly ISortMode[] ValidSortModes = - [ - ISortMode.FoldersFirst, - ISortMode.Lexicographical, - new DesignFileSystem.CreationDate(), - new DesignFileSystem.InverseCreationDate(), - new DesignFileSystem.UpdateDate(), - new DesignFileSystem.InverseUpdateDate(), - ISortMode.InverseFoldersFirst, - ISortMode.InverseLexicographical, - ISortMode.FoldersLast, - ISortMode.InverseFoldersLast, - ISortMode.InternalOrder, - ISortMode.InverseInternalOrder, - ]; - } - /// Convert SortMode Types to their name. - private class SortModeConverter : JsonConverter> + private class SortModeConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, ISortMode? value, JsonSerializer serializer) { - value ??= ISortMode.FoldersFirst; + value ??= ISortMode.FoldersFirst; serializer.Serialize(writer, value.GetType().Name); } - public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, - bool hasExistingValue, + public override ISortMode ReadJson(JsonReader reader, Type objectType, ISortMode? existingValue, bool hasExistingValue, JsonSerializer serializer) { - var name = serializer.Deserialize(reader); - if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode)) - return existingValue ?? ISortMode.FoldersFirst; + if (serializer.Deserialize(reader) is { } name) + return ISortMode.Valid.GetValueOrDefault(name, existingValue ?? ISortMode.FoldersFirst); - return mode; + return existingValue ?? ISortMode.FoldersFirst; } } } diff --git a/Glamourer/Configuration/DefaultDesignSettings.cs b/Glamourer/Config/DefaultDesignSettings.cs similarity index 85% rename from Glamourer/Configuration/DefaultDesignSettings.cs rename to Glamourer/Config/DefaultDesignSettings.cs index 4ed7dfe..b96cf85 100644 --- a/Glamourer/Configuration/DefaultDesignSettings.cs +++ b/Glamourer/Config/DefaultDesignSettings.cs @@ -1,4 +1,4 @@ -namespace Glamourer.Configuration; +namespace Glamourer.Config; public class DefaultDesignSettings { diff --git a/Glamourer/Configuration/DesignPanelFlag.cs b/Glamourer/Config/DesignPanelFlag.cs similarity index 93% rename from Glamourer/Configuration/DesignPanelFlag.cs rename to Glamourer/Config/DesignPanelFlag.cs index c381222..c3d1915 100644 --- a/Glamourer/Configuration/DesignPanelFlag.cs +++ b/Glamourer/Config/DesignPanelFlag.cs @@ -1,7 +1,7 @@ using ImSharp; using Luna.Generators; -namespace Glamourer.Configuration; +namespace Glamourer.Config; [Flags] [NamedEnum(Utf16: false)] @@ -40,9 +40,9 @@ public enum DesignPanelFlag : uint public static partial class DesignPanelFlagExtensions { - private static readonly StringU8 Expand = new("Expand"u8); + private static readonly StringU8 Expand = new("Expand"u8); - public static Im.HeaderDisposable Header(this DesignPanelFlag flag, global::Glamourer.Configuration.Configuration config) + public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config) { if (config.HideDesignPanel.HasFlag(flag)) return default; diff --git a/Glamourer/Configuration/EphemeralConfig.cs b/Glamourer/Config/EphemeralConfig.cs similarity index 93% rename from Glamourer/Configuration/EphemeralConfig.cs rename to Glamourer/Config/EphemeralConfig.cs index ebf1c75..96699b5 100644 --- a/Glamourer/Configuration/EphemeralConfig.cs +++ b/Glamourer/Config/EphemeralConfig.cs @@ -6,11 +6,11 @@ using Luna.Generators; using Newtonsoft.Json; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -namespace Glamourer.Configuration; +namespace Glamourer.Config; public partial class EphemeralConfig : ISavable { - public int Version { get; set; } = Configuration.Constants.CurrentVersion; + public int Version { get; set; } = Configuration.CurrentVersion; [ConfigProperty] private bool _incognitoMode; diff --git a/Glamourer/Configuration/HeightDisplayType.cs b/Glamourer/Config/HeightDisplayType.cs similarity index 87% rename from Glamourer/Configuration/HeightDisplayType.cs rename to Glamourer/Config/HeightDisplayType.cs index 5ff79e0..f16fdec 100644 --- a/Glamourer/Configuration/HeightDisplayType.cs +++ b/Glamourer/Config/HeightDisplayType.cs @@ -1,6 +1,6 @@ using Luna.Generators; -namespace Glamourer.Configuration; +namespace Glamourer.Config; [TooltipEnum] public enum HeightDisplayType diff --git a/Glamourer/Configuration/UiConfig.cs b/Glamourer/Config/UiConfig.cs similarity index 95% rename from Glamourer/Configuration/UiConfig.cs rename to Glamourer/Config/UiConfig.cs index c1ee1b4..e76266a 100644 --- a/Glamourer/Configuration/UiConfig.cs +++ b/Glamourer/Config/UiConfig.cs @@ -4,7 +4,7 @@ using Luna.Generators; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Glamourer.Configuration; +namespace Glamourer.Config; public sealed partial class UiConfig : ConfigurationFile { diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 69f4404..8804c8e 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -14,7 +14,7 @@ using Notification = Luna.Notification; namespace Glamourer.Designs; -public sealed class Design : DesignBase, ISavable, IDesignStandIn +public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemValue { #region Data @@ -44,6 +44,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public new const int FileVersion = 2; public Guid Identifier { get; internal init; } + public IFileSystemData? Node { get; set; } public DateTimeOffset CreationDate { get; internal init; } public DateTimeOffset LastEdit { get; internal set; } public LowerString Name { get; internal set; } = LowerString.Empty; @@ -57,6 +58,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string Color { get; internal set; } = string.Empty; public SortedList AssociatedMods { get; private set; } = []; public LinkContainer Links { get; private set; } = []; + public DataPath Path { get; } = new(); public string Incognito => Identifier.ToString()[..8]; @@ -124,6 +126,12 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["Mods"] = SerializeMods(), ["Links"] = Links.Serialize(), }; + if (Path.Folder.Length > 0) + ret["FileSystemFolder"] = Path.Folder; + if (Path.SortName is not null) + ret["SortOrderName"] = Path.SortName; + + return ret; } @@ -251,6 +259,9 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn }; if (design.LastEdit < creationDate) design.LastEdit = creationDate; + design.Path.Folder = json["FileSystemFolder"]?.Value() ?? string.Empty; + design.Path.SortName = json["SortOrderName"]?.Value()?.FixName(); + design.SetWriteProtected(json["WriteProtected"]?.ToObject() ?? false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); @@ -340,7 +351,13 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn } public string LogName(string fileName) - => Path.GetFileNameWithoutExtension(fileName); + => System.IO.Path.GetFileNameWithoutExtension(fileName); #endregion + + string IFileSystemValue.Identifier + => Identifier.ToString(); + + public string DisplayName + => Name.Text; } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index b9200eb..1dec82c 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Config; using Glamourer.Gui; using Glamourer.Services; using ImSharp; @@ -8,7 +9,7 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Designs; -public class DesignColorUi(DesignColors colors, Configuration.Configuration config) +public class DesignColorUi(DesignColors colors, Configuration config) { private string _newName = string.Empty; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index c7ca8e5..5ace949 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,6 +1,5 @@ using Glamourer.GameData; using Glamourer.Services; -using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String.Functions; @@ -47,8 +46,8 @@ public unsafe struct DesignData { } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - public readonly bool ContainsName(LowerString name) - => ItemNames.Any(name.IsContained); + public readonly bool ContainsName(string name) + => ItemNames.Any(i => i.Contains(name, StringComparison.OrdinalIgnoreCase)); public readonly StainIds Stain(EquipSlot slot) { diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 828ed43..1bfc4d3 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -1,3 +1,4 @@ +using Glamourer.Config; using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; @@ -5,7 +6,6 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; using ImSharp; -using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,14 +16,14 @@ public class DesignEditor( DesignChanged designChanged, CustomizeService customizations, ItemManager items, - Configuration.Configuration config) + Configuration config) : IDesignEditor { protected readonly DesignChanged DesignChanged = designChanged; protected readonly SaveService SaveService = saveService; protected readonly ItemManager Items = items; protected readonly CustomizeService Customizations = customizations; - protected readonly Configuration.Configuration Config = config; + protected readonly Configuration Config = config; protected readonly Dictionary UndoStore = []; private bool _forceFullItemOff; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 9aa5fb8..4810174 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -3,195 +3,64 @@ using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using Luna; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Glamourer.Designs; -public sealed class DesignFileSystem : OtterGui.Filesystem.FileSystem, IDisposable, ISavable +public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredService { - private readonly DesignChanged _designChanged; + private readonly DesignFileSystemSaver _saver; + private readonly DesignChanged _designChanged; - private readonly SaveService _saveService; - private readonly DesignManager _designManager; - - public DesignFileSystem(DesignManager designManager, SaveService saveService, DesignChanged designChanged) + public DesignFileSystem(Logger log, SaveService saveService, DesignStorage designs, DesignChanged designChanged) + : base("DesignFileSystem", log, true) { - _designManager = designManager; - _saveService = saveService; _designChanged = designChanged; - _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystem); - Changed += OnChange; - Reload(); + _saver = new DesignFileSystemSaver(log, this, saveService, designs); + + _saver.Load(); + _designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystem); } - private void Reload() + private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _) { - if (Load(new FileInfo(_saveService.FileNames.DesignFileSystem), _designManager.Designs, DesignToIdentifier, DesignToName)) - _saveService.ImmediateSave(this); + switch (type) + { + case DesignChanged.Type.ReloadedAll: _saver.Load(); break; + case DesignChanged.Type.Created: + var parent = Root; + if (design.Path.Folder.Length > 0) + try + { + parent = FindOrCreateAllFolders(design.Path.Folder); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, + $"Could not move design to {design.Path} because the folder could not be created.", + NotificationType.Error); + } - Glamourer.Log.Debug("Reloaded design filesystem."); + var (data, _) = CreateDuplicateDataNode(parent, design.Path.SortName ?? design.Name, design); + Selection.Select(data); + break; + case DesignChanged.Type.Deleted: + if (design.Node is { } node) + { + if (node.Selected) + Selection.UnselectAll(); + Delete(node); + } + + break; + case DesignChanged.Type.Renamed when design.Path.SortName is null: + RenameWithDuplicates(design.Node!, design.Path.GetIntendedName(design.Name.Text)); + break; + // TODO: Maybe add path changes? + } } public void Dispose() { - _designChanged.Unsubscribe(OnDesignChange); - } - - public struct CreationDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Creation Date (Older First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate)); - } - - public struct UpdateDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Update Date (Older First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit)); - } - - public struct InverseCreationDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Creation Date (Newer First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate)); - } - - public struct InverseUpdateDate : OtterGui.Filesystem.ISortMode - { - public ReadOnlySpan Name - => "Update Date (Newer First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit)); - } - - private void OnChange(OtterGui.Filesystem.FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3) - { - if (type != OtterGui.Filesystem.FileSystemChangeType.Reload) - _saveService.QueueSave(this); - } - - private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? data) - { - switch (type) - { - case DesignChanged.Type.Created: - var parent = Root; - if ((data as CreationTransaction?)?.Path is { } path) - try - { - parent = FindOrCreateAllFolders(path); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.", - NotificationType.Error); - } - - CreateDuplicateLeaf(parent, design.Name.Text, design); - - return; - case DesignChanged.Type.Deleted: - if (TryGetValue(design, out var leaf1)) - Delete(leaf1); - return; - case DesignChanged.Type.ReloadedAll: - Reload(); - return; - case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName: - if (!TryGetValue(design, out var leaf2)) - return; - - var old = oldName.FixName(); - if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old) - RenameWithDuplicates(leaf2, design.Name); - return; - } - } - - // Used for saving and loading. - private static string DesignToIdentifier(Design design) - => design.Identifier.ToString(); - - private static string DesignToName(Design design) - => design.Name.Text.FixName(); - - private static bool DesignHasDefaultPath(Design design, string fullPath) - { - var regex = new Regex($@"^{Regex.Escape(DesignToName(design))}( \(\d+\))?$"); - return regex.IsMatch(fullPath); - } - - private static (string, bool) SaveDesign(Design design, string fullPath) - // Only save pairs with non-default paths. - => DesignHasDefaultPath(design, fullPath) - ? (string.Empty, false) - : (DesignToIdentifier(design), true); - - internal static void MigrateOldPaths(SaveService saveService, Dictionary oldPaths) - { - if (oldPaths.Count == 0) - return; - - var file = saveService.FileNames.DesignFileSystem; - try - { - JObject jObject; - if (File.Exists(file)) - { - var text = File.ReadAllText(file); - jObject = JObject.Parse(text); - var dict = jObject["Data"]?.ToObject>(); - if (dict != null) - foreach (var (key, value) in dict) - oldPaths.TryAdd(key, value); - - jObject["Data"] = JToken.FromObject(oldPaths); - } - else - { - jObject = new JObject - { - ["Data"] = JToken.FromObject(oldPaths), - ["EmptyFolders"] = JToken.FromObject(Array.Empty()), - }; - } - - var data = jObject.ToString(Formatting.Indented); - File.WriteAllText(file, data); - } - catch (Exception ex) - { - Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}"); - } - } - - public string ToFilePath(FilenameService fileNames) - => fileNames.DesignFileSystem; - - public void Save(StreamWriter writer) - { - SaveToFile(writer, SaveDesign, true); + _designChanged.Unsubscribe(OnDesignChanged); } } diff --git a/Glamourer/Designs/DesignFileSystemSaver.cs b/Glamourer/Designs/DesignFileSystemSaver.cs new file mode 100644 index 0000000..9899b28 --- /dev/null +++ b/Glamourer/Designs/DesignFileSystemSaver.cs @@ -0,0 +1,57 @@ +using Glamourer.Services; +using Luna; + +namespace Glamourer.Designs; + +public sealed class DesignFileSystemSaver(Logger log, BaseFileSystem fileSystem, SaveService saveService, DesignStorage designs) + : FileSystemSaver(log, fileSystem, saveService) +{ + protected override void SaveDataValue(IFileSystemValue value) + { + if (value is Design design) + SaveService.QueueSave(design); + } + + protected override string LockedFile(FilenameService provider) + => provider.FileSystemLockedNodes; + + protected override string ExpandedFile(FilenameService provider) + => provider.FileSystemExpandedFolders; + + protected override string EmptyFoldersFile(FilenameService provider) + => provider.FileSystemEmptyFolders; + + protected override string SelectionFile(FilenameService provider) + => provider.FileSystemSelectedNodes; + + protected override string MigrationFile(FilenameService provider) + => provider.MigrationDesignFileSystem; + + protected override bool GetValueFromIdentifier(ReadOnlySpan identifier, [NotNullWhen(true)] out IFileSystemValue? value) + { + if (!Guid.TryParse(identifier, out var guid)) + { + value = null; + return false; + } + + value = designs.FirstOrDefault(d => d.Identifier == guid); + return value is not null; + } + + protected override void CreateDataNodes() + { + foreach (var design in designs) + { + try + { + var folder = design.Path.Folder.Length is 0 ? FileSystem.Root : FileSystem.FindOrCreateAllFolders(design.Path.Folder); + FileSystem.CreateDuplicateDataNode(folder, design.Path.SortName ?? design.Name, design); + } + catch (Exception ex) + { + Log.Error($"Could not create folder structure for design {design.Name} at path {design.Path.Folder}: {ex}"); + } + } + } +} diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 2e1a1f9..ab0551b 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,4 +1,5 @@ using Dalamud.Utility; +using Glamourer.Config; using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; @@ -21,7 +22,7 @@ public sealed class DesignManager : DesignEditor private readonly HumanModelList _humans; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, - DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration.Configuration config) + DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) : base(saveService, @event, customizations, items, config) { Designs = storage; @@ -545,7 +546,6 @@ public sealed class DesignManager : DesignEditor } } - DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths); Glamourer.Log.Information( $"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs."); } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 709ae47..a75c7cb 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,5 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; @@ -15,7 +16,7 @@ namespace Glamourer.Designs.Links; public class DesignMerger( DesignManager designManager, CustomizeService customizeService, - Configuration.Configuration config, + Configuration config, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks) : IService { diff --git a/Glamourer/Designs/SortMode.cs b/Glamourer/Designs/SortMode.cs new file mode 100644 index 0000000..db746b2 --- /dev/null +++ b/Glamourer/Designs/SortMode.cs @@ -0,0 +1,99 @@ +using System.Collections.Frozen; +using Luna; + +namespace Glamourer.Designs; + +public readonly struct CreationDate : ISortMode +{ + public static readonly CreationDate Instance = new(); + + public ReadOnlySpan Name + => "Creation Date (Older First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast().Concat(f.GetLeaves().OfType>().OrderBy(l => l.Value.CreationDate)); +} + +public readonly struct UpdateDate : ISortMode +{ + public static readonly UpdateDate Instance = new(); + + public ReadOnlySpan Name + => "Update Date (Older First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast().Concat(f.GetLeaves().OfType>().OrderBy(l => l.Value.LastEdit)); +} + +public readonly struct InverseCreationDate : ISortMode +{ + public static readonly InverseCreationDate Instance = new(); + + public ReadOnlySpan Name + => "Creation Date (Newer First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast() + .Concat(f.GetLeaves().OfType>().OrderByDescending(l => l.Value.CreationDate)); +} + +public readonly struct InverseUpdateDate : ISortMode +{ + public static readonly InverseUpdateDate Instance = new(); + + public ReadOnlySpan Name + => "Update Date (Newer First)"u8; + + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; + + public IEnumerable GetChildren(IFileSystemFolder f) + => f.GetSubFolders().Cast() + .Concat(f.GetLeaves().OfType>().OrderByDescending(l => l.Value.LastEdit)); +} + +public static class SortModeExtensions +{ + private static readonly FrozenDictionary ValidSortModes = new Dictionary + { + [nameof(ISortMode.FoldersFirst)] = ISortMode.FoldersFirst, + [nameof(ISortMode.Lexicographical)] = ISortMode.Lexicographical, + [nameof(CreationDate)] = ISortMode.CreationDate, + [nameof(InverseCreationDate)] = ISortMode.InverseCreationDate, + [nameof(UpdateDate)] = ISortMode.UpdateDate, + [nameof(InverseUpdateDate)] = ISortMode.InverseUpdateDate, + [nameof(ISortMode.InverseFoldersFirst)] = ISortMode.InverseFoldersFirst, + [nameof(ISortMode.InverseLexicographical)] = ISortMode.InverseLexicographical, + [nameof(ISortMode.FoldersLast)] = ISortMode.FoldersLast, + [nameof(ISortMode.InverseFoldersLast)] = ISortMode.InverseFoldersLast, + [nameof(ISortMode.InternalOrder)] = ISortMode.InternalOrder, + [nameof(ISortMode.InverseInternalOrder)] = ISortMode.InverseInternalOrder, + }.ToFrozenDictionary(); + + extension(ISortMode) + { + public static ISortMode CreationDate + => CreationDate.Instance; + + public static ISortMode InverseCreationDate + => InverseCreationDate.Instance; + + public static ISortMode UpdateDate + => UpdateDate.Instance; + + public static ISortMode InverseUpdateDate + => InverseUpdateDate.Instance; + + public static IReadOnlyDictionary Valid + => ValidSortModes; + } +} diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index ab736f4..0c62aa2 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -1,9 +1,9 @@ - +using Glamourer.Config; using Luna; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration.Configuration config) : IService +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { private readonly Random _rng = new(); private readonly WeakReference _lastDesign = new(null!, false); diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs index ae05f8f..172d249 100644 --- a/Glamourer/Designs/Special/RandomPredicate.cs +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -10,19 +10,19 @@ public interface IDesignPredicate => Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath); public IEnumerable Get(IEnumerable designs, DesignFileSystem fileSystem) - => designs.Select(d => Transform(d, fileSystem)) + => designs.Select(Transform) .Where(Invoke) .Select(t => t.Design); public static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs, DesignFileSystem fileSystem) => predicates.Count > 0 - ? designs.Select(d => Transform(d, fileSystem)) + ? designs.Select(Transform) .Where(t => predicates.Any(p => p.Invoke(t))) .Select(t => t.Design) : designs; - private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs) - => (d, d.Name.Lower, d.Identifier.ToString(), fs.TryGetValue(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); + private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d) + => (d, d.Name.Lower, d.Identifier.ToString(), d.Path.CurrentPath.ToLowerInvariant()); } public static class RandomPredicate diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 04bb46a..83fc18b 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,6 +1,7 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui; +using Glamourer.Gui.Tabs.DesignTab; using OtterGui.Classes; namespace Glamourer.Events; @@ -135,10 +136,13 @@ public sealed class DesignChanged() /// AutoDesignManager = 1, - /// + /// DesignFileSystem = 0, - /// + /// + DesignHeader = 0, + + /// DesignFileSystemSelector = -1, /// diff --git a/Glamourer/Events/TabSelected.cs b/Glamourer/Events/TabSelected.cs index 8dcf917..1880d37 100644 --- a/Glamourer/Events/TabSelected.cs +++ b/Glamourer/Events/TabSelected.cs @@ -1,5 +1,6 @@ using Glamourer.Designs; using Glamourer.Gui; +using Glamourer.Gui.Tabs.DesignTab; using OtterGui.Classes; namespace Glamourer.Events; @@ -16,7 +17,7 @@ public sealed class TabSelected() { public enum Priority { - /// + /// DesignSelector = 0, /// diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index c059b4f..bb6dd4f 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Interop; @@ -61,7 +62,7 @@ public class Glamourer : IDalamudPlugin public string GatherSupportInformation() { var sb = new StringBuilder(10240); - var config = _services.GetService(); + var config = _services.GetService(); sb.AppendLine("**Settings**"); sb.Append($"> **`Plugin Version: `** {Version}\n"); sb.Append($"> **`Commit Hash: `** {CommitHash}\n"); diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 4bd1c25..0c1602c 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer @@ -22,6 +22,11 @@ + + false + false + + @@ -31,7 +36,8 @@ - + @@ -50,7 +56,8 @@ - + diff --git a/Glamourer/Glamourer.csproj.DotSettings b/Glamourer/Glamourer.csproj.DotSettings new file mode 100644 index 0000000..9252e38 --- /dev/null +++ b/Glamourer/Glamourer.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index dab72db..a1e9780 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,3 +1,4 @@ +using Glamourer.Config; using ImSharp; namespace Glamourer.Gui; @@ -83,6 +84,6 @@ public static class Colors => _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor; /// Set the configurable colors dictionary to a value. - public static void SetColors(Configuration.Configuration config) + public static void SetColors(Configuration config) => _colors = config.Colors; } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index dd8a272..4665880 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,5 +1,5 @@ using System.Text.Unicode; -using Glamourer.Configuration; +using Glamourer.Config; using ImSharp; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index d6ebf5d..a5b22b3 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; +using Glamourer.Config; using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; @@ -14,7 +15,7 @@ namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( ITextureProvider textures, CustomizeService service, - Configuration.Configuration config, + Configuration config, FavoriteManager favorites, HeightService heightService) : IDisposable @@ -81,7 +82,7 @@ public partial class CustomizationDrawer( private CustomizeValue _currentByte = CustomizeValue.Zero; private bool _currentApply; private int _currentCount; - private StringU8 _currentOption = StringU8.Empty; + private StringU8 _currentOption = StringU8.Empty; // Prepare a new customization option. private Im.IdDisposable SetId(CustomizeIndex index) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 65a4e7d..e9004ac 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.PalettePlus; using Glamourer.State; @@ -7,7 +8,7 @@ using Luna; namespace Glamourer.Gui.Customization; -public class CustomizeParameterDrawer(Configuration.Configuration config, PaletteImport import) : IService +public class CustomizeParameterDrawer(Configuration config, PaletteImport import) : IService { private readonly Dictionary _lastData = []; private StringU8 _paletteName = StringU8.Empty; @@ -125,7 +126,7 @@ public class CustomizeParameterDrawer(Configuration.Configuration config, Palett DrawColorFormatOptions(withApply); var value = config.ShowColorConfig; Im.Line.Same(); - if (Im.Checkbox("Show Config"u8, ref value)) + if (Im.Checkbox("Show Configuration"u8, ref value)) { config.ShowColorConfig = value; config.Save(); diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index cead406..6763cfe 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -9,7 +9,7 @@ using Luna; namespace Glamourer.Gui; public abstract class DesignComboBase( - Configuration.EphemeralConfig config, + Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -17,18 +17,18 @@ public abstract class DesignComboBase( DesignFileSystem designFileSystem) : FilterComboBase(new DesignFilter(), ConfigData.Default with { ComputeWidth = true }) { - protected readonly Configuration.EphemeralConfig Config = config; - protected readonly DesignChanged DesignChanged = designChanged; - protected readonly DesignColors DesignColors = designColors; - protected readonly DesignFileSystem DesignFileSystem = designFileSystem; - protected readonly TabSelected TabSelected = tabSelected; - protected readonly DesignManager Designs = designs; - protected IDesignStandIn? CurrentDesign; + protected readonly Config.EphemeralConfig Config = config; + protected readonly DesignChanged DesignChanged = designChanged; + protected readonly DesignColors DesignColors = designColors; + protected readonly DesignFileSystem DesignFileSystem = designFileSystem; + protected readonly TabSelected TabSelected = tabSelected; + protected readonly DesignManager Designs = designs; + protected IDesignStandIn? CurrentDesign; protected CacheItem CreateItem(IDesignStandIn design) { var color = design is Design d1 ? DesignColors.GetColor(d1).ToVector() : ColorId.NormalDesign.Value().ToVector(); - var path = design is Design d2 && DesignFileSystem.TryGetValue(d2, out var leaf) ? leaf.FullName() : string.Empty; + var path = design is Design d2 ? d2.Node!.FullPath : string.Empty; var name = design.ResolveName(false); if (path == name) path = string.Empty; @@ -118,7 +118,10 @@ public abstract class DesignComboBase( protected override void ComputeWidth() => ComboWidth = UnfilteredItems.Max(d - => d.Name.Utf8.CalculateSize(false).X + d.FullPath.Utf8.CalculateSize(false).X + 2 * Im.Style.ItemSpacing.X + Im.Style.ScrollbarSize); + => d.Name.Utf8.CalculateSize(false).X + + d.FullPath.Utf8.CalculateSize(false).X + + 2 * Im.Style.ItemSpacing.X + + Im.Style.ScrollbarSize); protected override void Dispose(bool disposing) { @@ -203,7 +206,7 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService } - public QuickDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, + public QuickDesignCombo(Config.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, DesignFileSystem designFileSystem, DesignManager designs) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem) { @@ -264,7 +267,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable { public Design? NewSelection { get; private set; } - public LinkDesignCombo(Configuration.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, + public LinkDesignCombo(Config.EphemeralConfig config, DesignChanged designChanged, DesignColors designColors, TabSelected tabSelected, DesignFileSystem designFileSystem, DesignManager designs) : base(config, designs, designChanged, designColors, tabSelected, designFileSystem) { @@ -295,7 +298,7 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable } public sealed class RandomDesignCombo( - Configuration.EphemeralConfig config, + Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, @@ -307,14 +310,10 @@ public sealed class RandomDesignCombo( { return exact.Which switch { - RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value), - RandomPredicate.Exact.Type.Path => DesignFileSystem.Find(exact.Value.Text, out var c) && c is DesignFileSystem.Leaf l - ? l.Value - : null, - RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) - ? g - : Guid.Empty), - _ => null, + RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value), + RandomPredicate.Exact.Type.Path => Designs.Designs.FirstOrDefault(d => d.Node!.FullPath == exact.Value.Text), + RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) ? g : Guid.Empty), + _ => null, }; } @@ -346,7 +345,7 @@ public sealed class SpecialDesignCombo : DesignComboBase, IUiService private readonly CacheItem _revert; private readonly CacheItem _quick; - public SpecialDesignCombo(Configuration.EphemeralConfig config, + public SpecialDesignCombo(Config.EphemeralConfig config, DesignManager designs, DesignChanged designChanged, DesignColors designColors, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index cd76118..30bab89 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using Dalamud.Plugin.Services; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -33,19 +34,19 @@ public sealed class DesignQuickBar : Window, IDisposable ? WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing | WindowFlags.NoMove : WindowFlags.NoDecoration | WindowFlags.NoDocking | WindowFlags.NoFocusOnAppearing; - private readonly Configuration.Configuration _config; - private readonly QuickDesignCombo _designCombo; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ActorObjectManager _objects; - private readonly PenumbraService _penumbra; - private readonly IKeyState _keyState; - private readonly Im.ColorStyleDisposable _style = new(); - private DateTime _keyboardToggle = DateTime.UnixEpoch; - private int _numButtons; - private readonly StringBuilder _tooltipBuilder = new(512); + private readonly Configuration _config; + private readonly QuickDesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ActorObjectManager _objects; + private readonly PenumbraService _penumbra; + private readonly IKeyState _keyState; + private readonly Im.ColorStyleDisposable _style = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); - public DesignQuickBar(Configuration.Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, + public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", WindowFlags.NoDecoration | WindowFlags.NoDocking) { diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 217d8c4..ba69c20 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Config; using Glamourer.Events; using Glamourer.Gui.Materials; using Glamourer.Services; @@ -22,7 +23,7 @@ public class EquipmentDrawer private readonly BonusItemCombo[] _bonusItemCombo; private readonly Dictionary _weaponCombo; private readonly TextureService _textures; - private readonly Configuration.Configuration _config; + private readonly Configuration _config; private readonly GPoseService _gPose; private readonly AdvancedDyePopup _advancedDyes; private readonly ItemCopyService _itemCopy; @@ -32,7 +33,7 @@ public class EquipmentDrawer private EquipSlot _dragTarget; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, - Configuration.Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) + Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) { _items = items; _textures = textures; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index cb13a18..f0a6441 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,17 +1,18 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Config; using ImSharp; namespace Glamourer.Gui; public class GenericPopupWindow : Luna.Window { - private readonly Configuration.Configuration _config; - private readonly ICondition _condition; - private readonly IClientState _state; - public bool OpenFestivalPopup { get; internal set; } + private readonly Configuration _config; + private readonly ICondition _condition; + private readonly IClientState _state; + public bool OpenFestivalPopup { get; internal set; } - public GenericPopupWindow(Configuration.Configuration config, IClientState state, ICondition condition) + public GenericPopupWindow(Configuration config, IClientState state, ICondition condition) : base("Glamourer Popups", WindowFlags.NoBringToFrontOnFocus | WindowFlags.NoDecoration diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a30dce6..a6c3e48 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -1,3 +1,4 @@ +using Glamourer.Config; using ImSharp; using Luna; @@ -5,11 +6,11 @@ namespace Glamourer.Gui; public class GlamourerChangelog { - public const int LastChangelogVersion = 0; - private readonly Configuration.Configuration _config; - public readonly Changelog Changelog; + public const int LastChangelogVersion = 0; + private readonly Configuration _config; + public readonly Changelog Changelog; - public GlamourerChangelog(Configuration.Configuration config) + public GlamourerChangelog(Configuration config) { _config = config; Changelog = new Changelog("Glamourer Changelog", ConfigData, Save); diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 6379a4c..820927c 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -1,5 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; +using Glamourer.Config; using Glamourer.Gui.Tabs.UnlocksTab; namespace Glamourer.Gui; @@ -11,7 +12,7 @@ public class GlamourerWindowSystem : IDisposable private readonly MainWindow _ui; public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, - Configuration.Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) + Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; _ui = ui; diff --git a/Glamourer/Gui/MainTabBar.cs b/Glamourer/Gui/MainTabBar.cs index 0862faa..23f5180 100644 --- a/Glamourer/Gui/MainTabBar.cs +++ b/Glamourer/Gui/MainTabBar.cs @@ -14,11 +14,11 @@ namespace Glamourer.Gui; public sealed class MainTabBar : TabBar { - private readonly Configuration.EphemeralConfig _config; + private readonly Config.EphemeralConfig _config; public readonly TabSelected Event; public readonly SettingsTab Settings; - public MainTabBar(Logger log, Configuration.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, + public MainTabBar(Logger log, Config.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, AutomationTab automation, UnlocksTab unlocks, NpcTab npcs, MessagesTab messages, DebugTab debug, TabSelected @event) : base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug) { diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d1d5e3b..1952418 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; +using Glamourer.Config; using Glamourer.Interop.Penumbra; using ImSharp; using Luna; @@ -8,20 +9,20 @@ namespace Glamourer.Gui; public sealed class MainWindow : Window, IDisposable { - private readonly Configuration.Configuration _config; - private readonly PenumbraService _penumbra; - private readonly DesignQuickBar _quickBar; - private readonly MainTabBar _mainTabBar; - private bool _ignorePenumbra; + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly DesignQuickBar _quickBar; + private readonly MainTabBar _mainTabBar; + private bool _ignorePenumbra; - public MainWindow(IDalamudPluginInterface pi, Configuration.Configuration config, PenumbraService penumbra, + public MainWindow(IDalamudPluginInterface pi, Configuration config, PenumbraService penumbra, MainTabBar mainTabBar, DesignQuickBar quickBar) : base("GlamourerMainWindow") { pi.UiBuilder.DisableGposeUiHide = true; SizeConstraints = new WindowSizeConstraints { - MinimumSize = new Vector2(700, 675), + MinimumSize = new Vector2(700, 675), MaximumSize = new Vector2(3840, 2160), }; _mainTabBar = mainTabBar; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 20d65dc..cd5da51 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -2,7 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; @@ -17,7 +17,7 @@ using Notification = Luna.Notification; namespace Glamourer.Gui.Materials; public sealed unsafe class AdvancedDyePopup( - Configuration.Configuration config, + Configuration config, StateManager stateManager, LiveColorTablePreviewer preview, DirectXService directX) : IService diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index db9f8ce..d55666d 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.Interop.Material; using ImSharp; using Luna; @@ -7,7 +8,7 @@ using Penumbra.GameData.Files.MaterialStructs; namespace Glamourer.Gui.Materials; -public class MaterialDrawer(DesignManager designManager, Configuration.Configuration config) : IService +public class MaterialDrawer(DesignManager designManager, Configuration config) : IService { public const float GlossWidth = 100; public const float SpecularStrengthWidth = 125; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 0e8ef3c..f0f5be4 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui.Customization; @@ -25,7 +25,7 @@ public sealed class ActorPanel : IPanel private readonly CustomizationDrawer _customizationDrawer; private readonly EquipmentDrawer _equipmentDrawer; private readonly AutoDesignApplier _autoDesignApplier; - private readonly Configuration.Configuration _config; + private readonly Configuration _config; private readonly DesignConverter _converter; private readonly ActorObjectManager _objects; private readonly ImportService _importService; @@ -37,7 +37,7 @@ public sealed class ActorPanel : IPanel CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, AutoDesignApplier autoDesignApplier, - Configuration.Configuration config, + Configuration config, DesignConverter converter, ActorObjectManager objects, DesignManager designManager, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 82d54ff..d4f3f9c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -14,7 +14,7 @@ public readonly struct ActorCacheItem(ActorIdentifier identifier, ActorData data public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label)); } -public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Configuration.EphemeralConfig config) : IPanel +public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Config.EphemeralConfig config) : IPanel { public ReadOnlySpan Id => "ActorSelector"u8; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index c250d7a..ed97ddc 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,4 +1,4 @@ -using Glamourer.Configuration; +using Glamourer.Config; using ImSharp; using Luna; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs index 907c1f4..4386d14 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorsHeader.cs @@ -6,10 +6,10 @@ namespace Glamourer.Gui.Tabs.ActorTab; public sealed class ActorsHeader : SplitButtonHeader { private readonly ActorSelection _selection; - private readonly Configuration.EphemeralConfig _config; + private readonly Config.EphemeralConfig _config; public ActorsHeader(SetFromClipboardButton setFromClipboard, ExportToClipboardButton exportToClipboard, SaveAsDesignButton save, - UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Configuration.EphemeralConfig config) + UndoButton undo, LockedButton locked, IncognitoButton incognito, ActorSelection selection, Config.EphemeralConfig config) { _selection = selection; _config = config; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs index 6ef6583..8c0d1b6 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationButtons.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Config; using ImSharp; using Luna; using Penumbra.GameData.Actors; @@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab; public sealed class AutomationButtons : ButtonFooter { - public AutomationButtons(Configuration.Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects) + public AutomationButtons(Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects) { Buttons.AddButton(new AddButton(objects, manager), 100); Buttons.AddButton(new DuplicateButton(selection, manager), 90); @@ -134,7 +135,7 @@ public sealed class AutomationButtons : ButtonFooter } } - private sealed class DeleteButton(AutomationSelection selection, Configuration.Configuration config, AutoDesignManager manager) + private sealed class DeleteButton(AutomationSelection selection, Configuration config, AutoDesignManager manager) : BaseIconButton { private bool _enabled; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs index f5381e7..93c5dbb 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationHeader.cs @@ -1,13 +1,28 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; -public sealed class AutomationHeader(Configuration.Configuration config, AutomationSelection selection) : IHeader +public sealed class AutomationHeader : SplitButtonHeader { - public bool Collapsed - => false; + private readonly Configuration _config; + private readonly AutomationSelection _selection; - public void Draw(Vector2 size) - => ImEx.TextFramed(config.Ephemeral.IncognitoMode ? selection.Incognito : selection.Name, size with { Y = Im.Style.FrameHeight }); + public AutomationHeader(Configuration config, AutomationSelection selection, IncognitoButton incognito) + { + _config = config; + _selection = selection; + RightButtons.AddButton(incognito, 100); + } + + public override void Draw(Vector2 size) + { + var color = ColorId.HeaderButtons.Value(); + using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color); + base.Draw(size with { Y = Im.Style.FrameHeight }); + } + + public override ReadOnlySpan Text + => _config.Ephemeral.IncognitoMode ? _selection.Incognito : _selection.Name; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index a2c601b..441ae26 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -1,14 +1,15 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; public class AutomationTab : TwoPanelLayout, ITab { - private readonly Configuration.Configuration _config; + private readonly Configuration _config; public AutomationTab(AutomationFilter filter, SetSelector selector, SetPanel panel, AutomationButtons buttons, AutomationHeader header, - Configuration.Configuration config) + Configuration config) { _config = config; LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index 42cdc9c..87bf442 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -1,5 +1,6 @@ using Dalamud.Interface; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; @@ -13,19 +14,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private AutoDesignSet? _set; private int _designIndex = -1; - private readonly AutomationChanged _automationChanged; - private readonly Configuration.Configuration _config; - private readonly AutoDesignManager _autoDesignManager; - private readonly RandomDesignCombo _randomDesignCombo; - private readonly AutomationSelection _selection; - private readonly DesignStorage _designs; - private readonly DesignFileSystem _designFileSystem; + private readonly AutomationChanged _automationChanged; + private readonly Configuration _config; + private readonly AutoDesignManager _autoDesignManager; + private readonly RandomDesignCombo _randomDesignCombo; + private readonly AutomationSelection _selection; + private readonly DesignStorage _designs; + private readonly DesignFileSystem _designFileSystem; private string _newText = string.Empty; private string? _newDefinition; private Design? _newDesign; - public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration.Configuration config, AutoDesignManager autoDesignManager, + public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager, RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs) { _automationChanged = automationChanged; @@ -268,19 +269,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable LookupTooltip(designs); } - private void LookupTooltip(IEnumerable designs) + private static void LookupTooltip(IEnumerable designs) { using var _ = Im.Tooltip.Begin(); using var enumerator = designs.GetEnumerator(); while (enumerator.MoveNext()) { Im.Text("Matches the following designs:"u8); - var name = _designFileSystem.TryGetValue(enumerator.Current, out var l) ? l.FullName() : enumerator.Current.Name.Text; + var name = enumerator.Current.Path.CurrentPath; Im.Separator(); Im.BulletText(name); while (enumerator.MoveNext()) { - name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text; + name = enumerator.Current.Path.CurrentPath; Im.BulletText(name); } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index bbd2fe5..e2ee40e 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -4,6 +4,7 @@ using Glamourer.Designs.Special; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; +using Glamourer.Config; using ImSharp; using Luna; using Penumbra.GameData.Enums; @@ -19,7 +20,7 @@ public class SetPanel( CustomizeUnlockManager customizeUnlocks, CustomizeService customizations, IdentifierDrawer identifierDrawer, - Configuration.Configuration config, + Configuration config, RandomRestrictionDrawer randomDrawer, AutomationSelection selection) : IPanel { diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index e61fdbc..10fb70d 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Events; using ImSharp; using Luna; @@ -8,7 +9,7 @@ namespace Glamourer.Gui.Tabs.AutomationTab; public sealed class SetSelector( AutomationSelection selection, - Configuration.Configuration config, + Configuration config, AutoDesignManager manager, AutomationFilter filter, ActorObjectManager objects, @@ -54,7 +55,8 @@ public sealed class SetSelector( var identifier = config.Ephemeral.IncognitoMode ? item.IdentifierIncognito : item.IdentifierString; var textSize = identifier.CalculateSize(); var textColor = item.Set.Identifiers.Any(objects.ContainsKey) ? cache.AutomationAvailable : cache.AutomationUnavailable; - Im.Cursor.Position = new Vector2(Im.ContentRegion.Available.X - textSize.X - Im.Style.FramePadding.X, Im.Cursor.Y - Im.Style.TextHeightWithSpacing); + Im.Cursor.Position = new Vector2(Im.ContentRegion.Available.X - textSize.X - Im.Style.FramePadding.X, + Im.Cursor.Y - Im.Style.TextHeightWithSpacing); Im.Text(identifier, textColor); } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index 0e298bd..24b6fa6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,11 +1,12 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.DebugTab; public sealed class DebugTab(ServiceManager manager) : ITab { - private readonly Configuration.Configuration _config = manager.GetService(); + private readonly Configuration _config = manager.GetService(); public bool IsVisible => _config.DebugMode; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index ef79666..ea8e97d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -84,7 +84,7 @@ public sealed class DesignConverterPanel(DesignConverter designConverter) : IGam Im.Text("JSON Parsing Successful!"u8); if (_tmpDesign is not null) - DesignManagerPanel.DrawDesign(_tmpDesign, null); + DesignManagerPanel.DrawDesign(_tmpDesign); if (_clipboardProblem.Length > 0) { diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 7da78dd..db45c52 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -24,7 +24,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy if (!t) continue; - DrawDesign(design, designFileSystem); + DrawDesign(design); var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta, design.WriteProtected()); @@ -50,12 +50,12 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy var designs = designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray(); foreach (var design in designs) designManager.Delete(design); - if (designFileSystem.Find("Test Designs", out var path) && path is DesignFileSystem.Folder { TotalChildren: 0 }) + if (designFileSystem.Find("Test Designs", out var path) && path is IFileSystemFolder { TotalDescendants: 0 }) designFileSystem.Delete(path); } } - public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem) + public static void DrawDesign(DesignBase design) { using var table = Im.Table.Begin("##equip"u8, 8, TableFlags.RowBackground | TableFlags.SizingFixedFit); if (design is Design d) @@ -70,8 +70,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy table.DrawDataPair("Identifier"u8, d.Identifier); table.NextRow(); table.DrawColumn("Design File System Path"u8); - if (fileSystem is not null) - table.DrawColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known"u8); + table.DrawColumn(d.Path.CurrentPath); table.NextRow(); table.DrawDataPair("Creation"u8, d.CreationDate); diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index 10c5b1d..07071ef 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,10 +1,11 @@ -using Glamourer.State; +using Glamourer.Config; +using Glamourer.State; using ImSharp; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public sealed class FunPanel(FunModule funModule, Configuration.Configuration config) : IGameDataDrawer +public sealed class FunPanel(FunModule funModule, Configuration config) : IGameDataDrawer { public ReadOnlySpan Label => "Fun Module"u8; diff --git a/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs b/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs new file mode 100644 index 0000000..bb728dc --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/ApplyCharacterButton.cs @@ -0,0 +1,55 @@ +using Dalamud.Interface; +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using Glamourer.State; +using ImSharp; +using Luna; +using Penumbra.GameData.Interop; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class ApplyCharacterButton( + DesignFileSystem fileSystem, + DesignManager manager, + ActorObjectManager objects, + StateManager stateManager, + DesignConverter converter) : BaseIconButton +{ + private static readonly AwesomeIcon UserIcon = FontAwesomeIcon.UserEdit; + + public override bool IsVisible + => fileSystem.Selection.Selection is not null && objects.Player.Valid; + + public override AwesomeIcon Icon + => UserIcon; + + public override bool Enabled + => !((Design)fileSystem.Selection.Selection!.Value).WriteProtected(); + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Overwrite this design with your character's current state."u8); + + public override void OnClick() + { + var selection = (Design)fileSystem.Selection.Selection!.Value; + try + { + var (player, actor) = objects.PlayerData; + if (!player.IsValid || !actor.Valid || !stateManager.GetOrCreate(player, actor.Objects[0], out var state)) + throw new Exception("No player state available."); + + var design = converter.Convert(state, ApplicationRules.FromModifiers(state)) + ?? throw new Exception("The clipboard did not contain valid data."); + selection.GetMaterialDataRef().Clear(); + manager.ApplyDesign(selection, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {selection.Name}.", + $"Could not apply player state to design {selection.Identifier}", NotificationType.Error, false); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 3c5e6c0..f06cbd8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.ImGuiNotification; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Services; using ImSharp; @@ -9,21 +9,19 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignDetailTab { - private readonly SaveService _saveService; - private readonly Configuration.Configuration _config; - private readonly DesignFileSystemSelector _selector; - private readonly DesignFileSystem _fileSystem; - private readonly DesignManager _manager; - private readonly DesignColors _colors; - private readonly DesignColorCombo _colorCombo; + private readonly SaveService _saveService; + private readonly Configuration _config; + private readonly DesignFileSystem _fileSystem; + private readonly DesignManager _manager; + private readonly DesignColors _colors; + private readonly DesignColorCombo _colorCombo; private bool _editDescriptionMode; - public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, - DesignColors colors, Configuration.Configuration config) + public DesignDetailTab(SaveService saveService, DesignManager manager, DesignFileSystem fileSystem, + DesignColors colors, Configuration config) { _saveService = saveService; - _selector = selector; _manager = manager; _fileSystem = fileSystem; _colors = colors; @@ -42,6 +40,8 @@ public class DesignDetailTab Im.Line.New(); } + private Design Selected + => (Design) _fileSystem.Selection.Selection!.Value; private void DrawDesignInfoTable() { @@ -57,13 +57,13 @@ public class DesignDetailTab table.NextColumn(); var width = Im.ContentRegion.Available with { Y = 0 }; Im.Item.SetNextWidth(width.X); - if (ImEx.InputOnDeactivation.Text("##Name"u8, _selector.Selected!.Name.Text, out string newName)) - _manager.Rename(_selector.Selected!, newName); + if (ImEx.InputOnDeactivation.Text("##Name"u8, Selected.Name.Text, out string newName)) + _manager.Rename(Selected, newName); - var identifier = _selector.Selected!.Identifier.ToString(); + var identifier = Selected.Identifier.ToString(); table.DrawFrameColumn("Unique Identifier"u8); table.NextColumn(); - var fileName = _saveService.FileNames.DesignFile(_selector.Selected!); + var fileName = _saveService.FileNames.DesignFile(Selected); using (Im.Font.PushMono()) { if (Im.Button(identifier, width)) @@ -87,10 +87,10 @@ public class DesignDetailTab table.DrawFrameColumn("Full Selector Path"u8); table.NextColumn(); Im.Item.SetNextWidth(width.X); - if (ImEx.InputOnDeactivation.Text("##Path"u8, _selector.SelectedLeaf!.FullName(), out string newPath)) + if (ImEx.InputOnDeactivation.Text("##Path"u8, Selected.Path.CurrentPath, out string newPath)) try { - _fileSystem.RenameAndMove(_selector.SelectedLeaf, newPath); + _fileSystem.RenameAndMove(Selected.Node!, newPath); } catch (Exception ex) { @@ -99,56 +99,56 @@ public class DesignDetailTab table.DrawFrameColumn("Quick Design Bar"u8); table.NextColumn(); - if (Im.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign)) - _manager.SetQuickDesign(_selector.Selected!, true); + if (Im.RadioButton("Display##qdb"u8, Selected.QuickDesign)) + _manager.SetQuickDesign(Selected, true); var hovered = Im.Item.Hovered(); Im.Line.SameInner(); - if (Im.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign)) - _manager.SetQuickDesign(_selector.Selected!, false); + if (Im.RadioButton("Hide##qdb"u8, !Selected.QuickDesign)) + _manager.SetQuickDesign(Selected, false); if (hovered || Im.Item.Hovered()) Im.Tooltip.Set("Display or hide this design in your quick design bar."u8); - var forceRedraw = _selector.Selected!.ForcedRedraw; + var forceRedraw = Selected.ForcedRedraw; table.DrawFrameColumn("Force Redrawing"u8); table.NextColumn(); if (Im.Checkbox("##ForceRedraw"u8, ref forceRedraw)) - _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); + _manager.ChangeForcedRedraw(Selected, forceRedraw); Im.Tooltip.OnHover("Set this design to always force a redraw when it is applied through any means."u8); - var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes; + var resetAdvancedDyes = Selected.ResetAdvancedDyes; table.DrawFrameColumn("Reset Advanced Dyes"u8); table.NextColumn(); if (Im.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes)) - _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); + _manager.ChangeResetAdvancedDyes(Selected, resetAdvancedDyes); Im.Tooltip.OnHover("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8); - var resetTemporarySettings = _selector.Selected!.ResetTemporarySettings; + var resetTemporarySettings = Selected.ResetTemporarySettings; table.DrawFrameColumn("Reset Temporary Settings"u8); table.NextColumn(); if (Im.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) - _manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); + _manager.ChangeResetTemporarySettings(Selected, resetTemporarySettings); Im.Tooltip.OnHover( "Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); table.DrawFrameColumn("Color"u8); table.NextColumn(); - if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color, + if (_colorCombo.Draw("##colorCombo"u8, Selected.Color.Length is 0 ? DesignColors.AutomaticName : Selected.Color, "Associate a color with this design.\n"u8 + "Right-Click to revert to automatic coloring.\n"u8 + "Hold Control and scroll the mousewheel to scroll."u8, width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName)) - _manager.ChangeColor(_selector.Selected!, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName); + _manager.ChangeColor(Selected, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName); if (Im.Item.RightClicked()) - _manager.ChangeColor(_selector.Selected!, string.Empty); + _manager.ChangeColor(Selected, string.Empty); - if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor)) + if (_colors.TryGetValue(Selected.Color, out var currentColor)) { Im.Line.Same(); - if (DesignColorUi.DrawColorButton($"Color associated with {_selector.Selected!.Color}", currentColor, out var newColor)) - _colors.SetColor(_selector.Selected!.Color, newColor); + if (DesignColorUi.DrawColorButton($"Color associated with {Selected.Color}", currentColor, out var newColor)) + _colors.SetColor(Selected.Color, newColor); } - else if (_selector.Selected!.Color.Length != 0) + else if (Selected.Color.Length is not 0) { Im.Line.Same(); ImEx.Icon.Draw(LunaStyle.WarningIcon, _colors.MissingColor); @@ -157,11 +157,11 @@ public class DesignDetailTab table.DrawFrameColumn("Creation Date"u8); table.NextColumn(); - ImEx.TextFramed($"{_selector.Selected!.CreationDate.LocalDateTime:F}", width, 0); + ImEx.TextFramed($"{Selected.CreationDate.LocalDateTime:F}", width, 0); table.DrawFrameColumn("Last Update Date"u8); table.NextColumn(); - ImEx.TextFramed($"{_selector.Selected!.LastEdit.LocalDateTime:F}", width, 0); + ImEx.TextFramed($"{Selected.LastEdit.LocalDateTime:F}", width, 0); table.DrawFrameColumn("Tags"u8); table.NextColumn(); @@ -170,26 +170,26 @@ public class DesignDetailTab private void DrawTags() { - var idx = TagButtons.Draw(StringU8.Empty, StringU8.Empty, _selector.Selected!.Tags, out var editedTag); + var idx = TagButtons.Draw(StringU8.Empty, StringU8.Empty, Selected.Tags, out var editedTag); if (idx < 0) return; - if (idx < _selector.Selected!.Tags.Length) + if (idx < Selected.Tags.Length) { if (editedTag.Length is 0) - _manager.RemoveTag(_selector.Selected!, idx); + _manager.RemoveTag(Selected, idx); else - _manager.RenameTag(_selector.Selected!, idx, editedTag); + _manager.RenameTag(Selected, idx, editedTag); } else { - _manager.AddTag(_selector.Selected!, editedTag); + _manager.AddTag(Selected, editedTag); } } private void DrawDescription() { - var desc = _selector.Selected!.Description; + var desc = Selected.Description; var size = Im.ContentRegion.Available with { Y = 12 * Im.Style.TextHeightWithSpacing }; if (!_editDescriptionMode) { @@ -205,7 +205,7 @@ public class DesignDetailTab else { if (ImEx.InputOnDeactivation.MultiLine("##desc"u8, desc, out string newDescription, size)) - _manager.ChangeDescription(_selector.Selected!, newDescription); + _manager.ChangeDescription(Selected, newDescription); if (Im.Button("Stop Editing"u8)) _editDescriptionMode = false; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs deleted file mode 100644 index 33e35e2..0000000 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ /dev/null @@ -1,405 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin.Services; -using Glamourer.Designs; -using Glamourer.Designs.History; -using Glamourer.Events; -using Glamourer.Services; -using Dalamud.Bindings.ImGui; -using ImSharp; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Filesystem; -using OtterGui.FileSystem.Selector; -using OtterGui.Log; -using OtterGui.Raii; -using OtterGui.Text; -using Luna; - -namespace Glamourer.Gui.Tabs.DesignTab; - -public sealed class DesignFileSystemSelector : FileSystemSelector, IPanel -{ - private readonly DesignManager _designManager; - private readonly DesignChanged _event; - private readonly Configuration.Configuration _config; - private readonly DesignConverter _converter; - private readonly TabSelected _selectionEvent; - private readonly DesignColors _designColors; - private readonly DesignApplier _designApplier; - - private string? _clipboardText; - private Design? _cloneDesign; - private string _newName = string.Empty; - - public new DesignFileSystem.Leaf? SelectedLeaf - => base.SelectedLeaf; - - public record struct DesignState(Rgba32 Color) - { } - - protected override float CurrentWidth - => _config.Ephemeral.CurrentDesignSelectorWidth * Im.Style.GlobalScale; - - protected override float MinimumAbsoluteRemainder - => 470 * Im.Style.GlobalScale; - - protected override float MinimumScaling - => _config.Ephemeral.DesignSelectorMinimumScale; - - protected override float MaximumScaling - => _config.Ephemeral.DesignSelectorMaximumScale; - - protected override void SetSize(Vector2 size) - { - base.SetSize(size); - var adaptedSize = MathF.Round(size.X / Im.Style.GlobalScale); - if (adaptedSize == _config.Ephemeral.CurrentDesignSelectorWidth) - return; - - _config.Ephemeral.CurrentDesignSelectorWidth = adaptedSize; - _config.Ephemeral.Save(); - } - - public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event, - Configuration.Configuration config, DesignConverter converter, TabSelected selectionEvent, OtterGui.Log.Logger log, DesignColors designColors, - DesignApplier designApplier) - : base(fileSystem, keyState, log, allowMultipleSelection: true) - { - _designManager = designManager; - _event = @event; - _config = config; - _converter = converter; - _selectionEvent = selectionEvent; - _designColors = designColors; - _designApplier = designApplier; - _event.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystemSelector); - _selectionEvent.Subscribe(OnTabSelected, TabSelected.Priority.DesignSelector); - _designColors.ColorChanged += SetFilterDirty; - - AddButton(NewDesignButton, 0); - AddButton(ImportDesignButton, 10); - AddButton(CloneDesignButton, 20); - AddButton(DeleteButton, 1000); - UnsubscribeRightClickLeaf(RenameLeaf); - SetRenameSearchPath(_config.ShowRename); - SetFilterTooltip(); - - if (_config.Ephemeral.SelectedDesign == Guid.Empty) - return; - - var design = designManager.Designs.ByIdentifier(_config.Ephemeral.SelectedDesign); - if (design != null) - SelectByValue(design); - } - - public void SetRenameSearchPath(RenameField value) - { - switch (value) - { - case RenameField.RenameSearchPath: - SubscribeRightClickLeaf(RenameLeafDesign, 1000); - UnsubscribeRightClickLeaf(RenameDesign); - break; - case RenameField.RenameData: - UnsubscribeRightClickLeaf(RenameLeafDesign); - SubscribeRightClickLeaf(RenameDesign, 1000); - break; - case RenameField.BothSearchPathPrio: - UnsubscribeRightClickLeaf(RenameLeafDesign); - UnsubscribeRightClickLeaf(RenameDesign); - SubscribeRightClickLeaf(RenameLeafDesign, 1001); - SubscribeRightClickLeaf(RenameDesign, 1000); - break; - case RenameField.BothDataPrio: - UnsubscribeRightClickLeaf(RenameLeafDesign); - UnsubscribeRightClickLeaf(RenameDesign); - SubscribeRightClickLeaf(RenameLeafDesign, 1000); - SubscribeRightClickLeaf(RenameDesign, 1001); - break; - default: - UnsubscribeRightClickLeaf(RenameLeafDesign); - UnsubscribeRightClickLeaf(RenameDesign); - break; - } - } - - private void RenameLeafDesign(DesignFileSystem.Leaf leaf) - { - Im.Separator(); - RenameLeaf(leaf); - } - - private void RenameDesign(DesignFileSystem.Leaf leaf) - { - Im.Separator(); - var currentName = leaf.Value.Name.Text; - if (ImGui.IsWindowAppearing()) - ImGui.SetKeyboardFocusHere(0); - ImGui.TextUnformatted("Rename Design:"); - if (Im.Input.Text("##RenameDesign"u8, ref currentName, StringU8.Empty, InputTextFlags.EnterReturnsTrue)) - { - _designManager.Rename(leaf.Value, currentName); - ImGui.CloseCurrentPopup(); - } - - ImGuiUtil.HoverTooltip("Enter a new name here to rename the changed design."); - } - - protected override void Select(FileSystem.Leaf? leaf, bool clear, in DesignState storage = default) - { - base.Select(leaf, clear, storage); - var id = SelectedLeaf?.Value.Identifier ?? Guid.Empty; - if (id != _config.Ephemeral.SelectedDesign) - { - _config.Ephemeral.SelectedDesign = id; - _config.Ephemeral.Save(); - } - } - - protected override void DrawPopups() - { - DrawNewDesignPopup(); - } - - protected override void DrawLeafName(FileSystem.Leaf leaf, in DesignState state, bool selected) - { - var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; - var name = _config.Ephemeral.IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text; - using var color = ImGuiColor.Text.Push(state.Color); - using var _ = ImUtf8.TreeNode(name, flag); - if (_config.AllowDoubleClickToApply && ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - _designApplier.ApplyToPlayer(leaf.Value); - } - - public override void Dispose() - { - base.Dispose(); - _event.Unsubscribe(OnDesignChange); - _selectionEvent.Unsubscribe(OnTabSelected); - _designColors.ColorChanged -= SetFilterDirty; - } - - public override ISortMode SortMode - => _config.SortMode; - - protected override uint ExpandedFolderColor - => ColorId.FolderExpanded.Value().Color; - - protected override uint CollapsedFolderColor - => ColorId.FolderCollapsed.Value().Color; - - protected override uint FolderLineColor - => ColorId.FolderLine.Value().Color; - - protected override bool FoldersDefaultOpen - => _config.OpenFoldersByDefault; - - private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _) - { - switch (type) - { - case DesignChanged.Type.ReloadedAll: - case DesignChanged.Type.Renamed: - case DesignChanged.Type.AddedTag: - case DesignChanged.Type.ChangedTag: - case DesignChanged.Type.RemovedTag: - case DesignChanged.Type.AddedMod: - case DesignChanged.Type.RemovedMod: - case DesignChanged.Type.Created: - case DesignChanged.Type.Deleted: - case DesignChanged.Type.ApplyCustomize: - case DesignChanged.Type.ApplyEquip: - case DesignChanged.Type.ApplyStain: - case DesignChanged.Type.ApplyCrest: - case DesignChanged.Type.Customize: - case DesignChanged.Type.Equip: - case DesignChanged.Type.ChangedColor: - SetFilterDirty(); - break; - } - } - - private void NewDesignButton(Vector2 size) - { - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false, - true)) - { - _cloneDesign = null; - _clipboardText = null; - ImGui.OpenPopup("##NewDesign"); - } - } - - private void ImportDesignButton(Vector2 size) - { - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, "Try to import a design from your clipboard.", false, - true)) - return; - - try - { - _cloneDesign = null; - _clipboardText = ImGui.GetClipboardText(); - ImGui.OpenPopup("##NewDesign"); - } - catch - { - Glamourer.Messager.NotificationMessage("Could not import data from clipboard.", NotificationType.Error, false); - } - } - - private void CloneDesignButton(Vector2 size) - { - var tt = SelectedLeaf == null - ? "No design selected." - : "Clone the currently selected design to a duplicate"; - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true)) - return; - - _clipboardText = null; - _cloneDesign = Selected!; - ImGui.OpenPopup("##NewDesign"); - } - - private void DeleteButton(Vector2 size) - => DeleteSelectionButton(size, - new OtterGui.Classes.DoubleModifier(new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier1), - new OtterGui.Classes.ModifierHotkey(_config.DeleteDesignModifier.Modifier2)), "design", "designs", _designManager.Delete); - - private void DrawNewDesignPopup() - { - if (!ImGuiUtil.OpenNameField("##NewDesign", ref _newName)) - return; - - if (_clipboardText != null) - { - var design = _converter.FromBase64(_clipboardText, true, true, out _); - if (design is Design d) - _designManager.CreateClone(d, _newName, true); - else if (design != null) - _designManager.CreateClone(design, _newName, true); - else - Glamourer.Messager.NotificationMessage("Could not create a design, clipboard did not contain valid design data.", - NotificationType.Error, false); - _clipboardText = null; - } - else if (_cloneDesign != null) - { - _designManager.CreateClone(_cloneDesign, _newName, true); - _cloneDesign = null; - } - else - { - _designManager.CreateEmpty(_newName, true); - } - - _newName = string.Empty; - } - - private void OnTabSelected(MainTabType type, Design? design) - { - if (type == MainTabType.Designs && design != null) - SelectByValue(design); - } - - #region Filters - - private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase; - private LowerString _designFilter = LowerString.Empty; - private int _filterType = -1; - - private void SetFilterTooltip() - { - FilterTooltip = "Filter designs for those where their full paths or names contain the given substring.\n" - + "Enter m:[string] to filter for designs with with a mod association containing the string.\n" - + "Enter t:[string] to filter for designs set to specific tags.\n" - + "Enter c:[string] to filter for designs set to specific colors.\n" - + "Enter i:[string] to filter for designs containing specific items.\n" - + "Enter n:[string] to filter only for design names and no paths.\n\n" - + "Use None as a placeholder value that only matches empty lists or names."; - } - - /// Appropriately identify and set the string filter and its type. - protected override bool ChangeFilter(string filterValue) - { - (_designFilter, _filterType) = filterValue.Length switch - { - 0 => (LowerString.Empty, -1), - > 1 when filterValue[1] == ':' => - filterValue[0] switch - { - 'n' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), - 'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), - 'm' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), - 'M' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), - 't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), - 'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), - 'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), - 'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), - 'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5), - 'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5), - _ => (new LowerString(filterValue), 0), - }, - _ => (new LowerString(filterValue), 0), - }; - - return true; - } - - private const int EmptyOffset = 128; - - private static (LowerString, int) ParseFilter(string value, int id) - { - value = value[2..]; - var lower = new LowerString(value); - return (lower, lower.Lower is "none" ? id + EmptyOffset : id); - } - - /// - /// The overwritten filter method also computes the state. - /// Folders have default state and are filtered out on the direct string instead of the other options. - /// If any filter is set, they should be hidden by default unless their children are visible, - /// or they contain the path search string. - /// - protected override bool ApplyFiltersAndState(FileSystem.IPath path, out DesignState state) - { - if (path is DesignFileSystem.Folder f) - { - state = default; - return FilterValue.Length > 0 && !f.FullName().Contains(FilterValue, IgnoreCase); - } - - return ApplyFiltersAndState((DesignFileSystem.Leaf)path, out state); - } - - /// Apply the string filters. - private bool ApplyStringFilters(DesignFileSystem.Leaf leaf, Design design) - { - return _filterType switch - { - -1 => false, - 0 => !(_designFilter.IsContained(leaf.FullName()) || design.Name.Contains(_designFilter)), - 1 => !design.Name.Contains(_designFilter), - 2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)), - 3 => !design.Tags.Any(_designFilter.IsContained), - 4 => !design.DesignData.ContainsName(_designFilter), - 5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color), - 2 + EmptyOffset => design.AssociatedMods.Count > 0, - 3 + EmptyOffset => design.Tags.Length > 0, - _ => false, // Should never happen - }; - } - - /// Combined wrapper for handling all filters and setting state. - private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) - { - state = new DesignState(_designColors.GetColor(leaf.Value)); - return ApplyStringFilters(leaf, leaf.Value); - } - - #endregion - - public ReadOnlySpan Id - => "DesignSelector"u8; -} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs index b8baa4d..5b75fe3 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs @@ -1,21 +1,107 @@ -using Luna; +using System.Security.AccessControl; +using Glamourer.Config; +using Glamourer.Designs; +using Glamourer.Designs.History; +using Glamourer.Events; +using Glamourer.State; +using ImSharp; +using Luna; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignHeader : SplitButtonHeader +public sealed class DesignHeader : SplitButtonHeader, IDisposable { - public DesignHeader(DesignSelection selection, IncognitoButton incognito) + private readonly DesignFileSystem _fileSystem; + private readonly DesignChanged _designChanged; + private readonly Configuration _config; + + private StringU8 _header = new("No Selection"u8); + private StringU8 _incognito = new("No Selection"u8); + + public DesignHeader(DesignFileSystem fileSystem, IncognitoButton incognito, DesignChanged designChanged, Configuration config, + DesignConverter converter, StateManager stateManager, EditorHistory history, DesignManager manager, ActorObjectManager objects) { - RightButtons.AddButton(incognito, 50); - RightButtons.AddButton(new LockedButton(selection), 100); + _fileSystem = fileSystem; + _designChanged = designChanged; + _config = config; + LeftButtons.AddButton(new SetFromClipboardButton(fileSystem, converter, manager), 100); + LeftButtons.AddButton(new DesignUndoButton(fileSystem, manager), 90); + LeftButtons.AddButton(new ExportToClipboardButton(fileSystem, converter), 80); + LeftButtons.AddButton(new ApplyCharacterButton(fileSystem, manager, objects, stateManager, converter), 70); + LeftButtons.AddButton(new UndoButton(fileSystem, history), 60); + + RightButtons.AddButton(incognito, 50); + RightButtons.AddButton(new LockedButton(fileSystem, manager), 100); + _fileSystem.Selection.Changed += OnSelectionChanged; + OnSelectionChanged(); + designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignHeader); } - private sealed class LockedButton(DesignSelection selection) : BaseIconButton + private void OnDesignChanged(DesignChanged.Type arg1, Design arg2, ITransaction? arg3) + { + if (arg1 is not DesignChanged.Type.Renamed) + return; + + if (arg2 != _fileSystem.Selection.Selection?.Value) + return; + + _header = new StringU8(arg2.Name.Text); + } + + private void OnSelectionChanged() + { + if (_fileSystem.Selection.Selection?.GetValue() is { } selection) + { + _header = new StringU8(selection.Name.Text); + _incognito = new StringU8(selection.Incognito); + } + else if (_fileSystem.Selection.OrderedNodes.Count > 0) + { + _header = new StringU8($"{_fileSystem.Selection.OrderedNodes.Count} Objects Selected"); + _incognito = _header; + } + else + { + _header = new StringU8("No Selection"u8); + _incognito = _header; + } + } + + public override void Draw(Vector2 size) + { + var color = ColorId.HeaderButtons.Value(); + using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color); + base.Draw(size with { Y = Im.Style.FrameHeight }); + } + + public override ReadOnlySpan Text + => _config.Ephemeral.IncognitoMode ? _incognito : _header; + + private sealed class LockedButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton { public override bool IsVisible - => selection.Design is not null; + => fileSystem.Selection.Selection is not null; public override AwesomeIcon Icon - => selection.Design!.WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon; + => ((Design)fileSystem.Selection.Selection!.Value).WriteProtected() ? LunaStyle.LockedIcon : LunaStyle.UnlockedIcon; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text(((Design)fileSystem.Selection.Selection!.Value).WriteProtected() + ? "Make this design editable."u8 + : "Write-protect this design."u8); + + public override void OnClick() + => manager.SetWriteProtection((Design)fileSystem.Selection.Selection!.Value, + !((Design)fileSystem.Selection.Selection!.Value).WriteProtected()); + } + + public void Dispose() + { + _fileSystem.Selection.Changed -= OnSelectionChanged; + _designChanged.Unsubscribe(OnDesignChanged); } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index fd3ed75..ee568f7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Glamourer.Automation; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using ImSharp; @@ -10,16 +10,19 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignLinkDrawer( DesignLinkManager linkManager, - DesignFileSystemSelector selector, + DesignFileSystem fileSystem, LinkDesignCombo combo, DesignColors colorManager, - Configuration.Configuration config) : IUiService + Configuration config) : IUiService { private int _dragDropIndex = -1; private LinkOrder _dragDropOrder = LinkOrder.None; private int _dragDropTargetIndex = -1; private LinkOrder _dragDropTargetOrder = LinkOrder.None; + private Design Selected + => (Design)fileSystem.Selection.Selection!.Value; + public void Draw() { using var h = DesignPanelFlag.DesignLinks.Header(config); @@ -45,23 +48,23 @@ public class DesignLinkDrawer( switch (_dragDropTargetOrder) { case LinkOrder.Before: - for (var i = selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i) - linkManager.MoveDesignLink(selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After); + for (var i = Selected.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i) + linkManager.MoveDesignLink(Selected, i, LinkOrder.Before, 0, LinkOrder.After); break; case LinkOrder.After: for (var i = 0; i <= _dragDropTargetIndex; ++i) { - linkManager.MoveDesignLink(selector.Selected!, 0, LinkOrder.After, selector.Selected!.Links.Before.Count, + linkManager.MoveDesignLink(Selected, 0, LinkOrder.After, Selected.Links.Before.Count, LinkOrder.Before); } break; } else if (_dragDropTargetOrder is LinkOrder.Self) - linkManager.MoveDesignLink(selector.Selected!, _dragDropIndex, _dragDropOrder, selector.Selected!.Links.Before.Count, + linkManager.MoveDesignLink(Selected, _dragDropIndex, _dragDropOrder, Selected.Links.Before.Count, LinkOrder.Before); else - linkManager.MoveDesignLink(selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); + linkManager.MoveDesignLink(Selected, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); _dragDropIndex = -1; _dragDropTargetIndex = -1; @@ -81,9 +84,9 @@ public class DesignLinkDrawer( 6 * Im.Style.FrameHeight + 5 * Im.Style.ItemInnerSpacing.X); using var style = ImStyleDouble.ItemSpacing.Push(Im.Style.ItemInnerSpacing); - DrawSubList(table, selector.Selected!.Links.Before, LinkOrder.Before); + DrawSubList(table, Selected.Links.Before, LinkOrder.Before); DrawSelf(table); - DrawSubList(table, selector.Selected!.Links.After, LinkOrder.After); + DrawSubList(table, Selected.Links.After, LinkOrder.After); DrawNew(table); MoveLink(); } @@ -92,7 +95,7 @@ public class DesignLinkDrawer( { using var id = Im.Id.Push((int)LinkOrder.Self); table.NextColumn(); - var color = colorManager.GetColor(selector.Selected!); + var color = colorManager.GetColor(Selected); using (AwesomeIcon.Font.Push()) { using var c = ImGuiColor.Text.Push(color); @@ -104,11 +107,11 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(color)) { Im.Cursor.FrameAlign(); - Im.Selectable(config.Ephemeral.IncognitoMode ? selector.Selected!.Incognito : selector.Selected!.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? Selected.Incognito : Selected.Name.Text); } Im.Tooltip.OnHover("Current Design"u8); - DrawDragDrop(selector.Selected!, LinkOrder.Self, 0); + DrawDragDrop(Selected, LinkOrder.Self, 0); table.NextColumn(); using (AwesomeIcon.Font.Push()) { @@ -144,7 +147,7 @@ public class DesignLinkDrawer( DrawApplicationBoxes(i, order, flags); if (delete) - linkManager.RemoveDesignLink(selector.Selected!, i--, order); + linkManager.RemoveDesignLink(Selected, i--, order); } } @@ -164,11 +167,11 @@ public class DesignLinkDrawer( } else { - canAddBefore = LinkContainer.CanAddLink(selector.Selected!, design, LinkOrder.Before, out var error); + canAddBefore = LinkContainer.CanAddLink(Selected, design, LinkOrder.Before, out var error); ttBefore = canAddBefore ? $"Add a link at the top of the list to {design.Name}." : $"Can not add a link to {design.Name}:\n{error}"; - canAddAfter = LinkContainer.CanAddLink(selector.Selected!, design, LinkOrder.After, out error); + canAddAfter = LinkContainer.CanAddLink(Selected, design, LinkOrder.After, out error); ttAfter = canAddAfter ? $"Add a link at the bottom of the list to {design.Name}." : $"Can not add a link to {design.Name}:\n{error}"; @@ -176,13 +179,13 @@ public class DesignLinkDrawer( if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleUp.Icon(), ttBefore, !canAddBefore)) { - linkManager.AddDesignLink(selector.Selected!, design!, LinkOrder.Before); - linkManager.MoveDesignLink(selector.Selected!, selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before); + linkManager.AddDesignLink(Selected, design!, LinkOrder.Before); + linkManager.MoveDesignLink(Selected, Selected.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before); } Im.Line.Same(); if (ImEx.Icon.Button(FontAwesomeIcon.ArrowCircleDown.Icon(), ttAfter, !canAddAfter)) - linkManager.AddDesignLink(selector.Selected!, design!, LinkOrder.After); + linkManager.AddDesignLink(Selected, design!, LinkOrder.After); } private void DrawDragDrop(Design design, LinkOrder order, int index) @@ -228,7 +231,7 @@ public class DesignLinkDrawer( Im.Line.Same(); Box(4); if (newType != current) - linkManager.ChangeApplicationType(selector.Selected!, idx, order, newType); + linkManager.ChangeApplicationType(Selected, idx, order, newType); return; void Box(int i) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 1a08fae..f04beed 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,51 +1,40 @@ -using Dalamud.Interface; -using Dalamud.Interface.ImGuiFileDialog; +using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Api.Enums; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; -using Dalamud.Bindings.ImGui; -using Glamourer.Configuration; using ImSharp; using Luna; -using OtterGui; -using OtterGui.Raii; -using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; public class DesignPanel : IPanel { - private readonly FileDialogManager _fileDialog = new(); - private readonly DesignSelection _selection; - private readonly CustomizationDrawer _customizationDrawer; - private readonly DesignManager _manager; - private readonly ActorObjectManager _objects; - private readonly StateManager _state; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly ModAssociationsTab _modAssociations; - private readonly Configuration.Configuration _config; - private readonly DesignDetailTab _designDetails; - private readonly ImportService _importService; - private readonly DesignConverter _converter; - private readonly MultiDesignPanel _multiDesignPanel; - private readonly CustomizeParameterDrawer _parameterDrawer; - private readonly DesignLinkDrawer _designLinkDrawer; - private readonly MaterialDrawer _materials; - private readonly EditorHistory _history; - private readonly Button[] _leftButtons; - private readonly Button[] _rightButtons; + private readonly FileDialogManager _fileDialog = new(); + private readonly CustomizationDrawer _customizationDrawer; + private readonly DesignFileSystem _fileSystem; + private readonly DesignManager _manager; + private readonly ActorObjectManager _objects; + private readonly StateManager _state; + private readonly EquipmentDrawer _equipmentDrawer; + private readonly ModAssociationsTab _modAssociations; + private readonly Configuration _config; + private readonly DesignDetailTab _designDetails; + private readonly ImportService _importService; + private readonly MultiDesignPanel _multiDesignPanel; + private readonly CustomizeParameterDrawer _parameterDrawer; + private readonly DesignLinkDrawer _designLinkDrawer; + private readonly MaterialDrawer _materials; public DesignPanel(CustomizationDrawer customizationDrawer, @@ -54,7 +43,7 @@ public class DesignPanel : IPanel StateManager state, EquipmentDrawer equipmentDrawer, ModAssociationsTab modAssociations, - Configuration.Configuration config, + Configuration config, DesignDetailTab designDetails, DesignConverter converter, ImportService importService, @@ -62,7 +51,7 @@ public class DesignPanel : IPanel CustomizeParameterDrawer parameterDrawer, DesignLinkDrawer designLinkDrawer, MaterialDrawer materials, - EditorHistory history, DesignSelection selection) + DesignFileSystem fileSystem) { _customizationDrawer = customizationDrawer; _manager = manager; @@ -73,33 +62,16 @@ public class DesignPanel : IPanel _config = config; _designDetails = designDetails; _importService = importService; - _converter = converter; _multiDesignPanel = multiDesignPanel; _parameterDrawer = parameterDrawer; _designLinkDrawer = designLinkDrawer; _materials = materials; - _history = history; - _selection = selection; - _leftButtons = - [ - new SetFromClipboardButton(this), - new DesignUndoButton(this), - new ExportToClipboardButton(this), - new ApplyCharacterButton(this), - new UndoButton(this), - ]; - _rightButtons = - [ - new LockButton(this), - //new IncognitoButton(_config), - ]; + _fileSystem = fileSystem; } - private void DrawHeader() - => HeaderDrawer.Draw(SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons); - private string SelectionName - => _selection.Design == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? _selection.Design.Incognito : _selection.Design.Name.Text; + private Design Selection + => (Design)_fileSystem.Selection.Selection!.Value; private void DrawEquipment() { @@ -109,22 +81,22 @@ public class DesignPanel : IPanel _equipmentDrawer.Prepare(); - var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selection.Design!.WriteProtected()); + var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, Selection.WriteProtected()); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var data = EquipDrawData.FromDesign(_manager, _selection.Design!, slot); + var data = EquipDrawData.FromDesign(_manager, Selection, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _manager.ChangeStains(_selection.Design, slot, newAllStain); + _manager.ChangeStains(Selection, slot, newAllStain); } - var mainhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.MainHand); - var offhand = EquipDrawData.FromDesign(_manager, _selection.Design!, EquipSlot.OffHand); + var mainhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.MainHand); + var offhand = EquipDrawData.FromDesign(_manager, Selection, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, true); foreach (var slot in BonusExtensions.AllFlags) { - var data = BonusDrawData.FromDesign(_manager, _selection.Design!, slot); + var data = BonusDrawData.FromDesign(_manager, Selection, slot); _equipmentDrawer.DrawBonusItem(data); } @@ -136,30 +108,30 @@ public class DesignPanel : IPanel private void DrawEquipmentMetaToggles() { - using (var _ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selection.Design!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, Selection)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, Selection)); } Im.Line.Same(); - using (var _ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selection.Design!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, Selection)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, Selection)); } Im.Line.Same(); - using (var _ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selection.Design!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, Selection)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, Selection)); } Im.Line.Same(); - using (var _ = ImRaii.Group()) + using (Im.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, Selection)); } } @@ -169,25 +141,25 @@ public class DesignPanel : IPanel return; var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); - using var h = Im.Tree.HeaderId(_selection.Design!.DesignData.ModelId is 0 - ? "Customization" - : $"Customization (Model Id #{_selection.Design!.DesignData.ModelId})###Customization", + using var h = Im.Tree.HeaderId(Selection.DesignData.ModelId is 0 + ? "Customization"u8 + : $"Customization (Model Id #{Selection.DesignData.ModelId})###Customization", expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); if (!h) return; - if (_customizationDrawer.Draw(_selection.Design!.DesignData.Customize, _selection.Design.Application.Customize, - _selection.Design!.WriteProtected(), false)) + if (_customizationDrawer.Draw(Selection.DesignData.Customize, Selection.Application.Customize, + Selection.WriteProtected(), false)) foreach (var idx in CustomizeIndex.Values) { var flag = idx.ToFlag(); var newValue = _customizationDrawer.ChangeApply.HasFlag(flag); - _manager.ChangeApplyCustomize(_selection.Design, idx, newValue); + _manager.ChangeApplyCustomize(Selection, idx, newValue); if (_customizationDrawer.Changed.HasFlag(flag)) - _manager.ChangeCustomize(_selection.Design, idx, _customizationDrawer.Customize[idx]); + _manager.ChangeCustomize(Selection, idx, _customizationDrawer.Customize[idx]); } - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selection.Design!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, Selection)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); } @@ -197,7 +169,7 @@ public class DesignPanel : IPanel if (!h) return; - _parameterDrawer.Draw(_manager, _selection.Design!); + _parameterDrawer.Draw(_manager, Selection); } private void DrawMaterialValues() @@ -206,52 +178,52 @@ public class DesignPanel : IPanel if (!h) return; - _materials.Draw(_selection.Design!); + _materials.Draw(Selection); } private void DrawCustomizeApplication() { - using var id = ImUtf8.PushId("Customizations"u8); - var set = _selection.Design!.CustomizeSet; + using var id = Im.Id.Push("Customizations"u8); + var set = Selection.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; - var flags = _selection.Design!.ApplyCustomizeExcludingBodyType == 0 ? 0 : - (_selection.Design!.ApplyCustomize & available) == available ? 3 : 1; - if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) + var flags = Selection.ApplyCustomizeExcludingBodyType is 0 ? 0ul : + (Selection.ApplyCustomize & available) == available ? 3ul : 1ul; + if (Im.Checkbox("Apply All Customizations"u8, ref flags, 3ul)) { - var newFlags = flags == 3; - _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, newFlags); - _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, newFlags); + var newFlags = flags is 3; + _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, newFlags); + _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, newFlags); foreach (var index in CustomizationExtensions.AllBasic) - _manager.ChangeApplyCustomize(_selection.Design!, index, newFlags); + _manager.ChangeApplyCustomize(Selection, index, newFlags); } - var applyClan = _selection.Design!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan)) - _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Clan, applyClan); + var applyClan = Selection.DoApplyCustomize(CustomizeIndex.Clan); + if (Im.Checkbox($"Apply {CustomizeIndex.Clan.ToNameU8()}", ref applyClan)) + _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Clan, applyClan); - var applyGender = _selection.Design!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender)) - _manager.ChangeApplyCustomize(_selection.Design!, CustomizeIndex.Gender, applyGender); + var applyGender = Selection.DoApplyCustomize(CustomizeIndex.Gender); + if (Im.Checkbox($"Apply {CustomizeIndex.Gender.ToNameU8()}", ref applyGender)) + _manager.ChangeApplyCustomize(Selection, CustomizeIndex.Gender, applyGender); foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) { - var apply = _selection.Design!.DoApplyCustomize(index); - if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) - _manager.ChangeApplyCustomize(_selection.Design!, index, apply); + var apply = Selection.DoApplyCustomize(index); + if (Im.Checkbox($"Apply {set.Option(index)}", ref apply)) + _manager.ChangeApplyCustomize(Selection, index, apply); } } private void DrawCrestApplication() { - using var id = ImUtf8.PushId("Crests"u8); - var flags = (uint)_selection.Design!.Application.Crest; - var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); + using var id = Im.Id.Push("Crests"u8); + var flags = (ulong)Selection.Application.Crest; + var bigChange = Im.Checkbox("Apply All Crests"u8, ref flags, (ulong)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { - var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selection.Design!.DoApplyCrest(flag); - if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) - _manager.ChangeApplyCrest(_selection.Design!, flag, apply); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : Selection.DoApplyCrest(flag); + if (Im.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) + _manager.ChangeApplyCrest(Selection, flag, apply); } } @@ -261,63 +233,59 @@ public class DesignPanel : IPanel if (!h) return; - using var disabled = Im.Disabled(_selection.Design!.WriteProtected()); + using var disabled = Im.Disabled(Selection.WriteProtected()); DrawAllButtons(); - using (var _ = ImUtf8.Group()) + using (Im.Group()) { DrawCustomizeApplication(); - ImUtf8.IconDummy(); + Im.FrameDummy(); DrawCrestApplication(); - ImUtf8.IconDummy(); + Im.FrameDummy(); DrawMetaApplication(); } - ImGui.SameLine(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X); - using (var _ = ImRaii.Group()) + Im.Line.Same(210 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X); + using (Im.Group()) { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(allFlags & _selection.Design!.Application.Equip); - using var id = ImUtf8.PushId(label); - var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); + var flags = (ulong)(allFlags & Selection.Application.Equip); + using var id = Im.Id.Push(label); + var bigChange = Im.Checkbox($"Apply All {label}", ref flags, (ulong)allFlags); if (stain) foreach (var slot in slots) { - var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selection.Design!.DoApplyStain(slot); - if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) - _manager.ChangeApplyStains(_selection.Design!, slot, apply); + var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : Selection.DoApplyStain(slot); + if (Im.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) + _manager.ChangeApplyStains(Selection, slot, apply); } else foreach (var slot in slots) { - var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selection.Design!.DoApplyEquip(slot); - if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) - _manager.ChangeApplyItem(_selection.Design!, slot, apply); + var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : Selection.DoApplyEquip(slot); + if (Im.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) + _manager.ChangeApplyItem(Selection, slot, apply); } } - ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[] - { - EquipSlot.MainHand, - EquipSlot.OffHand, - }); + ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, [EquipSlot.MainHand, EquipSlot.OffHand]); - ImUtf8.IconDummy(); + Im.FrameDummy(); ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); - ImUtf8.IconDummy(); + Im.FrameDummy(); ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); - ImUtf8.IconDummy(); + Im.FrameDummy(); ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true, EquipSlotExtensions.FullSlots); - ImUtf8.IconDummy(); + Im.FrameDummy(); DrawParameterApplication(); - ImUtf8.IconDummy(); + Im.FrameDummy(); DrawBonusSlotApplication(); } } @@ -327,9 +295,9 @@ public class DesignPanel : IPanel var enabled = _config.DeleteDesignModifier.IsActive(); bool? equip = null; bool? customize = null; - var size = new Vector2(210 * Im.Style.GlobalScale, 0); - if (ImUtf8.ButtonEx("Disable Everything"u8, - "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + var size = ImEx.ScaledVectorX(210); + if (ImEx.Button("Disable Everything"u8, size, + "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, !enabled)) { equip = false; @@ -337,11 +305,11 @@ public class DesignPanel : IPanel } if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Line.Same(); - if (ImUtf8.ButtonEx("Enable Everything"u8, - "Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + if (ImEx.Button("Enable Everything"u8, size, + "Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, !enabled)) { equip = true; @@ -349,10 +317,10 @@ public class DesignPanel : IPanel } if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); - if (ImUtf8.ButtonEx("Equipment Only"u8, - "Enable application of anything related to gear, disable anything that is not related to gear."u8, size, + if (ImEx.Button("Equipment Only"u8, size, + "Enable application of anything related to gear, disable anything that is not related to gear."u8, !enabled)) { equip = true; @@ -360,11 +328,11 @@ public class DesignPanel : IPanel } if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Line.Same(); - if (ImUtf8.ButtonEx("Customization Only"u8, - "Enable application of anything related to customization, disable anything that is not related to customization."u8, size, + if (ImEx.Button("Customization Only"u8, size, + "Enable application of anything related to customization, disable anything that is not related to customization."u8, !enabled)) { equip = false; @@ -372,85 +340,82 @@ public class DesignPanel : IPanel } if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); - if (ImUtf8.ButtonEx("Default Application"u8, + if (ImEx.Button("Default Application"u8, size, "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, - size, !enabled)) { - _manager.ChangeApplyMulti(_selection.Design!, true, true, true, false, true, true, false, true); - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, false); + _manager.ChangeApplyMulti(Selection, true, true, true, false, true, true, false, true); + _manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, false); } if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); Im.Line.Same(); - if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, - size, - !enabled)) - _manager.ChangeApplyMulti(_selection.Design!, null, null, null, false, null, null, false, null); + if (ImEx.Button("Disable Advanced"u8, size, "Disable all advanced dyes and customizations but keep everything else as is."u8, !enabled)) + _manager.ChangeApplyMulti(Selection, null, null, null, false, null, null, false, null); if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selection.Design!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, + _manager.ChangeApplyMulti(Selection, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.HatState, equip.Value); - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.VisorState, equip.Value); - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.WeaponState, equip.Value); - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.EarState, equip.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.HatState, equip.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.VisorState, equip.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.WeaponState, equip.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.EarState, equip.Value); } if (customize.HasValue) - _manager.ChangeApplyMeta(_selection.Design!, MetaIndex.Wetness, customize.Value); + _manager.ChangeApplyMeta(Selection, MetaIndex.Wetness, customize.Value); } - private static readonly IReadOnlyList MetaLabels = + private static readonly IReadOnlyList MetaLabels = [ - "Apply Wetness", - "Apply Hat Visibility", - "Apply Visor State", - "Apply Weapon Visibility", - "Apply Viera Ear Visibility", + new("Apply Wetness"u8), + new("Apply Hat Visibility"u8), + new("Apply Visor State"u8), + new("Apply Weapon Visibility"u8), + new("Apply Viera Ear Visibility"u8), ]; private void DrawMetaApplication() { - using var id = ImUtf8.PushId("Meta"); - const uint all = (uint)MetaExtensions.All; - var flags = (uint)_selection.Design!.Application.Meta; - var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); + using var id = Im.Id.Push("Meta"u8); + const ulong all = (ulong)MetaExtensions.All; + var flags = (ulong)Selection.Application.Meta; + var bigChange = Im.Checkbox("Apply All Meta Changes"u8, ref flags, all); foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) { - var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selection.Design!.DoApplyMeta(index); - if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyMeta(_selection.Design!, index, apply); + var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : Selection.DoApplyMeta(index); + if (Im.Checkbox(label, ref apply) || bigChange) + _manager.ChangeApplyMeta(Selection, index, apply); } } - private static readonly IReadOnlyList BonusSlotLabels = + private static readonly IReadOnlyList BonusSlotLabels = [ - "Apply Facewear", + new("Apply Facewear"u8), ]; private void DrawBonusSlotApplication() { - using var id = ImUtf8.PushId("Bonus"u8); - var flags = _selection.Design!.Application.BonusItem; - var bigChange = BonusExtensions.AllFlags.Count > 1 && ImUtf8.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All); + using var id = Im.Id.Push("Bonus"u8); + var flags = Selection.Application.BonusItem; + var bigChange = BonusExtensions.AllFlags.Count > 1 && Im.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All); foreach (var (index, label) in BonusExtensions.AllFlags.Zip(BonusSlotLabels)) { - var apply = bigChange ? flags.HasFlag(index) : _selection.Design!.DoApplyBonusItem(index); - if (ImUtf8.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyBonusItem(_selection.Design!, index, apply); + var apply = bigChange ? flags.HasFlag(index) : Selection.DoApplyBonusItem(index); + if (Im.Checkbox(label, ref apply) || bigChange) + _manager.ChangeApplyBonusItem(Selection, index, apply); } } @@ -458,69 +423,62 @@ public class DesignPanel : IPanel private void DrawParameterApplication() { using var id = Im.Id.Push("Parameter"u8); - var flags = (ulong)_selection.Design!.Application.Parameters; + var flags = (ulong)Selection.Application.Parameters; var bigChange = Im.Checkbox("Apply All Customize Parameters"u8, ref flags, (ulong)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { - var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selection.Design!.DoApplyParameter(flag); + var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : Selection.DoApplyParameter(flag); if (Im.Checkbox($"Apply {flag.ToNameU8()}", ref apply) || bigChange) - _manager.ChangeApplyParameter(_selection.Design!, flag, apply); + _manager.ChangeApplyParameter(Selection, flag, apply); } } public ReadOnlySpan Id - => "Designs"u8; + => "DesignPanel"u8; public void Draw() { - using var group = ImUtf8.Group(); - //if (_selection.DesignPaths.Count > 1) - if (false) + _importService.CreateDatSource(); + if (_fileSystem.Selection.OrderedNodes.Count > 1) { _multiDesignPanel.Draw(); + return; } - else + + DrawPanel(); + + if (_fileSystem.Selection.Selection is null || Selection.WriteProtected()) + return; + + if (_importService.CreateDatTarget(out var dat)) { - DrawHeader(); - DrawPanel(); - - if (_selection.Design == null || _selection.Design.WriteProtected()) - return; - - if (_importService.CreateDatTarget(out var dat)) - { - _manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); - _manager.ChangeCustomize(_selection.Design!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); - foreach (var idx in CustomizationExtensions.AllBasic) - _manager.ChangeCustomize(_selection.Design!, idx, dat.Customize[idx]); - Glamourer.Messager.NotificationMessage( - $"Applied games .dat file {dat.Description} customizations to {_selection.Design.Name}.", NotificationType.Success, false); - } - else if (_importService.CreateCharaTarget(out var designBase, out var name)) - { - _manager.ApplyDesign(_selection.Design!, designBase); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selection.Design.Name}.", - NotificationType.Success, false); - } + _manager.ChangeCustomize(Selection, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); + _manager.ChangeCustomize(Selection, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); + foreach (var idx in CustomizationExtensions.AllBasic) + _manager.ChangeCustomize(Selection, idx, dat.Customize[idx]); + Glamourer.Messager.NotificationMessage( + $"Applied games .dat file {dat.Description} customizations to {Selection.Name}.", NotificationType.Success, false); + } + else if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + _manager.ApplyDesign(Selection, designBase); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {Selection.Name}.", + NotificationType.Success, false); } - - _importService.CreateDatSource(); } private void DrawPanel() { - using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available); - if (!table || _selection.Design is null) + using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.ScrollY, Im.ContentRegion.Available); + if (!table || _fileSystem.Selection.Selection is null) return; - ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableNextColumn(); - if (_selection.Design is null) - return; + table.SetupScrollFreeze(0, 1); + table.NextColumn(); Im.Dummy(Vector2.Zero); DrawButtonRow(); - ImGui.TableNextColumn(); + table.NextColumn(); DrawCustomize(); DrawEquipment(); @@ -547,15 +505,15 @@ public class DesignPanel : IPanel private void DrawApplyToSelf() { var (id, data) = _objects.PlayerData; - if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, - "Apply the current design with its settings to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", + if (!ImEx.Button("Apply to Yourself"u8, Vector2.Zero, + "Apply the current design with its settings to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); + using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); + _state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -564,33 +522,33 @@ public class DesignPanel : IPanel var (id, data) = _objects.TargetData; var tt = id.IsValid ? data.Valid - ? "Apply the current design with its settings to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." - : "The current target can not be manipulated." - : "No valid target selected."; - if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) + ? "Apply the current design with its settings to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8 + : "The current target can not be manipulated."u8 + : "No valid target selected."u8; + if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - using var _ = _selection.Design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selection.Design!, ApplySettings.ManualWithLinks with { IsFinal = true }); + using var _ = Selection.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); + _state.ApplyDesign(state, Selection, ApplySettings.ManualWithLinks with { IsFinal = true }); } } private void DrawSaveToDat() { - var verified = _importService.Verify(_selection.Design!.DesignData.Customize, out _); + var verified = _importService.Verify(Selection.DesignData.Customize, out _); var tt = verified - ? "Export the currently configured customizations of this design to a character creation data file." - : "The current design contains customizations that can not be applied during character creation."; + ? "Export the currently configured customizations of this design to a character creation data file."u8 + : "The current design contains customizations that can not be applied during character creation."u8; var startPath = GetUserPath(); - if (startPath.Length == 0) + if (startPath.Length is 0) startPath = null; - if (ImGuiUtil.DrawDisabledButton("Export to Dat", Vector2.Zero, tt, !verified)) + if (ImEx.Button("Export to Dat"u8, Vector2.Zero, tt, !verified)) _fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) => { - if (v && _selection.Design != null) - _importService.SaveDesignAsDat(path, _selection.Design!.DesignData.Customize, _selection.Design!.Name); + if (v && _fileSystem.Selection.Selection?.GetValue() is not null) + _importService.SaveDesignAsDat(path, Selection.DesignData.Customize, Selection.Name); }, startPath); _fileDialog.Draw(); @@ -598,165 +556,4 @@ public class DesignPanel : IPanel private static unsafe string GetUserPath() => Framework.Instance()->UserPathString; - - - - private sealed class LockButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selection.Design != null; - - protected override string Description - => panel._selection.Design!.WriteProtected() - ? "Make this design editable." - : "Write-protect this design."; - - protected override FontAwesomeIcon Icon - => panel._selection.Design!.WriteProtected() - ? FontAwesomeIcon.Lock - : FontAwesomeIcon.LockOpen; - - protected override void OnClick() - => panel._manager.SetWriteProtection(panel._selection.Design!, !panel._selection.Design!.WriteProtected()); - } - - private sealed class SetFromClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => panel._selection.Design?.WriteProtected() ?? true; - - protected override string Description - => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Clipboard; - - protected override void OnClick() - { - try - { - var text = ImGui.GetClipboardText(); - var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._manager.ApplyDesign(panel._selection.Design!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._selection.Design!.Name}.", - $"Could not apply clipboard to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); - } - } - } - - private sealed class DesignUndoButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => !panel._manager.CanUndo(panel._selection.Design) || (panel._selection.Design?.WriteProtected() ?? true); - - protected override string Description - => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.SyncAlt; - - protected override void OnClick() - { - try - { - panel._manager.UndoDesignChange(panel._selection.Design!); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selection.Design!.Name}.", - NotificationType.Error, - false); - } - } - } - - private sealed class ExportToClipboardButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selection.Design != null; - - protected override string Description - => "Copy the current design to your clipboard."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Copy; - - protected override void OnClick() - { - try - { - var text = panel._converter.ShareBase64(panel._selection.Design!); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selection.Design!.Name} data to clipboard.", - $"Could not copy data from design {panel._selection.Design!.Identifier} to clipboard", NotificationType.Error, false); - } - } - } - - private sealed class ApplyCharacterButton(DesignPanel panel) : Button - { - public override bool Visible - => panel._selection.Design != null && panel._objects.Player.Valid; - - protected override string Description - => "Overwrite this design with your character's current state."; - - protected override bool Disabled - => panel._selection.Design?.WriteProtected() ?? true; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.UserEdit; - - protected override void OnClick() - { - try - { - var (player, actor) = panel._objects.PlayerData; - if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) - throw new Exception("No player state available."); - - var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) - ?? throw new Exception("The clipboard did not contain valid data."); - panel._selection.Design!.GetMaterialDataRef().Clear(); - panel._manager.ApplyDesign(panel._selection.Design!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selection.Design!.Name}.", - $"Could not apply player state to design {panel._selection.Design!.Identifier}", NotificationType.Error, false); - } - } - } - - private sealed class UndoButton(DesignPanel panel) : Button - { - protected override string Description - => "Undo the last change."; - - protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Undo; - - public override bool Visible - => panel._selection.Design != null; - - protected override bool Disabled - => (panel._selection.Design?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selection.Design); - - protected override void OnClick() - => panel._history.Undo(panel._selection.Design!); - } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs b/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs deleted file mode 100644 index f070208..0000000 --- a/Glamourer/Gui/Tabs/DesignTab/DesignSelection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Glamourer.Designs; -using Luna; - -namespace Glamourer.Gui.Tabs.DesignTab; - -public sealed class DesignSelection : IUiService, IDisposable -{ - public Design? Design { get; private set; } - - public void Dispose() - { } -} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 4bef23b..839c392 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.ImGuiNotification; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop; using ImSharp; @@ -7,36 +7,55 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignTab(DesignFileSystemSelector selector, DesignPanel panel, ImportService importService, DesignManager manager) - : ITab +public sealed class DesignTab : TwoPanelLayout, ITab { - public ReadOnlySpan Label + private readonly ImportService _importService; + private readonly DesignManager _manager; + private readonly UiConfig _uiConfig; + + public DesignTab(DesignFileSystemDrawer drawer, DesignPanel panel, ImportService importService, DesignManager manager, DesignFilter filter, + DesignHeader header, UiConfig uiConfig) + { + LeftHeader = drawer.Header; + LeftPanel = drawer; + LeftFooter = drawer.Footer; + + RightHeader = header; + RightPanel = panel; + RightFooter = NopHeaderFooter.Instance; + _importService = importService; + _manager = manager; + _uiConfig = uiConfig; + } + + public override ReadOnlySpan Label => "Designs"u8; public MainTabType Identifier => MainTabType.Designs; - public void DrawContent() + protected override void DrawLeftGroup(in TwoPanelWidth width) { - selector.Draw(); - if (importService.CreateCharaTarget(out var designBase, out var name)) + base.DrawLeftGroup(in width); + if (_importService.CreateCharaTarget(out var designBase, out var name)) { - var newDesign = manager.CreateClone(designBase, name, true); + var newDesign = _manager.CreateClone(designBase, name, true); Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}", NotificationType.Success, false); } - - Im.Line.Same(); - panel.Draw(); - importService.CreateCharaSource(); + _importService.CreateCharaSource(); } - //protected override void SetWidth(float width, ScalingMode mode) - // => _uiConfig.ActorsTabScale = new TwoPanelWidth(width, mode); - // - //protected override float MinimumWidth - // => LeftFooter.MinimumWidth; - // - //protected override float MaximumWidth - // => Im.Window.Width - 500 * Im.Style.GlobalScale; + protected override float MinimumWidth + => LeftFooter.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; + + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.DesignsTabScale = new TwoPanelWidth(width, mode); + + + public void DrawContent() + => Draw(_uiConfig.DesignsTabScale); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs b/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs new file mode 100644 index 0000000..4242b0a --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignUndoButton.cs @@ -0,0 +1,39 @@ +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignUndoButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton +{ + public override bool IsVisible + => fileSystem.Selection.Selection is not null; + + public override AwesomeIcon Icon + => LunaStyle.ResetIcon; + + public override bool Enabled + => !((Design)fileSystem.Selection.Selection!.Value).WriteProtected() && manager.CanUndo((Design)fileSystem.Selection.Selection!.Value); + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text( + "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."u8); + + public override void OnClick() + { + try + { + manager.UndoDesignChange((Design)fileSystem.Selection.Selection!.Value); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, + $"Could not undo last changes to {((Design)fileSystem.Selection.Selection!.Value).Name}.", + NotificationType.Error, false); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs b/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs new file mode 100644 index 0000000..990a3f2 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/ExportToClipboardButton.cs @@ -0,0 +1,36 @@ +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class ExportToClipboardButton(DesignFileSystem fileSystem, DesignConverter converter) : BaseIconButton +{ + public override bool IsVisible + => fileSystem.Selection.Selection is not null; + + public override AwesomeIcon Icon + => LunaStyle.ToClipboardIcon; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Copy the current design to your clipboard."u8); + + public override void OnClick() + { + var design = (Design)fileSystem.Selection.Selection!.Value; + try + { + var text = converter.ShareBase64(design); + Im.Clipboard.Set(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {design.Name} data to clipboard.", + $"Could not copy data from design {design.Identifier} to clipboard", NotificationType.Error, false); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/LockButton.cs b/Glamourer/Gui/Tabs/DesignTab/LockButton.cs new file mode 100644 index 0000000..62225b8 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/LockButton.cs @@ -0,0 +1,28 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class LockButton(DesignFileSystem fileSystem, DesignManager manager) : BaseIconButton +{ + public override bool IsVisible + => fileSystem.Selection.Selection is not null; + + public override AwesomeIcon Icon + => ((Design)fileSystem.Selection.Selection!.Value).WriteProtected() + ? LunaStyle.LockedIcon + : LunaStyle.UnlockedIcon; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text(((Design)fileSystem.Selection.Selection!.Value).WriteProtected() + ? "Make this design editable."u8 + : "Write-protect this design."u8); + + public override void OnClick() + => manager.SetWriteProtection((Design)fileSystem.Selection.Selection!.Value, + !((Design)fileSystem.Selection.Selection!.Value).WriteProtected()); +} diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index cfde48b..16d6f8d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.ImGuiNotification; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -8,11 +8,14 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab(PenumbraService penumbra, DesignSelection selection, DesignManager manager, Configuration.Configuration config) +public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystem fileSystem, DesignManager manager, Configuration config) { - private readonly ModCombo _modCombo = new(penumbra, selection); + private readonly ModCombo _modCombo = new(penumbra, fileSystem); private (Mod, ModSettings)[]? _copy; + private Design Selection + => (Design)fileSystem.Selection.Selection!.Value; + public void Draw() { using var h = DesignPanelFlag.ModAssociations.Header(config); @@ -36,7 +39,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select { var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0); if (Im.Button("Copy All to Clipboard"u8, size)) - _copy = selection.Design!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + _copy = Selection.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); Im.Line.Same(); @@ -45,7 +48,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select ? $"Add {_copy.Length} mod association(s) from clipboard." : "Copy some mod associations to the clipboard, first."u8, _copy is null)) foreach (var (mod, setting) in _copy!) - manager.UpdateMod(selection.Design!, mod, setting); + manager.UpdateMod(Selection, mod, setting); Im.Line.Same(); @@ -54,10 +57,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." : "Copy some mod associations to the clipboard, first."u8, _copy is null)) { - while (selection.Design!.AssociatedMods.Count > 0) - manager.RemoveMod(selection.Design!, selection.Design!.AssociatedMods.Keys[0]); + while (Selection.AssociatedMods.Count > 0) + manager.RemoveMod(Selection, Selection.AssociatedMods.Keys[0]); foreach (var (mod, setting) in _copy!) - manager.AddMod(selection.Design!, mod, setting); + manager.AddMod(Selection, mod, setting); } } @@ -76,13 +79,13 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select var (id, name) = penumbra.CurrentCollection; if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero, $"Try to apply all associated mod settings to Penumbras current collection {name}", - selection.Design!.AssociatedMods.Count is 0 || id == Guid.Empty)) + Selection.AssociatedMods.Count is 0 || id == Guid.Empty)) ApplyAll(); } public void ApplyAll() { - foreach (var (mod, settings) in selection.Design!.AssociatedMods) + foreach (var (mod, settings) in Selection.AssociatedMods) penumbra.SetMod(mod, settings, StateSource.Manual, false); } @@ -104,7 +107,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select Mod? removedMod = null; (Mod mod, ModSettings settings)? updatedMod = null; - foreach (var (idx, (mod, settings)) in selection.Design!.AssociatedMods.Index()) + foreach (var (idx, (mod, settings)) in Selection.AssociatedMods.Index()) { using var id = Im.Id.Push(idx); DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp); @@ -117,10 +120,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select DrawNewModRow(table); if (removedMod.HasValue) - manager.RemoveMod(selection.Design!, removedMod.Value); + manager.RemoveMod(Selection, removedMod.Value); if (updatedMod.HasValue) - manager.UpdateMod(selection.Design!, updatedMod.Value.mod, updatedMod.Value.settings); + manager.UpdateMod(Selection, updatedMod.Value.mod, updatedMod.Value.settings); } private void DrawAssociatedModRow(in Im.TableDisposable table, Mod mod, ModSettings settings, out Mod? removedMod, @@ -245,12 +248,12 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignSelection select table.NextColumn(); var tt = currentDir.Length is 0 ? "Please select a mod first."u8 - : selection.Design!.AssociatedMods.ContainsKey(new Mod(_modCombo.SelectionName, currentDir)) + : Selection.AssociatedMods.ContainsKey(new Mod(_modCombo.SelectionName, currentDir)) ? "The design already contains an association with the selected mod."u8 : StringU8.Empty; if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0)) - manager.AddMod(selection.Design!, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings); + manager.AddMod(Selection, new Mod(_modCombo.SelectionName, _modCombo.Selection), _modCombo.Settings); table.NextColumn(); _modCombo.Draw("##new"u8, Im.ContentRegion.Available.X); } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 58f5adb..98575a9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,9 +1,10 @@ -using Glamourer.Interop.Penumbra; +using Glamourer.Designs; +using Glamourer.Interop.Penumbra; using ImSharp; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection) : FilterComboBase(new ModFilter()) +public sealed class ModCombo(PenumbraService penumbra, DesignFileSystem fileSystem) : FilterComboBase(new ModFilter()) { public readonly struct CacheItem(in Mod mod, in ModSettings settings, int count) { @@ -42,7 +43,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignSelection selection => Im.Style.TextHeightWithSpacing; protected override IEnumerable GetItems() - => penumbra.GetMods(selection.Design?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count)); + => penumbra.GetMods(fileSystem.Selection.Selection?.GetValue()?.FilteredItemNames.ToArray() ?? []).Select(t => new CacheItem(t.Mod, t.Settings, t.Count)); protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) { diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 472f571..71e0e30 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.Interop.Material; using ImSharp; using Luna; @@ -6,21 +7,21 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; public class MultiDesignPanel( - DesignFileSystemSelector selector, + DesignFileSystem fileSystem, DesignManager editor, DesignColors colors, - Configuration.Configuration config) + Configuration config) { private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() { - if (selector.SelectedPaths.Count == 0) + if (fileSystem.Selection.OrderedNodes.Count is 0) return; var width = ImEx.ScaledVectorX(145); var treeNodePos = Im.Cursor.Position; - _numDesigns = DrawDesignList(); + DrawDesignList(); DrawCounts(treeNodePos); var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); @@ -36,14 +37,15 @@ public class MultiDesignPanel( private void DrawCounts(Vector2 treeNodePos) { var startPos = Im.Cursor.Position; - var numFolders = selector.SelectedPaths.Count - _numDesigns; + var numDesigns = fileSystem.Selection.DataNodes.Count; + var numFolders = fileSystem.Selection.Folders.Count; Im.Cursor.Position = treeNodePos; - ImEx.TextRightAligned((_numDesigns, numFolders) switch + ImEx.TextRightAligned((numDesigns, numFolders) switch { - (0, 0) => StringU8.Empty, // should not happen - ( > 0, 0) => $"{_numDesigns} Designs", - (0, > 0) => $"{numFolders} Folders", - _ => $"{_numDesigns} Designs, {numFolders} Folders", + (0, 0) => StringU8.Empty, // should not happen + (> 0, 0) => $"{numDesigns} Designs", + (0, > 0) => $"{numFolders} Folders", + _ => $"{numDesigns} Designs, {numFolders} Folders", }); Im.Cursor.Position = startPos; } @@ -59,10 +61,10 @@ public class MultiDesignPanel( _numAdvancedDyes = 0; } - private bool CountLeaves(DesignFileSystem.IPath path) + private void CountLeaves(IFileSystemNode path) { - if (path is not DesignFileSystem.Leaf l) - return false; + if (path is not IFileSystemData l) + return; if (l.Value.QuickDesign) ++_numQuickDesignEnabled; @@ -79,55 +81,48 @@ public class MultiDesignPanel( ++_numDesignsWithAdvancedDyes; _numAdvancedDyes += l.Value.Materials.Count; } - - return true; } - private int DrawDesignList() + private void DrawDesignList() { ResetCounts(); using var tree = Im.Tree.Node("Currently Selected Objects"u8, TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen); Im.Separator(); if (!tree) - return selector.SelectedPaths.Count(CountLeaves); + return; var sizeType = new Vector2(Im.Style.FrameHeight); var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; - var numDesigns = 0; using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground)) { if (!table) - return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); + return; table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X); table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods); table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders); - var i = 0; - foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p)) - .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) + foreach (var (index, node) in fileSystem.Selection.OrderedNodes.Index()) { - using var id = Im.Id.Push(i++); - var (icon, text) = path is DesignFileSystem.Leaf l + using var id = Im.Id.Push(index); + var (icon, text) = node is IFileSystemData l ? (LunaStyle.RemoveFileIcon, l.Value.Name.Text) : (LunaStyle.RemoveFolderIcon, string.Empty); table.NextColumn(); if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType)) - selector.RemovePathFromMultiSelection(path); + fileSystem.Selection.RemoveFromSelection(node); table.DrawFrameColumn(text); - table.DrawFrameColumn(fullName); + table.DrawFrameColumn(node.FullPath); - if (CountLeaves(path)) - ++numDesigns; + CountLeaves(node); } } Im.Separator(); - return numDesigns; } private string _tag = string.Empty; @@ -138,7 +133,6 @@ public class MultiDesignPanel( private int _numDesignsResetDyes; private int _numAdvancedDyes; private int _numDesignsWithAdvancedDyes; - private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; @@ -153,23 +147,25 @@ public class MultiDesignPanel( UpdateTagCache(); Im.Line.Same(); if (ImEx.Button(_addDesigns.Count > 0 - ? $"Add to {_addDesigns.Count} Designs" - : "Add"u8, width, _addDesigns.Count is 0 - ? _tag.Length is 0 - ? "No tag specified."u8 - : $"All designs selected already contain the tag \"{_tag}\"." - : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) + ? $"Add to {_addDesigns.Count} Designs" + : "Add"u8, width, _addDesigns.Count is 0 + ? _tag.Length is 0 + ? "No tag specified."u8 + : $"All designs selected already contain the tag \"{_tag}\"." + : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", + _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.AddTag(design, _tag); Im.Line.Same(); if (ImEx.Button(_removeDesigns.Count > 0 - ? $"Remove from {_removeDesigns.Count} Designs" - : "Remove", width, _removeDesigns.Count is 0 - ? _tag.Length is 0 - ? "No tag specified."u8 - : $"No selected design contains the tag \"{_tag}\" locally." - : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) + ? $"Remove from {_removeDesigns.Count} Designs" + : "Remove", width, _removeDesigns.Count is 0 + ? _tag.Length is 0 + ? "No tag specified."u8 + : $"No selected design contains the tag \"{_tag}\" locally." + : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}", + _removeDesigns.Count is 0)) foreach (var (design, index) in _removeDesigns) editor.RemoveTag(design, index); Im.Separator(); @@ -181,23 +177,21 @@ public class MultiDesignPanel( ImEx.TextFrameAligned("Multi QDB:"u8); Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); - var diff = _numDesigns - _numQuickDesignEnabled; + var diff = fileSystem.Selection.DataNodes.Count - _numQuickDesignEnabled; if (ImEx.Button("Display Selected Designs in QDB"u8, buttonWidth, diff is 0 - ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." - : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs.", diff is 0)) - { - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetQuickDesign(design.Value, true); - } + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already displayed in the quick design bar." + : $"Display all {fileSystem.Selection.DataNodes.Count} selected designs in the quick design bar. Changes {diff} designs.", + diff is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.SetQuickDesign(design.GetValue()!, true); Im.Line.Same(); if (ImEx.Button("Hide Selected Designs in QDB"u8, buttonWidth, _numQuickDesignEnabled is 0 - ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." - : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.", _numQuickDesignEnabled is 0)) - { - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetQuickDesign(design.Value, false); - } + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already hidden in the quick design bar." + : $"Hide all {fileSystem.Selection.DataNodes.Count} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.", + _numQuickDesignEnabled is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.SetQuickDesign(design.GetValue()!, false); Im.Separator(); } @@ -207,19 +201,20 @@ public class MultiDesignPanel( ImEx.TextFrameAligned("Multi Lock:"u8); Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); - var diff = _numDesigns - _numDesignsLocked; + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsLocked; if (ImEx.Button("Turn Write-Protected"u8, buttonWidth, diff is 0 - ? $"All {_numDesigns} selected designs are already write protected." - : $"Write-protect all {_numDesigns} designs. Changes {diff} designs.", diff is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetWriteProtection(design.Value, true); + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs are already write protected." + : $"Write-protect all {fileSystem.Selection.DataNodes.Count} designs. Changes {diff} designs.", diff is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.SetWriteProtection(design.GetValue()!, true); Im.Line.Same(); if (ImEx.Button("Remove Write-Protection"u8, buttonWidth, _numDesignsLocked is 0 - ? $"None of the {_numDesigns} selected designs are write-protected." - : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs.", _numDesignsLocked is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.SetWriteProtection(design.Value, false); + ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs are write-protected." + : $"Remove the write protection of the {fileSystem.Selection.DataNodes.Count} selected designs. Changes {_numDesignsLocked} designs.", + _numDesignsLocked is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.SetWriteProtection(design.GetValue()!, false); Im.Separator(); } @@ -228,19 +223,21 @@ public class MultiDesignPanel( ImEx.TextFrameAligned("Settings:"u8); Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); - var diff = _numDesigns - _numDesignsResetSettings; + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetSettings; if (ImEx.Button("Set Reset Temp. Settings"u8, buttonWidth, diff is 0 - ? $"All {_numDesigns} selected designs already reset temporary settings." - : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs.", diff is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetTemporarySettings(design.Value, true); + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already reset temporary settings." + : $"Make all {fileSystem.Selection.DataNodes.Count} selected designs reset temporary settings. Changes {diff} designs.", + diff is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeResetTemporarySettings(design.GetValue()!, true); Im.Line.Same(); if (ImEx.Button("Remove Reset Temp. Settings"u8, buttonWidth, _numDesignsResetSettings is 0 - ? $"None of the {_numDesigns} selected designs reset temporary settings." - : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs.", _numDesignsResetSettings is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetTemporarySettings(design.Value, false); + ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs reset temporary settings." + : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs.", + _numDesignsResetSettings is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeResetTemporarySettings(design.GetValue()!, false); Im.Separator(); } @@ -249,19 +246,20 @@ public class MultiDesignPanel( ImEx.TextFrameAligned("Adv. Dyes:"u8); Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); - var diff = _numDesigns - _numDesignsResetDyes; + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsResetDyes; if (ImEx.Button("Set Reset Dyes"u8, buttonWidth, diff is 0 - ? $"All {_numDesigns} selected designs already reset advanced dyes." - : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs.", diff is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetAdvancedDyes(design.Value, true); + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already reset advanced dyes." + : $"Make all {fileSystem.Selection.DataNodes.Count} selected designs reset advanced dyes. Changes {diff} designs.", diff is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeResetAdvancedDyes(design.GetValue()!, true); Im.Line.Same(); if (ImEx.Button("Remove Reset Dyes"u8, buttonWidth, _numDesignsLocked is 0 - ? $"None of the {_numDesigns} selected designs reset advanced dyes." - : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs.", _numDesignsResetDyes is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeResetAdvancedDyes(design.Value, false); + ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs reset advanced dyes." + : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs.", + _numDesignsResetDyes is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeResetAdvancedDyes(design.GetValue()!, false); Im.Separator(); } @@ -270,19 +268,20 @@ public class MultiDesignPanel( ImEx.TextFrameAligned("Redrawing:"u8); Im.Line.Same(offset, Im.Style.ItemSpacing.X); var buttonWidth = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0); - var diff = _numDesigns - _numDesignsForcedRedraw; + var diff = fileSystem.Selection.DataNodes.Count - _numDesignsForcedRedraw; if (ImEx.Button("Force Redraws"u8, buttonWidth, diff is 0 - ? $"All {_numDesigns} selected designs already force redraws." - : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs.", diff is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeForcedRedraw(design.Value, true); + ? $"All {fileSystem.Selection.DataNodes.Count} selected designs already force redraws." + : $"Make all {fileSystem.Selection.DataNodes.Count} designs force redraws. Changes {diff} designs.", diff is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeForcedRedraw(design.GetValue()!, true); Im.Line.Same(); if (ImEx.Button("Remove Forced Redraws"u8, buttonWidth, _numDesignsLocked is 0 - ? $"None of the {_numDesigns} selected designs force redraws." - : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs.", _numDesignsForcedRedraw is 0)) - foreach (var design in selector.SelectedPaths.OfType()) - editor.ChangeForcedRedraw(design.Value, false); + ? $"None of the {fileSystem.Selection.DataNodes.Count} selected designs force redraws." + : $"Stop all {fileSystem.Selection.DataNodes.Count} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs.", + _numDesignsForcedRedraw is 0)) + foreach (var design in fileSystem.Selection.DataNodes) + editor.ChangeForcedRedraw(design.GetValue()!, false); Im.Separator(); } @@ -299,30 +298,28 @@ public class MultiDesignPanel( UpdateColorCache(); Im.Line.Same(); if (ImEx.Button(_addDesigns.Count > 0 - ? $"Set for {_addDesigns.Count} Designs" - : "Set"u8, width, _addDesigns.Count is 0 - ? _colorComboSelection switch - { - null => "No color specified."u8, - DesignColors.AutomaticName => "Use the other button to set to automatic."u8, - _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", - } - : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", _addDesigns.Count is 0)) - { + ? $"Set for {_addDesigns.Count} Designs" + : "Set"u8, width, _addDesigns.Count is 0 + ? _colorComboSelection switch + { + null => "No color specified."u8, + DesignColors.AutomaticName => "Use the other button to set to automatic."u8, + _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", + } + : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", + _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.ChangeColor(design, _colorComboSelection!); - } Im.Line.Same(); if (ImEx.Button(_removeDesigns.Count > 0 - ? $"Unset {_removeDesigns.Count} Designs" - : "Unset"u8, width, _removeDesigns.Count is 0 - ? "No selected design is set to a non-automatic color."u8 - : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}", _removeDesigns.Count is 0)) - { + ? $"Unset {_removeDesigns.Count} Designs" + : "Unset"u8, width, _removeDesigns.Count is 0 + ? "No selected design is set to a non-automatic color."u8 + : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}", + _removeDesigns.Count is 0)) foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); - } Im.Separator(); } @@ -333,14 +330,15 @@ public class MultiDesignPanel( Im.Line.Same(offset, Im.Style.ItemSpacing.X); var enabled = config.DeleteDesignModifier.IsActive(); if (ImEx.Button("Delete All Advanced Dyes"u8, Im.ContentRegion.Available with { Y = 0 }, _numDesignsWithAdvancedDyes is 0 - ? "No selected designs contain any advanced dyes."u8 - : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.", + ? "No selected designs contain any advanced dyes."u8 + : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs.", !enabled || _numDesignsWithAdvancedDyes is 0)) - foreach (var design in selector.SelectedPaths.OfType()) + foreach (var design in fileSystem.Selection.DataNodes) { - while (design.Value.Materials.Count > 0) - editor.ChangeMaterialValue(design.Value, MaterialValueIndex.FromKey(design.Value.Materials[0].Item1), null); + while (design.GetValue()!.Materials.Count > 0) + editor.ChangeMaterialValue(design.GetValue()!, + MaterialValueIndex.FromKey(design.GetValue()!.Materials[0].Item1), null); } if (!enabled && _numDesignsWithAdvancedDyes is not 0) @@ -359,8 +357,8 @@ public class MultiDesignPanel( using (Im.Group()) { if (ImEx.Button("Disable Everything"u8, width, - _numDesigns > 0 - ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + fileSystem.Selection.DataNodes.Count > 0 + ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {fileSystem.Selection.DataNodes.Count} designs." : "No designs selected."u8, !enabled)) { equip = false; @@ -372,8 +370,8 @@ public class MultiDesignPanel( Im.Line.Same(); if (ImEx.Button("Enable Everything"u8, width, - _numDesigns > 0 - ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + fileSystem.Selection.DataNodes.Count > 0 + ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {fileSystem.Selection.DataNodes.Count} designs." : "No designs selected."u8, !enabled)) { equip = true; @@ -384,8 +382,8 @@ public class MultiDesignPanel( Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); if (ImEx.Button("Equipment Only"u8, width, - _numDesigns > 0 - ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." + fileSystem.Selection.DataNodes.Count > 0 + ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {fileSystem.Selection.DataNodes.Count} designs." : "No designs selected."u8, !enabled)) { equip = true; @@ -397,8 +395,8 @@ public class MultiDesignPanel( Im.Line.Same(); if (ImEx.Button("Customization Only"u8, width, - _numDesigns > 0 - ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." + fileSystem.Selection.DataNodes.Count > 0 + ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {fileSystem.Selection.DataNodes.Count} designs." : "No designs selected."u8, !enabled)) { equip = false; @@ -409,10 +407,10 @@ public class MultiDesignPanel( Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); if (ImEx.Button("Default Application"u8, width, - _numDesigns > 0 - ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." + fileSystem.Selection.DataNodes.Count > 0 + ? $"Set the application rules to the default values as if the {fileSystem.Selection.DataNodes.Count} were newly created,without any advanced features or wetness." : "No designs selected."u8, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) { editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); @@ -422,10 +420,10 @@ public class MultiDesignPanel( Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); Im.Line.Same(); - if (ImEx.Button("Disable Advanced"u8, width, _numDesigns > 0 - ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." + if (ImEx.Button("Disable Advanced"u8, width, fileSystem.Selection.DataNodes.Count > 0 + ? $"Disable all advanced dyes and customizations but keep everything else as is for all {fileSystem.Selection.DataNodes.Count} designs." : "No designs selected."u8, !enabled)) - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); if (!enabled) @@ -436,7 +434,7 @@ public class MultiDesignPanel( if (equip is null && customize is null) return; - foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + foreach (var design in fileSystem.Selection.DataNodes.Select(l => l.GetValue()!)) { editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); @@ -459,13 +457,14 @@ public class MultiDesignPanel( if (_tag.Length is 0) return; - foreach (var leaf in selector.SelectedPaths.OfType()) + foreach (var leaf in fileSystem.Selection.DataNodes) { - var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); + var design = leaf.GetValue()!; + var index = design.Tags.AsEnumerable().IndexOf(_tag); if (index >= 0) - _removeDesigns.Add((leaf.Value, index)); + _removeDesigns.Add((design, index)); else - _addDesigns.Add(leaf.Value); + _addDesigns.Add(design); } } @@ -474,12 +473,13 @@ public class MultiDesignPanel( _addDesigns.Clear(); _removeDesigns.Clear(); var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection; - foreach (var leaf in selector.SelectedPaths.OfType()) + foreach (var leaf in fileSystem.Selection.DataNodes) { - if (leaf.Value.Color.Length > 0) - _removeDesigns.Add((leaf.Value, 0)); - if (selection != DesignColors.AutomaticName && leaf.Value.Color != selection) - _addDesigns.Add(leaf.Value); + var design = leaf.GetValue()!; + if (design.Color.Length > 0) + _removeDesigns.Add((design, 0)); + if (selection != DesignColors.AutomaticName && design.Color != selection) + _addDesigns.Add(design); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs new file mode 100644 index 0000000..c35a57b --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DeleteSelectionButton.cs @@ -0,0 +1,45 @@ +using Glamourer.Config; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DeleteSelectionButton(DesignFileSystem fileSystem, DesignManager manager, Configuration config) + : BaseIconButton +{ + /// + public override AwesomeIcon Icon + => LunaStyle.DeleteIcon; + + /// + public override bool HasTooltip + => true; + + /// + public override void DrawTooltip() + { + var anySelected = fileSystem.Selection.DataNodes.Count > 0; + var modifier = Enabled; + + Im.Text(anySelected + ? "Delete the currently selected designs entirely from your drive\nThis can not be undone."u8 + : "No designs selected."u8); + if (!modifier) + Im.Text($"\nHold {config.DeleteDesignModifier} while clicking to delete the designs."); + } + + /// + public override bool Enabled + => config.DeleteDesignModifier.IsActive() && fileSystem.Selection.DataNodes.Count > 0; + + /// + public override void OnClick() + { + foreach (var node in fileSystem.Selection.DataNodes.ToArray()) + { + if (node.GetValue() is { } design) + manager.Delete(design); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs new file mode 100644 index 0000000..91310b7 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs @@ -0,0 +1,112 @@ +using Glamourer.Designs; +using Glamourer.Designs.History; +using Glamourer.Events; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignFileSystemCache : FileSystemCache +{ + public DesignFileSystemCache(DesignFileSystemDrawer parent) + : base(parent) + { + parent.DesignChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystemSelector); + parent.DesignColors.ColorChanged += OnColorChanged; + } + + private void OnColorChanged() + { + foreach (var node in AllNodes.Values) + node.Dirty = true; + } + + private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _2) + { + switch (type) + { + case DesignChanged.Type.Created: + case DesignChanged.Type.Deleted: + case DesignChanged.Type.ReloadedAll: + case DesignChanged.Type.Renamed: + case DesignChanged.Type.ChangedDescription: + case DesignChanged.Type.ChangedColor: + case DesignChanged.Type.AddedTag: + case DesignChanged.Type.RemovedTag: + case DesignChanged.Type.ChangedTag: + case DesignChanged.Type.AddedMod: + case DesignChanged.Type.RemovedMod: + case DesignChanged.Type.UpdatedMod: + case DesignChanged.Type.ChangedLink: + case DesignChanged.Type.Equip: + case DesignChanged.Type.BonusItem: + case DesignChanged.Type.Weapon: + VisibleDirty = true; + break; + } + + if (design.Node is { } node && AllNodes.TryGetValue(node, out var cache)) + cache.Dirty = true; + } + + private new DesignFileSystemDrawer Parent + => (DesignFileSystemDrawer)base.Parent; + + public override void Update() + { + if (ColorsDirty) + { + CollapsedFolderColor = ColorId.FolderCollapsed.Value().ToVector(); + ExpandedFolderColor = ColorId.FolderExpanded.Value().ToVector(); + LineColor = ColorId.FolderLine.Value().ToVector(); + Dirty &= ~IManagedCache.DirtyFlags.Colors; + OnColorChanged(); + } + } + + protected override DesignData ConvertNode(in IFileSystemNode node) + => new((IFileSystemData)node); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + Parent.DesignChanged.Unsubscribe(OnDesignChanged); + Parent.DesignColors.ColorChanged -= OnColorChanged; + } + + public sealed class DesignData(IFileSystemData node) : BaseFileSystemNodeCache + { + public readonly IFileSystemData Node = node; + public Vector4 Color; + public StringU8 Name = new(node.Value.Name.Text); + public StringU8 Incognito = new(node.Value.Incognito); + + public override void Update(FileSystemCache cache, IFileSystemNode node) + { + var drawer = (DesignFileSystemDrawer)cache.Parent; + Color = drawer.DesignColors.GetColor(Node.Value).ToVector(); + Name = new StringU8(Node.Value.Name.Text); + } + + protected override void DrawInternal(FileSystemCache cache, IFileSystemNode node) + { + var c = (DesignFileSystemCache)cache; + using var color = ImGuiColor.Text.Push(Color); + using var id = Im.Id.Push(Node.Value.Index); + var flags = node.Selected ? TreeNodeFlags.NoTreePushOnOpen | TreeNodeFlags.Selected : TreeNodeFlags.NoTreePushOnOpen; + Im.Tree.Leaf(c.Parent.Config.Ephemeral.IncognitoMode ? Incognito : Name, flags); + CheckDoubleClick(c); + } + + private void CheckDoubleClick(DesignFileSystemCache cache) + { + if (!cache.Parent.Config.AllowDoubleClickToApply) + return; + if (!Im.Item.Hovered()) + return; + + if (Im.Mouse.IsDoubleClicked(MouseButton.Left)) + cache.Parent.DesignApplier.ApplyToPlayer(Node.Value); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs new file mode 100644 index 0000000..9bb29da --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemDrawer.cs @@ -0,0 +1,65 @@ +using Glamourer.Config; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Services; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignFileSystemDrawer : FileSystemDrawer, IDisposable +{ + internal readonly Configuration Config; + internal readonly DesignApplier DesignApplier; + internal readonly DesignChanged DesignChanged; + internal readonly DesignColors DesignColors; + internal readonly DesignManager Manager; + + public DesignFileSystemDrawer(DesignFileSystem fileSystem, DesignManager manager, DesignConverter converter, Configuration config, + DesignApplier designApplier, DesignChanged designChanged, DesignColors designColors) + : base(fileSystem, new DesignFilter()) + { + Manager = manager; + Config = config; + DesignApplier = designApplier; + DesignChanged = designChanged; + DesignColors = designColors; + Footer.Buttons.AddButton(new NewDesignButton(manager), 1000); + Footer.Buttons.AddButton(new ImportDesignButton(converter, manager), 900); + Footer.Buttons.AddButton(new DuplicateDesignButton(fileSystem, manager), 800); + Footer.Buttons.AddButton(new DeleteSelectionButton(fileSystem, manager, config), -100); + + SortMode = Config.SortMode; + OnRenameChanged(Config.ShowRename, default); + Config.OnRenameChanged += OnRenameChanged; + } + + private void OnRenameChanged(RenameField newValue, RenameField _) + { + DataContext.RemoveButtons(); + DataContext.RemoveButtons(); + switch (newValue) + { + case RenameField.RenameSearchPath: DataContext.AddButton(new RenameDesignInput(this), -1000); break; + case RenameField.RenameData: DataContext.AddButton(new MoveDesignInput(this), -1000); break; + case RenameField.BothSearchPathPrio: + DataContext.AddButton(new RenameDesignInput(this), -1000); + DataContext.AddButton(new MoveDesignInput(this), -1001); + break; + case RenameField.BothDataPrio: + DataContext.AddButton(new RenameDesignInput(this), -1001); + DataContext.AddButton(new MoveDesignInput(this), -1000); + break; + } + } + + public void Dispose() + { + Config.OnRenameChanged -= OnRenameChanged; + } + + public override ReadOnlySpan Id + => "Designs"u8; + + protected override FileSystemCache CreateCache() + => new DesignFileSystemCache(this); +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs new file mode 100644 index 0000000..e8feaac --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs @@ -0,0 +1,150 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignFilter : TokenizedFilter, + IFileSystemFilter, IUiService +{ + protected override void DrawTooltip() + { + if (!Im.Item.Hovered()) + return; + + using var tt = Im.Tooltip.Begin(); + var highlightColor = ColorId.EnabledAutoSet.Value().ToVector(); + Im.Text("Filter designs for those where their full paths or names contain the given strings, split by spaces."u8); + ImEx.TextMultiColored("Enter "u8).Then("m:[string]"u8, highlightColor) + .Then(" to filter for designs with a mod association containing the string."u8).End(); + ImEx.TextMultiColored("Enter "u8).Then("t:[string]"u8, highlightColor).Then(" to filter for designs set to specific tags."u8).End(); + ImEx.TextMultiColored("Enter "u8).Then("c:[string]"u8, highlightColor) + .Then(" to filter for designs set to specific colors."u8).End(); + ImEx.TextMultiColored("Enter "u8).Then("i:[string]"u8, highlightColor).Then(" to filter for designs containing specific items."u8) + .End(); + ImEx.TextMultiColored("Enter "u8).Then("n:[string]"u8, highlightColor).Then(" to filter only for design names, ignoring the paths."u8) + .End(); + ImEx.TextMultiColored("Enter "u8).Then("f:[string]"u8, highlightColor).Then( + " to filter for designs containing the text in name, path, description, tags, mod associations, colors or contained items."u8) + .End(); + Im.Line.New(); + ImEx.TextMultiColored("Use "u8).Then("None"u8, highlightColor).Then(" as a placeholder value that only matches empty lists or names."u8) + .End(); + Im.Text("Regularly, a design has to match all supplied criteria separately."u8); + ImEx.TextMultiColored("Put a "u8).Then("'-'"u8, highlightColor) + .Then(" in front of a search token to search only for designs not matching the criterion."u8).End(); + ImEx.TextMultiColored("Put a "u8).Then("'?'"u8, highlightColor) + .Then(" in front of a search token to search for designs matching at least one of the '?'-criteria."u8).End(); + ImEx.TextMultiColored("Wrap spaces in "u8).Then("\"[string with space]\""u8, highlightColor) + .Then(" to match this exact combination of words."u8).End(); + } + + protected override bool Matches(in DesignFilterToken token, in DesignFileSystemCache.DesignData cacheItem) + => token.Type switch + { + DesignFilterTokenType.Default => cacheItem.Node.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase) + || cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Mod => CheckMods(token.Needle, cacheItem), + DesignFilterTokenType.Tag => CheckTags(token.Needle, cacheItem), + DesignFilterTokenType.Color => cacheItem.Node.Value.Color.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Item => cacheItem.Node.Value.DesignData.ContainsName(token.Needle), + DesignFilterTokenType.Name => cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.FullContext => CheckFullContext(token.Needle, cacheItem), + _ => true, + }; + + protected override bool MatchesNone(DesignFilterTokenType type, bool negated, in DesignFileSystemCache.DesignData cacheItem) + => type switch + { + DesignFilterTokenType.Mod when negated => cacheItem.Node.Value.AssociatedMods.Count > 0, + DesignFilterTokenType.Mod => cacheItem.Node.Value.AssociatedMods.Count is 0, + DesignFilterTokenType.Tag when negated => cacheItem.Node.Value.Tags.Length > 0, + DesignFilterTokenType.Tag => cacheItem.Node.Value.Tags.Length is 0, + _ => true, + }; + + private static bool CheckMods(string needle, in DesignFileSystemCache.DesignData cacheItem) + => cacheItem.Node.Value.AssociatedMods.Any(kvp => kvp.Key.Name.Contains(needle, StringComparison.OrdinalIgnoreCase)); + + private static bool CheckTags(string needle, in DesignFileSystemCache.DesignData cacheItem) + => cacheItem.Node.Value.Tags.Any(t => t.Contains(needle, StringComparison.OrdinalIgnoreCase)); + + private static bool CheckFullContext(string needle, in DesignFileSystemCache.DesignData cacheItem) + { + if (needle.Length is 0) + return true; + + if (cacheItem.Node.FullPath.Contains(needle, StringComparison.OrdinalIgnoreCase)) + return true; + + var design = cacheItem.Node.Value; + if (design.Name.Text.Contains(needle, StringComparison.OrdinalIgnoreCase)) + return true; + + if (design.Description.Contains(needle, StringComparison.OrdinalIgnoreCase)) + return true; + + if (CheckTags(needle, cacheItem)) + return true; + + if (design.Color.Contains(needle, StringComparison.OrdinalIgnoreCase)) + return true; + + if (CheckMods(needle, cacheItem)) + return true; + + if (design.DesignData.ContainsName(needle)) + return true; + + if (design.Identifier.ToString().Contains(needle, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + public bool WouldBeVisible(in FileSystemFolderCache folder) + { + switch (State) + { + case FilterState.NoFilters: return true; + case FilterState.NoMatches: return false; + } + + foreach (var token in Forced) + { + if (token.Type switch + { + DesignFilterTokenType.Name => !folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Default => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.FullContext => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + _ => true, + }) + return false; + } + + foreach (var token in Negated) + { + if (token.Type switch + { + DesignFilterTokenType.Name => folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Default => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.FullContext => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + _ => false, + }) + return false; + } + + foreach (var token in General) + { + if (token.Type switch + { + DesignFilterTokenType.Name => folder.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Default => folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.FullContext => !folder.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + _ => false, + }) + return true; + } + + return General.Count is 0; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs new file mode 100644 index 0000000..2df214f --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilterToken.cs @@ -0,0 +1,49 @@ +using ImSharp; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public enum DesignFilterTokenType +{ + Default, + Mod, + Tag, + Color, + Item, + Name, + FullContext, +} + +public readonly struct DesignFilterToken() : IFilterToken +{ + public string Needle { get; init; } = string.Empty; + public DesignFilterTokenType Type { get; init; } + + public bool Contains(DesignFilterToken other) + { + if (Type != other.Type) + return false; + + return Needle.Contains(other.Needle); + } + + public static bool ConvertToken(char tokenCharacter, out DesignFilterTokenType type) + { + type = tokenCharacter switch + { + 'm' or 'M' => DesignFilterTokenType.Mod, + 'n' or 'N' => DesignFilterTokenType.Name, + 't' or 'T' => DesignFilterTokenType.Tag, + 'i' or 'I' => DesignFilterTokenType.Item, + 'c' or 'C' => DesignFilterTokenType.Color, + 'f' or 'F' => DesignFilterTokenType.FullContext, + _ => DesignFilterTokenType.Default, + }; + return type is not DesignFilterTokenType.Default; + } + + public static bool AllowsNone(DesignFilterTokenType type) + => type is DesignFilterTokenType.Tag or DesignFilterTokenType.Mod; + + public static void ProcessList(List list) + { } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs new file mode 100644 index 0000000..e1e4405 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DuplicateDesignButton.cs @@ -0,0 +1,38 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DuplicateDesignButton(DesignFileSystem fileSystem, DesignManager designManager) : BaseIconButton +{ + private readonly WeakReference _design = new(null!); + + public override AwesomeIcon Icon + => LunaStyle.DuplicateIcon; + + public override bool HasTooltip + => true; + + public override bool Enabled + => fileSystem.Selection.Selection is not null; + + public override void DrawTooltip() + => Im.Text(fileSystem.Selection.Selection is null ? "No design selected."u8 : "Clone the currently selected design to a duplicate."u8); + + public override void OnClick() + { + _design.SetTarget(fileSystem.Selection.Selection?.GetValue()!); + Im.Popup.Open("##CloneDesign"u8); + } + + protected override void PostDraw() + { + if (!InputPopup.OpenName("##CloneDesign"u8, out var newName)) + return; + + if (_design.TryGetTarget(out var design)) + designManager.CreateClone(design, newName, true); + _design.SetTarget(null!); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs new file mode 100644 index 0000000..cc5f03f --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/ImportDesignButton.cs @@ -0,0 +1,52 @@ +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class ImportDesignButton(DesignConverter converter, DesignManager manager) : BaseIconButton +{ + private string _clipboardText = string.Empty; + + public override AwesomeIcon Icon + => LunaStyle.ImportIcon; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Try to import a design from your clipboard."u8); + + public override void OnClick() + { + try + { + _clipboardText = Im.Clipboard.GetUtf16(); + Im.Popup.Open("##ImportDesign"u8); + } + catch (Exception) + { + Glamourer.Messager.NotificationMessage("Could not import data from clipboard.", NotificationType.Error, false); + } + } + + protected override void PostDraw() + { + if (!InputPopup.OpenName("##ImportDesign"u8, out var newName)) + return; + + if (_clipboardText.Length is 0) + return; + + var design = converter.FromBase64(_clipboardText, true, true, out _); + if (design is Design d) + manager.CreateClone(d, newName, true); + else if (design is not null) + manager.CreateClone(design, newName, true); + else + Glamourer.Messager.NotificationMessage("Could not create a design, clipboard did not contain valid design data.", + NotificationType.Error, false); + _clipboardText = string.Empty; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs new file mode 100644 index 0000000..ca86538 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/MoveDesignInput.cs @@ -0,0 +1,34 @@ +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class MoveDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton +{ + /// + public override ReadOnlySpan Label(in IFileSystemData _) + => "##Move"u8; + + /// Replaces the normal menu item handling for a text input, so the other fields are not used. + /// + public override bool DrawMenuItem(in IFileSystemData data) + { + var currentPath = data.FullPath; + using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding); + MenuSeparator.DrawSeparator(); + Im.Text("Move Design:"u8); + if (Im.Window.Appearing) + Im.Keyboard.SetFocusHere(); + var ret = Im.Input.Text(Label(data), ref currentPath, flags: InputTextFlags.EnterReturnsTrue); + Im.Tooltip.OnHover( + "Enter a full path here to move the design or change its search path. Creates all required parent directories, if possible."u8); + if (!ret) + return false; + + fileSystem.FileSystem.RenameAndMove(data, currentPath); + fileSystem.FileSystem.ExpandAllAncestors(data); + Im.Popup.CloseCurrent(); + + return ret; + } +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs new file mode 100644 index 0000000..258e077 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/NewDesignButton.cs @@ -0,0 +1,28 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class NewDesignButton(DesignManager designManager) : BaseIconButton +{ + public override AwesomeIcon Icon + => LunaStyle.AddObjectIcon; + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Create a new design with default configuration."u8); + + public override void OnClick() + => Im.Popup.Open("##NewDesign"u8); + + protected override void PostDraw() + { + if (!InputPopup.OpenName("##NewDesign"u8, out var newName)) + return; + + designManager.CreateEmpty(newName, true); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs new file mode 100644 index 0000000..2170c3a --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/RenameDesignInput.cs @@ -0,0 +1,34 @@ +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class RenameDesignInput(DesignFileSystemDrawer fileSystem) : BaseButton +{ + /// + public override ReadOnlySpan Label(in IFileSystemData _) + => "##Rename"u8; + + /// Replaces the normal menu item handling for a text input, so the other fields are not used. + /// + public override bool DrawMenuItem(in IFileSystemData data) + { + var design = (Design)data.Value; + var currentName = design.Name.Text; + using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding); + MenuSeparator.DrawSeparator(); + Im.Text("Rename Design:"u8); + if (Im.Window.Appearing) + Im.Keyboard.SetFocusHere(); + var ret = Im.Input.Text(Label(data), ref currentName, flags: InputTextFlags.EnterReturnsTrue); + Im.Tooltip.OnHover("Enter a new name here to rename the changed design."u8); + if (!ret) + return false; + + fileSystem.Manager.Rename(design, currentName); + Im.Popup.CloseCurrent(); + + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs b/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs new file mode 100644 index 0000000..0e67431 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/SetFromClipboardButton.cs @@ -0,0 +1,44 @@ +using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class SetFromClipboardButton(DesignFileSystem fileSystem, DesignConverter converter, DesignManager manager) + : BaseIconButton +{ + public override bool IsVisible + => fileSystem.Selection.Selection is not null; + + public override AwesomeIcon Icon + => LunaStyle.FromClipboardIcon; + + public override bool Enabled + => !((Design)fileSystem.Selection.Selection!.Value).WriteProtected(); + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text( + "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8); + + public override void OnClick() + { + try + { + var text = Im.Clipboard.GetUtf16(); + var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var design = converter.FromBase64(text, applyCustomize, applyEquip, out _) + ?? throw new Exception("The clipboard did not contain valid data."); + manager.ApplyDesign((Design)fileSystem.Selection.Selection!.Value, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {((Design)fileSystem.Selection.Selection!.Value).Name}.", + $"Could not apply clipboard to design {((Design)fileSystem.Selection.Selection!.Value).Identifier}", NotificationType.Error, + false); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs b/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs new file mode 100644 index 0000000..889893a --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/UndoButton.cs @@ -0,0 +1,27 @@ +using Glamourer.Designs; +using Glamourer.Designs.History; +using ImSharp; +using Luna; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class UndoButton(DesignFileSystem fileSystem, EditorHistory history) : BaseIconButton +{ + public override bool IsVisible + => fileSystem.Selection.Selection is not null; + + public override AwesomeIcon Icon + => LunaStyle.UndoIcon; + + public override bool Enabled + => !((Design)fileSystem.Selection.Selection!.Value).WriteProtected() && history.CanUndo((Design)fileSystem.Selection.Selection!.Value); + + public override bool HasTooltip + => true; + + public override void DrawTooltip() + => Im.Text("Undo the last change."u8); + + public override void OnClick() + => history.Undo((Design)fileSystem.Selection.Selection!.Value); +} diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs deleted file mode 100644 index 6025b0c..0000000 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using ImSharp; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui.Tabs; - -public static class HeaderDrawer -{ - public abstract class Button - { - protected abstract void OnClick(); - - protected virtual string Description - => string.Empty; - - protected virtual Rgba32 BorderColor - => ColorId.HeaderButtons.Value(); - - protected virtual Rgba32 TextColor - => ColorId.HeaderButtons.Value(); - - protected virtual FontAwesomeIcon Icon - => FontAwesomeIcon.None; - - protected virtual bool Disabled - => false; - - public virtual bool Visible - => true; - - public void Draw(float width) - { - if (!Visible) - return; - - using var color = ImGuiColor.Border.Push(BorderColor) - .Push(ImGuiColor.Text, TextColor, TextColor.IsVisible); - if (ImGuiUtil.DrawDisabledButton(Icon.ToIconString(), new Vector2(width, Im.Style.FrameHeight), string.Empty, Disabled, true)) - OnClick(); - color.Pop(); - ImGuiUtil.HoverTooltip(Description); - } - } - - public static void Draw(string text, uint textColor, uint frameColor, Button[] leftButtons, Button[] rightButtons) - { - var width = Im.Style.FrameHeightWithSpacing; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .Push(ImGuiStyleVar.FrameRounding, 0) - .Push(ImGuiStyleVar.FrameBorderSize, Im.Style.GlobalScale); - - var leftButtonSize = 0f; - foreach (var button in leftButtons.Where(b => b.Visible)) - { - button.Draw(width); - Im.Line.Same(); - leftButtonSize += width; - } - - var rightButtonSize = rightButtons.Count(b => b.Visible) * width; - var midSize = Im.ContentRegion.Available.X - rightButtonSize - Im.Style.GlobalScale; - - style.Pop(); - style.Push(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.5f + (rightButtonSize - leftButtonSize) / midSize, 0.5f)); - if (textColor != 0) - ImGuiUtil.DrawTextButton(text, new Vector2(midSize, Im.Style.FrameHeight), frameColor, textColor); - else - ImGuiUtil.DrawTextButton(text, new Vector2(midSize, Im.Style.FrameHeight), frameColor); - style.Pop(); - style.Push(ImGuiStyleVar.FrameBorderSize, Im.Style.GlobalScale); - - foreach (var button in rightButtons.Where(b => b.Visible)) - { - Im.Line.Same(); - button.Draw(width); - } - } -} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/IncognitoButton.cs b/Glamourer/Gui/Tabs/IncognitoButton.cs index a7f3eb9..46f3394 100644 --- a/Glamourer/Gui/Tabs/IncognitoButton.cs +++ b/Glamourer/Gui/Tabs/IncognitoButton.cs @@ -1,9 +1,10 @@ -using ImSharp; +using Glamourer.Config; +using ImSharp; using Luna; namespace Glamourer.Gui.Tabs; -public sealed class IncognitoButton(Configuration.Configuration config) : BaseIconButton, IUiService +public sealed class IncognitoButton(Configuration config) : BaseIconButton, IUiService { public override AwesomeIcon Icon => config.Ephemeral.IncognitoMode diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 4b0daa6..3c3376e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,5 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; @@ -13,7 +13,7 @@ using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.NpcTab; public sealed class NpcPanel( - Configuration.Configuration config, + Configuration config, NpcSelection selection, CustomizationDrawer customizeDrawer, EquipmentDrawer equipmentDrawer, diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 60c64a9..4b420c6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,4 +1,4 @@ -using Glamourer.Configuration; +using Glamourer.Config; using ImSharp; using Luna; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index ca27f95..9beb9ff 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -1,16 +1,12 @@ -using Dalamud.Interface; +using Glamourer.Config; using Glamourer.Services; using Glamourer.State; using ImSharp; using Luna; -using OtterGui.Filesystem; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Text.EndObjects; namespace Glamourer.Gui.Tabs.SettingsTab; -public class CodeDrawer(Configuration.Configuration config, CodeService codeService, FunModule funModule) : IUiService +public class CodeDrawer(Configuration config, CodeService codeService, FunModule funModule) : IUiService { private static ReadOnlySpan Tooltip => "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8 @@ -41,7 +37,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ private void DrawCodeInput() { var color = codeService.CheckCode(_currentCode).Item2 is not 0 ? ColorId.ActorAvailable : ColorId.ActorUnavailable; - using var border = ImRaii.PushFrameBorder(Im.Style.GlobalScale, color.Value().Color, _currentCode.Length > 0); + using var border = ImStyleBorder.Frame.Push(color.Value(), Im.Style.GlobalScale, _currentCode.Length > 0); Im.Item.SetNextWidth(500 * Im.Style.GlobalScale + Im.Style.ItemSpacing.X); if (Im.Input.Text("##Code"u8, ref _currentCode, "Enter Cheat Code..."u8, InputTextFlags.EnterReturnsTrue)) { @@ -50,24 +46,22 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ } Im.Line.Same(); - ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, ImGuiColor.TextDisabled.Get().Color); + ImEx.Icon.Draw(LunaStyle.WarningIcon, ImGuiColor.TextDisabled.Get()); DrawTooltip(); } private void DrawCopyButtons() { - var buttonSize = new Vector2(250 * Im.Style.GlobalScale, 0); - if (ImUtf8.Button("Who am I?!?"u8, buttonSize)) + var buttonSize = ImEx.ScaledVectorX(250); + if (Im.Button("Who am I?!?"u8, buttonSize)) funModule.WhoAmI(); - ImUtf8.HoverTooltip( - "Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); + Im.Tooltip.OnHover("Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); Im.Line.Same(); - if (ImUtf8.Button("Who is that!?!"u8, buttonSize)) + if (Im.Button("Who is that!?!"u8, buttonSize)) funModule.WhoIsThat(); - ImUtf8.HoverTooltip( - "Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); + Im.Tooltip.OnHover("Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); } private CodeService.CodeFlag DrawCodes() @@ -76,7 +70,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ CodeService.CodeFlag knownFlags = 0; for (var i = 0; i < config.Codes.Count; ++i) { - using var id = ImUtf8.PushId(i); + using var id = Im.Id.Push(i); var (code, state) = config.Codes[i]; var (action, flag) = codeService.CheckCode(code); if (flag is 0) @@ -84,8 +78,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ var data = CodeService.GetData(flag); - if (ImUtf8.IconButton(FontAwesomeIcon.Trash, - $"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}", + if (ImEx.Icon.Button(LunaStyle.DeleteIcon, $"Delete this cheat code.{(canDelete ? StringU8.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}", disabled: !canDelete)) { action!(false); @@ -95,7 +88,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ knownFlags |= flag; Im.Line.SameInner(); - if (ImUtf8.Checkbox("\0"u8, ref state)) + if (Im.Checkbox(StringU8.Empty, ref state)) { action!(state); codeService.SaveState(); @@ -103,14 +96,14 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ var hovered = Im.Item.Hovered(); Im.Line.Same(); - ImUtf8.Selectable(code); + Im.Selectable(code); hovered |= Im.Item.Hovered(); DrawSource(i, code); DrawTarget(i); if (hovered) { - using var tt = ImUtf8.Tooltip(); - ImUtf8.Text(data.Effect); + using var tt = Im.Tooltip.Begin(); + Im.Text(data.Effect); } } @@ -119,22 +112,22 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ private void DrawSource(int idx, string code) { - using var source = ImUtf8.DragDropSource(); + using var source = Im.DragDrop.Source(); if (!source) return; - if (!DragDropSource.SetPayload(DragDropLabel)) + if (!source.SetPayload(DragDropLabel)) _dragCodeIdx = idx; - ImUtf8.Text($"Dragging {code}..."); + Im.Text($"Dragging {code}..."); } private void DrawTarget(int idx) { - using var target = ImUtf8.DragDropTarget(); - if (!target.IsDropping(DragDropLabel) || _dragCodeIdx == -1) + using var target = Im.DragDrop.Target(); + if (!target.IsDropping(DragDropLabel) || _dragCodeIdx is -1) return; - if (Extensions.Move(config.Codes, _dragCodeIdx, idx)) + if (config.Codes.Move(_dragCodeIdx, idx)) codeService.SaveState(); _dragCodeIdx = -1; } @@ -144,7 +137,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ if (knownFlags.HasFlag(CodeService.AllHintCodes)) return; - if (ImUtf8.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8)) + if (Im.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8)) _showCodeHints = !_showCodeHints; if (!_showCodeHints) @@ -162,23 +155,23 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ Im.Dummy(Vector2.Zero); Im.Separator(); Im.Dummy(Vector2.Zero); - ImUtf8.Text(data.Effect); - using var indent = ImRaii.PushIndent(2); - using (ImUtf8.Group()) + Im.Text(data.Effect); + using var indent = Im.Indent(2); + using (Im.Group()) { - ImUtf8.Text("Capitalized letters: "u8); - ImUtf8.Text("Punctuation: "u8); + Im.Text("Capitalized letters: "u8); + Im.Text("Punctuation: "u8); } Im.Line.SameInner(); - using (ImUtf8.Group()) + using (Im.Group()) { using var mono = Im.Font.PushMono(); - ImUtf8.Text($"{data.CapitalCount}"); - ImUtf8.Text($"{data.Punctuation}"); + Im.Text($"{data.CapitalCount}"); + Im.Text($"{data.Punctuation}"); } - ImUtf8.TextWrapped(data.Hint); + Im.TextWrapped(data.Hint); } } @@ -189,7 +182,7 @@ public class CodeDrawer(Configuration.Configuration config, CodeService codeServ return; Im.Window.SetNextSize(new Vector2(400, 0)); - using var tt = ImUtf8.Tooltip(); - ImUtf8.TextWrapped(Tooltip); + using var tt = Im.Tooltip.Begin(); + Im.TextWrapped(Tooltip); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs index 99b91a7..a8ebfb2 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -1,35 +1,82 @@ -using Dalamud.Interface; +using Glamourer.Config; using Glamourer.Interop.Penumbra; using ImSharp; using Luna; -using OtterGui.Widgets; -using Logger = OtterGui.Log.Logger; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Tabs.SettingsTab; -public sealed class CollectionCombo(Configuration.Configuration config, PenumbraService penumbra, Logger log) - : FilterComboCache<(Guid Id, string IdShort, string Name)>( - () => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(), - MouseWheelType.Control, log), IUiService +public sealed class CollectionCombo(Configuration config, PenumbraService penumbra) + : FilterComboBase(new CollectionFilter()), IUiService { - protected override bool DrawSelectable(int globalIdx, bool selected) + private Guid _selected = Guid.Empty; + + public readonly struct CacheItem(Guid id, string name) + { + public readonly StringPair Name = new(name); + public readonly Guid Id = id; + public readonly StringU8 Incognito = id.ShortGuidU8(); + public readonly StringU8 ShortId = new($"({id.ShortGuidU8()})"); + } + + protected override float ItemHeight + => Im.Style.TextHeightWithSpacing; + + protected override IEnumerable GetItems() + => penumbra.GetCollections().Select(kvp => new CacheItem(kvp.Key, kvp.Value)); + + public bool Draw(Utf8StringHandler label, Utf8StringHandler preview, out string newName, + ref Guid id, float width) + { + _selected = id; + if (!base.Draw(label, preview, StringU8.Empty, width, out var ret)) + { + newName = string.Empty; + return false; + } + + newName = ret.Name.Utf16; + id = ret.Id; + return true; + } + + protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) { - var (_, idShort, name) = Items[globalIdx]; if (config.Ephemeral.IncognitoMode) using (Im.Font.PushMono()) { - return Im.Selectable(idShort); + return Im.Selectable(item.Incognito, selected); } - var ret = Im.Selectable(name, selected); + var ret = Im.Selectable(item.Name.Utf8, selected); Im.Line.Same(); + using (Im.Font.PushMono()) { using var color = ImGuiColor.Text.Push(ImGuiColor.TextDisabled.Get()); - ImEx.TextRightAligned($"({idShort})"); + ImEx.TextRightAligned(item.ShortId); } return ret; } + + protected override bool IsSelected(CacheItem item, int globalIndex) + => item.Id == _selected; + + private sealed class CollectionFilter : Utf8FilterBase + { + public override bool WouldBeVisible(in CacheItem item, int globalIndex) + => base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.Incognito); + + protected override ReadOnlySpan ToFilterString(in CacheItem item, int globalIndex) + => item.Name; + } + + protected override FilterComboBaseCache CreateCache() + => new Cache(this); + + private sealed class Cache(CollectionCombo parent) : FilterComboBaseCache(parent) + { + protected override void ComputeWidth() + => ComboWidth = AllItems.Max(i => i.Name.Utf8.CalculateSize().X + Im.Style.ItemSpacing.X * 2 + i.ShortId.CalculateSize().X); + } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index a98eb71..dcbae3c 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,4 +1,5 @@ using Dalamud.Interface; +using Glamourer.Config; using Glamourer.Interop.Penumbra; using Glamourer.Services; using ImSharp; @@ -10,7 +11,7 @@ namespace Glamourer.Gui.Tabs.SettingsTab; public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, - Configuration.Configuration config, + Configuration config, ActorObjectManager objects, ActorManager actors, PenumbraService penumbra, @@ -58,18 +59,15 @@ public class CollectionOverrideDrawer( DrawActorIdentifier(idx, actor); table.NextColumn(); - if (combo.Draw("##collection", name, "Select the overriding collection. Current GUID:", Im.ContentRegion.Available.X, - Im.Style.TextHeight)) - { - var (guid, _, newName) = combo.CurrentSelection; - collectionOverrides.ChangeOverride(idx, guid, newName); - } + if (combo.Draw("##collection"u8, name, out var newName, ref collection, Im.ContentRegion.Available.X)) + collectionOverrides.ChangeOverride(idx, collection, newName); if (Im.Item.Hovered()) { - using var tt = Im.Tooltip.Begin(); - using var font = Im.Font.PushMono(); - Im.Text($" {collection}"); + using var tt = Im.Tooltip.Begin(); + Im.Text("Select the overriding collection. Current GUID:"u8); + using var indent = Im.Indent(); + ImEx.MonoText($"{collection}"); } table.NextColumn(); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 97fa1e3..de01743 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -2,7 +2,7 @@ using Dalamud.Interface; using Dalamud.Plugin.Services; using Glamourer.Automation; -using Glamourer.Configuration; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Tabs.DesignTab; @@ -15,8 +15,8 @@ using Luna; namespace Glamourer.Gui.Tabs.SettingsTab; public sealed class SettingsTab( - Configuration.Configuration config, - DesignFileSystemSelector selector, + Configuration config, + DesignFileSystemDrawer drawer, ContextMenuService contextMenuService, IUiBuilder uiBuilder, GlamourerChangelog changelog, @@ -28,7 +28,8 @@ public sealed class SettingsTab( Glamourer glamourer, AutoDesignApplier autoDesignApplier, AutoRedrawChanged autoRedraw, - PcpService pcpService) + PcpService pcpService, + IgnoredMods ignoredMods) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -61,6 +62,7 @@ public sealed class SettingsTab( DrawInterfaceSettings(); DrawColorSettings(); overrides.Draw(); + DrawIgnoredMods(); codeDrawer.Draw(); } @@ -256,7 +258,7 @@ public sealed class SettingsTab( Im.Line.New(); Im.Text("Show the following panels in their respective tabs:"u8); Im.Dummy(Vector2.Zero); - Configuration.DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => + DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => { config.HideDesignPanel = v; config.Save(); @@ -280,8 +282,8 @@ public sealed class SettingsTab( Checkbox("Show Unobtained Item Warnings"u8, "Show information whether you have unlocked all items and customizations in your automated design or not."u8, config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); - Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, - config.ShowColorConfig, v => config.ShowColorConfig = v); + Checkbox("Show Color Display Configuration"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, + config.ShowColorConfig, v => config.ShowColorConfig = v); Checkbox("Show Palette+ Import Button"u8, "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); @@ -376,7 +378,7 @@ public sealed class SettingsTab( if (Im.Button("Import Palette+ to Designs"u8)) paletteImport.ImportDesigns(); Im.Tooltip.OnHover( - $"Import all existing Palettes from your Palette+ Config into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); + $"Import all existing Palettes from your Palette+ Configuration into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); } /// Draw the entire Color subsection. @@ -402,6 +404,7 @@ public sealed class SettingsTab( continue; config.Colors[color] = newColor.Color; + CacheManager.Instance.SetColorsDirty(); config.Save(); } } @@ -445,20 +448,20 @@ public sealed class SettingsTab( using (var combo = Im.Combo.Begin("##sortMode"u8, sortMode.Name)) { if (combo) - foreach (var val in Configuration.Configuration.Constants.ValidSortModes) + foreach (var (_, value) in ISortMode.Valid) { - if (Im.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) + if (Im.Selectable(value.Name, value.GetType() == sortMode.GetType()) && value.GetType() != sortMode.GetType()) { - config.SortMode = val; - selector.SetFilterDirty(); + config.SortMode = value; + drawer.SortMode = value; config.Save(); } - Im.Tooltip.OnHover(val.Description); + Im.Tooltip.OnHover(value.Description); } } - LunaStyle.DrawAlignedHelpMarkerLabel("Sort Mode"u8, "Choose the sort mode for the mod selector in the designs tab."u8); + LunaStyle.DrawAlignedHelpMarkerLabel("Sort Mode"u8, "Choose the sort mode for the design selector in the designs tab."u8); } private void DrawRenameSettings() @@ -472,7 +475,6 @@ public sealed class SettingsTab( if (Im.Selectable(value.ToNameU8(), config.ShowRename == value)) { config.ShowRename = value; - selector.SetRenameSearchPath(value); config.Save(); } @@ -503,4 +505,46 @@ public sealed class SettingsTab( LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"u8, "Select how to display the height of characters in real-world units, if at all."u8); } + + private string _newIgnoredMod = string.Empty; + + private void DrawIgnoredMods() + { + using var header = Im.Tree.HeaderId("Ignored Mods"u8); + Im.Tooltip.OnHover("Add mods that are ignored for the 'modded' column in the Unlocks tab."u8); + if (!header) + return; + + using var listBox = Im.ListBox.Begin("##box"u8, new Vector2(0.4f * Im.ContentRegion.Available.X, Im.Style.FrameHeightWithSpacing * 10)); + if (!listBox) + return; + + var delete = string.Empty; + using var alignment = ImStyleDouble.ButtonTextAlign.PushX(0); + foreach (var (idx, mod) in ignoredMods.Index()) + { + using var id = Im.Id.Push(idx); + if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this ignored mod."u8)) + delete = mod; + + Im.Line.SameInner(); + ImEx.TextFramed(mod, Im.ContentRegion.Available with { Y = Im.Style.FrameHeight}); + } + + if (delete.Length > 0) + ignoredMods.Remove(delete); + + var tt = _newIgnoredMod.Length is 0 ? "Please enter a new mod name or mod directory to ignore."u8 : + ignoredMods.Contains(_newIgnoredMod) ? "This mod is already ignored."u8 : + "Ignore all mods with this name or directory in the Unlocks tab."u8; + if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt[0] is not (byte)'I')) + { + ignoredMods.Add(_newIgnoredMod); + _newIgnoredMod = string.Empty; + } + + Im.Line.SameInner(); + Im.Item.SetNextWidthFull(); + Im.Input.Text("##newMod"u8, ref _newIgnoredMod, "Ignore this Mod..."u8); + } } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs index e59e8d0..fb305c6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockCacheItem.cs @@ -38,7 +38,8 @@ public readonly struct UnlockCacheItem(in EquipItem item, in EquipItem offhand, public readonly StringPair OffhandModelString = offhand.Valid ? new StringPair(offhand.ModelString) : StringPair.Empty; public readonly StringPair GauntletModelString = gauntlets.Valid ? new StringPair(gauntlets.ModelString) : StringPair.Empty; public readonly StringPair RequiredLevel = new($"{item.Level.Value}"); - public required (string, string)[] Mods { get; init; } + public required (string, string)[] Mods { get; init; } + public int RelevantMods { get; init; } public readonly JobFlag Jobs = jobs.Flags; public readonly StringU8 JobText = jobs.Name.IsEmpty ? new StringU8($"Unknown {jobs.Id.Id}") : jobs.Name; public required bool Favorite { get; init; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index f6c747c..6d16d8b 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -1,6 +1,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface; using Dalamud.Interface.Utility.Table; +using Glamourer.Config; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -23,9 +24,10 @@ public sealed class UnlockTable : TableBase private readonly FavoriteManager _favorites; private readonly PenumbraService _penumbra; private readonly ObjectUnlocked _unlockEvent; + private readonly IgnoredMods _ignoredMods; public UnlockTable(JobService jobs, ItemManager items, ItemUnlockManager unlocks, PenumbraChangedItemTooltip tooltip, - ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures) + ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures, IgnoredMods ignoredMods) : base(new StringU8("Unlock Table"u8), new FavoriteColumn(favorites), new ModdedColumn(), new NameColumn(textures, tooltip), new SlotColumn(), new TypeColumn(), new UnlockDateColumn(), new ItemIdColumn(), new ModelDataColumn(), new JobColumn(jobs), new RequiredLevelColumn(), new DyableColumn(), new CrestColumn(), new TradableColumn()) @@ -36,6 +38,7 @@ public sealed class UnlockTable : TableBase _unlockEvent = unlockEvent; _favorites = favorites; _penumbra = penumbra; + _ignoredMods = ignoredMods; Flags |= TableFlags.Hideable | TableFlags.Reorderable | TableFlags.Resizable; } @@ -72,6 +75,7 @@ public sealed class UnlockTable : TableBase UnlockTimestamp = unlocked, Mods = mods, Favorite = favorite, + RelevantMods = mods.Count(m => !_ignoredMods.Contains(m.ModName) && !_ignoredMods.Contains(m.ModDirectory)), }; } @@ -94,24 +98,29 @@ public sealed class UnlockTable : TableBase => Im.Style.FrameHeightWithSpacing; public override void DrawColumn(in UnlockCacheItem item, int globalIndex) - { - Im.Cursor.FrameAlign(); - UiHelpers.DrawFavoriteStar(_favorites, item.Item); - } + => UiHelpers.DrawFavoriteStar(_favorites, item.Item); protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) => item.Favorite; } - private sealed class ModdedColumn : YesNoColumn + private sealed class ModdedColumn : FlagColumn { - private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle; + [Flags] + public enum Modded + { + Relevant = 1, + Ignored = 2, + None = 4, + } + + private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle; + private static readonly AwesomeIcon Hollow = FontAwesomeIcon.DotCircle; public ModdedColumn() { - Flags |= TableColumnFlags.NoResize; - Label = new StringU8("M"); - FilterLabel = new StringU8("Modded"u8); + Flags |= TableColumnFlags.NoResize; + Label = new StringU8("M"); } public override float ComputeWidth(IEnumerable allItems) @@ -124,8 +133,11 @@ public sealed class UnlockTable : TableBase using (AwesomeIcon.Font.Push()) { - using var color = ImGuiColor.Text.Push(ColorId.ModdedItemMarker.Value()); - ImEx.TextCentered(Dot.Span); + var (color, text) = item.RelevantMods > 0 + ? (ColorId.ModdedItemMarker.Value(), Dot) + : (ColorId.ModdedItemMarker.Value().HalfTransparent(), Hollow); + using var c = ImGuiColor.Text.Push(color); + Im.Text(text.Span); } if (Im.Item.Hovered()) @@ -137,11 +149,29 @@ public sealed class UnlockTable : TableBase } } - protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) - => item.Mods.Length > 0; + protected override Modded GetValue(in UnlockCacheItem item, int globalIndex) + => item.RelevantMods > 0 ? Modded.Relevant : item.Mods.Length > 0 ? Modded.Ignored : Modded.None; + + protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex) + => StringU8.Empty; + + protected override IReadOnlyList<(Modded Value, StringU8 Name)> EnumData + => + [ + (Modded.Relevant, new StringU8("Any Relevant Mods"u8)), + (Modded.Ignored, new StringU8("Only Ignored Mods"u8)), + (Modded.None, new StringU8("Unmodded"u8)), + ]; + public override int Compare(in UnlockCacheItem lhs, int lhsGlobalIndex, in UnlockCacheItem rhs, int rhsGlobalIndex) - => lhs.Mods.Length.CompareTo(rhs.Mods.Length); + { + var relevant = lhs.RelevantMods.CompareTo(rhs.RelevantMods); + if (relevant is not 0) + return relevant; + + return lhs.Mods.Length.CompareTo(rhs.Mods.Length); + } } private sealed class NameColumn : TextColumn @@ -241,9 +271,9 @@ public sealed class UnlockTable : TableBase { public UnlockDateColumn() { - Flags &= ~TableColumnFlags.NoResize; - Label = new StringU8("Unlocked"u8); - FilterLabel = Label; + Flags &= ~TableColumnFlags.NoResize; + Label = new StringU8("Unlocked"u8); + FilterLabel = Label; } public override float ComputeWidth(IEnumerable allItems) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 3be59f7..3574be3 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -5,11 +5,11 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public sealed class UnlocksTab : Window, ITab { - private readonly Configuration.EphemeralConfig _config; + private readonly Config.EphemeralConfig _config; private readonly UnlockOverview _overview; private readonly UnlockTable _table; - public UnlocksTab(Configuration.EphemeralConfig config, UnlockOverview overview, UnlockTable table) + public UnlocksTab(Config.EphemeralConfig config, UnlockOverview overview, UnlockTable table) : base("Unlocked Equipment") { _config = config; diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 32260ec..4805caf 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -1,6 +1,7 @@ using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; @@ -23,7 +24,7 @@ public class ContextMenuService : IDisposable private readonly MenuItem _inventoryItem; - public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration.Configuration config, + public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration config, IContextMenu context) { _contextMenu = context; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index d8c6ce9..04f4d2e 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -24,7 +25,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private readonly ThreadLocal> _deleteList = new(() => []); public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra, - Configuration.Configuration config) + Configuration config) { _stateManager = stateManager; _actors = actors; diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index d12b2be..153ed0f 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs.Links; +using Glamourer.Config; +using Glamourer.Designs.Links; using Glamourer.Services; using Glamourer.State; using Luna; @@ -7,14 +8,19 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration.Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier( + PenumbraService penumbra, + PenumbraAutoRedrawSkip autoRedrawSkip, + Configuration config, + ActorObjectManager objects, + CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; public void HandleStateApplication(ActorState state, MergedDesign design, StateSource source, bool skipAutoRedraw, bool respectManual) { - if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) + if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings) return; if (!objects.TryGetValue(state.Identifier, out var data)) @@ -90,6 +96,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip if (!respectManual && source.IsFixed()) penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual); } + return index; } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 406e830..ec81508 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; +using Glamourer.Config; using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.State; @@ -11,17 +12,17 @@ namespace Glamourer.Interop.Penumbra; public class PenumbraAutoRedraw : IDisposable, IRequiredService { - private const int WaitFrames = 5; - private readonly Configuration.Configuration _config; - private readonly PenumbraService _penumbra; - private readonly StateManager _state; - private readonly ActorObjectManager _objects; - private readonly IFramework _framework; - private readonly StateChanged _stateChanged; - private readonly PenumbraAutoRedrawSkip _skip; + private const int WaitFrames = 5; + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; + private readonly IFramework _framework; + private readonly StateChanged _stateChanged; + private readonly PenumbraAutoRedrawSkip _skip; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration.Configuration config, StateManager state, ActorObjectManager objects, + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ActorObjectManager objects, IFramework framework, StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 6759375..0438cbc 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; using Dalamud.Plugin.Ipc.Exceptions; +using Glamourer.Config; using Glamourer.Events; using Glamourer.State; using Luna; @@ -45,7 +46,7 @@ public class PenumbraService : IDisposable private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; - private readonly Configuration.Configuration _config; + private readonly Configuration _config; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; @@ -96,7 +97,7 @@ public class PenumbraService : IDisposable public int CurrentMinor { get; private set; } public DateTime AttachTime { get; private set; } - public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration.Configuration config) + public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config) { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index 3beac71..1103b59 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,27 +1,9 @@ using Luna; -using Backup = OtterGui.Classes.Backup; -using Logger = OtterGui.Log.Logger; namespace Glamourer.Services; -public class BackupService : IAsyncService +public class BackupService(Logger log, FilenameService provider) : BaseBackupService(log, provider) { - private readonly Logger _logger; - private readonly DirectoryInfo _configDirectory; - private readonly IReadOnlyList _fileNames; - - public BackupService(Logger logger, FilenameService fileNames) - { - _logger = logger; - _fileNames = GlamourerFiles(fileNames); - _configDirectory = new DirectoryInfo(fileNames.ConfigurationDirectory); - Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigurationDirectory), _fileNames)); - } - - /// Create a permanent backup with a given name for migrations. - public void CreateMigrationBackup(string name) - => Backup.CreatePermanentBackup(_logger, _configDirectory, _fileNames, name); - /// Collect all relevant files for glamourer configuration. private static IReadOnlyList GlamourerFiles(FilenameService fileNames) { @@ -29,7 +11,7 @@ public class BackupService : IAsyncService { new(fileNames.ConfigurationFile), new(fileNames.UiConfiguration), - new(fileNames.DesignFileSystem), + new(fileNames.MigrationDesignFileSystem), new(fileNames.MigrationDesignFile), new(fileNames.AutomationFile), new(fileNames.UnlockFileCustomize), @@ -42,9 +24,4 @@ public class BackupService : IAsyncService return list; } - - public Task Awaiter { get; } - - public bool Finished - => Awaiter.IsCompletedSuccessfully; } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index bd1f9c3..750c335 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -1,13 +1,13 @@ +using Glamourer.Config; using ImSharp; -using Luna; using Penumbra.GameData.Enums; namespace Glamourer.Services; public class CodeService { - private readonly Configuration.Configuration _config; - private readonly SHA256 _hasher = SHA256.Create(); + private readonly Configuration _config; + private readonly SHA256 _hasher = SHA256.Create(); [Flags] public enum CodeFlag : ulong @@ -25,16 +25,17 @@ public class CodeService OopsAuRa = 0x000400, OopsHrothgar = 0x000800, OopsViera = 0x001000, + //Artisan = 0x002000, - SixtyThree = 0x004000, - Shirts = 0x008000, - World = 0x010000, - Elephants = 0x020000, - Crown = 0x040000, - Dolphins = 0x080000, - Face = 0x100000, - Manderville = 0x200000, - Smiles = 0x400000, + SixtyThree = 0x004000, + Shirts = 0x008000, + World = 0x010000, + Elephants = 0x020000, + Crown = 0x040000, + Dolphins = 0x080000, + Face = 0x100000, + Manderville = 0x200000, + Smiles = 0x400000, } public static readonly CodeFlag AllHintCodes = @@ -87,7 +88,7 @@ public class CodeService _ => Race.Unknown, }; - public CodeService(Configuration.Configuration config) + public CodeService(Configuration config) { _config = config; Load(); @@ -253,4 +254,3 @@ public class CodeService _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } - diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 8a7c086..019424c 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -2,6 +2,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.GameData; @@ -24,27 +25,27 @@ public class CommandService : IDisposable, IApiService private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ActorObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly Configuration.Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; - private readonly CustomizeService _customizeService; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignResolver _resolver; - private readonly PenumbraService _penumbra; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly CustomizeService _customizeService; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, - DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration.Configuration config, ModSettingApplier modApplier, - ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, + ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemDrawer designDrawer, QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) { _commands = commands; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index dc6291f..2fefe15 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -1,20 +1,20 @@ using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Gui; using ImSharp; -using Luna; using Newtonsoft.Json.Linq; namespace Glamourer.Services; public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) { - private Configuration.Configuration _config = null!; - private JObject _data = null!; + private Configuration _config = null!; + private JObject _data = null!; - public void Migrate(Configuration.Configuration config) + public void Migrate(Configuration config) { _config = config; - if (config.Version >= Configuration.Configuration.Constants.CurrentVersion || !File.Exists(saveService.FileNames.ConfigurationFile)) + if (config.Version >= Configuration.CurrentVersion || !File.Exists(saveService.FileNames.ConfigurationFile)) { AddColors(config, false); return; @@ -27,9 +27,22 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator MigrateV5To6(); MigrateV6To7(); MigrateV7To8(); + MigrateV8To9(); AddColors(config, true); } + private void MigrateV8To9() + { + if (_config.Version > 8) + return; + + backupService.CreateMigrationBackup("pre_filesystem_update", saveService.FileNames.MigrationDesignFileSystem); + _config.Version = 9; + _config.Ephemeral.Version = 9; + _config.Save(); + _config.Ephemeral.Save(); + } + private void MigrateV7To8() { if (_config.Version > 7) @@ -60,7 +73,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Version = 6; } - // Ephemeral Config. + // Ephemeral Configuration. private void MigrateV4To5() { if (_config.Version > 4) @@ -71,7 +84,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Ephemeral.ShowDesignQuickBar = _data["ShowDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.ShowDesignQuickBar; _config.Ephemeral.LockDesignQuickBar = _data["LockDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.LockDesignQuickBar; _config.Ephemeral.LockMainWindow = _data["LockMainWindow"]?.ToObject() ?? _config.Ephemeral.LockMainWindow; - _config.Ephemeral.SelectedMainTab = _data["SelectedTab"]?.ToObject() ?? _config.Ephemeral.SelectedMainTab; + _config.Ephemeral.SelectedMainTab = _data["SelectedTab"]?.ToObject() ?? _config.Ephemeral.SelectedMainTab; _config.Ephemeral.LastSeenVersion = _data["LastSeenVersion"]?.ToObject() ?? _config.Ephemeral.LastSeenVersion; _config.Version = 5; _config.Ephemeral.Version = 5; @@ -83,7 +96,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator if (_config.Version > 1) return; - backupService.CreateMigrationBackup("pre_v1_to_v2_migration"); + backupService.CreateMigrationBackup("pre_v1_to_v2_migration", saveService.FileNames.MigrationDesignFile); fixedDesignMigrator.Migrate(_data["FixedDesigns"]); _config.Version = 2; var customizationColor = _data["CustomizationColor"]?.ToObject() ?? ColorId.CustomizationDesign.Data().DefaultColor; @@ -103,7 +116,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator _config.Codes = _config.Codes.DistinctBy(c => c.Code).ToList(); } - private static void AddColors(Configuration.Configuration config, bool forceSave) + private static void AddColors(Configuration config, bool forceSave) { var save = false; foreach (var color in ColorId.Values) diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs index ef1e3b9..9849ad9 100644 --- a/Glamourer/Services/DesignResolver.cs +++ b/Glamourer/Services/DesignResolver.cs @@ -3,14 +3,13 @@ using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Gui; -using Glamourer.Gui.Tabs.DesignTab; using Luna; using ImSharp; namespace Glamourer.Services; public class DesignResolver( - DesignFileSystemSelector designSelector, + DesignFileSystem fileSystem, QuickDesignCombo quickDesignCombo, DesignConverter converter, DesignManager manager, @@ -66,8 +65,8 @@ public class DesignResolver( [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GetSelectedDesign(ref DesignBase? design, ref SeString? error) { - design = designSelector.Selected; - if (design != null) + design = (Design?)fileSystem.Selection.Selection?.Value; + if (design is not null) return true; error = "You do not have selected any design in the Designs Tab."; @@ -147,7 +146,7 @@ public class DesignResolver( // Search for design by name and partial identifier. design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(lower)); // Search for design by path, if nothing was found. - if (design == null && designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf) + if (design is null && designFileSystem.Find(lower, out var child) && child is IFileSystemData leaf) design = leaf.Value; } diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 60f1773..831c6ad 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -6,10 +6,11 @@ namespace Glamourer.Services; public class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) { - public readonly string DesignFileSystem = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json"); + public readonly string MigrationDesignFileSystem = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json"); public readonly string MigrationDesignFile = Path.Combine(pi.ConfigDirectory.FullName, "Designs.json"); public readonly string DesignDirectory = Path.Combine(pi.ConfigDirectory.FullName, "designs"); public readonly string AutomationFile = Path.Combine(pi.ConfigDirectory.FullName, "automation.json"); + public readonly string IgnoredModsFile = Path.Combine(pi.ConfigDirectory.FullName, "ignored_mods.json"); public readonly string UnlockFileCustomize = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_customize.json"); public readonly string UnlockFileItems = Path.Combine(pi.ConfigDirectory.FullName, "unlocks_items.json"); public readonly string FavoriteFile = Path.Combine(pi.ConfigDirectory.FullName, "favorites.json"); @@ -39,7 +40,22 @@ public class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider( public string DesignFile(Design design) => DesignFile(design.Identifier.ToString()); - // TODO public override List GetBackupFiles() - => []; + { + var list = new List(16) + { + new(ConfigurationFile), + new(AutomationFile), + new(IgnoredModsFile), + new(UnlockFileCustomize), + new(UnlockFileItems), + new(FavoriteFile), + new(DesignColorFile), + new(FileSystemEmptyFolders), + new(FileSystemLockedNodes), + }; + // Do not back up expanded folders, selected nodes, ui configuration or ephemeral config. + list.AddRange(Designs()); + return list; + } } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index d26a7f3..4f0a83d 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Config; using Lumina.Excel; using Lumina.Excel.Sheets; using Penumbra.GameData.Data; @@ -15,7 +16,7 @@ public class ItemManager public const string SmallClothesNpc = "Smallclothes (NPC)"; public const ushort SmallClothesNpcModel = 9903; - private readonly Configuration.Configuration _config; + private readonly Configuration _config; public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; @@ -26,7 +27,7 @@ public class ItemManager public readonly EquipItem DefaultSword; - public ItemManager(Configuration.Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, + public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems) { _config = config; diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs index 639026a..cc8a54d 100644 --- a/Glamourer/Services/PcpService.cs +++ b/Glamourer/Services/PcpService.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Config; +using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; using Luna; @@ -10,14 +11,14 @@ namespace Glamourer.Services; public class PcpService : IRequiredService { - private readonly Configuration.Configuration _config; - private readonly PenumbraService _penumbra; - private readonly ActorObjectManager _objects; - private readonly StateManager _state; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly ActorObjectManager _objects; + private readonly StateManager _state; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; - public PcpService(Configuration.Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, + public PcpService(Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, DesignConverter designConverter, DesignManager designManager) { _config = config; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index e65f7ea..6b36d23 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -2,6 +2,7 @@ using Glamourer.Api; using Glamourer.Api.Api; using Glamourer.Automation; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui; @@ -73,8 +74,8 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); @@ -158,7 +159,7 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 6199d33..26b6610 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui; @@ -23,19 +24,19 @@ public unsafe class FunModule : IDisposable AprilFirst, } - private readonly WorldSets _worldSets = new(); - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly Configuration.Configuration _config; - private readonly CodeService _codes; - private readonly Random _rng; - private readonly GenericPopupWindow _popupWindow; - private readonly StateManager _stateManager; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; - private readonly ActorObjectManager _objects; - private readonly NpcCustomizeSet _npcs; - private readonly StainId[] _stains; + private readonly WorldSets _worldSets = new(); + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly Configuration _config; + private readonly CodeService _codes; + private readonly Random _rng; + private readonly GenericPopupWindow _popupWindow; + private readonly StateManager _stateManager; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + private readonly ActorObjectManager _objects; + private readonly NpcCustomizeSet _npcs; + private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; private FunEquipSet? _festivalSet; @@ -66,7 +67,7 @@ public unsafe class FunModule : IDisposable internal void ResetFestival() => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); - public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration.Configuration config, + public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ActorObjectManager objects, DesignConverter designConverter, DesignManager designManager, NpcCustomizeSet npcs) { diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 305575f..1bfb66a 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,4 +1,5 @@ using Glamourer.Api.Enums; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Designs.Links; @@ -6,7 +7,6 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -20,18 +20,18 @@ public class StateEditor( StateChanged stateChanged, StateFinalized stateFinalized, JobChangeState jobChange, - Configuration.Configuration config, + Configuration config, ItemManager items, DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) : IDesignEditor { - protected readonly InternalStateEditor Editor = editor; - protected readonly StateApplier Applier = applier; - protected readonly StateChanged StateChanged = stateChanged; - protected readonly StateFinalized StateFinalized = stateFinalized; - protected readonly Configuration.Configuration Config = config; - protected readonly ItemManager Items = items; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateFinalized StateFinalized = stateFinalized; + protected readonly Configuration Config = config; + protected readonly ItemManager Items = items; /// Turn an actor to. public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 85878ed..6856d4e 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -15,6 +15,7 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using Glamourer.Api.Enums; +using Glamourer.Config; namespace Glamourer.State; @@ -25,31 +26,31 @@ namespace Glamourer.State; /// public class StateListener : IDisposable { - private readonly Configuration.Configuration _config; - private readonly ActorManager _actors; - private readonly ActorObjectManager _objects; - private readonly StateManager _manager; - private readonly StateApplier _applier; - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly PenumbraService _penumbra; - private readonly EquipSlotUpdating _equipSlotUpdating; - private readonly BonusSlotUpdating _bonusSlotUpdating; - private readonly GearsetDataLoaded _gearsetDataLoaded; - private readonly WeaponLoading _weaponLoading; - private readonly HeadGearVisibilityChanged _headGearVisibility; - private readonly VisorStateChanged _visorState; - private readonly VieraEarStateChanged _vieraEarState; - private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateFinalized _stateFinalized; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly FunModule _funModule; - private readonly HumanModelList _humans; - private readonly MovedEquipment _movedEquipment; - private readonly GPoseService _gPose; - private readonly ChangeCustomizeService _changeCustomizeService; - private readonly CrestService _crestService; - private readonly ICondition _condition; + private readonly Configuration _config; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _manager; + private readonly StateApplier _applier; + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly PenumbraService _penumbra; + private readonly EquipSlotUpdating _equipSlotUpdating; + private readonly BonusSlotUpdating _bonusSlotUpdating; + private readonly GearsetDataLoaded _gearsetDataLoaded; + private readonly WeaponLoading _weaponLoading; + private readonly HeadGearVisibilityChanged _headGearVisibility; + private readonly VisorStateChanged _visorState; + private readonly VieraEarStateChanged _vieraEarState; + private readonly WeaponVisibilityChanged _weaponVisibility; + private readonly StateFinalized _stateFinalized; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly FunModule _funModule; + private readonly HumanModelList _humans; + private readonly MovedEquipment _movedEquipment; + private readonly GPoseService _gPose; + private readonly ChangeCustomizeService _changeCustomizeService; + private readonly CrestService _crestService; + private readonly ICondition _condition; private readonly Dictionary _fistOffhands = []; @@ -58,7 +59,7 @@ public class StateListener : IDisposable private ActorState? _creatingState; private ActorState? _customizeState; - public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration.Configuration config, + public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index c0fffab..b767f48 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; @@ -27,7 +28,7 @@ public sealed class StateManager( InternalStateEditor editor, HumanModelList humans, IClientState clientState, - Configuration.Configuration config, + Configuration config, JobChangeState jobChange, DesignMerger merger, ModSettingApplier modApplier, diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index cf267ef..3268050 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -2,12 +2,6 @@ "version": 1, "dependencies": { "net10.0-windows7.0": { - "DalamudPackager": { - "type": "Direct", - "requested": "[14.0.1, )", - "resolved": "14.0.1", - "contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA==" - }, "DotNet.ReproducibleBuilds": { "type": "Direct", "requested": "[1.2.39, )", diff --git a/Luna b/Luna index bff400e..4a46e85 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit bff400eb44aeded41fb6b1b3d35948881b578737 +Subproject commit 4a46e8551dd4438465c013c4a01d2936e5b4d4da diff --git a/Penumbra.Api b/Penumbra.Api index a79ff8d..9cfcaf3 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit a79ff8d87c5b1ac12192f18563fb4247173ff4f0 +Subproject commit 9cfcaf39fef363e281f066b55811deef72908f2f diff --git a/Penumbra.GameData b/Penumbra.GameData index 223fb1b..22fc61d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 223fb1b04fee05c439b7679e7f62bc890e5d0885 +Subproject commit 22fc61d0298e13e18c97e9352b84a90353176c39 diff --git a/Penumbra.String b/Penumbra.String index 9bd016f..c13b802 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 9bd016fbef5fb2de467dd42165267fdd93cd9592 +Subproject commit c13b8020b2bb2e269c28a85f7d87fca86842fa88 From 8012c92704d9dafd22a586bffa8594416f2bd674 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 Feb 2026 23:12:37 +0100 Subject: [PATCH 7/8] Update events, remove LowerString, remove remaining OtterGui. Make transactions classes to avoid boxing. --- OtterGui | 1 - 1 file changed, 1 deletion(-) delete mode 160000 OtterGui diff --git a/OtterGui b/OtterGui deleted file mode 160000 index ff1e654..0000000 --- a/OtterGui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ff1e6543845e3b8c53a5f8b240bc38faffb1b3bf From f1d377c9dcadc120066beddcf42256847838e17f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 Feb 2026 23:20:58 +0100 Subject: [PATCH 8/8] Actually push what I said in the message before. --- Glamourer.sln | 12 +- Glamourer/Api/ApiHelpers.cs | 21 +- Glamourer/Api/DesignsApi.cs | 4 +- Glamourer/Api/StateApi.cs | 44 +++-- Glamourer/Automation/AutoDesignApplier.cs | 55 +++--- Glamourer/Automation/AutoDesignManager.cs | 62 +++--- Glamourer/Automation/FixedDesignMigrator.cs | 2 +- Glamourer/Config/Configuration.cs | 2 +- Glamourer/Config/EphemeralConfig.cs | 13 +- Glamourer/Designs/Design.cs | 15 +- Glamourer/Designs/DesignBase64Migration.cs | 6 +- Glamourer/Designs/DesignColors.cs | 110 +---------- Glamourer/Designs/DesignConverter.cs | 51 ++--- Glamourer/Designs/DesignEditor.cs | 30 +-- Glamourer/Designs/DesignFileSystem.cs | 31 +-- Glamourer/Designs/DesignManager.cs | 66 +++---- Glamourer/Designs/History/EditorHistory.cs | 16 +- Glamourer/Designs/History/Transaction.cs | 18 +- Glamourer/Designs/Links/DesignLinkManager.cs | 20 +- Glamourer/Designs/Links/LinkContainer.cs | 2 +- .../Designs/Special/RandomDesignGenerator.cs | 6 +- Glamourer/Designs/Special/RandomPredicate.cs | 64 +++--- Glamourer/Events/AutoRedrawChanged.cs | 8 +- Glamourer/Events/AutomationChanged.cs | 136 ++++++++++--- Glamourer/Events/BonusSlotUpdating.cs | 31 +-- Glamourer/Events/DesignChanged.cs | 21 +- Glamourer/Events/EquipSlotUpdating.cs | 31 +-- Glamourer/Events/EquippedGearset.cs | 26 +-- Glamourer/Events/GPoseService.cs | 8 +- Glamourer/Events/GearsetDataLoaded.cs | 18 +- Glamourer/Events/HeadGearVisibilityChanged.cs | 23 ++- Glamourer/Events/MovedEquipment.cs | 8 +- Glamourer/Events/ObjectUnlocked.cs | 24 +-- Glamourer/Events/PenumbraReloaded.cs | 12 +- Glamourer/Events/StateChanged.cs | 32 +-- Glamourer/Events/StateFinalized.cs | 20 +- Glamourer/Events/TabSelected.cs | 24 ++- Glamourer/Events/VieraEarStateChanged.cs | 24 +-- Glamourer/Events/VisorStateChanged.cs | 29 +-- Glamourer/Events/WeaponLoading.cs | 27 +-- Glamourer/Events/WeaponVisibilityChanged.cs | 22 ++- Glamourer/GameData/CustomizeSet.cs | 7 +- Glamourer/Glamourer.cs | 2 +- Glamourer/Glamourer.csproj | 7 +- .../Gui/Customization/CustomizationDrawer.cs | 4 +- Glamourer/Gui/DesignCombo.cs | 36 ++-- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- Glamourer/Gui/GenericPopupWindow.cs | 3 +- Glamourer/Gui/GlamourerChangelog.cs | 2 +- Glamourer/Gui/GlamourerWindowSystem.cs | 5 +- Glamourer/Gui/MainTabBar.cs | 14 +- .../Tabs/AutomationTab/AutomationSelection.cs | 12 +- .../Gui/Tabs/AutomationTab/AutomationTab.cs | 2 +- .../Tabs/AutomationTab/IdentifierDrawer.cs | 5 +- .../AutomationTab/RandomRestrictionDrawer.cs | 37 ++-- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 4 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 17 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 4 +- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 4 +- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 5 +- Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs | 15 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 4 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 14 +- .../Selector/DesignFileSystemCache.cs | 11 +- .../Tabs/DesignTab/Selector/DesignFilter.cs | 6 +- .../DesignTab/Selector/RenameDesignInput.cs | 2 +- .../Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 3 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 1 + .../Gui/Tabs/SettingsTab/DesignColorUi.cs | 113 +++++++++++ .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 4 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 9 +- Glamourer/Interop/ChangeCustomizeService.cs | 31 +-- Glamourer/Interop/ContextMenuService.cs | 3 +- Glamourer/Interop/CrestService.cs | 34 ++-- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/InventoryService.cs | 34 ++-- Glamourer/Interop/JobService.cs | 3 +- Glamourer/Interop/Material/MaterialManager.cs | 18 +- Glamourer/Interop/Material/PrepareColorSet.cs | 25 ++- Glamourer/Interop/MetaService.cs | 15 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 11 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 2 +- Glamourer/Interop/ScalingService.cs | 3 +- Glamourer/Interop/UpdateSlotService.cs | 13 +- Glamourer/Interop/VieraEarService.cs | 7 +- Glamourer/Interop/VisorService.cs | 7 +- Glamourer/Interop/WeaponService.cs | 11 +- Glamourer/Services/BackupService.cs | 2 +- Glamourer/Services/CodeService.cs | 3 +- .../Services/CollectionOverrideService.cs | 11 +- Glamourer/Services/CommandService.cs | 1 - Glamourer/Services/ConfigMigrationService.cs | 3 +- Glamourer/Services/DesignResolver.cs | 13 +- Glamourer/Services/FilenameService.cs | 2 +- Glamourer/Services/ItemManager.cs | 3 +- Glamourer/Services/SaveService.cs | 2 +- Glamourer/Services/ServiceManager.cs | 164 +--------------- Glamourer/Services/TextureService.cs | 33 +++- Glamourer/State/FunModule.cs | 2 +- Glamourer/State/InternalStateEditor.cs | 5 +- Glamourer/State/StateApplier.cs | 67 +++---- Glamourer/State/StateEditor.cs | 52 ++--- Glamourer/State/StateListener.cs | 185 +++++++++--------- Glamourer/State/StateManager.cs | 59 +++--- Glamourer/Unlocks/CustomizeUnlockManager.cs | 13 +- Glamourer/Unlocks/FavoriteManager.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 36 ++-- 109 files changed, 1201 insertions(+), 1208 deletions(-) create mode 100644 Glamourer/Gui/Tabs/SettingsTab/DesignColorUi.cs diff --git a/Glamourer.sln b/Glamourer.sln index 2eb4741..a791f23 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11415.281 d18.3 +VisualStudioVersion = 18.3.11415.281 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}" ProjectSection(SolutionItems) = preProject @@ -20,8 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumb EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.Api", "Glamourer.Api\Glamourer.Api.csproj", "{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luna", "Luna\Luna\Luna.csproj", "{DEA936D7-1386-55A1-7451-E0C240F56E9D}" @@ -68,14 +66,6 @@ Global {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64 {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|x64.ActiveCfg = Release|x64 {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|x64.Build.0 = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|x64.ActiveCfg = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|x64.Build.0 = Debug|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|x64.ActiveCfg = Release|x64 - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|x64.Build.0 = Release|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|x64 {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index f8ac724..5adf76e 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -2,7 +2,6 @@ using Glamourer.Designs; using Glamourer.State; using Luna; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -73,26 +72,26 @@ public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, A [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal static void Lock(ActorState state, uint key, ApplyFlag flags) { - if ((flags & ApplyFlag.Lock) != 0 && key != 0) + if ((flags & ApplyFlag.Lock) is not 0 && key is not 0) state.Lock(key); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal IEnumerable FindStates(string objectName) { - if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString)) + if (objectName.Length is 0 || !ByteString.FromString(objectName, out var byteString)) return []; return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString) - .Concat(ArrayExtensions.SelectWhere(objects - .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString), kvp => - { - if (stateManager.ContainsKey(kvp.Key)) - return (false, null); + .Concat(objects + .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString).SelectWhere(kvp => + { + if (stateManager.ContainsKey(kvp.Key)) + return (false, null); - var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); - return (ret, state); - })); + var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); + return (ret, state); + })); } diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index d2da5b1..c3ec325 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -16,14 +16,14 @@ public class DesignsApi( : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() - => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name); public Dictionary GetDesignListExtended() => designs.Designs.ToDictionary(d => d.Identifier, d => (d.DisplayName, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign)); public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) => designs.Designs.ByIdentifier(designId) is { } d - ? (d.Name.Text, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign) + ? (d.Name, d.Path.CurrentPath, color.GetColor(d).Color, d.QuickDesign) : (string.Empty, string.Empty, 0, false); public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index f400aa8..1986925 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -2,7 +2,6 @@ using Glamourer.Api.Enums; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.State; using Luna; @@ -143,10 +142,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); var states = _helpers.FindExistingStates(playerName); - var any = false; + var any = false; var anyReapplied = false; foreach (var state in states) { @@ -154,7 +153,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) continue; - anyReapplied = true; + anyReapplied = true; anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success; } @@ -227,13 +226,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock) { var args = ApiHelpers.Args("Index", objectIndex, "Key", key); - isLocked = false; + isLocked = false; canUnlock = true; if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); if (state is null) - return ApiHelpers.Return(GlamourerApiEc.Success, args); - isLocked = state.IsLocked; + return ApiHelpers.Return(GlamourerApiEc.Success, args); + + isLocked = state.IsLocked; canUnlock = state.CanUnlock(key); return ApiHelpers.Return(GlamourerApiEc.Success, args); } @@ -423,29 +423,31 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable }; } - private void OnAutoRedrawChange(bool autoReload) + private void OnAutoRedrawChange(in bool autoReload) => AutoReloadGearChanged?.Invoke(autoReload); - private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) + private void OnStateChanged(in StateChanged.Arguments arguments) { - Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateChanged != null) - foreach (var actor in actors.Objects) + Glamourer.Log.Excessive( + $"[OnStateChanged] State Changed with Type {arguments.Type} [Affecting {arguments.Actors.ToLazyString("nothing")}.]"); + if (StateChanged is not null) + foreach (var actor in arguments.Actors.Objects) StateChanged.Invoke(actor.Address); - if (StateChangedWithType != null) - foreach (var actor in actors.Objects) - StateChangedWithType.Invoke(actor.Address, type); + if (StateChangedWithType is not null) + foreach (var actor in arguments.Actors.Objects) + StateChangedWithType.Invoke(actor.Address, arguments.Type); } - private void OnStateFinalized(StateFinalizationType type, ActorData actors) + private void OnStateFinalized(in StateFinalized.Arguments arguments) { - Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateFinalized != null) - foreach (var actor in actors.Objects) - StateFinalized.Invoke(actor.Address, type); + Glamourer.Log.Verbose( + $"[OnStateUpdated] State Updated with Type {arguments.Type}. [Affecting {arguments.Actors.ToLazyString("nothing")}.]"); + if (StateFinalized is not null) + foreach (var actor in arguments.Actors.Objects) + StateFinalized.Invoke(actor.Address, arguments.Type); } - private void OnGPoseChange(bool gPose) + private void OnGPoseChange(in bool gPose) => GPoseChanged?.Invoke(gPose); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index f88fd69..a41760e 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -7,6 +7,7 @@ using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Material; using Glamourer.State; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -15,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Automation; -public sealed class AutoDesignApplier : IDisposable +public sealed class AutoDesignApplier : IDisposable, IRequiredService { private readonly Configuration _config; private readonly AutoDesignManager _manager; @@ -72,38 +73,38 @@ public sealed class AutoDesignApplier : IDisposable _jobs.JobChanged -= OnJobChange; } - private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + private void OnWeaponLoading(in WeaponLoading.Arguments arguments) { if (!_jobChangeState.HasState || !_config.EnableAutoDesigns) return; - var id = actor.GetIdentifier(_actors); + var id = arguments.Actor.GetIdentifier(_actors); if (id == _jobChangeState.Identifier) { var state = _jobChangeState.State!; - var current = state.BaseData.Item(slot); - switch (slot) + var current = state.BaseData.Item(arguments.Slot); + switch (arguments.Slot) { case EquipSlot.MainHand: { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) + if (_jobChangeState.TryGetValue(current.Type, arguments.Actor.Job, false, out var data)) { Glamourer.Log.Verbose( - $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); + $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{arguments.Actor.Address:X}."); _state.ChangeItem(state, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2)); - weapon = state.ModelData.Weapon(EquipSlot.MainHand); + arguments.Weapon = state.ModelData.Weapon(EquipSlot.MainHand); } break; } case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand(): { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) + if (_jobChangeState.TryGetValue(current.Type, arguments.Actor.Job, false, out var data)) { Glamourer.Log.Verbose( - $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); + $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{arguments.Actor.Address:X}."); _state.ChangeItem(state, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2)); - weapon = state.ModelData.Weapon(EquipSlot.OffHand); + arguments.Weapon = state.ModelData.Weapon(EquipSlot.OffHand); } _jobChangeState.Reset(); @@ -117,22 +118,22 @@ public sealed class AutoDesignApplier : IDisposable } } - private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? bonusData) + private void OnAutomationChange(in AutomationChanged.Arguments arguments) { - if (!_config.EnableAutoDesigns || set == null) + if (!_config.EnableAutoDesigns) return; - switch (type) + switch (arguments.Type) { - case AutomationChanged.Type.ToggleSet when !set.Enabled: - case AutomationChanged.Type.DeletedDesign when set.Enabled: - // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. - RemoveOld(set.Identifiers); + // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. + case AutomationChanged.Type.ToggleSet when arguments.Set.Enabled: + case AutomationChanged.Type.DeletedDesign when arguments.Set.Enabled: + RemoveOld(arguments.Set.Identifiers); break; - case AutomationChanged.Type.ChangeIdentifier when set.Enabled: + case AutomationChanged.Type.ChangeIdentifier + when arguments.As().Set is { Enabled: true } set: // Remove fixed state from the old identifiers assigned and the old enabled set, if any. - var (oldIds, _, _) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; - RemoveOld(oldIds); + RemoveOld(arguments.As().OldIdentifiers); ApplyNew(set); // Does not need to disable oldSet because same identifiers. break; case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers. @@ -143,7 +144,7 @@ public sealed class AutoDesignApplier : IDisposable case AutomationChanged.Type.ChangedConditions: case AutomationChanged.Type.ChangedType: case AutomationChanged.Type.ChangedData: - ApplyNew(set); + ApplyNew(arguments.Set); break; } @@ -303,7 +304,7 @@ public sealed class AutoDesignApplier : IDisposable mergedDesign.ResetTemporarySettings = true; } - _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange)); forcedRedraw = mergedDesign.ForcedRedraw; } @@ -345,7 +346,7 @@ public sealed class AutoDesignApplier : IDisposable internal static int NewGearsetId = -1; - private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) + private void OnEquippedGearset(in EquippedGearset.Arguments arguments) { if (!_config.EnableAutoDesigns) return; @@ -357,9 +358,9 @@ public sealed class AutoDesignApplier : IDisposable if (!GetPlayerSet(player, out var set) || !_state.TryGetValue(player, out var state)) return; - var respectManual = prior == id; - NewGearsetId = id; - Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, prior == id, out var forcedRedraw); + var respectManual = arguments.PriorId == arguments.Id; + NewGearsetId = arguments.Id; + Reduce(data.Objects[0], state, set, respectManual, arguments.JobId != state.LastJob, arguments.PriorId == arguments.Id, out var forcedRedraw); NewGearsetId = -1; foreach (var actor in data.Objects) _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index eb76bd0..53a0a92 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,14 +1,12 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,7 +14,7 @@ using Luna; namespace Glamourer.Automation; -public class AutoDesignManager : ISavable, IReadOnlyList, IDisposable +public sealed class AutoDesignManager : ISavable, IReadOnlyList, IDisposable, IService { public const int CurrentVersion = 1; @@ -77,7 +75,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _data.Add(newSet); Save(); Glamourer.Log.Debug($"Created new design set for {newSet.Identifiers[0].Incognito(null)}."); - _event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name)); + _event.Invoke(new AutomationChanged.AddedSetArguments(newSet, _data.Count - 1, name)); } public void DuplicateDesignSet(AutoDesignSet set) @@ -102,7 +100,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Duplicated new design set for {newSet.Identifiers[0].Incognito(null)} with {newSet.Designs.Count} auto designs from existing set."); - _event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name)); + _event.Invoke(new AutomationChanged.AddedSetArguments(newSet, _data.Count - 1, name)); } public void DeleteDesignSet(int whichSet) @@ -121,12 +119,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _data.RemoveAt(whichSet); Save(); Glamourer.Log.Debug($"Deleted design set {whichSet + 1}."); - _event.Invoke(AutomationChanged.Type.DeletedSet, set, whichSet); + _event.Invoke(new AutomationChanged.DeletedSetArguments(set, whichSet)); } public void Rename(int whichSet, string newName) { - if (whichSet >= _data.Count || whichSet < 0 || newName.Length == 0) + if (whichSet >= _data.Count || whichSet < 0 || newName.Length is 0) return; var set = _data[whichSet]; @@ -137,7 +135,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.Name = newName; Save(); Glamourer.Log.Debug($"Renamed design set {whichSet + 1} from {old} to {newName}."); - _event.Invoke(AutomationChanged.Type.RenamedSet, set, (old, newName)); + _event.Invoke(new AutomationChanged.RenamedSetArguments(set, old, newName)); } @@ -148,7 +146,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Moved design set {whichSet + 1} to position {toWhichSet + 1}."); - _event.Invoke(AutomationChanged.Type.MovedSet, _data[toWhichSet], (whichSet, toWhichSet)); + _event.Invoke(new AutomationChanged.MovedSetArguments(_data[toWhichSet], whichSet, toWhichSet)); } public void ChangeIdentifier(int whichSet, ActorIdentifier to) @@ -180,7 +178,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed Identifier of design set {whichSet + 1} from {old[0].Incognito(null)} to {to.Incognito(null)}."); - _event.Invoke(AutomationChanged.Type.ChangeIdentifier, set, (old, to, oldEnabled)); + _event.Invoke(new AutomationChanged.ChangeIdentifierArguments(set, old, to, oldEnabled)); } public void SetState(int whichSet, bool value) @@ -214,7 +212,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed enabled state of design set {whichSet + 1} to {value}."); - _event.Invoke(AutomationChanged.Type.ToggleSet, set, oldEnabled); + _event.Invoke(new AutomationChanged.ToggleSetArguments(set, oldEnabled)); } public void ChangeBaseState(int whichSet, AutoDesignSet.Base newBase) @@ -230,7 +228,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.BaseState = newBase; Save(); Glamourer.Log.Debug($"Changed base state of set {whichSet + 1} from {old} to {newBase}."); - _event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); + _event.Invoke(new AutomationChanged.ChangedBaseArguments(set, old, newBase)); } public void ChangeResetSettings(int whichSet, bool newValue) @@ -246,12 +244,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.ResetTemporarySettings = newValue; Save(); Glamourer.Log.Debug($"Changed resetting of temporary settings of set {whichSet + 1} from {old} to {newValue}."); - _event.Invoke(AutomationChanged.Type.ChangedTemporarySettingsReset, set, newValue); + _event.Invoke(new AutomationChanged.ChangedTemporarySettingsResetArguments(set, newValue)); } public void AddDesign(AutoDesignSet set, IDesignStandIn design) { - var newDesign = new AutoDesign() + var newDesign = new AutoDesign { Design = design, Type = ApplicationType.All, @@ -261,7 +259,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Added new associated design {design.ResolveName(true)} as design {set.Designs.Count} to design set."); - _event.Invoke(AutomationChanged.Type.AddedDesign, set, set.Designs.Count - 1); + _event.Invoke(new AutomationChanged.AddedDesignArguments(set, set.Designs.Count - 1)); } /// Only used to move between sets. @@ -275,8 +273,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos from.Designs.RemoveAt(idx); Save(); Glamourer.Log.Debug($"Moved design {idx} from design set {from.Name} to design set {to.Name}."); - _event.Invoke(AutomationChanged.Type.AddedDesign, to, to.Designs.Count - 1); - _event.Invoke(AutomationChanged.Type.DeletedDesign, from, idx); + _event.Invoke(new AutomationChanged.AddedDesignArguments(to, to.Designs.Count - 1)); + _event.Invoke(new AutomationChanged.DeletedDesignArguments(from, idx)); } public void DeleteDesign(AutoDesignSet set, int which) @@ -287,7 +285,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.Designs.RemoveAt(which); Save(); Glamourer.Log.Debug($"Removed associated design {which + 1} from design set."); - _event.Invoke(AutomationChanged.Type.DeletedDesign, set, which); + _event.Invoke(new AutomationChanged.DeletedDesignArguments(set, which)); } public void MoveDesign(AutoDesignSet set, int from, int to) @@ -297,7 +295,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Moved design {from + 1} to {to + 1} in design set."); - _event.Invoke(AutomationChanged.Type.MovedDesign, set, (from, to)); + _event.Invoke(new AutomationChanged.MovedDesignArguments(set, from, to)); } public void ChangeDesign(AutoDesignSet set, int which, IDesignStandIn newDesign) @@ -314,7 +312,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug( $"Changed linked design from {old.ResolveName(true)} to {newDesign.ResolveName(true)} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedDesign, set, (which, old, newDesign)); + _event.Invoke(new AutomationChanged.ChangedDesignArguments(set, which, old, newDesign)); } public void ChangeJobCondition(AutoDesignSet set, int which, JobGroup jobs) @@ -331,7 +329,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.Jobs = jobs; Save(); Glamourer.Log.Debug($"Changed job condition from {old.Id} to {jobs.Id} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs)); + _event.Invoke(new AutomationChanged.ChangedConditionsArguments(set, which, old, jobs)); } public void ChangeGearsetCondition(AutoDesignSet set, int which, short index) @@ -347,7 +345,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.GearsetIndex = index; Save(); Glamourer.Log.Debug($"Changed gearset condition from {old} to {index} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); + _event.Invoke(new AutomationChanged.ChangedConditionsArguments(set, which, default, default)); } public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType) @@ -364,7 +362,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design.Type = applicationType; Save(); Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType)); + _event.Invoke(new AutomationChanged.ChangedTypeArguments(set, which, old, applicationType)); } public void ChangeData(AutoDesignSet set, int which, object data) @@ -378,7 +376,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Save(); Glamourer.Log.Debug($"Changed additional design data for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedData, set, (which, data)); + _event.Invoke(new AutomationChanged.ChangedDataArguments(set, which, data)); } public string ToFilePath(FilenameService fileNames) @@ -397,7 +395,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos foreach (var set in _data) array.Add(set.Serialize()); - return new JObject() + return new JObject { ["Version"] = CurrentVersion, ["Data"] = array, @@ -424,9 +422,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Glamourer.Messager.NotificationMessage("Failure to load automated designs: No valid version available.", NotificationType.Error); break; - case 1: - LoadV1(obj["Data"]); - break; + case 1: LoadV1(obj["Data"]); break; } } catch (Exception ex) @@ -638,17 +634,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos } } - private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _) + private void OnDesignChange(in DesignChanged.Arguments arguments) { - if (type is not DesignChanged.Type.Deleted) + if (arguments.Type is not DesignChanged.Type.Deleted) return; - foreach (var (set, idx) in this.WithIndex()) + foreach (var (idx, set) in this.Index()) { var deleted = 0; for (var i = 0; i < set.Designs.Count; ++i) { - if (set.Designs[i].Design != design) + if (set.Designs[i].Design != arguments.Design) continue; DeleteDesign(set, i--); @@ -657,7 +653,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos if (deleted > 0) Glamourer.Log.Information( - $"Removed {deleted} automated designs from automated design set {idx} due to deletion of {design.Incognito}."); + $"Removed {deleted} automated designs from automated design set {idx} due to deletion of {arguments.Design.Incognito}."); } } } diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 3340457..7344c27 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -9,7 +9,7 @@ using Penumbra.String; namespace Glamourer.Automation; -public class FixedDesignMigrator(JobService jobs) +public sealed class FixedDesignMigrator(JobService jobs) : IRequiredService { private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; diff --git a/Glamourer/Config/Configuration.cs b/Glamourer/Config/Configuration.cs index 31452bc..5f15c8b 100644 --- a/Glamourer/Config/Configuration.cs +++ b/Glamourer/Config/Configuration.cs @@ -13,7 +13,7 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Glamourer.Config; -public sealed partial class Configuration : IPluginConfiguration, ISavable +public sealed partial class Configuration : IPluginConfiguration, ISavable, IService { public const int CurrentVersion = 9; diff --git a/Glamourer/Config/EphemeralConfig.cs b/Glamourer/Config/EphemeralConfig.cs index 96699b5..6218e83 100644 --- a/Glamourer/Config/EphemeralConfig.cs +++ b/Glamourer/Config/EphemeralConfig.cs @@ -8,7 +8,7 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Glamourer.Config; -public partial class EphemeralConfig : ISavable +public partial class EphemeralConfig : ISavable, IService { public int Version { get; set; } = Configuration.CurrentVersion; @@ -16,18 +16,13 @@ public partial class EphemeralConfig : ISavable private bool _incognitoMode; public bool UnlockDetailMode { get; set; } = true; - public bool ShowDesignQuickBar { get; set; } = false; - public bool LockDesignQuickBar { get; set; } = false; - public bool LockMainWindow { get; set; } = false; + public bool ShowDesignQuickBar { get; set; } + public bool LockDesignQuickBar { get; set; } + public bool LockMainWindow { get; set; } public MainTabType SelectedMainTab { get; set; } = MainTabType.Settings; - public Guid SelectedDesign { get; set; } = Guid.Empty; public Guid SelectedQuickDesign { get; set; } = Guid.Empty; public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; - public float CurrentDesignSelectorWidth { get; set; } = 200f; - public float DesignSelectorMinimumScale { get; set; } = 0.1f; - public float DesignSelectorMaximumScale { get; set; } = 0.5f; - [JsonIgnore] private readonly SaveService _saveService; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 8804c8e..9b123d0 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -7,7 +7,6 @@ using Glamourer.Services; using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Classes; using Penumbra.GameData.Structs; using Luna; using Notification = Luna.Notification; @@ -47,7 +46,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa public IFileSystemData? Node { get; set; } public DateTimeOffset CreationDate { get; internal init; } public DateTimeOffset LastEdit { get; internal set; } - public LowerString Name { get; internal set; } = LowerString.Empty; + public string Name { get; internal set; } = string.Empty; public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } @@ -71,7 +70,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa #region IDesignStandIn public string ResolveName(bool incognito) - => incognito ? Incognito : Name.Text; + => incognito ? Incognito : Name; public string SerializeName() => Identifier.ToString(); @@ -109,7 +108,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa ["Identifier"] = Identifier, ["CreationDate"] = CreationDate, ["LastEdit"] = LastEdit, - ["Name"] = Name.Text, + ["Name"] = Name, ["Description"] = Description, ["ForcedRedraw"] = ForcedRedraw, ["ResetAdvancedDyes"] = ResetAdvancedDyes, @@ -200,7 +199,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa var hasNegativeGloss = false; var hasNonPositiveGloss = false; var specularLarger = 0; - foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + foreach (var (_, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) { hasNegativeGloss |= value.Value.GlossStrength < 0; hasNonPositiveGloss |= value.Value.GlossStrength <= 0; @@ -239,7 +238,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa Glamourer.Messager.AddMessage(new Notification( $"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}", NotificationType.Info)); - saveService.Save(Luna.SaveType.ImmediateSync, design); + saveService.Save(SaveType.ImmediateSync, design); } } @@ -251,7 +250,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa { CreationDate = creationDate, Identifier = json["Identifier"]?.ToObject() ?? throw new ArgumentNullException("Identifier"), - Name = new LowerString(json["Name"]?.ToObject() ?? throw new ArgumentNullException("Name")), + Name = json["Name"]?.ToObject() ?? throw new ArgumentNullException("Name"), Description = json["Description"]?.ToObject() ?? string.Empty, Tags = ParseTags(json), LastEdit = json["LastEdit"]?.ToObject() ?? creationDate, @@ -359,5 +358,5 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn, IFileSystemVa => Identifier.ToString(); public string DisplayName - => Name.Text; + => Name; } diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 8cd137f..dda16b0 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,7 +1,5 @@ using Glamourer.Api.Enums; using Glamourer.Services; -using OtterGui; -using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -107,7 +105,7 @@ public class DesignBase64Migration } data.Customize = *(CustomizeArray*)(ptr + 4); - foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + foreach (var (idx, slot) in EquipSlotExtensions.EqdpSlots.Index()) { var mdl = eq[idx]; var item = items.Identify(slot, mdl.Set, mdl.Variant); @@ -121,7 +119,7 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].Skeleton.Id == 0 + var main = cur[0].Skeleton.Id is 0 ? items.DefaultSword : items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant); if (!main.Valid) diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 1dec82c..44be523 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -1,5 +1,4 @@ using Dalamud.Interface.ImGuiNotification; -using Glamourer.Config; using Glamourer.Gui; using Glamourer.Services; using ImSharp; @@ -9,114 +8,7 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Designs; -public class DesignColorUi(DesignColors colors, Configuration config) -{ - private string _newName = string.Empty; - - public void Draw() - { - using var table = Im.Table.Begin("designColors"u8, 3, TableFlags.RowBackground); - if (!table) - return; - - var changeString = string.Empty; - Rgba32? changeValue = null; - - table.SetupColumn("##Delete"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); - table.SetupColumn("##Select"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight); - table.SetupColumn("Color Name"u8, TableColumnFlags.WidthStretch); - - table.HeaderRow(); - - table.NextColumn(); - if (ImEx.Icon.Button(LunaStyle.RefreshIcon, "Revert the color used for missing design colors to its default."u8, - colors.MissingColor == DesignColors.MissingColorDefault)) - { - changeString = DesignColors.MissingColorName; - changeValue = DesignColors.MissingColorDefault; - } - - table.NextColumn(); - if (DrawColorButton(DesignColors.MissingColorNameU8, colors.MissingColor, out var newColor)) - { - changeString = DesignColors.MissingColorName; - changeValue = newColor; - } - - table.NextColumn(); - Im.Cursor.X += Im.Style.FramePadding.X; - Im.Text(DesignColors.MissingColorNameU8); - Im.Tooltip.OnHover("This color is used when the color specified in a design is not available."u8); - - var disabled = !config.DeleteDesignModifier.IsActive(); - foreach (var (idx, (name, color)) in colors.Index()) - { - using var id = Im.Id.Push(idx); - table.NextColumn(); - - if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this color. This does not remove it from designs using it."u8, disabled)) - { - changeString = name; - changeValue = null; - } - - if (disabled) - Im.Tooltip.OnHover($"\nHold {config.DeleteDesignModifier} to delete."); - - table.NextColumn(); - if (DrawColorButton(name, color, out newColor)) - { - changeString = name; - changeValue = newColor; - } - - table.NextColumn(); - Im.Cursor.X += Im.Style.FramePadding.X; - Im.Text(name); - } - - table.NextColumn(); - (var tt, disabled) = _newName.Length == 0 - ? ("Specify a name for a new color first.", true) - : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName - ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) - : colors.ContainsKey(_newName) - ? ($"The color {_newName} already exists, please choose a different name.", true) - : ($"Add a new color {_newName} to your list.", false); - if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, disabled)) - { - changeString = _newName; - changeValue = 0xFFFFFFFF; - } - - table.NextColumn(); - table.NextColumn(); - Im.Item.SetNextWidth(Im.ContentRegion.Available.X); - if (Im.Input.Text("##newDesignColor"u8, ref _newName, "New Color Name..."u8, InputTextFlags.EnterReturnsTrue)) - { - changeString = _newName; - changeValue = 0xFFFFFFFF; - } - - if (changeString.Length > 0) - { - if (!changeValue.HasValue) - colors.DeleteColor(changeString); - else - colors.SetColor(changeString, changeValue.Value); - } - } - - public static bool DrawColorButton(Utf8StringHandler tooltip, Rgba32 color, out Rgba32 newColor) - { - var ret = Im.Color.Editor(tooltip, ref color, ColorEditorFlags.AlphaPreviewHalf | ColorEditorFlags.NoInputs); - Im.Tooltip.OnHover(ref tooltip); - newColor = color; - return ret; - } -} - -public class DesignColors : ISavable, IReadOnlyDictionary +public sealed class DesignColors : ISavable, IReadOnlyDictionary, IService { public const string AutomaticName = "Automatic"; public static readonly StringU8 AutomaticNameU8 = new("Automatic"u8); diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 058b023..46ee6d3 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -3,6 +3,7 @@ using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; +using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; @@ -12,13 +13,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter( +public sealed class DesignConverter( SaveService saveService, - ItemManager _items, - DesignManager _designs, - CustomizeService _customize, - HumanModelList _humans, - DesignLinkLoader _linkLoader) + ItemManager items, + DesignManager designs, + CustomizeService customizeService, + HumanModelList humans, + DesignLinkLoader linkLoader) : IService { public const byte Version = 6; @@ -54,9 +55,9 @@ public class DesignConverter( public DesignBase Convert(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { - var design = _designs.CreateTemporary(); + var design = designs.CreateTemporary(); rules.Apply(design); - design.SetDesignData(_customize, data); + design.SetDesignData(customizeService, data); if (rules.Materials) ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip); return design; @@ -70,8 +71,8 @@ public class DesignConverter( try { var ret = jObject["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObject) - : DesignBase.LoadDesignBase(_customize, _items, jObject); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObject) + : DesignBase.LoadDesignBase(customizeService, items, jObject); if (!customize) ret.Application.RemoveCustomize(); @@ -101,14 +102,14 @@ public class DesignConverter( case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj1) - : DesignBase.LoadDesignBase(_customize, _items, jObj1); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj1) + : DesignBase.LoadDesignBase(customizeService, items, jObj1); break; case 1: case 2: case 4: - ret = _designs.CreateTemporary(); - ret.MigrateBase64(_customize, _items, _humans, base64); + ret = designs.CreateTemporary(); + ret.MigrateBase64(customizeService, items, humans, base64); break; case 3: { @@ -116,8 +117,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } case 5: @@ -127,8 +128,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } case 6: @@ -137,8 +138,8 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); + ? Design.LoadDesign(saveService, customizeService, items, linkLoader, jObj2) + : DesignBase.LoadDesignBase(customizeService, items, jObj2); break; } @@ -177,7 +178,7 @@ public class DesignConverter( { var index = (int)slot.ToIndex(); var armor = armors[index]; - var item = _items.Identify(slot, armor.Set, armor.Variant); + var item = items.Identify(slot, armor.Set, armor.Variant); if (!item.Valid) { if (!skipWarnings) @@ -188,20 +189,20 @@ public class DesignConverter( yield return (slot, item, armor.Stains); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); + var mh = items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); if (!skipWarnings && !mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); - mh = _items.DefaultSword; + mh = items.DefaultSword; } yield return (EquipSlot.MainHand, mh, mainhand.Stains); - var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); + var oh = items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!skipWarnings && !oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); - oh = _items.GetDefaultOffhand(mh); + oh = items.GetDefaultOffhand(mh); if (!oh.Valid) oh = ItemManager.NothingItem(FullEquipType.Shield); } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 1bfc4d3..db5b48c 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -75,7 +75,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed customize {idx.ToName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value))); } /// @@ -91,7 +91,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize))); } /// @@ -106,7 +106,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new))); } /// @@ -130,9 +130,9 @@ public class DesignEditor( SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Weapon, design, new WeaponTransaction(currentMain, currentOff, currentGauntlets, item, newOff ?? currentOff, - newGauntlets ?? currentGauntlets)); + newGauntlets ?? currentGauntlets))); return; } case EquipSlot.OffHand: @@ -150,8 +150,8 @@ public class DesignEditor( SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, - new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Weapon, design, + new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets))); return; } default: @@ -167,7 +167,7 @@ public class DesignEditor( Glamourer.Log.Debug( $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item))); return; } } @@ -187,7 +187,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {slot} bonus item to {item}."); - DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item))); } /// @@ -204,7 +204,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); - DesignChanged.Invoke(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains))); } /// @@ -227,7 +227,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); - DesignChanged.Invoke(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest))); } /// @@ -240,7 +240,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value))); } public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert) @@ -253,7 +253,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}"); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert))); } public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row) @@ -288,7 +288,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.DelaySave(design); - DesignChanged.Invoke(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row))); } public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) @@ -301,7 +301,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}."); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value))); } diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 4810174..c2dc048 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,5 +1,4 @@ using Dalamud.Interface.ImGuiNotification; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using Luna; @@ -10,41 +9,50 @@ public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredSer { private readonly DesignFileSystemSaver _saver; private readonly DesignChanged _designChanged; + private readonly TabSelected _tabSelected; - public DesignFileSystem(Logger log, SaveService saveService, DesignStorage designs, DesignChanged designChanged) + public DesignFileSystem(Logger log, SaveService saveService, DesignStorage designs, DesignChanged designChanged, TabSelected tabSelected) : base("DesignFileSystem", log, true) { _designChanged = designChanged; + _tabSelected = tabSelected; _saver = new DesignFileSystemSaver(log, this, saveService, designs); _saver.Load(); _designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignFileSystem); + _tabSelected.Subscribe(OnTabSelected, TabSelected.Priority.DesignSelector); } - private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _) + private void OnTabSelected(in TabSelected.Arguments arguments) { - switch (type) + if (arguments.Design?.Node is { } node) + Selection.Select(node); + } + + private void OnDesignChanged(in DesignChanged.Arguments arguments) + { + switch (arguments.Type) { case DesignChanged.Type.ReloadedAll: _saver.Load(); break; case DesignChanged.Type.Created: var parent = Root; - if (design.Path.Folder.Length > 0) + if (arguments.Design.Path.Folder.Length > 0) try { - parent = FindOrCreateAllFolders(design.Path.Folder); + parent = FindOrCreateAllFolders(arguments.Design.Path.Folder); } catch (Exception ex) { Glamourer.Messager.NotificationMessage(ex, - $"Could not move design to {design.Path} because the folder could not be created.", + $"Could not move design to {arguments.Design.Path} because the folder could not be created.", NotificationType.Error); } - var (data, _) = CreateDuplicateDataNode(parent, design.Path.SortName ?? design.Name, design); + var (data, _) = CreateDuplicateDataNode(parent, arguments.Design.Path.SortName ?? arguments.Design.Name, arguments.Design); Selection.Select(data); break; case DesignChanged.Type.Deleted: - if (design.Node is { } node) + if (arguments.Design.Node is { } node) { if (node.Selected) Selection.UnselectAll(); @@ -52,8 +60,8 @@ public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredSer } break; - case DesignChanged.Type.Renamed when design.Path.SortName is null: - RenameWithDuplicates(design.Node!, design.Path.GetIntendedName(design.Name.Text)); + case DesignChanged.Type.Renamed when arguments.Design.Path.SortName is null: + RenameWithDuplicates(arguments.Design.Node!, arguments.Design.Path.GetIntendedName(arguments.Design.Name)); break; // TODO: Maybe add path changes? } @@ -61,6 +69,7 @@ public sealed class DesignFileSystem : BaseFileSystem, IDisposable, IRequiredSer public void Dispose() { + _tabSelected.Unsubscribe(OnTabSelected); _designChanged.Unsubscribe(OnDesignChanged); } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index ab0551b..5e12b57 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -7,7 +7,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; -using OtterGui.Extensions; +using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; @@ -16,7 +16,7 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public sealed class DesignManager : DesignEditor +public sealed class DesignManager : DesignEditor, IService { public readonly DesignStorage Designs; private readonly HumanModelList _humans; @@ -89,7 +89,7 @@ public sealed class DesignManager : DesignEditor Glamourer.Log.Information( $"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); - DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ReloadedAll, null!)); } /// Create a new temporary design without adding it to the manager. @@ -116,7 +116,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -141,7 +141,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -162,7 +162,7 @@ public sealed class DesignManager : DesignEditor Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path))); return design; } @@ -173,7 +173,7 @@ public sealed class DesignManager : DesignEditor --d.Index; Designs.RemoveAt(design.Index); SaveService.ImmediateDelete(design); - DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Deleted, design)); } #endregion @@ -183,7 +183,7 @@ public sealed class DesignManager : DesignEditor /// Rename a design. public void Rename(Design design, string newName) { - var oldName = design.Name.Text; + var oldName = design.Name; if (oldName == newName) return; @@ -191,7 +191,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName))); } /// Change the description of a design. @@ -205,7 +205,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed description of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description))); } /// Change the associated color of a design. @@ -219,7 +219,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor))); } /// Add a new tag to a design. The tags remain sorted. @@ -233,7 +233,7 @@ public sealed class DesignManager : DesignEditor var idx = design.Tags.AsEnumerable().IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx))); } /// Remove a tag from a design by its index. @@ -247,7 +247,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx))); } /// Rename a tag from a design by its index. The tags stay sorted. @@ -262,8 +262,8 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, - new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag))); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedTag, design, + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag)))); } /// Add an associated mod to a design. @@ -275,7 +275,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings))); } /// Remove an associated mod from a design. @@ -287,7 +287,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings))); } /// Add or update an associated mod to a design. @@ -300,12 +300,12 @@ public sealed class DesignManager : DesignEditor if (hasOldSettings) { Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings))); } else { Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings))); } } @@ -317,7 +317,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected."); - DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.WriteProtection, design)); } /// Set the quick design bar display status of a design. @@ -330,7 +330,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar."); - DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.QuickDesignBar, design)); } #endregion @@ -345,7 +345,7 @@ public sealed class DesignManager : DesignEditor design.ForcedRedraw = forcedRedraw; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? string.Empty : "not")} force redraws."); - DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ForceRedraw, design)); } public void ChangeResetAdvancedDyes(Design design, bool resetAdvancedDyes) @@ -356,7 +356,7 @@ public sealed class DesignManager : DesignEditor design.ResetAdvancedDyes = resetAdvancedDyes; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? string.Empty : "not")} reset advanced dyes."); - DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ResetAdvancedDyes, design)); } public void ChangeResetTemporarySettings(Design design, bool resetTemporarySettings) @@ -367,7 +367,7 @@ public sealed class DesignManager : DesignEditor design.ResetTemporarySettings = resetTemporarySettings; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {design.Identifier} to {(resetTemporarySettings ? string.Empty : "not")} reset temporary settings."); - DesignChanged.Invoke(DesignChanged.Type.ResetTemporarySettings, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ResetTemporarySettings, design)); } /// Change whether to apply a specific customize value. @@ -379,7 +379,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of customization {idx.ToName()} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value))); } /// Change whether to apply a specific equipment piece. @@ -391,7 +391,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value))); } /// Change whether to apply a specific equipment piece. @@ -403,7 +403,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value))); } /// Change whether to apply a specific stain. @@ -415,7 +415,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value))); } /// Change whether to apply a specific crest visibility. @@ -427,7 +427,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value))); } /// Change the application value of one of the meta flags. @@ -439,7 +439,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value))); } /// Change the application value of a customize parameter. @@ -451,7 +451,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value)); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value))); } /// Change multiple application values at once. @@ -512,7 +512,6 @@ public sealed class DesignManager : DesignEditor { var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile); var dict = JsonConvert.DeserializeObject>(text) ?? new Dictionary(); - var migratedFileSystemPaths = new Dictionary(dict.Count); foreach (var (name, base64) in dict) { try @@ -529,7 +528,6 @@ public sealed class DesignManager : DesignEditor if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); - migratedFileSystemPaths.Add(design.Identifier.ToString(), name); ++successes; } else @@ -632,7 +630,7 @@ public sealed class DesignManager : DesignEditor if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, null); + DesignChanged.Invoke(new DesignChanged.Arguments(DesignChanged.Type.Created, design)); } /// Split a given string into its folder path and its name, if is true. diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 2676a84..4fc418b 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -1,8 +1,6 @@ -using Glamourer.Api.Enums; using Glamourer.Events; using Glamourer.State; using Luna; -using Penumbra.GameData.Interop; namespace Glamourer.Designs.History; @@ -171,21 +169,21 @@ public class EditorHistory : IDisposable, IService } - private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData actors, ITransaction? data) + private void OnStateChanged(in StateChanged.Arguments arguments) { - if (_undoMode || source is not StateSource.Manual) + if (_undoMode || arguments.Source is not StateSource.Manual) return; - if (data is not null) - AddStateTransaction(state, data); + if (arguments.Transaction is not null) + AddStateTransaction(arguments.State, arguments.Transaction); } - private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? data) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { if (_undoMode) return; - if (data is not null) - AddDesignTransaction(design, data); + if (arguments.Transaction is not null) + AddDesignTransaction(arguments.Design, arguments.Transaction); } } diff --git a/Glamourer/Designs/History/Transaction.cs b/Glamourer/Designs/History/Transaction.cs index 47b10bf..234c7bb 100644 --- a/Glamourer/Designs/History/Transaction.cs +++ b/Glamourer/Designs/History/Transaction.cs @@ -10,7 +10,7 @@ public interface ITransaction public void Revert(IDesignEditor editor, object data); } -public readonly record struct CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New) +public record CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -20,7 +20,7 @@ public readonly record struct CustomizeTransaction(CustomizeIndex Slot, Customiz => editor.ChangeCustomize(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New) +public record EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -30,7 +30,7 @@ public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, Cu => editor.ChangeEntireCustomize(data, Old, Apply, ApplySettings.Manual); } -public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New) +public record EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -40,7 +40,7 @@ public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, Eq => editor.ChangeItem(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New) +public record BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -50,7 +50,7 @@ public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem => editor.ChangeBonusItem(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct WeaponTransaction( +public record WeaponTransaction( EquipItem OldMain, EquipItem OldOff, EquipItem OldGauntlets, @@ -72,7 +72,7 @@ public readonly record struct WeaponTransaction( } } -public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, StainIds New) +public record StainTransaction(EquipSlot Slot, StainIds Old, StainIds New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -82,7 +82,7 @@ public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, Sta => editor.ChangeStains(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool New) +public record CrestTransaction(CrestFlag Slot, bool Old, bool New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -92,7 +92,7 @@ public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool Ne => editor.ChangeCrest(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New) +public record ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New) : ITransaction { public ITransaction? Merge(ITransaction older) @@ -102,7 +102,7 @@ public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, => editor.ChangeCustomizeParameter(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct MetaTransaction(MetaIndex Slot, bool Old, bool New) +public record MetaTransaction(MetaIndex Slot, bool Old, bool New) : ITransaction { public ITransaction? Merge(ITransaction older) diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs index efed8ab..a5833e6 100644 --- a/Glamourer/Designs/Links/DesignLinkManager.cs +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -1,5 +1,4 @@ using Glamourer.Automation; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using Luna; @@ -32,7 +31,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void AddDesignLink(Design parent, Design child, LinkOrder order) @@ -43,7 +42,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void RemoveDesignLink(Design parent, int idx, LinkOrder order) @@ -54,7 +53,7 @@ public sealed class DesignLinkManager : IService, IDisposable parent.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(parent); Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } public void ChangeApplicationType(Design parent, int idx, LinkOrder order, ApplicationType applicationType) @@ -64,22 +63,23 @@ public sealed class DesignLinkManager : IService, IDisposable return; _saveService.QueueSave(parent); - Glamourer.Log.Debug($"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + Glamourer.Log.Debug( + $"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}."); + _event.Invoke(new DesignChanged.Arguments(DesignChanged.Type.ChangedLink, parent)); } - private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type is not DesignChanged.Type.Deleted) + if (arguments.Type is not DesignChanged.Type.Deleted) return; foreach (var design in _storage) { - if (!design.Links.Remove(deletedDesign)) + if (!design.Links.Remove(arguments.Design)) continue; design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); + Glamourer.Log.Debug($"Removed {arguments.Design.Identifier} from {design.Identifier} links due to deletion."); _saveService.QueueSave(design); } } diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs index 6cfc121..10e2416 100644 --- a/Glamourer/Designs/Links/LinkContainer.cs +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -1,6 +1,6 @@ using Glamourer.Automation; +using Luna; using Newtonsoft.Json.Linq; -using OtterGui.Filesystem; namespace Glamourer.Designs.Links; diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 0c62aa2..e45122a 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -3,7 +3,7 @@ using Luna; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService +public class RandomDesignGenerator(DesignStorage designs, Configuration config) : IService { private readonly Random _rng = new(); private readonly WeakReference _lastDesign = new(null!, false); @@ -34,7 +34,7 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS => Design(designs); public Design? Design(IDesignPredicate predicate) - => Design(predicate.Get(designs, fileSystem).ToList()); + => Design(predicate.Get(designs).ToList()); public Design? Design(IReadOnlyList predicates) { @@ -42,7 +42,7 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS { 0 => Design(), 1 => Design(predicates[0]), - _ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()), + _ => Design(IDesignPredicate.Get(predicates, designs).ToList()), }; } diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs index 172d249..9039eaa 100644 --- a/Glamourer/Designs/Special/RandomPredicate.cs +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -1,61 +1,59 @@ -using OtterGui.Classes; - -namespace Glamourer.Designs.Special; +namespace Glamourer.Designs.Special; public interface IDesignPredicate { - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath); + bool Invoke(Design design, string name, string identifier, string path); - public bool Invoke((Design Design, string LowerName, string Identifier, string LowerPath) args) - => Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath); + bool Invoke((Design Design, string Name, string Identifier, string Path) args) + => Invoke(args.Design, args.Name, args.Identifier, args.Path); - public IEnumerable Get(IEnumerable designs, DesignFileSystem fileSystem) + IEnumerable Get(IEnumerable designs) => designs.Select(Transform) .Where(Invoke) .Select(t => t.Design); - public static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs, DesignFileSystem fileSystem) + static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs) => predicates.Count > 0 ? designs.Select(Transform) .Where(t => predicates.Any(p => p.Invoke(t))) .Select(t => t.Design) : designs; - private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d) - => (d, d.Name.Lower, d.Identifier.ToString(), d.Path.CurrentPath.ToLowerInvariant()); + private static (Design Design, string Name, string Identifier, string Path) Transform(Design d) + => (d, d.Name, d.Identifier.ToString(), d.Path.CurrentPath); } public static class RandomPredicate { public readonly struct StartsWith(string value) : IDesignPredicate { - public LowerString Value { get; } = value; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) - => lowerPath.StartsWith(Value.Lower); + public bool Invoke(Design design, string name, string identifier, string path) + => path.StartsWith(Value, StringComparison.OrdinalIgnoreCase); public override string ToString() - => $"/{Value.Text}"; + => $"/{Value}"; } public readonly struct Contains(string value) : IDesignPredicate { - public LowerString Value { get; } = value; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + public bool Invoke(Design design, string name, string identifier, string path) { - if (lowerName.Contains(Value.Lower)) + if (name.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; - if (identifier.Contains(Value.Lower)) + if (identifier.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; - if (lowerPath.Contains(Value.Lower)) + if (path.Contains(Value, StringComparison.OrdinalIgnoreCase)) return true; return false; } public override string ToString() - => Value.Text; + => Value; } public readonly struct Exact(Exact.Type type, string value) : IDesignPredicate @@ -69,25 +67,25 @@ public static class RandomPredicate Color, } - public Type Which { get; } = type; - public LowerString Value { get; } = value; + public Type Which { get; } = type; + public string Value { get; } = value; - public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + public bool Invoke(Design design, string name, string identifier, string path) => Which switch { - Type.Name => lowerName == Value.Lower, - Type.Path => lowerPath == Value.Lower, - Type.Identifier => identifier == Value.Lower, + Type.Name => string.Equals(name, Value, StringComparison.OrdinalIgnoreCase), + Type.Path => string.Equals(path, Value, StringComparison.OrdinalIgnoreCase), + Type.Identifier => string.Equals(identifier, Value, StringComparison.OrdinalIgnoreCase), Type.Tag => IsContained(Value, design.Tags), - Type.Color => design.Color == Value, + Type.Color => string.Equals(design.Color, Value, StringComparison.OrdinalIgnoreCase), _ => false, }; - private static bool IsContained(LowerString value, IEnumerable data) - => data.Any(t => t == value); + private static bool IsContained(string value, IEnumerable data) + => data.Any(t => string.Equals(t, value, StringComparison.OrdinalIgnoreCase)); public override string ToString() - => $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value.Text}\""; + => $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value}\""; } public static IDesignPredicate CreateSinglePredicate(string restriction) @@ -125,7 +123,7 @@ public static class RandomPredicate public static List GeneratePredicates(string restrictions) { - if (restrictions.Length == 0) + if (restrictions.Length is 0) return []; List predicates = new(1); @@ -153,9 +151,9 @@ public static class RandomPredicate public static string GeneratePredicateString(IReadOnlyCollection predicates) { - if (predicates.Count == 0) + if (predicates.Count is 0) return string.Empty; - if (predicates.Count == 1) + if (predicates.Count is 1) return predicates.First()!.ToString()!; return $"{{{string.Join("; ", predicates)}}}"; diff --git a/Glamourer/Events/AutoRedrawChanged.cs b/Glamourer/Events/AutoRedrawChanged.cs index a8dd03a..5f9eebb 100644 --- a/Glamourer/Events/AutoRedrawChanged.cs +++ b/Glamourer/Events/AutoRedrawChanged.cs @@ -1,16 +1,16 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; /// /// Triggered when the auto-reload gear setting is changed in glamourer configuration. /// -public sealed class AutoRedrawChanged() - : EventWrapper(nameof(AutoRedrawChanged)) +public sealed class AutoRedrawChanged(Logger log) + : EventBase(nameof(AutoRedrawChanged), log) { public enum Priority { /// StateApi = int.MinValue, } -} \ No newline at end of file +} diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 9ee3e1b..67c2c76 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -1,64 +1,60 @@ using Glamourer.Automation; -using OtterGui.Classes; +using Glamourer.Designs; +using Luna; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when an automated design is changed in any way. -/// -/// Parameter is the type of the change -/// Parameter is the added or changed design set or null on deletion. -/// Parameter is additional data depending on the type of change. -/// -/// -public sealed class AutomationChanged() - : EventWrapper(nameof(AutomationChanged)) +/// Triggered when an automated design is changed in any way. +public sealed class AutomationChanged(Logger log) + : EventBase(nameof(AutomationChanged), log) { public enum Type { - /// Add a new set. Names and identifiers do not have to be unique. It is not enabled by default. Additional data is the index it gets added at and the name [(int, string)]. + /// Add a new set. Names and identifiers do not have to be unique. It is not enabled by default. AddedSet, - /// Delete a given set. Additional data is the index it got removed from [int]. + /// Delete a given set. DeletedSet, - /// Rename a given set. Names do not have to be unique. Additional data is the old name and the new name [(string, string)]. + /// Rename a given set. Names do not have to be unique. RenamedSet, - /// Move a given set to a different position. Additional data is the old index of the set and the new index of the set [(int, int)]. + /// Move a given set to a different position. MovedSet, - /// Change the identifier a given set is associated with to another one. Additional data is the old identifier and the new one, and a potentially disabled other design set. [(ActorIdentifier[], ActorIdentifier, AutoDesignSet?)]. + /// Change the identifier a given set is associated with to another one. ChangeIdentifier, - /// Toggle the enabled state of a given set. Additional data is the thus disabled other set, if any [AutoDesignSet?]. + /// Toggle the enabled state of a given set. ToggleSet, - /// Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. + /// Change the used base state of a given set. ChangedBase, - /// Change the resetting of temporary settings for a given set. Additional data is the new value. + /// Change the resetting of temporary settings for a given set. ChangedTemporarySettingsReset, - /// Add a new associated design to a given set. Additional data is the index it got added at [int]. + /// Add a new associated design to a given set. AddedDesign, - /// Remove a given associated design from a given set. Additional data is the index it got removed from [int]. + /// Remove a given associated design from a given set. DeletedDesign, - /// Move a given associated design in the list of a given set. Additional data is the index that got moved and the index it got moved to [(int, int)]. + /// Move a given associated design in the list of a given set. MovedDesign, - /// Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, IDesignStandIn, IDesignStandIn)]. + /// Change the linked design in an associated design for a given set. ChangedDesign, - /// Change the job condition in an associated design for a given set. Additional data is the index of the changed associated design, the old job group and the new job group [(int, JobGroup, JobGroup)]. + /// Change the job condition in an associated design for a given set. ChangedConditions, - /// Change the application type in an associated design for a given set. Additional data is the index of the changed associated design, the old type and the new type. [(int, AutoDesign.Type, AutoDesign.Type)]. + /// Change the application type in an associated design for a given set. ChangedType, - /// Change the additional data for a specific design type. Additional data is the index of the changed associated design and the new data. [(int, object)] + /// Change the additional data for a specific design type. ChangedData, } @@ -76,4 +72,92 @@ public sealed class AutomationChanged() /// RandomRestrictionDrawer = -1, } + + /// The type of change. + public record Arguments(Type Type, AutoDesignSet Set) + { + public T As() where T : Arguments + => (T)this; + } + + /// The added set. + /// The index the set was added at. + /// The name of the added set. + public sealed record AddedSetArguments(AutoDesignSet Set, int Index, string Name) : Arguments(Type.AddedSet, Set); + + /// The deleted set. + /// The index the set was deleted from. + public sealed record DeletedSetArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedSet, Set); + + /// The renamed set. + /// The old name of the set. + /// The new name of the set. + public sealed record RenamedSetArguments(AutoDesignSet Set, string OldName, string NewName) : Arguments(Type.RenamedSet, Set); + + /// The moved set. + /// The index the set was moved from. + /// The index the set was moved to. + public sealed record MovedSetArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedSet, Set); + + /// The set that got its associated identifiers changed. + /// The prior identifiers that got removed. + /// The new identifiers associated with the set. + /// A set that was associated with the new identifiers before and got disabled through this change, or null. + public sealed record ChangeIdentifierArguments( + AutoDesignSet Set, + ActorIdentifier[] OldIdentifiers, + ActorIdentifier NewIdentifier, + AutoDesignSet? DisabledSet) : Arguments(Type.ChangeIdentifier, Set); + + /// The set that got toggled on or off. + /// A set with the same association that got disabled due to this change, or null. + public sealed record ToggleSetArguments(AutoDesignSet Set, AutoDesignSet? DisabledSet) : Arguments(Type.ToggleSet, Set); + + /// The set that changed its base state. + /// The old base state of the set. + /// The new base state of the set. + public sealed record ChangedBaseArguments(AutoDesignSet Set, AutoDesignSet.Base OldBase, AutoDesignSet.Base NewBase) : Arguments(Type.ChangedBase, Set); + + /// The set that changed whether it resets all temporary settings. + /// The new state of resetting temporary settings. + public sealed record ChangedTemporarySettingsResetArguments(AutoDesignSet Set, bool NewValue) : Arguments(Type.ChangedTemporarySettingsReset, Set); + + /// The set that added a new design. + /// The index the new design was added in the set. + public sealed record AddedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.AddedDesign, Set); + + /// The set that removed a design. + /// The index the design was removed from. + public sealed record DeletedDesignArguments(AutoDesignSet Set, int Index) : Arguments(Type.DeletedDesign, Set); + + /// The set that moved a design. + /// The index the design was moved from in the set. + /// The index the design was moved from to the set. + public sealed record MovedDesignArguments(AutoDesignSet Set, int OldIndex, int NewIndex) : Arguments(Type.MovedDesign, Set); + + /// The set that changed a design. + /// The index of the changed design. + /// The design previously assigned to the set. + /// The design the old one was changed to. + public sealed record ChangedDesignArguments(AutoDesignSet Set, int DesignIndex, IDesignStandIn OldDesign, IDesignStandIn NewDesign) + : Arguments(Type.ChangedDesign, Set); + + /// The set that changed a job group condition. + /// The index of the changed design. + /// The prior job condition for the design. + /// The new job condition for the design. + public sealed record ChangedConditionsArguments(AutoDesignSet Set, int DesignIndex, JobGroup OldGroup, JobGroup NewGroup) + : Arguments(Type.ChangedConditions, Set); + + /// The set that changed its application type. + /// The index of the changed design. + /// The old application flags. + /// The new application flags. + public sealed record ChangedTypeArguments(AutoDesignSet Set, int DesignIndex, ApplicationType OldType, ApplicationType NewType) + : Arguments(Type.ChangedType, Set); + + /// The set that got a design data changed. + /// The index of the changed design. + /// The new additional data for the changed design. + public sealed record ChangedDataArguments(AutoDesignSet Set, int DesignIndex, object NewData) : Arguments(Type.ChangedData, Set); } diff --git a/Glamourer/Events/BonusSlotUpdating.cs b/Glamourer/Events/BonusSlotUpdating.cs index 3f6e761..b796744 100644 --- a/Glamourer/Events/BonusSlotUpdating.cs +++ b/Glamourer/Events/BonusSlotUpdating.cs @@ -1,25 +1,32 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags a bonus slot for an update. -/// -/// Parameter is the model with a flagged slot. -/// Parameter is the bonus slot changed. -/// Parameter is the model values to change the bonus piece to. -/// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. -/// -/// -public sealed class BonusSlotUpdating() - : EventWrapperRef34(nameof(BonusSlotUpdating)) +/// Triggered when a model flags a bonus slot for an update. +public sealed class BonusSlotUpdating(Logger log) + : EventBase(nameof(BonusSlotUpdating), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Model model, BonusItemFlag flag, ref CharacterArmor armor, ref ulong returnValue) + { + /// The draw object with an updated bonus slot. + public readonly Model Model = model; + + /// The updated slot. + public readonly BonusItemFlag Slot = flag; + + /// The model data for the new bonus slot. This can be changed. + public ref CharacterArmor Armor = ref armor; + + /// The return value of the event. If this is , the original function will be called and its return value used, otherwise this value will be returned. + public ref ulong ReturnValue = ref returnValue; + } } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 83fc18b..1d9e897 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -2,20 +2,13 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a Design is edited in any way. -/// -/// Parameter is the type of the change -/// Parameter is the changed Design. -/// Parameter is any additional data depending on the type of change. -/// -/// -public sealed class DesignChanged() - : EventWrapper(nameof(DesignChanged)) +/// Triggered when a Design is edited in any way. +public sealed class DesignChanged(Logger log) + : EventBase(nameof(DesignChanged), log) { public enum Type { @@ -151,4 +144,10 @@ public sealed class DesignChanged() /// EditorHistory = -1000, } + + /// Arguments for the DesignChanged event. + /// The type of changed. + /// The changed design. + /// A transaction with further data corresponding to the change. + public readonly record struct Arguments(Type Type, Design Design, ITransaction? Transaction = null); } diff --git a/Glamourer/Events/EquipSlotUpdating.cs b/Glamourer/Events/EquipSlotUpdating.cs index a2daf85..da2face 100644 --- a/Glamourer/Events/EquipSlotUpdating.cs +++ b/Glamourer/Events/EquipSlotUpdating.cs @@ -1,25 +1,32 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags an equipment slot for an update. -/// -/// Parameter is the model with a flagged slot. -/// Parameter is the equipment slot changed. -/// Parameter is the model values to change the equipment piece to. -/// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. -/// -/// -public sealed class EquipSlotUpdating() - : EventWrapperRef34(nameof(EquipSlotUpdating)) +/// Triggered when a model flags an equipment slot for an update. +public sealed class EquipSlotUpdating(Logger log) + : EventBase(nameof(EquipSlotUpdating), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) + { + /// The draw object with an updated equipment slot. + public readonly Model Model = model; + + /// The updated slot. + public readonly EquipSlot Slot = slot; + + /// The model data for the new equipment slot. This can be changed. + public ref CharacterArmor Armor = ref armor; + + /// The return value of the event. If this is , the original function will be called and its return value used, otherwise this value will be returned. + public ref ulong ReturnValue = ref returnValue; + } } \ No newline at end of file diff --git a/Glamourer/Events/EquippedGearset.cs b/Glamourer/Events/EquippedGearset.cs index c4252eb..012b59f 100644 --- a/Glamourer/Events/EquippedGearset.cs +++ b/Glamourer/Events/EquippedGearset.cs @@ -1,23 +1,23 @@ -using OtterGui.Classes; +using Luna; +using Penumbra.String; namespace Glamourer.Events; -/// -/// Triggered when the player equips a gear set. -/// -/// Parameter is the name of the gear set. -/// Parameter is the id of the gear set. -/// Parameter is the id of the prior gear set. -/// Parameter is the id of the associated glamour. -/// Parameter is the job id of the associated job. -/// -/// -public sealed class EquippedGearset() - : EventWrapper(nameof(EquippedGearset)) +/// Triggered when the player equips a gear set. +public sealed class EquippedGearset(Logger log) + : EventBase(nameof(EquippedGearset), log) { public enum Priority { /// AutoDesignApplier = 0, } + + /// Arguments for the EquippedGearset event. + /// The name of the equipped gear set. + /// The ID of the equipped gear set. + /// The ID of the gear set previously equipped. + /// The ID of the associated glamour plate. + /// The job ID of the associated job. + public readonly record struct Arguments(ByteString Name, int Id, int PriorId, int GlamourId, byte JobId); } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index 44421a0..08a75a6 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -1,9 +1,9 @@ using Dalamud.Plugin.Services; -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -public sealed class GPoseService : EventWrapper +public sealed class GPoseService : EventBase { private readonly IFramework _framework; private readonly IClientState _state; @@ -19,8 +19,8 @@ public sealed class GPoseService : EventWrapper public bool InGPose { get; private set; } - public GPoseService(IFramework framework, IClientState state) - : base(nameof(GPoseService)) + public GPoseService(IFramework framework, IClientState state, Logger log) + : base(nameof(GPoseService), log) { _framework = framework; _state = state; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 620bdab..31daae1 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -1,21 +1,23 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; /// -/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) -/// This defines an endpoint for when the gameState is updated. -/// -/// The model draw object associated with the finished load (Also fired by other players on render) -/// +/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) +/// This defines an endpoint for when the gameState is updated. /// -public sealed class GearsetDataLoaded() - : EventWrapper(nameof(GearsetDataLoaded)) +public sealed class GearsetDataLoaded(Logger log) + : EventBase(nameof(GearsetDataLoaded), log) { public enum Priority { /// StateListener = 0, } + + /// Arguments for the GearsetDataLoaded event. + /// The actor that loaded a gear set. + /// The draw object that finished the load. + public readonly record struct Arguments(Actor Actor, Model Model); } diff --git a/Glamourer/Events/HeadGearVisibilityChanged.cs b/Glamourer/Events/HeadGearVisibilityChanged.cs index d8f722b..eaf067c 100644 --- a/Glamourer/Events/HeadGearVisibilityChanged.cs +++ b/Glamourer/Events/HeadGearVisibilityChanged.cs @@ -1,21 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the visibility of head gear is changed. -/// -/// Parameter is the actor with changed head gear visibility. -/// Parameter is the new state. -/// -/// -public sealed class HeadGearVisibilityChanged() - : EventWrapperRef2(nameof(HeadGearVisibilityChanged)) +/// Triggered when the visibility of head gear is changed. +public sealed class HeadGearVisibilityChanged(Logger log) + : EventBase(nameof(HeadGearVisibilityChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool visible) + { + /// The actor whose head gear visibility changed. + public readonly Actor Actor = actor; + + /// The new visibility state. + public ref bool Visible = ref visible; + } } diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs index 9d24a03..6f2952c 100644 --- a/Glamourer/Events/MovedEquipment.cs +++ b/Glamourer/Events/MovedEquipment.cs @@ -1,4 +1,4 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -10,12 +10,14 @@ namespace Glamourer.Events; /// Parameter is an array of slots updated and corresponding item ids and stains. /// /// -public sealed class MovedEquipment() - : EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment)) +public sealed class MovedEquipment(Logger log) + : EventBase(nameof(MovedEquipment), log) { public enum Priority { /// StateListener = 0, } + + public readonly record struct Arguments(params (EquipSlot, uint, StainIds)[] Items); } diff --git a/Glamourer/Events/ObjectUnlocked.cs b/Glamourer/Events/ObjectUnlocked.cs index b15acd2..e2d103c 100644 --- a/Glamourer/Events/ObjectUnlocked.cs +++ b/Glamourer/Events/ObjectUnlocked.cs @@ -1,17 +1,10 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a new item or customization is unlocked. -/// -/// Parameter is the type of the unlocked object -/// Parameter is the id of the unlocked object. -/// Parameter is the timestamp of the unlock. -/// -/// -public sealed class ObjectUnlocked() - : EventWrapper(nameof(ObjectUnlocked)) +/// Triggered when a new item or customization is unlocked. +public sealed class ObjectUnlocked(Logger log) + : EventBase(nameof(ObjectUnlocked), log), IRequiredService { public enum Type { @@ -21,8 +14,15 @@ public sealed class ObjectUnlocked() public enum Priority { - /// + /// /// Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework. UnlockTable = 0, } + + + /// Arguments for the ObjectUnlocked event. + /// The type of the unlocked object. + /// The ID of the unlocked object. + /// The timestamp of the unlock event. + public readonly record struct Arguments(Type Type, uint Id, DateTimeOffset Timestamp); } diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 0975670..0546daa 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -1,22 +1,22 @@ -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; /// /// Triggered when Penumbra is reloaded. /// -public sealed class PenumbraReloaded() - : EventWrapper(nameof(PenumbraReloaded)) +public sealed class PenumbraReloaded(Logger log) + : EventBase(nameof(PenumbraReloaded), log) { public enum Priority { - /// + /// ChangeCustomizeService = 0, - /// + /// VisorService = 0, - /// + /// VieraEarService = 0, } } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 2bcc6fe..a1bf6df 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -1,33 +1,37 @@ using Glamourer.Api.Enums; using Glamourer.Designs.History; -using Glamourer.Interop.Structs; using Glamourer.State; -using OtterGui.Classes; using Penumbra.GameData.Interop; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when a Design is edited in any way. -/// -/// Parameter is the type of the change -/// Parameter is the changed saved state. -/// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. -/// -/// -public sealed class StateChanged() - : EventWrapper(nameof(StateChanged)) +/// Triggered when a state changes in any way. +public sealed class StateChanged(Logger log) + : EventBase(nameof(StateChanged), log) { public enum Priority { /// GlamourerIpc = int.MinValue, - /// + /// PenumbraAutoRedraw = 0, /// EditorHistory = -1000, } + + /// Arguments for the StateChanged event. + /// The type of change that occured. + /// The source of the change. + /// The changed state. + /// Any currently affected actors. + /// Additional data depending on the type of change. + public readonly record struct Arguments( + StateChangeType Type, + StateSource Source, + ActorState State, + ActorData Actors, + ITransaction? Transaction = null); } diff --git a/Glamourer/Events/StateFinalized.cs b/Glamourer/Events/StateFinalized.cs index 0ccaa8b..8944835 100644 --- a/Glamourer/Events/StateFinalized.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -1,24 +1,22 @@ using Glamourer.Api; using Glamourer.Api.Enums; -using Glamourer.Interop.Structs; -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. -/// -/// Parameter is the operation that finished updating the saved state. -/// Parameter is the existing actors using this saved state. -/// -/// -public sealed class StateFinalized() - : EventWrapper(nameof(StateFinalized)) +/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. +public sealed class StateFinalized(Logger log) + : EventBase(nameof(StateFinalized), log) { public enum Priority { /// StateApi = int.MinValue, } + + /// Arguments for the StateFinalized event. + /// The operation that finished updating the saved state. + /// The existing actors using this saved state at the moment. + public readonly record struct Arguments(StateFinalizationType Type, ActorData Actors); } diff --git a/Glamourer/Events/TabSelected.cs b/Glamourer/Events/TabSelected.cs index 1880d37..1b81d46 100644 --- a/Glamourer/Events/TabSelected.cs +++ b/Glamourer/Events/TabSelected.cs @@ -1,26 +1,24 @@ using Glamourer.Designs; using Glamourer.Gui; -using Glamourer.Gui.Tabs.DesignTab; -using OtterGui.Classes; +using Luna; namespace Glamourer.Events; -/// -/// Triggered when an automated design is changed in any way. -/// -/// Parameter is the tab to select. -/// Parameter is the design to select if the tab is the designs tab. -/// -/// -public sealed class TabSelected() - : EventWrapper(nameof(TabSelected)) +/// Triggered to select a tab or design. +public sealed class TabSelected(Logger log) + : EventBase(nameof(TabSelected), log) { public enum Priority { - /// + /// DesignSelector = 0, - /// + /// MainWindow = 1, } + + /// Arguments for the TabSelected event. + /// The tab to be selected. + /// The design to be selected in the Designs tab. + public readonly record struct Arguments(MainTabType Type, Design? Design = null); } diff --git a/Glamourer/Events/VieraEarStateChanged.cs b/Glamourer/Events/VieraEarStateChanged.cs index 65730b8..5390469 100644 --- a/Glamourer/Events/VieraEarStateChanged.cs +++ b/Glamourer/Events/VieraEarStateChanged.cs @@ -1,22 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the state of viera ear visibility for any draw object is changed. -/// -/// Parameter is the model with a changed viera ear visibility state. -/// Parameter is the new state. -/// Parameter is whether to call the original function. -/// -/// -public sealed class VieraEarStateChanged() - : EventWrapperRef2(nameof(VieraEarStateChanged)) +/// Triggered when the state of viera ear visibility for any draw object is changed. +public sealed class VieraEarStateChanged(Logger log) + : EventBase(nameof(VieraEarStateChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool state) + { + /// The actor with a changed viera ear visibility state. + public readonly Actor Actor = actor; + + /// The new ear visibility state. + public ref bool State = ref state; + } } diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index 03b7336..b070bda 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -1,22 +1,27 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the state of a visor for any draw object is changed. -/// -/// Parameter is the model with a changed visor state. -/// Parameter is the new state. -/// Parameter is whether to call the original function. -/// -/// -public sealed class VisorStateChanged() - : EventWrapperRef3(nameof(VisorStateChanged)) +/// Triggered when the state of a visor for any draw object is changed. +public sealed class VisorStateChanged(Logger log) + : EventBase(nameof(VisorStateChanged), log) { public enum Priority { /// StateListener = 0, } -} \ No newline at end of file + + public ref struct Arguments(Model model, bool visorState, ref bool value) + { + /// The model with a changed visor state. + public readonly Model Model = model; + + /// The game's visor state. + public readonly bool NewVisorState = visorState; + + /// The actual new value + public ref bool Value = ref value; + } +} diff --git a/Glamourer/Events/WeaponLoading.cs b/Glamourer/Events/WeaponLoading.cs index fda0b2f..fe29702 100644 --- a/Glamourer/Events/WeaponLoading.cs +++ b/Glamourer/Events/WeaponLoading.cs @@ -1,20 +1,13 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; -/// -/// Triggered when a model flags an equipment slot for an update. -/// -/// Parameter is the actor that has its weapons changed. -/// Parameter is the equipment slot changed (Mainhand or Offhand). -/// Parameter is the model values to change the weapon to. -/// -/// -public sealed class WeaponLoading() - : EventWrapperRef3(nameof(WeaponLoading)) +/// Triggered when a model flags an equipment slot for an update. +public sealed class WeaponLoading(Logger log) + : EventBase(nameof(WeaponLoading), log) { public enum Priority { @@ -24,4 +17,16 @@ public sealed class WeaponLoading() /// AutoDesignApplier = -1, } + + public ref struct Arguments(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + { + /// The actor that has its weapons changed. + public readonly Actor Actor = actor; + + /// The changed equipment slot (either Mainhand or Offhand). + public readonly EquipSlot Slot = slot; + + /// The model data for the new weapon. + public ref CharacterWeapon Weapon = ref weapon; + } } diff --git a/Glamourer/Events/WeaponVisibilityChanged.cs b/Glamourer/Events/WeaponVisibilityChanged.cs index f75fa68..5683336 100644 --- a/Glamourer/Events/WeaponVisibilityChanged.cs +++ b/Glamourer/Events/WeaponVisibilityChanged.cs @@ -1,20 +1,24 @@ -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Events; -/// -/// Triggered when the visibility of weapons is changed. -/// -/// Parameter is the actor with changed weapon visibility. -/// Parameter is the new state. -/// -/// -public sealed class WeaponVisibilityChanged() : EventWrapperRef2(nameof(WeaponVisibilityChanged)) +/// Triggered when the visibility of weapons is changed. +public sealed class WeaponVisibilityChanged(Logger log) + : EventBase(nameof(WeaponVisibilityChanged), log) { public enum Priority { /// StateListener = 0, } + + public ref struct Arguments(Actor actor, ref bool value) + { + /// The actor with changed weapon visibility. + public readonly Actor Actor = actor; + + /// The new state. + public ref bool Value = ref value; + } } diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index abcc90d..c388a5f 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,5 +1,4 @@ using ImSharp; -using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; @@ -139,10 +138,10 @@ public class CustomizeSet _ => Invalid(out custom), }; - int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) + static int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) { - var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v); - if (val == null) + var (idx, val) = list.Cast().Index().FirstOrDefault(p => p.Item!.Value.Value == v); + if (val is null) { output = null; return -1; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index bb6dd4f..fc5d67d 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -10,7 +10,7 @@ using Glamourer.State; using ImSharp; using Luna; using Penumbra.GameData.Interop; -using Logger = OtterGui.Log.Logger; +using Vortice.Direct3D11.Debug; namespace Glamourer; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 0c1602c..7ce1901 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -30,14 +30,12 @@ - - + @@ -56,8 +54,7 @@ - + diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a5b22b3..287176b 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -12,13 +12,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer( +public sealed partial class CustomizationDrawer( ITextureProvider textures, CustomizeService service, Configuration config, FavoriteManager favorites, HeightService heightService) - : IDisposable + : IDisposable, IUiService { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(textures); diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 6763cfe..f19ea5c 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -1,6 +1,5 @@ using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using ImSharp; @@ -54,7 +53,7 @@ public abstract class DesignComboBase( if (CurrentDesign is Design design) { if (Im.Item.RightClicked() && Im.Io.KeyControl) - TabSelected.Invoke(MainTabType.Designs, design); + TabSelected.Invoke(new TabSelected.Arguments(MainTabType.Designs, design)); Im.Tooltip.OnHover("Control + Right-Click to move to design."u8); } else @@ -81,7 +80,7 @@ public abstract class DesignComboBase( Im.Text("Currently resolving to "u8); using var color = ImGuiColor.Text.Push(DesignColors.GetColor(linkedDesign)); Im.Line.NoSpacing(); - Im.Text(linkedDesign.Name.Text); + Im.Text(linkedDesign.Name); } else { @@ -133,9 +132,9 @@ public abstract class DesignComboBase( private void OnDesignColorChanged() => Dirty |= IManagedCache.DirtyFlags.Custom; - private void OnDesignChanged(DesignChanged.Type type, Design? _1, ITransaction? _2 = null) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type switch + if (arguments.Type switch { DesignChanged.Type.Created => true, DesignChanged.Type.Renamed => true, @@ -215,18 +214,18 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService DesignChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignCombo); } - private void OnDesignChanged(DesignChanged.Type type, Design changedDesign, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - switch (type) + switch (arguments.Type) { case DesignChanged.Type.Created: // If the quick design bar has no selection, select the new design if it supports the bar. - if (QuickDesign is null && changedDesign.QuickDesign) - QuickDesign = changedDesign; + if (QuickDesign is null && arguments.Design.QuickDesign) + QuickDesign = arguments.Design; break; case DesignChanged.Type.Deleted: // If the deleted design was selected, select the first design that supports the bar, if any. - if (QuickDesign == changedDesign) + if (QuickDesign == arguments.Design) QuickDesign = Designs.Designs.FirstOrDefault(d => d.QuickDesign); break; case DesignChanged.Type.ReloadedAll: @@ -235,10 +234,10 @@ public sealed class QuickDesignCombo : DesignComboBase, IDisposable, IUiService break; case DesignChanged.Type.QuickDesignBar: // If the quick design support of a design was changed, select the new design if the bar has no selection and the design now supports it, - if (QuickDesign is null && changedDesign.QuickDesign) - QuickDesign = changedDesign; + if (QuickDesign is null && arguments.Design.QuickDesign) + QuickDesign = arguments.Design; // or select the first design that supports the bar, if any, if the support was removed from the currently selected design. - else if (QuickDesign == changedDesign && !changedDesign.QuickDesign) + else if (QuickDesign == arguments.Design && !arguments.Design.QuickDesign) QuickDesign = Designs.Designs.FirstOrDefault(d => d.QuickDesign); break; } @@ -290,9 +289,10 @@ public sealed class LinkDesignCombo : DesignComboBase, IUiService, IDisposable public void Dispose() => DesignChanged.Unsubscribe(OnDesignChanged); - private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (type is DesignChanged.Type.Deleted && design == NewSelection || type is DesignChanged.Type.ReloadedAll) + if (arguments.Type is DesignChanged.Type.Deleted && arguments.Design == NewSelection + || arguments.Type is DesignChanged.Type.ReloadedAll) NewSelection = null; } } @@ -311,8 +311,8 @@ public sealed class RandomDesignCombo( return exact.Which switch { RandomPredicate.Exact.Type.Name => Designs.Designs.FirstOrDefault(d => d.Name == exact.Value), - RandomPredicate.Exact.Type.Path => Designs.Designs.FirstOrDefault(d => d.Node!.FullPath == exact.Value.Text), - RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) ? g : Guid.Empty), + RandomPredicate.Exact.Type.Path => Designs.Designs.FirstOrDefault(d => d.Node!.FullPath == exact.Value), + RandomPredicate.Exact.Type.Identifier => Designs.Designs.ByIdentifier(Guid.TryParse(exact.Value, out var g) ? g : Guid.Empty), _ => null, }; } @@ -320,7 +320,7 @@ public sealed class RandomDesignCombo( public bool Draw(RandomPredicate.Exact exact, [NotNullWhen(true)] out Design? newDesign, float width) { var design = GetDesign(exact); - if (Draw(StringU8.Empty, design?.ResolveName(Config.IncognitoMode) ?? $"Not Found [{exact.Value.Text}]", StringU8.Empty, width, + if (Draw(StringU8.Empty, design?.ResolveName(Config.IncognitoMode) ?? $"Not Found [{exact.Value}]", StringU8.Empty, width, out var newItem) && newItem.Design is Design d) { diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index ba69c20..5c874bd 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -12,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public class EquipmentDrawer +public sealed class EquipmentDrawer : IUiService { private const float DefaultWidth = 280; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index f0a6441..ab53622 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -2,10 +2,11 @@ using Dalamud.Plugin.Services; using Glamourer.Config; using ImSharp; +using Luna; namespace Glamourer.Gui; -public class GenericPopupWindow : Luna.Window +public sealed class GenericPopupWindow : Window { private readonly Configuration _config; private readonly ICondition _condition; diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a6c3e48..b0195a4 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -4,7 +4,7 @@ using Luna; namespace Glamourer.Gui; -public class GlamourerChangelog +public sealed class GlamourerChangelog : IUiService { public const int LastChangelogVersion = 0; private readonly Configuration _config; diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 820927c..9ed596e 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -1,11 +1,12 @@ using Dalamud.Interface; -using Dalamud.Interface.Windowing; using Glamourer.Config; using Glamourer.Gui.Tabs.UnlocksTab; +using Luna; +using WindowSystem = Dalamud.Interface.Windowing.WindowSystem; namespace Glamourer.Gui; -public class GlamourerWindowSystem : IDisposable +public sealed class GlamourerWindowSystem : IDisposable, IUiService { private readonly WindowSystem _windowSystem = new("Glamourer"); private readonly IUiBuilder _uiBuilder; diff --git a/Glamourer/Gui/MainTabBar.cs b/Glamourer/Gui/MainTabBar.cs index 23f5180..4abf279 100644 --- a/Glamourer/Gui/MainTabBar.cs +++ b/Glamourer/Gui/MainTabBar.cs @@ -1,4 +1,4 @@ -using Glamourer.Designs; +using Glamourer.Config; using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; @@ -14,11 +14,11 @@ namespace Glamourer.Gui; public sealed class MainTabBar : TabBar { - private readonly Config.EphemeralConfig _config; - public readonly TabSelected Event; - public readonly SettingsTab Settings; + private readonly EphemeralConfig _config; + public readonly TabSelected Event; + public readonly SettingsTab Settings; - public MainTabBar(Logger log, Config.EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, + public MainTabBar(Logger log, EphemeralConfig config, SettingsTab settings, ActorTab actors, DesignTab designs, AutomationTab automation, UnlocksTab unlocks, NpcTab npcs, MessagesTab messages, DebugTab debug, TabSelected @event) : base("MainTabBar", log, settings, actors, designs, automation, unlocks, npcs, messages, debug) { @@ -30,8 +30,8 @@ public sealed class MainTabBar : TabBar NextTab = _config.SelectedMainTab; } - private void OnEvent(MainTabType tab, Design? _) - => NextTab = tab; + private void OnEvent(in TabSelected.Arguments arguments) + => NextTab = arguments.Type; private void OnTabSelected(in MainTabType arguments) { diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs index 5682895..89d520d 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationSelection.cs @@ -13,7 +13,7 @@ public sealed class AutomationSelection : IUiService, IDisposable public int DraggedDesignIndex = -1; - public AutoDesignSet? Set { get; private set; } + public AutoDesignSet? Set { get; private set; } public int Index { get; private set; } = -1; public StringU8 Name { get; private set; } = NoSelection; public StringU8 Incognito { get; private set; } = NoSelection; @@ -29,16 +29,16 @@ public sealed class AutomationSelection : IUiService, IDisposable _automationChanged.Unsubscribe(OnAutomationChanged); } - private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChanged(in AutomationChanged.Arguments arguments) { - if (set != Set) + if (arguments.Set != Set) return; - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: Update(null); break; - case AutomationChanged.Type.RenamedSet: Name = new StringU8(set!.Name); break; - case AutomationChanged.Type.MovedSet: Index = (((int, int))data!).Item2; break; + case AutomationChanged.Type.RenamedSet: Name = new StringU8(arguments.Set!.Name); break; + case AutomationChanged.Type.MovedSet: Index = arguments.As().NewIndex; break; } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 441ae26..4f70ab8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -4,7 +4,7 @@ using Luna; namespace Glamourer.Gui.Tabs.AutomationTab; -public class AutomationTab : TwoPanelLayout, ITab +public sealed class AutomationTab : TwoPanelLayout, ITab { private readonly Configuration _config; diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index c808ba9..f86c942 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,5 +1,6 @@ using Dalamud.Game.ClientState.Objects.Enums; using ImSharp; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Gui; @@ -7,13 +8,13 @@ using Penumbra.String; namespace Glamourer.Gui.Tabs.AutomationTab; -public class IdentifierDrawer( +public sealed class IdentifierDrawer( ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc, - HumanModelList humans) + HumanModelList humans) : IUiService { private readonly WorldCombo _worldCombo = new(dictWorld); private readonly HumanNpcCombo _humanNpcCombo = new(bNpcNames, dictModelChara, humans, bNpc); diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index 87bf442..fc23aba 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -20,21 +20,19 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private readonly RandomDesignCombo _randomDesignCombo; private readonly AutomationSelection _selection; private readonly DesignStorage _designs; - private readonly DesignFileSystem _designFileSystem; private string _newText = string.Empty; private string? _newDefinition; private Design? _newDesign; public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager, - RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs) + RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignStorage designs) { _automationChanged = automationChanged; _config = config; _autoDesignManager = autoDesignManager; _randomDesignCombo = randomDesignCombo; _selection = selection; - _designFileSystem = designFileSystem; _designs = designs; _automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer); } @@ -169,7 +167,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable { ImEx.TextFrameAligned("that contain"u8); table.NextColumn(); - var data = contains.Value.Text; + var data = contains.Value; Im.Item.SetNextWidthFull(); if (Im.Input.Text("##match"u8, ref data, "Name, Path, or Identifier Contains..."u8)) { @@ -186,7 +184,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable { ImEx.TextFrameAligned("whose path starts with"u8); table.NextColumn(); - var data = startsWith.Value.Text; + var data = startsWith.Value; Im.Item.SetNextWidthFull(); if (Im.Input.Text("##startsWith"u8, ref data, "Path Starts With..."u8)) { @@ -204,7 +202,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable ImEx.TextFrameAligned("that contain the tag"u8); table.NextColumn(); Im.Item.SetNextWidthFull(); - var data = exact.Value.Text; + var data = exact.Value; if (Im.Input.Text("##color"u8, ref data, "Contained tag..."u8)) { if (data.Length is 0) @@ -221,7 +219,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable ImEx.TextFrameAligned("that are set to the color"u8); table.NextColumn(); Im.Item.SetNextWidthFull(); - var data = exact.Value.Text; + var data = exact.Value; if (Im.Input.Text("##color"u8, ref data, "Assigned Color is..."u8)) { if (data.Length is 0) @@ -265,7 +263,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable if (!Im.Item.Hovered()) return; - var designs = predicate.Get(_designs, _designFileSystem); + var designs = predicate.Get(_designs); LookupTooltip(designs); } @@ -376,7 +374,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private void DrawTotalPreview(IReadOnlyList list) { - var designs = IDesignPredicate.Get(list, _designs, _designFileSystem).ToList(); + var designs = IDesignPredicate.Get(list, _designs).ToList(); Im.Button(designs.Count > 0 ? $"All Restrictions Combined Match {designs.Count} Designs" : "None of the Restrictions Matches Any Designs"u8, Im.ContentRegion.Available with { Y = 0 }); @@ -414,26 +412,27 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable DrawManualInput(list); } - private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChange(in AutomationChanged.Arguments arguments) { - if (set != _set || _set is null) + if (arguments.Set != _set || _set is null) return; - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: - case AutomationChanged.Type.DeletedDesign when data is int index && _designIndex == index: + case AutomationChanged.Type.DeletedDesign when arguments.As().Index == _designIndex: Close(); break; - case AutomationChanged.Type.MovedDesign when data is (int from, int to): - if (_designIndex == from) - _designIndex = to; - else if (_designIndex < from && _designIndex > to) + case AutomationChanged.Type.MovedDesign: + var data = arguments.As(); + if (_designIndex == data.OldIndex) + _designIndex = data.NewIndex; + else if (_designIndex < data.OldIndex && _designIndex > data.NewIndex) _designIndex++; - else if (_designIndex > to && _designIndex < from) + else if (_designIndex > data.NewIndex && _designIndex < data.OldIndex) _designIndex--; break; - case AutomationChanged.Type.ChangedDesign when data is (int index, IDesignStandIn _, IDesignStandIn _) && index == _designIndex: + case AutomationChanged.Type.ChangedDesign when arguments.As().DesignIndex == _designIndex: Close(); break; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index e2ee40e..c6026a8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -12,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.AutomationTab; -public class SetPanel( +public sealed class SetPanel( AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 10fb70d..64b3f5d 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -125,9 +125,9 @@ public sealed class SetSelector( base.Dispose(disposing); } - private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data) + private void OnAutomationChanged(in AutomationChanged.Arguments arguments) { - switch (type) + switch (arguments.Type) { case AutomationChanged.Type.DeletedSet: case AutomationChanged.Type.AddedSet: diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index d6f2369..e627395 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,19 +1,18 @@ using Glamourer.Gui.Tabs.DebugTab.IpcTester; using ImSharp; using Microsoft.Extensions.DependencyInjection; -using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) +public class DebugTabHeader(ReadOnlySpan label, params IGameDataDrawer[] subTrees) { - public string Label { get; } = label; + public StringU8 Label { get; } = new(label); public IReadOnlyList SubTrees { get; } = subTrees; public void Draw() { - using var h = ImRaii.CollapsingHeader(Label); + using var h = Im.Tree.HeaderId(Label); if (!h) return; @@ -32,7 +31,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateInterop(IServiceProvider provider) => new ( - "Interop", + "Interop"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -46,7 +45,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateGameData(IServiceProvider provider) => new ( - "Game Data", + "Game Data"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -62,7 +61,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateDesigns(IServiceProvider provider) => new ( - "Designs", + "Designs"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), @@ -72,7 +71,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateState(IServiceProvider provider) => new ( - "State", + "State"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService() @@ -81,7 +80,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public static DebugTabHeader CreateUnlocks(IServiceProvider provider) => new ( - "Unlocks", + "Unlocks"u8, provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index db45c52..79fe516 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -20,7 +20,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy foreach (var (idx, design) in designManager.Designs.Index()) { using var id = Im.Id.Push(idx); - using var t = Im.Tree.Node(design.Name.Text); + using var t = Im.Tree.Node(design.Name); if (!t) continue; @@ -61,7 +61,7 @@ public sealed class DesignManagerPanel(DesignManager designManager, DesignFileSy if (design is Design d) { table.DrawColumn("Name"u8); - table.DrawColumn(d.Name.Text); + table.DrawColumn(d.Name); table.DrawColumn($"({d.Index})"); table.DrawColumn("Description (Hover)"u8); Im.Tooltip.OnHover(d.Description); diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 40a73d1..7ca377d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -1,5 +1,5 @@ using Glamourer.State; -using OtterGui.Raii; +using ImSharp; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; @@ -17,7 +17,7 @@ public sealed class RetainedStatePanel(StateManager stateManager, ActorObjectMan { foreach (var (identifier, state) in stateManager.Where(kvp => !objectManager.ContainsKey(kvp.Key))) { - using var t = ImRaii.TreeNode(identifier.ToString()); + using var t = Im.Tree.Node($"{identifier}"); if (t) ActiveStatePanel.DrawState(stateManager, ActorData.Invalid, state); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index f06cbd8..6316092 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,13 +1,14 @@ using Dalamud.Interface.ImGuiNotification; using Glamourer.Config; using Glamourer.Designs; +using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Services; using ImSharp; using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignDetailTab +public sealed class DesignDetailTab : IUiService { private readonly SaveService _saveService; private readonly Configuration _config; @@ -57,7 +58,7 @@ public class DesignDetailTab table.NextColumn(); var width = Im.ContentRegion.Available with { Y = 0 }; Im.Item.SetNextWidth(width.X); - if (ImEx.InputOnDeactivation.Text("##Name"u8, Selected.Name.Text, out string newName)) + if (ImEx.InputOnDeactivation.Text("##Name"u8, Selected.Name, out string newName)) _manager.Rename(Selected, newName); var identifier = Selected.Identifier.ToString(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs index 5b75fe3..b38a2b5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignHeader.cs @@ -1,5 +1,4 @@ -using System.Security.AccessControl; -using Glamourer.Config; +using Glamourer.Config; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Events; @@ -31,29 +30,29 @@ public sealed class DesignHeader : SplitButtonHeader, IDisposable LeftButtons.AddButton(new ApplyCharacterButton(fileSystem, manager, objects, stateManager, converter), 70); LeftButtons.AddButton(new UndoButton(fileSystem, history), 60); - RightButtons.AddButton(incognito, 50); + RightButtons.AddButton(incognito, 50); RightButtons.AddButton(new LockedButton(fileSystem, manager), 100); _fileSystem.Selection.Changed += OnSelectionChanged; OnSelectionChanged(); designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignHeader); } - private void OnDesignChanged(DesignChanged.Type arg1, Design arg2, ITransaction? arg3) + private void OnDesignChanged(in DesignChanged.Arguments arguments) { - if (arg1 is not DesignChanged.Type.Renamed) + if (arguments.Type is not DesignChanged.Type.Renamed) return; - if (arg2 != _fileSystem.Selection.Selection?.Value) + if (arguments.Design != _fileSystem.Selection.Selection?.Value) return; - _header = new StringU8(arg2.Name.Text); + _header = new StringU8(arguments.Design.Name); } private void OnSelectionChanged() { if (_fileSystem.Selection.Selection?.GetValue() is { } selection) { - _header = new StringU8(selection.Name.Text); + _header = new StringU8(selection.Name); _incognito = new StringU8(selection.Incognito); } else if (_fileSystem.Selection.OrderedNodes.Count > 0) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index ee568f7..1cf485e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -107,7 +107,7 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(color)) { Im.Cursor.FrameAlign(); - Im.Selectable(config.Ephemeral.IncognitoMode ? Selected.Incognito : Selected.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? Selected.Incognito : Selected.Name); } Im.Tooltip.OnHover("Current Design"u8); @@ -137,7 +137,7 @@ public class DesignLinkDrawer( using (ImGuiColor.Text.Push(colorManager.GetColor(design))) { Im.Cursor.FrameAlign(); - Im.Selectable(config.Ephemeral.IncognitoMode ? design.Incognito : design.Name.Text); + Im.Selectable(config.Ephemeral.IncognitoMode ? design.Incognito : design.Name); } DrawDragDrop(design, order, i); diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 16d6f8d..ac8a326 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -8,7 +8,7 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystem fileSystem, DesignManager manager, Configuration config) +public sealed class ModAssociationsTab(PenumbraService penumbra, DesignFileSystem fileSystem, DesignManager manager, Configuration config) : IUiService { private readonly ModCombo _modCombo = new(penumbra, fileSystem); private (Mod, ModSettings)[]? _copy; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 71e0e30..4d1c483 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -6,11 +6,11 @@ using Luna; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel( +public sealed class MultiDesignPanel( DesignFileSystem fileSystem, DesignManager editor, DesignColors colors, - Configuration config) + Configuration config) : IUiService { private readonly DesignColorCombo _colorCombo = new(colors, true); @@ -109,7 +109,7 @@ public class MultiDesignPanel( { using var id = Im.Id.Push(index); var (icon, text) = node is IFileSystemData l - ? (LunaStyle.RemoveFileIcon, l.Value.Name.Text) + ? (LunaStyle.RemoveFileIcon, l.Value.Name) : (LunaStyle.RemoveFolderIcon, string.Empty); table.NextColumn(); if (ImEx.Icon.Button(icon, "Remove from selection."u8, sizeType)) @@ -152,7 +152,7 @@ public class MultiDesignPanel( ? _tag.Length is 0 ? "No tag specified."u8 : $"All designs selected already contain the tag \"{_tag}\"." - : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", + : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name))}", _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.AddTag(design, _tag); @@ -164,7 +164,7 @@ public class MultiDesignPanel( ? _tag.Length is 0 ? "No tag specified."u8 : $"No selected design contains the tag \"{_tag}\" locally." - : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}", + : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name))}", _removeDesigns.Count is 0)) foreach (var (design, index) in _removeDesigns) editor.RemoveTag(design, index); @@ -306,7 +306,7 @@ public class MultiDesignPanel( DesignColors.AutomaticName => "Use the other button to set to automatic."u8, _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".", } - : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name.Text))}", + : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{StringU8.Join("\n\t"u8, _addDesigns.Select(m => m.Name))}", _addDesigns.Count is 0)) foreach (var design in _addDesigns) editor.ChangeColor(design, _colorComboSelection!); @@ -316,7 +316,7 @@ public class MultiDesignPanel( ? $"Unset {_removeDesigns.Count} Designs" : "Unset"u8, width, _removeDesigns.Count is 0 ? "No selected design is set to a non-automatic color."u8 - : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name.Text))}", + : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{StringU8.Join("\n\t"u8, _removeDesigns.Select(m => m.Item1.Name))}", _removeDesigns.Count is 0)) foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs index 91310b7..4bbc7d6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFileSystemCache.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Designs.History; using Glamourer.Events; using ImSharp; using Luna; @@ -21,9 +20,9 @@ public sealed class DesignFileSystemCache : FileSystemCache Node = node; public Vector4 Color; - public StringU8 Name = new(node.Value.Name.Text); + public StringU8 Name = new(node.Value.Name); public StringU8 Incognito = new(node.Value.Incognito); public override void Update(FileSystemCache cache, IFileSystemNode node) { var drawer = (DesignFileSystemDrawer)cache.Parent; Color = drawer.DesignColors.GetColor(Node.Value).ToVector(); - Name = new StringU8(Node.Value.Name.Text); + Name = new StringU8(Node.Value.Name); } protected override void DrawInternal(FileSystemCache cache, IFileSystemNode node) diff --git a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs index e8feaac..2fa7ef8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs +++ b/Glamourer/Gui/Tabs/DesignTab/Selector/DesignFilter.cs @@ -42,12 +42,12 @@ public sealed class DesignFilter : TokenizedFilter token.Type switch { DesignFilterTokenType.Default => cacheItem.Node.FullPath.Contains(token.Needle, StringComparison.OrdinalIgnoreCase) - || cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + || cacheItem.Node.Value.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), DesignFilterTokenType.Mod => CheckMods(token.Needle, cacheItem), DesignFilterTokenType.Tag => CheckTags(token.Needle, cacheItem), DesignFilterTokenType.Color => cacheItem.Node.Value.Color.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), DesignFilterTokenType.Item => cacheItem.Node.Value.DesignData.ContainsName(token.Needle), - DesignFilterTokenType.Name => cacheItem.Node.Value.Name.Text.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), + DesignFilterTokenType.Name => cacheItem.Node.Value.Name.Contains(token.Needle, StringComparison.OrdinalIgnoreCase), DesignFilterTokenType.FullContext => CheckFullContext(token.Needle, cacheItem), _ => true, }; @@ -77,7 +77,7 @@ public sealed class DesignFilter : TokenizedFilter 0) + { + if (!changeValue.HasValue) + colors.DeleteColor(changeString); + else + colors.SetColor(changeString, changeValue.Value); + } + } + + public static bool DrawColorButton(Utf8StringHandler tooltip, Rgba32 color, out Rgba32 newColor) + { + var ret = Im.Color.Editor(tooltip, ref color, ColorEditorFlags.AlphaPreviewHalf | ColorEditorFlags.NoInputs); + Im.Tooltip.OnHover(ref tooltip); + newColor = color; + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 7d645a9..2002604 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.UnlocksTab; -public class UnlockOverview( +public sealed class UnlockOverview( ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, @@ -21,7 +21,7 @@ public class UnlockOverview( CodeService codes, JobService jobs, FavoriteManager favorites, - PenumbraService penumbra) + PenumbraService penumbra) : IUiService { private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 6d16d8b..961f65f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -16,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.UnlocksTab; -public sealed class UnlockTable : TableBase +public sealed class UnlockTable : TableBase, IUiService { private readonly JobService _jobs; private readonly ItemManager _items; @@ -554,16 +554,17 @@ public sealed class UnlockTable : TableBase } } - private void OnItemUnlock(ObjectUnlocked.Type type, uint id, DateTimeOffset timestamp) + private void OnItemUnlock(in ObjectUnlocked.Arguments arguments) { - if (type is not ObjectUnlocked.Type.Item) + if (arguments.Type is not ObjectUnlocked.Type.Item) return; FilterDirty = true; SortDirty = true; + var id = arguments.Id; var idx = UnfilteredItems.IndexOf(i => i.Item.ItemId == id); if (idx >= 0) - UpdateSingleItem(idx, UnfilteredItems[idx] with { UnlockTimestamp = timestamp }, false); + UpdateSingleItem(idx, UnfilteredItems[idx] with { UnlockTimestamp = arguments.Timestamp }, false); } public override void Update() diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 495d69c..06acd2f 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,7 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; -using OtterGui.Classes; +using Luna; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; @@ -13,13 +13,19 @@ namespace Glamourer.Interop; /// Changes in Race, body type or Gender are probably ignored. /// This operates on draw objects, not game objects. /// -public unsafe class ChangeCustomizeService : EventWrapperRef2 +public sealed unsafe class ChangeCustomizeService : EventBase { - private readonly PenumbraReloaded _penumbraReloaded; - private readonly IGameInteropProvider _interop; + private readonly PenumbraReloaded _penumbraReloaded; + private readonly IGameInteropProvider _interop; private readonly delegate* unmanaged _original; - private readonly Post _postEvent = new(); - + private readonly Post _postEvent; + + public ref struct Arguments(Model model, ref CustomizeArray customize) + { + public readonly Model Model = model; + public ref CustomizeArray Customize = ref customize; + } + /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. public static readonly InMethodChecker InUpdate = new(); @@ -30,8 +36,8 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) + public void Subscribe(InAction action, Post.Priority priority) => _postEvent.Subscribe(action, priority); - public void Unsubscribe(Action action) + public void Unsubscribe(InAction action) => _postEvent.Unsubscribe(action); - public sealed class Post() : EventWrapper(nameof(ChangeCustomizeService) + '.' + nameof(Post)) + public sealed class Post(Logger log) : EventBase(nameof(ChangeCustomizeService) + '.' + nameof(Post), log) { public enum Priority { diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 4805caf..be728b5 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -5,13 +5,14 @@ using Glamourer.Config; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class ContextMenuService : IDisposable +public sealed class ContextMenuService : IDisposable, IRequiredService { public const int ChatLogContextItemId = 0x958; diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 2b55f94..6e56f49 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,24 +2,16 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Classes; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -/// -/// Triggered when the crest visibility is updated on a model. -/// -/// Parameter is the model with an update. -/// Parameter is the equipment slot changed. -/// Parameter is whether the crest will be shown. -/// -/// -public sealed unsafe class CrestService : EventWrapperRef3 +/// Triggered when the crest visibility is updated on a model. +public sealed unsafe class CrestService : EventBase { public enum Priority { @@ -27,8 +19,20 @@ public sealed unsafe class CrestService : EventWrapperRef3 The game object with a crest update. + public readonly Actor Actor = actor; + + /// The equipment slot changed. + public readonly CrestFlag Slot = slot; + + /// The new value. + public ref bool Value = ref value; + } + + public CrestService(IGameInteropProvider interop, Logger log) + : base(nameof(CrestService), log) { interop.InitializeFromAttributes(this); _humanSetFreeCompanyCrestVisibleOnSlot = @@ -76,7 +80,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 886fb58..2aa3a06 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -19,10 +19,12 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); + _equipGearsetHook = + interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, + EquipGearSetDetour); _moveItemHook.Enable(); _equipGearsetHook.Enable(); @@ -36,14 +38,14 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook = null!; + private readonly Hook _equipGearsetHook; private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset((int)gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new EquippedGearset.Arguments(new ByteString(set->Name), (int)gearsetId, prior, glamourPlateId, set->ClassJob)); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { @@ -111,7 +113,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService Add(EquipSlot.LFinger, ref entry->Items[12]); } - _movedItemsEvent.Invoke(_itemList.ToArray()); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(_itemList.ToArray())); } return ret; @@ -130,25 +132,15 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); - if (ret == 0) + if (ret is 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _movedItemsEvent.Invoke(new[] - { - source, - target, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(source, target)); else - _movedItemsEvent.Invoke(new[] - { - source, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(source)); else if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _movedItemsEvent.Invoke(new[] - { - target, - }); + _movedItemsEvent.Invoke(new MovedEquipment.Arguments(target)); } return ret; diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 1797809..5315e35 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -2,6 +2,7 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Luna; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Interop; @@ -9,7 +10,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class JobService : IDisposable +public sealed class JobService : IDisposable, IRequiredService { private readonly nint _characterDataOffset; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 04f4d2e..e504187 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -37,11 +37,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable public void Dispose() => _event.Unsubscribe(OnPrepareColorSet); - private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainIds stain, ref nint ret) + private void OnPrepareColorSet(in PrepareColorSet.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(characterBase); - var validType = FindType(characterBase, actor, out var type); - var (slotId, materialId) = FindMaterial(characterBase, material); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); + var validType = FindType(arguments.Model.AsCharacterBase, actor, out var type); + var (slotId, materialId) = FindMaterial(arguments.Model.AsCharacterBase, arguments.Handle); if (!validType || type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0 @@ -55,19 +55,19 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (values.Length == 0) return; - if (!PrepareColorSet.TryGetColorTable(material, stain, out var baseColorSet)) + if (!PrepareColorSet.TryGetColorTable(arguments.Handle, arguments.Ids, out var baseColorSet)) return; var drawData = type switch { - MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId), - _ => GetTempSlot((Weapon*)characterBase), + MaterialValueIndex.DrawObjectType.Human => GetTempSlot(arguments.Model.AsHuman, (HumanSlot)slotId), + _ => GetTempSlot(arguments.Model.AsWeapon), }; - var mode = PrepareColorSet.GetMode(material); + var mode = PrepareColorSet.GetMode(arguments.Handle); UpdateMaterialValues(state, values, drawData, ref baseColorSet, mode); if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) - ret = (nint)texture; + arguments.ReturnValue = (nint)texture; } /// Update and apply the glamourer state of an actor according to the application sources when updated by the game. diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 83c8b9b..eac45b3 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -3,7 +3,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Luna; -using OtterGui.Classes; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; @@ -13,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; public sealed unsafe class PrepareColorSet - : EventWrapperPtr12Ref34, IHookService + : EventBase, IHookService { private readonly UpdateColorSets _updateColorSets; @@ -23,11 +22,19 @@ public sealed unsafe class PrepareColorSet MaterialManager = 0, } - public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets) - : base("Prepare Color Set ") + public ref struct Arguments(Model model, MaterialResourceHandle* handle, ref StainIds ids, ref nint returnValue) + { + public readonly Model Model = model; + public readonly MaterialResourceHandle* Handle = handle; + public ref StainIds Ids = ref ids; + public ref nint ReturnValue = ref returnValue; + } + + public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets, Logger log) + : base("Prepare Color Set", log) { _updateColorSets = updateColorSets; - _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); } private readonly Task> _task; @@ -64,7 +71,7 @@ public sealed unsafe class PrepareColorSet var ret = nint.Zero; var stainIds = new StainIds(stainId1, stainId2); - Invoke(characterBase.AsCharacterBase, material, ref stainIds, ref ret); + Invoke(new Arguments(characterBase.AsCharacterBase, material, ref stainIds, ref ret)); if (ret != nint.Zero) return (Texture*)ret; @@ -74,7 +81,7 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) + if (material->DataSet is null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) { table = default; return false; @@ -83,10 +90,10 @@ public sealed unsafe class PrepareColorSet var newTable = *(ColorTable.Table*)material->DataSet; if (GetDyeTable(material, out var dyeTable)) { - if (stainIds.Stain1.Id != 0) + if (stainIds.Stain1.Id is not 0) material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); - if (stainIds.Stain2.Id != 0) + if (stainIds.Stain2.Id is not 0) material->ReadStainingTemplate(dyeTable, stainIds.Stain2.Id, (Half*)&newTable, 1); } diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 6225986..7baa255 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -2,11 +2,12 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public unsafe class MetaService : IDisposable +public sealed unsafe class MetaService : IDisposable, IRequiredService { private readonly HeadGearVisibilityChanged _headGearEvent; private readonly WeaponVisibilityChanged _weaponEvent; @@ -75,8 +76,8 @@ public unsafe class MetaService : IDisposable } Actor actor = drawData->OwnerObject; - var v = value == 0; - _headGearEvent.Invoke(actor, ref v); + var v = value is 0; + _headGearEvent.Invoke(new HeadGearVisibilityChanged.Arguments(actor, ref v)); value = (byte)(v ? 0 : 1); Glamourer.Log.Verbose($"[MetaService] Hide Hat triggered with 0x{(nint)drawData:X} {id} {value} for {actor.Utf8Name}."); _hideHatGearHook.Original(drawData, id, value); @@ -85,8 +86,8 @@ public unsafe class MetaService : IDisposable private void HideWeaponsDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var v = value == 0; - _weaponEvent.Invoke(actor, ref v); + var v = value is 0; + _weaponEvent.Invoke(new WeaponVisibilityChanged.Arguments(actor, ref v)); Glamourer.Log.Verbose($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _hideWeaponsHook.Original(drawData, (byte)(v ? 0 : 1)); } @@ -94,8 +95,8 @@ public unsafe class MetaService : IDisposable private void ToggleVisorDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var v = value != 0; - _visorEvent.Invoke(actor.Model, true, ref v); + var v = value is not 0; + _visorEvent.Invoke(new VisorStateChanged.Arguments(actor.Model, true, ref v)); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _toggleVisorHook.Original(drawData, (byte)(v ? 1 : 0)); } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index ec81508..546400d 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,7 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; using Glamourer.Config; -using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.State; using Luna; @@ -10,7 +9,7 @@ using Penumbra.GameData.Interop; namespace Glamourer.Interop.Penumbra; -public class PenumbraAutoRedraw : IDisposable, IRequiredService +public sealed class PenumbraAutoRedraw : IDisposable, IRequiredService { private const int WaitFrames = 5; private readonly Configuration _config; @@ -46,13 +45,13 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } private readonly ConcurrentQueue<(ActorState, Action, int)> _actions = []; - private readonly OtterGui.Classes.ConcurrentSet _skips = []; + private readonly ConcurrentSet _skips = []; private DateTime _frame; - private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData _1, ITransaction? _2) + private void OnStateChanged(in StateChanged.Arguments arguments) { - if (type is StateChangeType.Design && source.IsIpc()) - _skips.TryAdd(state); + if (arguments.Type is StateChangeType.Design && arguments.Source.IsIpc()) + _skips.TryAdd(arguments.State); } private void OnFramework(IFramework _) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 0438cbc..382960d 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -35,7 +35,7 @@ public readonly record struct ModSettings(Dictionary> Setti => new(); } -public class PenumbraService : IDisposable +public sealed class PenumbraService : IDisposable, IService { public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraFeatureVersion = 13; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 2a89a25..92acb83 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -6,6 +6,7 @@ using Penumbra.GameData; using Penumbra.GameData.Interop; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.State; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -13,7 +14,7 @@ using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; namespace Glamourer.Interop; -public unsafe class ScalingService : IDisposable +public sealed unsafe class ScalingService : IDisposable, IRequiredService { private readonly ActorManager _actors; private readonly StateManager _state; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 3ef99d9..4a9dd95 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Network; using Glamourer.Events; +using Luna; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -12,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public unsafe class UpdateSlotService : IDisposable +public sealed unsafe class UpdateSlotService : IDisposable, IRequiredService { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; @@ -97,18 +98,18 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; - EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + EquipSlotUpdatingEvent.Invoke(new EquipSlotUpdating.Arguments(drawObject, slot, ref *data, ref returnValue)); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + return returnValue is ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; - BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + BonusSlotUpdatingEvent.Invoke(new BonusSlotUpdating.Arguments(drawObject, slot, ref *data, ref returnValue)); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + return returnValue is ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) @@ -120,7 +121,7 @@ public unsafe class UpdateSlotService : IDisposable { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); var drawObject = drawDataContainer->OwnerObject->DrawObject; - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject, drawObject); + GearsetDataLoadedEvent.Invoke(new GearsetDataLoaded.Arguments(drawDataContainer->OwnerObject, drawObject)); Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs index a6afd1d..c909486 100644 --- a/Glamourer/Interop/VieraEarService.cs +++ b/Glamourer/Interop/VieraEarService.cs @@ -2,12 +2,13 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public unsafe class VieraEarService : IDisposable +public unsafe sealed class VieraEarService : IDisposable, IRequiredService { private readonly PenumbraReloaded _penumbra; private readonly IGameInteropProvider _interop; @@ -57,10 +58,10 @@ public unsafe class VieraEarService : IDisposable private void SetupVieraEarDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - var originalOn = value != 0; + var originalOn = value is not 0; var on = originalOn; // Invoke an event that can change the requested value - Event.Invoke(actor, ref on); + Event.Invoke(new VieraEarStateChanged.Arguments(actor, ref on)); Glamourer.Log.Verbose( $"[SetVieraEarState] Invoked from game on 0x{actor.Address:X} switching to {on} (original {originalOn} from {value})."); diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 83262e4..b13287c 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -2,12 +2,13 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public class VisorService : IDisposable +public sealed class VisorService : IDisposable, IRequiredService { private readonly PenumbraReloaded _penumbra; private readonly IGameInteropProvider _interop; @@ -58,11 +59,11 @@ public class VisorService : IDisposable private void SetupVisorDetour(nint human, ushort modelId, byte value) { - var originalOn = value != 0; + var originalOn = value is not 0; var on = originalOn; // Invoke an event that can change the requested value // and also control whether the function should be called at all. - Event.Invoke(human, false, ref on); + Event.Invoke(new VisorStateChanged.Arguments(human, false, ref on)); Glamourer.Log.Verbose( $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn} from {value} with {modelId})."); diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 54f318b..a5e1ee1 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -2,13 +2,14 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public unsafe class WeaponService : IDisposable +public sealed unsafe class WeaponService : IDisposable, IRequiredService { private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); @@ -59,17 +60,17 @@ public unsafe class WeaponService : IDisposable var tmpWeapon = weapon; // First call the regular function. if (equipSlot is not EquipSlot.Unknown) - _event.Invoke(actor, equipSlot, ref tmpWeapon); + _event.Invoke(new WeaponLoading.Arguments(actor, equipSlot, ref tmpWeapon)); // Sage hack for weapons appearing in animations? // Check for weapon value 0 for certain cases (e.g. carbuncles transforming to humans) because that breaks some stuff (weapon hiding?) otherwise. - else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0) - _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); + else if (weaponValue == actor.GetMainhand().Value && weaponValue is not 0) + _event.Invoke(new WeaponLoading.Arguments(actor, EquipSlot.MainHand, ref tmpWeapon)); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4, unk5); if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.Skeleton.Id == 0) + if (tmpWeapon.Skeleton.Id is 0) tmpWeapon.Stains = StainIds.None; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4, unk5); } diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index 1103b59..fdd3f90 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -2,7 +2,7 @@ using Luna; namespace Glamourer.Services; -public class BackupService(Logger log, FilenameService provider) : BaseBackupService(log, provider) +public sealed class BackupService(Logger log, FilenameService provider) : BaseBackupService(log, provider) { /// Collect all relevant files for glamourer configuration. private static IReadOnlyList GlamourerFiles(FilenameService fileNames) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 750c335..0616fd4 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -1,10 +1,11 @@ using Glamourer.Config; using ImSharp; +using Luna; using Penumbra.GameData.Enums; namespace Glamourer.Services; -public class CodeService +public sealed class CodeService : IService { private readonly Configuration _config; private readonly SHA256 _hasher = SHA256.Create(); diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 164e4b1..b754fa5 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,12 +1,9 @@ -using Dalamud.Interface.ImGuiNotification; using Glamourer.Interop.Penumbra; using Luna; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; -using Extensions = OtterGui.Filesystem.Extensions; using Notification = Luna.Notification; namespace Glamourer.Services; @@ -31,7 +28,7 @@ public sealed class CollectionOverrideService : IService, ISavable if (!identifier.IsValid) identifier = _actors.FromObject(actor.AsObject, out _, true, true, true); - return ArrayExtensions.FindFirst(_overrides, p => p.Actor.Matches(identifier), out var ret) + return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret) ? (ret.CollectionId, ret.DisplayName, true) : (_penumbra.GetActorCollection(actor, out var name), name, false); } @@ -78,7 +75,7 @@ public sealed class CollectionOverrideService : IService, ISavable if (idx < 0 || idx >= _overrides.Count) return; - if (newCollectionId == Guid.Empty || newDisplayName.Length == 0) + if (newCollectionId == Guid.Empty || newDisplayName.Length is 0) return; var current = _overrides[idx]; @@ -106,7 +103,7 @@ public sealed class CollectionOverrideService : IService, ISavable public void MoveOverride(int idxFrom, int idxTo) { - if (!Extensions.Move(_overrides, idxFrom, idxTo)) + if (!_overrides.Move(idxFrom, idxTo)) return; Glamourer.Log.Debug($"Moved collection override {idxFrom + 1} to {idxTo + 1}."); @@ -192,7 +189,7 @@ public sealed class CollectionOverrideService : IService, ISavable public void Save(StreamWriter writer) { - var jObj = new JObject() + var jObj = new JObject { ["Version"] = Version, ["Overrides"] = SerializeOverrides(), diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 019424c..575ea6f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -16,7 +16,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using SeStringBuilderExtensions = OtterGui.Classes.SeStringBuilderExtensions; namespace Glamourer.Services; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 2fefe15..1c6dcf6 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -2,11 +2,12 @@ using Glamourer.Config; using Glamourer.Gui; using ImSharp; +using Luna; using Newtonsoft.Json.Linq; namespace Glamourer.Services; -public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) +public sealed class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) : IRequiredService { private Configuration _config = null!; private JObject _data = null!; diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs index 9849ad9..9ce09b0 100644 --- a/Glamourer/Services/DesignResolver.cs +++ b/Glamourer/Services/DesignResolver.cs @@ -142,11 +142,10 @@ public class DesignResolver( } else { - var lower = argument.ToLowerInvariant(); // Search for design by name and partial identifier. - design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(lower)); + design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(argument)); // Search for design by path, if nothing was found. - if (design is null && designFileSystem.Find(lower, out var child) && child is IFileSystemData leaf) + if (design is null && designFileSystem.Find(argument, out var child) && child is IFileSystemData leaf) design = leaf.Value; } @@ -159,13 +158,13 @@ public class DesignResolver( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Func MatchNameAndIdentifier(string lower) + private static Func MatchNameAndIdentifier(string text) { // Check for names and identifiers, prefer names - if (lower.Length > 3) - return d => d.Name.Lower == lower || d.Identifier.ToString().StartsWith(lower); + if (text.Length > 3) + return d => string.Equals(d.Name, text, StringComparison.OrdinalIgnoreCase) || d.Identifier.ToString().StartsWith(text, StringComparison.OrdinalIgnoreCase); // Check only for names. - return d => d.Name.Lower == lower; + return d => string.Equals(d.Name, text, StringComparison.OrdinalIgnoreCase); } } diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 831c6ad..e59d97c 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -4,7 +4,7 @@ using Luna; namespace Glamourer.Services; -public class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) +public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) { public readonly string MigrationDesignFileSystem = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json"); public readonly string MigrationDesignFile = Path.Combine(pi.ConfigDirectory.FullName, "Designs.json"); diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 4f0a83d..b81e1b1 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Glamourer.Config; using Lumina.Excel; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -10,7 +11,7 @@ using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Services; -public class ItemManager +public sealed class ItemManager : IService { public const string Nothing = EquipItem.Nothing; public const string SmallClothesNpc = "Smallclothes (NPC)"; diff --git a/Glamourer/Services/SaveService.cs b/Glamourer/Services/SaveService.cs index 1ccaaea..ac0ad8f 100644 --- a/Glamourer/Services/SaveService.cs +++ b/Glamourer/Services/SaveService.cs @@ -8,4 +8,4 @@ namespace Glamourer.Services; public interface ISavable : ISavable; public sealed class SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) - : BaseSaveService(log, framework, fileNames); + : BaseSaveService(log, framework, fileNames), IService; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 6b36d23..e06cdb4 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,37 +1,12 @@ using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Api.Api; -using Glamourer.Automation; -using Glamourer.Config; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Gui; -using Glamourer.Gui.Customization; -using Glamourer.Gui.Equipment; -using Glamourer.Gui.Tabs; -using Glamourer.Gui.Tabs.ActorTab; -using Glamourer.Gui.Tabs.AutomationTab; -using Glamourer.Gui.Tabs.DebugTab; -using Glamourer.Gui.Tabs.DesignTab; -using Glamourer.Gui.Tabs.NpcTab; -using Glamourer.Gui.Tabs.SettingsTab; -using Glamourer.Gui.Tabs.UnlocksTab; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; -using Glamourer.State; -using Glamourer.Unlocks; using Luna; using Microsoft.Extensions.DependencyInjection; -using OtterGui.Classes; -using OtterGui.Raii; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; -using Penumbra.GameData.DataContainers; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using FrameworkManager = OtterGui.Classes.FrameworkManager; -using Logger = OtterGui.Log.Logger; -using MessageService = Luna.MessageService; namespace Glamourer.Services; @@ -39,146 +14,19 @@ public static class StaticServiceManager { public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger log, Glamourer glamourer) { - EventWrapperBase.ChangeLogger(log); - var services = new ServiceManager(new Luna.Logger("Glamourer")) + var services = new ServiceManager(log) .AddExistingService(log) - .AddMeta() - .AddInterop() - .AddEvents() - .AddData() - .AddDesigns() - .AddState() - .AddUi() + .AddSingleton() + .AddSingleton() + .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddExistingService(glamourer); - DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); - services.AddIServices(typeof(ImRaii).Assembly); services.AddIServices(typeof(ServiceManager).Assembly); - services.AddIServices(typeof(Glamourer).Assembly); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(p => p.GetRequiredService()); + DalamudServices.AddServices(services, pi); + services.BuildProvider(); return services; } - - private static ServiceManager AddMeta(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddEvents(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddData(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddInterop(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddDesigns(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddState(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddUi(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); } diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 635584f..ba11c9a 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,41 +1,56 @@ using Dalamud.Interface; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using ImSharp; using Luna; -using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) - : TextureCache(dataManager, textureProvider), IDisposable +public sealed class TextureService(IUiBuilder uiBuilder, ITextureProvider textureProvider) + : IDisposable, IUiService { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); + public IDalamudTextureWrap? LoadIcon(uint iconId) + { + var icon = textureProvider.GetFromGameIcon(new GameIconLookup(iconId)); + if (!icon.TryGetWrap(out var wrap, out _)) + return null; + + return wrap; + } + + public bool TryLoadIcon(uint iconId, [NotNullWhen(true)] out IDalamudTextureWrap? wrap) + { + wrap = LoadIcon(iconId); + return wrap is not null; + } + public (ImTextureId, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) { - if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) + if (item.IconId.Id is not 0 && TryLoadIcon(item.IconId.Id, out var ret)) return (ret.Id, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); - return idx < 12 && _slotIcons[idx] != null + return idx < 12 && _slotIcons[idx] is not null ? (_slotIcons[idx]!.Id, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) : (default, Vector2.Zero, true); } public (ImTextureId, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) { - if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) + if (item.IconId.Id is not 0 && TryLoadIcon(item.IconId.Id, out var ret)) return (ret.Id, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); - if (idx == uint.MaxValue) + if (idx is uint.MaxValue) return (default, Vector2.Zero, true); idx += 12; - return idx < 13 && _slotIcons[idx] != null + return idx < 13 && _slotIcons[idx] is not null ? (_slotIcons[idx]!.Id, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) : (default, Vector2.Zero, true); } @@ -72,7 +87,7 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage SetIcon(EquipSlot.RFinger, 28); SetIcon(EquipSlot.MainHand, 17); SetIcon(EquipSlot.OffHand, 18); - Set(BonusItemFlag.Glasses.ToName(), (int) BonusItemFlag.Glasses.ToIndex() + 12, 55); + Set(BonusItemFlag.Glasses.ToName(), (int)BonusItemFlag.Glasses.ToIndex() + 12, 55); ret[EquipSlot.LFinger.ToIndex()] = ret[EquipSlot.RFinger.ToIndex()]; return ret; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 26b6610..e129376 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -14,7 +14,7 @@ using Luna; namespace Glamourer.State; -public unsafe class FunModule : IDisposable +public sealed unsafe class FunModule : IDisposable, IRequiredService { public enum FestivalType { diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index eb3403d..72520ec 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -5,6 +5,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; using ImSharp; +using Luna; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,7 +17,7 @@ public class InternalStateEditor( HumanModelList humans, ItemManager items, GPoseService gPose, - ICondition condition) + ICondition condition) : IService { /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. @@ -168,7 +169,7 @@ public class InternalStateEditor( public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, out StainIds oldStains, uint key = 0) { - oldItem = state.ModelData.Item(slot); + oldItem = state.ModelData.Item(slot); oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 9800445..75992f2 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -5,6 +5,7 @@ using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; +using Luna; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -17,22 +18,22 @@ namespace Glamourer.State; /// It handles applying those changes as well as redrawing the actor if necessary. /// public class StateApplier( - UpdateSlotService _updateSlot, - VisorService _visor, - WeaponService _weapon, - ChangeCustomizeService _changeCustomize, - ItemManager _items, - PenumbraService _penumbra, - MetaService _metaService, - ActorObjectManager _objects, - CrestService _crests, - DirectXService _directX) + UpdateSlotService updateSlot, + VisorService visor, + WeaponService weaponService, + ChangeCustomizeService changeCustomize, + ItemManager items, + PenumbraService penumbra, + MetaService metaService, + ActorObjectManager objects, + CrestService crests, + DirectXService directX) : IRequiredService { /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) { foreach (var actor in data.Objects) - _penumbra.RedrawObject(actor, RedrawType.Redraw); + penumbra.RedrawObject(actor, RedrawType.Redraw); } /// @@ -60,17 +61,17 @@ public class StateApplier( var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize); if (!flags.RequiresRedraw() || !mdl.IsHuman) { - _changeCustomize.UpdateCustomize(mdl, customize); + changeCustomize.UpdateCustomize(mdl, customize); } - else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene) + else if (data.Objects.Count > 1 && objects.IsInGPose && !actor.IsGPoseOrCutscene) { var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize; *mdlCustomize = customize; - _penumbra.RedrawObject(actor, RedrawType.AfterGPose); + penumbra.RedrawObject(actor, RedrawType.AfterGPose); } else { - _penumbra.RedrawObject(actor, RedrawType.Redraw); + penumbra.RedrawObject(actor, RedrawType.Redraw); } } } @@ -104,12 +105,12 @@ public class StateApplier( if (checkRestrictions) { var customize = mdl.GetCustomize(); - var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); - _updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem); + var (_, resolvedItem) = items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); + updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem); } else { - _updateSlot.UpdateEquipSlot(actor.Model, slot, armor); + updateSlot.UpdateEquipSlot(actor.Model, slot, armor); } } } @@ -134,7 +135,7 @@ public class StateApplier( if (!mdl.IsHuman) continue; - _updateSlot.UpdateBonusSlot(actor.Model, slot, item); + updateSlot.UpdateBonusSlot(actor.Model, slot, item); } } @@ -164,15 +165,15 @@ public class StateApplier( { case < 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _updateSlot.UpdateStain(actor.Model, slot, stains); + updateSlot.UpdateStain(actor.Model, slot, stains); break; case 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.MainHand, stains); + weaponService.LoadStain(actor, EquipSlot.MainHand, stains); break; case 11: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.OffHand, stains); + weaponService.LoadStain(actor, EquipSlot.OffHand, stains); break; } } @@ -217,7 +218,7 @@ public class StateApplier( { var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); + weaponService.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); } /// Apply a weapon to the offhand. @@ -225,7 +226,7 @@ public class StateApplier( { stains = weapon.PrimaryId.Id == 0 ? StainIds.None : stains; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); + weaponService.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); } /// Change a meta state. @@ -242,24 +243,24 @@ public class StateApplier( case MetaIndex.HatState: { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetHatState(actor, value); + metaService.SetHatState(actor, value); return; } case MetaIndex.WeaponState: { // Only apply to the GPose character because otherwise we get some weird incompatibility when leaving GPose. - if (_objects.IsInGPose) + if (objects.IsInGPose) foreach (var actor in data.Objects.Where(a => a.IsGPoseOrCutscene)) - _metaService.SetWeaponState(actor, value); + metaService.SetWeaponState(actor, value); else foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetWeaponState(actor, value); + metaService.SetWeaponState(actor, value); return; } case MetaIndex.VisorState: { foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _visor.SetVisorState(actor.Model, value); + visor.SetVisorState(actor.Model, value); return; } case MetaIndex.EarState: @@ -286,7 +287,7 @@ public class StateApplier( public void ChangeCrests(ActorData data, CrestFlag flags) { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _crests.UpdateCrests(actor, flags); + crests.UpdateCrests(actor, flags); } /// @@ -337,7 +338,7 @@ public class StateApplier( value.Model.Apply(ref baseTable[MaterialValueIndex.FromKey(index).RowIndex], mode); } - _directX.ReplaceColorTable(texture, baseTable); + directX.ReplaceColorTable(texture, baseTable); } } @@ -370,7 +371,7 @@ public class StateApplier( foreach (var (key, value) in values) value.Model.Apply(ref table[key.RowIndex], mode); - _directX.ReplaceColorTable(texture, table); + directX.ReplaceColorTable(texture, table); } } } @@ -421,5 +422,5 @@ public class StateApplier( } public ActorData GetData(ActorState state) - => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; + => objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 1bfb66a..a325f15 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -43,8 +43,8 @@ public class StateEditor( var actors = Applier.ForceRedraw(state, source.RequiresChange()); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateFinalized.Invoke(StateFinalizationType.ModelChange, actors); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Model, source, state, actors)); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.ModelChange, actors)); } /// @@ -57,7 +57,7 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {idx.ToName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value))); } /// @@ -70,8 +70,8 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, - new EntireCustomizeTransaction(applied, old, customizeInput)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.EntireCustomize, settings.Source, state, actors, + new EntireCustomizeTransaction(applied, old, customizeInput))); } /// @@ -95,21 +95,21 @@ public class StateEditor( if (type is StateChangeType.Equip) { - StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, new EquipTransaction(slot, old, item))); } else if (slot is EquipSlot.MainHand) { var oldOff = state.ModelData.Item(EquipSlot.OffHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(old, oldOff, oldGauntlets, item, oldOff, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item, oldOff, oldGauntlets))); } else { var oldMain = state.ModelData.Item(EquipSlot.MainHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item, oldGauntlets))); } } @@ -122,7 +122,7 @@ public class StateEditor( var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item))); } /// @@ -157,24 +157,24 @@ public class StateEditor( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { - StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value))); } else if (slot is EquipSlot.MainHand) { var oldOff = state.ModelData.Item(EquipSlot.OffHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(old, oldOff, oldGauntlets, item!.Value, oldOff, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item!.Value, oldOff, oldGauntlets))); } else { var oldMain = state.ModelData.Item(EquipSlot.MainHand); var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); - StateChanged.Invoke(type, settings.Source, state, actors, - new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item!.Value, oldGauntlets)); + StateChanged.Invoke(new StateChanged.Arguments(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item!.Value, oldGauntlets))); } - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, oldStains, stains!.Value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, oldStains, stains!.Value))); } /// @@ -187,7 +187,7 @@ public class StateEditor( var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains))); } /// @@ -200,7 +200,7 @@ public class StateEditor( var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Crest, settings.Source, state, actors, new CrestTransaction(slot, old, crest)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Crest, settings.Source, state, actors, new CrestTransaction(slot, old, crest))); } /// @@ -218,7 +218,7 @@ public class StateEditor( var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {flag} in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, new ParameterTransaction(flag, old, @new)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Parameter, settings.Source, state, actors, new ParameterTransaction(flag, old, @new))); } public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings) @@ -230,8 +230,8 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, - new MaterialTransaction(index, oldValue, newValue.Game)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.MaterialValue, settings.Source, state, actors, + new MaterialTransaction(index, oldValue, newValue.Game))); } public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) @@ -243,7 +243,7 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, true); Glamourer.Log.Verbose( $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null))); } /// @@ -256,7 +256,7 @@ public class StateEditor( var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value))); } /// @@ -421,9 +421,9 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors)); // FIXME: maybe later if (settings.IsFinal) - StateFinalized.Invoke(StateFinalizationType.DesignApplied, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.DesignApplied, actors)); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 6856d4e..0991e62 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -16,6 +16,7 @@ using Glamourer.Designs; using Penumbra.GameData.Interop; using Glamourer.Api.Enums; using Glamourer.Config; +using Luna; namespace Glamourer.State; @@ -24,7 +25,7 @@ namespace Glamourer.State; /// it always updates the base state for existing states, /// and either discards the changes or updates the model state too. /// -public class StateListener : IDisposable +public sealed class StateListener : IDisposable, IRequiredService { private readonly Configuration _config; private readonly ActorManager _actors; @@ -156,12 +157,12 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private void OnCustomizeChange(Model model, ref CustomizeArray customize) + private void OnCustomizeChange(in ChangeCustomizeService.Arguments arguments) { - if (!model.IsHuman) + if (!arguments.Model.IsHuman) return; - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -169,7 +170,7 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out _customizeState)) return; - UpdateCustomize(actor, _customizeState, ref customize, false); + UpdateCustomize(actor, _customizeState, ref arguments.Customize, false); } private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) @@ -215,9 +216,9 @@ public class StateListener : IDisposable /// A draw model loads a new equipment piece. /// Update base data, apply or update model data, and protect against restricted gear. /// - private void OnEquipSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) + private void OnEquipSlotUpdating(in EquipSlotUpdating.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -228,66 +229,66 @@ public class StateListener : IDisposable if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { - HandleEquipSlot(actor, state, slot, ref armor); - locked = state.Sources[slot, false] is StateSource.IpcFixed; + HandleEquipSlot(actor, state, arguments.Slot, ref arguments.Armor); + locked = state.Sources[arguments.Slot, false] is StateSource.IpcFixed; } - _funModule.ApplyFunToSlot(actor, ref armor, slot); + _funModule.ApplyFunToSlot(actor, ref arguments.Armor, arguments.Slot); if (!_config.UseRestrictedGearProtection || locked) return; - var customize = model.GetCustomize(); - (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); + var customize = arguments.Model.GetCustomize(); + (_, arguments.Armor) = _items.RestrictedGear.ResolveRestricted(arguments.Armor, arguments.Slot, customize.Race, customize.Gender); } - private void OnBonusSlotUpdating(Model model, BonusItemFlag slot, ref CharacterArmor item, ref ulong returnValue) + private void OnBonusSlotUpdating(in BonusSlotUpdating.Arguments arguments) { - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) - switch (UpdateBaseData(actor, state, slot, item)) + switch (UpdateBaseData(actor, state, arguments.Slot, arguments.Armor)) { // Base data changed equipment while actors were not there. // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (!state.Sources[slot].IsFixed()) - _manager.ChangeBonusItem(state, slot, state.BaseData.BonusItem(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot].IsFixed()) + _manager.ChangeBonusItem(state, arguments.Slot, state.BaseData.BonusItem(arguments.Slot), ApplySettings.Game); else apply = true; if (apply) - item = state.ModelData.BonusItem(slot).Armor(); + arguments.Armor = state.ModelData.BonusItem(arguments.Slot).Armor(); break; // Use current model data. - case UpdateState.NoChange: item = state.ModelData.BonusItem(slot).Armor(); break; + case UpdateState.NoChange: arguments.Armor = state.ModelData.BonusItem(arguments.Slot).Armor(); break; case UpdateState.Transformed: break; } } - private void OnGearsetDataLoaded(Actor actor, Model model) + private void OnGearsetDataLoaded(in GearsetDataLoaded.Arguments arguments) { - if (!actor.Valid || _condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (!arguments.Actor.Valid || _condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // ensure actor and state are valid. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); + _stateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Gearset, actors)); } - private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) + private void OnMovedEquipment(in MovedEquipment.Arguments arguments) { var (identifier, objects) = _objects.PlayerData; if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var state)) return; - foreach (var (slot, item, stain) in items) + foreach (var (slot, item, stain) in arguments.Items) { var currentItem = state.BaseData.Item(slot); var model = slot is EquipSlot.MainHand or EquipSlot.OffHand @@ -336,75 +337,75 @@ public class StateListener : IDisposable /// Update base data, apply or update model data. /// Verify consistent weapon types. /// - private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) + private void OnWeaponLoading(in WeaponLoading.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand - && weapon.Variant == 0 - && weapon.Weapon.Id != 0 - && _fistOffhands.TryGetValue(actor, out var lastFistOffhand)) + if (arguments.Slot is EquipSlot.OffHand + && arguments.Weapon.Variant.Id is 0 + && arguments.Weapon.Weapon.Id is not 0 + && _fistOffhands.TryGetValue(arguments.Actor, out var lastFistOffhand)) { - Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); - weapon = lastFistOffhand; + Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{arguments.Actor.Address:X}."); + arguments.Weapon = lastFistOffhand; } - if (!actor.Identifier(_actors, out var identifier) + if (!arguments.Actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; var apply = false; - switch (UpdateBaseData(actor, state, slot, weapon)) + switch (UpdateBaseData(arguments.Actor, state, arguments.Slot, arguments.Weapon)) { // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (!state.Sources[slot, false].IsFixed()) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot, false].IsFixed()) + _manager.ChangeItem(state, arguments.Slot, state.BaseData.Item(arguments.Slot), ApplySettings.Game); else apply = true; - if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot, true].IsFixed()) + _manager.ChangeStains(state, arguments.Slot, state.BaseData.Stain(arguments.Slot), ApplySettings.Game); else apply = true; break; case UpdateState.NoChange: apply = true; break; } - var baseType = slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; - var modelType = state.ModelData.Item(slot).Type; + var baseType = arguments.Slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; + var modelType = state.ModelData.Item(arguments.Slot).Type; if (apply) { // Only allow overwriting identical weapons var canApply = baseType == modelType - || _gPose.InGPose && actor.IsGPoseOrCutscene; - var newWeapon = state.ModelData.Weapon(slot); + || _gPose.InGPose && arguments.Actor.IsGPoseOrCutscene; + var newWeapon = state.ModelData.Weapon(arguments.Slot); if (canApply) { - weapon = newWeapon; + arguments.Weapon = newWeapon; } else { - if (weapon.Skeleton.Id != 0) - weapon = weapon.With(newWeapon.Stains); + if (arguments.Weapon.Skeleton.Id is not 0) + arguments.Weapon = arguments.Weapon.With(newWeapon.Stains); // Force unlock if necessary. - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); + _manager.ChangeItem(state, arguments.Slot, state.BaseData.Item(arguments.Slot), ApplySettings.Game with { Key = state.Combination }); } } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) + if (arguments.Slot is EquipSlot.MainHand && arguments.Weapon.Skeleton.Id is > 1600 and < 1651) { - lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, - weapon.Stains); - _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + lastFistOffhand = new CharacterWeapon((PrimaryId)(arguments.Weapon.Skeleton.Id + 50), arguments.Weapon.Weapon, arguments.Weapon.Variant, + arguments.Weapon.Stains); + _fistOffhands[arguments.Actor] = lastFistOffhand; + Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{arguments.Actor.Address:X}."); } - _funModule.ApplyFunToWeapon(actor, ref weapon, slot); + _funModule.ApplyFunToWeapon(arguments.Actor, ref arguments.Weapon, arguments.Slot); } /// Update base data for a single changed equipment slot. @@ -527,26 +528,26 @@ public class StateListener : IDisposable } } - private void OnCrestChange(Actor actor, CrestFlag slot, ref bool value) + private void OnCrestChange(in CrestService.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors, out var identifier) + if (!arguments.Actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; - switch (UpdateBaseCrest(actor, state, slot, value)) + switch (UpdateBaseCrest(arguments.Actor, state, arguments.Slot, arguments.Value)) { case UpdateState.Change: - if (!state.Sources[slot].IsFixed()) - _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game); + if (!state.Sources[arguments.Slot].IsFixed()) + _manager.ChangeCrest(state, arguments.Slot, state.BaseData.Crest(arguments.Slot), ApplySettings.Game); else - value = state.ModelData.Crest(slot); + arguments.Value = state.ModelData.Crest(arguments.Slot); break; case UpdateState.NoChange: case UpdateState.HatHack: - value = state.ModelData.Crest(slot); + arguments.Value = state.ModelData.Crest(arguments.Slot); break; case UpdateState.Transformed: break; } @@ -675,7 +676,7 @@ public class StateListener : IDisposable } /// Handle visor state changes made by the game. - private unsafe void OnVisorChange(Model model, bool game, ref bool value) + private unsafe void OnVisorChange(in VisorStateChanged.Arguments arguments) { // Skip updates when in customize update. if (ChangeCustomizeService.InUpdate.InMethod) @@ -684,14 +685,14 @@ public class StateListener : IDisposable // Find appropriate actor and state. // We do not need to handle fixed designs, // since a fixed design would already have established state-tracking. - var actor = _penumbra.GameObjectFromDrawObject(model); + var actor = _penumbra.GameObjectFromDrawObject(arguments.Model); if (!actor.IsCharacter) return; // Only actually change anything if the actor state changed, // when equipping headgear the method is called with the current draw object state, // which corrupts Glamourer's assumed game state otherwise. - if (!game && actor.AsCharacter->DrawData.IsVisorToggled != value) + if (!arguments.NewVisorState && actor.AsCharacter->DrawData.IsVisorToggled != arguments.Value) return; if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -704,24 +705,24 @@ public class StateListener : IDisposable return; // Update visor base state. - if (state.BaseData.SetVisor(value)) + if (state.BaseData.SetVisor(arguments.Value)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.VisorState].IsFixed()) - value = state.ModelData.IsVisorToggled(); + arguments.Value = state.ModelData.IsVisorToggled(); else - _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.VisorState, arguments.Value, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsVisorToggled(); + arguments.Value = state.ModelData.IsVisorToggled(); } } /// Handle visor state changes made by the game. - private void OnVieraEarChange(Actor actor, ref bool value) + private void OnVieraEarChange(in VieraEarStateChanged.Arguments arguments) { // Value is inverted compared to our own handling. @@ -729,98 +730,98 @@ public class StateListener : IDisposable if (ChangeCustomizeService.InUpdate.InMethod) return; - if (!actor.IsCharacter) + if (!arguments.Actor.IsCharacter) return; - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update visor base state. - if (state.BaseData.SetEarsVisible(!value)) + if (state.BaseData.SetEarsVisible(!arguments.State)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.EarState].IsFixed()) - value = !state.ModelData.AreEarsVisible(); + arguments.State = !state.ModelData.AreEarsVisible(); else - _manager.ChangeMetaState(state, MetaIndex.EarState, !value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.EarState, !arguments.State, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = !state.ModelData.AreEarsVisible(); + arguments.State = !state.ModelData.AreEarsVisible(); } } /// Handle Hat Visibility changes. These act on the game object. - private void OnHeadGearVisibilityChange(Actor actor, ref bool value) + private void OnHeadGearVisibilityChange(in HeadGearVisibilityChanged.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Find appropriate state. // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update hat visibility state. - if (state.BaseData.SetHatVisible(value)) + if (state.BaseData.SetHatVisible(arguments.Visible)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.HatState].IsFixed()) - value = state.ModelData.IsHatVisible(); + arguments.Visible = state.ModelData.IsHatVisible(); else - _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.HatState, arguments.Visible, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsHatVisible(); + arguments.Visible = state.ModelData.IsHatVisible(); } } /// Handle Weapon Visibility changes. These act on the game object. - private void OnWeaponVisibilityChange(Actor actor, ref bool value) + private void OnWeaponVisibilityChange(in WeaponVisibilityChanged.Arguments arguments) { - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (_condition[ConditionFlag.CreatingCharacter] && arguments.Actor.Index >= ObjectIndex.CutsceneStart) return; // Find appropriate state. // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors, out var identifier)) + if (!arguments.Actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) return; // Update weapon visibility state. - if (state.BaseData.SetWeaponVisible(value)) + if (state.BaseData.SetWeaponVisible(arguments.Value)) { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state.Sources[MetaIndex.WeaponState].IsFixed()) - value = state.ModelData.IsWeaponVisible(); + arguments.Value = state.ModelData.IsWeaponVisible(); else - _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); + _manager.ChangeMetaState(state, MetaIndex.WeaponState, arguments.Value, ApplySettings.Game); } else { // if base state did not change, overwrite the value with the model state one. - value = state.ModelData.IsWeaponVisible(); + arguments.Value = state.ModelData.IsWeaponVisible(); } } @@ -894,9 +895,9 @@ public class StateListener : IDisposable ApplyParameters(_creatingState, drawObject); } - private void OnCustomizeChanged(Model model) + private void OnCustomizeChanged(in Model model) { - if (_customizeState == null) + if (_customizeState is null) { var actor = _penumbra.GameObjectFromDrawObject(model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index b767f48..df2029b 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -11,6 +11,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using ImSharp; +using Luna; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -34,7 +35,7 @@ public sealed class StateManager( ModSettingApplier modApplier, GPoseService gPose) : StateEditor(editor, applier, changeEvent, finalizeEvent, jobChange, config, items, merger, modApplier, gPose), - IReadOnlyDictionary + IReadOnlyDictionary, IService { private readonly Dictionary _states = []; @@ -279,10 +280,10 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // only invoke if we define this reset call as the final call in our state update. if (isFinal) - StateFinalized.Invoke(StateFinalizationType.Revert, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Revert, objects)); } public void ResetAdvancedDyes(ActorState state, StateSource source, uint key = 0) @@ -304,9 +305,9 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset advanced dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, objects)); } public void ResetAdvancedCustomizations(ActorState state, StateSource source, uint key = 0) @@ -327,9 +328,9 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, objects)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, objects)); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -342,21 +343,21 @@ public sealed class StateManager( foreach (var flag in CustomizeParameterExtensions.AllFlags) state.Sources[flag] = StateSource.Game; - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) { - actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + data = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); foreach (var (idx, mat) in state.Materials.Values) - Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game); + Applier.ChangeMaterialValue(state, data, MaterialValueIndex.FromKey(idx), mat.Game); } state.Materials.Clear(); Glamourer.Log.Verbose( - $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reset, source, state, data)); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertAdvanced, data)); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -369,13 +370,13 @@ public sealed class StateManager( state.ModelData.ModelId = state.BaseData.ModelId; state.ModelData.Customize = state.BaseData.Customize; - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) - actors = Applier.ChangeCustomize(state, true); + data = Applier.ChangeCustomize(state, true); Glamourer.Log.Verbose( - $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertCustomize, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertCustomize, data)); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -401,32 +402,32 @@ public sealed class StateManager( state.ModelData.SetBonusItem(slot, state.BaseData.BonusItem(slot)); } - var actors = ActorData.Invalid; + var data = ActorData.Invalid; if (source is not StateSource.Game) { - actors = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); + data = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) { - Applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), + Applier.ChangeArmor(data, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); } foreach (var slot in BonusExtensions.AllFlags) { var item = state.ModelData.BonusItem(slot); - Applier.ChangeBonusItem(actors, slot, item.PrimaryId, item.Variant); + Applier.ChangeBonusItem(data, slot, item.PrimaryId, item.Variant); } - var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; + var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? data.OnlyGPose() : data; Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); - var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; + var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? data.OnlyGPose() : data; Applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); } Glamourer.Log.Verbose( - $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {data.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateFinalized.Invoke(StateFinalizationType.RevertEquipment, actors); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.RevertEquipment, data)); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -517,9 +518,9 @@ public sealed class StateManager( forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reapply, source, state, data)); if (isFinal) - StateFinalized.Invoke(StateFinalizationType.Reapply, data); + StateFinalized.Invoke(new StateFinalized.Arguments(StateFinalizationType.Reapply, data)); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -538,9 +539,9 @@ public sealed class StateManager( forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + StateChanged.Invoke(new StateChanged.Arguments(StateChangeType.Reapply, source, state, data)); // invoke the automation update based on what reset is. - StateFinalized.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); + StateFinalized.Invoke(new StateFinalized.Arguments(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data)); } public void DeleteState(ActorIdentifier identifier) diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 71e99b3..66d2d67 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -7,6 +7,7 @@ using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -14,7 +15,7 @@ using StringU8 = ImSharp.StringU8; namespace Glamourer.Unlocks; -public class CustomizeUnlockManager : IDisposable, ISavable +public sealed class CustomizeUnlockManager : IDisposable, ISavable, IRequiredService { private readonly SaveService _saveService; private readonly IClientState _clientState; @@ -78,7 +79,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable _unlocked.TryAdd(pair.Data, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); time = DateTimeOffset.UtcNow; - _event.Invoke(ObjectUnlocked.Type.Customization, pair.Data, time); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, pair.Data, time)); Save(); return true; } @@ -87,7 +88,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable public unsafe bool IsUnlockedGame(uint dataId) { var instance = UIState.Instance(); - if (instance == null) + if (instance is null) return false; return UIState.Instance()->IsUnlockLinkUnlocked(dataId); @@ -101,7 +102,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable Glamourer.Log.Debug("[UnlockManager] Scanning for new unlocked customizations."); var instance = UIState.Instance(); - if (instance == null) + if (instance is null) return; try @@ -112,7 +113,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable { if (instance->IsUnlockLinkUnlocked(id) && _unlocked.TryAdd(id, time)) { - _event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time)); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time))); ++count; } } @@ -148,7 +149,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable if (id != data || !_unlocked.TryAdd(id, time)) continue; - _event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time)); + _event.Invoke(new ObjectUnlocked.Arguments(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time))); Save(); break; } diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 1f6f3f5..87ce720 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Unlocks; -public sealed class FavoriteManager : ISavable +public sealed class FavoriteManager : ISavable, IService { public readonly record struct FavoriteHairStyle(Gender Gender, SubRace Race, CustomizeIndex Type, CustomizeValue Id) { diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 6f8e097..fbc35c9 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.Sheets; +using Luna; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -11,13 +12,13 @@ using Cabinet = Lumina.Excel.Sheets.Cabinet; namespace Glamourer.Unlocks; -public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary +public sealed class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary, IService { - private readonly SaveService _saveService; - private readonly ItemManager _items; - private readonly IClientState _clientState; - private readonly IFramework _framework; - private readonly ObjectUnlocked _event; + private readonly SaveService _saveService; + private readonly ItemManager _items; + private readonly IClientState _clientState; + private readonly IFramework _framework; + private readonly ObjectUnlocked _event; private readonly ObjectIdentification _identifier; private readonly Dictionary _unlocked = new(); @@ -100,12 +101,13 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary= (uint) _items.ItemSheet.Count) + if (itemId.Id >= (uint)_items.ItemSheet.Count) { time = DateTimeOffset.MinValue; return true; @@ -210,7 +212,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary