diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 6aaa11c..f7607b5 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Configuration; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; @@ -18,25 +19,30 @@ namespace Glamourer; public class Configuration : IPluginConfiguration, ISavable { - public bool Enabled { get; set; } = true; - public bool UseRestrictedGearProtection { get; set; } = false; - public bool OpenFoldersByDefault { get; set; } = false; - public bool AutoRedrawEquipOnChanges { get; set; } = false; - public bool EnableAutoDesigns { get; set; } = true; - public bool IncognitoMode { get; set; } = false; - public bool UnlockDetailMode { get; set; } = true; - public bool HideApplyCheckmarks { get; set; } = false; - public bool SmallEquip { get; set; } = false; - public bool UnlockedItemMode { get; set; } = false; - public byte DisableFestivals { get; set; } = 1; - public bool EnableGameContextMenu { get; set; } = true; - public bool HideWindowInCutscene { get; set; } = false; - public bool ShowAutomationSetEditing { get; set; } = true; - public bool ShowAllAutomatedApplicationRules { get; set; } = true; - public bool ShowUnlockedItemWarnings { get; set; } = true; - public bool RevertManualChangesOnZoneChange { get; set; } = false; - public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; - public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); + public bool Enabled { get; set; } = true; + public bool UseRestrictedGearProtection { get; set; } = false; + public bool OpenFoldersByDefault { get; set; } = false; + public bool AutoRedrawEquipOnChanges { get; set; } = false; + public bool EnableAutoDesigns { get; set; } = true; + public bool IncognitoMode { get; set; } = false; + public bool UnlockDetailMode { get; set; } = true; + public bool HideApplyCheckmarks { get; set; } = false; + public bool SmallEquip { get; set; } = false; + public bool UnlockedItemMode { get; set; } = false; + public byte DisableFestivals { get; set; } = 1; + public bool EnableGameContextMenu { get; set; } = true; + public bool HideWindowInCutscene { get; set; } = false; + public bool ShowAutomationSetEditing { get; set; } = true; + public bool ShowAllAutomatedApplicationRules { get; set; } = true; + public bool ShowUnlockedItemWarnings { get; set; } = true; + public bool RevertManualChangesOnZoneChange { get; set; } = false; + public bool ShowDesignQuickBar { get; set; } = false; + public bool LockDesignQuickBar { get; set; } = false; + public bool ShowQuickBarInTabs { get; set; } = true; + + public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.D, ModifierHotkey.Control, ModifierHotkey.Shift); + public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index c06eb53..9c8e189 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Designs; +using Glamourer.Gui; using OtterGui.Classes; namespace Glamourer.Events; @@ -76,14 +77,17 @@ public sealed class DesignChanged : EventWrapper + AutoDesignManager = 1, + /// DesignFileSystem = 0, /// DesignFileSystemSelector = -1, - /// - AutoDesignManager = 1, + /// + DesignCombo = -2, } public DesignChanged() diff --git a/Glamourer/Gui/ConvenienceRevertButtons.cs b/Glamourer/Gui/ConvenienceRevertButtons.cs deleted file mode 100644 index bf8bd11..0000000 --- a/Glamourer/Gui/ConvenienceRevertButtons.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Numerics; -using Dalamud.Interface; -using Glamourer.Automation; -using Glamourer.Events; -using Glamourer.Interop; -using Glamourer.State; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui; - -public class ConvenienceRevertButtons -{ - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ObjectManager _objects; - private readonly Configuration _config; - - - public ConvenienceRevertButtons(StateManager stateManager, AutoDesignApplier autoDesignApplier, ObjectManager objects, - Configuration config) - { - _stateManager = stateManager; - _autoDesignApplier = autoDesignApplier; - _objects = objects; - _config = config; - } - - public void DrawButtons(float yPos) - { - _objects.Update(); - var (playerIdentifier, playerData) = _objects.PlayerData; - - string? error = null; - if (!playerIdentifier.IsValid || !playerData.Valid) - error = "No player character available."; - - if (!_stateManager.TryGetValue(playerIdentifier, out var state)) - error = "The player character was not modified by Glamourer yet."; - else if (state.IsLocked) - error = "The state of the player character is currently locked."; - - var buttonSize = new Vector2(ImGui.GetFrameHeight()); - var spacing = ImGui.GetStyle().ItemInnerSpacing; - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - 2 * buttonSize.X - spacing.X, yPos - 1)); - - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), buttonSize, - error ?? "Revert the player character to its game state.", error != null, true)) - _stateManager.ResetState(state, StateChanged.Source.Manual); - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.SyncAlt.ToIconString(), buttonSize, - error ?? "Revert the player character to its automation state.", error != null && _config.EnableAutoDesigns, true)) - foreach (var actor in playerData.Objects) - { - _autoDesignApplier.ReapplyAutomation(actor, playerIdentifier, state); - _stateManager.ReapplyState(actor); - } - } -} diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs new file mode 100644 index 0000000..a60a69b --- /dev/null +++ b/Glamourer/Gui/DesignCombo.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Interface.Utility; +using Glamourer.Automation; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Log; +using OtterGui.Widgets; + +namespace Glamourer.Gui; + +public abstract class DesignComboBase : FilterComboCache>, IDisposable +{ + private readonly Configuration _config; + private readonly DesignChanged _designChanged; + protected readonly TabSelected TabSelected; + protected float InnerWidth; + + protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, + TabSelected tabSelected, Configuration config) + : base(generator, log) + { + _designChanged = designChanged; + TabSelected = tabSelected; + _config = config; + _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignCombo); + } + + public bool Incognito + => _config.IncognitoMode; + + void IDisposable.Dispose() + => _designChanged.Unsubscribe(OnDesignChange); + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var ret = base.DrawSelectable(globalIdx, selected); + var (design, path) = Items[globalIdx]; + if (path.Length > 0 && design.Name != path) + { + var start = ImGui.GetItemRectMin(); + var pos = start.X + ImGui.CalcTextSize(design.Name).X; + var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; + var remainingSpace = maxSize - pos; + var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; + var offset = remainingSpace - requiredSize; + if (ImGui.GetScrollMaxY() == 0) + offset -= ImGui.GetStyle().ItemInnerSpacing.X; + + if (offset < ImGui.GetStyle().ItemSpacing.X) + ImGuiUtil.HoverTooltip(path); + else + ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, + ImGui.GetColorU32(ImGuiCol.TextDisabled), path); + } + + return ret; + } + + protected bool Draw(Design? currentDesign, string? label, float width) + { + InnerWidth = 400 * ImGuiHelpers.GlobalScale; + CurrentSelectionIdx = Math.Max(Items.IndexOf(p => currentDesign == p.Item1), 0); + CurrentSelection = Items[CurrentSelectionIdx]; + var name = label ?? "Select Design Here..."; + var ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) + && CurrentSelection != null; + + if (currentDesign != null) + { + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) + TabSelected.Invoke(MainWindow.TabType.Designs, currentDesign); + ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); + } + + return ret; + } + + protected override string ToString(Tuple obj) + => obj.Item1.Name.Text; + + protected override float GetFilterWidth() + => InnerWidth - 2 * ImGui.GetStyle().FramePadding.X; + + protected override bool IsVisible(int globalIndex, LowerString filter) + { + var (design, path) = Items[globalIndex]; + return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower); + } + + private void OnDesignChange(DesignChanged.Type type, Design design, object? data = null) + { + switch (type) + { + case DesignChanged.Type.Created: + case DesignChanged.Type.Renamed: + Cleanup(); + break; + case DesignChanged.Type.Deleted: + Cleanup(); + if (CurrentSelection?.Item1 == design) + { + CurrentSelectionIdx = -1; + CurrentSelection = null; + } + + break; + } + } +} + +public sealed class DesignCombo : DesignComboBase +{ + public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, + Configuration config) + : base( + () => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .ToList(), log, designChanged, tabSelected, config) + { } + + public Design? Design + => CurrentSelection?.Item1; + + public void Draw(float width) + => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); +} + +public sealed class RevertDesignCombo : DesignComboBase, IDisposable +{ + public const int RevertDesignIndex = -1228; + public readonly Design RevertDesign; + private readonly AutoDesignManager _autoDesignManager; + + public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, + Configuration config) + : this(designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) + { } + + private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) + : base(() => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .Prepend(new Tuple(revertDesign, string.Empty)) + .ToList(), log, designChanged, tabSelected, config) + { + RevertDesign = revertDesign; + _autoDesignManager = autoDesignManager; + } + + + public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex) + { + if (!Draw(design?.Design, design?.Name(Incognito), ImGui.GetContentRegionAvail().X)) + return; + + if (autoDesignIndex >= 0) + _autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + else + _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + } + + private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) + => new(customize, items) + { + Index = RevertDesignIndex, + Name = AutoDesign.RevertName, + ApplyCustomize = CustomizeFlagExtensions.AllRelevant, + }; +} diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs new file mode 100644 index 0000000..fd9976f --- /dev/null +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -0,0 +1,255 @@ +using System; +using System.Numerics; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Services; +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using Penumbra.GameData.Actors; + +namespace Glamourer.Gui; + +public class DesignQuickBar : Window, IDisposable +{ + private ImGuiWindowFlags GetFlags + => _config.LockDesignQuickBar + ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoBackground + : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; + + private readonly Configuration _config; + private readonly DesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ObjectManager _objects; + private readonly IKeyState _keyState; + private readonly ImRaii.Style _windowPadding = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + + public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, + ObjectManager objects, AutoDesignApplier autoDesignApplier) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration) + { + _config = config; + _designCombo = designCombo; + _stateManager = stateManager; + _keyState = keyState; + _objects = objects; + _autoDesignApplier = autoDesignApplier; + IsOpen = _config.ShowDesignQuickBar; + DisableWindowSounds = true; + Size = Vector2.Zero; + } + + public void Dispose() + => _windowPadding.Dispose(); + + public override void PreOpenCheck() + { + CheckHotkeys(); + IsOpen = _config.ShowDesignQuickBar; + } + + public override void PreDraw() + { + Flags = GetFlags; + Size = new Vector2(12 * ImGui.GetFrameHeight(), ImGui.GetFrameHeight()); + + _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)); + } + + public override void PostDraw() + => _windowPadding.Dispose(); + + + public override void Draw() + => Draw(ImGui.GetContentRegionAvail().X); + + private void Draw(float width) + { + _objects.Update(); + using var group = ImRaii.Group(); + var spacing = ImGui.GetStyle().ItemInnerSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); + var contentRegion = width; + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + var comboSize = contentRegion - 3 * buttonSize.X - 3 * spacing.X; + _designCombo.Draw(comboSize); + PrepareButtons(); + ImGui.SameLine(); + DrawApplyButton(buttonSize); + ImGui.SameLine(); + DrawRevertButton(buttonSize); + ImGui.SameLine(); + DrawRevertAutomationButton(buttonSize); + } + + private ActorIdentifier _playerIdentifier; + private ActorData _playerData; + private ActorState? _playerState; + + private ActorData _targetData; + private ActorIdentifier _targetIdentifier; + private ActorState? _targetState; + + private void PrepareButtons() + { + _objects.Update(); + (_playerIdentifier, _playerData) = _objects.PlayerData; + (_targetIdentifier, _targetData) = _objects.TargetData; + if (!_stateManager.TryGetValue(_playerIdentifier, out _playerState)) + _playerState = null; + if (!_stateManager.TryGetValue(_targetIdentifier, out _targetState)) + _targetState = null; + } + + private void DrawApplyButton(Vector2 size) + { + var design = _designCombo.Design; + var available = 0; + var tooltip = string.Empty; + if (design == null) + { + tooltip = "No design selected."; + } + else + { + if (_playerIdentifier.IsValid && _playerData.Valid) + { + available |= 1; + tooltip = $"Left-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to yourself."; + } + + if (_targetIdentifier.IsValid && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}."; + } + + if (available == 0) + tooltip = "Neither player character nor target available."; + } + + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available); + if (!clicked) + return; + + if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state)) + { + Glamourer.Messager.NotificationMessage($"Could not apply {design!.Incognito} to {id.Incognito(null)}: Failed to create state."); + return; + } + + var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize); + _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + } + + public void DrawRevertButton(Vector2 buttonSize) + { + var available = 0; + var tooltip = string.Empty; + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false }) + { + available |= 1; + tooltip = "Left-Click: Revert the player character to their game state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false }) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert {_targetIdentifier} to their game state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.RedoAlt, buttonSize, tooltip, available); + if (clicked) + _stateManager.ResetState(state!, StateChanged.Source.Manual); + } + + public void DrawRevertAutomationButton(Vector2 buttonSize) + { + var available = 0; + var tooltip = string.Empty; + if (!_config.EnableAutoDesigns) + { + tooltip = "Automation is not enabled, you can not reset to automation state."; + } + else + { + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the player character to their automation state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + } + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); + if (!clicked) + { } + else + { + foreach (var actor in data.Objects) + { + _autoDesignApplier.ReapplyAutomation(actor, id, state!); + _stateManager.ReapplyState(actor); + } + } + } + + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, + int available) + { + ImGuiUtil.DrawDisabledButton(icon.ToIconString(), buttonSize, tooltip, available == 0, true); + if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) + return (true, _playerIdentifier, _playerData, _playerState); + if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) + return (true, _targetIdentifier, _targetData, _targetState); + + return (false, ActorIdentifier.Invalid, ActorData.Invalid, null); + } + + private void CheckHotkeys() + { + if (_keyboardToggle > DateTime.UtcNow || !CheckKeyState(_config.ToggleQuickDesignBar, false)) + return; + + _keyboardToggle = DateTime.UtcNow.AddMilliseconds(500); + _config.ShowDesignQuickBar = !_config.ShowDesignQuickBar; + _config.Save(); + } + + public bool CheckKeyState(ModifiableHotkey key, bool noKey) + { + if (key.Hotkey == VirtualKey.NO_KEY) + return noKey; + + return _keyState[key.Hotkey] && key.Modifier1.IsActive() && key.Modifier2.IsActive(); + } +} diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 8858c9e..95a1042 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -13,7 +13,7 @@ public class GlamourerWindowSystem : IDisposable private readonly PenumbraChangedItemTooltip _penumbraTooltip; public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, - Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog) + Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; _ui = ui; @@ -22,6 +22,7 @@ public class GlamourerWindowSystem : IDisposable _windowSystem.AddWindow(popups); _windowSystem.AddWindow(unlocksTab); _windowSystem.AddWindow(changelog.Changelog); + _windowSystem.AddWindow(quick); _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.OpenConfigUi += _ui.Toggle; _uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 1a68018..4b58979 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,6 +1,6 @@ using System; using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; @@ -30,10 +30,10 @@ public class MainWindow : Window, IDisposable Messages = 6, } - private readonly Configuration _config; - private readonly TabSelected _event; - private readonly ConvenienceRevertButtons _convenienceButtons; - private readonly ITab[] _tabs; + private readonly Configuration _config; + private readonly DesignQuickBar _quickBar; + private readonly TabSelected _event; + private readonly ITab[] _tabs; public readonly SettingsTab Settings; public readonly ActorTab Actors; @@ -46,8 +46,7 @@ public class MainWindow : Window, IDisposable public TabType SelectTab = TabType.None; public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, - DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, ConvenienceRevertButtons convenienceButtons, - MessagesTab messages) + DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -56,16 +55,16 @@ public class MainWindow : Window, IDisposable MinimumSize = new Vector2(700, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; - Settings = settings; - Actors = actors; - Designs = designs; - Automation = automation; - Debug = debugTab; - Unlocks = unlocks; - _event = @event; - _convenienceButtons = convenienceButtons; - Messages = messages; - _config = config; + Settings = settings; + Actors = actors; + Designs = designs; + Automation = automation; + Debug = debugTab; + Unlocks = unlocks; + _event = @event; + Messages = messages; + _quickBar = quickBar; + _config = config; _tabs = new ITab[] { settings, @@ -93,7 +92,11 @@ public class MainWindow : Window, IDisposable _config.Save(); } - _convenienceButtons.DrawButtons(yPos); + if (_config.ShowQuickBarInTabs) + { + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - 10 * ImGui.GetFrameHeight(), yPos - ImGuiHelpers.GlobalScale)); + _quickBar.Draw(); + } } private ReadOnlySpan ToLabel(TabType type) diff --git a/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs deleted file mode 100644 index 998270b..0000000 --- a/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Linq; -using Dalamud.Interface.Utility; -using Glamourer.Automation; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Log; -using OtterGui.Widgets; - -namespace Glamourer.Gui.Tabs.AutomationTab; - -public sealed class DesignCombo : FilterComboCache<(Design, string)> -{ - public const int RevertDesignIndex = -1228; - public readonly Design RevertDesign; - - private readonly AutoDesignManager _manager; - private readonly TabSelected _tabSelected; - private float _innerWidth; - - public DesignCombo(AutoDesignManager manager, DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, - ItemManager items, CustomizationService customize, Logger log) - : this(manager, designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log) - { } - - private DesignCombo(AutoDesignManager manager, DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, - Design revertDesign, Logger log) - : base(() => designs.Designs.Select(d => (d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)).OrderBy(d => d.Item2) - .Prepend((revertDesign, string.Empty)).ToList(), log) - { - _manager = manager; - _tabSelected = tabSelected; - RevertDesign = revertDesign; - } - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var ret = base.DrawSelectable(globalIdx, selected); - var (design, path) = Items[globalIdx]; - if (path.Length > 0 && design.Name != path) - { - var start = ImGui.GetItemRectMin(); - var pos = start.X + ImGui.CalcTextSize(design.Name).X; - var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; - var remainingSpace = maxSize - pos; - var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; - var offset = remainingSpace - requiredSize; - if (ImGui.GetScrollMaxY() == 0) - offset -= ImGui.GetStyle().ItemInnerSpacing.X; - - if (offset < ImGui.GetStyle().ItemSpacing.X) - ImGuiUtil.HoverTooltip(path); - else - ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, - ImGui.GetColorU32(ImGuiCol.TextDisabled), path); - } - - return ret; - } - - protected override float GetFilterWidth() - => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X; - - public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex, bool incognito) - { - _innerWidth = 400 * ImGuiHelpers.GlobalScale; - CurrentSelectionIdx = Math.Max(Items.IndexOf(p => design?.Design == p.Item1), 0); - CurrentSelection = Items[CurrentSelectionIdx]; - var name = design?.Name(incognito) ?? "Select Design Here..."; - if (Draw("##design", name, string.Empty, ImGui.GetContentRegionAvail().X, - ImGui.GetTextLineHeightWithSpacing()) - && CurrentSelection.Item1 != null) - { - if (autoDesignIndex >= 0) - _manager.ChangeDesign(set, autoDesignIndex, CurrentSelection.Item1 == RevertDesign ? null : CurrentSelection.Item1); - else - _manager.AddDesign(set, CurrentSelection.Item1 == RevertDesign ? null : CurrentSelection.Item1); - } - - if (design?.Design != null) - { - if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) - _tabSelected.Invoke(MainWindow.TabType.Designs, design.Design); - ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); - } - } - - protected override string ToString((Design, string) obj) - => obj.Item1.Name.Text; - - protected override bool IsVisible(int globalIndex, LowerString filter) - { - var (design, path) = Items[globalIndex]; - return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower); - } - - private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) - => new(customize, items) - { - Index = RevertDesignIndex, - Name = AutoDesign.RevertName, - ApplyCustomize = CustomizeFlagExtensions.AllRelevant, - }; -} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index a1c1fcf..3a0f437 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -30,7 +30,7 @@ public class SetPanel private readonly CustomizationService _customizations; private readonly Configuration _config; - private readonly DesignCombo _designCombo; + private readonly RevertDesignCombo _designCombo; private readonly JobGroupCombo _jobGroupCombo; private readonly IdentifierDrawer _identifierDrawer; @@ -39,7 +39,7 @@ public class SetPanel private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, DesignCombo designCombo, + public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, RevertDesignCombo designCombo, CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) { _selector = selector; @@ -209,7 +209,7 @@ public class SetPanel ImGui.Selectable($"#{idx + 1:D2}"); DrawDragDrop(Selection, idx); ImGui.TableNextColumn(); - _designCombo.Draw(Selection, design, idx, _selector.IncognitoMode); + _designCombo.Draw(Selection, design, idx); DrawDragDrop(Selection, idx); if (singleRow) { @@ -237,7 +237,7 @@ public class SetPanel ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("New"); ImGui.TableNextColumn(); - _designCombo.Draw(Selection, null, -1, _selector.IncognitoMode); + _designCombo.Draw(Selection, null, -1); ImGui.TableNextRow(); _endAction?.Invoke(); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ff55b14..a604860 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -1,9 +1,12 @@ using System; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; +using Dalamud.Plugin.Services; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -18,6 +21,7 @@ namespace Glamourer.Gui.Tabs; public class SettingsTab : ITab { + private readonly VirtualKey[] _validKeys; private readonly Configuration _config; private readonly DesignFileSystemSelector _selector; private readonly StateListener _stateListener; @@ -30,7 +34,7 @@ public class SettingsTab : ITab public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener, CodeService codeService, PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService, UiBuilder uiBuilder, - GlamourerChangelog changelog, FunModule funModule) + GlamourerChangelog changelog, FunModule funModule, IKeyState keys) { _config = config; _selector = selector; @@ -41,6 +45,7 @@ public class SettingsTab : ITab _uiBuilder = uiBuilder; _changelog = changelog; _funModule = funModule; + _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); } public ReadOnlySpan Label @@ -99,11 +104,23 @@ public class SettingsTab : ITab if (!ImGui.CollapsingHeader("Interface")) return; - Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", - _config.SmallEquip, v => _config.SmallEquip = v); - Checkbox("Show Application Checkboxes", - "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules.", - !_config.HideApplyCheckmarks, v => _config.HideApplyCheckmarks = !v); + Checkbox("Show Quick Design Bar", + "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", + _config.ShowDesignQuickBar, v => _config.ShowDesignQuickBar = v); + Checkbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.LockDesignQuickBar, + v => _config.LockDesignQuickBar = v); + if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", + 100 * ImGuiHelpers.GlobalScale, + _config.ToggleQuickDesignBar, v => _config.ToggleQuickDesignBar = v, _validKeys)) + _config.Save(); + Checkbox("Show Quick Design Bar in Main Window", + "Show the quick design bar in the tab selection part of the main window, too.", + _config.ShowQuickBarInTabs, v => _config.ShowQuickBarInTabs = v); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.", _config.EnableGameContextMenu, v => { @@ -120,14 +137,29 @@ public class SettingsTab : ITab _config.HideWindowInCutscene = v; _uiBuilder.DisableCutsceneUiHide = !v; }); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + + Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", + _config.SmallEquip, v => _config.SmallEquip = v); + Checkbox("Show Application Checkboxes", + "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules.", + !_config.HideApplyCheckmarks, v => _config.HideApplyCheckmarks = !v); if (Widget.DoubleModifierSelector("Design Deletion Modifier", "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, _config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v)) _config.Save(); - DrawFolderSortType(); Checkbox("Auto-Open Design Folders", "Have design folders open or closed as their default state after launching.", _config.OpenFoldersByDefault, v => _config.OpenFoldersByDefault = v); + DrawFolderSortType(); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + Checkbox("Show all Application Rule Checkboxes for Automation", "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", _config.ShowAllAutomatedApplicationRules, v => _config.ShowAllAutomatedApplicationRules = v); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index a848f96..2552f44 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -131,6 +131,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -142,7 +143,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton(); private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton()