mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 08:32:51 +08:00
Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
f54ff89290 | |||
62d565777d | |||
870f82ab26 | |||
9370c5e0e6 | |||
b8cf96438c | |||
7be7daf4d7 | |||
8f54415ae0 | |||
aef4e11c01 | |||
af7e32ec49 | |||
6f44a3376b | |||
3a6b573ac3 | |||
cbe17927fb | |||
15f3f37948 | |||
de6760e427 | |||
83edd1b9bb | |||
613be34e95 | |||
58c65b9b8b | |||
6fcf6a521c | |||
81a174f865 | |||
b5e3cc2ea5 | |||
14f46ade6a | |||
e92556805b | |||
8662742461 | |||
63393a9d66 | |||
13986f95c1 | |||
b47bfa1e83 | |||
12c51248fe | |||
9b9cb54a79 | |||
6a28a93e3a | |||
32e718faeb | |||
4d46b74d54 | |||
7e5246cead | |||
abf5267364 | |||
3afee7254c | |||
1643d4b7dd | |||
cef8c12d20 | |||
5e07847356 | |||
2dc6e386df | |||
ff882296fd | |||
ecc33927ee | |||
6e91f2a792 | |||
97cb14d6fc | |||
8b861f7c77 | |||
9379e0f813 | |||
bdda12a040 | |||
75bd654a94 | |||
f174c7543a | |||
08f2c6035e | |||
475e24a66a | |||
c62b93535d | |||
374d0b3bae | |||
1f87e89b97 | |||
495bc41a8d | |||
9cf62c3250 | |||
4c9b3115cd | |||
ebdce70418 | |||
7c0b37440b | |||
0d8ab8bf14 | |||
5a3cad9be2 | |||
70d67f1dad | |||
469484d129 | |||
c5e262d1c3 | |||
53c8dfcb6d | |||
ae3ac21992 | |||
7765b64748 | |||
a5023d03f4 | |||
a5e6b65dee |
@ -115,7 +115,7 @@ The inspector is used to see detailed information on objects of any type and man
|
||||
### Hook Manager
|
||||
|
||||
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
|
||||
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
|
||||
* Simply enter any class and hook the methods you want from the menu.
|
||||
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
|
||||
|
||||
### Mouse-Inspect
|
||||
@ -124,6 +124,13 @@ The inspector is used to see detailed information on objects of any type and man
|
||||
* <b>World</b>: uses Physics.Raycast to look for Colliders
|
||||
* <b>UI</b>: uses GraphicRaycasters to find UI objects
|
||||
|
||||
### Freecam
|
||||
|
||||
* UnityExplorer provides a basic Free Camera which you can control with your keyboard and mouse.
|
||||
* Unlike all other features of UnityExplorer, you can still use Freecam while UnityExplorer's menu is hidden.
|
||||
* Supports using the game's main Camera or a separate custom Camera.
|
||||
* See the Freecam panel for further instructions and details.
|
||||
|
||||
### Clipboard
|
||||
|
||||
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`)
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.sinai-dev.unityexplorer",
|
||||
"version": "4.7.0",
|
||||
"version": "4.7.11",
|
||||
"displayName": "UnityExplorer",
|
||||
"description": "An in-game UI for exploring, debugging and modifying Unity games.",
|
||||
"unity": "2017.1",
|
||||
|
@ -23,28 +23,33 @@ namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public static class ConsoleController
|
||||
{
|
||||
public static ScriptEvaluator Evaluator;
|
||||
public static LexerBuilder Lexer;
|
||||
public static CSAutoCompleter Completer;
|
||||
|
||||
private static HashSet<string> usingDirectives;
|
||||
private static StringBuilder evaluatorOutput;
|
||||
private static StringWriter evaluatorStringWriter;
|
||||
|
||||
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
|
||||
public static InputFieldRef Input => Panel.Input;
|
||||
public static ScriptEvaluator Evaluator { get; private set; }
|
||||
public static LexerBuilder Lexer { get; private set; }
|
||||
public static CSAutoCompleter Completer { get; private set; }
|
||||
|
||||
public static bool SRENotSupported { get; private set; }
|
||||
public static int LastCaretPosition { get; private set; }
|
||||
internal static float defaultInputFieldAlpha;
|
||||
public static float DefaultInputFieldAlpha { get; set; }
|
||||
|
||||
// Todo save as config?
|
||||
public static bool EnableCtrlRShortcut { get; private set; } = true;
|
||||
public static bool EnableAutoIndent { get; private set; } = true;
|
||||
public static bool EnableSuggestions { get; private set; } = true;
|
||||
|
||||
internal static string ScriptsFolder => Path.Combine(ExplorerCore.ExplorerFolder, "Scripts");
|
||||
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
|
||||
public static InputFieldRef Input => Panel.Input;
|
||||
|
||||
internal static readonly string[] DefaultUsing = new string[]
|
||||
public static string ScriptsFolder => Path.Combine(ExplorerCore.ExplorerFolder, "Scripts");
|
||||
|
||||
static HashSet<string> usingDirectives;
|
||||
static StringBuilder evaluatorOutput;
|
||||
static StringWriter evaluatorStringWriter;
|
||||
static float timeOfLastCtrlR;
|
||||
|
||||
static bool settingCaretCoroutine;
|
||||
static string previousInput;
|
||||
static int previousContentLength = 0;
|
||||
|
||||
static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"System.Linq",
|
||||
@ -52,17 +57,17 @@ namespace UnityExplorer.CSConsole
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"UnityEngine",
|
||||
"UniverseLib",
|
||||
#if CPP
|
||||
"UnhollowerBaseLib",
|
||||
"UnhollowerRuntimeLib",
|
||||
#endif
|
||||
};
|
||||
|
||||
const int CSCONSOLE_LINEHEIGHT = 18;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
InitEventSystemPropertyHandlers();
|
||||
|
||||
// Make sure console is supported on this platform
|
||||
try
|
||||
{
|
||||
ResetConsole(false);
|
||||
@ -112,31 +117,10 @@ namespace UnityExplorer.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
#region UI Listeners and options
|
||||
|
||||
// TODO save
|
||||
|
||||
private static void OnToggleAutoIndent(bool value)
|
||||
{
|
||||
EnableAutoIndent = value;
|
||||
}
|
||||
|
||||
private static void OnToggleCtrlRShortcut(bool value)
|
||||
{
|
||||
EnableCtrlRShortcut = value;
|
||||
}
|
||||
|
||||
private static void OnToggleSuggestions(bool value)
|
||||
{
|
||||
EnableSuggestions = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Evaluating
|
||||
|
||||
private static void GenerateTextWriter()
|
||||
static void GenerateTextWriter()
|
||||
{
|
||||
evaluatorOutput = new StringBuilder();
|
||||
evaluatorStringWriter = new StringWriter(evaluatorOutput);
|
||||
@ -248,16 +232,45 @@ namespace UnityExplorer.CSConsole
|
||||
#endregion
|
||||
|
||||
|
||||
// Updating and event listeners
|
||||
#region Update loop and event listeners
|
||||
|
||||
private static bool settingCaretCoroutine;
|
||||
public static void Update()
|
||||
{
|
||||
if (SRENotSupported)
|
||||
return;
|
||||
|
||||
private static void OnInputScrolled() => HighlightVisibleInput();
|
||||
if (InputManager.GetKeyDown(KeyCode.Home))
|
||||
JumpToStartOrEndOfLine(true);
|
||||
else if (InputManager.GetKeyDown(KeyCode.End))
|
||||
JumpToStartOrEndOfLine(false);
|
||||
|
||||
private static string previousInput;
|
||||
UpdateCaret(out bool caretMoved);
|
||||
|
||||
// Invoked at most once per frame
|
||||
private static void OnInputChanged(string value)
|
||||
if (!settingCaretCoroutine && EnableSuggestions)
|
||||
{
|
||||
if (AutoCompleteModal.CheckEscape(Completer))
|
||||
{
|
||||
OnAutocompleteEscaped();
|
||||
return;
|
||||
}
|
||||
|
||||
if (caretMoved)
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
|
||||
}
|
||||
|
||||
if (EnableCtrlRShortcut
|
||||
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
|
||||
&& InputManager.GetKeyDown(KeyCode.R)
|
||||
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
|
||||
{
|
||||
timeOfLastCtrlR = Time.realtimeSinceStartup;
|
||||
Evaluate(Panel.Input.Text);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnInputScrolled() => HighlightVisibleInput(out _);
|
||||
|
||||
static void OnInputChanged(string value)
|
||||
{
|
||||
if (SRENotSupported)
|
||||
return;
|
||||
@ -284,7 +297,7 @@ namespace UnityExplorer.CSConsole
|
||||
DoAutoIndent();
|
||||
}
|
||||
|
||||
bool inStringOrComment = HighlightVisibleInput();
|
||||
HighlightVisibleInput(out bool inStringOrComment);
|
||||
|
||||
if (!settingCaretCoroutine)
|
||||
{
|
||||
@ -300,40 +313,27 @@ namespace UnityExplorer.CSConsole
|
||||
UpdateCaret(out _);
|
||||
}
|
||||
|
||||
private static float timeOfLastCtrlR;
|
||||
|
||||
public static void Update()
|
||||
static void OnToggleAutoIndent(bool value)
|
||||
{
|
||||
if (SRENotSupported)
|
||||
return;
|
||||
|
||||
UpdateCaret(out bool caretMoved);
|
||||
|
||||
if (!settingCaretCoroutine && EnableSuggestions)
|
||||
{
|
||||
if (AutoCompleteModal.CheckEscape(Completer))
|
||||
{
|
||||
OnAutocompleteEscaped();
|
||||
return;
|
||||
}
|
||||
|
||||
if (caretMoved)
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
|
||||
}
|
||||
|
||||
if (EnableCtrlRShortcut
|
||||
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
|
||||
&& InputManager.GetKeyDown(KeyCode.R)
|
||||
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
|
||||
{
|
||||
timeOfLastCtrlR = Time.realtimeSinceStartup;
|
||||
Evaluate(Panel.Input.Text);
|
||||
}
|
||||
EnableAutoIndent = value;
|
||||
}
|
||||
|
||||
private const int CSCONSOLE_LINEHEIGHT = 18;
|
||||
static void OnToggleCtrlRShortcut(bool value)
|
||||
{
|
||||
EnableCtrlRShortcut = value;
|
||||
}
|
||||
|
||||
private static void UpdateCaret(out bool caretMoved)
|
||||
static void OnToggleSuggestions(bool value)
|
||||
{
|
||||
EnableSuggestions = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Caret position
|
||||
|
||||
static void UpdateCaret(out bool caretMoved)
|
||||
{
|
||||
int prevCaret = LastCaretPosition;
|
||||
caretMoved = false;
|
||||
@ -378,112 +378,98 @@ namespace UnityExplorer.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCaretPosition(int caretPosition)
|
||||
public static void SetCaretPosition(int caretPosition)
|
||||
{
|
||||
Input.Component.caretPosition = caretPosition;
|
||||
|
||||
// Fix to make sure we always really set the caret position.
|
||||
// Yields a frame and fixes text-selection issues.
|
||||
settingCaretCoroutine = true;
|
||||
Input.Component.readOnly = true;
|
||||
RuntimeHelper.StartCoroutine(SetCaretCoroutine(caretPosition));
|
||||
RuntimeHelper.StartCoroutine(DoSetCaretCoroutine(caretPosition));
|
||||
}
|
||||
|
||||
static void InitEventSystemPropertyHandlers()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (MemberInfo member in typeof(EventSystem).GetMembers(AccessTools.all))
|
||||
{
|
||||
if (member.Name == "m_CurrentSelected")
|
||||
{
|
||||
Type backingType;
|
||||
if (member.MemberType == MemberTypes.Property)
|
||||
backingType = (member as PropertyInfo).PropertyType;
|
||||
else
|
||||
backingType = (member as FieldInfo).FieldType;
|
||||
|
||||
usingEventSystemDictionaryMembers = ReflectionUtility.IsDictionary(backingType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception checking EventSystem property backing type: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
static bool usingEventSystemDictionaryMembers;
|
||||
|
||||
static readonly AmbiguousMemberHandler<EventSystem, GameObject> m_CurrentSelected_Handler_Normal
|
||||
= new(true, true, "m_CurrentSelected", "m_currentSelected");
|
||||
static readonly AmbiguousMemberHandler<EventSystem, Dictionary<int, GameObject>> m_CurrentSelected_Handler_Dictionary
|
||||
= new(true, true, "m_CurrentSelected", "m_currentSelected");
|
||||
|
||||
static readonly AmbiguousMemberHandler<EventSystem, bool> m_SelectionGuard_Handler_Normal
|
||||
= new(true, true, "m_SelectionGuard", "m_selectionGuard");
|
||||
static readonly AmbiguousMemberHandler<EventSystem, Dictionary<int, bool>> m_SelectionGuard_Handler_Dictionary
|
||||
= new(true, true, "m_SelectionGuard", "m_selectionGuard");
|
||||
|
||||
static void SetCurrentSelectedGameObject(EventSystem instance, GameObject value)
|
||||
{
|
||||
instance.SetSelectedGameObject(value);
|
||||
|
||||
if (usingEventSystemDictionaryMembers)
|
||||
m_CurrentSelected_Handler_Dictionary.GetValue(instance)[0] = value;
|
||||
else
|
||||
m_CurrentSelected_Handler_Normal.SetValue(instance, value);
|
||||
}
|
||||
|
||||
static void SetSelectionGuard(EventSystem instance, bool value)
|
||||
{
|
||||
if (usingEventSystemDictionaryMembers)
|
||||
m_SelectionGuard_Handler_Dictionary.GetValue(instance)[0] = value;
|
||||
else
|
||||
m_SelectionGuard_Handler_Normal.SetValue(instance, value);
|
||||
}
|
||||
|
||||
private static IEnumerator SetCaretCoroutine(int caretPosition)
|
||||
static IEnumerator DoSetCaretCoroutine(int caretPosition)
|
||||
{
|
||||
Color color = Input.Component.selectionColor;
|
||||
color.a = 0f;
|
||||
Input.Component.selectionColor = color;
|
||||
|
||||
try { SetCurrentSelectedGameObject(CursorUnlocker.CurrentEventSystem, null); }
|
||||
catch (Exception ex) { ExplorerCore.Log($"Failed removing selected object: {ex}"); }
|
||||
|
||||
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
|
||||
|
||||
try { SetSelectionGuard(CursorUnlocker.CurrentEventSystem, false); }
|
||||
catch (Exception ex) { ExplorerCore.Log($"Failed setting selection guard: {ex}"); }
|
||||
|
||||
try { SetCurrentSelectedGameObject(CursorUnlocker.CurrentEventSystem, Input.GameObject); }
|
||||
catch (Exception ex) { ExplorerCore.Log($"Failed setting selected gameobject: {ex}"); }
|
||||
|
||||
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
|
||||
|
||||
EventSystemHelper.SetSelectionGuard(false);
|
||||
Input.Component.Select();
|
||||
|
||||
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
|
||||
|
||||
Input.Component.caretPosition = caretPosition;
|
||||
Input.Component.selectionFocusPosition = caretPosition;
|
||||
LastCaretPosition = Input.Component.caretPosition;
|
||||
|
||||
color.a = defaultInputFieldAlpha;
|
||||
color.a = DefaultInputFieldAlpha;
|
||||
Input.Component.selectionColor = color;
|
||||
|
||||
Input.Component.readOnly = false;
|
||||
settingCaretCoroutine = false;
|
||||
}
|
||||
|
||||
// For Home and End keys
|
||||
static void JumpToStartOrEndOfLine(bool toStart)
|
||||
{
|
||||
// Determine the current and next line
|
||||
UILineInfo thisline = default;
|
||||
UILineInfo? nextLine = null;
|
||||
for (int i = 0; i < Input.Component.cachedInputTextGenerator.lineCount; i++)
|
||||
{
|
||||
UILineInfo line = Input.Component.cachedInputTextGenerator.lines[i];
|
||||
|
||||
if (line.startCharIdx > LastCaretPosition)
|
||||
{
|
||||
nextLine = line;
|
||||
break;
|
||||
}
|
||||
thisline = line;
|
||||
}
|
||||
|
||||
if (toStart)
|
||||
{
|
||||
// Determine where the indented text begins
|
||||
int endOfLine = nextLine == null ? Input.Text.Length : nextLine.Value.startCharIdx;
|
||||
int indentedStart = thisline.startCharIdx;
|
||||
while (indentedStart < endOfLine - 1 && char.IsWhiteSpace(Input.Text[indentedStart]))
|
||||
indentedStart++;
|
||||
|
||||
// Jump to either the true start or the non-whitespace position,
|
||||
// depending on which one we are not at.
|
||||
if (LastCaretPosition == indentedStart)
|
||||
SetCaretPosition(thisline.startCharIdx);
|
||||
else
|
||||
SetCaretPosition(indentedStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is no next line, jump to the end of this line (+1, to the invisible next character position)
|
||||
if (nextLine == null)
|
||||
SetCaretPosition(Input.Text.Length);
|
||||
else // jump to the next line start index - 1, ie. end of this line
|
||||
SetCaretPosition(nextLine.Value.startCharIdx - 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Lexer Highlighting
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if caret is inside string or comment, false otherwise
|
||||
/// </summary>
|
||||
private static bool HighlightVisibleInput()
|
||||
private static void HighlightVisibleInput(out bool inStringOrComment)
|
||||
{
|
||||
inStringOrComment = false;
|
||||
if (string.IsNullOrEmpty(Input.Text))
|
||||
{
|
||||
Panel.HighlightText.text = "";
|
||||
Panel.LineNumberText.text = "1";
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the visible lines
|
||||
@ -518,7 +504,7 @@ namespace UnityExplorer.CSConsole
|
||||
|
||||
// Highlight the visible text with the LexerBuilder
|
||||
|
||||
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret);
|
||||
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out inStringOrComment);
|
||||
|
||||
// Set the line numbers
|
||||
|
||||
@ -556,7 +542,7 @@ namespace UnityExplorer.CSConsole
|
||||
|
||||
Panel.LineNumberText.text = sb.ToString();
|
||||
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -596,13 +582,11 @@ namespace UnityExplorer.CSConsole
|
||||
|
||||
#region Auto indenting
|
||||
|
||||
private static int prevContentLen = 0;
|
||||
|
||||
private static void DoAutoIndent()
|
||||
{
|
||||
if (Input.Text.Length > prevContentLen)
|
||||
if (Input.Text.Length > previousContentLength)
|
||||
{
|
||||
int inc = Input.Text.Length - prevContentLen;
|
||||
int inc = Input.Text.Length - previousContentLength;
|
||||
|
||||
if (inc == 1)
|
||||
{
|
||||
@ -621,7 +605,7 @@ namespace UnityExplorer.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
prevContentLen = Input.Text.Length;
|
||||
previousContentLength = Input.Text.Length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -629,8 +613,6 @@ namespace UnityExplorer.CSConsole
|
||||
|
||||
#region "Help" interaction
|
||||
|
||||
private static bool SRENotSupported;
|
||||
|
||||
private static void DisableConsole(Exception ex)
|
||||
{
|
||||
SRENotSupported = true;
|
||||
@ -704,7 +686,7 @@ Doorstop example:
|
||||
// It is recommended to use the Log panel (or a console log window) while using this tool.
|
||||
// Use the Help dropdown to see detailed examples of how to use the console.
|
||||
|
||||
// To execute a script automatically on startup, put the script at 'UnityExplorer\Scripts\startup.cs'</color>";
|
||||
// To execute a script automatically on startup, put the script at 'sinai-dev-UnityExplorer\Scripts\startup.cs'</color>";
|
||||
|
||||
internal const string HELP_USINGS = @"// You can add a using directive to any namespace, but you must compile for it to take effect.
|
||||
// It will remain in effect until you Reset the console.
|
||||
|
@ -17,8 +17,6 @@ namespace UnityExplorer.CSConsole
|
||||
|
||||
public class LexerBuilder
|
||||
{
|
||||
#region Core and initialization
|
||||
|
||||
public const char WHITESPACE = ' ';
|
||||
public readonly HashSet<char> IndentOpenChars = new() { '{', '(' };
|
||||
public readonly HashSet<char> IndentCloseChars = new() { '}', ')' };
|
||||
@ -50,8 +48,6 @@ namespace UnityExplorer.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The last committed index for a match or no-match. Starts at -1 for a new parse.</summary>
|
||||
public int CommittedIndex { get; private set; }
|
||||
/// <summary>The index of the character we are currently parsing, at minimum it will be CommittedIndex + 1.</summary>
|
||||
|
@ -32,13 +32,7 @@ namespace UnityExplorer.CSConsole.Lexers
|
||||
|
||||
if (IsSymbol(lexer.Current))
|
||||
{
|
||||
do
|
||||
{
|
||||
lexer.Commit();
|
||||
lexer.PeekNext();
|
||||
}
|
||||
while (IsSymbol(lexer.Current));
|
||||
|
||||
lexer.Commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ namespace UnityExplorer.CacheObject
|
||||
this.Owner = inspector;
|
||||
this.NameLabelText = this switch
|
||||
{
|
||||
CacheMethod => SignatureHighlighter.HighlightMethod(member as MethodInfo),
|
||||
CacheConstructor => SignatureHighlighter.HighlightConstructor(member as ConstructorInfo),
|
||||
CacheMethod => SignatureHighlighter.ParseMethod(member as MethodInfo),
|
||||
CacheConstructor => SignatureHighlighter.ParseConstructor(member as ConstructorInfo),
|
||||
_ => SignatureHighlighter.Parse(member.DeclaringType, false, member),
|
||||
};
|
||||
|
||||
|
@ -54,6 +54,10 @@ namespace UnityExplorer.Config
|
||||
Handler.LoadConfig();
|
||||
InternalHandler.LoadConfig();
|
||||
|
||||
#if STANDALONE
|
||||
Loader.Standalone.ExplorerEditorBehaviour.Instance?.LoadConfigs();
|
||||
#endif
|
||||
|
||||
//InitConsoleCallback();
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,9 @@ namespace UnityExplorer.Config
|
||||
TomlDocument document = TomlParser.ParseFile(CONFIG_PATH);
|
||||
foreach (string key in document.Keys)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(UIManager.Panels), key))
|
||||
continue;
|
||||
|
||||
UIManager.Panels panelKey = (UIManager.Panels)Enum.Parse(typeof(UIManager.Panels), key);
|
||||
ConfigManager.GetPanelSaveData(panelKey).Value = document.GetString(key);
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
@ -29,5 +33,39 @@ namespace UnityExplorer
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
}
|
||||
|
||||
// For editor, to clean up objects
|
||||
|
||||
internal void OnDestroy()
|
||||
{
|
||||
OnApplicationQuit();
|
||||
}
|
||||
|
||||
internal bool quitting;
|
||||
|
||||
internal void OnApplicationQuit()
|
||||
{
|
||||
if (quitting) return;
|
||||
quitting = true;
|
||||
|
||||
TryDestroy(UIManager.UIRoot?.transform.root.gameObject);
|
||||
|
||||
TryDestroy((typeof(Universe).Assembly.GetType("UniverseLib.UniversalBehaviour")
|
||||
.GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic)
|
||||
.GetValue(null, null)
|
||||
as Component).gameObject);
|
||||
|
||||
TryDestroy(this.gameObject);
|
||||
}
|
||||
|
||||
internal void TryDestroy(GameObject obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj)
|
||||
Destroy(obj);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace UnityExplorer
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "4.7.2";
|
||||
public const string VERSION = "4.7.11";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.unityexplorer";
|
||||
|
||||
|
@ -16,14 +16,13 @@ namespace UnityExplorer.Hooks
|
||||
public float DefaultHeight => 30;
|
||||
|
||||
public Text MethodNameLabel;
|
||||
public Text HookedLabel;
|
||||
public ButtonRef HookButton;
|
||||
|
||||
public int CurrentDisplayedIndex;
|
||||
|
||||
private void OnHookClicked()
|
||||
{
|
||||
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
|
||||
HookCreator.AddHookClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
@ -44,9 +43,6 @@ namespace UnityExplorer.Hooks
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
|
||||
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
|
||||
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
|
||||
|
||||
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
|
||||
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
HookButton.OnClick += OnHookClicked;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
@ -24,17 +25,18 @@ namespace UnityExplorer.Hooks
|
||||
|
||||
private void OnToggleActiveClicked()
|
||||
{
|
||||
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
|
||||
HookList.EnableOrDisableHookClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
private void OnDeleteClicked()
|
||||
{
|
||||
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
|
||||
HookList.DeleteHookClicked(CurrentDisplayedIndex);
|
||||
HookCreator.AddHooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private void OnEditPatchClicked()
|
||||
{
|
||||
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
|
||||
HookList.EditPatchClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
@ -48,18 +50,18 @@ namespace UnityExplorer.Hooks
|
||||
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
|
||||
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "On", new Color(0.15f, 0.2f, 0.15f));
|
||||
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 35);
|
||||
ToggleActiveButton.OnClick += OnToggleActiveClicked;
|
||||
|
||||
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
DeleteButton.OnClick += OnDeleteClicked;
|
||||
|
||||
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 35);
|
||||
EditPatchButton.OnClick += OnEditPatchClicked;
|
||||
|
||||
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "X", new Color(0.2f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 35);
|
||||
DeleteButton.OnClick += OnDeleteClicked;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
|
342
src/Hooks/HookCreator.cs
Normal file
342
src/Hooks/HookCreator.cs
Normal file
@ -0,0 +1,342 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookCreator : ICellPoolDataSource<AddHookCell>
|
||||
{
|
||||
public int ItemCount => filteredEligibleMethods.Count;
|
||||
|
||||
static readonly List<MethodInfo> currentAddEligibleMethods = new();
|
||||
static readonly List<MethodInfo> filteredEligibleMethods = new();
|
||||
static readonly List<string> currentEligibleNamesForFiltering = new();
|
||||
|
||||
// hook editor
|
||||
static readonly LexerBuilder Lexer = new();
|
||||
internal static HookInstance CurrentEditedHook;
|
||||
|
||||
// Add Hooks UI
|
||||
internal static GameObject AddHooksRoot;
|
||||
internal static ScrollPool<AddHookCell> AddHooksScrollPool;
|
||||
internal static Text AddHooksLabel;
|
||||
internal static InputFieldRef AddHooksMethodFilterInput;
|
||||
internal static InputFieldRef ClassSelectorInputField;
|
||||
internal static Type pendingGenericDefinition;
|
||||
internal static MethodInfo pendingGenericMethod;
|
||||
|
||||
public static bool PendingGeneric => pendingGenericDefinition != null || pendingGenericMethod != null;
|
||||
|
||||
// Hook Source Editor UI
|
||||
public static GameObject EditorRoot { get; private set; }
|
||||
public static Text EditingHookLabel { get; private set; }
|
||||
public static InputFieldScroller EditorInputScroller { get; private set; }
|
||||
public static InputFieldRef EditorInput => EditorInputScroller.InputField;
|
||||
public static Text EditorInputText { get; private set; }
|
||||
public static Text EditorHighlightText { get; private set; }
|
||||
|
||||
// ~~~~~~ New hook method selector ~~~~~~~
|
||||
|
||||
public void OnClassSelectedForHooks(string typeFullName)
|
||||
{
|
||||
Type type = ReflectionUtility.GetTypeByName(typeFullName);
|
||||
if (type == null)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
|
||||
return;
|
||||
}
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
pendingGenericDefinition = type;
|
||||
HookManagerPanel.genericArgsHandler.Show(OnGenericClassChosen, OnGenericClassCancel, type);
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowMethodsForType(type);
|
||||
}
|
||||
|
||||
void ShowMethodsForType(Type type)
|
||||
{
|
||||
SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
|
||||
|
||||
AddHooksMethodFilterInput.Text = string.Empty;
|
||||
|
||||
filteredEligibleMethods.Clear();
|
||||
currentAddEligibleMethods.Clear();
|
||||
currentEligibleNamesForFiltering.Clear();
|
||||
foreach (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if (UERuntimeHelper.IsBlacklisted(method))
|
||||
continue;
|
||||
currentAddEligibleMethods.Add(method);
|
||||
currentEligibleNamesForFiltering.Add(SignatureHighlighter.RemoveHighlighting(SignatureHighlighter.ParseMethod(method)));
|
||||
filteredEligibleMethods.Add(method);
|
||||
}
|
||||
|
||||
AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
void OnGenericClassChosen(Type[] genericArgs)
|
||||
{
|
||||
Type generic = pendingGenericDefinition.MakeGenericType(genericArgs);
|
||||
ShowMethodsForType(generic);
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
}
|
||||
|
||||
void OnGenericClassCancel()
|
||||
{
|
||||
pendingGenericDefinition = null;
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
}
|
||||
|
||||
public void SetAddHooksLabelType(string typeText)
|
||||
{
|
||||
AddHooksLabel.text = $"Adding hooks to: {typeText}";
|
||||
|
||||
AddHooksMethodFilterInput.GameObject.SetActive(true);
|
||||
AddHooksScrollPool.UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public static void AddHookClicked(int index)
|
||||
{
|
||||
if (index >= filteredEligibleMethods.Count)
|
||||
return;
|
||||
|
||||
MethodInfo method = filteredEligibleMethods[index];
|
||||
if (!method.IsGenericMethod && HookList.hookedSignatures.Contains(method.FullDescription()))
|
||||
{
|
||||
ExplorerCore.Log($"Non-generic methods can only be hooked once.");
|
||||
return;
|
||||
}
|
||||
else if (method.IsGenericMethod)
|
||||
{
|
||||
pendingGenericMethod = method;
|
||||
HookManagerPanel.genericArgsHandler.Show(OnGenericMethodChosen, OnGenericMethodCancel, method);
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
|
||||
return;
|
||||
}
|
||||
|
||||
AddHook(filteredEligibleMethods[index]);
|
||||
}
|
||||
|
||||
static void OnGenericMethodChosen(Type[] arguments)
|
||||
{
|
||||
MethodInfo generic = pendingGenericMethod.MakeGenericMethod(arguments);
|
||||
AddHook(generic);
|
||||
}
|
||||
|
||||
static void OnGenericMethodCancel()
|
||||
{
|
||||
pendingGenericMethod = null;
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
}
|
||||
|
||||
public static void AddHook(MethodInfo method)
|
||||
{
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
|
||||
string sig = method.FullDescription();
|
||||
if (HookList.hookedSignatures.Contains(sig))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Method is already hooked!");
|
||||
return;
|
||||
}
|
||||
|
||||
HookInstance hook = new(method);
|
||||
if (hook.Enabled)
|
||||
{
|
||||
HookList.hookedSignatures.Add(sig);
|
||||
HookList.currentHooks.Add(sig, hook);
|
||||
}
|
||||
|
||||
AddHooksScrollPool.Refresh(true, false);
|
||||
HookList.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void OnAddHookFilterInputChanged(string input)
|
||||
{
|
||||
filteredEligibleMethods.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
filteredEligibleMethods.AddRange(currentAddEligibleMethods);
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < currentAddEligibleMethods.Count; i++)
|
||||
{
|
||||
MethodInfo eligible = currentAddEligibleMethods[i];
|
||||
string sig = currentEligibleNamesForFiltering[i];
|
||||
if (sig.ContainsIgnoreCase(input))
|
||||
filteredEligibleMethods.Add(eligible);
|
||||
}
|
||||
}
|
||||
|
||||
AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
// Set eligible method cell
|
||||
|
||||
public void OnCellBorrowed(AddHookCell cell) { }
|
||||
|
||||
public void SetCell(AddHookCell cell, int index)
|
||||
{
|
||||
if (index >= filteredEligibleMethods.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
MethodInfo method = filteredEligibleMethods[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(method);
|
||||
}
|
||||
|
||||
// ~~~~~~~~ Hook source editor ~~~~~~~~
|
||||
|
||||
internal static void SetEditedHook(HookInstance hook)
|
||||
{
|
||||
CurrentEditedHook = hook;
|
||||
EditingHookLabel.text = $"Editing: {SignatureHighlighter.Parse(hook.TargetMethod.DeclaringType, false, hook.TargetMethod)}";
|
||||
EditorInput.Text = hook.PatchSourceCode;
|
||||
}
|
||||
|
||||
internal static void OnEditorInputChanged(string value)
|
||||
{
|
||||
EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0, EditorInput.Component.caretPosition, out _);
|
||||
}
|
||||
|
||||
internal static void EditorInputCancel()
|
||||
{
|
||||
CurrentEditedHook = null;
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
}
|
||||
|
||||
internal static void EditorInputSave()
|
||||
{
|
||||
string input = EditorInput.Text;
|
||||
bool wasEnabled = CurrentEditedHook.Enabled;
|
||||
if (CurrentEditedHook.CompileAndGenerateProcessor(input))
|
||||
{
|
||||
if (wasEnabled)
|
||||
CurrentEditedHook.Patch();
|
||||
|
||||
CurrentEditedHook.PatchSourceCode = input;
|
||||
CurrentEditedHook = null;
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
}
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
internal void ConstructAddHooksView(GameObject rightGroup)
|
||||
{
|
||||
AddHooksRoot = UIFactory.CreateUIObject("AddHooksPanel", rightGroup);
|
||||
UIFactory.SetLayoutElement(AddHooksRoot, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(AddHooksRoot, false, false, true, true);
|
||||
|
||||
GameObject addRow = UIFactory.CreateHorizontalGroup(AddHooksRoot, "AddRow", false, true, true, true, 4,
|
||||
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(addRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
ClassSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
|
||||
UIFactory.SetLayoutElement(ClassSelectorInputField.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
TypeCompleter completer = new(typeof(object), ClassSelectorInputField, true, false, true);
|
||||
//completer.AllTypes = true;
|
||||
|
||||
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "View Methods");
|
||||
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 110, minHeight: 25);
|
||||
addButton.OnClick += () => { OnClassSelectedForHooks(ClassSelectorInputField.Text); };
|
||||
|
||||
AddHooksLabel = UIFactory.CreateLabel(AddHooksRoot, "AddLabel", "Choose a class to begin...", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(AddHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
|
||||
|
||||
AddHooksMethodFilterInput = UIFactory.CreateInputField(AddHooksRoot, "FilterInputField", "Filter method names...");
|
||||
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
AddHooksMethodFilterInput.OnValueChanged += OnAddHookFilterInputChanged;
|
||||
|
||||
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(AddHooksRoot, "MethodAddScrollPool",
|
||||
out GameObject addScrollRoot, out GameObject addContent);
|
||||
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
|
||||
AddHooksScrollPool.Initialize(this);
|
||||
|
||||
AddHooksMethodFilterInput.GameObject.SetActive(false);
|
||||
AddHooksScrollPool.UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void ConstructEditor(GameObject parent)
|
||||
{
|
||||
EditorRoot = UIFactory.CreateUIObject("HookSourceEditor", parent);
|
||||
UIFactory.SetLayoutElement(EditorRoot, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(EditorRoot, true, true, true, true, 2, 3, 3, 3, 3);
|
||||
|
||||
EditingHookLabel = UIFactory.CreateLabel(EditorRoot, "EditingHookLabel", "NOT SET", TextAnchor.MiddleCenter);
|
||||
EditingHookLabel.fontStyle = FontStyle.Bold;
|
||||
UIFactory.SetLayoutElement(EditingHookLabel.gameObject, flexibleWidth: 9999, minHeight: 25);
|
||||
|
||||
Text editorLabel = UIFactory.CreateLabel(EditorRoot,
|
||||
"EditorLabel",
|
||||
"* Accepted method names are <b>Prefix</b>, <b>Postfix</b>, <b>Finalizer</b> and <b>Transpiler</b> (can define multiple).\n" +
|
||||
"* Your patch methods must be static.\n" +
|
||||
"* Hooks are temporary! Copy the source into your IDE to avoid losing work if you wish to keep it!",
|
||||
TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(EditorRoot, "ButtonRow", false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ButtonRef editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorSaveButton.OnClick += EditorInputSave;
|
||||
|
||||
ButtonRef editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorDoneButton.OnClick += EditorInputCancel;
|
||||
|
||||
int fontSize = 16;
|
||||
GameObject inputObj = UIFactory.CreateScrollInputField(EditorRoot, "EditorInput", "", out InputFieldScroller inputScroller, fontSize);
|
||||
EditorInputScroller = inputScroller;
|
||||
EditorInput.OnValueChanged += OnEditorInputChanged;
|
||||
|
||||
EditorInputText = EditorInput.Component.textComponent;
|
||||
EditorInputText.supportRichText = false;
|
||||
EditorInputText.color = Color.clear;
|
||||
EditorInput.Component.customCaretColor = true;
|
||||
EditorInput.Component.caretColor = Color.white;
|
||||
EditorInput.PlaceholderText.fontSize = fontSize;
|
||||
|
||||
// Lexer highlight text overlay
|
||||
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
|
||||
RectTransform highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||
highlightTextRect.pivot = new Vector2(0, 1);
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
EditorHighlightText = highlightTextObj.AddComponent<Text>();
|
||||
EditorHighlightText.color = Color.white;
|
||||
EditorHighlightText.supportRichText = true;
|
||||
EditorHighlightText.fontSize = fontSize;
|
||||
|
||||
// Set fonts
|
||||
EditorInputText.font = UniversalUI.ConsoleFont;
|
||||
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
|
||||
EditorHighlightText.font = UniversalUI.ConsoleFont;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using Mono.CSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -14,12 +15,14 @@ namespace UnityExplorer.Hooks
|
||||
{
|
||||
// Static
|
||||
|
||||
private static readonly StringBuilder evalOutput = new();
|
||||
private static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evalOutput));
|
||||
//static readonly StringBuilder evalOutput = new();
|
||||
static readonly StringBuilder evaluatorOutput;
|
||||
static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder()));
|
||||
|
||||
static HookInstance()
|
||||
{
|
||||
scriptEvaluator.Run("using System;");
|
||||
scriptEvaluator.Run("using System.Text;");
|
||||
scriptEvaluator.Run("using System.Reflection;");
|
||||
scriptEvaluator.Run("using System.Collections;");
|
||||
scriptEvaluator.Run("using System.Collections.Generic;");
|
||||
@ -42,7 +45,7 @@ namespace UnityExplorer.Hooks
|
||||
public HookInstance(MethodInfo targetMethod)
|
||||
{
|
||||
this.TargetMethod = targetMethod;
|
||||
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
|
||||
this.shortSignature = TargetMethod.FullDescription();
|
||||
|
||||
GenerateDefaultPatchSourceCode(targetMethod);
|
||||
|
||||
@ -59,15 +62,15 @@ namespace UnityExplorer.Hooks
|
||||
{
|
||||
Unpatch();
|
||||
|
||||
StringBuilder codeBuilder = new();
|
||||
|
||||
try
|
||||
{
|
||||
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
|
||||
|
||||
// Dynamically compile the patch method
|
||||
|
||||
StringBuilder codeBuilder = new();
|
||||
|
||||
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
|
||||
codeBuilder.AppendLine($"static class DynamicPatch_{DateTime.Now.Ticks}");
|
||||
codeBuilder.AppendLine("{");
|
||||
codeBuilder.AppendLine(patchSource);
|
||||
codeBuilder.AppendLine("}");
|
||||
@ -107,30 +110,59 @@ namespace UnityExplorer.Hooks
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
|
||||
if (ex is FormatException)
|
||||
{
|
||||
string output = scriptEvaluator._textWriter.ToString();
|
||||
string[] outputSplit = output.Split('\n');
|
||||
if (outputSplit.Length >= 2)
|
||||
output = outputSplit[outputSplit.Length - 2];
|
||||
evaluatorOutput.Clear();
|
||||
|
||||
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
|
||||
ExplorerCore.LogWarning($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
|
||||
else
|
||||
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
|
||||
|
||||
// ExplorerCore.Log(codeBuilder.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static string FullDescriptionClean(Type type)
|
||||
{
|
||||
string description = type.FullDescription().Replace("+", ".");
|
||||
if (description.EndsWith("&"))
|
||||
description = $"ref {description.Substring(0, description.Length - 1)}";
|
||||
return description;
|
||||
}
|
||||
|
||||
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
|
||||
{
|
||||
StringBuilder codeBuilder = new();
|
||||
// Arguments
|
||||
|
||||
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
|
||||
codeBuilder.Append("static void Postfix("); // System.Reflection.MethodBase __originalMethod
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
|
||||
bool isStatic = targetMethod.IsStatic;
|
||||
if (!isStatic)
|
||||
codeBuilder.Append($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance");
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
|
||||
{
|
||||
if (!isStatic)
|
||||
codeBuilder.Append(", ");
|
||||
codeBuilder.Append($"{FullDescriptionClean(targetMethod.ReturnType)} __result");
|
||||
}
|
||||
|
||||
ParameterInfo[] parameters = targetMethod.GetParameters();
|
||||
|
||||
int paramIdx = 0;
|
||||
foreach (ParameterInfo param in parameters)
|
||||
{
|
||||
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
|
||||
codeBuilder.Append($", {FullDescriptionClean(param.ParameterType)} __{paramIdx}");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
@ -139,42 +171,39 @@ namespace UnityExplorer.Hooks
|
||||
// Patch body
|
||||
|
||||
codeBuilder.AppendLine("{");
|
||||
|
||||
codeBuilder.AppendLine(" try {");
|
||||
|
||||
// Log message
|
||||
|
||||
StringBuilder logMessage = new();
|
||||
logMessage.Append($"Patch called: {shortSignature}\\n");
|
||||
codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();");
|
||||
codeBuilder.AppendLine($" sb.AppendLine(\"---- Patched called ----\");");
|
||||
codeBuilder.AppendLine($" sb.AppendLine(\"{shortSignature}\");");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
logMessage.Append("__instance: {__instance.ToString()}\\n");
|
||||
codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());");
|
||||
|
||||
paramIdx = 0;
|
||||
foreach (ParameterInfo param in parameters)
|
||||
{
|
||||
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
|
||||
codeBuilder.Append($" sb.Append(\"- Parameter {paramIdx} '{param.Name}': \")");
|
||||
|
||||
Type pType = param.ParameterType;
|
||||
if (pType.IsByRef) pType = pType.GetElementType();
|
||||
if (pType.IsValueType)
|
||||
logMessage.Append($"{{__{paramIdx}.ToString()}}");
|
||||
codeBuilder.AppendLine($".AppendLine(__{paramIdx}.ToString());");
|
||||
else
|
||||
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
|
||||
logMessage.Append("\\n");
|
||||
codeBuilder.AppendLine($".AppendLine(__{paramIdx}?.ToString() ?? \"null\");");
|
||||
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
{
|
||||
logMessage.Append("Return value: ");
|
||||
codeBuilder.Append(" sb.Append(\"- Return value: \")");
|
||||
if (targetMethod.ReturnType.IsValueType)
|
||||
logMessage.Append("{__result.ToString()}");
|
||||
codeBuilder.AppendLine(".AppendLine(__result.ToString());");
|
||||
else
|
||||
logMessage.Append("{__result?.ToString() ?? \"null\"}");
|
||||
logMessage.Append("\\n");
|
||||
codeBuilder.AppendLine(".AppendLine(__result?.ToString() ?? \"null\");");
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log(sb.ToString());");
|
||||
codeBuilder.AppendLine(" }");
|
||||
codeBuilder.AppendLine(" catch (System.Exception ex) {");
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
|
||||
|
97
src/Hooks/HookList.cs
Normal file
97
src/Hooks/HookList.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookList : ICellPoolDataSource<HookCell>
|
||||
{
|
||||
public int ItemCount => currentHooks.Count;
|
||||
|
||||
internal static readonly HashSet<string> hookedSignatures = new();
|
||||
internal static readonly OrderedDictionary currentHooks = new();
|
||||
|
||||
internal static GameObject UIRoot;
|
||||
internal static ScrollPool<HookCell> HooksScrollPool;
|
||||
|
||||
public static void EnableOrDisableHookClicked(int index)
|
||||
{
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
hook.TogglePatch();
|
||||
|
||||
HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public static void DeleteHookClicked(int index)
|
||||
{
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
|
||||
if (HookCreator.CurrentEditedHook == hook)
|
||||
HookCreator.EditorInputCancel();
|
||||
|
||||
hook.Unpatch();
|
||||
currentHooks.RemoveAt(index);
|
||||
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
|
||||
|
||||
HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public static void EditPatchClicked(int index)
|
||||
{
|
||||
if (HookCreator.PendingGeneric)
|
||||
HookManagerPanel.genericArgsHandler.Cancel();
|
||||
|
||||
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor);
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
HookCreator.SetEditedHook(hook);
|
||||
}
|
||||
|
||||
// Set current hook cell
|
||||
|
||||
public void OnCellBorrowed(HookCell cell) { }
|
||||
|
||||
public void SetCell(HookCell cell, int index)
|
||||
{
|
||||
if (index >= currentHooks.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(hook.TargetMethod);
|
||||
|
||||
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "On" : "Off";
|
||||
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
|
||||
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
internal void ConstructUI(GameObject leftGroup)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("CurrentHooksPanel", leftGroup);
|
||||
UIFactory.SetLayoutElement(UIRoot, preferredHeight: 150, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, true, true, true, true);
|
||||
|
||||
Text hooksLabel = UIFactory.CreateLabel(UIRoot, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
|
||||
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(UIRoot, "HooksScrollPool",
|
||||
out GameObject hooksScroll, out GameObject hooksContent);
|
||||
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
|
||||
HooksScrollPool.Initialize(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
|
||||
{
|
||||
private static HookManager s_instance;
|
||||
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
|
||||
|
||||
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
|
||||
|
||||
// This class acts as the data source for both current hooks and eligable methods when adding hooks.
|
||||
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
|
||||
// correct pool cells.
|
||||
private bool isAddingMethods;
|
||||
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
|
||||
|
||||
// current hooks
|
||||
private readonly HashSet<string> hookedSignatures = new();
|
||||
private readonly OrderedDictionary currentHooks = new();
|
||||
|
||||
// adding hooks
|
||||
private readonly List<MethodInfo> currentAddEligableMethods = new();
|
||||
private readonly List<MethodInfo> filteredEligableMethods = new();
|
||||
|
||||
// hook editor
|
||||
private readonly LexerBuilder Lexer = new();
|
||||
private HookInstance currentEditedHook;
|
||||
|
||||
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void EnableOrDisableHookClicked(int index)
|
||||
{
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
hook.TogglePatch();
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void DeleteHookClicked(int index)
|
||||
{
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
hook.Unpatch();
|
||||
currentHooks.RemoveAt(index);
|
||||
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void EditPatchClicked(int index)
|
||||
{
|
||||
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
|
||||
HookInstance hook = (HookInstance)currentHooks[index];
|
||||
currentEditedHook = hook;
|
||||
Panel.EditorInput.Text = hook.PatchSourceCode;
|
||||
}
|
||||
|
||||
// Set current hook cell
|
||||
|
||||
public void OnCellBorrowed(HookCell cell) { }
|
||||
|
||||
public void SetCell(HookCell cell, int index)
|
||||
{
|
||||
if (index >= this.currentHooks.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
HookInstance hook = (HookInstance)this.currentHooks[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(hook.TargetMethod);
|
||||
|
||||
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
|
||||
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
|
||||
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void OnClassSelectedForHooks(string typeFullName)
|
||||
{
|
||||
Type type = ReflectionUtility.GetTypeByName(typeFullName);
|
||||
if (type == null)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
|
||||
|
||||
Panel.ResetMethodFilter();
|
||||
filteredEligableMethods.Clear();
|
||||
currentAddEligableMethods.Clear();
|
||||
foreach (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if (method.IsGenericMethod || UERuntimeHelper.IsBlacklisted(method))
|
||||
continue;
|
||||
currentAddEligableMethods.Add(method);
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
|
||||
isAddingMethods = true;
|
||||
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
public void DoneAddingHooks()
|
||||
{
|
||||
isAddingMethods = false;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHookClicked(int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
return;
|
||||
|
||||
AddHook(filteredEligableMethods[index]);
|
||||
Panel.AddHooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHook(MethodInfo method)
|
||||
{
|
||||
string sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
return;
|
||||
|
||||
HookInstance hook = new(method);
|
||||
if (hook.Enabled)
|
||||
{
|
||||
hookedSignatures.Add(sig);
|
||||
currentHooks.Add(sig, hook);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAddHookFilterInputChanged(string input)
|
||||
{
|
||||
filteredEligableMethods.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
filteredEligableMethods.AddRange(currentAddEligableMethods);
|
||||
else
|
||||
{
|
||||
foreach (MethodInfo method in currentAddEligableMethods)
|
||||
{
|
||||
if (method.Name.ContainsIgnoreCase(input))
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
}
|
||||
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
// Set eligable method cell
|
||||
|
||||
public void OnCellBorrowed(AddHookCell cell) { }
|
||||
|
||||
public void SetCell(AddHookCell cell, int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
MethodInfo method = this.filteredEligableMethods[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(method);
|
||||
|
||||
string sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(false);
|
||||
cell.HookedLabel.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(true);
|
||||
cell.HookedLabel.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
|
||||
|
||||
public void OnEditorInputChanged(string value)
|
||||
{
|
||||
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
|
||||
Panel.EditorInput.Component.caretPosition, out _);
|
||||
}
|
||||
|
||||
public void EditorInputCancel()
|
||||
{
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
|
||||
public void EditorInputSave()
|
||||
{
|
||||
string input = Panel.EditorInput.Text;
|
||||
bool wasEnabled = currentEditedHook.Enabled;
|
||||
if (currentEditedHook.CompileAndGenerateProcessor(input))
|
||||
{
|
||||
if (wasEnabled)
|
||||
currentEditedHook.Patch();
|
||||
currentEditedHook.PatchSourceCode = input;
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -74,7 +74,7 @@ namespace UnityExplorer.Inspectors
|
||||
InspectorManager.ReleaseInspector(this);
|
||||
}
|
||||
|
||||
public void ChangeTarget(GameObject newTarget)
|
||||
public void OnTransformCellClicked(GameObject newTarget)
|
||||
{
|
||||
this.Target = newTarget;
|
||||
GOControls.UpdateGameObjectInfo(true, true);
|
||||
@ -193,7 +193,8 @@ namespace UnityExplorer.Inspectors
|
||||
compInstanceIDs.Clear();
|
||||
foreach (Component comp in comps)
|
||||
{
|
||||
if (!comp) continue;
|
||||
if (!comp)
|
||||
continue;
|
||||
componentEntries.Add(comp);
|
||||
compInstanceIDs.Add(comp.GetInstanceID());
|
||||
}
|
||||
@ -202,8 +203,23 @@ namespace UnityExplorer.Inspectors
|
||||
behaviourEnabledStates.Clear();
|
||||
foreach (Behaviour behaviour in behaviours)
|
||||
{
|
||||
if (!behaviour) continue;
|
||||
behaviourEntries.Add(behaviour);
|
||||
if (!behaviour)
|
||||
continue;
|
||||
|
||||
// Don't ask me how, but in some games this can be true for certain components.
|
||||
// They get picked up from GetComponents<Behaviour>, but they are not actually Behaviour...?
|
||||
if (!typeof(Behaviour).IsAssignableFrom(behaviour.GetType()))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
behaviourEntries.Add(behaviour);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning(ex);
|
||||
}
|
||||
|
||||
behaviourEnabledStates.Add(behaviour.enabled);
|
||||
}
|
||||
|
||||
@ -293,12 +309,8 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
transformScroll = UIFactory.CreateScrollPool<TransformCell>(leftGroup, "TransformTree", out GameObject transformObj,
|
||||
out GameObject transformContent, new Color(0.11f, 0.11f, 0.11f));
|
||||
UIFactory.SetLayoutElement(transformObj, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutElement(transformContent, flexibleHeight: 9999);
|
||||
|
||||
TransformTree = new TransformTree(transformScroll, GetTransformEntries);
|
||||
TransformTree.Init();
|
||||
TransformTree.OnClickOverrideHandler = ChangeTarget;
|
||||
TransformTree = new TransformTree(transformScroll, GetTransformEntries, OnTransformCellClicked);
|
||||
|
||||
// Right group (Components)
|
||||
|
||||
@ -321,7 +333,7 @@ namespace UnityExplorer.Inspectors
|
||||
addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); };
|
||||
|
||||
// comp autocompleter
|
||||
new TypeCompleter(typeof(Component), addCompInput);
|
||||
new TypeCompleter(typeof(Component), addCompInput, false, false, false);
|
||||
|
||||
// Component List
|
||||
|
||||
|
@ -148,7 +148,7 @@ namespace UnityExplorer.Inspectors
|
||||
{
|
||||
if (this.GOTarget && this.GOTarget.transform.parent)
|
||||
{
|
||||
Parent.ChangeTarget(this.GOTarget.transform.parent.gameObject);
|
||||
Parent.OnTransformCellClicked(this.GOTarget.transform.parent.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
@ -9,6 +11,7 @@ namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public bool IsActive { get; internal set; }
|
||||
public object Target { get; set; }
|
||||
public Type TargetType { get; protected set; }
|
||||
|
||||
public InspectorTab Tab { get; internal set; }
|
||||
|
||||
@ -24,6 +27,8 @@ namespace UnityExplorer.Inspectors
|
||||
public virtual void OnBorrowedFromPool(object target)
|
||||
{
|
||||
this.Target = target;
|
||||
this.TargetType = target is Type type ? type : target.GetActualType();
|
||||
|
||||
Tab = Pool<InspectorTab>.Borrow();
|
||||
Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false);
|
||||
|
||||
|
@ -23,7 +23,7 @@ namespace UnityExplorer
|
||||
|
||||
public static event Action OnInspectedTabsChanged;
|
||||
|
||||
public static void Inspect(object obj, CacheObjectBase sourceCache = null)
|
||||
public static void Inspect(object obj, CacheObjectBase parent = null)
|
||||
{
|
||||
if (obj.IsNullOrDestroyed())
|
||||
return;
|
||||
@ -36,19 +36,34 @@ namespace UnityExplorer
|
||||
if (obj is GameObject)
|
||||
CreateInspector<GameObjectInspector>(obj);
|
||||
else
|
||||
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
|
||||
CreateInspector<ReflectionInspector>(obj, false, parent);
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
if (TryFocusActiveInspector(type))
|
||||
return;
|
||||
|
||||
CreateInspector<ReflectionInspector>(type, true);
|
||||
}
|
||||
|
||||
private static bool TryFocusActiveInspector(object target)
|
||||
static bool TryFocusActiveInspector(object target)
|
||||
{
|
||||
foreach (InspectorBase inspector in Inspectors)
|
||||
{
|
||||
if (inspector.Target.ReferenceEqual(target))
|
||||
bool shouldFocus = false;
|
||||
|
||||
if (target is Type targetAsType)
|
||||
{
|
||||
if (inspector.TargetType.FullName == targetAsType.FullName)
|
||||
shouldFocus = true;
|
||||
}
|
||||
else if(inspector.Target.ReferenceEqual(target))
|
||||
{
|
||||
shouldFocus = true;
|
||||
}
|
||||
|
||||
if (shouldFocus)
|
||||
{
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
|
||||
SetInspectorActive(inspector);
|
||||
@ -76,7 +91,7 @@ namespace UnityExplorer
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CloseAllTabs()
|
||||
public static void CloseAllTabs()
|
||||
{
|
||||
if (Inspectors.Any())
|
||||
{
|
||||
@ -89,18 +104,17 @@ namespace UnityExplorer
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
|
||||
}
|
||||
|
||||
private static void CreateInspector<T>(object target, bool staticReflection = false,
|
||||
CacheObjectBase parentObject = null) where T : InspectorBase
|
||||
static void CreateInspector<T>(object target, bool staticReflection = false, CacheObjectBase parent = null) where T : InspectorBase
|
||||
{
|
||||
T inspector = Pool<T>.Borrow();
|
||||
Inspectors.Add(inspector);
|
||||
inspector.Target = target;
|
||||
|
||||
if (parentObject != null && parentObject.CanWrite)
|
||||
if (parent != null && parent.CanWrite)
|
||||
{
|
||||
// only set parent cache object if we are inspecting a struct, otherwise there is no point.
|
||||
if (target.GetType().IsValueType && inspector is ReflectionInspector ri)
|
||||
ri.ParentCacheObject = parentObject;
|
||||
ri.ParentCacheObject = parent;
|
||||
}
|
||||
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
|
||||
@ -115,7 +129,7 @@ namespace UnityExplorer
|
||||
OnInspectedTabsChanged?.Invoke();
|
||||
}
|
||||
|
||||
internal static void ReleaseInspector<T>(T inspector) where T : InspectorBase
|
||||
public static void ReleaseInspector<T>(T inspector) where T : InspectorBase
|
||||
{
|
||||
if (lastActiveInspector == inspector)
|
||||
lastActiveInspector = null;
|
||||
|
@ -17,7 +17,7 @@ namespace UnityExplorer.Inspectors
|
||||
UI
|
||||
}
|
||||
|
||||
public class MouseInspector : UEPanel
|
||||
public class MouseInspector : PanelBase
|
||||
{
|
||||
public static MouseInspector Instance { get; private set; }
|
||||
|
||||
@ -38,19 +38,15 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
// UIPanel
|
||||
internal static readonly string UIBaseGUID = $"{ExplorerCore.GUID}.MouseInspector";
|
||||
private UIBase inspectorUIBase;
|
||||
internal static UIBase inspectorUIBase;
|
||||
|
||||
public override string Name => "Inspect Under Mouse";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.MouseInspector;
|
||||
public override int MinWidth => -1;
|
||||
public override int MinHeight => -1;
|
||||
public override Vector2 DefaultAnchorMin => Vector2.zero;
|
||||
public override Vector2 DefaultAnchorMax => Vector2.zero;
|
||||
|
||||
public override bool CanDragAndResize => false;
|
||||
public override bool NavButtonWanted => false;
|
||||
public override bool ShouldSaveActiveState => false;
|
||||
public override bool ShowByDefault => false;
|
||||
|
||||
internal Text objNameLabel;
|
||||
internal Text objPathLabel;
|
||||
@ -225,11 +221,10 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
|
||||
// Create a new canvas for this panel to live on.
|
||||
// It needs to always be shown on the main display, other panels can move displays.
|
||||
|
||||
inspectorUIBase = UniversalUI.RegisterUI(UIBaseGUID, null);
|
||||
UIRoot.transform.SetParent(inspectorUIBase.RootObject.transform);
|
||||
//// Create a new canvas for this panel to live on.
|
||||
//// It needs to always be shown on the main display, other panels can move displays.
|
||||
//
|
||||
//UIRoot.transform.SetParent(inspectorUIBase.RootObject.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,54 +33,52 @@ namespace UnityExplorer.Inspectors
|
||||
public class ReflectionInspector : InspectorBase, ICellPoolDataSource<CacheMemberCell>, ICacheObjectController
|
||||
{
|
||||
public CacheObjectBase ParentCacheObject { get; set; }
|
||||
public Type TargetType { get; private set; }
|
||||
//public Type TargetType { get; private set; }
|
||||
public bool StaticOnly { get; internal set; }
|
||||
public bool CanWrite => true;
|
||||
|
||||
public bool AutoUpdateWanted => autoUpdateToggle.isOn;
|
||||
|
||||
private List<CacheMember> members = new();
|
||||
private readonly List<CacheMember> filteredMembers = new();
|
||||
List<CacheMember> members = new();
|
||||
readonly List<CacheMember> filteredMembers = new();
|
||||
|
||||
private BindingFlags scopeFlagsFilter;
|
||||
private string nameFilter;
|
||||
|
||||
private MemberFilter MemberFilter = MemberFilter.All;
|
||||
string nameFilter;
|
||||
BindingFlags scopeFlagsFilter;
|
||||
MemberFilter memberFilter = MemberFilter.All;
|
||||
|
||||
// Updating
|
||||
|
||||
private bool refreshWanted;
|
||||
private string lastNameFilter;
|
||||
private BindingFlags lastFlagsFilter;
|
||||
private MemberFilter lastMemberFilter = MemberFilter.All;
|
||||
private float timeOfLastAutoUpdate;
|
||||
bool refreshWanted;
|
||||
string lastNameFilter;
|
||||
BindingFlags lastFlagsFilter;
|
||||
MemberFilter lastMemberFilter = MemberFilter.All;
|
||||
float timeOfLastAutoUpdate;
|
||||
|
||||
// UI
|
||||
|
||||
internal GameObject mainContentHolder;
|
||||
private static int LeftGroupWidth { get; set; }
|
||||
private static int RightGroupWidth { get; set; }
|
||||
static int LeftGroupWidth { get; set; }
|
||||
static int RightGroupWidth { get; set; }
|
||||
|
||||
static readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
|
||||
static readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
|
||||
|
||||
public GameObject ContentRoot { get; private set; }
|
||||
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
|
||||
public int ItemCount => filteredMembers.Count;
|
||||
public UnityObjectWidget UnityWidget { get; private set; }
|
||||
public string TabButtonText { get; set; }
|
||||
|
||||
public UnityObjectWidget UnityWidget;
|
||||
InputFieldRef hiddenNameText;
|
||||
Text nameText;
|
||||
Text assemblyText;
|
||||
Toggle autoUpdateToggle;
|
||||
|
||||
public InputFieldRef HiddenNameText;
|
||||
public Text NameText;
|
||||
public Text AssemblyText;
|
||||
private Toggle autoUpdateToggle;
|
||||
ButtonRef makeGenericButton;
|
||||
GenericConstructorWidget genericConstructor;
|
||||
|
||||
internal string currentBaseTabText;
|
||||
|
||||
private readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
|
||||
private readonly List<Toggle> memberTypeToggles = new();
|
||||
private InputFieldRef filterInputField;
|
||||
|
||||
// const
|
||||
|
||||
private readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
|
||||
private readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
|
||||
InputFieldRef filterInputField;
|
||||
readonly List<Toggle> memberTypeToggles = new();
|
||||
readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
|
||||
|
||||
// Setup
|
||||
|
||||
@ -125,6 +123,8 @@ namespace UnityExplorer.Inspectors
|
||||
this.UnityWidget = null;
|
||||
}
|
||||
|
||||
genericConstructor?.Cancel();
|
||||
|
||||
base.OnReturnToPool();
|
||||
}
|
||||
|
||||
@ -138,6 +138,8 @@ namespace UnityExplorer.Inspectors
|
||||
Target = null;
|
||||
TargetType = target as Type;
|
||||
prefix = "[S]";
|
||||
|
||||
makeGenericButton.GameObject.SetActive(TargetType.IsGenericTypeDefinition);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -146,17 +148,17 @@ namespace UnityExplorer.Inspectors
|
||||
}
|
||||
|
||||
// Setup main labels and tab text
|
||||
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
|
||||
Tab.TabText.text = currentBaseTabText;
|
||||
NameText.text = SignatureHighlighter.Parse(TargetType, true);
|
||||
HiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(NameText.text);
|
||||
TabButtonText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
|
||||
Tab.TabText.text = TabButtonText;
|
||||
nameText.text = SignatureHighlighter.Parse(TargetType, true);
|
||||
hiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(nameText.text);
|
||||
|
||||
string asmText;
|
||||
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
|
||||
asmText = $"{TargetType.Assembly.GetName().Name} <color=grey><i>(in memory)</i></color>";
|
||||
else
|
||||
asmText = Path.GetFileName(TargetType.Assembly.Location);
|
||||
AssemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
|
||||
assemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
|
||||
|
||||
// Unity object helper widget
|
||||
|
||||
@ -195,11 +197,11 @@ namespace UnityExplorer.Inspectors
|
||||
}
|
||||
|
||||
// check filter changes or force-refresh
|
||||
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter)
|
||||
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != memberFilter)
|
||||
{
|
||||
lastNameFilter = nameFilter;
|
||||
lastFlagsFilter = scopeFlagsFilter;
|
||||
lastMemberFilter = MemberFilter;
|
||||
lastMemberFilter = memberFilter;
|
||||
|
||||
FilterMembers();
|
||||
MemberScrollPool.Refresh(true, true);
|
||||
@ -219,17 +221,8 @@ namespace UnityExplorer.Inspectors
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateClicked()
|
||||
{
|
||||
UpdateDisplayedMembers();
|
||||
}
|
||||
|
||||
// Filtering
|
||||
|
||||
public void SetFilter(string name) => SetFilter(name, scopeFlagsFilter);
|
||||
|
||||
public void SetFilter(BindingFlags flags) => SetFilter(nameFilter, flags);
|
||||
|
||||
public void SetFilter(string name, BindingFlags flags)
|
||||
{
|
||||
this.nameFilter = name;
|
||||
@ -245,15 +238,7 @@ namespace UnityExplorer.Inspectors
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMemberTypeToggled(MemberFilter flag, bool val)
|
||||
{
|
||||
if (!val)
|
||||
MemberFilter &= ~flag;
|
||||
else
|
||||
MemberFilter |= flag;
|
||||
}
|
||||
|
||||
private void FilterMembers()
|
||||
void FilterMembers()
|
||||
{
|
||||
filteredMembers.Clear();
|
||||
|
||||
@ -268,10 +253,10 @@ namespace UnityExplorer.Inspectors
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFilter.Method))
|
||||
|| (member is CacheField && !MemberFilter.HasFlag(MemberFilter.Field))
|
||||
|| (member is CacheProperty && !MemberFilter.HasFlag(MemberFilter.Property))
|
||||
|| (member is CacheConstructor && !MemberFilter.HasFlag(MemberFilter.Constructor)))
|
||||
if ((member is CacheMethod && !memberFilter.HasFlag(MemberFilter.Method))
|
||||
|| (member is CacheField && !memberFilter.HasFlag(MemberFilter.Field))
|
||||
|| (member is CacheProperty && !memberFilter.HasFlag(MemberFilter.Property))
|
||||
|| (member is CacheConstructor && !memberFilter.HasFlag(MemberFilter.Constructor)))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !member.NameForFiltering.ContainsIgnoreCase(nameFilter))
|
||||
@ -281,7 +266,7 @@ namespace UnityExplorer.Inspectors
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisplayedMembers()
|
||||
void UpdateDisplayedMembers()
|
||||
{
|
||||
bool shouldRefresh = false;
|
||||
foreach (CacheMemberCell cell in MemberScrollPool.CellPool)
|
||||
@ -320,13 +305,13 @@ namespace UnityExplorer.Inspectors
|
||||
SetCellLayout(cell);
|
||||
}
|
||||
|
||||
private void CalculateLayouts()
|
||||
void CalculateLayouts()
|
||||
{
|
||||
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);
|
||||
RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65);
|
||||
}
|
||||
|
||||
private void SetCellLayout(CacheObjectCell cell)
|
||||
void SetCellLayout(CacheObjectCell cell)
|
||||
{
|
||||
cell.NameLayout.minWidth = LeftGroupWidth;
|
||||
cell.RightGroupLayout.minWidth = RightGroupWidth;
|
||||
@ -335,11 +320,66 @@ namespace UnityExplorer.Inspectors
|
||||
cell.Occupant.IValue.SetLayout();
|
||||
}
|
||||
|
||||
private void OnCopyClicked()
|
||||
// UI listeners
|
||||
|
||||
void OnUpdateClicked()
|
||||
{
|
||||
UpdateDisplayedMembers();
|
||||
}
|
||||
|
||||
public void OnSetNameFilter(string name)
|
||||
{
|
||||
SetFilter(name, scopeFlagsFilter);
|
||||
}
|
||||
|
||||
public void OnSetFlags(BindingFlags flags)
|
||||
{
|
||||
SetFilter(nameFilter, flags);
|
||||
}
|
||||
|
||||
void OnMemberTypeToggled(MemberFilter flag, bool val)
|
||||
{
|
||||
if (!val)
|
||||
memberFilter &= ~flag;
|
||||
else
|
||||
memberFilter |= flag;
|
||||
}
|
||||
|
||||
void OnCopyClicked()
|
||||
{
|
||||
ClipboardPanel.Copy(this.Target ?? this.TargetType);
|
||||
}
|
||||
|
||||
void OnMakeGenericClicked()
|
||||
{
|
||||
ContentRoot.SetActive(false);
|
||||
|
||||
if (genericConstructor == null)
|
||||
{
|
||||
genericConstructor = new();
|
||||
genericConstructor.ConstructUI(UIRoot);
|
||||
}
|
||||
|
||||
genericConstructor.UIRoot.SetActive(true);
|
||||
genericConstructor.Show(OnGenericSubmit, OnGenericCancel, TargetType);
|
||||
}
|
||||
|
||||
void OnGenericSubmit(Type[] args)
|
||||
{
|
||||
ContentRoot.SetActive(true);
|
||||
genericConstructor.UIRoot.SetActive(false);
|
||||
|
||||
Type newType = TargetType.MakeGenericType(args);
|
||||
InspectorManager.Inspect(newType);
|
||||
//InspectorManager.ReleaseInspector(this);
|
||||
}
|
||||
|
||||
void OnGenericCancel()
|
||||
{
|
||||
ContentRoot.SetActive(true);
|
||||
genericConstructor.UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
@ -349,51 +389,57 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
// Class name, assembly
|
||||
|
||||
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default, new(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default,
|
||||
new(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(topRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject titleHolder = UIFactory.CreateUIObject("TitleHolder", topRow);
|
||||
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
|
||||
RectTransform namerect = NameText.GetComponent<RectTransform>();
|
||||
nameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
|
||||
RectTransform namerect = nameText.GetComponent<RectTransform>();
|
||||
namerect.anchorMin = new Vector2(0, 0);
|
||||
namerect.anchorMax = new Vector2(1, 1);
|
||||
NameText.fontSize = 17;
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
|
||||
nameText.fontSize = 17;
|
||||
UIFactory.SetLayoutElement(nameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
|
||||
|
||||
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
|
||||
RectTransform hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
|
||||
hiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
|
||||
RectTransform hiddenrect = hiddenNameText.Component.gameObject.GetComponent<RectTransform>();
|
||||
hiddenrect.anchorMin = new Vector2(0, 0);
|
||||
hiddenrect.anchorMax = new Vector2(1, 1);
|
||||
HiddenNameText.Component.readOnly = true;
|
||||
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
|
||||
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
HiddenNameText.Component.textComponent.fontSize = 17;
|
||||
HiddenNameText.Component.textComponent.color = Color.clear;
|
||||
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
hiddenNameText.Component.readOnly = true;
|
||||
hiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
hiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
|
||||
hiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
hiddenNameText.Component.textComponent.fontSize = 17;
|
||||
hiddenNameText.Component.textComponent.color = Color.clear;
|
||||
UIFactory.SetLayoutElement(hiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
makeGenericButton = UIFactory.CreateButton(topRow, "MakeGenericButton", "Construct Generic", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(makeGenericButton.GameObject, minWidth: 140, minHeight: 25);
|
||||
makeGenericButton.OnClick += OnMakeGenericClicked;
|
||||
makeGenericButton.GameObject.SetActive(false);
|
||||
|
||||
ButtonRef copyButton = UIFactory.CreateButton(topRow, "CopyButton", "Copy to Clipboard", new Color(0.2f, 0.2f, 0.2f, 1));
|
||||
copyButton.ButtonText.color = Color.yellow;
|
||||
UIFactory.SetLayoutElement(copyButton.Component.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 0);
|
||||
copyButton.OnClick += OnCopyClicked;
|
||||
|
||||
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
assemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(assemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
|
||||
ContentRoot = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
|
||||
new Color(0.12f, 0.12f, 0.12f));
|
||||
UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutElement(ContentRoot, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
ConstructFirstRow(mainContentHolder);
|
||||
ConstructFirstRow(ContentRoot);
|
||||
|
||||
ConstructSecondRow(mainContentHolder);
|
||||
ConstructSecondRow(ContentRoot);
|
||||
|
||||
// Member scroll pool
|
||||
|
||||
GameObject memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2, 2, 2, 2),
|
||||
bgColor: new Color(0.05f, 0.05f, 0.05f));
|
||||
GameObject memberBorder = UIFactory.CreateVerticalGroup(ContentRoot, "ScrollPoolHolder", false, false, true, true,
|
||||
padding: new Vector4(2, 2, 2, 2), bgColor: new Color(0.05f, 0.05f, 0.05f));
|
||||
UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
MemberScrollPool = UIFactory.CreateScrollPool<CacheMemberCell>(memberBorder, "MemberList", out GameObject scrollObj,
|
||||
@ -411,7 +457,7 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
// First row
|
||||
|
||||
private void ConstructFirstRow(GameObject parent)
|
||||
void ConstructFirstRow(GameObject parent)
|
||||
{
|
||||
GameObject rowObj = UIFactory.CreateUIObject("FirstRow", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, true, true, true, true, 5, 2, 2, 2, 2);
|
||||
@ -422,7 +468,7 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
filterInputField = UIFactory.CreateInputField(rowObj, "NameFilterInput", "...");
|
||||
UIFactory.SetLayoutElement(filterInputField.UIRoot, minHeight: 25, flexibleWidth: 300);
|
||||
filterInputField.OnValueChanged += (string val) => { SetFilter(val); };
|
||||
filterInputField.OnValueChanged += (string val) => { OnSetNameFilter(val); };
|
||||
|
||||
GameObject spacer = UIFactory.CreateUIObject("Spacer", rowObj);
|
||||
UIFactory.SetLayoutElement(spacer, minWidth: 25);
|
||||
@ -431,7 +477,7 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
ButtonRef updateButton = UIFactory.CreateButton(rowObj, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f));
|
||||
UIFactory.SetLayoutElement(updateButton.Component.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0);
|
||||
updateButton.OnClick += UpdateClicked;
|
||||
updateButton.OnClick += OnUpdateClicked;
|
||||
|
||||
GameObject toggleObj = UIFactory.CreateToggle(rowObj, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText);
|
||||
UIFactory.SetLayoutElement(toggleObj, minWidth: 125, minHeight: 25);
|
||||
@ -441,7 +487,7 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
// Second row
|
||||
|
||||
private void ConstructSecondRow(GameObject parent)
|
||||
void ConstructSecondRow(GameObject parent)
|
||||
{
|
||||
GameObject rowObj = UIFactory.CreateUIObject("SecondRow", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 2, 2, 2, 2);
|
||||
@ -466,7 +512,7 @@ namespace UnityExplorer.Inspectors
|
||||
AddMemberTypeToggle(rowObj, MemberTypes.Constructor, 110);
|
||||
}
|
||||
|
||||
private void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
|
||||
void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
|
||||
{
|
||||
string lbl = flags == BindingFlags.Default ? "All" : flags.ToString();
|
||||
Color color = setAsActive ? enabledButtonColor : disabledButtonColor;
|
||||
@ -475,10 +521,10 @@ namespace UnityExplorer.Inspectors
|
||||
UIFactory.SetLayoutElement(button.Component.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 70, flexibleWidth: 0);
|
||||
scopeFilterButtons.Add(flags, button);
|
||||
|
||||
button.OnClick += () => { SetFilter(flags); };
|
||||
button.OnClick += () => { OnSetFlags(flags); };
|
||||
}
|
||||
|
||||
private void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
|
||||
void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
|
||||
{
|
||||
GameObject toggleObj = UIFactory.CreateToggle(parent, "Toggle_" + type, out Toggle toggle, out Text toggleText);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: width);
|
||||
|
@ -59,7 +59,7 @@ namespace UnityExplorer.Loader.BIE
|
||||
|
||||
public override void SaveConfig()
|
||||
{
|
||||
// not required
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,53 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.Loader.Standalone
|
||||
{
|
||||
public class ExplorerEditorBehaviour : MonoBehaviour
|
||||
{
|
||||
internal static ExplorerEditorBehaviour Instance { get; private set; }
|
||||
|
||||
public bool Hide_On_Startup = true;
|
||||
public KeyCode Master_Toggle_Key = KeyCode.F7;
|
||||
public UIManager.VerticalAnchor Main_Navbar_Anchor = UIManager.VerticalAnchor.Top;
|
||||
public bool Log_Unity_Debug = false;
|
||||
public float Startup_Delay_Time = 1f;
|
||||
public KeyCode World_MouseInspect_Keybind;
|
||||
public KeyCode UI_MouseInspect_Keybind;
|
||||
public bool Force_Unlock_Mouse = true;
|
||||
public KeyCode Force_Unlock_Toggle;
|
||||
public bool Disable_EventSystem_Override;
|
||||
|
||||
internal void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
ExplorerEditorLoader.Initialize();
|
||||
DontDestroyOnLoad(this);
|
||||
this.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
}
|
||||
|
||||
internal void OnDestroy()
|
||||
{
|
||||
OnApplicationQuit();
|
||||
}
|
||||
|
||||
internal void OnApplicationQuit()
|
||||
{
|
||||
if (UI.UIManager.UIRoot)
|
||||
Destroy(UI.UIManager.UIRoot.transform.root.gameObject);
|
||||
Destroy(this.gameObject);
|
||||
}
|
||||
|
||||
internal void LoadConfigs()
|
||||
{
|
||||
ConfigManager.Hide_On_Startup.Value = this.Hide_On_Startup;
|
||||
ConfigManager.Master_Toggle.Value = this.Master_Toggle_Key;
|
||||
ConfigManager.Main_Navbar_Anchor.Value = this.Main_Navbar_Anchor;
|
||||
ConfigManager.Log_Unity_Debug.Value = this.Log_Unity_Debug;
|
||||
ConfigManager.Startup_Delay_Time.Value = this.Startup_Delay_Time;
|
||||
ConfigManager.World_MouseInspect_Keybind.Value = this.World_MouseInspect_Keybind;
|
||||
ConfigManager.UI_MouseInspect_Keybind.Value = this.UI_MouseInspect_Keybind;
|
||||
ConfigManager.Force_Unlock_Mouse.Value = this.Force_Unlock_Mouse;
|
||||
ConfigManager.Force_Unlock_Toggle.Value = this.Force_Unlock_Toggle;
|
||||
ConfigManager.Disable_EventSystem_Override.Value = this.Disable_EventSystem_Override;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace UnityExplorer.Loader.Standalone
|
||||
protected override void CheckExplorerFolder()
|
||||
{
|
||||
if (explorerFolderDest == null)
|
||||
explorerFolderDest = Application.dataPath;
|
||||
explorerFolderDest = Path.GetDirectoryName(Application.dataPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,12 +34,15 @@ namespace UnityExplorer.ObjectExplorer
|
||||
private ScrollPool<ButtonCell> resultsScrollPool;
|
||||
private List<object> currentResults = new();
|
||||
|
||||
//public TypeCompleter typeAutocompleter;
|
||||
public TypeCompleter unityObjectTypeCompleter;
|
||||
public TypeCompleter allTypesCompleter;
|
||||
|
||||
public override GameObject UIRoot => uiRoot;
|
||||
private GameObject uiRoot;
|
||||
private GameObject sceneFilterRow;
|
||||
private GameObject childFilterRow;
|
||||
private GameObject classInputRow;
|
||||
public TypeCompleter typeAutocompleter;
|
||||
private GameObject nameInputRow;
|
||||
private InputFieldRef nameInputField;
|
||||
private Text resultsLabel;
|
||||
@ -98,14 +101,18 @@ namespace UnityExplorer.ObjectExplorer
|
||||
|
||||
nameInputRow.SetActive(context == SearchContext.UnityObject);
|
||||
|
||||
if (context == SearchContext.Class)
|
||||
typeAutocompleter.AllTypes = true;
|
||||
else
|
||||
switch (context)
|
||||
{
|
||||
typeAutocompleter.BaseType = context == SearchContext.UnityObject ? typeof(UnityEngine.Object) : typeof(object);
|
||||
typeAutocompleter.AllTypes = false;
|
||||
case SearchContext.UnityObject:
|
||||
unityObjectTypeCompleter.Enabled = true;
|
||||
allTypesCompleter.Enabled = false;
|
||||
break;
|
||||
case SearchContext.Singleton:
|
||||
case SearchContext.Class:
|
||||
allTypesCompleter.Enabled = true;
|
||||
unityObjectTypeCompleter.Enabled = false;
|
||||
break;
|
||||
}
|
||||
typeAutocompleter.CacheTypes();
|
||||
}
|
||||
|
||||
private void OnSceneFilterDropChanged(int value) => sceneFilter = (SceneFilter)value;
|
||||
@ -185,7 +192,9 @@ namespace UnityExplorer.ObjectExplorer
|
||||
InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "...");
|
||||
UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField);
|
||||
unityObjectTypeCompleter = new(typeof(UnityEngine.Object), classInputField, true, false, true);
|
||||
allTypesCompleter = new(null, classInputField, true, false, true);
|
||||
allTypesCompleter.Enabled = false;
|
||||
classInputField.OnValueChanged += OnTypeInputChanged;
|
||||
|
||||
//unityObjectClassRow.SetActive(false);
|
||||
|
@ -13,6 +13,7 @@ using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
@ -156,7 +157,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
if ((!string.IsNullOrEmpty(input) && !Tree.Filtering) || (string.IsNullOrEmpty(input) && Tree.Filtering))
|
||||
{
|
||||
Tree.cachedTransforms.Clear();
|
||||
Tree.Clear();
|
||||
}
|
||||
|
||||
Tree.CurrentFilter = input;
|
||||
@ -259,8 +260,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999);
|
||||
|
||||
Tree = new TransformTree(scrollPool, GetRootEntries);
|
||||
Tree.Init();
|
||||
Tree = new TransformTree(scrollPool, GetRootEntries, OnCellClicked);
|
||||
Tree.RefreshData(true, true, true, false);
|
||||
//scrollPool.Viewport.GetComponent<Mask>().enabled = false;
|
||||
//UIRoot.GetComponent<Mask>().enabled = false;
|
||||
@ -272,6 +272,8 @@ namespace UnityExplorer.ObjectExplorer
|
||||
RuntimeHelper.StartCoroutine(TempFixCoro());
|
||||
}
|
||||
|
||||
void OnCellClicked(GameObject obj) => InspectorManager.Inspect(obj);
|
||||
|
||||
// To "fix" a strange FPS drop issue with MelonLoader.
|
||||
private IEnumerator TempFixCoro()
|
||||
{
|
||||
|
@ -135,7 +135,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (Type type in asm.TryGetTypes())
|
||||
foreach (Type type in asm.GetTypes())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
|
||||
continue;
|
||||
@ -173,7 +173,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
// Search all non-static, non-enum classes.
|
||||
foreach (Type type in asm.TryGetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
|
||||
foreach (Type type in asm.GetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -174,6 +174,7 @@ namespace UnityExplorer.Tests
|
||||
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
|
||||
public static Il2CppSystem.Collections.IList IL2CPP_IList;
|
||||
public static Dictionary<Il2CppSystem.Object, Il2CppSystem.Object> IL2CPP_BoxedDict;
|
||||
public static Il2CppSystem.Array IL2CPP_NonGenericArray;
|
||||
|
||||
public static Il2CppSystem.Object IL2CPP_BoxedInt;
|
||||
public static Il2CppSystem.Int32 IL2CPP_Int;
|
||||
@ -187,6 +188,9 @@ namespace UnityExplorer.Tests
|
||||
|
||||
private static void Init_IL2CPP()
|
||||
{
|
||||
ExplorerCore.Log("IL2CPP 0: Non-generic array");
|
||||
IL2CPP_NonGenericArray = new Il2CppStructArray<int>(5).TryCast<Il2CppSystem.Array>();
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 1: Il2Cpp Dictionary<string, string>");
|
||||
IL2CPP_Dict = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
|
||||
IL2CPP_Dict.Add("key1", "value1");
|
||||
|
@ -69,15 +69,22 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
if (CurrentHandler == provider)
|
||||
{
|
||||
Suggestions.Clear();
|
||||
CurrentHandler = null;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSuggestions(IEnumerable<Suggestion> suggestions)
|
||||
public void SetSuggestions(List<Suggestion> suggestions, bool jumpToTop = true)
|
||||
{
|
||||
Suggestions = suggestions as List<Suggestion> ?? suggestions.ToList();
|
||||
SelectedIndex = 0;
|
||||
Suggestions = suggestions;
|
||||
|
||||
if (jumpToTop)
|
||||
{
|
||||
SelectedIndex = 0;
|
||||
if (scrollPool.DataSource.ItemCount > 0)
|
||||
scrollPool.JumpToIndex(0, null);
|
||||
}
|
||||
|
||||
if (!Suggestions.Any())
|
||||
base.UIRoot.SetActive(false);
|
||||
@ -86,7 +93,7 @@ namespace UnityExplorer.UI.Panels
|
||||
base.UIRoot.SetActive(true);
|
||||
base.UIRoot.transform.SetAsLastSibling();
|
||||
buttonListDataHandler.RefreshData();
|
||||
scrollPool.Refresh(true, true);
|
||||
scrollPool.Refresh(true, jumpToTop);
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,6 +201,12 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
private void SetCell(ButtonCell cell, int index)
|
||||
{
|
||||
if (CurrentHandler == null)
|
||||
{
|
||||
UIRoot.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= Suggestions.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
@ -225,13 +238,18 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
InputFieldRef input = CurrentHandler.InputField;
|
||||
|
||||
if (!input.Component.isFocused || input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition)
|
||||
return;
|
||||
lastInputPosition = input.UIRoot.transform.position;
|
||||
lastCaretPosition = input.Component.caretPosition;
|
||||
//if (!input.Component.isFocused
|
||||
// || (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition))
|
||||
// return;
|
||||
|
||||
if (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition)
|
||||
return;
|
||||
|
||||
if (CurrentHandler.AnchorToCaretPosition)
|
||||
{
|
||||
if (!input.Component.isFocused)
|
||||
return;
|
||||
|
||||
TextGenerator textGen = input.Component.cachedInputTextGenerator;
|
||||
int caretIdx = Math.Max(0, Math.Min(textGen.characterCount - 1, input.Component.caretPosition));
|
||||
|
||||
@ -248,6 +266,9 @@ namespace UnityExplorer.UI.Panels
|
||||
uiRoot.transform.position = input.Transform.position + new Vector3(-(input.Transform.rect.width / 2) + 10, -20, 0);
|
||||
}
|
||||
|
||||
lastInputPosition = input.UIRoot.transform.position;
|
||||
lastCaretPosition = input.Component.caretPosition;
|
||||
|
||||
this.Dragger.OnEndResize();
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ namespace UnityExplorer.UI.Panels
|
||||
GameObject inputObj = UIFactory.CreateScrollInputField(inputArea, "ConsoleInput", ConsoleController.STARTUP_TEXT,
|
||||
out InputFieldScroller inputScroller, fontSize);
|
||||
InputScroller = inputScroller;
|
||||
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
|
||||
ConsoleController.DefaultInputFieldAlpha = Input.Component.selectionColor.a;
|
||||
Input.OnValueChanged += InvokeOnValueChanged;
|
||||
|
||||
// move line number text with input field
|
||||
|
394
src/UI/Panels/FreeCamPanel.cs
Normal file
394
src/UI/Panels/FreeCamPanel.cs
Normal file
@ -0,0 +1,394 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
internal class FreeCamPanel : UEPanel
|
||||
{
|
||||
public FreeCamPanel(UIBase owner) : base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
public override string Name => "Freecam";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.Freecam;
|
||||
public override int MinWidth => 400;
|
||||
public override int MinHeight => 320;
|
||||
public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f);
|
||||
public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f);
|
||||
public override bool NavButtonWanted => true;
|
||||
public override bool ShouldSaveActiveState => true;
|
||||
|
||||
internal static bool inFreeCamMode;
|
||||
internal static bool usingGameCamera;
|
||||
internal static Camera ourCamera;
|
||||
internal static Camera lastMainCamera;
|
||||
internal static FreeCamBehaviour freeCamScript;
|
||||
|
||||
internal static float desiredMoveSpeed = 10f;
|
||||
|
||||
internal static Vector3 originalCameraPosition;
|
||||
internal static Quaternion originalCameraRotation;
|
||||
|
||||
internal static Vector3? currentUserCameraPosition;
|
||||
internal static Quaternion? currentUserCameraRotation;
|
||||
|
||||
internal static Vector3 previousMousePosition;
|
||||
|
||||
internal static Vector3 lastSetCameraPosition;
|
||||
|
||||
static ButtonRef startStopButton;
|
||||
static Toggle useGameCameraToggle;
|
||||
static InputFieldRef positionInput;
|
||||
static InputFieldRef moveSpeedInput;
|
||||
static ButtonRef inspectButton;
|
||||
|
||||
internal static void BeginFreecam()
|
||||
{
|
||||
inFreeCamMode = true;
|
||||
|
||||
previousMousePosition = InputManager.MousePosition;
|
||||
|
||||
CacheMainCamera();
|
||||
SetupFreeCamera();
|
||||
|
||||
inspectButton.GameObject.SetActive(true);
|
||||
}
|
||||
|
||||
static void CacheMainCamera()
|
||||
{
|
||||
Camera currentMain = Camera.main;
|
||||
if (currentMain)
|
||||
{
|
||||
lastMainCamera = currentMain;
|
||||
originalCameraPosition = currentMain.transform.position;
|
||||
originalCameraRotation = currentMain.transform.rotation;
|
||||
|
||||
if (currentUserCameraPosition == null)
|
||||
{
|
||||
currentUserCameraPosition = currentMain.transform.position;
|
||||
currentUserCameraRotation = currentMain.transform.rotation;
|
||||
}
|
||||
}
|
||||
else
|
||||
originalCameraRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
static void SetupFreeCamera()
|
||||
{
|
||||
if (useGameCameraToggle.isOn)
|
||||
{
|
||||
if (!lastMainCamera)
|
||||
{
|
||||
ExplorerCore.LogWarning($"There is no previous Camera found, reverting to default Free Cam.");
|
||||
useGameCameraToggle.isOn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
usingGameCamera = true;
|
||||
ourCamera = lastMainCamera;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useGameCameraToggle.isOn)
|
||||
{
|
||||
usingGameCamera = false;
|
||||
|
||||
if (lastMainCamera)
|
||||
lastMainCamera.enabled = false;
|
||||
}
|
||||
|
||||
if (!ourCamera)
|
||||
{
|
||||
ourCamera = new GameObject("UE_Freecam").AddComponent<Camera>();
|
||||
ourCamera.gameObject.tag = "MainCamera";
|
||||
GameObject.DontDestroyOnLoad(ourCamera.gameObject);
|
||||
ourCamera.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
}
|
||||
|
||||
if (!freeCamScript)
|
||||
freeCamScript = ourCamera.gameObject.AddComponent<FreeCamBehaviour>();
|
||||
|
||||
ourCamera.transform.position = (Vector3)currentUserCameraPosition;
|
||||
ourCamera.transform.rotation = (Quaternion)currentUserCameraRotation;
|
||||
|
||||
ourCamera.gameObject.SetActive(true);
|
||||
ourCamera.enabled = true;
|
||||
}
|
||||
|
||||
internal static void EndFreecam()
|
||||
{
|
||||
inFreeCamMode = false;
|
||||
|
||||
if (usingGameCamera)
|
||||
{
|
||||
ourCamera = null;
|
||||
|
||||
if (lastMainCamera)
|
||||
{
|
||||
lastMainCamera.transform.position = originalCameraPosition;
|
||||
lastMainCamera.transform.rotation = originalCameraRotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (ourCamera)
|
||||
ourCamera.gameObject.SetActive(false);
|
||||
else
|
||||
inspectButton.GameObject.SetActive(false);
|
||||
|
||||
if (freeCamScript)
|
||||
{
|
||||
GameObject.Destroy(freeCamScript);
|
||||
freeCamScript = null;
|
||||
}
|
||||
|
||||
if (lastMainCamera)
|
||||
lastMainCamera.enabled = true;
|
||||
}
|
||||
|
||||
static void SetCameraPosition(Vector3 pos)
|
||||
{
|
||||
if (!ourCamera || lastSetCameraPosition == pos)
|
||||
return;
|
||||
|
||||
ourCamera.transform.position = pos;
|
||||
lastSetCameraPosition = pos;
|
||||
}
|
||||
|
||||
internal static void UpdatePositionInput()
|
||||
{
|
||||
if (!ourCamera)
|
||||
return;
|
||||
|
||||
lastSetCameraPosition = ourCamera.transform.position;
|
||||
positionInput.Text = ParseUtility.ToStringForInput<Vector3>(lastSetCameraPosition);
|
||||
}
|
||||
|
||||
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
|
||||
|
||||
protected override void ConstructPanelContent()
|
||||
{
|
||||
startStopButton = UIFactory.CreateButton(ContentRoot, "ToggleButton", "Freecam");
|
||||
UIFactory.SetLayoutElement(startStopButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999);
|
||||
startStopButton.OnClick += StartStopButton_OnClick;
|
||||
SetToggleButtonState();
|
||||
|
||||
AddSpacer(5);
|
||||
|
||||
GameObject toggleObj = UIFactory.CreateToggle(ContentRoot, "UseGameCameraToggle", out useGameCameraToggle, out Text toggleText);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, flexibleWidth: 9999);
|
||||
useGameCameraToggle.onValueChanged.AddListener(OnUseGameCameraToggled);
|
||||
useGameCameraToggle.isOn = false;
|
||||
toggleText.text = "Use Game Camera?";
|
||||
|
||||
AddSpacer(5);
|
||||
|
||||
GameObject posRow = AddInputField("Position", "Freecam Pos:", "eg. 0 0 0", out positionInput, PositionInput_OnEndEdit);
|
||||
|
||||
ButtonRef resetPosButton = UIFactory.CreateButton(posRow, "ResetButton", "Reset");
|
||||
UIFactory.SetLayoutElement(resetPosButton.GameObject, minWidth: 70, minHeight: 25);
|
||||
resetPosButton.OnClick += OnResetPosButtonClicked;
|
||||
|
||||
AddSpacer(5);
|
||||
|
||||
AddInputField("MoveSpeed", "Move Speed:", "Default: 1", out moveSpeedInput, MoveSpeedInput_OnEndEdit);
|
||||
moveSpeedInput.Text = desiredMoveSpeed.ToString();
|
||||
|
||||
AddSpacer(5);
|
||||
|
||||
string instructions = @"Controls:
|
||||
- WASD / Arrows: Movement
|
||||
- Space / PgUp: Move up
|
||||
- LeftCtrl / PgDown: Move down
|
||||
- Right Mouse Button: Free look
|
||||
- Shift: Super speed";
|
||||
|
||||
Text instructionsText = UIFactory.CreateLabel(ContentRoot, "Instructions", instructions, TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(instructionsText.gameObject, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
AddSpacer(5);
|
||||
|
||||
inspectButton = UIFactory.CreateButton(ContentRoot, "InspectButton", "Inspect Free Camera");
|
||||
UIFactory.SetLayoutElement(inspectButton.GameObject, flexibleWidth: 9999, minHeight: 25);
|
||||
inspectButton.OnClick += () => { InspectorManager.Inspect(ourCamera); };
|
||||
inspectButton.GameObject.SetActive(false);
|
||||
|
||||
AddSpacer(5);
|
||||
}
|
||||
|
||||
void AddSpacer(int height)
|
||||
{
|
||||
GameObject obj = UIFactory.CreateUIObject("Spacer", ContentRoot);
|
||||
UIFactory.SetLayoutElement(obj, minHeight: height, flexibleHeight: 0);
|
||||
}
|
||||
|
||||
GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action<string> onInputEndEdit)
|
||||
{
|
||||
GameObject row = UIFactory.CreateHorizontalGroup(ContentRoot, $"{name}_Group", false, false, true, true, 3, default, new(1, 1, 1, 0));
|
||||
|
||||
Text posLabel = UIFactory.CreateLabel(row, $"{name}_Label", labelText);
|
||||
UIFactory.SetLayoutElement(posLabel.gameObject, minWidth: 100, minHeight: 25);
|
||||
|
||||
inputField = UIFactory.CreateInputField(row, $"{name}_Input", placeHolder);
|
||||
UIFactory.SetLayoutElement(inputField.GameObject, minWidth: 125, minHeight: 25, flexibleWidth: 9999);
|
||||
inputField.Component.GetOnEndEdit().AddListener(onInputEndEdit);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
void StartStopButton_OnClick()
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (inFreeCamMode)
|
||||
EndFreecam();
|
||||
else
|
||||
BeginFreecam();
|
||||
|
||||
SetToggleButtonState();
|
||||
}
|
||||
|
||||
void SetToggleButtonState()
|
||||
{
|
||||
if (inFreeCamMode)
|
||||
{
|
||||
RuntimeHelper.SetColorBlockAuto(startStopButton.Component, new(0.4f, 0.2f, 0.2f));
|
||||
startStopButton.ButtonText.text = "End Freecam";
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeHelper.SetColorBlockAuto(startStopButton.Component, new(0.2f, 0.4f, 0.2f));
|
||||
startStopButton.ButtonText.text = "Begin Freecam";
|
||||
}
|
||||
}
|
||||
|
||||
void OnUseGameCameraToggled(bool value)
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!inFreeCamMode)
|
||||
return;
|
||||
|
||||
EndFreecam();
|
||||
BeginFreecam();
|
||||
}
|
||||
|
||||
void OnResetPosButtonClicked()
|
||||
{
|
||||
currentUserCameraPosition = originalCameraPosition;
|
||||
currentUserCameraRotation = originalCameraRotation;
|
||||
|
||||
if (inFreeCamMode && ourCamera)
|
||||
{
|
||||
ourCamera.transform.position = (Vector3)currentUserCameraPosition;
|
||||
ourCamera.transform.rotation = (Quaternion)currentUserCameraRotation;
|
||||
}
|
||||
|
||||
positionInput.Text = ParseUtility.ToStringForInput<Vector3>(originalCameraPosition);
|
||||
}
|
||||
|
||||
void PositionInput_OnEndEdit(string input)
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!ParseUtility.TryParse(input, out Vector3 parsed, out Exception parseEx))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse position to Vector3: {parseEx.ReflectionExToString()}");
|
||||
UpdatePositionInput();
|
||||
return;
|
||||
}
|
||||
|
||||
SetCameraPosition(parsed);
|
||||
}
|
||||
|
||||
void MoveSpeedInput_OnEndEdit(string input)
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!ParseUtility.TryParse(input, out float parsed, out Exception parseEx))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}");
|
||||
moveSpeedInput.Text = desiredMoveSpeed.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
desiredMoveSpeed = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
internal class FreeCamBehaviour : MonoBehaviour
|
||||
{
|
||||
#if CPP
|
||||
static FreeCamBehaviour()
|
||||
{
|
||||
UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp<FreeCamBehaviour>();
|
||||
}
|
||||
|
||||
public FreeCamBehaviour(IntPtr ptr) : base(ptr) { }
|
||||
#endif
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (FreeCamPanel.inFreeCamMode)
|
||||
{
|
||||
if (!FreeCamPanel.ourCamera)
|
||||
{
|
||||
FreeCamPanel.EndFreecam();
|
||||
return;
|
||||
}
|
||||
|
||||
Transform transform = FreeCamPanel.ourCamera.transform;
|
||||
|
||||
FreeCamPanel.currentUserCameraPosition = transform.position;
|
||||
FreeCamPanel.currentUserCameraRotation = transform.rotation;
|
||||
|
||||
float moveSpeed = FreeCamPanel.desiredMoveSpeed * Time.deltaTime;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.LeftShift) || InputManager.GetKey(KeyCode.RightShift))
|
||||
moveSpeed *= 10f;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.LeftArrow) || InputManager.GetKey(KeyCode.A))
|
||||
transform.position += transform.right * -1 * moveSpeed;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.RightArrow) || InputManager.GetKey(KeyCode.D))
|
||||
transform.position += transform.right * moveSpeed;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.UpArrow) || InputManager.GetKey(KeyCode.W))
|
||||
transform.position += transform.forward * moveSpeed;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.DownArrow) || InputManager.GetKey(KeyCode.S))
|
||||
transform.position += transform.forward * -1 * moveSpeed;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.Space) || InputManager.GetKey(KeyCode.PageUp))
|
||||
transform.position += transform.up * moveSpeed;
|
||||
|
||||
if (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.PageDown))
|
||||
transform.position += transform.up * -1 * moveSpeed;
|
||||
|
||||
if (InputManager.GetMouseButton(1))
|
||||
{
|
||||
Vector3 mouseDelta = InputManager.MousePosition - FreeCamPanel.previousMousePosition;
|
||||
|
||||
float newRotationX = transform.localEulerAngles.y + mouseDelta.x * 0.3f;
|
||||
float newRotationY = transform.localEulerAngles.x - mouseDelta.y * 0.3f;
|
||||
transform.localEulerAngles = new Vector3(newRotationY, newRotationX, 0f);
|
||||
}
|
||||
|
||||
FreeCamPanel.UpdatePositionInput();
|
||||
|
||||
FreeCamPanel.previousMousePosition = InputManager.MousePosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Hooks;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets;
|
||||
@ -11,76 +14,58 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class HookManagerPanel : UEPanel
|
||||
{
|
||||
public static HookManagerPanel Instance { get; private set; }
|
||||
|
||||
public enum Pages
|
||||
{
|
||||
CurrentHooks,
|
||||
ClassMethodSelector,
|
||||
HookSourceEditor
|
||||
HookSourceEditor,
|
||||
GenericArgsSelector,
|
||||
}
|
||||
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
|
||||
public static HookCreator hookCreator;
|
||||
public static HookList hookList;
|
||||
public static GenericConstructorWidget genericArgsHandler;
|
||||
|
||||
// Panel
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
|
||||
public override string Name => "Hooks";
|
||||
public override bool ShowByDefault => false;
|
||||
|
||||
public override int MinWidth => 500;
|
||||
public override int MinHeight => 600;
|
||||
public override int MinWidth => 400;
|
||||
public override int MinHeight => 400;
|
||||
public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f);
|
||||
public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f);
|
||||
|
||||
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
|
||||
|
||||
private GameObject currentHooksPanel;
|
||||
public ScrollPool<HookCell> HooksScrollPool;
|
||||
private InputFieldRef classSelectorInputField;
|
||||
|
||||
private GameObject addHooksPanel;
|
||||
public ScrollPool<AddHookCell> AddHooksScrollPool;
|
||||
private Text addHooksLabel;
|
||||
private InputFieldRef AddHooksMethodFilterInput;
|
||||
|
||||
private GameObject editorPanel;
|
||||
|
||||
public InputFieldScroller EditorInputScroller { get; private set; }
|
||||
public InputFieldRef EditorInput => EditorInputScroller.InputField;
|
||||
public Text EditorInputText { get; private set; }
|
||||
public Text EditorHighlightText { get; private set; }
|
||||
public Pages CurrentPage { get; private set; } = Pages.ClassMethodSelector;
|
||||
|
||||
public HookManagerPanel(UIBase owner) : base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
private void OnClassInputAddClicked()
|
||||
{
|
||||
HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
|
||||
}
|
||||
|
||||
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
|
||||
|
||||
public void SetPage(Pages page)
|
||||
{
|
||||
switch (page)
|
||||
{
|
||||
case Pages.CurrentHooks:
|
||||
currentHooksPanel.SetActive(true);
|
||||
addHooksPanel.SetActive(false);
|
||||
editorPanel.SetActive(false);
|
||||
break;
|
||||
case Pages.ClassMethodSelector:
|
||||
currentHooksPanel.SetActive(false);
|
||||
addHooksPanel.SetActive(true);
|
||||
editorPanel.SetActive(false);
|
||||
HookCreator.AddHooksRoot.SetActive(true);
|
||||
HookCreator.EditorRoot.SetActive(false);
|
||||
genericArgsHandler.UIRoot.SetActive(false);
|
||||
break;
|
||||
|
||||
case Pages.HookSourceEditor:
|
||||
currentHooksPanel.SetActive(false);
|
||||
addHooksPanel.SetActive(false);
|
||||
editorPanel.SetActive(true);
|
||||
HookCreator.AddHooksRoot.SetActive(false);
|
||||
HookCreator.EditorRoot.SetActive(true);
|
||||
genericArgsHandler.UIRoot.SetActive(false);
|
||||
break;
|
||||
|
||||
case Pages.GenericArgsSelector:
|
||||
HookCreator.AddHooksRoot.SetActive(false);
|
||||
HookCreator.EditorRoot.SetActive(false);
|
||||
genericArgsHandler.UIRoot.SetActive(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
|
||||
|
||||
public override void SetDefaultSizeAndPosition()
|
||||
{
|
||||
base.SetDefaultSizeAndPosition();
|
||||
@ -91,115 +76,35 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
protected override void ConstructPanelContent()
|
||||
{
|
||||
// ~~~~~~~~~ Active hooks scroll pool
|
||||
Instance = this;
|
||||
hookList = new();
|
||||
hookCreator = new();
|
||||
genericArgsHandler = new();
|
||||
|
||||
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.ContentRoot);
|
||||
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ContentRoot, true, false);
|
||||
|
||||
GameObject addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
|
||||
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
|
||||
// GameObject baseHoriGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "HoriGroup", true, true, true, true);
|
||||
// UIFactory.SetLayoutElement(baseHoriGroup, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
|
||||
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
|
||||
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
|
||||
// // Left Group
|
||||
|
||||
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
|
||||
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
|
||||
addButton.OnClick += OnClassInputAddClicked;
|
||||
//GameObject leftGroup = UIFactory.CreateVerticalGroup(ContentRoot, "LeftGroup", true, true, true, true);
|
||||
UIFactory.SetLayoutElement(ContentRoot.gameObject, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
Text hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
hookList.ConstructUI(ContentRoot);
|
||||
|
||||
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
|
||||
out GameObject hooksScroll, out GameObject hooksContent);
|
||||
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
|
||||
HooksScrollPool.Initialize(HookManager.Instance);
|
||||
// // Right Group
|
||||
|
||||
// ~~~~~~~~~ Add hooks panel
|
||||
//GameObject rightGroup = UIFactory.CreateVerticalGroup(ContentRoot, "RightGroup", true, true, true, true);
|
||||
UIFactory.SetLayoutElement(ContentRoot, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.ContentRoot);
|
||||
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
|
||||
hookCreator.ConstructAddHooksView(ContentRoot);
|
||||
|
||||
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
|
||||
hookCreator.ConstructEditor(ContentRoot);
|
||||
HookCreator.EditorRoot.SetActive(false);
|
||||
|
||||
GameObject buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ButtonRef doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
doneButton.OnClick += HookManager.Instance.DoneAddingHooks;
|
||||
|
||||
AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
|
||||
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
|
||||
|
||||
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
|
||||
out GameObject addScrollRoot, out GameObject addContent);
|
||||
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
|
||||
AddHooksScrollPool.Initialize(HookManager.Instance);
|
||||
|
||||
addHooksPanel.gameObject.SetActive(false);
|
||||
|
||||
// ~~~~~~~~~ Hook source editor panel
|
||||
|
||||
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.ContentRoot);
|
||||
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
|
||||
|
||||
Text editorLabel = UIFactory.CreateLabel(editorPanel,
|
||||
"EditorLabel",
|
||||
"Edit Harmony patch source as desired. Accepted method names are Prefix, Postfix, Finalizer and Transpiler (can define multiple).\n\n" +
|
||||
"Hooks are temporary! Please copy the source into your IDE to avoid losing work if you wish to keep it!",
|
||||
TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ButtonRef editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorSaveButton.OnClick += HookManager.Instance.EditorInputSave;
|
||||
|
||||
ButtonRef editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorDoneButton.OnClick += HookManager.Instance.EditorInputCancel;
|
||||
|
||||
int fontSize = 16;
|
||||
GameObject inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out InputFieldScroller inputScroller, fontSize);
|
||||
EditorInputScroller = inputScroller;
|
||||
EditorInput.OnValueChanged += HookManager.Instance.OnEditorInputChanged;
|
||||
|
||||
EditorInputText = EditorInput.Component.textComponent;
|
||||
EditorInputText.supportRichText = false;
|
||||
EditorInputText.color = Color.clear;
|
||||
EditorInput.Component.customCaretColor = true;
|
||||
EditorInput.Component.caretColor = Color.white;
|
||||
EditorInput.PlaceholderText.fontSize = fontSize;
|
||||
|
||||
// Lexer highlight text overlay
|
||||
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
|
||||
RectTransform highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||
highlightTextRect.pivot = new Vector2(0, 1);
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
EditorHighlightText = highlightTextObj.AddComponent<Text>();
|
||||
EditorHighlightText.color = Color.white;
|
||||
EditorHighlightText.supportRichText = true;
|
||||
EditorHighlightText.fontSize = fontSize;
|
||||
|
||||
// Set fonts
|
||||
EditorInputText.font = UniversalUI.ConsoleFont;
|
||||
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
|
||||
EditorHighlightText.font = UniversalUI.ConsoleFont;
|
||||
|
||||
editorPanel.SetActive(false);
|
||||
genericArgsHandler.ConstructUI(ContentRoot);
|
||||
genericArgsHandler.UIRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,6 @@ using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
// TODO move the logic out of this class into a LogUtil class (also move ExplorerCore.Log into that)
|
||||
|
||||
public class LogPanel : UEPanel, ICellPoolDataSource<ConsoleLogCell>
|
||||
{
|
||||
public struct LogInfo
|
||||
|
@ -75,13 +75,14 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public void SaveInternalData()
|
||||
{
|
||||
if (UIManager.Initializing)
|
||||
if (UIManager.Initializing || ApplyingSaveData)
|
||||
return;
|
||||
|
||||
SetSaveDataToConfigValue();
|
||||
}
|
||||
|
||||
private void SetSaveDataToConfigValue() => ConfigManager.GetPanelSaveData(this.PanelType).Value = this.ToSaveData();
|
||||
private void SetSaveDataToConfigValue()
|
||||
=> ConfigManager.GetPanelSaveData(this.PanelType).Value = this.ToSaveData();
|
||||
|
||||
public virtual string ToSaveData()
|
||||
{
|
||||
@ -118,6 +119,8 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
Rect.SetAnchorsFromString(split[1]);
|
||||
Rect.SetPositionFromString(split[2]);
|
||||
this.EnsureValidSize();
|
||||
this.EnsureValidPosition();
|
||||
this.SetActive(bool.Parse(split[0]));
|
||||
}
|
||||
catch
|
||||
@ -156,9 +159,10 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
protected override void LateConstructUI()
|
||||
{
|
||||
ApplyingSaveData = true;
|
||||
|
||||
base.LateConstructUI();
|
||||
|
||||
ApplyingSaveData = true;
|
||||
// apply panel save data or revert to default
|
||||
try
|
||||
{
|
||||
@ -177,6 +181,8 @@ namespace UnityExplorer.UI.Panels
|
||||
};
|
||||
|
||||
ApplyingSaveData = false;
|
||||
|
||||
Dragger.OnEndResize();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,10 @@ namespace UnityExplorer.UI
|
||||
Options,
|
||||
ConsoleLog,
|
||||
AutoCompleter,
|
||||
MouseInspector,
|
||||
UIInspectorResults,
|
||||
HookManager,
|
||||
Clipboard
|
||||
Clipboard,
|
||||
Freecam
|
||||
}
|
||||
|
||||
public enum VerticalAnchor
|
||||
@ -99,24 +99,27 @@ namespace UnityExplorer.UI
|
||||
UIPanels.Add(Panels.Inspector, new InspectorPanel(UiBase));
|
||||
UIPanels.Add(Panels.CSConsole, new CSConsolePanel(UiBase));
|
||||
UIPanels.Add(Panels.HookManager, new HookManagerPanel(UiBase));
|
||||
UIPanels.Add(Panels.Freecam, new FreeCamPanel(UiBase));
|
||||
UIPanels.Add(Panels.Clipboard, new ClipboardPanel(UiBase));
|
||||
UIPanels.Add(Panels.ConsoleLog, new LogPanel(UiBase));
|
||||
UIPanels.Add(Panels.Options, new OptionsPanel(UiBase));
|
||||
UIPanels.Add(Panels.UIInspectorResults, new MouseInspectorResultsPanel(UiBase));
|
||||
UIPanels.Add(Panels.MouseInspector, new MouseInspector(UiBase));
|
||||
|
||||
MouseInspector.inspectorUIBase = UniversalUI.RegisterUI(MouseInspector.UIBaseGUID, null);
|
||||
new MouseInspector(MouseInspector.inspectorUIBase);
|
||||
|
||||
// Call some initialize methods
|
||||
Notification.Init();
|
||||
ConsoleController.Init();
|
||||
|
||||
// Set default menu visibility
|
||||
ShowMenu = !ConfigManager.Hide_On_Startup.Value;
|
||||
|
||||
// Failsafe fix, in some games all dropdowns displayed values are blank on startup for some reason.
|
||||
foreach (Dropdown dropdown in UIRoot.GetComponentsInChildren<Dropdown>(true))
|
||||
dropdown.RefreshShownValue();
|
||||
|
||||
Initializing = false;
|
||||
|
||||
if (ConfigManager.Hide_On_Startup.Value)
|
||||
ShowMenu = false;
|
||||
}
|
||||
|
||||
// Main UI Update loop
|
||||
@ -285,9 +288,9 @@ namespace UnityExplorer.UI
|
||||
|
||||
// UnityExplorer title
|
||||
|
||||
string titleTxt = $"{ExplorerCore.NAME} <i><color=grey>{ExplorerCore.VERSION}</color></i>";
|
||||
Text title = UIFactory.CreateLabel(navbarPanel, "Title", titleTxt, TextAnchor.MiddleLeft, default, true, 17);
|
||||
UIFactory.SetLayoutElement(title.gameObject, minWidth: 170, flexibleWidth: 0);
|
||||
string titleTxt = $"UE <i><color=grey>{ExplorerCore.VERSION}</color></i>";
|
||||
Text title = UIFactory.CreateLabel(navbarPanel, "Title", titleTxt, TextAnchor.MiddleCenter, default, true, 14);
|
||||
UIFactory.SetLayoutElement(title.gameObject, minWidth: 75, flexibleWidth: 0);
|
||||
|
||||
// panel tabs
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Models;
|
||||
@ -12,133 +15,217 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
get => enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
if (!_enabled)
|
||||
enabled = value;
|
||||
if (!enabled)
|
||||
{
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this);
|
||||
if (getSuggestionsCoroutine != null)
|
||||
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _enabled = true;
|
||||
bool enabled = true;
|
||||
|
||||
public event Action<Suggestion> SuggestionClicked;
|
||||
|
||||
public Type BaseType { get; set; }
|
||||
public Type[] GenericConstraints { get; set; }
|
||||
public bool AllTypes { get; set; }
|
||||
|
||||
private readonly bool allowAbstract;
|
||||
private readonly bool allowEnum;
|
||||
|
||||
public InputFieldRef InputField { get; }
|
||||
public bool AnchorToCaretPosition => false;
|
||||
|
||||
private readonly List<Suggestion> suggestions = new();
|
||||
private readonly HashSet<string> suggestedNames = new();
|
||||
readonly bool allowAbstract;
|
||||
readonly bool allowEnum;
|
||||
readonly bool allowGeneric;
|
||||
|
||||
private HashSet<Type> allowedTypes;
|
||||
public Type BaseType { get; set; }
|
||||
HashSet<Type> allowedTypes;
|
||||
string pendingInput;
|
||||
Coroutine getSuggestionsCoroutine;
|
||||
readonly Stopwatch cacheTypesStopwatch = new();
|
||||
|
||||
private string chosenSuggestion;
|
||||
readonly List<Suggestion> suggestions = new();
|
||||
readonly HashSet<string> suggestedTypes = new();
|
||||
string chosenSuggestion;
|
||||
|
||||
readonly List<Suggestion> loadingSuggestions = new()
|
||||
{
|
||||
new("<color=grey>Loading...</color>", "")
|
||||
};
|
||||
|
||||
bool ISuggestionProvider.AllowNavigation => false;
|
||||
|
||||
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true) { }
|
||||
static readonly Dictionary<string, Type> shorthandToType = new()
|
||||
{
|
||||
{ "object", typeof(object) },
|
||||
{ "string", typeof(string) },
|
||||
{ "bool", typeof(bool) },
|
||||
{ "byte", typeof(byte) },
|
||||
{ "sbyte", typeof(sbyte) },
|
||||
{ "char", typeof(char) },
|
||||
{ "decimal", typeof(decimal) },
|
||||
{ "double", typeof(double) },
|
||||
{ "float", typeof(float) },
|
||||
{ "int", typeof(int) },
|
||||
{ "uint", typeof(uint) },
|
||||
{ "long", typeof(long) },
|
||||
{ "ulong", typeof(ulong) },
|
||||
{ "short", typeof(short) },
|
||||
{ "ushort", typeof(ushort) },
|
||||
{ "void", typeof(void) },
|
||||
};
|
||||
|
||||
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum)
|
||||
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true, true) { }
|
||||
|
||||
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum, bool allowGeneric)
|
||||
{
|
||||
BaseType = baseType;
|
||||
InputField = inputField;
|
||||
|
||||
this.allowAbstract = allowAbstract;
|
||||
this.allowEnum = allowEnum;
|
||||
this.allowGeneric = allowGeneric;
|
||||
|
||||
inputField.OnValueChanged += OnInputFieldChanged;
|
||||
|
||||
if (BaseType != null)
|
||||
CacheTypes();
|
||||
}
|
||||
|
||||
public void CacheTypes()
|
||||
{
|
||||
if (!AllTypes)
|
||||
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowEnum, false);
|
||||
else
|
||||
{
|
||||
allowedTypes = new();
|
||||
foreach (KeyValuePair<string, Type> entry in ReflectionUtility.AllTypes)
|
||||
{
|
||||
// skip <PrivateImplementationDetails> and <AnonymousClass> classes
|
||||
Type type = entry.Value;
|
||||
if (type.FullName.Contains("PrivateImplementationDetails")
|
||||
|| type.FullName.Contains("DisplayClass")
|
||||
|| type.FullName.Contains('<'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
allowedTypes.Add(type);
|
||||
}
|
||||
}
|
||||
CacheTypes();
|
||||
}
|
||||
|
||||
public void OnSuggestionClicked(Suggestion suggestion)
|
||||
{
|
||||
chosenSuggestion = suggestion.UnderlyingValue;
|
||||
InputField.Text = suggestion.UnderlyingValue;
|
||||
SuggestionClicked?.Invoke(suggestion);
|
||||
|
||||
suggestions.Clear();
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions);
|
||||
chosenSuggestion = suggestion.UnderlyingValue;
|
||||
//AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this);
|
||||
}
|
||||
|
||||
private void OnInputFieldChanged(string value)
|
||||
public void CacheTypes()
|
||||
{
|
||||
allowedTypes = null;
|
||||
cacheTypesStopwatch.Reset();
|
||||
cacheTypesStopwatch.Start();
|
||||
ReflectionUtility.GetImplementationsOf(BaseType, OnTypesCached, allowAbstract, allowGeneric, allowEnum);
|
||||
}
|
||||
|
||||
void OnTypesCached(HashSet<Type> set)
|
||||
{
|
||||
allowedTypes = set;
|
||||
|
||||
// ExplorerCore.Log($"Cached {allowedTypes.Count} TypeCompleter types in {cacheTypesStopwatch.ElapsedMilliseconds * 0.001f} seconds.");
|
||||
|
||||
if (pendingInput != null)
|
||||
{
|
||||
GetSuggestions(pendingInput);
|
||||
pendingInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnInputFieldChanged(string input)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrEmpty(value) || value == chosenSuggestion)
|
||||
{
|
||||
if (input != chosenSuggestion)
|
||||
chosenSuggestion = null;
|
||||
|
||||
if (string.IsNullOrEmpty(input) || input == chosenSuggestion)
|
||||
{
|
||||
if (getSuggestionsCoroutine != null)
|
||||
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSuggestions(value);
|
||||
|
||||
AutoCompleteModal.TakeOwnership(this);
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions);
|
||||
GetSuggestions(input);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSuggestions(string value)
|
||||
void GetSuggestions(string input)
|
||||
{
|
||||
suggestions.Clear();
|
||||
suggestedNames.Clear();
|
||||
|
||||
if (BaseType == null)
|
||||
if (allowedTypes == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Autocompleter Base type is null!");
|
||||
if (pendingInput != null)
|
||||
{
|
||||
AutoCompleteModal.TakeOwnership(this);
|
||||
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true);
|
||||
}
|
||||
|
||||
pendingInput = input;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSuggestionsCoroutine != null)
|
||||
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
|
||||
|
||||
getSuggestionsCoroutine = RuntimeHelper.StartCoroutine(GetSuggestionsAsync(input));
|
||||
}
|
||||
|
||||
IEnumerator GetSuggestionsAsync(string input)
|
||||
{
|
||||
suggestions.Clear();
|
||||
suggestedTypes.Clear();
|
||||
|
||||
AutoCompleteModal.TakeOwnership(this);
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
|
||||
|
||||
// shorthand types all inherit from System.Object
|
||||
if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand))
|
||||
AddSuggestion(shorthand);
|
||||
|
||||
foreach (KeyValuePair<string, Type> entry in shorthandToType)
|
||||
{
|
||||
if (allowedTypes.Contains(entry.Value) && entry.Key.StartsWith(input, StringComparison.InvariantCultureIgnoreCase))
|
||||
AddSuggestion(entry.Value);
|
||||
}
|
||||
|
||||
// Check for exact match first
|
||||
if (ReflectionUtility.GetTypeByName(value) is Type t && allowedTypes.Contains(t))
|
||||
if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(t))
|
||||
AddSuggestion(t);
|
||||
|
||||
if (!suggestions.Any())
|
||||
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, false);
|
||||
else
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
|
||||
|
||||
Stopwatch sw = new();
|
||||
sw.Start();
|
||||
|
||||
// ExplorerCore.Log($"Checking {allowedTypes.Count} types...");
|
||||
|
||||
foreach (Type entry in allowedTypes)
|
||||
{
|
||||
if (entry.FullName.ContainsIgnoreCase(value))
|
||||
if (AutoCompleteModal.CurrentHandler == null)
|
||||
yield break;
|
||||
|
||||
if (sw.ElapsedMilliseconds > 10)
|
||||
{
|
||||
yield return null;
|
||||
if (suggestions.Any())
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
|
||||
|
||||
sw.Reset();
|
||||
sw.Start();
|
||||
}
|
||||
|
||||
if (entry.FullName.ContainsIgnoreCase(input))
|
||||
AddSuggestion(entry);
|
||||
}
|
||||
|
||||
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
|
||||
|
||||
// ExplorerCore.Log($"Fetched {suggestions.Count} TypeCompleter suggestions in {sw.ElapsedMilliseconds * 0.001f} seconds.");
|
||||
}
|
||||
|
||||
internal static readonly Dictionary<string, string> sharedTypeToLabel = new();
|
||||
|
||||
void AddSuggestion(Type type)
|
||||
{
|
||||
if (suggestedNames.Contains(type.FullName))
|
||||
if (suggestedTypes.Contains(type.FullName))
|
||||
return;
|
||||
suggestedNames.Add(type.FullName);
|
||||
suggestedTypes.Add(type.FullName);
|
||||
|
||||
if (!sharedTypeToLabel.ContainsKey(type.FullName))
|
||||
sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true));
|
||||
|
@ -9,8 +9,6 @@ namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public abstract class BaseArgumentHandler : IPooledObject
|
||||
{
|
||||
protected EvaluateWidget evaluator;
|
||||
|
||||
internal Text argNameLabel;
|
||||
internal InputFieldRef inputField;
|
||||
internal TypeCompleter typeCompleter;
|
||||
|
@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
GenericArgumentHandler holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow();
|
||||
holder.UIRoot.transform.SetParent(this.genericArgumentsHolder.transform, false);
|
||||
holder.OnBorrowed(this, type);
|
||||
holder.OnBorrowed(type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
ParameterHandler holder = paramHandlers[i] = Pool<ParameterHandler>.Borrow();
|
||||
holder.UIRoot.transform.SetParent(this.parametersHolder.transform, false);
|
||||
holder.OnBorrowed(this, param);
|
||||
holder.OnBorrowed(param);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,30 +7,28 @@ namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class GenericArgumentHandler : BaseArgumentHandler
|
||||
{
|
||||
private Type genericType;
|
||||
private Type genericArgument;
|
||||
|
||||
public void OnBorrowed(EvaluateWidget evaluator, Type genericConstraint)
|
||||
public void OnBorrowed(Type genericArgument)
|
||||
{
|
||||
this.evaluator = evaluator;
|
||||
this.genericType = genericConstraint;
|
||||
this.genericArgument = genericArgument;
|
||||
|
||||
typeCompleter.Enabled = true;
|
||||
typeCompleter.BaseType = genericType;
|
||||
typeCompleter.BaseType = this.genericArgument;
|
||||
typeCompleter.CacheTypes();
|
||||
|
||||
Type[] constraints = genericType.GetGenericParameterConstraints();
|
||||
typeCompleter.GenericConstraints = constraints;
|
||||
Type[] constraints = this.genericArgument.GetGenericParameterConstraints();
|
||||
|
||||
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{genericType.Name}</color>");
|
||||
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{this.genericArgument.Name}</color>");
|
||||
|
||||
for (int j = 0; j < constraints.Length; j++)
|
||||
for (int i = 0; i < constraints.Length; i++)
|
||||
{
|
||||
if (j == 0) sb.Append(' ').Append('(');
|
||||
if (i == 0) sb.Append(' ').Append('(');
|
||||
else sb.Append(',').Append(' ');
|
||||
|
||||
sb.Append(SignatureHighlighter.Parse(constraints[j], false));
|
||||
sb.Append(SignatureHighlighter.Parse(constraints[i], false));
|
||||
|
||||
if (j + 1 == constraints.Length)
|
||||
if (i + 1 == constraints.Length)
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
@ -39,8 +37,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
public void OnReturned()
|
||||
{
|
||||
this.evaluator = null;
|
||||
this.genericType = null;
|
||||
this.genericArgument = null;
|
||||
|
||||
this.typeCompleter.Enabled = false;
|
||||
|
||||
|
134
src/UI/Widgets/EvaluateWidget/GenericConstructorWidget.cs
Normal file
134
src/UI/Widgets/EvaluateWidget/GenericConstructorWidget.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class GenericConstructorWidget
|
||||
{
|
||||
GenericArgumentHandler[] handlers;
|
||||
|
||||
Type[] currentGenericParameters;
|
||||
Action<Type[]> currentOnSubmit;
|
||||
Action currentOnCancel;
|
||||
|
||||
public GameObject UIRoot;
|
||||
Text Title;
|
||||
GameObject ArgsHolder;
|
||||
|
||||
public void Show(Action<Type[]> onSubmit, Action onCancel, Type genericTypeDefinition)
|
||||
{
|
||||
Title.text = $"Setting generic arguments for {SignatureHighlighter.Parse(genericTypeDefinition, false)}...";
|
||||
|
||||
OnShow(onSubmit, onCancel, genericTypeDefinition.GetGenericArguments());
|
||||
}
|
||||
|
||||
public void Show(Action<Type[]> onSubmit, Action onCancel, MethodInfo genericMethodDefinition)
|
||||
{
|
||||
Title.text = $"Setting generic arguments for {SignatureHighlighter.ParseMethod(genericMethodDefinition)}...";
|
||||
|
||||
OnShow(onSubmit, onCancel, genericMethodDefinition.GetGenericArguments());
|
||||
}
|
||||
|
||||
void OnShow(Action<Type[]> onSubmit, Action onCancel, Type[] genericParameters)
|
||||
{
|
||||
currentOnSubmit = onSubmit;
|
||||
currentOnCancel = onCancel;
|
||||
|
||||
SetGenericParameters(genericParameters);
|
||||
}
|
||||
|
||||
void SetGenericParameters(Type[] genericParameters)
|
||||
{
|
||||
currentGenericParameters = genericParameters;
|
||||
|
||||
handlers = new GenericArgumentHandler[genericParameters.Length];
|
||||
for (int i = 0; i < genericParameters.Length; i++)
|
||||
{
|
||||
Type type = genericParameters[i];
|
||||
|
||||
GenericArgumentHandler holder = handlers[i] = Pool<GenericArgumentHandler>.Borrow();
|
||||
holder.UIRoot.transform.SetParent(ArgsHolder.transform, false);
|
||||
holder.OnBorrowed(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void TrySubmit()
|
||||
{
|
||||
Type[] args = new Type[currentGenericParameters.Length];
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
GenericArgumentHandler handler = handlers[i];
|
||||
Type arg;
|
||||
try
|
||||
{
|
||||
arg = handler.Evaluate();
|
||||
if (arg == null) throw new Exception();
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.LogWarning($"Generic argument '{handler.inputField.Text}' is not a valid type.");
|
||||
return;
|
||||
}
|
||||
args[i] = arg;
|
||||
}
|
||||
|
||||
OnClose();
|
||||
currentOnSubmit(args);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
OnClose();
|
||||
|
||||
currentOnCancel?.Invoke();
|
||||
}
|
||||
|
||||
void OnClose()
|
||||
{
|
||||
if (handlers != null)
|
||||
{
|
||||
foreach (GenericArgumentHandler widget in handlers)
|
||||
{
|
||||
widget.OnReturned();
|
||||
Pool<GenericArgumentHandler>.Return(widget);
|
||||
}
|
||||
handlers = null;
|
||||
}
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
internal void ConstructUI(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "GenericArgsHandler", false, false, true, true, 5, new Vector4(5, 5, 5, 5),
|
||||
childAlignment: TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
ButtonRef submitButton = UIFactory.CreateButton(UIRoot, "SubmitButton", "Submit", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(submitButton.GameObject, minHeight: 25, minWidth: 200);
|
||||
submitButton.OnClick += TrySubmit;
|
||||
|
||||
ButtonRef cancelButton = UIFactory.CreateButton(UIRoot, "CancelButton", "Cancel", new Color(0.3f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(cancelButton.GameObject, minHeight: 25, minWidth: 200);
|
||||
cancelButton.OnClick += Cancel;
|
||||
|
||||
Title = UIFactory.CreateLabel(UIRoot, "Title", "Generic Arguments", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(Title.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject scrollview = UIFactory.CreateScrollView(UIRoot, "GenericArgsScrollView", out ArgsHolder, out _, new(0.1f, 0.1f, 0.1f));
|
||||
UIFactory.SetLayoutElement(scrollview, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ArgsHolder, padTop: 5, padLeft: 5, padBottom: 5, padRight: 5);
|
||||
}
|
||||
}
|
||||
}
|
@ -26,9 +26,8 @@ namespace UnityExplorer.UI.Widgets
|
||||
private Text basicLabel;
|
||||
private ButtonRef pasteButton;
|
||||
|
||||
public void OnBorrowed(EvaluateWidget evaluator, ParameterInfo paramInfo)
|
||||
public void OnBorrowed(ParameterInfo paramInfo)
|
||||
{
|
||||
this.evaluator = evaluator;
|
||||
this.paramInfo = paramInfo;
|
||||
|
||||
this.paramType = paramInfo.ParameterType;
|
||||
@ -85,7 +84,6 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
public void OnReturned()
|
||||
{
|
||||
this.evaluator = null;
|
||||
this.paramInfo = null;
|
||||
|
||||
this.enumCompleter.Enabled = false;
|
||||
|
@ -1,55 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class CachedTransform
|
||||
{
|
||||
public TransformTree Tree { get; }
|
||||
public Transform Value { get; private set; }
|
||||
public int InstanceID { get; }
|
||||
public CachedTransform Parent { get; internal set; }
|
||||
|
||||
public int Depth { get; internal set; }
|
||||
public int ChildCount { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public bool Enabled { get; internal set; }
|
||||
public int SiblingIndex { get; internal set; }
|
||||
|
||||
public bool Expanded => Tree.IsTransformExpanded(InstanceID);
|
||||
|
||||
public CachedTransform(TransformTree tree, Transform transform, int depth, CachedTransform parent = null)
|
||||
{
|
||||
InstanceID = transform.GetInstanceID();
|
||||
|
||||
Tree = tree;
|
||||
Value = transform;
|
||||
Parent = parent;
|
||||
SiblingIndex = transform.GetSiblingIndex();
|
||||
Update(transform, depth);
|
||||
}
|
||||
|
||||
public bool Update(Transform transform, int depth)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (Value != transform
|
||||
|| depth != Depth
|
||||
|| ChildCount != transform.childCount
|
||||
|| Name != transform.name
|
||||
|| Enabled != transform.gameObject.activeSelf
|
||||
|| SiblingIndex != transform.GetSiblingIndex())
|
||||
{
|
||||
changed = true;
|
||||
|
||||
Value = transform;
|
||||
Depth = depth;
|
||||
ChildCount = transform.childCount;
|
||||
Name = transform.name;
|
||||
Enabled = transform.gameObject.activeSelf;
|
||||
SiblingIndex = transform.GetSiblingIndex();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class TransformCell : ICell
|
||||
{
|
||||
public float DefaultHeight => 25f;
|
||||
|
||||
public bool Enabled => enabled;
|
||||
private bool enabled;
|
||||
|
||||
public Action<CachedTransform> OnExpandToggled;
|
||||
public Action<CachedTransform> OnEnableToggled;
|
||||
public Action<GameObject> OnGameObjectClicked;
|
||||
|
||||
public CachedTransform cachedTransform;
|
||||
|
||||
public GameObject UIRoot { get; set; }
|
||||
public RectTransform Rect { get; set; }
|
||||
|
||||
public ButtonRef ExpandButton;
|
||||
public ButtonRef NameButton;
|
||||
public Toggle EnabledToggle;
|
||||
public InputFieldRef SiblingIndex;
|
||||
|
||||
public LayoutElement spacer;
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
enabled = true;
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
enabled = false;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void ConfigureCell(CachedTransform cached)
|
||||
{
|
||||
if (cached == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Setting TransformTree cell but the CachedTransform is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enabled)
|
||||
Enable();
|
||||
|
||||
cachedTransform = cached;
|
||||
|
||||
spacer.minWidth = cached.Depth * 15;
|
||||
|
||||
if (cached.Value)
|
||||
{
|
||||
string name = cached.Value.name?.Trim();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = "<i><color=grey>untitled</color></i>";
|
||||
NameButton.ButtonText.text = name;
|
||||
NameButton.ButtonText.color = cached.Value.gameObject.activeSelf ? Color.white : Color.grey;
|
||||
|
||||
EnabledToggle.Set(cached.Value.gameObject.activeSelf, false);
|
||||
|
||||
if (!cached.Value.parent)
|
||||
SiblingIndex.GameObject.SetActive(false);
|
||||
else
|
||||
{
|
||||
SiblingIndex.GameObject.SetActive(true);
|
||||
if (!SiblingIndex.Component.isFocused)
|
||||
SiblingIndex.Text = cached.Value.GetSiblingIndex().ToString();
|
||||
}
|
||||
|
||||
int childCount = cached.Value.childCount;
|
||||
if (childCount > 0)
|
||||
{
|
||||
NameButton.ButtonText.text = $"<color=grey>[{childCount}]</color> {NameButton.ButtonText.text}";
|
||||
|
||||
ExpandButton.Component.interactable = true;
|
||||
ExpandButton.ButtonText.text = cached.Expanded ? "▼" : "►";
|
||||
ExpandButton.ButtonText.color = cached.Expanded ? new Color(0.5f, 0.5f, 0.5f) : new Color(0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandButton.Component.interactable = false;
|
||||
ExpandButton.ButtonText.text = "▪";
|
||||
ExpandButton.ButtonText.color = new Color(0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NameButton.ButtonText.text = $"[Destroyed]";
|
||||
NameButton.ButtonText.color = Color.red;
|
||||
|
||||
SiblingIndex.GameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMainButtonClicked()
|
||||
{
|
||||
if (cachedTransform.Value)
|
||||
OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject);
|
||||
else
|
||||
ExplorerCore.LogWarning("The object was destroyed!");
|
||||
}
|
||||
|
||||
public void OnExpandClicked()
|
||||
{
|
||||
OnExpandToggled?.Invoke(cachedTransform);
|
||||
}
|
||||
|
||||
private void OnEnableClicked(bool value)
|
||||
{
|
||||
OnEnableToggled?.Invoke(cachedTransform);
|
||||
}
|
||||
|
||||
private void OnSiblingIndexEndEdit(string input)
|
||||
{
|
||||
if (this.cachedTransform == null || !this.cachedTransform.Value)
|
||||
return;
|
||||
|
||||
if (int.TryParse(input.Trim(), out int index))
|
||||
this.cachedTransform.Value.SetSiblingIndex(index);
|
||||
|
||||
this.SiblingIndex.Text = this.cachedTransform.Value.GetSiblingIndex().ToString();
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("TransformCell", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 2, childAlignment: TextAnchor.MiddleCenter);
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(25, 25);
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
GameObject spacerObj = UIFactory.CreateUIObject("Spacer", UIRoot, new Vector2(0, 0));
|
||||
UIFactory.SetLayoutElement(spacerObj, minWidth: 0, flexibleWidth: 0, minHeight: 0, flexibleHeight: 0);
|
||||
this.spacer = spacerObj.GetComponent<LayoutElement>();
|
||||
|
||||
// Expand arrow
|
||||
|
||||
ExpandButton = UIFactory.CreateButton(this.UIRoot, "ExpandButton", "►");
|
||||
UIFactory.SetLayoutElement(ExpandButton.Component.gameObject, minWidth: 15, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
// Enabled toggle
|
||||
|
||||
GameObject toggleObj = UIFactory.CreateToggle(UIRoot, "BehaviourToggle", out EnabledToggle, out Text behavText, default, 17, 17);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 17, flexibleHeight: 0, minWidth: 17);
|
||||
EnabledToggle.onValueChanged.AddListener(OnEnableClicked);
|
||||
|
||||
// Name button
|
||||
|
||||
GameObject nameBtnHolder = UIFactory.CreateHorizontalGroup(this.UIRoot, "NameButtonHolder",
|
||||
false, false, true, true, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(nameBtnHolder, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
nameBtnHolder.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
NameButton = UIFactory.CreateButton(nameBtnHolder, "NameButton", "Name", null);
|
||||
UIFactory.SetLayoutElement(NameButton.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
Text nameLabel = NameButton.Component.GetComponentInChildren<Text>();
|
||||
nameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
nameLabel.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
// Sibling index input
|
||||
|
||||
SiblingIndex = UIFactory.CreateInputField(this.UIRoot, "SiblingIndexInput", string.Empty);
|
||||
SiblingIndex.Component.textComponent.fontSize = 11;
|
||||
SiblingIndex.Component.textComponent.alignment = TextAnchor.MiddleRight;
|
||||
Image siblingImage = SiblingIndex.GameObject.GetComponent<Image>();
|
||||
siblingImage.color = new(0f, 0f, 0f, 0.25f);
|
||||
UIFactory.SetLayoutElement(SiblingIndex.GameObject, 35, 20, 0, 0);
|
||||
SiblingIndex.Component.GetOnEndEdit().AddListener(OnSiblingIndexEndEdit);
|
||||
|
||||
// Setup selectables
|
||||
|
||||
Color normal = new(0.11f, 0.11f, 0.11f);
|
||||
Color highlight = new(0.25f, 0.25f, 0.25f);
|
||||
Color pressed = new(0.05f, 0.05f, 0.05f);
|
||||
Color disabled = new(1, 1, 1, 0);
|
||||
RuntimeHelper.SetColorBlock(ExpandButton.Component, normal, highlight, pressed, disabled);
|
||||
RuntimeHelper.SetColorBlock(NameButton.Component, normal, highlight, pressed, disabled);
|
||||
|
||||
NameButton.OnClick += OnMainButtonClicked;
|
||||
ExpandButton.OnClick += OnExpandClicked;
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
|
||||
return this.UIRoot;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,369 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class TransformTree : ICellPoolDataSource<TransformCell>
|
||||
{
|
||||
public Func<IEnumerable<GameObject>> GetRootEntriesMethod;
|
||||
public Action<GameObject> OnClickOverrideHandler;
|
||||
|
||||
public ScrollPool<TransformCell> ScrollPool;
|
||||
|
||||
// IMPORTANT CAVEAT WITH OrderedDictionary:
|
||||
// While the performance is mostly good, there are two methods we should NEVER use:
|
||||
// - Remove(object)
|
||||
// - set_Item[object]
|
||||
// These two methods have extremely bad performance due to using IndexOfKey(), which iterates the whole dictionary.
|
||||
// Currently we do not use either of these methods, so everything should be constant time lookups.
|
||||
// We DO make use of get_Item[object], get_Item[index], Add, Insert, Contains and RemoveAt, which OrderedDictionary meets our needs for.
|
||||
/// <summary>
|
||||
/// Key: UnityEngine.Transform instance ID<br/>
|
||||
/// Value: CachedTransform
|
||||
/// </summary>
|
||||
internal readonly OrderedDictionary cachedTransforms = new();
|
||||
|
||||
// for keeping track of which actual transforms are expanded or not, outside of the cache data.
|
||||
private readonly HashSet<int> expandedInstanceIDs = new();
|
||||
private readonly HashSet<int> autoExpandedIDs = new();
|
||||
|
||||
// state for Traverse parse
|
||||
private readonly HashSet<int> visited = new();
|
||||
private bool needRefreshUI;
|
||||
private int displayIndex;
|
||||
int prevDisplayIndex;
|
||||
|
||||
private Coroutine refreshCoroutine;
|
||||
private readonly Stopwatch traversedThisFrame = new();
|
||||
|
||||
// ScrollPool item count. PrevDisplayIndex is the highest index + 1 from our last traverse.
|
||||
public int ItemCount => prevDisplayIndex;
|
||||
|
||||
// Search filter
|
||||
public bool Filtering => !string.IsNullOrEmpty(currentFilter);
|
||||
private bool wasFiltering;
|
||||
|
||||
public string CurrentFilter
|
||||
{
|
||||
get => currentFilter;
|
||||
set
|
||||
{
|
||||
currentFilter = value ?? "";
|
||||
if (!wasFiltering && Filtering)
|
||||
wasFiltering = true;
|
||||
else if (wasFiltering && !Filtering)
|
||||
{
|
||||
wasFiltering = false;
|
||||
autoExpandedIDs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string currentFilter;
|
||||
|
||||
public TransformTree(ScrollPool<TransformCell> scrollPool, Func<IEnumerable<GameObject>> getRootEntriesMethod)
|
||||
{
|
||||
ScrollPool = scrollPool;
|
||||
GetRootEntriesMethod = getRootEntriesMethod;
|
||||
}
|
||||
|
||||
// Initialize and reset
|
||||
|
||||
// Must be called externally from owner of this TransformTree
|
||||
public void Init()
|
||||
{
|
||||
ScrollPool.Initialize(this);
|
||||
}
|
||||
|
||||
// Called to completely reset the tree, ie. switching inspected GameObject
|
||||
public void Rebuild()
|
||||
{
|
||||
autoExpandedIDs.Clear();
|
||||
expandedInstanceIDs.Clear();
|
||||
|
||||
RefreshData(true, true, true, false);
|
||||
}
|
||||
|
||||
// Called to completely wipe our data (ie, GameObject inspector returning to pool)
|
||||
public void Clear()
|
||||
{
|
||||
this.cachedTransforms.Clear();
|
||||
displayIndex = 0;
|
||||
autoExpandedIDs.Clear();
|
||||
expandedInstanceIDs.Clear();
|
||||
this.ScrollPool.Refresh(true, true);
|
||||
if (refreshCoroutine != null)
|
||||
{
|
||||
RuntimeHelper.StopCoroutine(refreshCoroutine);
|
||||
refreshCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the given Instance ID is expanded or not
|
||||
public bool IsTransformExpanded(int instanceID)
|
||||
{
|
||||
return Filtering ? autoExpandedIDs.Contains(instanceID)
|
||||
: expandedInstanceIDs.Contains(instanceID);
|
||||
}
|
||||
|
||||
// Jumps to a specific Transform in the tree and highlights it.
|
||||
public void JumpAndExpandToTransform(Transform transform)
|
||||
{
|
||||
// make sure all parents of the object are expanded
|
||||
Transform parent = transform.parent;
|
||||
while (parent)
|
||||
{
|
||||
int pid = parent.GetInstanceID();
|
||||
if (!expandedInstanceIDs.Contains(pid))
|
||||
expandedInstanceIDs.Add(pid);
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
// Refresh cached transforms (no UI rebuild yet).
|
||||
// Stop existing coroutine and do it oneshot.
|
||||
RefreshData(false, false, true, true);
|
||||
|
||||
int transformID = transform.GetInstanceID();
|
||||
|
||||
// find the index of our transform in the list and jump to it
|
||||
int idx;
|
||||
for (idx = 0; idx < cachedTransforms.Count; idx++)
|
||||
{
|
||||
CachedTransform cache = (CachedTransform)cachedTransforms[idx];
|
||||
if (cache.InstanceID == transformID)
|
||||
break;
|
||||
}
|
||||
|
||||
ScrollPool.JumpToIndex(idx, OnCellJumpedTo);
|
||||
}
|
||||
|
||||
private void OnCellJumpedTo(TransformCell cell)
|
||||
{
|
||||
RuntimeHelper.StartCoroutine(HighlightCellCoroutine(cell));
|
||||
}
|
||||
|
||||
private IEnumerator HighlightCellCoroutine(TransformCell cell)
|
||||
{
|
||||
UnityEngine.UI.Button button = cell.NameButton.Component;
|
||||
button.StartColorTween(new Color(0.2f, 0.3f, 0.2f), false);
|
||||
|
||||
float start = Time.realtimeSinceStartup;
|
||||
while (Time.realtimeSinceStartup - start < 1.5f)
|
||||
yield return null;
|
||||
|
||||
button.OnDeselect(null);
|
||||
}
|
||||
|
||||
// Perform a Traverse and optionally refresh the ScrollPool as well.
|
||||
// If oneShot, then this happens instantly with no yield.
|
||||
public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoroutine, bool oneShot)
|
||||
{
|
||||
if (refreshCoroutine != null)
|
||||
{
|
||||
if (stopExistingCoroutine)
|
||||
{
|
||||
RuntimeHelper.StopCoroutine(refreshCoroutine);
|
||||
refreshCoroutine = null;
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
visited.Clear();
|
||||
displayIndex = 0;
|
||||
needRefreshUI = false;
|
||||
traversedThisFrame.Reset();
|
||||
traversedThisFrame.Start();
|
||||
|
||||
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(andRefreshUI, jumpToTop, oneShot));
|
||||
}
|
||||
|
||||
IEnumerator RefreshCoroutine(bool andRefreshUI, bool jumpToTop, bool oneShot)
|
||||
{
|
||||
// Instead of doing string.IsNullOrEmpty(CurrentFilter) many times, let's just do it once per update.
|
||||
bool filtering = Filtering;
|
||||
|
||||
IEnumerable<GameObject> rootObjects = GetRootEntriesMethod();
|
||||
foreach (GameObject gameObj in rootObjects)
|
||||
{
|
||||
if (!gameObj)
|
||||
continue;
|
||||
|
||||
IEnumerator enumerator = Traverse(gameObj.transform, null, 0, oneShot, filtering);
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (!oneShot)
|
||||
yield return enumerator.Current;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune displayed transforms that we didnt visit in that traverse
|
||||
for (int i = cachedTransforms.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (traversedThisFrame.ElapsedMilliseconds > 2)
|
||||
{
|
||||
yield return null;
|
||||
traversedThisFrame.Reset();
|
||||
traversedThisFrame.Start();
|
||||
}
|
||||
|
||||
CachedTransform cached = (CachedTransform)cachedTransforms[i];
|
||||
if (!visited.Contains(cached.InstanceID))
|
||||
{
|
||||
cachedTransforms.RemoveAt(i);
|
||||
needRefreshUI = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (andRefreshUI && needRefreshUI)
|
||||
ScrollPool.Refresh(true, jumpToTop);
|
||||
|
||||
prevDisplayIndex = displayIndex;
|
||||
refreshCoroutine = null;
|
||||
}
|
||||
|
||||
// Recursive method to check a Transform and its children (if expanded).
|
||||
// Parent and depth can be null/default.
|
||||
private IEnumerator Traverse(Transform transform, CachedTransform parent, int depth, bool oneShot, bool filtering)
|
||||
{
|
||||
if (traversedThisFrame.ElapsedMilliseconds > 2)
|
||||
{
|
||||
yield return null;
|
||||
traversedThisFrame.Reset();
|
||||
traversedThisFrame.Start();
|
||||
}
|
||||
|
||||
int instanceID = transform.GetInstanceID();
|
||||
|
||||
// Unlikely, but since this method is async it could theoretically happen in extremely rare circumstances
|
||||
if (visited.Contains(instanceID))
|
||||
yield break;
|
||||
|
||||
if (filtering)
|
||||
{
|
||||
if (!FilterHierarchy(transform))
|
||||
yield break;
|
||||
|
||||
if (!autoExpandedIDs.Contains(instanceID))
|
||||
autoExpandedIDs.Add(instanceID);
|
||||
}
|
||||
|
||||
visited.Add(instanceID);
|
||||
|
||||
CachedTransform cached;
|
||||
if (cachedTransforms.Contains(instanceID))
|
||||
{
|
||||
cached = (CachedTransform)cachedTransforms[(object)instanceID];
|
||||
int prevSiblingIdx = cached.SiblingIndex;
|
||||
if (cached.Update(transform, depth))
|
||||
{
|
||||
needRefreshUI = true;
|
||||
|
||||
// If the sibling index changed, we need to shuffle it in our cached transforms list.
|
||||
if (prevSiblingIdx != cached.SiblingIndex)
|
||||
{
|
||||
cachedTransforms.Remove(instanceID);
|
||||
if (cachedTransforms.Count <= displayIndex)
|
||||
cachedTransforms.Add(instanceID, cached);
|
||||
else
|
||||
cachedTransforms.Insert(displayIndex, instanceID, cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
needRefreshUI = true;
|
||||
cached = new CachedTransform(this, transform, depth, parent);
|
||||
if (cachedTransforms.Count <= displayIndex)
|
||||
cachedTransforms.Add(instanceID, cached);
|
||||
else
|
||||
cachedTransforms.Insert(displayIndex, instanceID, cached);
|
||||
}
|
||||
|
||||
displayIndex++;
|
||||
|
||||
if (IsTransformExpanded(instanceID) && cached.Value.childCount > 0)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
IEnumerator enumerator = Traverse(transform.GetChild(i), cached, depth + 1, oneShot, filtering);
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (!oneShot)
|
||||
yield return enumerator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterHierarchy(Transform obj)
|
||||
{
|
||||
if (obj.name.ContainsIgnoreCase(currentFilter))
|
||||
return true;
|
||||
|
||||
if (obj.childCount <= 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < obj.childCount; i++)
|
||||
if (FilterHierarchy(obj.GetChild(i)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetCell(TransformCell cell, int index)
|
||||
{
|
||||
if (index < cachedTransforms.Count)
|
||||
{
|
||||
cell.ConfigureCell((CachedTransform)cachedTransforms[index]);
|
||||
if (Filtering)
|
||||
{
|
||||
if (cell.cachedTransform.Name.ContainsIgnoreCase(currentFilter))
|
||||
cell.NameButton.ButtonText.color = Color.green;
|
||||
}
|
||||
}
|
||||
else
|
||||
cell.Disable();
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(TransformCell cell)
|
||||
{
|
||||
cell.OnExpandToggled += OnCellExpandToggled;
|
||||
cell.OnGameObjectClicked += OnGameObjectClicked;
|
||||
cell.OnEnableToggled += OnCellEnableToggled;
|
||||
}
|
||||
|
||||
private void OnGameObjectClicked(GameObject obj)
|
||||
{
|
||||
if (OnClickOverrideHandler != null)
|
||||
OnClickOverrideHandler.Invoke(obj);
|
||||
else
|
||||
InspectorManager.Inspect(obj);
|
||||
}
|
||||
|
||||
public void OnCellExpandToggled(CachedTransform cache)
|
||||
{
|
||||
int instanceID = cache.InstanceID;
|
||||
if (expandedInstanceIDs.Contains(instanceID))
|
||||
expandedInstanceIDs.Remove(instanceID);
|
||||
else
|
||||
expandedInstanceIDs.Add(instanceID);
|
||||
|
||||
RefreshData(true, false, true, true);
|
||||
}
|
||||
|
||||
public void OnCellEnableToggled(CachedTransform cache)
|
||||
{
|
||||
cache.Value.gameObject.SetActive(!cache.Value.gameObject.activeSelf);
|
||||
|
||||
RefreshData(true, false, true, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
textureViewerRoot.SetActive(false);
|
||||
toggleButton.ButtonText.text = "View Texture";
|
||||
|
||||
ParentInspector.mainContentHolder.SetActive(true);
|
||||
ParentInspector.ContentRoot.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -85,7 +85,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
textureViewerRoot.SetActive(true);
|
||||
toggleButton.ButtonText.text = "Hide Texture";
|
||||
|
||||
ParentInspector.mainContentHolder.gameObject.SetActive(false);
|
||||
ParentInspector.ContentRoot.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
if (this.UnityObjectRef)
|
||||
{
|
||||
nameInput.Text = UnityObjectRef.name;
|
||||
ParentInspector.Tab.TabText.text = $"{ParentInspector.currentBaseTabText} \"{UnityObjectRef.name}\"";
|
||||
ParentInspector.Tab.TabText.text = $"{ParentInspector.TabButtonText} \"{UnityObjectRef.name}\"";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,11 +79,11 @@
|
||||
<!-- il2cpp nuget -->
|
||||
<ItemGroup Condition="'$(Configuration)'=='ML_Cpp_net6' or '$(Configuration)'=='ML_Cpp_net472' or '$(Configuration)'=='STANDALONE_Cpp' or '$(Configuration)'=='BIE_Cpp'">
|
||||
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" IncludeAssets="compile" />
|
||||
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.3" />
|
||||
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.12" />
|
||||
</ItemGroup>
|
||||
<!-- mono nuget -->
|
||||
<ItemGroup Condition="'$(Configuration)'=='BIE6_Mono' or '$(Configuration)'=='BIE5_Mono' or '$(Configuration)'=='ML_Mono' or '$(Configuration)'=='STANDALONE_Mono'">
|
||||
<PackageReference Include="UniverseLib.Mono" Version="1.3.3" />
|
||||
<PackageReference Include="UniverseLib.Mono" Version="1.3.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ -->
|
||||
|
Reference in New Issue
Block a user