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()