diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/DesignPanelFlag.cs index 2416866..a5a353a 100644 --- a/Glamourer/DesignPanelFlag.cs +++ b/Glamourer/DesignPanelFlag.cs @@ -41,7 +41,6 @@ public enum DesignPanelFlag : uint public static partial class DesignPanelFlagExtensions { 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) { @@ -56,12 +55,13 @@ public static partial class DesignPanelFlagExtensions Action setterExpand) { 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) + 10 * Im.Style.CellPadding.X + 2 * Im.Style.WindowPadding.X + 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)); if (!table) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 2e1530d..496a89d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,27 +1,29 @@ using Glamourer.Designs; using ImSharp; -using OtterGui; -using OtterGui.Widgets; -using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) : - FilterComboCache(skipAutomatic - ? designColors.Keys.OrderBy(k => k) - : designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), - MouseWheelType.Control, Glamourer.Log) +public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) : SimpleFilterCombo(SimpleFilterType.Text) { - 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 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 item, int globalIndex, bool selected) { - var isAutomatic = !skipAutomatic && globalIdx is 0; - var key = Items[globalIdx]; - var color = isAutomatic ? 0 : designColors[key]; - using var c = ImGuiColor.Text.Push(color, !color.IsTransparent); - var ret = base.DrawSelectable(globalIdx, selected); + var isAutomatic = !skipAutomatic && globalIndex is 0; + var ret = base.DrawItem(item, globalIndex, selected); if (isAutomatic) - ImGuiUtil.HoverTooltip( - "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); + Im.Tooltip.OnHover( + "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."u8); return ret; } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 3f5d984..4326a4b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -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); ImUtf8.DrawFrameColumn("Color"u8); - var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; ImGui.TableNextColumn(); - if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n" - + "Right-Click to revert to automatic coloring.\n" - + "Hold Control and scroll the mousewheel to scroll.", - width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight) - && _colorCombo.CurrentSelection != null) - { - colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; - _manager.ChangeColor(_selector.Selected!, colorName); - } + 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 + + "Hold Control and scroll the mousewheel to scroll."u8, + width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName)) + _manager.ChangeColor(_selector.Selected!, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName); if (Im.Item.RightClicked()) _manager.ChangeColor(_selector.Selected!, string.Empty); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 17d6c9f..c7ca7c6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -315,30 +315,33 @@ public class MultiDesignPanel( Im.Separator(); } + private string _colorComboSelection = string.Empty; + private void DrawMultiColor(Vector2 width, float offset) { ImUtf8.TextFrameAligned("Multi Colors:"u8); ImGui.SameLine(offset, Im.Style.ItemSpacing.X); - _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", - Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), Im.Style.TextHeight); + if (_colorCombo.Draw("##color"u8, _colorComboSelection, "Select a design color."u8, + Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), out var newSelection)) + _colorComboSelection = newSelection; UpdateColorCache(); var label = _addDesigns.Count > 0 ? $"Set for {_addDesigns.Count} Designs" : "Set"; - var tooltip = _addDesigns.Count == 0 - ? _colorCombo.CurrentSelection switch + var tooltip = _addDesigns.Count is 0 + ? _colorComboSelection switch { null => "No color specified.", 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(); - if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) + if (ImEx.Button(label, width, tooltip, _addDesigns.Count is 0)) { foreach (var design in _addDesigns) - editor.ChangeColor(design, _colorCombo.CurrentSelection!); + editor.ChangeColor(design, _colorComboSelection!); } label = _removeDesigns.Count > 0 @@ -348,7 +351,7 @@ public class MultiDesignPanel( ? "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))}"; 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) editor.ChangeColor(design, string.Empty); @@ -503,7 +506,7 @@ public class MultiDesignPanel( { _addDesigns.Clear(); _removeDesigns.Clear(); - var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; + var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection; foreach (var leaf in selector.SelectedPaths.OfType()) { if (leaf.Value.Color.Length > 0) diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index 0f004bf..4caa246 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -36,7 +36,7 @@ public class LocalNpcAppearanceData : ISavable private Rgba32 GetColor(string color, bool favorite, ObjectKind kind) { - if (color.Length == 0) + if (color.Length is 0) { if (favorite) return ColorId.FavoriteStarOn.Value(); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcCacheItem.cs b/Glamourer/Gui/Tabs/NpcTab/NpcCacheItem.cs new file mode 100644 index 0000000..d5d2515 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcCacheItem.cs @@ -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; +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs index 1b698f9..cab073e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs @@ -1,44 +1,92 @@ -using Glamourer.Designs; -using Glamourer.GameData; -using OtterGui.Classes; +using ImSharp; +using Luna; namespace Glamourer.Gui.Tabs.NpcTab; -public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility +public sealed class NpcFilter : TokenizedFilter, IUiService { - protected override string Tooltip - => "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) + public enum TokenType : byte { - var color = _favorites.GetColor(value); - return color.Length == 0 ? DesignColors.AutomaticName : color; + Name, + 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 + { + 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 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; } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcHeader.cs b/Glamourer/Gui/Tabs/NpcTab/NpcHeader.cs new file mode 100644 index 0000000..29e921e --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcHeader.cs @@ -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 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 + { + 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 + { + 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 + { + 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; + } + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index d549c43..f26e1a4 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,7 +1,4 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; @@ -9,126 +6,38 @@ using Glamourer.Gui.Tabs.DesignTab; using Glamourer.State; using ImSharp; using Luna; -using OtterGui; -using OtterGui.Raii; -using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using static Glamourer.Gui.Tabs.HeaderDrawer; 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 _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; + private readonly DesignColorCombo _combo = new(designColors, true); - public NpcPanel(NpcSelector selector, - LocalNpcAppearanceData favorites, - 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 ReadOnlySpan Id + => "NpcPanel"u8; public void Draw() { - using var group = ImRaii.Group(); - - 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)) + using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.None, Im.ContentRegion.Available); + if (!table || !selection.HasSelection) return; - if (_newDesign != null && _newName.Length > 0) - _designManager.CreateClone(_newDesign, _newName, true); - _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(); + table.SetupScrollFreeze(0, 1); + table.NextColumn(); Im.Dummy(Vector2.Zero); DrawButtonRow(); - ImGui.TableNextColumn(); + table.NextColumn(); DrawCustomization(); DrawEquipment(); DrawAppearanceInfo(); @@ -143,96 +52,82 @@ public class NpcPanel private void DrawCustomization() { - if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + if (config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) return; - var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); - using var h = Im.Tree.HeaderId(_selector.Selection.ModelId is 0 + var expand = config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = Im.Tree.HeaderId(selection.Data.ModelId is 0 ? "Customization"u8 - : $"Customization (Model Id #{_selector.Selection.ModelId})###Customization", + : $"Customization (Model Id #{selection.Data.ModelId})###Customization", expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None); if (!h) return; - _customizeDrawer.Draw(_selector.Selection.Customize, true, true); + customizeDrawer.Draw(selection.Data.Customize, true, true); Im.Dummy(new Vector2(Im.Style.TextHeight / 2)); } private void DrawEquipment() { - using var h = DesignPanelFlag.Equipment.Header(_config); + using var h = DesignPanelFlag.Equipment.Header(config); if (!h) return; - _equipDrawer.Prepare(); - var designData = ToDesignData(); + equipmentDrawer.Prepare(); + var designData = selection.ToDesignData(); foreach (var slot in EquipSlotExtensions.EqdpSlots) { var data = new EquipDrawData(slot, designData) { Locked = true }; - _equipDrawer.DrawEquip(data); + equipmentDrawer.DrawEquip(data); } var mainhandData = new EquipDrawData(EquipSlot.MainHand, 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)); - 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)); } - 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() { - var (id, data) = _objects.PlayerData; - if (!ImUtf8.ButtonEx("Apply to Yourself"u8, + var (id, data) = objects.PlayerData; + 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, - Vector2.Zero, !data.Valid)) + !data.Valid)) 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()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); + var design = selection.ToDesignBase(); + stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } private void DrawApplyToTarget() { - var (id, data) = _objects.TargetData; + var (id, data) = objects.TargetData; var tt = id.IsValid ? data.Valid ? "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 : "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; - 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()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); + var design = selection.ToDesignBase(); + stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } private void DrawAppearanceInfo() { - using var h = DesignPanelFlag.AppearanceDetails.Header(_config); + using var h = DesignPanelFlag.AppearanceDetails.Header(config); if (!h) return; @@ -240,112 +135,64 @@ public class NpcPanel if (!table) return; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Last Update Datem"u8).X); + using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f)); + table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateButtonSize("Last Update Date"u8).X); table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch); - var selection = _selector.Selection; - CopyButton("NPC Name"u8, selection.Name); - CopyButton("NPC ID"u8, selection.Id.Id.ToString()); - ImGuiUtil.DrawFrameColumn("NPC Type"); - ImGui.TableNextColumn(); + CopyButton(table, "NPC Name"u8, selection.Name); + CopyButton(table, "NPC ID"u8, $"{selection.Data.Id.Id}"); + table.DrawFrameColumn("NPC Type"u8); + table.NextColumn(); 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()); - ImUtf8.DrawFrameColumn("Color"u8); - var color = _favorites.GetColor(selection); - var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; - ImGui.TableNextColumn(); - if (_colorCombo.Draw("##colorCombo", colorName, - "Associate a color with this NPC appearance.\n" - + "Right-Click to revert to automatic coloring.\n" - + "Hold Control and scroll the mousewheel to scroll.", - width - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight) - && _colorCombo.CurrentSelection != null) - { - color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; - _favorites.SetColor(selection, color); - } + table.DrawFrameColumn("Color"u8); + table.NextColumn(); + var color = selection.ColorText; + if (_combo.Draw("##colorCombo"u8, selection.ColorTextU8, + "Associate a color with this NPC appearance.\n"u8 + + "Right-Click to revert to automatic coloring.\n"u8 + + "Hold Control and scroll the mousewheel to scroll."u8, + width - Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight, out var newColorText)) + favorites.SetColor(selection.Data, newColorText.Item == DesignColors.AutomaticName ? string.Empty : newColorText.Item); if (Im.Item.RightClicked()) { - _favorites.SetColor(selection, string.Empty); + favorites.SetColor(selection.Data, 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)) - _colors.SetColor(color, newColor); + designColors.SetColor(color, newColor); } else if (color.Length is not 0) { - Im.Line.Same(); - var size = new Vector2(Im.Style.FrameHeight); - using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, _colors.MissingColor); - ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); + Im.Line.SameInner(); + var size = new Vector2(Im.Style.FrameHeight); + using (AwesomeIcon.Font.Push()) + { + ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, designColors.MissingColor); + } + + Im.Tooltip.OnHover("The color associated with this design does not exist."u8); } return; - static void CopyButton(ReadOnlySpan label, string text) + static void CopyButton(in Im.TableDisposable table, ReadOnlySpan label, Utf8StringHandler text) { - ImUtf8.DrawFrameColumn(label); - ImGui.TableNextColumn(); - if (ImUtf8.Button(text, new Vector2(Im.ContentRegion.Available.X, 0))) - ImUtf8.SetClipboardText(text); - ImUtf8.HoverTooltip("Click to copy to clipboard."u8); - } - } + table.DrawFrameColumn(label); + table.NextColumn(); + if (!text.GetSpan(out var span)) + return; - private sealed class ExportToClipboardButton(NpcPanel panel) : Button - { - protected override string Description - => "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()); + if (Im.Button(span, Im.ContentRegion.Available with { Y = 0 })) + Im.Clipboard.Set(span); + Im.Tooltip.OnHover("Click to copy to clipboard."u8); } } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelection.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelection.cs new file mode 100644 index 0000000..71bcf2f --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelection.cs @@ -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; + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index 44134ee..d07b309 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,92 +1,85 @@ using Glamourer.GameData; -using Dalamud.Bindings.ImGui; +using Glamourer.Designs; using ImSharp; -using OtterGui.Extensions; -using OtterGui.Raii; -using ImGuiClip = OtterGui.ImGuiClip; +using Luna; 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 LocalNpcAppearanceData _favorites; + private readonly NpcCustomizeSet _npcs = npcs; + private readonly LocalNpcAppearanceData _favorites = favorites; + private readonly NpcFilter _filter = filter; + private readonly DesignColors _designColors = designColors; - private NpcFilter _filter; - private readonly List _visibleOrdered = []; - private int _selectedGlobalIndex; - private bool _listDirty = true; - private Vector2 _defaultItemSpacing; - private float _width; + public ReadOnlySpan Id + => "NpcSelector"u8; - - public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites) + public void Draw() { - _npcs = npcs; - _favorites = favorites; - _filter = new NpcFilter(_favorites); - _favorites.DataChanged += OnFavoriteChange; + Im.Cursor.Y += Im.Style.FramePadding.Y; + var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(this)); + using var clipper = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing); + 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() - => _listDirty = true; - - public void UpdateList() + private sealed class Cache : BasicFilterCache { - if (!_listDirty) - return; + private readonly NpcSelector _parent; - _listDirty = false; - _visibleOrdered.Clear(); - var enumerable = _npcs.WithIndex(); - if (!_filter.IsEmpty) - enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value)); - var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value)) - .ThenBy(d => d.Index) - .Select(d => d.Index); - _visibleOrdered.AddRange(range); - } + public Cache(NpcSelector parent) + : base(parent._filter) + { + _parent = parent; + _parent._favorites.DataChanged += OnDataChange; + _parent._designColors.ColorChanged += OnDataChange; + } - public bool HasSelection - => _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count; + private void OnDataChange() + => Dirty |= IManagedCache.DirtyFlags.Custom; - public NpcData Selection - => HasSelection ? _npcs[_selectedGlobalIndex] : default; + protected override void Dispose(bool disposing) + { + _parent._favorites.DataChanged -= OnDataChange; + _parent._designColors.ColorChanged -= OnDataChange; + base.Dispose(disposing); + } - 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); - - 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; + protected override IEnumerable GetItems() + => _parent._npcs.Select(n => _parent.CreateItem(n)).OrderByDescending(n => n.Favorite); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 973d782..27d8233 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -3,18 +3,24 @@ using Luna; namespace Glamourer.Gui.Tabs.NpcTab; -public sealed class NpcTab(NpcSelector selector, NpcPanel panel) : ITab +public sealed class NpcTab : TwoPanelLayout, ITab { - public ReadOnlySpan Label + public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header) + { + LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); + LeftPanel = selector; + LeftFooter = NopHeaderFooter.Instance; + RightHeader = header; + RightPanel = panel; + RightFooter = NopHeaderFooter.Instance; + } + + public override ReadOnlySpan Label => "NPCs"u8; public MainTabType Identifier => MainTabType.Npcs; public void DrawContent() - { - selector.Draw(200 * Im.Style.GlobalScale); - Im.Line.Same(); - panel.Draw(); - } + => Draw(TwoPanelWidth.IndeterminateRelative); } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index e80d830..de30188 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -299,6 +299,28 @@ public sealed class SettingsTab( 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() { var showAuto = config.EnableAutoDesigns; @@ -310,23 +332,11 @@ public sealed class SettingsTab( if (!table) return; - IEnumerable, 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 - foreach (var (text, display, _) in columns) + foreach (var (text, flag) in _columns) { - if (!display) + if (!DisplayButton(flag, showAuto, config.UseTemporarySettings)) continue; table.NextColumn(); @@ -334,9 +344,9 @@ public sealed class SettingsTab( } // ReSharper disable once PossibleMultipleEnumeration - foreach (var (_, display, flag) in columns) + foreach (var (_, flag) in _columns) { - if (!display) + if (!DisplayButton(flag, showAuto, config.UseTemporarySettings)) continue; using var id = Im.Id.Push((int)flag); diff --git a/Luna b/Luna index c52743f..042b829 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit c52743f736892dde62f39e6a2b06fde4096cdff7 +Subproject commit 042b829099c34608782c2a70da3cf189ac5f53be