Filtering reagents (#18211)
* Making it work * Refactoring * Autoformat revert * Implementing suggestions * Changed to file scoped namespaces.
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
Content.Client/Guidebook/Controls/ISearchableControl.cs
Normal file
9
Content.Client/Guidebook/Controls/ISearchableControl.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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"/>.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user