A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityEngine.UIElements;
6using UnityEditor.ShaderGraph.Drawing;
7using UnityEditor.Searcher;
8
9namespace UnityEditor.ShaderGraph
10{
11 public class SearchWindowAdapter : SearcherAdapter
12 {
13 readonly VisualTreeAsset m_DefaultItemTemplate;
14 public override bool HasDetailsPanel => false;
15
16 public SearchWindowAdapter(string title) : base(title)
17 {
18 m_DefaultItemTemplate = Resources.Load<VisualTreeAsset>("SearcherItem");
19 }
20
21 private SearcherItem GetFirstChildItem(SearcherItem item)
22 {
23 if (item.Children.Count != 0)
24 {
25 SearcherItem childIterator = null;
26 // Discard searcher item for selection if it is a category, get next best child item from it instead
27 // There is no utility in selecting category headers/titles, only the leaf entries
28 childIterator = item.Children[0];
29 while (childIterator != null && childIterator.Children.Count != 0)
30 {
31 childIterator = childIterator.Children[0];
32 }
33
34 item = childIterator;
35 }
36
37 return item;
38 }
39
40 private int ComputeScoreForMatch(string[] queryTerms, SearcherItem matchItem)
41 {
42 // Scoring Criteria:
43 // - Exact name match is most preferred.
44 // - Partial name match is next.
45 // - Exact synonym match is next.
46 // - Partial synonym match is next.
47 // - No match is last.
48 int score = 0;
49
50 // Split the entry name so that we can remove suffix that looks like "Clamp: In(4)"
51 var nameSansSuffix = matchItem.Name.Split(':').First();
52
53 int nameCharactersMatched = 0;
54
55 foreach (var queryWord in queryTerms)
56 {
57 if (nameSansSuffix.Contains(queryWord, StringComparison.OrdinalIgnoreCase))
58 {
59 score += 100000;
60 nameCharactersMatched += queryWord.Length;
61 }
62
63 // Check for synonym matches -- give a bonus to each
64 if (matchItem.Synonyms != null)
65 {
66 foreach (var syn in matchItem.Synonyms)
67 {
68 if (syn.Equals(queryWord, StringComparison.OrdinalIgnoreCase))
69 {
70 score += 10000;
71 }
72 else if (syn.Contains(queryWord, StringComparison.OrdinalIgnoreCase))
73 {
74 score += 1000;
75 score -= (syn.Length - queryWord.Length);
76 }
77 }
78 }
79 }
80
81 if (nameCharactersMatched > 0)
82 {
83 int unmatchedCharacters = (nameSansSuffix.Length - nameCharactersMatched);
84 score -= unmatchedCharacters;
85 }
86
87 return score;
88 }
89
90 public override SearcherItem OnSearchResultsFilter(IEnumerable<SearcherItem> searchResults, string searchQuery)
91 {
92 if (searchQuery.Length == 0)
93 return GetFirstChildItem(searchResults.FirstOrDefault());
94
95 // Sort results by length so that shorter length results are prioritized
96 // prevents entries with short names getting stuck at end of list after entries with longer names when both contain the same word
97 searchResults = searchResults.OrderBy(x => x.Name.Length).ToList();
98
99 var bestMatch = GetFirstChildItem(searchResults.FirstOrDefault());
100 int bestScore = 0;
101 List<int> visitedItems = new List<int>();
102 var queryTerms = searchQuery.Split(' ');
103 foreach (var result in searchResults)
104 {
105 var currentItem = GetFirstChildItem(result);
106
107 if (currentItem.Parent != null)
108 {
109 SearcherItem parentItem = currentItem.Parent;
110 foreach (var matchItem in parentItem.Children)
111 {
112 if (visitedItems.Contains(matchItem.Id))
113 continue;
114
115 int currentScore = ComputeScoreForMatch(queryTerms, matchItem);
116 if (currentScore > bestScore)
117 {
118 bestScore = currentScore;
119 bestMatch = matchItem;
120 }
121
122 visitedItems.Add(matchItem.Id);
123 }
124 }
125 }
126
127 return bestMatch;
128 }
129 }
130
131 internal class SearchNodeItem : SearcherItem
132 {
133 public NodeEntry NodeGUID;
134
135 public SearchNodeItem(string name, NodeEntry nodeGUID, string[] synonyms,
136 string help = " ", List<SearchNodeItem> children = null) : base(name)
137 {
138 NodeGUID = nodeGUID;
139 Synonyms = synonyms;
140 }
141 }
142}