From 226dbdd4a8c982941420a52ea64480aaadcb5d71 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 18 Nov 2023 13:16:51 +0100 Subject: [PATCH] Improve multi design selection. --- .../Gui/Tabs/DesignTab/DesignColorCombo.cs | 28 +++ .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 24 +-- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 98 ++------- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 186 ++++++++++++++++++ Glamourer/Services/ServiceManager.cs | 1 + 5 files changed, 233 insertions(+), 104 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs new file mode 100644 index 0000000..0db26b7 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; + +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), + Glamourer.Log) +{ + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var isAutomatic = !_skipAutomatic && globalIdx == 0; + var key = Items[globalIdx]; + var color = isAutomatic ? 0 : _designColors[key]; + using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); + var ret = base.DrawSelectable(globalIdx, selected); + if (isAutomatic) + ImGuiUtil.HoverTooltip( + "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 2b0cac6..ca58393 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; @@ -14,27 +13,6 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignColorCombo : FilterComboCache -{ - private readonly DesignColors _designColors; - - public DesignColorCombo(DesignColors designColors) - : base(designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), Glamourer.Log) - => _designColors = designColors; - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var key = Items[globalIdx]; - var color = globalIdx == 0 ? 0 : _designColors[key]; - using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); - var ret = base.DrawSelectable(globalIdx, selected); - if (globalIdx == 0) - ImGuiUtil.HoverTooltip( - "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); - return ret; - } -} - public class DesignDetailTab { private readonly SaveService _saveService; @@ -61,7 +39,7 @@ public class DesignDetailTab _manager = manager; _fileSystem = fileSystem; _colors = colors; - _colorCombo = new DesignColorCombo(_colors); + _colorCombo = new DesignColorCombo(_colors, false); } public void Draw() diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 64324d2..c5e6943 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -13,7 +13,6 @@ using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; -using Glamourer.Services; using Glamourer.State; using Glamourer.Structs; using ImGuiNET; @@ -24,37 +23,11 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignPanel +public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, + ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, + DesignDetailTab _designDetails, DesignConverter _converter, DatFileService _datFileService, MultiDesignPanel _multiDesignPanel) { - private readonly ObjectManager _objects; - private readonly DesignFileSystemSelector _selector; - private readonly DesignManager _manager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly StateManager _state; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly CustomizationService _customizationService; - private readonly ModAssociationsTab _modAssociations; - private readonly DesignDetailTab _designDetails; - private readonly DesignConverter _converter; - private readonly DatFileService _datFileService; - private readonly FileDialogManager _fileDialog = new(); - - public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects, - StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, ModAssociationsTab modAssociations, - DesignDetailTab designDetails, DesignConverter converter, DatFileService datFileService) - { - _selector = selector; - _customizationDrawer = customizationDrawer; - _manager = manager; - _objects = objects; - _state = state; - _equipmentDrawer = equipmentDrawer; - _customizationService = customizationService; - _modAssociations = modAssociations; - _designDetails = designDetails; - _converter = converter; - _datFileService = datFileService; - } + private readonly FileDialogManager _fileDialog = new(); private HeaderDrawer.Button LockButton() => _selector.Selected == null @@ -88,10 +61,10 @@ public class DesignPanel => new() { Description = "Undo the last change if you accidentally overwrote your design with a different one.", - Icon = FontAwesomeIcon.Undo, - OnClick = UndoOverwrite, - Visible = _selector.Selected != null, - Disabled = !_manager.CanUndo(_selector.Selected), + Icon = FontAwesomeIcon.Undo, + OnClick = UndoOverwrite, + Visible = _selector.Selected != null, + Disabled = !_manager.CanUndo(_selector.Selected), }; private HeaderDrawer.Button ExportToClipboardButton() @@ -215,7 +188,7 @@ public class DesignPanel if (!ImGui.CollapsingHeader("Application Rules")) return; - using (var group1 = ImRaii.Group()) + using (var _ = ImRaii.Group()) { var set = _selector.Selected!.CustomizationSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; @@ -247,13 +220,13 @@ public class DesignPanel } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); - using (var group2 = ImRaii.Group()) + using (var _ = ImRaii.Group()) { - void ApplyEquip(string label, EquipFlag all, bool stain, IEnumerable slots) + void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(all & _selector.Selected!.ApplyEquip); + var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); - var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)all); + var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { @@ -316,7 +289,7 @@ public class DesignPanel using var group = ImRaii.Group(); if (_selector.SelectedPaths.Count > 1) { - DrawMultiSelection(); + _multiDesignPanel.Draw(); } else { @@ -338,44 +311,6 @@ public class DesignPanel _datFileService.CreateSource(); } - private void DrawMultiSelection() - { - if (_selector.SelectedPaths.Count == 0) - return; - - var sizeType = ImGui.GetFrameHeight(); - var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; - var sizeMods = availableSizePercent * 35; - var sizeFolders = availableSizePercent * 65; - - ImGui.NewLine(); - ImGui.TextUnformatted("Currently Selected Objects"); - ImGui.Separator(); - using var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg); - ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); - ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); - ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); - - var i = 0; - foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) - .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) - { - using var id = ImRaii.PushId(i++); - ImGui.TableNextColumn(); - var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); - if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) - _selector.RemovePathFromMultiselection(path); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(fullName); - } - } - private void DrawPanel() { using var child = ImRaii.Child("##Panel", -Vector2.One, true); @@ -426,7 +361,8 @@ public class DesignPanel } catch (Exception ex) { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, false); + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, + false); } } @@ -481,7 +417,7 @@ public class DesignPanel private void DrawSaveToDat() { - var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out var voice); + var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out _); var tt = verified ? "Export the currently configured customizations of this design to a character creation data file." : "The current design contains customizations that can not be applied during character creation."; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs new file mode 100644 index 0000000..4b1e128 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _editor, DesignColors _colors) +{ + private readonly DesignColorCombo _colorCombo = new(_colors, true); + + public void Draw() + { + if (_selector.SelectedPaths.Count == 0) + return; + + var width = ImGuiHelpers.ScaledVector2(145, 0); + ImGui.NewLine(); + DrawDesignList(); + var offset = DrawMultiTagger(width); + DrawMultiColor(width, offset); + } + + private void DrawDesignList() + { + using var tree = ImRaii.TreeNode("Currently Selected Objects", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); + ImGui.Separator(); + if (!tree) + return; + + var sizeType = ImGui.GetFrameHeight(); + var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; + var sizeMods = availableSizePercent * 35; + var sizeFolders = availableSizePercent * 65; + + using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) + { + if (!table) + return; + + ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); + ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); + ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); + + var i = 0; + foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) + .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) + { + using var id = ImRaii.PushId(i++); + ImGui.TableNextColumn(); + var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); + if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) + _selector.RemovePathFromMultiselection(path); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(fullName); + } + } + + ImGui.Separator(); + } + + private string _tag = string.Empty; + private readonly List _addDesigns = []; + private readonly List<(Design, int)> _removeDesigns = []; + + private float DrawMultiTagger(Vector2 width) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi Tagger:"); + ImGui.SameLine(); + var offset = ImGui.GetItemRectSize().X; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); + ImGui.InputTextWithHint("##tag", "Tag Name...", ref _tag, 128); + + UpdateTagCache(); + var label = _addDesigns.Count > 0 + ? $"Add to {_addDesigns.Count} Designs" + : "Add"; + var tooltip = _addDesigns.Count == 0 + ? _tag.Length == 0 + ? "No tag specified." + : $"All designs selected already contain the tag \"{_tag}\"." + : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + foreach (var design in _addDesigns) + _editor.AddTag(design, _tag); + + label = _removeDesigns.Count > 0 + ? $"Remove from {_removeDesigns.Count} Designs" + : "Remove"; + tooltip = _removeDesigns.Count == 0 + ? _tag.Length == 0 + ? "No tag specified." + : $"No selected design contains the tag \"{_tag}\" locally." + : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + foreach (var (design, index) in _removeDesigns) + _editor.RemoveTag(design, index); + ImGui.Separator(); + return offset; + } + + private void DrawMultiColor(Vector2 width, float offset) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi Colors:"); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", + ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); + + UpdateColorCache(); + var label = _addDesigns.Count > 0 + ? $"Set for {_addDesigns.Count} Designs" + : "Set"; + var tooltip = _addDesigns.Count == 0 + ? _colorCombo.CurrentSelection 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}\".", + } + : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + foreach (var design in _addDesigns) + _editor.ChangeColor(design, _colorCombo.CurrentSelection!); + + label = _removeDesigns.Count > 0 + ? $"Unset {_removeDesigns.Count} Designs" + : "Unset"; + tooltip = _removeDesigns.Count == 0 + ? "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))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + foreach (var (design, _) in _removeDesigns) + _editor.ChangeColor(design, string.Empty); + + ImGui.Separator(); + } + + private void UpdateTagCache() + { + _addDesigns.Clear(); + _removeDesigns.Clear(); + if (_tag.Length == 0) + return; + + foreach (var leaf in _selector.SelectedPaths.OfType()) + { + var index = leaf.Value.Tags.IndexOf(_tag); + if (index >= 0) + _removeDesigns.Add((leaf.Value, index)); + else + _addDesigns.Add(leaf.Value); + } + } + + private void UpdateColorCache() + { + _addDesigns.Clear(); + _removeDesigns.Clear(); + var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; + foreach (var leaf in _selector.SelectedPaths.OfType()) + { + if (leaf.Value.Color.Length > 0) + _removeDesigns.Add((leaf.Value, 0)); + if (selection != DesignColors.AutomaticName && leaf.Value.Color != selection) + _addDesigns.Add(leaf.Value); + } + } +} diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0d38099..19ade32 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -130,6 +130,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton()