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
{
/// <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,
/// <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
{
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 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;
public class AutomationTab(SetSelector selector, SetPanel panel, Configuration config) : ITab<MainTabType>
public class AutomationTab : TwoPanelLayout, ITab<MainTabType>
{
public bool IsVisible
=> config.EnableAutoDesigns;
private readonly Configuration _config;
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;
public MainTabType Identifier
@ -16,11 +31,6 @@ public class AutomationTab(SetSelector selector, SetPanel panel, Configuration c
public void DrawContent()
{
selector.Draw(GetSetSelectorSize());
Im.Line.Same();
panel.Draw();
Draw(TwoPanelWidth.IndeterminateRelative);
}
public float GetSetSelectorSize()
=> 200f * Im.Style.GlobalScale;
}

View file

@ -13,26 +13,26 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private AutoDesignSet? _set;
private int _designIndex = -1;
private readonly AutomationChanged _automationChanged;
private readonly Configuration _config;
private readonly AutoDesignManager _autoDesignManager;
private readonly RandomDesignCombo _randomDesignCombo;
private readonly SetSelector _selector;
private readonly DesignStorage _designs;
private readonly DesignFileSystem _designFileSystem;
private readonly AutomationChanged _automationChanged;
private readonly Configuration _config;
private readonly AutoDesignManager _autoDesignManager;
private readonly RandomDesignCombo _randomDesignCombo;
private readonly AutomationSelection _selection;
private readonly DesignStorage _designs;
private readonly DesignFileSystem _designFileSystem;
private string _newText = string.Empty;
private string? _newDefinition;
private Design? _newDesign;
public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager,
RandomDesignCombo randomDesignCombo, SetSelector selector, DesignFileSystem designFileSystem, DesignStorage designs)
RandomDesignCombo randomDesignCombo, AutomationSelection selection, DesignFileSystem designFileSystem, DesignStorage designs)
{
_automationChanged = automationChanged;
_config = config;
_autoDesignManager = autoDesignManager;
_randomDesignCombo = randomDesignCombo;
_selector = selector;
_selection = selection;
_designFileSystem = designFileSystem;
_designs = designs;
_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)
return;
if (_set != _selector.Selection)
if (_set != _selection.Set)
{
Close();
return;
@ -283,8 +283,10 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
name = _designFileSystem.TryGetValue(enumerator.Current, out l) ? l.FullName() : enumerator.Current.Name.Text;
Im.BulletText(name);
}
return;
}
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(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;
Im.Line.SameInner();
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;
definition = definition.Replace(";", ";\n\t").Replace("{", "{\n\t").Replace("}", "\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))
_newDefinition = definition;
if (Im.Item.DeactivatedAfterEdit && _newDefinition is not null && _newDefinition != currentDefinition)
@ -373,8 +377,8 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
{
var designs = IDesignPredicate.Get(list, _designs, _designFileSystem).ToList();
Im.Button(designs.Count > 0
? $"All Restrictions Combined Match {designs.Count} Designs"
: "None of the Restrictions Matches Any Designs"u8, Im.ContentRegion.Available with { Y = 0 });
? $"All Restrictions Combined Match {designs.Count} Designs"
: "None of the Restrictions Matches Any Designs"u8, Im.ContentRegion.Available with { Y = 0 });
if (Im.Item.Hovered())
LookupTooltip(designs);
}

View file

@ -6,16 +6,12 @@ using Glamourer.Services;
using Glamourer.Unlocks;
using ImSharp;
using Luna;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Action = System.Action;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetPanel(
SetSelector selector,
AutoDesignManager manager,
JobService jobs,
ItemUnlockManager itemUnlocks,
@ -24,45 +20,34 @@ public class SetPanel(
CustomizeService customizations,
IdentifierDrawer identifierDrawer,
Configuration config,
RandomRestrictionDrawer randomDrawer)
RandomRestrictionDrawer randomDrawer,
AutomationSelection selection) : IPanel
{
private readonly JobGroupCombo _jobGroupCombo = new(manager, jobs, Glamourer.Log);
private readonly HeaderDrawer.Button[] _rightButtons = []; // [new IncognitoButton(config)];
private string? _tempName;
private readonly JobGroupCombo _jobGroupCombo = new(manager, jobs);
private int _dragIndex = -1;
private Action? _endAction;
private AutoDesignSet Selection
=> selector.Selection!;
public ReadOnlySpan<byte> Id
=> "SetPanel"u8;
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);
if (!child || !selector.HasSelection)
if (!child || selection.Index < 0)
return;
using (Im.Group())
{
var enabled = Selection.Enabled;
var enabled = selection.Set!.Enabled;
if (Im.Checkbox("##Enabled"u8, ref enabled))
manager.SetState(selector.SelectionIndex, enabled);
manager.SetState(selection.Index, enabled);
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);
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))
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,
"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);
@ -81,9 +66,9 @@ public class SetPanel(
LunaStyle.DrawAlignedHelpMarkerLabel("Show Editing"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))
manager.ChangeResetSettings(selector.SelectionIndex, resetSettings);
manager.ChangeResetSettings(selection.Index, resetSettings);
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);
@ -95,19 +80,13 @@ public class SetPanel(
Im.Separator();
Im.Dummy(Vector2.Zero);
var name = _tempName ?? Selection.Name;
var flags = config.Ephemeral.IncognitoMode ? InputTextFlags.ReadOnly | InputTextFlags.Password : InputTextFlags.None;
Im.Item.SetNextWidthScaled(330);
if (Im.Input.Text("Rename Set##Name"u8, ref name, StringU8.Empty, flags))
_tempName = name;
if (ImEx.InputOnDeactivation.Text("Rename Set##Name"u8, selection.Name, out string newName, default, flags))
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);
@ -176,26 +155,26 @@ public class SetPanel(
table.SetupColumn(""u8, TableColumnFlags.WidthFixed, 2 * Im.Style.FrameHeight + 4 * Im.Style.GlobalScale);
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);
table.NextColumn();
var keyValid = config.DeleteDesignModifier.IsActive();
if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Remove this design from the set."u8, !keyValid))
_endAction = () => manager.DeleteDesign(Selection, idx);
if(!keyValid)
_endAction = () => manager.DeleteDesign(selection.Set!, idx);
if (!keyValid)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} to remove.");
table.NextColumn();
DrawSelectable(idx, design.Design);
table.NextColumn();
DrawRandomEditing(Selection, design, idx);
designCombo.Draw(Selection, design, idx);
DrawDragDrop(Selection, idx);
DrawRandomEditing(selection.Set!, design, idx);
designCombo.Draw(selection.Set!, design, idx);
DrawDragDrop(selection.Set!, idx);
if (singleRow)
{
table.NextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
DrawApplicationTypeBoxes(selection.Set!, design, idx, singleRow);
table.NextColumn();
DrawConditions(design, idx);
}
@ -203,7 +182,7 @@ public class SetPanel(
{
DrawConditions(design, idx);
table.NextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
DrawApplicationTypeBoxes(selection.Set!, design, idx, singleRow);
}
if (config.ShowUnlockedItemWarnings)
@ -216,7 +195,7 @@ public class SetPanel(
table.NextColumn();
table.DrawFrameColumn("New"u8);
table.NextColumn();
designCombo.Draw(Selection, null, -1);
designCombo.Draw(selection.Set!, null, -1);
table.NextRow();
_endAction?.Invoke();
@ -258,7 +237,7 @@ public class SetPanel(
Im.Tooltip.OnHover($"{sb}");
DrawDragDrop(Selection, idx);
DrawDragDrop(selection.Set!, 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))
{
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);
@ -277,11 +256,11 @@ public class SetPanel(
{
Im.Item.SetNextWidthFull();
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
{
_jobGroupCombo.Draw(Selection, design, idx);
_jobGroupCombo.Draw(selection.Set!, design, idx);
}
}
@ -397,8 +376,8 @@ public class SetPanel(
Im.Text($"Moving design #{index + 1:D2}...");
if (source.SetPayload("DesignDragDrop"u8))
{
_dragIndex = index;
selector.DragDesignIndex = index;
_dragIndex = index;
selection.DraggedDesignIndex = index;
}
}
}
@ -406,8 +385,8 @@ public class SetPanel(
private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine)
{
using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(2 * Im.Style.GlobalScale));
var newType = design.Type;
using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(2 * Im.Style.GlobalScale));
var newType = design.Type;
using (ImStyleBorder.Frame.Push(ColorId.FolderLine.Value()))
{
Im.Checkbox("##all"u8, ref newType, ApplicationType.All);
@ -468,23 +447,24 @@ public class SetPanel(
manager.ChangeIdentifier(setIndex, identifierDrawer.OwnedIdentifier);
}
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, OtterGui.Log.Logger log)
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), MouseWheelType.None, log)
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs)
: SimpleFilterCombo<JobGroup>(SimpleFilterType.Partwise)
{
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
{
CurrentSelection = design.Jobs;
CurrentSelectionIdx = jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
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);
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))
manager.ChangeJobCondition(set, autoDesignIndex, newGroup);
else if (Im.Io.KeyControl && Im.Item.RightClicked())
manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]);
}
protected override string ToString(JobGroup obj)
=> obj.Name.ToString();
public override StringU8 DisplayString(in JobGroup value)
=> 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 Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Automation;
using Glamourer.Events;
using Dalamud.Bindings.ImGui;
using ImSharp;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Raii;
using Luna;
using Penumbra.GameData.Interop;
using Penumbra.String;
using ImGuiClip = OtterGui.ImGuiClip;
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 AutoDesignManager _manager;
private readonly AutomationChanged _event;
private readonly ActorObjectManager _objects;
private readonly List<(AutoDesignSet, int)> _list = [];
private readonly AutomationFilter _filter = filter;
private readonly AutoDesignManager _manager = manager;
private readonly AutomationChanged _automationChanged = automationChanged;
public AutoDesignSet? Selection { get; private set; }
public int SelectionIndex { get; private set; } = -1;
public ReadOnlySpan<byte> Id
=> "Automation Selector"u8;
private int _dragIndex = -1;
private Action? _endAction;
internal int DragDesignIndex = -1;
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorObjectManager objects)
public void Draw()
{
_manager = manager;
_event = @event;
_config = config;
_objects = objects;
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector);
}
public void Dispose()
{
_event.Unsubscribe(OnAutomationChange);
}
public string SelectionName
=> GetSetName(Selection, SelectionIndex);
public string GetSetName(AutoDesignSet? set, int index)
=> set == null ? "No Selection" : _config.Ephemeral.IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name;
private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data)
{
switch (type)
Im.Cursor.Y += Im.Style.FramePadding.Y;
var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(this));
using var clip = new Im.ListClipper(cache.Count, cache.SelectableSize.Y);
foreach (var item in clip.Iterate(cache))
{
case AutomationChanged.Type.DeletedSet:
if (set == Selection)
Im.Cursor.X += Im.Style.FramePadding.X;
using var id = Im.Id.Push(item.Index);
using var group = Im.Group();
DrawSetSelectable(cache, item);
}
}
private void DrawSetSelectable(Cache cache, in AutomationCacheItem item)
{
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);
}
var lineEnd = Im.Item.LowerRightCorner;
var lineStart = new Vector2(Im.Item.UpperLeftCorner.X, lineEnd.Y);
Im.Window.DrawList.Shape.Line(lineStart, lineEnd, cache.LineColor, Im.Style.GlobalScale);
DrawDragDrop(cache, item);
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)
{
SelectionIndex = _manager.Count == 0 ? -1 : SelectionIndex == 0 ? 0 : SelectionIndex - 1;
Selection = SelectionIndex >= 0 ? _manager[SelectionIndex] : null;
var idx = cache.SetDragIndex;
_manager.MoveSet(idx, item.Index);
}
_dirty = true;
break;
case AutomationChanged.Type.AddedSet:
SelectionIndex = (((int, string))data!).Item1;
Selection = set!;
_dirty = true;
break;
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.ChangeIdentifier:
case AutomationChanged.Type.ToggleSet:
_dirty = true;
break;
}
}
private LowerString _filter = LowerString.Empty;
private uint _enabledFilter;
private float _width;
private Vector2 _defaultItemSpacing;
private Vector2 _selectableSize;
private bool _dirty = true;
private bool CheckFilters(AutoDesignSet set, string identifierString)
{
if (_enabledFilter switch
cache.SetDragIndex = -1;
}
else if (target.IsDropping("DesignDragDrop"u8))
{
1 => set.Enabled,
3 => !set.Enabled,
_ => false,
})
return false;
if (selection.DraggedDesignIndex >= 0)
{
var idx = selection.DraggedDesignIndex;
var setTo = item.Set;
var setFrom = selection.Set!;
_manager.MoveDesignToSet(setFrom, idx, setTo);
}
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;
selection.DraggedDesignIndex = -1;
}
}
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())
using (var source = Im.DragDrop.Source())
{
if (target.Success)
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)
{
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;
}
case AutomationChanged.Type.DeletedSet:
case AutomationChanged.Type.AddedSet:
case AutomationChanged.Type.MovedSet:
case AutomationChanged.Type.RenamedSet:
case AutomationChanged.Type.ChangeIdentifier:
case AutomationChanged.Type.ToggleSet:
Dirty |= IManagedCache.DirtyFlags.Custom;
break;
}
}
using (var source = ImRaii.DragDropSource())
protected override IEnumerable<AutomationCacheItem> GetItems()
=> _parent._manager.Index().Select(s => new AutomationCacheItem(s.Item, s.Index));
public override void Update()
{
if (source)
{
ImGui.TextUnformatted($"Moving design set {GetSetName(set, index)} from position {index + 1}...");
if (ImGui.SetDragDropPayload(dragDropLabel, null, 0))
_dragIndex = index;
}
SelectableSize = new Vector2(0, 2 * Im.Style.TextHeight + Im.Style.ItemSpacing.Y);
EnabledSet = ColorId.EnabledAutoSet.Value().ToVector();
DisabledSet = ColorId.DisabledAutoSet.Value().ToVector();
AutomationAvailable = ColorId.AutomationActorAvailable.Value().ToVector();
AutomationUnavailable = ColorId.AutomationActorUnavailable.Value().ToVector();
LineColor = ImGuiColor.Border.Get();
base.Update();
}
}
}

View file

@ -1,15 +1,8 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using Glamourer.Services;
using Dalamud.Bindings.ImGui;
using ImSharp;
using Luna;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Text;
using TagButtons = OtterGui.Widgets.TagButtons;
namespace Glamourer.Gui.Tabs.DesignTab;
@ -22,15 +15,8 @@ public class DesignDetailTab
private readonly DesignManager _manager;
private readonly DesignColors _colors;
private readonly DesignColorCombo _colorCombo;
private readonly TagButtons _tagButtons = new();
private string? _newPath;
private string? _newDescription;
private string? _newName;
private bool _editDescriptionMode;
private Design? _changeDesign;
private DesignFileSystem.Leaf? _changeLeaf;
private bool _editDescriptionMode;
public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem,
DesignColors colors, Configuration config)
@ -58,7 +44,7 @@ public class DesignDetailTab
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);
if (!table)
return;
@ -66,31 +52,20 @@ public class DesignDetailTab
table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Reset Temporary Settings"u8).X);
table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch);
ImUtf8.DrawFrameColumn("Design Name"u8);
ImGui.TableNextColumn();
var width = new Vector2(Im.ContentRegion.Available.X, 0);
var name = _newName ?? _selector.Selected!.Name;
ImGui.SetNextItemWidth(width.X);
if (ImUtf8.InputText("##Name"u8, ref name))
{
_newName = name;
_changeDesign = _selector.Selected;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changeDesign != null)
{
_manager.Rename(_changeDesign, name);
_newName = null;
_changeDesign = null;
}
table.DrawFrameColumn("Design Name"u8);
table.NextColumn();
var width = Im.ContentRegion.Available with { Y = 0 };
Im.Item.SetNextWidth(width.X);
if (ImEx.InputOnDeactivation.Text("##Name"u8, _selector.Selected!.Name.Text, out string newName))
_manager.Rename(_selector.Selected!, newName);
var identifier = _selector.Selected!.Identifier.ToString();
ImUtf8.DrawFrameColumn("Unique Identifier"u8);
ImGui.TableNextColumn();
table.DrawFrameColumn("Unique Identifier"u8);
table.NextColumn();
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!);
using (Im.Font.PushMono())
{
if (ImGui.Button(identifier, width))
if (Im.Button(identifier, width))
try
{
Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
@ -102,72 +77,60 @@ public class DesignDetailTab
}
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.");
ImUtf8.DrawFrameColumn("Full Selector Path"u8);
ImGui.TableNextColumn();
var path = _newPath ?? _selector.SelectedLeaf!.FullName();
ImGui.SetNextItemWidth(width.X);
if (ImUtf8.InputText("##Path"u8, ref path))
{
_newPath = path;
_changeLeaf = _selector.SelectedLeaf!;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changeLeaf != null)
table.DrawFrameColumn("Full Selector Path"u8);
table.NextColumn();
Im.Item.SetNextWidth(width.X);
if (ImEx.InputOnDeactivation.Text("##Path"u8, _selector.SelectedLeaf!.FullName(), out string newPath))
try
{
_fileSystem.RenameAndMove(_changeLeaf, path);
_newPath = null;
_changeLeaf = null;
_fileSystem.RenameAndMove(_selector.SelectedLeaf, newPath);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error);
}
ImUtf8.DrawFrameColumn("Quick Design Bar"u8);
ImGui.TableNextColumn();
if (ImUtf8.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign))
table.DrawFrameColumn("Quick Design Bar"u8);
table.NextColumn();
if (Im.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, true);
var hovered = ImGui.IsItemHovered();
Im.Line.Same();
if (ImUtf8.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign))
var hovered = Im.Item.Hovered();
Im.Line.SameInner();
if (Im.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, false);
if (hovered || ImGui.IsItemHovered())
{
using var tt = ImUtf8.Tooltip();
ImUtf8.Text("Display or hide this design in your quick design bar."u8);
}
if (hovered || Im.Item.Hovered())
Im.Tooltip.Set("Display or hide this design in your quick design bar."u8);
var forceRedraw = _selector.Selected!.ForcedRedraw;
ImUtf8.DrawFrameColumn("Force Redrawing"u8);
ImGui.TableNextColumn();
if (ImUtf8.Checkbox("##ForceRedraw"u8, ref forceRedraw))
table.DrawFrameColumn("Force Redrawing"u8);
table.NextColumn();
if (Im.Checkbox("##ForceRedraw"u8, ref 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;
ImUtf8.DrawFrameColumn("Reset Advanced Dyes"u8);
ImGui.TableNextColumn();
if (ImUtf8.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes))
table.DrawFrameColumn("Reset Advanced Dyes"u8);
table.NextColumn();
if (Im.Checkbox("##ResetAdvancedDyes"u8, ref 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;
ImUtf8.DrawFrameColumn("Reset Temporary Settings"u8);
ImGui.TableNextColumn();
if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings))
table.DrawFrameColumn("Reset Temporary Settings"u8);
table.NextColumn();
if (Im.Checkbox("##ResetTemporarySettings"u8, ref 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);
ImUtf8.DrawFrameColumn("Color"u8);
ImGui.TableNextColumn();
table.DrawFrameColumn("Color"u8);
table.NextColumn();
if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color,
"Associate a color with this design.\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);
}
ImUtf8.DrawFrameColumn("Creation Date"u8);
ImGui.TableNextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0);
table.DrawFrameColumn("Creation Date"u8);
table.NextColumn();
ImEx.TextFramed($"{_selector.Selected!.CreationDate.LocalDateTime:F}", width, 0);
ImUtf8.DrawFrameColumn("Last Update Date"u8);
ImGui.TableNextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0);
table.DrawFrameColumn("Last Update Date"u8);
table.NextColumn();
ImEx.TextFramed($"{_selector.Selected!.LastEdit.LocalDateTime:F}", width, 0);
ImUtf8.DrawFrameColumn("Tags"u8);
ImGui.TableNextColumn();
table.DrawFrameColumn("Tags"u8);
table.NextColumn();
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)
return;
if (idx < _selector.Selected!.Tags.Length)
{
if (editedTag.Length == 0)
if (editedTag.Length is 0)
_manager.RemoveTag(_selector.Selected!, idx);
else
_manager.RenameTag(_selector.Selected!, idx, editedTag);
@ -226,30 +189,24 @@ public class DesignDetailTab
private void DrawDescription()
{
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)
{
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;
}
else
{
var edit = _newDescription ?? desc;
if (ImUtf8.InputMultiLine("##desc"u8, ref edit, size))
_newDescription = edit;
if (ImEx.InputOnDeactivation.MultiLine("##desc"u8, desc, out string newDescription, size))
_manager.ChangeDescription(_selector.Selected!, newDescription);
if (ImGui.IsItemDeactivatedAfterEdit())
{
_manager.ChangeDescription(_selector.Selected!, edit);
_newDescription = null;
}
if (ImUtf8.Button("Stop Editing"u8))
if (Im.Button("Stop Editing"u8))
_editDescriptionMode = false;
}
}

View file

@ -1,19 +1,9 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using Dalamud.Utility;
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
using Dalamud.Bindings.ImGui;
using ImSharp;
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;
@ -28,11 +18,11 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
if (!h.Alive)
return;
ImGuiUtil.HoverTooltip(
"This tab can store information about specific mods associated with this design.\n\n"
+ "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n"
+ "You can also use it to quickly open the associated mod page in Penumbra.\n\n"
+ "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.");
Im.Tooltip.OnHover(
"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"u8
+ "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."u8);
if (!h)
return;
@ -44,24 +34,24 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
private void DrawCopyButtons()
{
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();
Im.Line.Same();
if (ImGuiUtil.DrawDisabledButton("Add from Clipboard", size,
_copy != null
if (ImEx.Button("Add from Clipboard"u8, size,
_copy is not null
? $"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!)
manager.UpdateMod(selector.Selected!, mod, setting);
Im.Line.Same();
if (ImGuiUtil.DrawDisabledButton("Set from Clipboard", size,
_copy != null
if (ImEx.Button("Set from Clipboard"u8, size,
_copy is not null
? $"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)
manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]);
@ -75,17 +65,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
var (id, name) = penumbra.CurrentCollection;
if (config.Ephemeral.IncognitoMode)
name = id.ShortGuid();
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll",
new Vector2(Im.ContentRegion.Available.X, 0), string.Empty, id == Guid.Empty))
if (ImEx.Button($"Try Applying All Associated Mods to {name}##applyAll",
Im.ContentRegion.Available with { Y = 0 }, string.Empty, id == Guid.Empty))
ApplyAll();
}
public void DrawApplyButton()
{
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}",
selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty))
selector.Selected!.AssociatedMods.Count is 0 || id == Guid.Empty))
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("Mod Name"u8, TableColumnFlags.WidthStretch);
if (config.UseTemporarySettings)
table.SetupColumn("Remove"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X);
table.SetupColumn("Inherit"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X);
table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X);
table.SetupColumn("Priority"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X);
table.SetupColumn("##Options"u8, TableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X);
table.SetupColumn("Remove"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Remove"u8).X);
table.SetupColumn("Inherit"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Inherit"u8).X);
table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("State"u8).X);
table.SetupColumn("Priority"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Priority"u8).X);
table.SetupColumn("##Options"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Applym"u8).X);
table.HeaderRow();
Mod? removedMod = 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);
DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp);
DrawAssociatedModRow(table, mod, settings, out var removedModTmp, out var updatedModTmp);
if (removedModTmp.HasValue)
removedMod = removedModTmp;
if (updatedModTmp.HasValue)
updatedMod = updatedModTmp;
}
DrawNewModRow();
DrawNewModRow(table);
if (removedMod.HasValue)
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);
}
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;
updatedMod = null;
ImGui.TableNextColumn();
table.NextColumn();
var canDelete = config.DeleteDesignModifier.IsActive();
if (canDelete)
{
if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this mod from associations."u8))
removedMod = mod;
}
else
{
ImUtf8.IconButton(FontAwesomeIcon.Trash, $"Delete this mod from associations.\nHold {config.DeleteDesignModifier} to delete.",
disabled: true);
}
if (ImEx.Icon.Button(LunaStyle.DeleteIcon, "Delete this mod from associations."u8))
removedMod = mod;
if (!canDelete)
Im.Tooltip.OnHover($"\nHold {config.DeleteDesignModifier} to delete.");
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)];
Im.Line.SameInner();
ImUtf8.IconButton(FontAwesomeIcon.RedoAlt, "Update the settings of this mod association."u8);
if (ImGui.IsItemHovered())
ImEx.Icon.Button(LunaStyle.RefreshIcon, "Update the settings of this mod association."u8);
if (Im.Item.Hovered())
{
var newSettings = penumbra.GetModSettings(mod, out var source);
if (ImGui.IsItemClicked())
if (Im.Item.Clicked())
updatedMod = (mod, newSettings);
using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * Im.Style.GlobalScale);
using var tt = ImUtf8.Tooltip();
using var style = ImStyleSingle.PopupBorderThickness.Push(2 * Im.Style.GlobalScale);
using var tt = Im.Tooltip.Begin();
if (source.Length > 0)
ImUtf8.Text($"Using temporary settings made by {source}.");
Im.Text($"Using temporary settings made by {source}.");
Im.Separator();
var namesDifferent = mod.Name != mod.DirectoryName;
Im.Dummy(new Vector2(300 * Im.Style.GlobalScale, 0));
using (ImRaii.Group())
Im.Dummy(300 * Im.Style.GlobalScale);
using (Im.Group())
{
if (namesDifferent)
ImUtf8.Text("Directory Name"u8);
ImUtf8.Text("Force Inherit"u8);
ImUtf8.Text("Enabled"u8);
ImUtf8.Text("Priority"u8);
Im.Text("Directory Name"u8);
Im.Text("Force Inherit"u8);
Im.Text("Enabled"u8);
Im.Text("Priority"u8);
ModCombo.DrawSettingsLeft(newSettings);
}
ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale));
using (ImRaii.Group())
Im.Line.Same(Math.Max(Im.Item.Size.X + 3 * Im.Style.ItemSpacing.X, 150 * Im.Style.GlobalScale));
using (Im.Group())
{
if (namesDifferent)
ImUtf8.Text(mod.DirectoryName);
Im.Text(mod.DirectoryName);
ImUtf8.Text(newSettings.ForceInherit.ToString());
ImUtf8.Text(newSettings.Enabled.ToString());
ImUtf8.Text(newSettings.Priority.ToString());
Im.Text($"{newSettings.ForceInherit}");
Im.Text($"{newSettings.Enabled}");
Im.Text($"{newSettings.Priority}");
ModCombo.DrawSettingsRight(newSettings);
}
}
ImGui.TableNextColumn();
table.NextColumn();
if (ImUtf8.Selectable($"{mod.Name}##name"))
if (Im.Selectable($"{mod.Name}##name"))
penumbra.OpenModPage(mod);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
Im.Tooltip.OnHover($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
if (config.UseTemporarySettings)
{
ImGui.TableNextColumn();
table.NextColumn();
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 });
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);
}
ImGui.TableNextColumn();
table.NextColumn();
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 });
ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8);
ImGui.TableNextColumn();
Im.Tooltip.OnHover("Force the mod to inherit its settings from inherited collections."u8);
table.NextColumn();
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 });
ImGui.TableNextColumn();
table.NextColumn();
var priority = settings.Priority;
ImGui.SetNextItemWidth(Im.ContentRegion.Available.X);
if (ImUtf8.InputScalarOnDeactivated("##Priority"u8, ref priority))
Im.Item.SetNextWidthFull();
if (ImEx.InputOnDeactivation.Scalar("##Priority"u8, ref priority))
updatedMod = (mod, settings with { Priority = priority });
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(Im.ContentRegion.Available.X, 0), string.Empty,
!penumbra.Available))
table.NextColumn();
if (ImEx.Button("Apply"u8, Im.ContentRegion.Available with { Y = 0 }, StringU8.Empty, !penumbra.Available))
{
var text = penumbra.SetMod(mod, settings, StateSource.Manual, false);
if (text.Length > 0)
@ -236,40 +219,39 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
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;
using var t = ImRaii.Tooltip();
ImGui.TextUnformatted("This will also try to apply the following settings to the current collection:");
using var t = Im.Tooltip.Begin();
Im.Text("This will also try to apply the following settings to the current collection:"u8);
Im.Line.New();
using (var _ = ImRaii.Group())
using (Im.Group())
{
ModCombo.DrawSettingsLeft(settings);
}
ImGui.SameLine(Im.ContentRegion.Available.X / 2);
using (var _ = ImRaii.Group())
Im.Line.Same(Im.ContentRegion.Available.X / 2);
using (Im.Group())
{
ModCombo.DrawSettingsRight(settings);
}
}
private void DrawNewModRow()
private void DrawNewModRow(in Im.TableDisposable table)
{
var currentName = _modCombo.CurrentSelection.Mod.Name;
ImGui.TableNextColumn();
var tt = currentName.IsNullOrEmpty()
? "Please select a mod first."
table.NextColumn();
var tt = string.IsNullOrEmpty(currentName)
? "Please select a mod first."u8
: selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod)
? "The design already contains an association with the selected mod."
: string.Empty;
? "The design already contains an association with the selected mod."u8
: StringU8.Empty;
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(Im.Style.FrameHeight), tt, tt.Length > 0,
true))
if (ImEx.Icon.Button(LunaStyle.AddObjectIcon, tt, tt.Length > 0))
manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
ImGui.TableNextColumn();
_modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty,
table.NextColumn();
_modCombo.Draw("##new", string.IsNullOrEmpty(currentName) ? "Select new Mod..." : currentName, string.Empty,
Im.ContentRegion.Available.X, Im.Style.TextHeight);
}
}

2
Luna

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