From 50a7e7efb7f0ea41583ab04b1dd6dd16ca2f992e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Jun 2024 16:34:09 +0200 Subject: [PATCH] Add more filter options. --- OtterGui | 2 +- .../Meta/Manipulations/ImcManipulation.cs | 1 - Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 106 ++++---------- .../UI/ModsTab/ModSearchStringSplitter.cs | 138 ++++++++++++++++++ 4 files changed, 171 insertions(+), 76 deletions(-) create mode 100644 Penumbra/UI/ModsTab/ModSearchStringSplitter.cs diff --git a/OtterGui b/OtterGui index 5de708b2..ac176daf 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5de708b27ed45c9cdead71742c7061ad9ce64323 +Subproject commit ac176daf068f42d0b04a77dbc149f68a425fd460 diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs index eb3720c9..5065a06e 100644 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 5b6cfa99..58f0b615 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -23,17 +23,20 @@ namespace Penumbra.UI.ModsTab; public sealed class ModFileSystemSelector : FileSystemSelector { - private readonly CommunicatorService _communicator; - private readonly MessageService _messager; - private readonly Configuration _config; - private readonly FileDialogService _fileDialog; - private readonly ModManager _modManager; - private readonly CollectionManager _collectionManager; - private readonly TutorialService _tutorial; - private readonly ModImportManager _modImportManager; - private readonly IDragDropManager _dragDrop; - public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; - public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; + private readonly CommunicatorService _communicator; + private readonly MessageService _messager; + private readonly Configuration _config; + private readonly FileDialogService _fileDialog; + private readonly ModManager _modManager; + private readonly CollectionManager _collectionManager; + private readonly TutorialService _tutorial; + private readonly ModImportManager _modImportManager; + private readonly IDragDropManager _dragDrop; + private readonly ModSearchStringSplitter Filter = new(); + + public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; + public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; + public ModFileSystemSelector(IKeyState keyState, CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog, @@ -568,78 +571,49 @@ public sealed class ModFileSystemSelector : FileSystemSelector Appropriately identify and set the string filter and its type. protected override bool ChangeFilter(string filterValue) { - (_modFilter, _filterType) = filterValue.Length switch - { - 0 => (LowerString.Empty, -1), - > 1 when filterValue[1] == ':' => - filterValue[0] switch - { - 'n' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), - 'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), - 'a' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), - 'A' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), - 'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), - 'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), - 't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4), - 'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4), - 's' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5), - 'S' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5), - _ => (new LowerString(filterValue), 0), - }, - _ => (new LowerString(filterValue), 0), - }; - + Filter.Parse(filterValue); return true; } - private const int EmptyOffset = 128; - - private (LowerString, int) ParseFilter(string value, int id) - { - value = value[2..]; - var lower = new LowerString(value); - if (id == 5 && !ChangedItemDrawer.TryParsePartial(lower.Lower, out _slotFilter)) - _slotFilter = 0; - - return (lower, lower.Lower is "none" ? id + EmptyOffset : id); - } - - /// /// Check the state filter for a specific pair of has/has-not flags. /// Uses count == 0 to check for has-not and count != 0 for has. /// Returns true if it should be filtered and false if not. /// private bool CheckFlags(int count, ModFilter hasNoFlag, ModFilter hasFlag) - { - return count switch + => count switch { 0 when _stateFilter.HasFlag(hasNoFlag) => false, 0 => true, _ when _stateFilter.HasFlag(hasFlag) => false, _ => true, }; - } /// /// The overwritten filter method also computes the state. @@ -653,7 +627,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector 0 && !f.FullName().Contains(FilterValue, IgnoreCase); + || !Filter.IsVisible(f); } return ApplyFiltersAndState((ModFileSystem.Leaf)path, out state); @@ -661,23 +635,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Apply the string filters. private bool ApplyStringFilters(ModFileSystem.Leaf leaf, Mod mod) - { - return _filterType switch - { - -1 => false, - 0 => !(leaf.FullName().Contains(_modFilter.Lower, IgnoreCase) || mod.Name.Contains(_modFilter)), - 1 => !mod.Name.Contains(_modFilter), - 2 => !mod.Author.Contains(_modFilter), - 3 => !mod.LowerChangedItemsString.Contains(_modFilter.Lower), - 4 => !mod.AllTagsLower.Contains(_modFilter.Lower), - 5 => mod.ChangedItems.All(p => (ChangedItemDrawer.GetCategoryIcon(p.Key, p.Value) & _slotFilter) == 0), - 2 + EmptyOffset => !mod.Author.IsEmpty, - 3 + EmptyOffset => mod.LowerChangedItemsString.Length > 0, - 4 + EmptyOffset => mod.AllTagsLower.Length > 0, - 5 + EmptyOffset => mod.ChangedItems.Count == 0, - _ => false, // Should never happen - }; - } + => !Filter.IsVisible(leaf); /// Only get the text color for a mod if no filters are set. private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) diff --git a/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs b/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs new file mode 100644 index 00000000..1ea70731 --- /dev/null +++ b/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs @@ -0,0 +1,138 @@ +using OtterGui.Filesystem; +using OtterGui.Filesystem.Selector; +using Penumbra.Mods; +using Penumbra.Mods.Manager; + +namespace Penumbra.UI.ModsTab; + +public enum ModSearchType : byte +{ + Default = 0, + ChangedItem, + Tag, + Name, + Author, + Category, +} + +public sealed class ModSearchStringSplitter : SearchStringSplitter.Leaf, ModSearchStringSplitter.Entry> +{ + public readonly struct Entry : ISplitterEntry + { + public string Needle { get; init; } + public ModSearchType Type { get; init; } + public ChangedItemDrawer.ChangedItemIcon IconFilter { get; init; } + + public bool Contains(Entry other) + { + if (Type != other.Type) + return false; + if (Type is ModSearchType.Category) + return IconFilter == other.IconFilter; + + return Needle.Contains(other.Needle); + } + } + + protected override bool ConvertToken(char token, out ModSearchType val) + { + val = token switch + { + 'c' or 'C' => ModSearchType.ChangedItem, + 't' or 'T' => ModSearchType.Tag, + 'n' or 'N' => ModSearchType.Name, + 'a' or 'A' => ModSearchType.Author, + 's' or 'S' => ModSearchType.Category, + _ => ModSearchType.Default, + }; + return val is not ModSearchType.Default; + } + + protected override bool AllowsNone(ModSearchType val) + => val switch + { + ModSearchType.Author => true, + ModSearchType.ChangedItem => true, + ModSearchType.Tag => true, + ModSearchType.Category => true, + _ => false, + }; + + protected override void PostProcessing() + { + base.PostProcessing(); + HandleList(General); + HandleList(Forced); + HandleList(Negated); + return; + + static void HandleList(List list) + { + for (var i = 0; i < list.Count; ++i) + { + var entry = list[i]; + if (entry.Type is not ModSearchType.Category) + continue; + + if (ChangedItemDrawer.TryParsePartial(entry.Needle, out var icon)) + list[i] = entry with + { + IconFilter = icon, + Needle = string.Empty, + }; + else + list.RemoveAt(i--); + } + } + } + + public bool IsVisible(ModFileSystem.Folder folder) + { + switch (State) + { + case FilterState.NoFilters: return true; + case FilterState.NoMatches: return false; + } + + var fullName = folder.FullName(); + return Forced.All(i => MatchesName(i, folder.Name, fullName)) + && !Negated.Any(i => MatchesName(i, folder.Name, fullName)) + && (General.Count == 0 || General.Any(i => MatchesName(i, folder.Name, fullName))); + } + + protected override bool Matches(Entry entry, ModFileSystem.Leaf leaf) + => entry.Type switch + { + ModSearchType.Default => leaf.FullName().AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase) + || leaf.Value.Name.Lower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), + ModSearchType.ChangedItem => leaf.Value.LowerChangedItemsString.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), + ModSearchType.Tag => leaf.Value.AllTagsLower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), + ModSearchType.Name => leaf.Value.Name.Lower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), + ModSearchType.Author => leaf.Value.Author.Lower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), + ModSearchType.Category => leaf.Value.ChangedItems.Any(p + => (ChangedItemDrawer.GetCategoryIcon(p.Key, p.Value) & entry.IconFilter) != 0), + _ => true, + }; + + protected override bool MatchesNone(ModSearchType type, bool negated, ModFileSystem.Leaf haystack) + => type switch + { + ModSearchType.Author when negated => !haystack.Value.Author.IsEmpty, + ModSearchType.Author => haystack.Value.Author.IsEmpty, + ModSearchType.ChangedItem when negated => haystack.Value.LowerChangedItemsString.Length > 0, + ModSearchType.ChangedItem => haystack.Value.LowerChangedItemsString.Length == 0, + ModSearchType.Tag when negated => haystack.Value.AllTagsLower.Length > 0, + ModSearchType.Tag => haystack.Value.AllTagsLower.Length == 0, + ModSearchType.Category when negated => haystack.Value.ChangedItems.Count > 0, + ModSearchType.Category => haystack.Value.ChangedItems.Count == 0, + _ => true, + }; + + private static bool MatchesName(Entry entry, ReadOnlySpan name, ReadOnlySpan fullName) + => entry.Type switch + { + ModSearchType.Default => fullName.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), + ModSearchType.Name => name.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), + _ => false, + }; +}