Filtering reagents (#18211)

* Making it work

* Refactoring

* Autoformat revert

* Implementing suggestions

* Changed to file scoped namespaces.
This commit is contained in:
Hebi
2023-07-26 10:05:09 +02:00
committed by GitHub
parent 158af403e8
commit 756699ffcc
9 changed files with 171 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ using System.Linq;
using Content.Client.Chemistry.EntitySystems; using Content.Client.Chemistry.EntitySystems;
using Content.Client.Guidebook.Richtext; using Content.Client.Guidebook.Richtext;
using Content.Client.Message; using Content.Client.Message;
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -20,7 +21,7 @@ namespace Content.Client.Guidebook.Controls;
/// Control for embedding a reagent into a guidebook. /// Control for embedding a reagent into a guidebook.
/// </summary> /// </summary>
[UsedImplicitly, GenerateTypedNameReferences] [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 IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
@@ -45,6 +46,16 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag
GenerateControl(reagent); 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<string, string> args, [NotNullWhen(true)] out Control? control) public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{ {
control = null; control = null;

View File

@@ -12,14 +12,24 @@
<fancyTree:FancyTree Name="Tree" VerticalExpand="True" HorizontalExpand="True" Access="Public"/> <fancyTree:FancyTree Name="Tree" VerticalExpand="True" HorizontalExpand="True" Access="Public"/>
<cc:VSeparator StyleClasses="LowDivider" Margin="0 -2"/> <cc:VSeparator StyleClasses="LowDivider" Margin="0 -2"/>
</BoxContainer> </BoxContainer>
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True"> <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<Control> <BoxContainer Name="SearchContainer" Visible="False" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/> <LineEdit
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5"> Name="SearchBar"
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/> PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/> HorizontalExpand="True"
</BoxContainer> Margin="0 5 10 5">
</Control> </LineEdit>
</ScrollContainer> </BoxContainer>
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<Control>
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>
</BoxContainer>
</Control>
</ScrollContainer>
</BoxContainer>
</SplitContainer> </SplitContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Client.Guidebook.RichText; using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree; using Content.Client.UserInterface.Controls.FancyTree;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -26,6 +27,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
Tree.OnSelectedItemChanged += OnSelectionChanged; Tree.OnSelectedItemChanged += OnSelectionChanged;
SearchBar.OnTextChanged += _ =>
{
HandleFilter();
};
} }
private void OnSelectionChanged(TreeItem? item) private void OnSelectionChanged(TreeItem? item)
@@ -40,6 +46,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{ {
Placeholder.Visible = true; Placeholder.Visible = true;
EntryContainer.Visible = false; EntryContainer.Visible = false;
SearchContainer.Visible = false;
EntryContainer.RemoveAllChildren(); EntryContainer.RemoveAllChildren();
} }
@@ -48,9 +55,12 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
Scroll.SetScrollValue(default); Scroll.SetScrollValue(default);
Placeholder.Visible = false; Placeholder.Visible = false;
EntryContainer.Visible = true; EntryContainer.Visible = true;
SearchBar.Text = "";
EntryContainer.RemoveAllChildren(); EntryContainer.RemoveAllChildren();
using var file = _resourceManager.ContentFileReadText(entry.Text); using var file = _resourceManager.ContentFileReadText(entry.Text);
SearchContainer.Visible = entry.FilterEnabled;
if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd())) if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
{ {
EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." }); EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
@@ -159,4 +169,20 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
ShowGuide(entry); 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());
}
}
}
} }

View File

@@ -0,0 +1,9 @@
namespace Content.Client.Guidebook.Controls;
public interface ISearchableControl
{
public bool CheckMatchesSearch(string query);
/// <summary>
/// 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.
/// </summary>
public void SetHiddenState(bool state, string query);
}

View File

@@ -29,6 +29,11 @@ public class GuideEntry
[DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer<GuideEntryPrototype>))] [DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer<GuideEntryPrototype>))]
public List<string> Children = new(); public List<string> Children = new();
/// <summary>
/// Enable filtering of items.
/// </summary>
[DataField("filterEnabled")] public bool FilterEnabled = default!;
/// <summary> /// <summary>
/// Priority for sorting top-level guides when shown in a tree / table of contents. /// 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 <see cref="Children"/>. /// If the guide is the child of some other guide, the order simply determined by the order of children in <see cref="Children"/>.

View File

@@ -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<T> GetControlOfType<T>(this Control parent) where T : Control
{
return parent.GetControlOfType<T>(typeof(T).Name, false);
}
public static List<T> GetControlOfType<T>(this Control parent, string childType) where T : Control
{
return parent.GetControlOfType<T>(childType, false);
}
public static List<T> GetControlOfType<T>(this Control parent, bool fullTreeSearch) where T : Control
{
return parent.GetControlOfType<T>(typeof(T).Name, fullTreeSearch);
}
public static List<T> GetControlOfType<T>(this Control parent, string childType, bool fullTreeSearch) where T : Control
{
List<T> controlList = new List<T>();
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<T>(childType, fullTreeSearch));
}
}
return controlList;
}
public static List<ISearchableControl> GetSearchableControls(this Control parent, bool fullTreeSearch = false)
{
List<ISearchableControl> controlList = new List<ISearchableControl>();
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<Label>();
var richTextLabels = parent.GetControlOfType<RichTextLabel>();
foreach (var label in labels)
{
if (label.Text != null && label.Text.Contains(search, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
foreach (var label in richTextLabels)
{
var text = label.GetMessage();
if (text != null && text.Contains(search, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}

View File

@@ -1,6 +1,7 @@
guidebook-window-title = Guidebook guidebook-window-title = Guidebook
guidebook-placeholder-text = Select an entry. guidebook-placeholder-text = Select an entry.
guidebook-placeholder-text-2 = If you're new, select the topmost entry to get started. guidebook-placeholder-text-2 = If you're new, select the topmost entry to get started.
guidebook-filter-placeholder-text = Filter items
guidebook-monkey-unspin = Unspin Monkey guidebook-monkey-unspin = Unspin Monkey

View File

@@ -35,8 +35,10 @@
id: Medicine id: Medicine
name: guide-entry-medicine name: guide-entry-medicine
text: "/ServerInfo/Guidebook/Medical/Medicine.xml" text: "/ServerInfo/Guidebook/Medical/Medicine.xml"
filterEnabled: True
- type: guideEntry - type: guideEntry
id: Botanicals id: Botanicals
name: guide-entry-botanicals name: guide-entry-botanicals
text: "/ServerInfo/Guidebook/Medical/Botanicals.xml" text: "/ServerInfo/Guidebook/Medical/Botanicals.xml"
filterEnabled: True

View File

@@ -23,6 +23,7 @@
id: Chemicals id: Chemicals
name: guide-entry-chemicals name: guide-entry-chemicals
text: "/ServerInfo/Guidebook/Chemicals.xml" text: "/ServerInfo/Guidebook/Chemicals.xml"
filterEnabled: True
- type: guideEntry - type: guideEntry
id: Janitorial id: Janitorial