Finish NPC Tab update.

This commit is contained in:
Ottermandias 2026-02-14 21:22:49 +01:00
parent ea573ccb6b
commit f2ac7e3353
14 changed files with 544 additions and 402 deletions

View file

@ -41,7 +41,6 @@ public enum DesignPanelFlag : uint
public static partial class DesignPanelFlagExtensions public static partial class DesignPanelFlagExtensions
{ {
private static readonly StringU8 Expand = new("Expand"u8); private static readonly StringU8 Expand = new("Expand"u8);
private static readonly StringU8 AdvancedCustomization = DesignPanelFlag.AdvancedCustomizations.ToNameU8();
public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config) public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config)
{ {
@ -56,12 +55,13 @@ public static partial class DesignPanelFlagExtensions
Action<DesignPanelFlag> setterExpand) Action<DesignPanelFlag> setterExpand)
{ {
var checkBoxWidth = Math.Max(Im.Style.FrameHeight, Expand.CalculateSize().X); var checkBoxWidth = Math.Max(Im.Style.FrameHeight, Expand.CalculateSize().X);
var textWidth = AdvancedCustomization.CalculateSize().X; var test = DesignPanelFlag.AdvancedCustomizations.ToNameU8();
var textWidth = AdvancedCustomizations_Name__GenU8.CalculateSize().X;
var tableSize = 2 * (textWidth + 2 * checkBoxWidth) var tableSize = 2 * (textWidth + 2 * checkBoxWidth)
+ 10 * Im.Style.CellPadding.X + 10 * Im.Style.CellPadding.X
+ 2 * Im.Style.WindowPadding.X + 2 * Im.Style.WindowPadding.X
+ 2 * Im.Style.FrameBorderThickness; + 2 * Im.Style.FrameBorderThickness;
using var table = Im.Table.Begin(label, 6, TableFlags.RowBackground | TableFlags.SizingFixedFit | TableFlags.Borders, using var table = Im.Table.Begin(label, 6, TableFlags.RowBackground | TableFlags.Borders,
new Vector2(tableSize, 6 * Im.Style.FrameHeight)); new Vector2(tableSize, 6 * Im.Style.FrameHeight));
if (!table) if (!table)
return; return;

View file

@ -1,27 +1,29 @@
using Glamourer.Designs; using Glamourer.Designs;
using ImSharp; using ImSharp;
using OtterGui;
using OtterGui.Widgets;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) : public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) : SimpleFilterCombo<string>(SimpleFilterType.Text)
FilterComboCache<string>(skipAutomatic
? designColors.Keys.OrderBy(k => k)
: designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
MouseWheelType.Control, Glamourer.Log)
{ {
protected override bool DrawSelectable(int globalIdx, bool selected) public override StringU8 DisplayString(in string value)
=> new(value);
public override string FilterString(in string value)
=> value;
public override IEnumerable<string> GetBaseItems()
=> skipAutomatic ? designColors.Keys.OrderBy(k => k) : designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName);
public override ColorParameter TextColor(in string value)
=> value is DesignColors.AutomaticName ? ColorParameter.Default : designColors[value];
protected override bool DrawItem(in SimpleCacheItem<string> item, int globalIndex, bool selected)
{ {
var isAutomatic = !skipAutomatic && globalIdx is 0; var isAutomatic = !skipAutomatic && globalIndex is 0;
var key = Items[globalIdx]; var ret = base.DrawItem(item, globalIndex, selected);
var color = isAutomatic ? 0 : designColors[key];
using var c = ImGuiColor.Text.Push(color, !color.IsTransparent);
var ret = base.DrawSelectable(globalIdx, selected);
if (isAutomatic) if (isAutomatic)
ImGuiUtil.HoverTooltip( Im.Tooltip.OnHover(
"The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."u8);
return ret; return ret;
} }
} }

View file

@ -167,17 +167,13 @@ public class DesignDetailTab
"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); ImUtf8.DrawFrameColumn("Color"u8);
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n" if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color,
+ "Right-Click to revert to automatic coloring.\n" "Associate a color with this design.\n"u8
+ "Hold Control and scroll the mousewheel to scroll.", + "Right-Click to revert to automatic coloring.\n"u8
width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight) + "Hold Control and scroll the mousewheel to scroll."u8,
&& _colorCombo.CurrentSelection != null) width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName))
{ _manager.ChangeColor(_selector.Selected!, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName);
colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
_manager.ChangeColor(_selector.Selected!, colorName);
}
if (Im.Item.RightClicked()) if (Im.Item.RightClicked())
_manager.ChangeColor(_selector.Selected!, string.Empty); _manager.ChangeColor(_selector.Selected!, string.Empty);

View file

@ -315,30 +315,33 @@ public class MultiDesignPanel(
Im.Separator(); Im.Separator();
} }
private string _colorComboSelection = string.Empty;
private void DrawMultiColor(Vector2 width, float offset) private void DrawMultiColor(Vector2 width, float offset)
{ {
ImUtf8.TextFrameAligned("Multi Colors:"u8); ImUtf8.TextFrameAligned("Multi Colors:"u8);
ImGui.SameLine(offset, Im.Style.ItemSpacing.X); ImGui.SameLine(offset, Im.Style.ItemSpacing.X);
_colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", if (_colorCombo.Draw("##color"u8, _colorComboSelection, "Select a design color."u8,
Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), Im.Style.TextHeight); Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), out var newSelection))
_colorComboSelection = newSelection;
UpdateColorCache(); UpdateColorCache();
var label = _addDesigns.Count > 0 var label = _addDesigns.Count > 0
? $"Set for {_addDesigns.Count} Designs" ? $"Set for {_addDesigns.Count} Designs"
: "Set"; : "Set";
var tooltip = _addDesigns.Count == 0 var tooltip = _addDesigns.Count is 0
? _colorCombo.CurrentSelection switch ? _colorComboSelection switch
{ {
null => "No color specified.", null => "No color specified.",
DesignColors.AutomaticName => "Use the other button to set to automatic.", DesignColors.AutomaticName => "Use the other button to set to automatic.",
_ => $"All designs selected are already set to the color \"{_colorCombo.CurrentSelection}\".", _ => $"All designs selected are already set to the color \"{_colorComboSelection}\".",
} }
: $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; : $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}";
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) if (ImEx.Button(label, width, tooltip, _addDesigns.Count is 0))
{ {
foreach (var design in _addDesigns) foreach (var design in _addDesigns)
editor.ChangeColor(design, _colorCombo.CurrentSelection!); editor.ChangeColor(design, _colorComboSelection!);
} }
label = _removeDesigns.Count > 0 label = _removeDesigns.Count > 0
@ -348,7 +351,7 @@ public class MultiDesignPanel(
? "No selected design is set to a non-automatic color." ? "No selected design is set to a non-automatic color."
: $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}";
Im.Line.Same(); Im.Line.Same();
if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) if (ImEx.Button(label, width, tooltip, _removeDesigns.Count is 0))
{ {
foreach (var (design, _) in _removeDesigns) foreach (var (design, _) in _removeDesigns)
editor.ChangeColor(design, string.Empty); editor.ChangeColor(design, string.Empty);
@ -503,7 +506,7 @@ public class MultiDesignPanel(
{ {
_addDesigns.Clear(); _addDesigns.Clear();
_removeDesigns.Clear(); _removeDesigns.Clear();
var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection;
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>())
{ {
if (leaf.Value.Color.Length > 0) if (leaf.Value.Color.Length > 0)

View file

@ -36,7 +36,7 @@ public class LocalNpcAppearanceData : ISavable
private Rgba32 GetColor(string color, bool favorite, ObjectKind kind) private Rgba32 GetColor(string color, bool favorite, ObjectKind kind)
{ {
if (color.Length == 0) if (color.Length is 0)
{ {
if (favorite) if (favorite)
return ColorId.FavoriteStarOn.Value(); return ColorId.FavoriteStarOn.Value();

View file

@ -0,0 +1,14 @@
using Glamourer.GameData;
using ImSharp;
namespace Glamourer.Gui.Tabs.NpcTab;
public readonly struct NpcCacheItem(in NpcData npc, string colorText, Rgba32 color, bool favorite)
{
public readonly StringPair Name = new(npc.Name);
public readonly StringU8 Id = new($"({npc.Id})");
public readonly NpcData Npc = npc;
public readonly string ColorText = colorText;
public readonly Vector4 Color = color.ToVector();
public readonly bool Favorite = favorite;
}

View file

@ -1,44 +1,92 @@
using Glamourer.Designs; using ImSharp;
using Glamourer.GameData; using Luna;
using OtterGui.Classes;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility<NpcData> public sealed class NpcFilter : TokenizedFilter<NpcFilter.TokenType, NpcCacheItem, NpcFilter.NpcFilterToken>, IUiService
{ {
protected override string Tooltip public enum TokenType : byte
=> "Filter NPC appearances for those where their names contain the given substring.\n"
+ "Enter i:[number] to filter for NPCs of certain IDs.\n"
+ "Enter c:[string] to filter for NPC appearances set to specific colors.";
protected override (LowerString, long, int) FilterChange(string input)
=> input.Length switch
{
0 => (LowerString.Empty, 0, -1),
> 1 when input[1] == ':' =>
input[0] switch
{
'i' or 'I' => input.Length == 2 ? (LowerString.Empty, 0, -1) :
long.TryParse(input.AsSpan(2), out var r) ? (LowerString.Empty, r, 1) : (LowerString.Empty, 0, -1),
'c' or 'C' => input.Length == 2 ? (LowerString.Empty, 0, -1) : (new LowerString(input[2..]), 0, 2),
_ => (new LowerString(input), 0, 0),
},
_ => (new LowerString(input), 0, 0),
};
public override bool ApplyFilter(in NpcData value)
=> FilterMode switch
{
-1 => false,
0 => Filter.IsContained(value.Name),
1 => value.Id.Id == NumericalFilter,
2 => Filter.IsContained(GetColor(value)),
_ => false, // Should never happen
};
private string GetColor(in NpcData value)
{ {
var color = _favorites.GetColor(value); Name,
return color.Length == 0 ? DesignColors.AutomaticName : color; Id,
Color,
} }
protected override void DrawTooltip()
{
if (!Im.Item.Hovered())
return;
using var style = Im.Style.PushDefault();
using var tt = Im.Tooltip.Begin();
Im.Text("Filter NPC appearances for those where their names contain the given substring."u8);
ImEx.TextMultiColored("Enter "u8).Then("i:[number]"u8, ColorId.TriStateCheck.Value()).Then(" to filter for NPCs of certain IDs."u8)
.End();
ImEx.TextMultiColored("Enter "u8).Then("c:[string]"u8, ColorId.TriStateCheck.Value())
.Then(" to filter for NPC appearances set to specific colors."u8).End();
}
public readonly struct NpcFilterToken() : IFilterToken<TokenType, NpcFilterToken>
{
public string Needle { get; init; } = string.Empty;
public uint ParsedNeedle { get; private init; } = 0;
public TokenType Type { get; init; }
public bool Contains(NpcFilterToken other)
{
if (Type != other.Type)
return false;
if (Type is TokenType.Id)
return ParsedNeedle == other.ParsedNeedle;
return Needle.Contains(other.Needle);
}
public static bool ConvertToken(char tokenCharacter, out TokenType type)
{
type = tokenCharacter switch
{
'i' or 'I' => TokenType.Id,
'c' or 'C' => TokenType.Color,
_ => TokenType.Name,
};
return type is not TokenType.Name;
}
public static bool AllowsNone(TokenType type)
=> false;
public static void ProcessList(List<NpcFilterToken> list)
{
for (var i = 0; i < list.Count; ++i)
{
var entry = list[i];
if (entry.Type is not TokenType.Id)
continue;
if (!uint.TryParse(entry.Needle, out var value))
list.RemoveAt(i--);
else
list[i] = new NpcFilterToken
{
ParsedNeedle = value,
Type = TokenType.Id,
};
}
}
}
protected override bool Matches(in NpcFilterToken token, in NpcCacheItem npcCacheItem)
{
return token.Type switch
{
TokenType.Name => npcCacheItem.Name.Utf16.Contains(token.Needle, StringComparison.InvariantCultureIgnoreCase),
TokenType.Id => npcCacheItem.Npc.Id == token.ParsedNeedle,
TokenType.Color => npcCacheItem.ColorText.Contains(token.Needle, StringComparison.InvariantCultureIgnoreCase),
_ => false,
};
}
protected override bool MatchesNone(TokenType type, bool negated, in NpcCacheItem npcCacheItem)
=> false;
} }

View file

@ -0,0 +1,126 @@
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcHeader : SplitButtonHeader
{
private readonly NpcSelection _selection;
public NpcHeader(NpcSelection selection, LocalNpcAppearanceData favorites, DesignManager designs)
{
_selection = selection;
LeftButtons.AddButton(new ExportToClipboardButton(selection), 100);
LeftButtons.AddButton(new SaveAsDesignButton(selection, designs), 50);
RightButtons.AddButton(new FavoriteButton(selection, favorites), 0);
}
public override ReadOnlySpan<byte> Text
=> _selection.HasSelection ? _selection.Name : "No Selection"u8;
public override ColorParameter TextColor
=> ColorId.NormalDesign.Value();
public override void Draw(Vector2 size)
{
var color = ColorId.HeaderButtons.Value();
using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color);
base.Draw(size with { Y = Im.Style.FrameHeight });
}
private sealed class FavoriteButton(NpcSelection selection, LocalNpcAppearanceData favorites) : BaseIconButton<AwesomeIcon>
{
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
selection.Favorite ? "Remove this NPC appearance from your favorites."u8 : "Add this NPC Appearance to your favorites."u8);
public override AwesomeIcon Icon
=> LunaStyle.FavoriteIcon;
public override bool IsVisible
=> selection.HasSelection;
public override void OnClick()
=> favorites.ToggleFavorite(selection.Data);
protected override void PreDraw()
=> ImGuiColor.Text.Push(selection.Favorite ? ColorId.FavoriteStarOn.Value() : 0x80000000);
protected override void PostDraw()
=> Im.ColorDisposable.PopUnsafe();
}
private sealed class ExportToClipboardButton(NpcSelection selection) : BaseIconButton<AwesomeIcon>
{
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
"Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."u8);
public override AwesomeIcon Icon
=> LunaStyle.ToClipboardIcon;
public override bool IsVisible
=> selection.HasSelection;
public override void OnClick()
{
try
{
var text = selection.ToBase64();
Im.Clipboard.Set(text);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {selection.Data.Name}'s data to clipboard.",
$"Could not copy data from NPC appearance {selection.Data.Kind} {selection.Data.Id.Id} to clipboard",
NotificationType.Error);
}
}
}
private sealed class SaveAsDesignButton(NpcSelection selection, DesignManager designs) : BaseIconButton<AwesomeIcon>
{
private StringU8 _newName = StringU8.Empty;
private DesignBase? _newDesign;
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
"Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."u8);
public override AwesomeIcon Icon
=> LunaStyle.SaveIcon;
public override bool IsVisible
=> selection.HasSelection;
public override void OnClick()
{
Im.Popup.Open("Save as Design"u8);
_newName = new StringU8(selection.Data.Name);
_newDesign = selection.ToDesignBase();
}
protected override void PostDraw()
{
if (!InputPopup.OpenName("Save as Design"u8, _newName, out var name))
return;
if (_newDesign is not null && name.Length > 0)
designs.CreateClone(_newDesign, name, true);
_newDesign = null;
_newName = StringU8.Empty;
}
}
}

View file

@ -1,7 +1,4 @@
using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
@ -9,126 +6,38 @@ using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.State; using Glamourer.State;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using static Glamourer.Gui.Tabs.HeaderDrawer;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
public class NpcPanel public sealed class NpcPanel(
Configuration config,
NpcSelection selection,
CustomizationDrawer customizeDrawer,
EquipmentDrawer equipmentDrawer,
ActorObjectManager objects,
StateManager stateManager,
LocalNpcAppearanceData favorites,
DesignColors designColors) : IPanel
{ {
private readonly Configuration _config; private readonly DesignColorCombo _combo = new(designColors, true);
private readonly DesignColorCombo _colorCombo;
private string _newName = string.Empty;
private DesignBase? _newDesign;
private readonly NpcSelector _selector;
private readonly LocalNpcAppearanceData _favorites;
private readonly CustomizationDrawer _customizeDrawer;
private readonly EquipmentDrawer _equipDrawer;
private readonly DesignConverter _converter;
private readonly DesignManager _designManager;
private readonly StateManager _state;
private readonly ActorObjectManager _objects;
private readonly DesignColors _colors;
private readonly Button[] _leftButtons;
private readonly Button[] _rightButtons;
public NpcPanel(NpcSelector selector, public ReadOnlySpan<byte> Id
LocalNpcAppearanceData favorites, => "NpcPanel"u8;
CustomizationDrawer customizeDrawer,
EquipmentDrawer equipDrawer,
DesignConverter converter,
DesignManager designManager,
StateManager state,
ActorObjectManager objects,
DesignColors colors,
Configuration config)
{
_selector = selector;
_favorites = favorites;
_customizeDrawer = customizeDrawer;
_equipDrawer = equipDrawer;
_converter = converter;
_designManager = designManager;
_state = state;
_objects = objects;
_colors = colors;
_config = config;
_colorCombo = new DesignColorCombo(colors, true);
_leftButtons =
[
new ExportToClipboardButton(this),
new SaveAsDesignButton(this),
];
_rightButtons =
[
new FavoriteButton(this),
];
}
public void Draw() public void Draw()
{ {
using var group = ImRaii.Group(); using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.None, Im.ContentRegion.Available);
if (!table || !selection.HasSelection)
DrawHeader();
DrawPanel();
}
private void DrawHeader()
{
HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value().Color,
ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons);
SaveDesignDrawPopup();
}
private sealed class FavoriteButton(NpcPanel panel) : Button
{
protected override string Description
=> panel._favorites.IsFavorite(panel._selector.Selection)
? "Remove this NPC appearance from your favorites."
: "Add this NPC Appearance to your favorites.";
protected override Rgba32 TextColor
=> panel._favorites.IsFavorite(panel._selector.Selection)
? ColorId.FavoriteStarOn.Value()
: 0x80000000;
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Star;
public override bool Visible
=> panel._selector.HasSelection;
protected override void OnClick()
=> panel._favorites.ToggleFavorite(panel._selector.Selection);
}
private void SaveDesignDrawPopup()
{
if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName))
return; return;
if (_newDesign != null && _newName.Length > 0) table.SetupScrollFreeze(0, 1);
_designManager.CreateClone(_newDesign, _newName, true); table.NextColumn();
_newDesign = null;
_newName = string.Empty;
}
private void DrawPanel()
{
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available);
if (!table || !_selector.HasSelection)
return;
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableNextColumn();
Im.Dummy(Vector2.Zero); Im.Dummy(Vector2.Zero);
DrawButtonRow(); DrawButtonRow();
ImGui.TableNextColumn(); table.NextColumn();
DrawCustomization(); DrawCustomization();
DrawEquipment(); DrawEquipment();
DrawAppearanceInfo(); DrawAppearanceInfo();
@ -143,96 +52,82 @@ public class NpcPanel
private void DrawCustomization() private void DrawCustomization()
{ {
if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) if (config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization))
return; return;
var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); var expand = config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
using var h = Im.Tree.HeaderId(_selector.Selection.ModelId is 0 using var h = Im.Tree.HeaderId(selection.Data.ModelId is 0
? "Customization"u8 ? "Customization"u8
: $"Customization (Model Id #{_selector.Selection.ModelId})###Customization", : $"Customization (Model Id #{selection.Data.ModelId})###Customization",
expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None);
if (!h) if (!h)
return; return;
_customizeDrawer.Draw(_selector.Selection.Customize, true, true); customizeDrawer.Draw(selection.Data.Customize, true, true);
Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
} }
private void DrawEquipment() private void DrawEquipment()
{ {
using var h = DesignPanelFlag.Equipment.Header(_config); using var h = DesignPanelFlag.Equipment.Header(config);
if (!h) if (!h)
return; return;
_equipDrawer.Prepare(); equipmentDrawer.Prepare();
var designData = ToDesignData(); var designData = selection.ToDesignData();
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var data = new EquipDrawData(slot, designData) { Locked = true }; var data = new EquipDrawData(slot, designData) { Locked = true };
_equipDrawer.DrawEquip(data); equipmentDrawer.DrawEquip(data);
} }
var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true }; var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true };
var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true }; var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true };
_equipDrawer.DrawWeapons(mainhandData, offhandData, false); equipmentDrawer.DrawWeapons(mainhandData, offhandData, false);
Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, selection.Data.VisorToggled));
Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
} }
private DesignData ToDesignData()
{
var selection = _selector.Selection;
var items = _converter.FromDrawData(selection.Equip(), selection.Mainhand, selection.Offhand, true).ToArray();
var designData = new DesignData { Customize = selection.Customize };
foreach (var (slot, item, stain) in items)
{
designData.SetItem(slot, item);
designData.SetStain(slot, stain);
}
return designData;
}
private void DrawApplyToSelf() private void DrawApplyToSelf()
{ {
var (id, data) = _objects.PlayerData; var (id, data) = objects.PlayerData;
if (!ImUtf8.ButtonEx("Apply to Yourself"u8, if (!ImEx.Button("Apply to Yourself"u8, Vector2.Zero,
"Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8,
Vector2.Zero, !data.Valid)) !data.Valid))
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (stateManager.GetOrCreate(id, data.Objects[0], out var state))
{ {
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); var design = selection.ToDesignBase();
_state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
} }
} }
private void DrawApplyToTarget() private void DrawApplyToTarget()
{ {
var (id, data) = _objects.TargetData; var (id, data) = objects.TargetData;
var tt = id.IsValid var tt = id.IsValid
? data.Valid ? data.Valid
? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8 ? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8
: "The current target can not be manipulated."u8 : "The current target can not be manipulated."u8
: "No valid target selected."u8; : "No valid target selected."u8;
if (!ImUtf8.ButtonEx("Apply to Target"u8, tt, Vector2.Zero, !data.Valid)) if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, !data.Valid))
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (stateManager.GetOrCreate(id, data.Objects[0], out var state))
{ {
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); var design = selection.ToDesignBase();
_state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
} }
} }
private void DrawAppearanceInfo() private void DrawAppearanceInfo()
{ {
using var h = DesignPanelFlag.AppearanceDetails.Header(_config); using var h = DesignPanelFlag.AppearanceDetails.Header(config);
if (!h) if (!h)
return; return;
@ -240,112 +135,64 @@ public class NpcPanel
if (!table) if (!table)
return; return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f));
table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Last Update Datem"u8).X); table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateButtonSize("Last Update Date"u8).X);
table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch); table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch);
var selection = _selector.Selection; CopyButton(table, "NPC Name"u8, selection.Name);
CopyButton("NPC Name"u8, selection.Name); CopyButton(table, "NPC ID"u8, $"{selection.Data.Id.Id}");
CopyButton("NPC ID"u8, selection.Id.Id.ToString()); table.DrawFrameColumn("NPC Type"u8);
ImGuiUtil.DrawFrameColumn("NPC Type"); table.NextColumn();
ImGui.TableNextColumn();
var width = Im.ContentRegion.Available.X; var width = Im.ContentRegion.Available.X;
ImEx.TextFramed(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC"u8 : "Event NPC"u8, new Vector2(width, 0), ImEx.TextFramed(selection.Data.Kind is ObjectKind.BattleNpc ? "Battle NPC"u8 : "Event NPC"u8, new Vector2(width, 0),
ImGuiColor.FrameBackground.Get()); ImGuiColor.FrameBackground.Get());
ImUtf8.DrawFrameColumn("Color"u8); table.DrawFrameColumn("Color"u8);
var color = _favorites.GetColor(selection); table.NextColumn();
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; var color = selection.ColorText;
ImGui.TableNextColumn(); if (_combo.Draw("##colorCombo"u8, selection.ColorTextU8,
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this NPC appearance.\n"u8
"Associate a color with this NPC appearance.\n" + "Right-Click to revert to automatic coloring.\n"u8
+ "Right-Click to revert to automatic coloring.\n" + "Hold Control and scroll the mousewheel to scroll."u8,
+ "Hold Control and scroll the mousewheel to scroll.", width - Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight, out var newColorText))
width - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight) favorites.SetColor(selection.Data, newColorText.Item == DesignColors.AutomaticName ? string.Empty : newColorText.Item);
&& _colorCombo.CurrentSelection != null)
{
color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
_favorites.SetColor(selection, color);
}
if (Im.Item.RightClicked()) if (Im.Item.RightClicked())
{ {
_favorites.SetColor(selection, string.Empty); favorites.SetColor(selection.Data, string.Empty);
color = string.Empty; color = string.Empty;
} }
if (_colors.TryGetValue(color, out var currentColor)) if (designColors.TryGetValue(color, out var currentColor))
{ {
Im.Line.Same(); Im.Line.SameInner();
if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor)) if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor))
_colors.SetColor(color, newColor); designColors.SetColor(color, newColor);
} }
else if (color.Length is not 0) else if (color.Length is not 0)
{ {
Im.Line.Same(); Im.Line.SameInner();
var size = new Vector2(Im.Style.FrameHeight); var size = new Vector2(Im.Style.FrameHeight);
using var font = ImRaii.PushFont(UiBuilder.IconFont); using (AwesomeIcon.Font.Push())
ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, _colors.MissingColor); {
ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, designColors.MissingColor);
}
Im.Tooltip.OnHover("The color associated with this design does not exist."u8);
} }
return; return;
static void CopyButton(ReadOnlySpan<byte> label, string text) static void CopyButton(in Im.TableDisposable table, ReadOnlySpan<byte> label, Utf8StringHandler<HintStringHandlerBuffer> text)
{ {
ImUtf8.DrawFrameColumn(label); table.DrawFrameColumn(label);
ImGui.TableNextColumn(); table.NextColumn();
if (ImUtf8.Button(text, new Vector2(Im.ContentRegion.Available.X, 0))) if (!text.GetSpan(out var span))
ImUtf8.SetClipboardText(text); return;
ImUtf8.HoverTooltip("Click to copy to clipboard."u8);
}
}
private sealed class ExportToClipboardButton(NpcPanel panel) : Button if (Im.Button(span, Im.ContentRegion.Available with { Y = 0 }))
{ Im.Clipboard.Set(span);
protected override string Description Im.Tooltip.OnHover("Click to copy to clipboard."u8);
=> "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Copy;
public override bool Visible
=> panel._selector.HasSelection;
protected override void OnClick()
{
try
{
var data = panel.ToDesignData();
var text = panel._converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
ImGui.SetClipboardText(text);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selection.Name}'s data to clipboard.",
$"Could not copy data from NPC appearance {panel._selector.Selection.Kind} {panel._selector.Selection.Id.Id} to clipboard",
NotificationType.Error);
}
}
}
private sealed class SaveAsDesignButton(NpcPanel panel) : Button
{
protected override string Description
=> "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.";
protected override FontAwesomeIcon Icon
=> FontAwesomeIcon.Save;
public override bool Visible
=> panel._selector.HasSelection;
protected override void OnClick()
{
ImGui.OpenPopup("Save as Design");
panel._newName = panel._selector.Selection.Name;
var data = panel.ToDesignData();
panel._newDesign = panel._converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
} }
} }
} }

View file

@ -0,0 +1,97 @@
using Glamourer.Designs;
using Glamourer.GameData;
using ImSharp;
using Luna;
namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcSelection : IUiService, IDisposable
{
private readonly LocalNpcAppearanceData _data;
private readonly DesignColors _colors;
private readonly DesignConverter _converter;
public NpcSelection(LocalNpcAppearanceData data, DesignColors colors, DesignConverter converter)
{
_data = data;
_colors = colors;
_converter = converter;
_data.DataChanged += OnDataChanged;
_colors.ColorChanged += OnColorChanged;
}
private void OnColorChanged()
{
if (!HasSelection)
return;
Color = _data.GetData(Data).Color.ToVector();
}
private void OnDataChanged()
{
if (!HasSelection)
return;
ColorText = _data.GetColor(Data);
ColorTextU8 = ColorText.Length is 0 ? DesignColors.AutomaticNameU8 : new StringU8(ColorText);
(var color, Favorite) = _data.GetData(Data);
Color = color.ToVector();
}
public NpcData Data { get; private set; }
public StringU8 Name { get; private set; } = StringU8.Empty;
public bool Favorite { get; private set; } = false;
public string ColorText { get; private set; } = string.Empty;
public StringU8 ColorTextU8 { get; private set; } = DesignColors.AutomaticNameU8;
public Vector4 Color { get; private set; }
public uint Id
=> Data.Id;
public bool HasSelection
=> Id is not 0;
public DesignData ToDesignData()
{
var items = _converter.FromDrawData(Data.Equip(), Data.Mainhand, Data.Offhand, true).ToArray();
var designData = new DesignData { Customize = Data.Customize };
foreach (var (slot, item, stain) in items)
{
designData.SetItem(slot, item);
designData.SetStain(slot, stain);
}
return designData;
}
public DesignBase ToDesignBase()
{
var data = ToDesignData();
return _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
}
public string ToBase64()
{
var data = ToDesignData();
return _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
}
public void Update(in NpcCacheItem item)
{
Data = item.Npc;
Name = item.Name.Utf8;
Favorite = item.Favorite;
ColorText = item.ColorText;
ColorTextU8 = ColorText.Length > 0 ? new StringU8(ColorText) : DesignColors.AutomaticNameU8;
Color = item.Color;
}
public void Dispose()
{
_data.DataChanged -= OnDataChanged;
_colors.ColorChanged -= OnColorChanged;
}
}

View file

@ -1,92 +1,85 @@
using Glamourer.GameData; using Glamourer.GameData;
using Dalamud.Bindings.ImGui; using Glamourer.Designs;
using ImSharp; using ImSharp;
using OtterGui.Extensions; using Luna;
using OtterGui.Raii;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
public class NpcSelector : IDisposable public sealed class NpcSelector(
NpcCustomizeSet npcs,
LocalNpcAppearanceData favorites,
NpcFilter filter,
DesignColors designColors,
NpcSelection selection) : IPanel
{ {
private readonly NpcCustomizeSet _npcs; private readonly NpcCustomizeSet _npcs = npcs;
private readonly LocalNpcAppearanceData _favorites; private readonly LocalNpcAppearanceData _favorites = favorites;
private readonly NpcFilter _filter = filter;
private readonly DesignColors _designColors = designColors;
private NpcFilter _filter; public ReadOnlySpan<byte> Id
private readonly List<int> _visibleOrdered = []; => "NpcSelector"u8;
private int _selectedGlobalIndex;
private bool _listDirty = true;
private Vector2 _defaultItemSpacing;
private float _width;
public void Draw()
public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites)
{ {
_npcs = npcs; Im.Cursor.Y += Im.Style.FramePadding.Y;
_favorites = favorites; var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(this));
_filter = new NpcFilter(_favorites); using var clipper = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing);
_favorites.DataChanged += OnFavoriteChange; using var color = new Im.ColorDisposable();
foreach (var item in clipper.Iterate(cache))
{
Im.Cursor.X += Im.Style.FramePadding.X;
using var id = Im.Id.Push(item.Npc.Id.Id);
color.Push(ImGuiColor.Text, item.Color);
if (Im.Selectable(item.Name.Utf8, item.Npc.Id == selection.Id))
selection.Update(item);
color.Pop();
var size = item.Id.CalculateSize();
Im.Line.Same();
if (Im.ContentRegion.Available.X >= size.X)
{
color.Push(ImGuiColor.Text, Im.Style[ImGuiColor.TextDisabled]);
ImEx.TextRightAligned(item.Id, 0, size.X);
color.Pop();
}
else
{
Im.Tooltip.OnHover(item.Id);
Im.Line.New();
}
}
} }
public void Dispose() private NpcCacheItem CreateItem(in NpcData data)
{ {
_favorites.DataChanged -= OnFavoriteChange; var colorText = _favorites.GetColor(data);
var (color, favorite) = _favorites.GetData(data);
return new NpcCacheItem(data, colorText, color, favorite);
} }
private void OnFavoriteChange() private sealed class Cache : BasicFilterCache<NpcCacheItem>
=> _listDirty = true;
public void UpdateList()
{ {
if (!_listDirty) private readonly NpcSelector _parent;
return;
_listDirty = false; public Cache(NpcSelector parent)
_visibleOrdered.Clear(); : base(parent._filter)
var enumerable = _npcs.WithIndex(); {
if (!_filter.IsEmpty) _parent = parent;
enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value)); _parent._favorites.DataChanged += OnDataChange;
var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value)) _parent._designColors.ColorChanged += OnDataChange;
.ThenBy(d => d.Index) }
.Select(d => d.Index);
_visibleOrdered.AddRange(range);
}
public bool HasSelection private void OnDataChange()
=> _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count; => Dirty |= IManagedCache.DirtyFlags.Custom;
public NpcData Selection protected override void Dispose(bool disposing)
=> HasSelection ? _npcs[_selectedGlobalIndex] : default; {
_parent._favorites.DataChanged -= OnDataChange;
_parent._designColors.ColorChanged -= OnDataChange;
base.Dispose(disposing);
}
public void Draw(float width) protected override IEnumerable<NpcCacheItem> GetItems()
{ => _parent._npcs.Select(n => _parent.CreateItem(n)).OrderByDescending(n => n.Favorite);
_width = width;
using var group = ImRaii.Group();
_defaultItemSpacing = Im.Style.ItemSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
if (_filter.Draw(width))
_listDirty = true;
UpdateList();
DrawSelector();
}
private void DrawSelector()
{
using var child = ImRaii.Child("##Selector", new Vector2(_width, Im.ContentRegion.Available.Y), true);
if (!child)
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
ImGuiClip.ClippedDraw(_visibleOrdered, DrawSelectable, Im.Style.TextHeight);
}
private void DrawSelectable(int globalIndex)
{
using var id = Im.Id.Push(globalIndex);
using var color = ImGuiColor.Text.Push(_favorites.GetData(_npcs[globalIndex]).Color);
if (ImGui.Selectable(_npcs[globalIndex].Name, _selectedGlobalIndex == globalIndex, ImGuiSelectableFlags.AllowItemOverlap))
_selectedGlobalIndex = globalIndex;
} }
} }

View file

@ -3,18 +3,24 @@ using Luna;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcTab(NpcSelector selector, NpcPanel panel) : ITab<MainTabType> public sealed class NpcTab : TwoPanelLayout, ITab<MainTabType>
{ {
public ReadOnlySpan<byte> Label public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header)
{
LeftHeader = new FilterHeader<NpcCacheItem>(filter, new StringU8("Filter..."u8));
LeftPanel = selector;
LeftFooter = NopHeaderFooter.Instance;
RightHeader = header;
RightPanel = panel;
RightFooter = NopHeaderFooter.Instance;
}
public override ReadOnlySpan<byte> Label
=> "NPCs"u8; => "NPCs"u8;
public MainTabType Identifier public MainTabType Identifier
=> MainTabType.Npcs; => MainTabType.Npcs;
public void DrawContent() public void DrawContent()
{ => Draw(TwoPanelWidth.IndeterminateRelative);
selector.Draw(200 * Im.Style.GlobalScale);
Im.Line.Same();
panel.Draw();
}
} }

View file

@ -299,6 +299,28 @@ public sealed class SettingsTab(
Im.Line.New(); Im.Line.New();
} }
private readonly (StringU8, QdbButtons)[] _columns =
[
(new StringU8("Apply Design"u8), QdbButtons.ApplyDesign),
(new StringU8("Revert All"u8), QdbButtons.RevertAll),
(new StringU8("Revert to Auto"u8), QdbButtons.RevertAutomation),
(new StringU8("Reapply Auto"u8), QdbButtons.ReapplyAutomation),
(new StringU8("Revert Equip"u8), QdbButtons.RevertEquip),
(new StringU8("Revert Customize"u8), QdbButtons.RevertCustomize),
(new StringU8("Revert Advanced Customization"u8), QdbButtons.RevertAdvancedCustomization),
(new StringU8("Revert Advanced Dyes"u8), QdbButtons.RevertAdvancedDyes),
(new StringU8("Reset Settings"u8), QdbButtons.ResetSettings),
];
private static bool DisplayButton(QdbButtons button, bool showAuto, bool useTemporarySettings)
=> button switch
{
QdbButtons.RevertAutomation => showAuto,
QdbButtons.ReapplyAutomation => showAuto,
QdbButtons.ResetSettings => useTemporarySettings,
_ => true,
};
private void DrawQuickDesignBoxes() private void DrawQuickDesignBoxes()
{ {
var showAuto = config.EnableAutoDesigns; var showAuto = config.EnableAutoDesigns;
@ -310,23 +332,11 @@ public sealed class SettingsTab(
if (!table) if (!table)
return; return;
IEnumerable<RefTuple<ReadOnlySpan<byte>, bool, QdbButtons>> columns =
[
RefTuple.Create("Apply Design"u8, true, QdbButtons.ApplyDesign),
RefTuple.Create("Revert All"u8, true, QdbButtons.RevertAll),
RefTuple.Create("Revert to Auto"u8, showAuto, QdbButtons.RevertAutomation),
RefTuple.Create("Reapply Auto"u8, showAuto, QdbButtons.ReapplyAutomation),
RefTuple.Create("Revert Equip"u8, true, QdbButtons.RevertEquip),
RefTuple.Create("Revert Customize"u8, true, QdbButtons.RevertCustomize),
RefTuple.Create("Revert Advanced Customization"u8, true, QdbButtons.RevertAdvancedCustomization),
RefTuple.Create("Revert Advanced Dyes"u8, true, QdbButtons.RevertAdvancedDyes),
RefTuple.Create("Reset Settings"u8, config.UseTemporarySettings, QdbButtons.ResetSettings),
];
// ReSharper disable once PossibleMultipleEnumeration // ReSharper disable once PossibleMultipleEnumeration
foreach (var (text, display, _) in columns) foreach (var (text, flag) in _columns)
{ {
if (!display) if (!DisplayButton(flag, showAuto, config.UseTemporarySettings))
continue; continue;
table.NextColumn(); table.NextColumn();
@ -334,9 +344,9 @@ public sealed class SettingsTab(
} }
// ReSharper disable once PossibleMultipleEnumeration // ReSharper disable once PossibleMultipleEnumeration
foreach (var (_, display, flag) in columns) foreach (var (_, flag) in _columns)
{ {
if (!display) if (!DisplayButton(flag, showAuto, config.UseTemporarySettings))
continue; continue;
using var id = Im.Id.Push((int)flag); using var id = Im.Id.Push((int)flag);

2
Luna

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