A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Collections.Generic;
6using System.Linq;
7using osu.Framework.Caching;
8using osu.Framework.Extensions.IEnumerableExtensions;
9
10namespace osu.Framework.Graphics.Containers
11{
12 public class SearchContainer : SearchContainer<Drawable>
13 {
14 }
15
16 /// <summary>
17 /// A container which filters children based on a search term.
18 /// Re-filtering will only be performed when the <see cref="SearchTerm"/> changes, or
19 /// new items are added as direct children of this container.
20 /// </summary>
21 /// <typeparam name="T"></typeparam>
22 public class SearchContainer<T> : FillFlowContainer<T> where T : Drawable
23 {
24 private string searchTerm;
25
26 /// <summary>
27 /// A string that should match the <see cref="IFilterable"/> children
28 /// </summary>
29 public string SearchTerm
30 {
31 get => searchTerm;
32 set
33 {
34 if (value == searchTerm)
35 return;
36
37 searchTerm = value;
38 filterValid.Invalidate();
39 }
40 }
41
42 protected internal override void AddInternal(Drawable drawable)
43 {
44 base.AddInternal(drawable);
45 filterValid.Invalidate();
46 }
47
48 private readonly Cached filterValid = new Cached();
49
50 protected override void Update()
51 {
52 base.Update();
53
54 if (!filterValid.IsValid)
55 {
56 performFilter();
57 filterValid.Validate();
58 }
59 }
60
61 private void performFilter()
62 {
63 var terms = (searchTerm ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
64 Children.OfType<IFilterable>().ForEach(child => match(child, terms, terms.Length > 0));
65 }
66
67 private static bool match(IFilterable filterable, IEnumerable<string> terms, bool searchActive)
68 {
69 //Words matched by parent is not needed to match children
70 var childTerms = terms.Where(term =>
71 !filterable.FilterTerms.Any(filterTerm =>
72 filterTerm.Contains(term, StringComparison.InvariantCultureIgnoreCase))).ToArray();
73
74 bool matching = childTerms.Length == 0;
75
76 //We need to check the children and should any child match this matches as well
77 if (filterable is IHasFilterableChildren hasFilterableChildren)
78 {
79 foreach (IFilterable child in hasFilterableChildren.FilterableChildren)
80 matching |= match(child, childTerms, searchActive);
81 }
82
83 filterable.FilteringActive = searchActive;
84 return filterable.MatchingFilter = matching;
85 }
86 }
87}