diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index ed9cd68..f935e7c 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -25,6 +25,7 @@ namespace UnityExplorer.UI CSConsole, Options, ConsoleLog, + AutoCompleter } public static GameObject CanvasRoot { get; private set; } @@ -33,16 +34,20 @@ namespace UnityExplorer.UI // panels internal static GameObject PanelHolder { get; private set; } + public static ObjectExplorer Explorer { get; private set; } public static InspectorTest Inspector { get; private set; } + public static AutoCompleter AutoCompleter { get; private set; } + + private static readonly Dictionary navButtonDict = new Dictionary(); + internal static readonly Color navButtonEnabledColor = new Color(0.2f, 0.4f, 0.28f); + internal static readonly Color navButtonDisabledColor = new Color(0.25f, 0.25f, 0.25f); // bundle assets internal static Font ConsoleFont { get; private set; } internal static Shader BackupShader { get; private set; } - internal static readonly Color navButtonEnabledColor = new Color(0.2f, 0.4f, 0.28f); - internal static readonly Color navButtonDisabledColor = new Color(0.25f, 0.25f, 0.25f); - + // main menu toggle public static bool ShowMenu { get => s_showMenu; @@ -58,29 +63,6 @@ namespace UnityExplorer.UI } public static bool s_showMenu = true; - internal static void InitUI() - { - LoadBundle(); - - UIFactory.Init(); - - CreateRootCanvas(); - CreateTopNavBar(); - - AutoCompleter.ConstructUI(); - //InspectUnderMouse.ConstructUI(); - - Explorer = new ObjectExplorer(); - Explorer.ConstructUI(CanvasRoot); - - Inspector = new InspectorTest(); - Inspector.ConstructUI(CanvasRoot); - - ShowMenu = !ConfigManager.Hide_On_Startup.Value; - - ExplorerCore.Log("UI initialized."); - } - public static void Update() { if (!CanvasRoot) @@ -111,6 +93,63 @@ namespace UnityExplorer.UI AutoCompleter.Update(); } + public static UIPanel GetPanel(Panels panel) + { + switch (panel) + { + case Panels.ObjectExplorer: + return Explorer; + case Panels.Inspector: + return Inspector; + case Panels.AutoCompleter: + return AutoCompleter; + default: + throw new NotImplementedException($"TODO GetPanel: {panel}"); + } + } + + public static void TogglePanel(Panels panel) + { + var uiPanel = GetPanel(panel); + SetPanelActive(panel, !uiPanel.Enabled); + } + + public static void SetPanelActive(Panels panel, bool active) + { + GetPanel(panel).SetActive(active); + + if (navButtonDict.ContainsKey(panel)) + { + var color = active ? navButtonEnabledColor : navButtonDisabledColor; + RuntimeProvider.Instance.SetColorBlock(navButtonDict[panel], color, color * 1.2f); + } + } + + internal static void InitUI() + { + LoadBundle(); + + UIFactory.Init(); + + CreateRootCanvas(); + CreateTopNavBar(); + + AutoCompleter = new AutoCompleter(); + AutoCompleter.ConstructUI(); + + //InspectUnderMouse.ConstructUI(); + + Explorer = new ObjectExplorer(); + Explorer.ConstructUI(); + + Inspector = new InspectorTest(); + Inspector.ConstructUI(); + + ShowMenu = !ConfigManager.Hide_On_Startup.Value; + + ExplorerCore.Log("UI initialized."); + } + private static void CreateRootCanvas() { CanvasRoot = new GameObject("ExplorerCanvas"); @@ -145,32 +184,6 @@ namespace UnityExplorer.UI PanelHolder.transform.SetAsFirstSibling(); } - public static UIPanel GetPanel(Panels panel) - { - switch (panel) - { - case Panels.ObjectExplorer: - return Explorer; - case Panels.Inspector: - return Inspector; - default: - throw new NotImplementedException($"TODO GetPanel: {panel}"); - } - } - - public static void TogglePanel(Panels panel) - { - var uiPanel = GetPanel(panel); - SetPanelActive(panel, !uiPanel.Enabled); - } - - public static void SetPanelActive(Panels panel, bool active) - { - GetPanel(panel).SetActive(active); - var color = active ? navButtonEnabledColor : navButtonDisabledColor; - RuntimeProvider.Instance.SetColorBlock(navButtonDict[panel], color, color * 1.2f); - } - public static void CreateTopNavBar() { var panel = UIFactory.CreateUIObject("MainNavbar", CanvasRoot); @@ -200,8 +213,6 @@ namespace UnityExplorer.UI new Color(0.81f, 0.25f, 0.2f), new Color(0.6f, 0.18f, 0.16f)); } - private static readonly Dictionary navButtonDict = new Dictionary(); - private static void CreateNavButton(GameObject navbar, Panels panel, string label) { var button = UIFactory.CreateButton(navbar, $"Button_{panel}", label); diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index 784518c..77eba82 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using UnityEngine; +using UnityExplorer.Core.Runtime; namespace UnityExplorer.UI.Utility { @@ -47,6 +48,8 @@ namespace UnityExplorer.UI.Utility if (type == null) throw new ArgumentNullException("type"); + //type = ReflectionProvider.Instance.GetDeobfuscatedType(type); + string ret = ""; if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter)) @@ -91,8 +94,10 @@ namespace UnityExplorer.UI.Utility return ret; } - private static string HighlightTypeName(Type type) + public static string HighlightTypeName(Type type) { + //type = RuntimeProvider.Instance.Reflection.GetDeobfuscatedType(type); + var typeName = type.Name; var gArgs = type.GetGenericArguments(); @@ -121,7 +126,7 @@ namespace UnityExplorer.UI.Utility return typeName; } - private static string ParseGenericArgs(Type[] gArgs, bool allGeneric = false) + public static string ParseGenericArgs(Type[] gArgs, bool allGeneric = false) { if (gArgs.Length < 1) return ""; @@ -146,7 +151,7 @@ namespace UnityExplorer.UI.Utility return args + ">"; } - private static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic) + public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic) { string memberColor = ""; isStatic = false; diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 3eaba34..8113f4a 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -14,44 +14,38 @@ namespace UnityExplorer.UI.Utility internal static Dictionary toStringMethods = new Dictionary(); internal static Dictionary toStringFormattedMethods = new Dictionary(); - public static string ToString(object value, Type fallbackType, bool includeNamespace = true, bool includeName = true, bool objectAsType = false) + public static string ToString(object value, Type fallbackType, bool includeNamespace = true, bool includeName = true) { if (value == null && fallbackType == null) return ""; - Type type; - if (objectAsType) - type = value.TryCast(); - else - type = value?.GetActualType() ?? fallbackType; - + Type type = value?.GetActualType() ?? fallbackType; var richType = SignatureHighlighter.ParseFullSyntax(type, includeNamespace); - if (objectAsType) - return richType; - if (!includeName) return richType; if (value.IsNullOrDestroyed()) - return $"null ({richType})"; + { + if (value == null) + return $"[null] ({richType})"; + else + return $"[Destroyed] ({richType})"; + } + + // value = value.TryCast(type); string label; // Two dirty fixes for TextAsset and EventSystem, which can have very long ToString results. if (value is TextAsset textAsset) { - label = textAsset.text; - - if (label.Length > 10) - label = $"{label.Substring(0, 10)}..."; - - label = $"\"{label}\" {textAsset.name} ({richType})"; + label = $"{textAsset.name} ({richType})"; } - else if (value is EventSystem) + else if (value is EventSystem es) { - label = richType; + label = $"{es.name} ({richType})"; } else // For everything else... { @@ -73,6 +67,8 @@ namespace UnityExplorer.UI.Utility var f3Method = toStringFormattedMethods[type]; var stdMethod = toStringMethods[type]; + value = value.TryCast(type); + string toString; if (f3Method != null) toString = (string)f3Method.Invoke(value, new object[] { "F3" }); @@ -85,7 +81,7 @@ namespace UnityExplorer.UI.Utility if (typeName.StartsWith("Il2CppSystem.")) typeName = typeName.Substring(6, typeName.Length - 6); - toString = ReflectionProvider.Instance.ProcessTypeNameInString(type, toString, ref typeName); + toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref typeName); // If the ToString is just the type name, use our syntax highlighted type name instead. if (toString == typeName) diff --git a/src/UI/Widgets/AutoComplete/AutoCompleter.cs b/src/UI/Widgets/AutoComplete/AutoCompleter.cs index a1e46a7..fc848ff 100644 --- a/src/UI/Widgets/AutoComplete/AutoCompleter.cs +++ b/src/UI/Widgets/AutoComplete/AutoCompleter.cs @@ -11,23 +11,60 @@ 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 class AutoCompleter : UIPanel { - public static ISuggestionProvider CurrentHandler; + // Static - public static GameObject UIRoot => uiRoot; - public static GameObject uiRoot; + public static AutoCompleter Instance => UIManager.AutoCompleter; - public static ButtonListSource dataHandler; - public static ScrollPool scrollPool; + // Instance - private static List suggestions = new List(); + public AutoCompleter() + { + OnPanelsReordered += UIPanel_OnPanelsReordered; + OnClickedOutsidePanels += AutoCompleter_OnClickedOutsidePanels; + } - private static int lastCaretPos; + private void AutoCompleter_OnClickedOutsidePanels() + { + if (!this.UIRoot || !this.UIRoot.activeInHierarchy) + return; - public static void Update() + if (CurrentHandler != null) + ReleaseOwnership(CurrentHandler); + else + UIRoot.SetActive(false); + } + + private void UIPanel_OnPanelsReordered() + { + if (!this.UIRoot || !this.UIRoot.activeInHierarchy) + return; + + if (this.UIRoot.transform.GetSiblingIndex() != UIManager.PanelHolder.transform.childCount - 1) + { + if (CurrentHandler != null) + ReleaseOwnership(CurrentHandler); + else + UIRoot.SetActive(false); + } + } + + public override string Name => "AutoCompleter"; + public override UIManager.Panels PanelType => UIManager.Panels.AutoCompleter; + + public override bool CanDrag => false; + + public ISuggestionProvider CurrentHandler { get; private set; } + + public ButtonListSource dataHandler; + public ScrollPool scrollPool; + + private List suggestions = new List(); + + private int lastCaretPos; + + public override void Update() { if (!UIRoot || !UIRoot.activeSelf) return; @@ -44,12 +81,12 @@ namespace UnityExplorer.UI.Widgets.AutoComplete } } - public static void TakeOwnership(ISuggestionProvider provider) + public void TakeOwnership(ISuggestionProvider provider) { CurrentHandler = provider; } - public static void ReleaseOwnership(ISuggestionProvider provider) + public void ReleaseOwnership(ISuggestionProvider provider) { if (CurrentHandler == null) return; @@ -61,11 +98,11 @@ namespace UnityExplorer.UI.Widgets.AutoComplete } } - private static List GetEntries() => suggestions; + private List GetEntries() => suggestions; - private static bool ShouldDisplay(Suggestion data, string filter) => true; + private bool ShouldDisplay(Suggestion data, string filter) => true; - public static void SetSuggestions(List collection) + public void SetSuggestions(List collection) { suggestions = collection; @@ -74,19 +111,19 @@ namespace UnityExplorer.UI.Widgets.AutoComplete else { UIRoot.SetActive(true); + UIRoot.transform.SetAsLastSibling(); dataHandler.RefreshData(); - scrollPool.Rebuild(); - //scrollPool.RefreshCells(true); + scrollPool.RefreshAndJumpToTop(); } } - private static void OnCellClicked(int dataIndex) + private void OnCellClicked(int dataIndex) { var suggestion = suggestions[dataIndex]; CurrentHandler.OnSuggestionClicked(suggestion); } - private static void SetCell(ButtonCell cell, int index) + private void SetCell(ButtonCell cell, int index) { if (index < 0 || index >= suggestions.Count) { @@ -98,43 +135,66 @@ namespace UnityExplorer.UI.Widgets.AutoComplete cell.buttonText.text = suggestion.DisplayText; } - private static void UpdatePosition() + private void UpdatePosition() { if (CurrentHandler == null || !CurrentHandler.InputField.isFocused) return; + Vector3 pos; var input = CurrentHandler.InputField; + var textGen = input.textComponent.cachedTextGenerator; - int caretPos = lastCaretPos; - caretPos--; + int caretPos = 0; + if (CurrentHandler.AnchorToCaretPosition) + { + caretPos = lastCaretPos--; - caretPos = Math.Max(0, caretPos); - caretPos = Math.Min(textGen.characters.Count - 1, caretPos); + caretPos = Math.Max(0, caretPos); + caretPos = Math.Min(textGen.characterCount - 1, caretPos); + } - var pos = textGen.characters[caretPos].cursorPos; + pos = textGen.characters[caretPos].cursorPos; pos = input.transform.TransformPoint(pos); uiRoot.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0); + + this.Dragger.OnEndResize(); } - public static void ConstructUI() + public override void SetDefaultPosAndAnchors() { - var parent = UIManager.CanvasRoot; - - dataHandler = new ButtonListSource(scrollPool, GetEntries, SetCell, ShouldDisplay, OnCellClicked); - - scrollPool = UIFactory.CreateScrollPool(parent, "AutoCompleter", out uiRoot, out GameObject scrollContent); var mainRect = uiRoot.GetComponent(); 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(scrollContent, true, false, true, false); + mainRect.anchorMin = new Vector2(0, 1); + mainRect.anchorMax = new Vector2(0, 1); + mainRect.offsetMin = new Vector2(25, 0); + mainRect.offsetMax = new Vector2(525, 169); + } - scrollPool.Initialize(dataHandler, ButtonCell.CreatePrototypeCell(parent)); + public override void ConstructPanelContent() + { + dataHandler = new ButtonListSource(scrollPool, GetEntries, SetCell, ShouldDisplay, OnCellClicked); + + var prototypeCell = ButtonCell.CreatePrototypeCell(this.content); + prototypeCell.GetComponentInChildren().supportRichText = true; + + scrollPool = UIFactory.CreateScrollPool(this.content, "AutoCompleter", out GameObject scrollObj, out GameObject scrollContent); + scrollPool.Initialize(dataHandler, prototypeCell); + UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); + + UIFactory.SetLayoutGroup(scrollContent, true, false, true, false); UIRoot.SetActive(false); } + + public override void SaveToConfigManager() + { + // not savable + } + + public override void LoadSaveData() + { + // not savable + } } } diff --git a/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs b/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs index ca1d6a3..61e8988 100644 --- a/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs +++ b/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs @@ -10,6 +10,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete public interface ISuggestionProvider { InputField InputField { get; } + bool AnchorToCaretPosition { get; } void OnSuggestionClicked(Suggestion suggestion); } diff --git a/src/UI/Widgets/AutoComplete/Suggestion.cs b/src/UI/Widgets/AutoComplete/Suggestion.cs index d0554f1..6731868 100644 --- a/src/UI/Widgets/AutoComplete/Suggestion.cs +++ b/src/UI/Widgets/AutoComplete/Suggestion.cs @@ -10,18 +10,18 @@ namespace UnityExplorer.UI.Widgets.AutoComplete public struct Suggestion { public readonly string DisplayText; + public readonly string UnderlyingValue; 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) + public Suggestion(string displayText, string prefix, string addition, string underlyingValue) { DisplayText = displayText; Addition = addition; Prefix = prefix; - TextColor = color; + UnderlyingValue = underlyingValue; } } } diff --git a/src/UI/Widgets/AutoComplete/TypeCompleter.cs b/src/UI/Widgets/AutoComplete/TypeCompleter.cs index 1133919..82720ab 100644 --- a/src/UI/Widgets/AutoComplete/TypeCompleter.cs +++ b/src/UI/Widgets/AutoComplete/TypeCompleter.cs @@ -10,24 +10,26 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { public class TypeCompleter : ISuggestionProvider { - private struct CachedType + private class CachedType { - public string FilteredName; + public string FullNameForFilter; + public string FullNameValue; public string DisplayName; } public Type BaseType { get; } public InputField InputField { get; } + public bool AnchorToCaretPosition => false; public event Action SuggestionClicked; public void OnSuggestionClicked(Suggestion suggestion) { SuggestionClicked?.Invoke(suggestion); suggestions.Clear(); - AutoCompleter.SetSuggestions(suggestions); + AutoCompleter.Instance.SetSuggestions(suggestions); timeOfLastCheck = Time.time; - InputField.text = suggestion.DisplayText; + InputField.text = suggestion.UnderlyingValue; } private readonly List suggestions = new List(); @@ -48,16 +50,34 @@ namespace UnityExplorer.UI.Widgets.AutoComplete inputField.onValueChanged.AddListener(OnInputFieldChanged); - var types = ReflectionUtility.GetImplementationsOf(typeof(UnityEngine.Object), true); - foreach (var type in types.OrderBy(it => it.FullName)) + var types = ReflectionUtility.GetImplementationsOf(this.BaseType, true, false); + + var list = new List(); + + foreach (var type in types) { - var name = type.FullName; - typeCache.Add(name.ToLower(), new CachedType - { - DisplayName = name, - FilteredName = name.ToLower() + string displayName = Utility.SignatureHighlighter.ParseFullSyntax(type, true); + string fullName = RuntimeProvider.Instance.Reflection.GetDeobfuscatedType(type).FullName; + + string filteredName = fullName.ToLower(); + + list.Add(new CachedType + { + FullNameValue = fullName, + FullNameForFilter = filteredName, + DisplayName = displayName, }); } + + list.Sort((CachedType a, CachedType b) => a.FullNameForFilter.CompareTo(b.FullNameForFilter)); + + foreach (var cache in list) + { + if (typeCache.ContainsKey(cache.FullNameForFilter)) + continue; + typeCache.Add(cache.FullNameForFilter, cache); + } + } private float timeOfLastCheck; @@ -73,14 +93,14 @@ namespace UnityExplorer.UI.Widgets.AutoComplete if (string.IsNullOrEmpty(value)) { - AutoCompleter.ReleaseOwnership(this); + AutoCompleter.Instance.ReleaseOwnership(this); } else { GetSuggestions(value); - AutoCompleter.TakeOwnership(this); - AutoCompleter.SetSuggestions(suggestions); + AutoCompleter.Instance.TakeOwnership(this); + AutoCompleter.Instance.SetSuggestions(suggestions); } } @@ -91,28 +111,27 @@ namespace UnityExplorer.UI.Widgets.AutoComplete var added = new HashSet(); 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)); - } + AddToDict(cache); foreach (var entry in typeCache.Values) { - if (added.Contains(entry.FilteredName)) + if (added.Contains(entry.FullNameValue)) 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)); - } + if (entry.FullNameForFilter.Contains(value)) + AddToDict(entry); - added.Add(value); + added.Add(entry.FullNameValue); + } + + void AddToDict(CachedType entry) + { + added.Add(entry.FullNameValue); + + suggestions.Add(new Suggestion(entry.DisplayName, + value, + entry.FullNameForFilter.Substring(value.Length, entry.FullNameForFilter.Length - value.Length), + entry.FullNameValue)); } } } diff --git a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs index f47a4cc..eba90d4 100644 --- a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs @@ -86,8 +86,12 @@ namespace UnityExplorer.UI.Widgets public void SetCell(ButtonCell cell, int index) { - bool objectAsType = m_context == SearchContext.StaticClass; - cell.buttonText.text = ToStringUtility.ToString(currentResults[index], currentResults[index].GetActualType(), objectAsType: objectAsType); + if (m_context == SearchContext.StaticClass) + { + cell.buttonText.text = SignatureHighlighter.HighlightTypeName(currentResults[index].GetActualType()); + } + else + cell.buttonText.text = ToStringUtility.ToString(currentResults[index], currentResults[index].GetActualType()); } private void OnCellClicked(int dataIndex)