From 756699ffcc07c99fa2e184f284b39c76fb7bd97b Mon Sep 17 00:00:00 2001 From: Hebi Date: Wed, 26 Jul 2023 10:05:09 +0200 Subject: [PATCH] Filtering reagents (#18211) * Making it work * Refactoring * Autoformat revert * Implementing suggestions * Changed to file scoped namespaces. --- .../Controls/GuideReagentEmbed.xaml.cs | 13 ++- .../Guidebook/Controls/GuidebookWindow.xaml | 28 ++++-- .../Controls/GuidebookWindow.xaml.cs | 26 +++++ .../Guidebook/Controls/ISearchableControl.cs | 9 ++ Content.Client/Guidebook/GuideEntry.cs | 5 + .../ControlExtensions/ControlExtension.cs | 96 +++++++++++++++++++ .../Locale/en-US/guidebook/guidebook.ftl | 1 + Resources/Prototypes/Guidebook/medical.yml | 2 + .../Prototypes/Guidebook/shiftandcrew.yml | 1 + 9 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 Content.Client/Guidebook/Controls/ISearchableControl.cs create mode 100644 Content.Client/UserInterface/ControlExtensions/ControlExtension.cs diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs index 4b832be02b..309fee3baf 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs @@ -3,6 +3,7 @@ using System.Linq; using Content.Client.Chemistry.EntitySystems; using Content.Client.Guidebook.Richtext; using Content.Client.Message; +using Content.Client.UserInterface.ControlExtensions; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using JetBrains.Annotations; @@ -20,7 +21,7 @@ namespace Content.Client.Guidebook.Controls; /// Control for embedding a reagent into a guidebook. /// [UsedImplicitly, GenerateTypedNameReferences] -public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag +public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISearchableControl { [Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; @@ -45,6 +46,16 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag GenerateControl(reagent); } + public bool CheckMatchesSearch(string query) + { + return this.ChildrenContainText(query); + } + + public void SetHiddenState(bool state, string query) + { + this.Visible = CheckMatchesSearch(query) ? state : !state; + } + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) { control = null; diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml index 1d19a92231..8dbfde3c47 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml @@ -12,14 +12,24 @@ - - - - - - - + + + + + + + + + + + + + diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs index 61054e9b0f..113c192beb 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Client.Guidebook.RichText; +using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls.FancyTree; using JetBrains.Annotations; @@ -26,6 +27,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler IoCManager.InjectDependencies(this); Tree.OnSelectedItemChanged += OnSelectionChanged; + + SearchBar.OnTextChanged += _ => + { + HandleFilter(); + }; } private void OnSelectionChanged(TreeItem? item) @@ -40,6 +46,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler { Placeholder.Visible = true; EntryContainer.Visible = false; + SearchContainer.Visible = false; EntryContainer.RemoveAllChildren(); } @@ -48,9 +55,12 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler Scroll.SetScrollValue(default); Placeholder.Visible = false; EntryContainer.Visible = true; + SearchBar.Text = ""; EntryContainer.RemoveAllChildren(); using var file = _resourceManager.ContentFileReadText(entry.Text); + SearchContainer.Visible = entry.FilterEnabled; + if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd())) { EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." }); @@ -159,4 +169,20 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler ShowGuide(entry); } } + + private void HandleFilter() + { + var emptySearch = SearchBar.Text.Trim().Length == 0; + + if (Tree.SelectedItem != null && Tree.SelectedItem.Metadata is GuideEntry entry && entry.FilterEnabled) + { + var foundElements = EntryContainer.GetSearchableControls(); + + foreach (var element in foundElements) + { + element.SetHiddenState(true, SearchBar.Text.Trim()); + } + } + + } } diff --git a/Content.Client/Guidebook/Controls/ISearchableControl.cs b/Content.Client/Guidebook/Controls/ISearchableControl.cs new file mode 100644 index 0000000000..972d7657bd --- /dev/null +++ b/Content.Client/Guidebook/Controls/ISearchableControl.cs @@ -0,0 +1,9 @@ +namespace Content.Client.Guidebook.Controls; +public interface ISearchableControl +{ + public bool CheckMatchesSearch(string query); + /// + /// Sets the hidden state for the control. In simple cases this could just disable/hide it, but you may want more complex behavior for some elements. + /// + public void SetHiddenState(bool state, string query); +} diff --git a/Content.Client/Guidebook/GuideEntry.cs b/Content.Client/Guidebook/GuideEntry.cs index f6029e78cb..b3c004267d 100644 --- a/Content.Client/Guidebook/GuideEntry.cs +++ b/Content.Client/Guidebook/GuideEntry.cs @@ -29,6 +29,11 @@ public class GuideEntry [DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer))] public List Children = new(); + /// + /// Enable filtering of items. + /// + [DataField("filterEnabled")] public bool FilterEnabled = default!; + /// /// Priority for sorting top-level guides when shown in a tree / table of contents. /// If the guide is the child of some other guide, the order simply determined by the order of children in . diff --git a/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs new file mode 100644 index 0000000000..c0e4a038a1 --- /dev/null +++ b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs @@ -0,0 +1,96 @@ +using Content.Client.Guidebook.Controls; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.ControlExtensions; + +public static class ControlExtension +{ + public static List GetControlOfType(this Control parent) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, false); + } + public static List GetControlOfType(this Control parent, string childType) where T : Control + { + return parent.GetControlOfType(childType, false); + } + + public static List GetControlOfType(this Control parent, bool fullTreeSearch) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, fullTreeSearch); + } + + public static List GetControlOfType(this Control parent, string childType, bool fullTreeSearch) where T : Control + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var isType = child.GetType().Name == childType; + var hasChildren = child.ChildCount > 0; + + var searchDeeper = hasChildren && !isType; + + if (isType) + { + controlList.Add((T) child); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetControlOfType(childType, fullTreeSearch)); + } + } + + return controlList; + } + + public static List GetSearchableControls(this Control parent, bool fullTreeSearch = false) + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var hasChildren = child.ChildCount > 0; + var searchDeeper = hasChildren && child is not ISearchableControl; + + if (child is ISearchableControl searchableChild) + { + controlList.Add(searchableChild); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetSearchableControls(fullTreeSearch)); + } + } + + return controlList; + } + + public static bool ChildrenContainText(this Control parent, string search) + { + var labels = parent.GetControlOfType