mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-15 15:57:52 +08:00
Added Search page and AutoCompleter
This commit is contained in:
140
src/UI/Widgets/AutoComplete/AutoCompleter.cs
Normal file
140
src/UI/Widgets/AutoComplete/AutoCompleter.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
// todo add a 'close' button if the user wants to manually hide the suggestions box
|
||||
|
||||
public static class AutoCompleter
|
||||
{
|
||||
public static ISuggestionProvider CurrentHandler;
|
||||
|
||||
public static GameObject UIRoot => uiRoot;
|
||||
public static GameObject uiRoot;
|
||||
|
||||
public static ButtonListSource<Suggestion> dataHandler;
|
||||
public static ScrollPool scrollPool;
|
||||
|
||||
private static List<Suggestion> suggestions = new List<Suggestion>();
|
||||
|
||||
private static int lastCaretPos;
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (!UIRoot || !UIRoot.activeSelf)
|
||||
return;
|
||||
|
||||
if (suggestions.Any() && CurrentHandler != null)
|
||||
{
|
||||
if (!CurrentHandler.InputField.gameObject.activeInHierarchy)
|
||||
ReleaseOwnership(CurrentHandler);
|
||||
else
|
||||
{
|
||||
lastCaretPos = CurrentHandler.InputField.caretPosition;
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void TakeOwnership(ISuggestionProvider provider)
|
||||
{
|
||||
CurrentHandler = provider;
|
||||
}
|
||||
|
||||
public static void ReleaseOwnership(ISuggestionProvider provider)
|
||||
{
|
||||
if (CurrentHandler == null)
|
||||
return;
|
||||
|
||||
if (CurrentHandler == provider)
|
||||
{
|
||||
CurrentHandler = null;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Suggestion> GetEntries() => suggestions;
|
||||
|
||||
private static bool ShouldDisplay(Suggestion data, string filter) => true;
|
||||
|
||||
public static void SetSuggestions(List<Suggestion> collection)
|
||||
{
|
||||
suggestions = collection;
|
||||
|
||||
if (!suggestions.Any())
|
||||
UIRoot.SetActive(false);
|
||||
else
|
||||
{
|
||||
UIRoot.SetActive(true);
|
||||
dataHandler.RefreshData();
|
||||
scrollPool.Rebuild();
|
||||
//scrollPool.RefreshCells(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCellClicked(int dataIndex)
|
||||
{
|
||||
var suggestion = suggestions[dataIndex];
|
||||
CurrentHandler.OnSuggestionClicked(suggestion);
|
||||
}
|
||||
|
||||
private static void SetCell(ButtonCell<Suggestion> cell, int index)
|
||||
{
|
||||
if (index < 0 || index >= suggestions.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
var suggestion = suggestions[index];
|
||||
cell.buttonText.text = suggestion.DisplayText;
|
||||
}
|
||||
|
||||
private static void UpdatePosition()
|
||||
{
|
||||
if (CurrentHandler == null || !CurrentHandler.InputField.isFocused)
|
||||
return;
|
||||
|
||||
var input = CurrentHandler.InputField;
|
||||
var textGen = input.textComponent.cachedTextGenerator;
|
||||
int caretPos = lastCaretPos;
|
||||
caretPos--;
|
||||
|
||||
caretPos = Math.Max(0, caretPos);
|
||||
caretPos = Math.Min(textGen.characters.Count - 1, caretPos);
|
||||
|
||||
var pos = textGen.characters[caretPos].cursorPos;
|
||||
pos = input.transform.TransformPoint(pos);
|
||||
|
||||
uiRoot.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
|
||||
}
|
||||
|
||||
public static void ConstructUI()
|
||||
{
|
||||
var parent = UIManager.CanvasRoot;
|
||||
|
||||
dataHandler = new ButtonListSource<Suggestion>(scrollPool, GetEntries, SetCell, ShouldDisplay, OnCellClicked);
|
||||
|
||||
scrollPool = UIFactory.CreateScrollPool(parent, "AutoCompleter", out uiRoot, out GameObject scrollContent);
|
||||
var mainRect = uiRoot.GetComponent<RectTransform>();
|
||||
mainRect.pivot = new Vector2(0f, 1f);
|
||||
mainRect.anchorMin = new Vector2(0.45f, 0.45f);
|
||||
mainRect.anchorMax = new Vector2(0.65f, 0.6f);
|
||||
mainRect.offsetMin = Vector2.zero;
|
||||
mainRect.offsetMax = Vector2.zero;
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(scrollContent, true, false, true, false);
|
||||
|
||||
scrollPool.Initialize(dataHandler, ButtonCell<Suggestion>.CreatePrototypeCell(parent));
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
16
src/UI/Widgets/AutoComplete/ISuggestionProvider.cs
Normal file
16
src/UI/Widgets/AutoComplete/ISuggestionProvider.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
public interface ISuggestionProvider
|
||||
{
|
||||
InputField InputField { get; }
|
||||
|
||||
void OnSuggestionClicked(Suggestion suggestion);
|
||||
}
|
||||
}
|
27
src/UI/Widgets/AutoComplete/Suggestion.cs
Normal file
27
src/UI/Widgets/AutoComplete/Suggestion.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
public struct Suggestion
|
||||
{
|
||||
public readonly string DisplayText;
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Color TextColor;
|
||||
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public Suggestion(string displayText, string prefix, string addition, Color color)
|
||||
{
|
||||
DisplayText = displayText;
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
TextColor = color;
|
||||
}
|
||||
}
|
||||
}
|
119
src/UI/Widgets/AutoComplete/TypeCompleter.cs
Normal file
119
src/UI/Widgets/AutoComplete/TypeCompleter.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
public class TypeCompleter : ISuggestionProvider
|
||||
{
|
||||
private struct CachedType
|
||||
{
|
||||
public string FilteredName;
|
||||
public string DisplayName;
|
||||
}
|
||||
|
||||
public Type BaseType { get; }
|
||||
public InputField InputField { get; }
|
||||
|
||||
public event Action<Suggestion> SuggestionClicked;
|
||||
public void OnSuggestionClicked(Suggestion suggestion)
|
||||
{
|
||||
SuggestionClicked?.Invoke(suggestion);
|
||||
suggestions.Clear();
|
||||
AutoCompleter.SetSuggestions(suggestions);
|
||||
|
||||
timeOfLastCheck = Time.time;
|
||||
InputField.text = suggestion.DisplayText;
|
||||
}
|
||||
|
||||
private readonly List<Suggestion> suggestions = new List<Suggestion>();
|
||||
|
||||
private readonly Dictionary<string, CachedType> typeCache = new Dictionary<string, CachedType>();
|
||||
|
||||
//// cached list of names for displaying (with proper case)
|
||||
//private readonly List<string> cachedTypesNames = new List<string>();
|
||||
//// cached list of lookup by index (lowercase)
|
||||
//private readonly List<string> cachedTypesFilter = new List<string>();
|
||||
//// cached hashset of names (lower case)
|
||||
//private readonly HashSet<string> cachedTypesSet = new HashSet<string>();
|
||||
|
||||
public TypeCompleter(Type baseType, InputField inputField)
|
||||
{
|
||||
BaseType = baseType;
|
||||
InputField = inputField;
|
||||
|
||||
inputField.onValueChanged.AddListener(OnInputFieldChanged);
|
||||
|
||||
var types = ReflectionUtility.GetImplementationsOf(typeof(UnityEngine.Object), true);
|
||||
foreach (var type in types.OrderBy(it => it.FullName))
|
||||
{
|
||||
var name = type.FullName;
|
||||
typeCache.Add(name.ToLower(), new CachedType
|
||||
{
|
||||
DisplayName = name,
|
||||
FilteredName = name.ToLower()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private float timeOfLastCheck;
|
||||
|
||||
private void OnInputFieldChanged(string value)
|
||||
{
|
||||
if (timeOfLastCheck == Time.time)
|
||||
return;
|
||||
|
||||
timeOfLastCheck = Time.time;
|
||||
|
||||
value = value?.ToLower() ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
AutoCompleter.ReleaseOwnership(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSuggestions(value);
|
||||
|
||||
AutoCompleter.TakeOwnership(this);
|
||||
AutoCompleter.SetSuggestions(suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSuggestions(string value)
|
||||
{
|
||||
suggestions.Clear();
|
||||
|
||||
var added = new HashSet<string>();
|
||||
|
||||
if (typeCache.TryGetValue(value, out CachedType cache))
|
||||
{
|
||||
added.Add(value);
|
||||
suggestions.Add(new Suggestion(cache.DisplayName,
|
||||
value,
|
||||
cache.FilteredName.Substring(value.Length, cache.FilteredName.Length - value.Length),
|
||||
Color.white));
|
||||
}
|
||||
|
||||
foreach (var entry in typeCache.Values)
|
||||
{
|
||||
if (added.Contains(entry.FilteredName))
|
||||
continue;
|
||||
|
||||
if (entry.FilteredName.Contains(value))
|
||||
{
|
||||
suggestions.Add(new Suggestion(entry.DisplayName,
|
||||
value,
|
||||
entry.FilteredName.Substring(value.Length, entry.FilteredName.Length - value.Length),
|
||||
Color.white));
|
||||
}
|
||||
|
||||
added.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user