diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 69d7afe..b195c9b 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -10,16 +10,15 @@ namespace Glamourer.Automation; public class AutoDesign { [Flags] - public enum Type : uint + public enum Type : byte { Armor = 0x01, Customizations = 0x02, - Meta = 0x04, - Weapons = 0x08, - Stains = 0x10, - Accessories = 0x20, + Weapons = 0x04, + Stains = 0x08, + Accessories = 0x10, - All = Armor | Accessories | Customizations | Meta | Weapons | Stains, + All = Armor | Accessories | Customizations | Weapons | Stains, } public Design Design; diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 46ebcbd..08b0c7e 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -148,6 +148,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList if (set.Enabled == value) return; + set.Enabled = value; AutoDesignSet? oldEnabled = null; if (value) { diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index ff1208c..fc78ecb 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -55,7 +55,10 @@ public sealed class AutomationChanged : EventWrapper + SetSelector = 0, + } public AutomationChanged() : base(nameof(AutomationChanged)) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 8dd8ee0..e9d72e4 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -12,6 +12,8 @@ public enum ColorId FolderExpanded, FolderCollapsed, FolderLine, + EnabledAutoSet, + DisabledAutoSet, } public static class Colors @@ -28,7 +30,9 @@ public static class Colors ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), - _ => (0x00000000, string.Empty, string.Empty ), + ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), + ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), + _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index ebc7712..281de98 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -4,6 +4,7 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; +using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DesignTab; using ImGuiNET; using OtterGui.Custom; @@ -15,25 +16,27 @@ public class MainWindow : Window { public enum TabType { - None = -1, - Settings = 0, - Debug = 1, - Actors = 2, - Designs = 3, + None = -1, + Settings = 0, + Debug = 1, + Actors = 2, + Designs = 3, + Automation = 4, } private readonly Configuration _config; private readonly ITab[] _tabs; - public readonly SettingsTab Settings; - public readonly ActorTab Actors; - public readonly DebugTab Debug; - public readonly DesignTab Designs; + public readonly SettingsTab Settings; + public readonly ActorTab Actors; + public readonly DebugTab Debug; + public readonly DesignTab Designs; + public readonly AutomationTab Automation; public TabType SelectTab = TabType.None; public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, - DebugTab debugTab) + DebugTab debugTab, AutomationTab automation) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -42,16 +45,18 @@ public class MainWindow : Window MinimumSize = new Vector2(675, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; - Settings = settings; - Debug = debugTab; - Designs = designs; - Actors = actors; - _config = config; + Settings = settings; + Actors = actors; + Designs = designs; + Automation = automation; + Debug = debugTab; + _config = config; _tabs = new ITab[] { settings, actors, designs, + automation, debugTab, }; @@ -71,20 +76,22 @@ public class MainWindow : Window private ReadOnlySpan ToLabel(TabType type) => type switch { - TabType.Settings => Settings.Label, - TabType.Debug => Debug.Label, - TabType.Actors => Actors.Label, - TabType.Designs => Designs.Label, - _ => ReadOnlySpan.Empty, + TabType.Settings => Settings.Label, + TabType.Debug => Debug.Label, + TabType.Actors => Actors.Label, + TabType.Designs => Designs.Label, + TabType.Automation => Automation.Label, + _ => ReadOnlySpan.Empty, }; private TabType FromLabel(ReadOnlySpan label) { // @formatter:off - if (label == Actors.Label) return TabType.Actors; - if (label == Designs.Label) return TabType.Designs; - if (label == Settings.Label) return TabType.Settings; - if (label == Debug.Label) return TabType.Debug; + if (label == Actors.Label) return TabType.Actors; + if (label == Designs.Label) return TabType.Designs; + if (label == Settings.Label) return TabType.Settings; + if (label == Automation.Label) return TabType.Automation; + if (label == Debug.Label) return TabType.Debug; // @formatter:on return TabType.None; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 52bd555..e77b042 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,11 +1,11 @@ using System.Numerics; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; +using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; @@ -54,9 +54,6 @@ public class ActorPanel if (!_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; - //if (_state != null) - // _stateManager.Update(ref _state.Data, _actor); - using var group = ImRaii.Group(); DrawHeader(); DrawPanel(); @@ -66,13 +63,9 @@ public class ActorPanel { var color = _data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value(); var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); - using var c = ImRaii.PushColor(ImGuiCol.Text, color) - .Push(ImGuiCol.Button, buttonColor) - .Push(ImGuiCol.ButtonHovered, buttonColor) - .Push(ImGuiCol.ButtonActive, buttonColor); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) .Push(ImGuiStyleVar.FrameRounding, 0); - ImGui.Button($"{(_data.Valid ? _data.Label : _identifier.ToString())}##playerHeader", -Vector2.UnitX); + ImGuiUtil.DrawTextButton($"{(_data.Valid ? _data.Label : _identifier.ToString())}##playerHeader", -Vector2.UnitX, buttonColor, color); } private unsafe void DrawPanel() diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs new file mode 100644 index 0000000..f1c2d15 --- /dev/null +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -0,0 +1,31 @@ +using System; +using Dalamud.Interface; +using ImGuiNET; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.AutomationTab; + +public class AutomationTab : ITab +{ + private readonly SetSelector _selector; + private readonly SetPanel _panel; + + public AutomationTab(SetSelector selector, SetPanel panel) + { + _selector = selector; + _panel = panel; + } + + public ReadOnlySpan Label + => "Automation"u8; + + public void DrawContent() + { + _selector.Draw(GetSetSelectorSize()); + ImGui.SameLine(); + _panel.Draw(); + } + + public float GetSetSelectorSize() + => 200f * ImGuiHelpers.GlobalScale; +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs new file mode 100644 index 0000000..37a2c18 --- /dev/null +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Glamourer.Automation; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.AutomationTab; + +public class SetPanel +{ + private readonly AutoDesignManager _manager; + private readonly SetSelector _selector; + + public SetPanel(SetSelector selector, AutoDesignManager manager) + { + _selector = selector; + _manager = manager; + } + + private AutoDesignSet Selection + => _selector.Selection!; + + public void Draw() + { + if (!_selector.HasSelection) + return; + + using var group = ImRaii.Group(); + DrawHeader(); + DrawPanel(); + } + + private void DrawHeader() + { + var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + ImGuiUtil.DrawTextButton(Selection.Name, -Vector2.UnitX, buttonColor); + } + + private string? _tempName; + + private void DrawPanel() + { + using var child = ImRaii.Child("##SetPanel", -Vector2.One, true); + if (!child) + return; + + var name = _tempName ?? Selection.Name; + if (ImGui.InputText("##Name", ref name, 64)) + _tempName = name; + + if (ImGui.IsItemDeactivated()) + { + _manager.Rename(_selector.SelectionIndex, name); + _tempName = null; + } + + ImGui.SameLine(); + var enabled = Selection.Enabled; + if (ImGui.Checkbox("Enabled", ref enabled)) + _manager.SetState(_selector.SelectionIndex, enabled); + + using var table = ImRaii.Table("SetTable", 4, ImGuiTableFlags.RowBg); + if (!table) + return; + + ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 40 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, 5 * ImGui.GetFrameHeight() + 4 * 2 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch); + + + foreach (var (design, idx) in Selection.Designs.WithIndex()) + { + ImGuiUtil.DrawTableColumn($"#{idx:D2}"); + ImGui.TableNextColumn(); + DrawDesignCombo(Selection, design, idx); + ImGui.TableNextColumn(); + + ImGui.TableNextColumn(); + DrawJobGroupCombo(Selection, design, idx); + } + } + + private void DrawDesignCombo(AutoDesignSet set, AutoDesign design, int autoDesignIndex) + { + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + using var combo = ImRaii.Combo("##design", design.Design.Name); + if (!combo) + return; + } + + private void DrawJobGroupCombo(AutoDesignSet set, AutoDesign design, int autoDesignIndex) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + using var combo = ImRaii.Combo("##JobGroups", design.Jobs.Name); + if (!combo) + return; + } + + private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, 2 * ImGuiHelpers.GlobalScale); + var newType = design.ApplicationType; + foreach (var (type, description) in Types) + { + var value = design.ApplicationType.HasFlag(type); + if (ImGui.Checkbox($"##{(byte)type}", ref value)) + newType = value ? newType | type : newType & ~type; + ImGuiUtil.HoverTooltip(description); + } + } + + private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[] + { + (AutoDesign.Type.Customizations, + "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), + (AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), + (AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), + (AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this design."), + (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), + }; +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs new file mode 100644 index 0000000..220d801 --- /dev/null +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Dalamud.Interface; +using Glamourer.Automation; +using Glamourer.Events; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using Vector2 = FFXIVClientStructs.FFXIV.Common.Math.Vector2; + +namespace Glamourer.Gui.Tabs.AutomationTab; + +public class SetSelector : IDisposable +{ + private readonly AutoDesignManager _manager; + private readonly AutomationChanged _event; + public AutoDesignSet? Selection { get; private set; } + public int SelectionIndex = -1; + + private bool IncognitoMode; + private readonly List _list = new(); + + public SetSelector(AutoDesignManager manager, AutomationChanged @event) + { + _manager = manager; + _event = @event; + _event.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector); + } + + public void Dispose() + { + _event.Unsubscribe(OnAutomationChanged); + } + + private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data) + { + switch (type) + { + case AutomationChanged.Type.DeletedSet: + if (set == Selection) + { + Selection = null; + SelectionIndex = -1; + } + + _dirty = true; + break; + case AutomationChanged.Type.AddedSet: + case AutomationChanged.Type.RenamedSet: + case AutomationChanged.Type.MovedSet: + case AutomationChanged.Type.ChangeIdentifier: + case AutomationChanged.Type.ToggleSet: + _dirty = true; + break; + } + } + + private LowerString _filter = LowerString.Empty; + private uint _enabledFilter = 0; + private float _width; + private Vector2 _defaultItemSpacing; + private Vector2 _selectableSize; + private bool _dirty = true; + + private bool CheckFilters(AutoDesignSet set, string identifierString) + { + if (_enabledFilter switch + { + 1 => set.Enabled, + 3 => !set.Enabled, + _ => false, + }) + return false; + + if (!_filter.IsEmpty && !_filter.IsContained(set.Name) && !_filter.IsContained(identifierString)) + return false; + + return true; + } + + private void UpdateList() + { + if (!_dirty) + return; + + _list.Clear(); + foreach (var set in _manager) + { + var id = set.Identifier.ToString(); + if (CheckFilters(set, id)) + _list.Add(set); + } + } + + public bool HasSelection + => Selection != null; + + public void Draw(float width) + { + _width = width; + using var group = ImRaii.Group(); + _defaultItemSpacing = ImGui.GetStyle().ItemSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + ImGui.SetNextItemWidth(_width - ImGui.GetFrameHeight()); + if (LowerString.InputWithHint("##filter", "Filter...", ref _filter, 64)) + _dirty = true; + ImGui.SameLine(); + var f = _enabledFilter; + if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3)) + { + _enabledFilter = _enabledFilter switch + { + 0 => 3, + 3 => 1, + _ => 0, + }; + _dirty = true; + } + + ImGuiUtil.HoverTooltip("Filter to show only enabled or disabled sets."); + + DrawSelector(); + DrawSelectionButtons(); + } + + private void DrawSelector() + { + using var child = ImRaii.Child("##actorSelector", new Vector2(_width, -ImGui.GetFrameHeight()), true); + if (!child) + return; + + UpdateList(); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); + _selectableSize = new Vector2(0, 2 * ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y); + ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); + } + + private void DrawSetSelectable(AutoDesignSet set, int index) + { + using var id = ImRaii.PushId(index); + using (var color = ImRaii.PushColor(ImGuiCol.Text, set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value())) + { + if (ImGui.Selectable(set.Name, set == Selection, ImGuiSelectableFlags.None, _selectableSize)) + { + Selection = set; + SelectionIndex = index; + } + } + + var text = set.Identifier.ToString(); + if (IncognitoMode) + text = set.Identifier.Incognito(text); + var textSize = ImGui.CalcTextSize(text); + ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - textSize.X, + ImGui.GetCursorPosY() - ImGui.GetTextLineHeightWithSpacing())); + ImGui.TextUnformatted(text); + } + + private void DrawSelectionButtons() + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + var buttonWidth = new Vector2(_width / 1, 0); + // TODO + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth + , "Select the local player character.", true, true); + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3f1552b..f4be9ab 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,16 +1,11 @@ using System.Numerics; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; -using Glamourer.Interop.Penumbra; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui.Raii; -using Penumbra.Api.Enums; -using Penumbra.Api.Helpers; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DesignTab; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 08d6502..2446483 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,7 +1,5 @@ using System; using Dalamud.Interface; -using Glamourer.Designs; -using Glamourer.Interop; using ImGuiNET; using OtterGui.Widgets; @@ -10,18 +8,12 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignTab : ITab { public readonly DesignFileSystemSelector Selector; - private readonly DesignFileSystem _fileSystem; - private readonly DesignManager _designManager; private readonly DesignPanel _panel; - private readonly ObjectManager _objects; - public DesignTab(DesignFileSystemSelector selector, DesignFileSystem fileSystem, DesignManager designManager, ObjectManager objects, DesignPanel panel) + public DesignTab(DesignFileSystemSelector selector, DesignPanel panel) { - Selector = selector; - _fileSystem = fileSystem; - _designManager = designManager; - _objects = objects; - _panel = panel; + Selector = selector; + _panel = panel; } public ReadOnlySpan Label diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2e2d2c7..639ef2d 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -8,6 +8,7 @@ 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.DesignTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -106,7 +107,10 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton()