Update automation tab, mod associations panel.

This commit is contained in:
Ottermandias 2026-02-15 22:08:34 +01:00
parent f2ac7e3353
commit 4646fd57ab
14 changed files with 676 additions and 611 deletions

View file

@ -64,7 +64,10 @@ public sealed class AutomationChanged()
public enum Priority public enum Priority
{ {
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.OnAutomationChange"/> /// <seealso cref="Gui.Tabs.AutomationTab.AutomationSelection.OnAutomationChanged"/>
AutomationSelection = 0,
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.Cache.OnAutomationChanged"/>
SetSelector = 0, SetSelector = 0,
/// <seealso cref="AutoDesignApplier.OnAutomationChange"/> /// <seealso cref="AutoDesignApplier.OnAutomationChange"/>

View file

@ -11,7 +11,7 @@ namespace Glamourer.Gui.Tabs.ActorTab;
public sealed class ActorSelection(StateManager manager, ActorObjectManager objects, ICondition conditions) : IUiService public sealed class ActorSelection(StateManager manager, ActorObjectManager objects, ICondition conditions) : IUiService
{ {
private static readonly StringU8 NoSelection = new("No Selection"u8); private static readonly StringU8 NoSelection = new("No Actor Selected"u8);
public ActorIdentifier Identifier { get; private set; } public ActorIdentifier Identifier { get; private set; }
public ActorState? State { get; private set; } public ActorState? State { get; private set; }

View file

@ -0,0 +1,168 @@
using Glamourer.Automation;
using ImSharp;
using Luna;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using Penumbra.String;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationButtons : ButtonFooter
{
public AutomationButtons(Configuration config, AutoDesignManager manager, AutomationSelection selection, ActorObjectManager objects)
{
Buttons.AddButton(new AddButton(objects, manager), 100);
Buttons.AddButton(new DuplicateButton(selection, manager), 90);
Buttons.AddButton(new HelpButton(), 80);
Buttons.AddButton(new DeleteButton(selection, config, manager), 70);
}
private sealed class AddButton(ActorObjectManager objects, AutoDesignManager manager) : BaseIconButton<AwesomeIcon>
{
private ActorIdentifier _identifier;
public override AwesomeIcon Icon
=> LunaStyle.AddObjectIcon;
public override bool HasTooltip
=> true;
protected override void PreDraw()
{
_identifier = objects.Actors.GetCurrentPlayer();
if (!_identifier.IsValid)
_identifier = objects.Actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), WorldId.AnyWorld);
}
public override void DrawTooltip()
=> Im.Text($"Create a new Automatic Design Set for {_identifier}. The associated player can be changed later.");
public override bool Enabled
=> _identifier.IsValid;
public override void OnClick()
=> manager.AddDesignSet("New Automation Set", _identifier);
}
private sealed class DuplicateButton(AutomationSelection selection, AutoDesignManager manager) : BaseIconButton<AwesomeIcon>
{
public override AwesomeIcon Icon
=> LunaStyle.DuplicateIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("Duplicate the current Automatic Design Set."u8);
public override bool Enabled
=> selection.Set is not null;
public override void OnClick()
=> manager.DuplicateDesignSet(selection.Set!);
}
private sealed class HelpButton : BaseIconButton<AwesomeIcon>
{
public override AwesomeIcon Icon
=> LunaStyle.InfoIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text("How does Automation work?"u8);
public override void OnClick()
=> Im.Popup.Open("Automation Help"u8);
protected override void PostDraw()
{
var longestLine =
Im.Font.CalculateSize(
"A single set can contain multiple automated designs that apply under different conditions and different parts of their design."u8);
ImEx.HelpPopup("Automation Help"u8, new Vector2(longestLine.X + 50 * Im.Style.GlobalScale, 33 * Im.Style.TextHeightWithSpacing),
DrawHelp);
}
private static void DrawHelp()
{
var halfLine = new Vector2(Im.Style.TextHeight / 2);
Im.Dummy(halfLine);
Im.Text("What is Automation?"u8);
Im.BulletText("Automation helps you to automatically apply Designs to specific characters under specific circumstances."u8);
Im.Dummy(halfLine);
Im.Text("Automated Design Sets"u8);
Im.BulletText("First, you create automated design sets. An automated design set can be... "u8);
using var indent = Im.Indent();
Im.BulletText("... enabled, or"u8, ColorId.EnabledAutoSet.Value());
Im.BulletText("... disabled."u8, ColorId.DisabledAutoSet.Value());
indent.Unindent();
Im.BulletText("You can create new, empty automated design sets, or duplicate existing ones."u8);
Im.BulletText("You can name automated design sets arbitrarily."u8);
Im.BulletText("You can re-order automated design sets via drag & drop in the selector."u8);
Im.BulletText("Each automated design set is assigned to exactly one specific character."u8);
indent.Indent();
Im.BulletText("On creation, it is assigned to your current Player Character."u8);
Im.BulletText("You can assign sets to any players, retainers, mannequins and most human NPCs."u8);
Im.BulletText("Only one automated design set can be enabled at the same time for each specific character."u8);
indent.Indent();
Im.BulletText("Enabling another automatically disables the prior one."u8);
indent.Unindent();
indent.Unindent();
Im.Dummy(halfLine);
Im.Text("Automated Designs"u8);
Im.BulletText(
"A single set can contain multiple automated designs that apply under different conditions and different parts of their design."u8);
Im.BulletText("The order of these automated designs can also be changed via drag & drop, and is relevant for the application."u8);
Im.BulletText("Automated designs respect their own, coarse applications rules, and the designs own application rules."u8);
Im.BulletText("Automated designs can be configured to be job- or job-group specific and only apply on these jobs, then."u8);
Im.BulletText("There is also the special option 'Reset', which can be used to reset remaining slots to the game's values."u8);
Im.BulletText("Automated designs apply from top to bottom, either on top of your characters current state, or its game state."u8);
Im.BulletText("For a value to apply, it needs to:"u8);
indent.Unindent();
Im.BulletText("Be configured to apply in the design itself."u8);
Im.BulletText("Be configured to apply in the automation rules."u8);
Im.BulletText("Fulfill the conditions of the automation rules."u8);
Im.BulletText("Be a valid value for the current (on its own application) state of the character."u8);
Im.BulletText("Not have had anything applied to the same value before from a different design."u8);
indent.Unindent();
}
}
private sealed class DeleteButton(AutomationSelection selection, Configuration config, AutoDesignManager manager)
: BaseIconButton<AwesomeIcon>
{
private bool _enabled;
public override AwesomeIcon Icon
=> LunaStyle.DeleteIcon;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
{
if (selection.Set is null)
{
Im.Text("No Automatic Design Set selected."u8);
}
else
{
Im.Text("Delete the currently selected design set."u8);
if (!_enabled)
Im.Text($"\nHold {config.DeleteDesignModifier} to delete.");
}
}
public override bool Enabled
=> (_enabled = config.DeleteDesignModifier.IsActive()) && selection.Set is not null;
public override void OnClick()
=> manager.DeleteDesignSet(selection.Index);
}
}

View file

@ -0,0 +1,14 @@
using Glamourer.Automation;
using ImSharp;
namespace Glamourer.Gui.Tabs.AutomationTab;
public readonly struct AutomationCacheItem(AutoDesignSet set, int index)
{
public readonly AutoDesignSet Set = set;
public readonly int Index = index;
public readonly StringPair Name = new(set.Name);
public readonly StringPair IdentifierString = new(set.Identifiers.First().ToString());
public readonly StringU8 Incognito = new($"Auto Design Set #{index + 1}");
public readonly StringU8 IdentifierIncognito = new(set.Identifiers.First().Incognito(null));
}

View file

@ -0,0 +1,64 @@
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationFilter : TextFilterBase<AutomationCacheItem>, IUiService
{
private bool? _setStateFilter;
protected override string ToFilterString(in AutomationCacheItem item, int globalIndex)
=> item.Name.Utf16;
public override bool WouldBeVisible(in AutomationCacheItem item, int globalIndex)
=> _setStateFilter switch
{
true => item.Set.Enabled,
false => !item.Set.Enabled,
null => true,
}
&& base.WouldBeVisible(in item, globalIndex);
public override bool DrawFilter(ReadOnlySpan<byte> label, Vector2 availableRegion)
{
availableRegion.X -= Im.Style.FrameHeight;
var ret = base.DrawFilter(label, availableRegion);
Im.Line.NoSpacing();
using (ImGuiColor.FrameBackground.Push(0x807070FF, _setStateFilter is not null))
{
if (ImEx.TriStateCheckbox("##state"u8, ref _setStateFilter, Rgba32.Transparent, ColorParameter.Default, ColorParameter.Default))
{
ret = true;
InvokeEvent();
}
}
if (Im.Item.Hovered())
{
if (Im.Item.RightClicked())
{
// Ensure that a right-click clears the text filter if it is currently being edited.
Im.Id.ClearActive();
Clear();
}
using var tt = Im.Tooltip.Begin();
Im.Text("Filter to only show enabled or disabled sets."u8);
if (Text.Length is not 0 || _setStateFilter is not null)
Im.Text("\nRight-click to clear all filters, including the text filter."u8);
}
var pos = Im.Item.UpperLeftCorner;
pos.X -= Im.Style.GlobalScale;
Im.Window.DrawList.Shape.Line(pos, pos with { Y = Im.Item.LowerRightCorner.Y }, ImGuiColor.Border.Get(), Im.Style.GlobalScale);
return ret;
}
public override void Clear()
{
var changes = _setStateFilter is not null;
_setStateFilter = null;
if (!Set(string.Empty) && changes)
InvokeEvent();
}
}

View file

@ -0,0 +1,13 @@
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationHeader(Configuration config, AutomationSelection selection) : IHeader
{
public bool Collapsed
=> false;
public void Draw(Vector2 size)
=> ImEx.TextFramed(config.Ephemeral.IncognitoMode ? selection.Incognito : selection.Name, size with { Y = Im.Style.FrameHeight });
}

View file

@ -0,0 +1,61 @@
using Glamourer.Automation;
using Glamourer.Events;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class AutomationSelection : IUiService, IDisposable
{
public static readonly StringU8 NoSelection = new("No Set Selected"u8);
private readonly AutomationChanged _automationChanged;
public int DraggedDesignIndex = -1;
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;
public AutomationSelection(AutomationChanged automationChanged)
{
_automationChanged = automationChanged;
_automationChanged.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
}
public void Dispose()
{
_automationChanged.Unsubscribe(OnAutomationChanged);
}
private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data)
{
if (set != Set)
return;
switch (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;
}
}
public void Update(in AutomationCacheItem? item)
{
Set = item?.Set;
if (Set is null)
{
Index = -1;
Name = NoSelection;
Incognito = NoSelection;
}
else
{
Index = item!.Value.Index;
Name = item!.Value.Name.Utf8;
Incognito = item!.Value.Incognito;
}
}
}

View file

@ -3,12 +3,27 @@ using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public class AutomationTab(SetSelector selector, SetPanel panel, Configuration config) : ITab<MainTabType> public class AutomationTab : TwoPanelLayout, ITab<MainTabType>
{ {
public bool IsVisible private readonly Configuration _config;
=> config.EnableAutoDesigns;
public ReadOnlySpan<byte> Label public AutomationTab(AutomationFilter filter, SetSelector selector, SetPanel panel, AutomationButtons buttons, AutomationHeader header,
Configuration config)
{
_config = config;
LeftHeader = new FilterHeader<AutomationCacheItem>(filter, new StringU8("Filter..."u8));
LeftPanel = selector;
LeftFooter = buttons;
RightHeader = header;
RightPanel = panel;
RightFooter = NopHeaderFooter.Instance;
}
public bool IsVisible
=> _config.EnableAutoDesigns;
public override ReadOnlySpan<byte> Label
=> "Automation"u8; => "Automation"u8;
public MainTabType Identifier public MainTabType Identifier
@ -16,11 +31,6 @@ public class AutomationTab(SetSelector selector, SetPanel panel, Configuration c
public void DrawContent() public void DrawContent()
{ {
selector.Draw(GetSetSelectorSize()); Draw(TwoPanelWidth.IndeterminateRelative);
Im.Line.Same();
panel.Draw();
} }
public float GetSetSelectorSize()
=> 200f * Im.Style.GlobalScale;
} }

View file

@ -17,7 +17,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private readonly Configuration _config; private readonly Configuration _config;
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
private readonly RandomDesignCombo _randomDesignCombo; private readonly RandomDesignCombo _randomDesignCombo;
private readonly SetSelector _selector; private readonly AutomationSelection _selection;
private readonly DesignStorage _designs; private readonly DesignStorage _designs;
private readonly DesignFileSystem _designFileSystem; private readonly DesignFileSystem _designFileSystem;
@ -26,13 +26,13 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private Design? _newDesign; private Design? _newDesign;
public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager, public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager,
RandomDesignCombo randomDesignCombo, SetSelector selector, DesignFileSystem designFileSystem, DesignStorage designs) RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs)
{ {
_automationChanged = automationChanged; _automationChanged = automationChanged;
_config = config; _config = config;
_autoDesignManager = autoDesignManager; _autoDesignManager = autoDesignManager;
_randomDesignCombo = randomDesignCombo; _randomDesignCombo = randomDesignCombo;
_selector = selector; _selection = selection;
_designFileSystem = designFileSystem; _designFileSystem = designFileSystem;
_designs = designs; _designs = designs;
_automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer); _automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer);
@ -87,7 +87,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
if (_set is null || _designIndex < 0 || _designIndex >= _set.Designs.Count) if (_set is null || _designIndex < 0 || _designIndex >= _set.Designs.Count)
return; return;
if (_set != _selector.Selection) if (_set != _selection.Set)
{ {
Close(); Close();
return; return;
@ -283,8 +283,10 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text; name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text;
Im.BulletText(name); Im.BulletText(name);
} }
return; return;
} }
Im.Text("Matches no currently existing designs."u8); Im.Text("Matches no currently existing designs."u8);
} }
@ -314,7 +316,8 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
"Add a new condition that the design must be assigned to the given color."u8, invalid) "Add a new condition that the design must be assigned to the given color."u8, invalid)
&& Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, _newText)); && Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, _newText));
if (_randomDesignCombo.Draw(StringU8.Empty, _newDesign, out var newDesign, Im.ContentRegion.Available.X - Im.Style.ItemInnerSpacing.X - buttonSize.X)) if (_randomDesignCombo.Draw(StringU8.Empty, _newDesign, out var newDesign,
Im.ContentRegion.Available.X - Im.Style.ItemInnerSpacing.X - buttonSize.X))
_newDesign = newDesign as Design; _newDesign = newDesign as Design;
Im.Line.SameInner(); Im.Line.SameInner();
if (ImEx.Button("Exact Design"u8, buttonSize, "Add a single, specific design."u8, _newDesign is null)) if (ImEx.Button("Exact Design"u8, buttonSize, "Add a single, specific design."u8, _newDesign is null))
@ -346,7 +349,8 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
var definition = _newDefinition ?? currentDefinition; var definition = _newDefinition ?? currentDefinition;
definition = definition.Replace(";", ";\n\t").Replace("{", "{\n\t").Replace("}", "\n}"); definition = definition.Replace(";", ";\n\t").Replace("{", "{\n\t").Replace("}", "\n}");
var lines = definition.Count(c => c is '\n'); var lines = definition.Count(c => c is '\n');
if (Im.Input.MultiLine("##definition"u8, ref definition, Im.ContentRegion.Available with { Y = (lines + 1) * Im.Style.TextHeight + Im.Style.FrameHeight }, if (Im.Input.MultiLine("##definition"u8, ref definition,
Im.ContentRegion.Available with { Y = (lines + 1) * Im.Style.TextHeight + Im.Style.FrameHeight },
InputTextFlags.CtrlEnterForNewLine)) InputTextFlags.CtrlEnterForNewLine))
_newDefinition = definition; _newDefinition = definition;
if (Im.Item.DeactivatedAfterEdit && _newDefinition is not null && _newDefinition != currentDefinition) if (Im.Item.DeactivatedAfterEdit && _newDefinition is not null && _newDefinition != currentDefinition)

View file

@ -6,16 +6,12 @@ using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui.Widgets;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Action = System.Action;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetPanel( public class SetPanel(
SetSelector selector,
AutoDesignManager manager, AutoDesignManager manager,
JobService jobs, JobService jobs,
ItemUnlockManager itemUnlocks, ItemUnlockManager itemUnlocks,
@ -24,45 +20,34 @@ public class SetPanel(
CustomizeService customizations, CustomizeService customizations,
IdentifierDrawer identifierDrawer, IdentifierDrawer identifierDrawer,
Configuration config, Configuration config,
RandomRestrictionDrawer randomDrawer) RandomRestrictionDrawer randomDrawer,
AutomationSelection selection) : IPanel
{ {
private readonly JobGroupCombo _jobGroupCombo = new(manager, jobs, Glamourer.Log); private readonly JobGroupCombo _jobGroupCombo = new(manager, jobs);
private readonly HeaderDrawer.Button[] _rightButtons = []; // [new IncognitoButton(config)];
private string? _tempName;
private int _dragIndex = -1; private int _dragIndex = -1;
private Action? _endAction; private Action? _endAction;
private AutoDesignSet Selection public ReadOnlySpan<byte> Id
=> selector.Selection!; => "SetPanel"u8;
public void Draw() public void Draw()
{
using var group = Im.Group();
DrawHeader();
DrawPanel();
}
private void DrawHeader()
=> HeaderDrawer.Draw(selector.SelectionName, 0, ImGuiColor.FrameBackground.Get().Color, [], _rightButtons);
private void DrawPanel()
{ {
using var child = Im.Child.Begin("##Panel"u8, Im.ContentRegion.Available, true); using var child = Im.Child.Begin("##Panel"u8, Im.ContentRegion.Available, true);
if (!child || !selector.HasSelection) if (!child || selection.Index < 0)
return; return;
using (Im.Group()) using (Im.Group())
{ {
var enabled = Selection.Enabled; var enabled = selection.Set!.Enabled;
if (Im.Checkbox("##Enabled"u8, ref enabled)) if (Im.Checkbox("##Enabled"u8, ref enabled))
manager.SetState(selector.SelectionIndex, enabled); manager.SetState(selection.Index, enabled);
LunaStyle.DrawAlignedHelpMarkerLabel("Enabled"u8, LunaStyle.DrawAlignedHelpMarkerLabel("Enabled"u8,
"Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."u8); "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."u8);
var useGame = selector.Selection!.BaseState is AutoDesignSet.Base.Game; var useGame = selection.Set!.BaseState is AutoDesignSet.Base.Game;
if (Im.Checkbox("##gameState"u8, ref useGame)) if (Im.Checkbox("##gameState"u8, ref useGame))
manager.ChangeBaseState(selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); manager.ChangeBaseState(selection.Index, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current);
LunaStyle.DrawAlignedHelpMarkerLabel("Use Game State as Base"u8, LunaStyle.DrawAlignedHelpMarkerLabel("Use Game State as Base"u8,
"When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "u8 "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "u8
+ "Otherwise, they will be applied on top of the characters actual current look using Glamourer."u8); + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."u8);
@ -81,9 +66,9 @@ public class SetPanel(
LunaStyle.DrawAlignedHelpMarkerLabel("Show Editing"u8, LunaStyle.DrawAlignedHelpMarkerLabel("Show Editing"u8,
"Show options to change the name or the associated character or NPC of this design set."u8); "Show options to change the name or the associated character or NPC of this design set."u8);
var resetSettings = selector.Selection!.ResetTemporarySettings; var resetSettings = selection.Set!.ResetTemporarySettings;
if (Im.Checkbox("##resetSettings"u8, ref resetSettings)) if (Im.Checkbox("##resetSettings"u8, ref resetSettings))
manager.ChangeResetSettings(selector.SelectionIndex, resetSettings); manager.ChangeResetSettings(selection.Index, resetSettings);
LunaStyle.DrawAlignedHelpMarkerLabel("Reset Temporary Settings"u8, LunaStyle.DrawAlignedHelpMarkerLabel("Reset Temporary Settings"u8,
"Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."u8); "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."u8);
@ -95,19 +80,13 @@ public class SetPanel(
Im.Separator(); Im.Separator();
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
var name = _tempName ?? Selection.Name;
var flags = config.Ephemeral.IncognitoMode ? InputTextFlags.ReadOnly | InputTextFlags.Password : InputTextFlags.None; var flags = config.Ephemeral.IncognitoMode ? InputTextFlags.ReadOnly | InputTextFlags.Password : InputTextFlags.None;
Im.Item.SetNextWidthScaled(330); Im.Item.SetNextWidthScaled(330);
if (Im.Input.Text("Rename Set##Name"u8, ref name, StringU8.Empty, flags)) if (ImEx.InputOnDeactivation.Text("Rename Set##Name"u8, selection.Name, out string newName, default, flags))
_tempName = name; manager.Rename(selection.Index, newName);
if (Im.Item.Deactivated)
{
manager.Rename(selector.SelectionIndex, name);
_tempName = null;
}
DrawIdentifierSelection(selector.SelectionIndex); DrawIdentifierSelection(selection.Index);
} }
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
@ -176,26 +155,26 @@ public class SetPanel(
table.SetupColumn(""u8, TableColumnFlags.WidthFixed, 2 * Im.Style.FrameHeight + 4 * Im.Style.GlobalScale); table.SetupColumn(""u8, TableColumnFlags.WidthFixed, 2 * Im.Style.FrameHeight + 4 * Im.Style.GlobalScale);
table.HeaderRow(); table.HeaderRow();
foreach (var (idx, design) in Selection.Designs.Index()) foreach (var (idx, design) in selection.Set!.Designs.Index())
{ {
using var id = Im.Id.Push(idx); using var id = Im.Id.Push(idx);
table.NextColumn(); table.NextColumn();
var keyValid = config.DeleteDesignModifier.IsActive(); var keyValid = config.DeleteDesignModifier.IsActive();
if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Remove this design from the set."u8, !keyValid)) if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Remove this design from the set."u8, !keyValid))
_endAction = () => manager.DeleteDesign(Selection, idx); _endAction = () => manager.DeleteDesign(selection.Set!, idx);
if (!keyValid) if (!keyValid)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} to remove."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} to remove.");
table.NextColumn(); table.NextColumn();
DrawSelectable(idx, design.Design); DrawSelectable(idx, design.Design);
table.NextColumn(); table.NextColumn();
DrawRandomEditing(Selection, design, idx); DrawRandomEditing(selection.Set!, design, idx);
designCombo.Draw(Selection, design, idx); designCombo.Draw(selection.Set!, design, idx);
DrawDragDrop(Selection, idx); DrawDragDrop(selection.Set!, idx);
if (singleRow) if (singleRow)
{ {
table.NextColumn(); table.NextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow); DrawApplicationTypeBoxes(selection.Set!, design, idx, singleRow);
table.NextColumn(); table.NextColumn();
DrawConditions(design, idx); DrawConditions(design, idx);
} }
@ -203,7 +182,7 @@ public class SetPanel(
{ {
DrawConditions(design, idx); DrawConditions(design, idx);
table.NextColumn(); table.NextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow); DrawApplicationTypeBoxes(selection.Set!, design, idx, singleRow);
} }
if (config.ShowUnlockedItemWarnings) if (config.ShowUnlockedItemWarnings)
@ -216,7 +195,7 @@ public class SetPanel(
table.NextColumn(); table.NextColumn();
table.DrawFrameColumn("New"u8); table.DrawFrameColumn("New"u8);
table.NextColumn(); table.NextColumn();
designCombo.Draw(Selection, null, -1); designCombo.Draw(selection.Set!, null, -1);
table.NextRow(); table.NextRow();
_endAction?.Invoke(); _endAction?.Invoke();
@ -258,7 +237,7 @@ public class SetPanel(
Im.Tooltip.OnHover($"{sb}"); Im.Tooltip.OnHover($"{sb}");
DrawDragDrop(Selection, idx); DrawDragDrop(selection.Set!, idx);
} }
private void DrawConditions(AutoDesign design, int idx) private void DrawConditions(AutoDesign design, int idx)
@ -267,7 +246,7 @@ public class SetPanel(
if (Im.Button(usingGearset ? "Gearset:##usingGearset"u8 : "Jobs:##usingGearset"u8)) if (Im.Button(usingGearset ? "Gearset:##usingGearset"u8 : "Jobs:##usingGearset"u8))
{ {
usingGearset = !usingGearset; usingGearset = !usingGearset;
manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1)); manager.ChangeGearsetCondition(selection.Set!, idx, (short)(usingGearset ? 0 : -1));
} }
Im.Tooltip.OnHover("Click to switch between Job and Gearset restrictions."u8); Im.Tooltip.OnHover("Click to switch between Job and Gearset restrictions."u8);
@ -277,11 +256,11 @@ public class SetPanel(
{ {
Im.Item.SetNextWidthFull(); Im.Item.SetNextWidthFull();
if (ImEx.InputOnDeactivation.Scalar("##whichGearset"u8, design.GearsetIndex + 1, out var newIndex)) if (ImEx.InputOnDeactivation.Scalar("##whichGearset"u8, design.GearsetIndex + 1, out var newIndex))
manager.ChangeGearsetCondition(Selection, idx, (short)(Math.Clamp(newIndex, 1, 100) - 1)); manager.ChangeGearsetCondition(selection.Set!, idx, (short)(Math.Clamp(newIndex, 1, 100) - 1));
} }
else else
{ {
_jobGroupCombo.Draw(Selection, design, idx); _jobGroupCombo.Draw(selection.Set!, design, idx);
} }
} }
@ -398,7 +377,7 @@ public class SetPanel(
if (source.SetPayload("DesignDragDrop"u8)) if (source.SetPayload("DesignDragDrop"u8))
{ {
_dragIndex = index; _dragIndex = index;
selector.DragDesignIndex = index; selection.DraggedDesignIndex = index;
} }
} }
} }
@ -468,23 +447,24 @@ public class SetPanel(
manager.ChangeIdentifier(setIndex, identifierDrawer.OwnedIdentifier); manager.ChangeIdentifier(setIndex, identifierDrawer.OwnedIdentifier);
} }
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, OtterGui.Log.Logger log) private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs)
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), MouseWheelType.None, log) : SimpleFilterCombo<JobGroup>(SimpleFilterType.Partwise)
{ {
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex) public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
{ {
CurrentSelection = design.Jobs; if (Draw("##jobGroups"u8, design.Jobs, "Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes."u8, Im.ContentRegion.Available.X, out var newGroup))
CurrentSelectionIdx = jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id); manager.ChangeJobCondition(set, autoDesignIndex, newGroup);
if (Draw("##JobGroups", design.Jobs.Name.ToString(),
"Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes.",
Im.ContentRegion.Available.X, Im.Style.TextHeightWithSpacing)
&& CurrentSelectionIdx >= 0)
manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
else if (Im.Io.KeyControl && Im.Item.RightClicked()) else if (Im.Io.KeyControl && Im.Item.RightClicked())
manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]); manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]);
} }
protected override string ToString(JobGroup obj) public override StringU8 DisplayString(in JobGroup value)
=> obj.Name.ToString(); => value.Name;
public override string FilterString(in JobGroup value)
=> value.Name.ToString();
public override IEnumerable<JobGroup> GetBaseItems()
=>jobs.JobGroups.Values;
} }
} }

View file

@ -1,346 +1,155 @@
using Dalamud.Interface; using Glamourer.Automation;
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Events; using Glamourer.Events;
using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using OtterGui; using Luna;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Raii;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.String;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetSelector : IDisposable public sealed class SetSelector(
AutomationSelection selection,
Configuration config,
AutoDesignManager manager,
AutomationFilter filter,
ActorObjectManager objects,
AutomationChanged automationChanged)
: IPanel
{ {
private readonly Configuration _config; private readonly AutomationFilter _filter = filter;
private readonly AutoDesignManager _manager; private readonly AutoDesignManager _manager = manager;
private readonly AutomationChanged _event; private readonly AutomationChanged _automationChanged = automationChanged;
private readonly ActorObjectManager _objects;
private readonly List<(AutoDesignSet, int)> _list = [];
public AutoDesignSet? Selection { get; private set; } public ReadOnlySpan<byte> Id
public int SelectionIndex { get; private set; } = -1; => "Automation Selector"u8;
private int _dragIndex = -1; public void Draw()
private Action? _endAction;
internal int DragDesignIndex = -1;
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorObjectManager objects)
{ {
_manager = manager; Im.Cursor.Y += Im.Style.FramePadding.Y;
_event = @event; var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(this));
_config = config; using var clip = new Im.ListClipper(cache.Count, cache.SelectableSize.Y);
_objects = objects; foreach (var item in clip.Iterate(cache))
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector); {
Im.Cursor.X += Im.Style.FramePadding.X;
using var id = Im.Id.Push(item.Index);
using var group = Im.Group();
DrawSetSelectable(cache, item);
}
} }
public void Dispose() private void DrawSetSelectable(Cache cache, in AutomationCacheItem item)
{ {
_event.Unsubscribe(OnAutomationChange); using (ImGuiColor.Text.Push(item.Set.Enabled ? cache.EnabledSet : cache.DisabledSet))
{
if (Im.Selectable(config.Ephemeral.IncognitoMode ? item.Incognito : item.Name.Utf8, item.Set == selection.Set,
SelectableFlags.None, cache.SelectableSize))
selection.Update(item);
} }
public string SelectionName var lineEnd = Im.Item.LowerRightCorner;
=> GetSetName(Selection, SelectionIndex); var lineStart = new Vector2(Im.Item.UpperLeftCorner.X, lineEnd.Y);
Im.Window.DrawList.Shape.Line(lineStart, lineEnd, cache.LineColor, Im.Style.GlobalScale);
public string GetSetName(AutoDesignSet? set, int index) DrawDragDrop(cache, item);
=> set == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name;
private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data) 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.Text(identifier, textColor);
}
private void DrawDragDrop(Cache cache, in AutomationCacheItem item)
{
using (var target = Im.DragDrop.Target())
{
if (target.IsDropping("DesignSetDragDrop"u8))
{
if (cache.SetDragIndex >= 0)
{
var idx = cache.SetDragIndex;
_manager.MoveSet(idx, item.Index);
}
cache.SetDragIndex = -1;
}
else if (target.IsDropping("DesignDragDrop"u8))
{
if (selection.DraggedDesignIndex >= 0)
{
var idx = selection.DraggedDesignIndex;
var setTo = item.Set;
var setFrom = selection.Set!;
_manager.MoveDesignToSet(setFrom, idx, setTo);
}
selection.DraggedDesignIndex = -1;
}
}
using (var source = Im.DragDrop.Source())
{
if (!source)
return;
Im.Text($"Moving design set {item.Name.Utf8} from position {item.Index + 1}...");
if (source.SetPayload("DesignSetDragDrop"u8))
cache.SetDragIndex = item.Index;
}
}
private sealed class Cache : BasicFilterCache<AutomationCacheItem>
{
public Vector2 SelectableSize;
public Vector4 EnabledSet;
public Vector4 DisabledSet;
public Vector4 AutomationAvailable;
public Vector4 AutomationUnavailable;
public Rgba32 LineColor;
public int SetDragIndex = -1;
private readonly SetSelector _parent;
public Cache(SetSelector parent)
: base(parent._filter)
{
_parent = parent;
_parent._automationChanged.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
}
protected override void Dispose(bool disposing)
{
_parent._automationChanged.Unsubscribe(OnAutomationChanged);
base.Dispose(disposing);
}
private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data)
{ {
switch (type) switch (type)
{ {
case AutomationChanged.Type.DeletedSet: case AutomationChanged.Type.DeletedSet:
if (set == Selection)
{
SelectionIndex = _manager.Count == 0 ? -1 : SelectionIndex == 0 ? 0 : SelectionIndex - 1;
Selection = SelectionIndex >= 0 ? _manager[SelectionIndex] : null;
}
_dirty = true;
break;
case AutomationChanged.Type.AddedSet: case AutomationChanged.Type.AddedSet:
SelectionIndex = (((int, string))data!).Item1;
Selection = set!;
_dirty = true;
break;
case AutomationChanged.Type.MovedSet: case AutomationChanged.Type.MovedSet:
_dirty = true;
var (oldIdx, newIdx) = ((int, int))data!;
if (SelectionIndex == oldIdx)
SelectionIndex = newIdx;
break;
case AutomationChanged.Type.RenamedSet: case AutomationChanged.Type.RenamedSet:
case AutomationChanged.Type.ChangeIdentifier: case AutomationChanged.Type.ChangeIdentifier:
case AutomationChanged.Type.ToggleSet: case AutomationChanged.Type.ToggleSet:
_dirty = true; Dirty |= IManagedCache.DirtyFlags.Custom;
break; break;
} }
} }
private LowerString _filter = LowerString.Empty; protected override IEnumerable<AutomationCacheItem> GetItems()
private uint _enabledFilter; => _parent._manager.Index().Select(s => new AutomationCacheItem(s.Item, s.Index));
private float _width;
private Vector2 _defaultItemSpacing;
private Vector2 _selectableSize;
private bool _dirty = true;
private bool CheckFilters(AutoDesignSet set, string identifierString) public override void Update()
{ {
if (_enabledFilter switch SelectableSize = new Vector2(0, 2 * Im.Style.TextHeight + Im.Style.ItemSpacing.Y);
{ EnabledSet = ColorId.EnabledAutoSet.Value().ToVector();
1 => set.Enabled, DisabledSet = ColorId.DisabledAutoSet.Value().ToVector();
3 => !set.Enabled, AutomationAvailable = ColorId.AutomationActorAvailable.Value().ToVector();
_ => false, AutomationUnavailable = ColorId.AutomationActorUnavailable.Value().ToVector();
}) LineColor = ImGuiColor.Border.Get();
return false; base.Update();
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, idx) in _manager.WithIndex())
{
var id = set.Identifiers[0].ToString();
if (CheckFilters(set, id))
_list.Add((set, idx));
}
}
public bool HasSelection
=> Selection != null;
public void Draw(float width)
{
_width = width;
using var group = ImRaii.Group();
_defaultItemSpacing = Im.Style.ItemSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
ImGui.SetNextItemWidth(_width - Im.Style.FrameHeight);
if (LowerString.InputWithHint("##filter", "Filter...", ref _filter, 64))
_dirty = true;
Im.Line.Same();
var f = _enabledFilter;
if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3u))
{
_enabledFilter = _enabledFilter switch
{
0 => 3,
3 => 1,
_ => 0,
};
_dirty = true;
}
var pos = ImGui.GetItemRectMin();
pos.X -= Im.Style.GlobalScale;
ImGui.GetWindowDrawList().AddLine(pos, pos with { Y = ImGui.GetItemRectMax().Y }, ImGuiColor.Border.Get().Color,
Im.Style.GlobalScale);
ImGuiUtil.HoverTooltip("Filter to show only enabled or disabled sets.");
DrawSelector();
DrawSelectionButtons();
}
private void DrawSelector()
{
using var child = ImRaii.Child("##Selector", new Vector2(_width, -Im.Style.FrameHeight), true);
if (!child)
return;
UpdateList();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
_selectableSize = new Vector2(0, 2 * Im.Style.TextHeight + Im.Style.ItemSpacing.Y);
ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * Im.Style.ItemSpacing.Y);
_endAction?.Invoke();
_endAction = null;
}
private void DrawSetSelectable((AutoDesignSet Set, int Index) pair)
{
using var id = Im.Id.Push(pair.Index);
using (ImGuiColor.Text.Push(pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value()))
{
if (ImGui.Selectable(GetSetName(pair.Set, pair.Index), pair.Set == Selection, ImGuiSelectableFlags.None, _selectableSize))
{
Selection = pair.Set;
SelectionIndex = pair.Index;
}
}
var lineEnd = ImGui.GetItemRectMax();
var lineStart = new Vector2(ImGui.GetItemRectMin().X, lineEnd.Y);
ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, ImGuiColor.Border.Get().Color, Im.Style.GlobalScale);
DrawDragDrop(pair.Set, pair.Index);
var text = pair.Set.Identifiers[0].ToString();
if (_config.Ephemeral.IncognitoMode)
text = pair.Set.Identifiers[0].Incognito(text);
var textSize = ImGui.CalcTextSize(text);
var textColor = pair.Set.Identifiers.Any(_objects.ContainsKey) ? ColorId.AutomationActorAvailable : ColorId.AutomationActorUnavailable;
ImGui.SetCursorPos(new Vector2(Im.ContentRegion.Available.X - textSize.X,
ImGui.GetCursorPosY() - Im.Style.TextHeightWithSpacing));
Im.Text(text, textColor.Value());
}
private void DrawSelectionButtons()
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
var buttonWidth = new Vector2(_width / 4, 0);
NewSetButton(buttonWidth);
Im.Line.Same();
DuplicateSetButton(buttonWidth);
Im.Line.Same();
HelpButton(buttonWidth);
Im.Line.Same();
DeleteSetButton(buttonWidth);
}
private static void HelpButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.QuestionCircle.ToIconString(), size, "How does Automation work?", false, true))
ImGui.OpenPopup("Automation Help");
static void HalfLine()
=> Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
const string longestLine =
"A single set can contain multiple automated designs that apply under different conditions and different parts of their design.";
ImGuiUtil.HelpPopup("Automation Help",
new Vector2(ImGui.CalcTextSize(longestLine).X + 50 * Im.Style.GlobalScale, 33 * Im.Style.TextHeightWithSpacing), () =>
{
HalfLine();
ImGui.TextUnformatted("What is Automation?");
ImGui.BulletText("Automation helps you to automatically apply Designs to specific characters under specific circumstances.");
HalfLine();
ImGui.TextUnformatted("Automated Design Sets");
ImGui.BulletText("First, you create automated design sets. An automated design set can be... ");
using var indent = ImRaii.PushIndent();
Im.BulletText("... enabled, or"u8, ColorId.EnabledAutoSet.Value());
Im.BulletText("... disabled."u8, ColorId.DisabledAutoSet.Value());
indent.Pop(1);
ImGui.BulletText("You can create new, empty automated design sets, or duplicate existing ones.");
ImGui.BulletText("You can name automated design sets arbitrarily.");
ImGui.BulletText("You can re-order automated design sets via drag & drop in the selector.");
ImGui.BulletText("Each automated design set is assigned to exactly one specific character.");
indent.Push();
ImGui.BulletText("On creation, it is assigned to your current Player Character.");
ImGui.BulletText("You can assign sets to any players, retainers, mannequins and most human NPCs.");
ImGui.BulletText("Only one automated design set can be enabled at the same time for each specific character.");
indent.Push();
ImGui.BulletText("Enabling another automatically disables the prior one.");
indent.Pop(2);
HalfLine();
ImGui.TextUnformatted("Automated Designs");
ImGui.BulletText(longestLine);
ImGui.BulletText(
"The order of these automated designs can also be changed via drag & drop, and is relevant for the application.");
ImGui.BulletText("Automated designs respect their own, coarse applications rules, and the designs own application rules.");
ImGui.BulletText("Automated designs can be configured to be job- or job-group specific and only apply on these jobs, then.");
ImGui.BulletText("There is also the special option 'Reset', which can be used to reset remaining slots to the game's values.");
ImGui.BulletText(
"Automated designs apply from top to bottom, either on top of your characters current state, or its game state.");
ImGui.BulletText("For a value to apply, it needs to:");
indent.Push();
ImGui.BulletText("Be configured to apply in the design itself.");
ImGui.BulletText("Be configured to apply in the automation rules.");
ImGui.BulletText("Fulfill the conditions of the automation rules.");
ImGui.BulletText("Be a valid value for the current (on its own application) state of the character.");
ImGui.BulletText("Not have had anything applied to the same value before from a different design.");
indent.Pop(1);
});
}
private void NewSetButton(Vector2 size)
{
var id = _objects.Actors.GetCurrentPlayer();
if (!id.IsValid)
id = _objects.Actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size,
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
_manager.AddDesignSet("New Automation Set", id);
}
private void DuplicateSetButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, "Duplicate the current Automatic Design Set.",
Selection == null, true))
_manager.DuplicateDesignSet(Selection!);
}
private void DeleteSetButton(Vector2 size)
{
var keyValid = _config.DeleteDesignModifier.IsActive();
var (disabled, tt) = HasSelection
? keyValid
? (false, "Delete the currently selected design set.")
: (true, $"Delete the currently selected design set.\nHold {_config.DeleteDesignModifier} to delete.")
: (true, "No Automatic Design Set selected.");
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, disabled, true))
_manager.DeleteDesignSet(SelectionIndex);
}
private void DrawDragDrop(AutoDesignSet set, int index)
{
const string dragDropLabel = "DesignSetDragDrop";
using (var target = ImRaii.DragDropTarget())
{
if (target.Success)
{
if (ImGuiUtil.IsDropping(dragDropLabel))
{
if (_dragIndex >= 0)
{
var idx = _dragIndex;
_endAction = () => _manager.MoveSet(idx, index);
}
_dragIndex = -1;
}
else if (ImGuiUtil.IsDropping("DesignDragDrop"))
{
if (DragDesignIndex >= 0)
{
var idx = DragDesignIndex;
var setTo = set;
var setFrom = Selection!;
_endAction = () => _manager.MoveDesignToSet(setFrom, idx, setTo);
}
DragDesignIndex = -1;
}
}
}
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.TextUnformatted($"Moving design set {GetSetName(set, index)} from position {index + 1}...");
if (ImGui.SetDragDropPayload(dragDropLabel, null, 0))
_dragIndex = index;
}
} }
} }
} }

View file

@ -1,15 +1,8 @@
using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Text;
using TagButtons = OtterGui.Widgets.TagButtons;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
@ -22,15 +15,8 @@ public class DesignDetailTab
private readonly DesignManager _manager; private readonly DesignManager _manager;
private readonly DesignColors _colors; private readonly DesignColors _colors;
private readonly DesignColorCombo _colorCombo; private readonly DesignColorCombo _colorCombo;
private readonly TagButtons _tagButtons = new();
private string? _newPath;
private string? _newDescription;
private string? _newName;
private bool _editDescriptionMode; private bool _editDescriptionMode;
private Design? _changeDesign;
private DesignFileSystem.Leaf? _changeLeaf;
public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem,
DesignColors colors, Configuration config) DesignColors colors, Configuration config)
@ -58,7 +44,7 @@ public class DesignDetailTab
private void DrawDesignInfoTable() private void DrawDesignInfoTable()
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f));
using var table = Im.Table.Begin("Details"u8, 2); using var table = Im.Table.Begin("Details"u8, 2);
if (!table) if (!table)
return; return;
@ -66,31 +52,20 @@ public class DesignDetailTab
table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Reset Temporary Settings"u8).X); table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Reset Temporary Settings"u8).X);
table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch); table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch);
ImUtf8.DrawFrameColumn("Design Name"u8); table.DrawFrameColumn("Design Name"u8);
ImGui.TableNextColumn(); table.NextColumn();
var width = new Vector2(Im.ContentRegion.Available.X, 0); var width = Im.ContentRegion.Available with { Y = 0 };
var name = _newName ?? _selector.Selected!.Name; Im.Item.SetNextWidth(width.X);
ImGui.SetNextItemWidth(width.X); if (ImEx.InputOnDeactivation.Text("##Name"u8, _selector.Selected!.Name.Text, out string newName))
if (ImUtf8.InputText("##Name"u8, ref name)) _manager.Rename(_selector.Selected!, newName);
{
_newName = name;
_changeDesign = _selector.Selected;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changeDesign != null)
{
_manager.Rename(_changeDesign, name);
_newName = null;
_changeDesign = null;
}
var identifier = _selector.Selected!.Identifier.ToString(); var identifier = _selector.Selected!.Identifier.ToString();
ImUtf8.DrawFrameColumn("Unique Identifier"u8); table.DrawFrameColumn("Unique Identifier"u8);
ImGui.TableNextColumn(); table.NextColumn();
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!); var fileName = _saveService.FileNames.DesignFile(_selector.Selected!);
using (Im.Font.PushMono()) using (Im.Font.PushMono())
{ {
if (ImGui.Button(identifier, width)) if (Im.Button(identifier, width))
try try
{ {
Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true }); Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
@ -102,72 +77,60 @@ public class DesignDetailTab
} }
if (Im.Item.RightClicked()) if (Im.Item.RightClicked())
ImGui.SetClipboardText(identifier); Im.Clipboard.Set(identifier);
} }
ImUtf8.HoverTooltip( Im.Tooltip.OnHover(
$"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard."); $"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard.");
ImUtf8.DrawFrameColumn("Full Selector Path"u8); table.DrawFrameColumn("Full Selector Path"u8);
ImGui.TableNextColumn(); table.NextColumn();
var path = _newPath ?? _selector.SelectedLeaf!.FullName(); Im.Item.SetNextWidth(width.X);
ImGui.SetNextItemWidth(width.X); if (ImEx.InputOnDeactivation.Text("##Path"u8, _selector.SelectedLeaf!.FullName(), out string newPath))
if (ImUtf8.InputText("##Path"u8, ref path))
{
_newPath = path;
_changeLeaf = _selector.SelectedLeaf!;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changeLeaf != null)
try try
{ {
_fileSystem.RenameAndMove(_changeLeaf, path); _fileSystem.RenameAndMove(_selector.SelectedLeaf, newPath);
_newPath = null;
_changeLeaf = null;
} }
catch (Exception ex) catch (Exception ex)
{ {
Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error);
} }
ImUtf8.DrawFrameColumn("Quick Design Bar"u8); table.DrawFrameColumn("Quick Design Bar"u8);
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign)) if (Im.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, true); _manager.SetQuickDesign(_selector.Selected!, true);
var hovered = ImGui.IsItemHovered(); var hovered = Im.Item.Hovered();
Im.Line.Same(); Im.Line.SameInner();
if (ImUtf8.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign)) if (Im.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, false); _manager.SetQuickDesign(_selector.Selected!, false);
if (hovered || ImGui.IsItemHovered()) if (hovered || Im.Item.Hovered())
{ Im.Tooltip.Set("Display or hide this design in your quick design bar."u8);
using var tt = ImUtf8.Tooltip();
ImUtf8.Text("Display or hide this design in your quick design bar."u8);
}
var forceRedraw = _selector.Selected!.ForcedRedraw; var forceRedraw = _selector.Selected!.ForcedRedraw;
ImUtf8.DrawFrameColumn("Force Redrawing"u8); table.DrawFrameColumn("Force Redrawing"u8);
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.Checkbox("##ForceRedraw"u8, ref forceRedraw)) if (Im.Checkbox("##ForceRedraw"u8, ref forceRedraw))
_manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw);
ImUtf8.HoverTooltip("Set this design to always force a redraw when it is applied through any means."u8); 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 = _selector.Selected!.ResetAdvancedDyes;
ImUtf8.DrawFrameColumn("Reset Advanced Dyes"u8); table.DrawFrameColumn("Reset Advanced Dyes"u8);
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes)) if (Im.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes))
_manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes);
ImUtf8.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8); 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 = _selector.Selected!.ResetTemporarySettings;
ImUtf8.DrawFrameColumn("Reset Temporary Settings"u8); table.DrawFrameColumn("Reset Temporary Settings"u8);
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) if (Im.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings))
_manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); _manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings);
ImUtf8.HoverTooltip( 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); "Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8);
ImUtf8.DrawFrameColumn("Color"u8); table.DrawFrameColumn("Color"u8);
ImGui.TableNextColumn(); table.NextColumn();
if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color, if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color,
"Associate a color with this design.\n"u8 "Associate a color with this design.\n"u8
+ "Right-Click to revert to automatic coloring.\n"u8 + "Right-Click to revert to automatic coloring.\n"u8
@ -191,28 +154,28 @@ public class DesignDetailTab
Im.Tooltip.OnHover("The color associated with this design does not exist."u8); Im.Tooltip.OnHover("The color associated with this design does not exist."u8);
} }
ImUtf8.DrawFrameColumn("Creation Date"u8); table.DrawFrameColumn("Creation Date"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0); ImEx.TextFramed($"{_selector.Selected!.CreationDate.LocalDateTime:F}", width, 0);
ImUtf8.DrawFrameColumn("Last Update Date"u8); table.DrawFrameColumn("Last Update Date"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0); ImEx.TextFramed($"{_selector.Selected!.LastEdit.LocalDateTime:F}", width, 0);
ImUtf8.DrawFrameColumn("Tags"u8); table.DrawFrameColumn("Tags"u8);
ImGui.TableNextColumn(); table.NextColumn();
DrawTags(); DrawTags();
} }
private void DrawTags() private void DrawTags()
{ {
var idx = _tagButtons.Draw(string.Empty, string.Empty, _selector.Selected!.Tags, out var editedTag); var idx = TagButtons.Draw(StringU8.Empty, StringU8.Empty, _selector.Selected!.Tags, out var editedTag);
if (idx < 0) if (idx < 0)
return; return;
if (idx < _selector.Selected!.Tags.Length) if (idx < _selector.Selected!.Tags.Length)
{ {
if (editedTag.Length == 0) if (editedTag.Length is 0)
_manager.RemoveTag(_selector.Selected!, idx); _manager.RemoveTag(_selector.Selected!, idx);
else else
_manager.RenameTag(_selector.Selected!, idx, editedTag); _manager.RenameTag(_selector.Selected!, idx, editedTag);
@ -226,30 +189,24 @@ public class DesignDetailTab
private void DrawDescription() private void DrawDescription()
{ {
var desc = _selector.Selected!.Description; var desc = _selector.Selected!.Description;
var size = new Vector2(Im.ContentRegion.Available.X, 12 * Im.Style.TextHeightWithSpacing); var size = Im.ContentRegion.Available with { Y = 12 * Im.Style.TextHeightWithSpacing };
if (!_editDescriptionMode) if (!_editDescriptionMode)
{ {
using (var textBox = ImUtf8.ListBox("##desc"u8, size)) using (var textBox = Im.ListBox.Begin("##desc"u8, size))
{ {
ImUtf8.TextWrapped(desc); if (textBox)
Im.TextWrapped(desc);
} }
if (ImUtf8.Button("Edit Description"u8)) if (Im.Button("Edit Description"u8))
_editDescriptionMode = true; _editDescriptionMode = true;
} }
else else
{ {
var edit = _newDescription ?? desc; if (ImEx.InputOnDeactivation.MultiLine("##desc"u8, desc, out string newDescription, size))
if (ImUtf8.InputMultiLine("##desc"u8, ref edit, size)) _manager.ChangeDescription(_selector.Selected!, newDescription);
_newDescription = edit;
if (ImGui.IsItemDeactivatedAfterEdit()) if (Im.Button("Stop Editing"u8))
{
_manager.ChangeDescription(_selector.Selected!, edit);
_newDescription = null;
}
if (ImUtf8.Button("Stop Editing"u8))
_editDescriptionMode = false; _editDescriptionMode = false;
} }
} }

View file

@ -1,19 +1,9 @@
using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using Dalamud.Utility;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.Widget;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
@ -28,11 +18,11 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
if (!h.Alive) if (!h.Alive)
return; return;
ImGuiUtil.HoverTooltip( Im.Tooltip.OnHover(
"This tab can store information about specific mods associated with this design.\n\n" "This tab can store information about specific mods associated with this design.\n\n"u8
+ "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n" + "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n"u8
+ "You can also use it to quickly open the associated mod page in Penumbra.\n\n" + "You can also use it to quickly open the associated mod page in Penumbra.\n\n"u8
+ "It is not feasible to apply those changes automatically in general cases, since there would be no way to revert those changes, handle multiple designs applying at once, etc."); + "It is not feasible to apply those changes automatically in general cases, since there would be no way to revert those changes, handle multiple designs applying at once, etc."u8);
if (!h) if (!h)
return; return;
@ -44,24 +34,24 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
private void DrawCopyButtons() private void DrawCopyButtons()
{ {
var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0); var size = new Vector2((Im.ContentRegion.Available.X - 2 * Im.Style.ItemSpacing.X) / 3, 0);
if (ImGui.Button("Copy All to Clipboard", size)) if (Im.Button("Copy All to Clipboard"u8, size))
_copy = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); _copy = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray();
Im.Line.Same(); Im.Line.Same();
if (ImGuiUtil.DrawDisabledButton("Add from Clipboard", size, if (ImEx.Button("Add from Clipboard"u8, size,
_copy != null _copy is not null
? $"Add {_copy.Length} mod association(s) from clipboard." ? $"Add {_copy.Length} mod association(s) from clipboard."
: "Copy some mod associations to the clipboard, first.", _copy == null)) : "Copy some mod associations to the clipboard, first."u8, _copy is null))
foreach (var (mod, setting) in _copy!) foreach (var (mod, setting) in _copy!)
manager.UpdateMod(selector.Selected!, mod, setting); manager.UpdateMod(selector.Selected!, mod, setting);
Im.Line.Same(); Im.Line.Same();
if (ImGuiUtil.DrawDisabledButton("Set from Clipboard", size, if (ImEx.Button("Set from Clipboard"u8, size,
_copy != null _copy is not null
? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing."
: "Copy some mod associations to the clipboard, first.", _copy == null)) : "Copy some mod associations to the clipboard, first."u8, _copy is null))
{ {
while (selector.Selected!.AssociatedMods.Count > 0) while (selector.Selected!.AssociatedMods.Count > 0)
manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]); manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]);
@ -75,17 +65,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
var (id, name) = penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (config.Ephemeral.IncognitoMode) if (config.Ephemeral.IncognitoMode)
name = id.ShortGuid(); name = id.ShortGuid();
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", if (ImEx.Button($"Try Applying All Associated Mods to {name}##applyAll",
new Vector2(Im.ContentRegion.Available.X, 0), string.Empty, id == Guid.Empty)) Im.ContentRegion.Available with { Y = 0 }, string.Empty, id == Guid.Empty))
ApplyAll(); ApplyAll();
} }
public void DrawApplyButton() public void DrawApplyButton()
{ {
var (id, name) = penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero, if (ImEx.Button("Apply Mod Associations"u8, Vector2.Zero,
$"Try to apply all associated mod settings to Penumbras current collection {name}", $"Try to apply all associated mod settings to Penumbras current collection {name}",
selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty)) selector.Selected!.AssociatedMods.Count is 0 || id == Guid.Empty))
ApplyAll(); ApplyAll();
} }
@ -104,26 +94,26 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
table.SetupColumn("##Buttons"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight * 3 + Im.Style.ItemInnerSpacing.X * 2); table.SetupColumn("##Buttons"u8, TableColumnFlags.WidthFixed, Im.Style.FrameHeight * 3 + Im.Style.ItemInnerSpacing.X * 2);
table.SetupColumn("Mod Name"u8, TableColumnFlags.WidthStretch); table.SetupColumn("Mod Name"u8, TableColumnFlags.WidthStretch);
if (config.UseTemporarySettings) if (config.UseTemporarySettings)
table.SetupColumn("Remove"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); table.SetupColumn("Remove"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Remove"u8).X);
table.SetupColumn("Inherit"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); table.SetupColumn("Inherit"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Inherit"u8).X);
table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X); table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("State"u8).X);
table.SetupColumn("Priority"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X); table.SetupColumn("Priority"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Priority"u8).X);
table.SetupColumn("##Options"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X); table.SetupColumn("##Options"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Applym"u8).X);
table.HeaderRow(); table.HeaderRow();
Mod? removedMod = null; Mod? removedMod = null;
(Mod mod, ModSettings settings)? updatedMod = null; (Mod mod, ModSettings settings)? updatedMod = null;
foreach (var ((mod, settings), idx) in selector.Selected!.AssociatedMods.WithIndex()) foreach (var (idx, (mod, settings)) in selector.Selected!.AssociatedMods.Index())
{ {
using var id = Im.Id.Push(idx); using var id = Im.Id.Push(idx);
DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp); DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp);
if (removedModTmp.HasValue) if (removedModTmp.HasValue)
removedMod = removedModTmp; removedMod = removedModTmp;
if (updatedModTmp.HasValue) if (updatedModTmp.HasValue)
updatedMod = updatedModTmp; updatedMod = updatedModTmp;
} }
DrawNewModRow(); DrawNewModRow(table);
if (removedMod.HasValue) if (removedMod.HasValue)
manager.RemoveMod(selector.Selected!, removedMod.Value); manager.RemoveMod(selector.Selected!, removedMod.Value);
@ -132,99 +122,92 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings);
} }
private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod) private void DrawAssociatedModRow(in Im.TableDisposable table, Mod mod, ModSettings settings, out Mod? removedMod,
out (Mod, ModSettings)? updatedMod)
{ {
removedMod = null; removedMod = null;
updatedMod = null; updatedMod = null;
ImGui.TableNextColumn(); table.NextColumn();
var canDelete = config.DeleteDesignModifier.IsActive(); var canDelete = config.DeleteDesignModifier.IsActive();
if (canDelete) if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this mod from associations."u8))
{
if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this mod from associations."u8))
removedMod = mod; removedMod = mod;
} if (!canDelete)
else Im.Tooltip.OnHover($"\nHold {config.DeleteDesignModifier} to delete.");
{
ImUtf8.IconButton(FontAwesomeIcon.Trash, $"Delete this mod from associations.\nHold {config.DeleteDesignModifier} to delete.",
disabled: true);
}
Im.Line.SameInner(); Im.Line.SameInner();
if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Copy this mod setting to clipboard."u8)) if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Copy this mod setting to clipboard."u8))
_copy = [(mod, settings)]; _copy = [(mod, settings)];
Im.Line.SameInner(); Im.Line.SameInner();
ImUtf8.IconButton(FontAwesomeIcon.RedoAlt, "Update the settings of this mod association."u8); ImEx.Icon.Button(LunaStyle.RefreshIcon, "Update the settings of this mod association."u8);
if (ImGui.IsItemHovered()) if (Im.Item.Hovered())
{ {
var newSettings = penumbra.GetModSettings(mod, out var source); var newSettings = penumbra.GetModSettings(mod, out var source);
if (ImGui.IsItemClicked()) if (Im.Item.Clicked())
updatedMod = (mod, newSettings); updatedMod = (mod, newSettings);
using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * Im.Style.GlobalScale); using var style = ImStyleSingle.PopupBorderThickness.Push(2 * Im.Style.GlobalScale);
using var tt = ImUtf8.Tooltip(); using var tt = Im.Tooltip.Begin();
if (source.Length > 0) if (source.Length > 0)
ImUtf8.Text($"Using temporary settings made by {source}."); Im.Text($"Using temporary settings made by {source}.");
Im.Separator(); Im.Separator();
var namesDifferent = mod.Name != mod.DirectoryName; var namesDifferent = mod.Name != mod.DirectoryName;
Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0)); Im.Dummy(300 * Im.Style.GlobalScale);
using (ImRaii.Group()) using (Im.Group())
{ {
if (namesDifferent) if (namesDifferent)
ImUtf8.Text("Directory Name"u8); Im.Text("Directory Name"u8);
ImUtf8.Text("Force Inherit"u8); Im.Text("Force Inherit"u8);
ImUtf8.Text("Enabled"u8); Im.Text("Enabled"u8);
ImUtf8.Text("Priority"u8); Im.Text("Priority"u8);
ModCombo.DrawSettingsLeft(newSettings); ModCombo.DrawSettingsLeft(newSettings);
} }
ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale)); Im.Line.Same(Math.Max(Im.Item.Size.X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale));
using (ImRaii.Group()) using (Im.Group())
{ {
if (namesDifferent) if (namesDifferent)
ImUtf8.Text(mod.DirectoryName); Im.Text(mod.DirectoryName);
ImUtf8.Text(newSettings.ForceInherit.ToString()); Im.Text($"{newSettings.ForceInherit}");
ImUtf8.Text(newSettings.Enabled.ToString()); Im.Text($"{newSettings.Enabled}");
ImUtf8.Text(newSettings.Priority.ToString()); Im.Text($"{newSettings.Priority}");
ModCombo.DrawSettingsRight(newSettings); ModCombo.DrawSettingsRight(newSettings);
} }
} }
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.Selectable($"{mod.Name}##name")) if (Im.Selectable($"{mod.Name}##name"))
penumbra.OpenModPage(mod); penumbra.OpenModPage(mod);
if (ImGui.IsItemHovered()) Im.Tooltip.OnHover($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
if (config.UseTemporarySettings) if (config.UseTemporarySettings)
{ {
ImGui.TableNextColumn(); table.NextColumn();
var remove = settings.Remove; var remove = settings.Remove;
if (TwoStateCheckbox.Instance.Draw("##Remove"u8, ref remove)) if (ImEx.TwoStateCheckbox("##Remove"u8, ref remove))
updatedMod = (mod, settings with { Remove = remove }); updatedMod = (mod, settings with { Remove = remove });
ImUtf8.HoverTooltip( Im.Tooltip.OnHover(
"Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8); "Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8);
} }
ImGui.TableNextColumn(); table.NextColumn();
var inherit = settings.ForceInherit; var inherit = settings.ForceInherit;
if (TwoStateCheckbox.Instance.Draw("##ForceInherit"u8, ref inherit)) if (ImEx.TwoStateCheckbox("##ForceInherit"u8, ref inherit))
updatedMod = (mod, settings with { ForceInherit = inherit }); updatedMod = (mod, settings with { ForceInherit = inherit });
ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8); Im.Tooltip.OnHover("Force the mod to inherit its settings from inherited collections."u8);
ImGui.TableNextColumn(); table.NextColumn();
var enabled = settings.Enabled; var enabled = settings.Enabled;
if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref enabled)) if (ImEx.TwoStateCheckbox("##Enabled"u8, ref enabled))
updatedMod = (mod, settings with { Enabled = enabled }); updatedMod = (mod, settings with { Enabled = enabled });
ImGui.TableNextColumn(); table.NextColumn();
var priority = settings.Priority; var priority = settings.Priority;
ImGui.SetNextItemWidth(Im.ContentRegion.Available.X); Im.Item.SetNextWidthFull();
if (ImUtf8.InputScalarOnDeactivated("##Priority"u8, ref priority)) if (ImEx.InputOnDeactivation.Scalar("##Priority"u8, ref priority))
updatedMod = (mod, settings with { Priority = priority }); updatedMod = (mod, settings with { Priority = priority });
ImGui.TableNextColumn(); table.NextColumn();
if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(Im.ContentRegion.Available.X, 0), string.Empty, if (ImEx.Button("Apply"u8, Im.ContentRegion.Available with { Y = 0 }, StringU8.Empty, !penumbra.Available))
!penumbra.Available))
{ {
var text = penumbra.SetMod(mod, settings, StateSource.Manual, false); var text = penumbra.SetMod(mod, settings, StateSource.Manual, false);
if (text.Length > 0) if (text.Length > 0)
@ -236,40 +219,39 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
private static void DrawAssociatedModTooltip(ModSettings settings) private static void DrawAssociatedModTooltip(ModSettings settings)
{ {
if (settings is not { Enabled: true, Settings.Count: > 0 } || !ImGui.IsItemHovered()) if (settings is not { Enabled: true, Settings.Count: > 0 } || !Im.Item.Hovered())
return; return;
using var t = ImRaii.Tooltip(); using var t = Im.Tooltip.Begin();
ImGui.TextUnformatted("This will also try to apply the following settings to the current collection:"); Im.Text("This will also try to apply the following settings to the current collection:"u8);
Im.Line.New(); Im.Line.New();
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
ModCombo.DrawSettingsLeft(settings); ModCombo.DrawSettingsLeft(settings);
} }
ImGui.SameLine(Im.ContentRegion.Available.X / 2); Im.Line.Same(Im.ContentRegion.Available.X / 2);
using (var _ = ImRaii.Group()) using (Im.Group())
{ {
ModCombo.DrawSettingsRight(settings); ModCombo.DrawSettingsRight(settings);
} }
} }
private void DrawNewModRow() private void DrawNewModRow(in Im.TableDisposable table)
{ {
var currentName = _modCombo.CurrentSelection.Mod.Name; var currentName = _modCombo.CurrentSelection.Mod.Name;
ImGui.TableNextColumn(); table.NextColumn();
var tt = currentName.IsNullOrEmpty() var tt = string.IsNullOrEmpty(currentName)
? "Please select a mod first." ? "Please select a mod first."u8
: selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod)
? "The design already contains an association with the selected mod." ? "The design already contains an association with the selected mod."u8
: string.Empty; : StringU8.Empty;
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(Im.Style.FrameHeight), tt, tt.Length > 0, if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0))
true))
manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
ImGui.TableNextColumn(); table.NextColumn();
_modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty, _modCombo.Draw("##new", string.IsNullOrEmpty(currentName) ? "Select new Mod..." : currentName, string.Empty,
Im.ContentRegion.Available.X, Im.Style.TextHeight); Im.ContentRegion.Available.X, Im.Style.TextHeight);
} }
} }

2
Luna

@ -1 +1 @@
Subproject commit 042b829099c34608782c2a70da3cf189ac5f53be Subproject commit e80bdf5c10c6c30efccdff7d1adcb4a638eba4df