mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-04 04:22:53 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
92447b55cd | |||
87d5d5a2de | |||
08cff3386b | |||
6033200579 | |||
94ec1c4908 | |||
7a400e762c | |||
67f9f744bb | |||
7a59f9a2a1 | |||
9b42eef1b9 | |||
830000b019 | |||
34910ab273 | |||
86b036095e | |||
b57e5be2e6 | |||
2d8ae45814 | |||
66dc262a68 | |||
4342901206 | |||
58b7c72a5c | |||
623dc7b7be | |||
e6f4939cc9 |
@ -6,11 +6,14 @@
|
||||
🔍 An in-game UI for exploring, debugging and modifying Unity games.
|
||||
</p>
|
||||
<p align="center">
|
||||
✔️ Supports most Unity versions from 5.2 to 2020+ (IL2CPP and Mono).
|
||||
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
|
||||
</p>
|
||||
<p align="center">
|
||||
☕ Enjoy this tool? Consider supporting me on <a href="https://ko-fi.com/sinaidev">ko-fi</a>!
|
||||
</p>
|
||||
<p align="center">
|
||||
⚡ UnityExplorer is on <a href="https://thunderstore.io/package/sinai-dev/UnityExplorer/">Thunderstore</a>! (and as <a href="https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP/">IL2CPP</a>)
|
||||
</p>
|
||||
|
||||
# Releases [](../../releases)
|
||||
|
||||
@ -88,6 +91,7 @@ The inspector is used to see detailed information on objects of any type and man
|
||||
### C# Console
|
||||
|
||||
* The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
|
||||
* You can execute a script automatically on startup by naming it `startup.cs` and placing it in the `UnityExplorer\Scripts\` folder (this folder will be created where you placed the DLL file).
|
||||
* See the "Help" dropdown in the C# console menu for more detailed information.
|
||||
|
||||
### Mouse-Inspect
|
||||
@ -115,7 +119,7 @@ For Visual Studio:
|
||||
|
||||
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
|
||||
1. Open the `src\UnityExplorer.sln` project.
|
||||
2. Build `mcs`, and if using IL2CPP then build `UnhollowerBaseLib` as well.
|
||||
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`), and if using IL2CPP then build `Il2CppAssemblyUnhollower` (Release/AnyCPU) as well.
|
||||
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
|
||||
|
||||
# Acknowledgments
|
||||
|
@ -99,8 +99,10 @@ namespace UnityExplorer
|
||||
|
||||
public static Exception GetInnerMostException(this Exception e)
|
||||
{
|
||||
while (e.InnerException != null)
|
||||
while (e != null)
|
||||
{
|
||||
if (e.InnerException == null)
|
||||
break;
|
||||
#if CPP
|
||||
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
|
||||
break;
|
||||
|
@ -132,7 +132,6 @@ namespace UnityExplorer
|
||||
return null;
|
||||
|
||||
var type = obj.GetType();
|
||||
|
||||
try
|
||||
{
|
||||
if (IsString(obj))
|
||||
@ -216,7 +215,7 @@ namespace UnityExplorer
|
||||
// from other structs to il2cpp object
|
||||
else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
|
||||
{
|
||||
return BoxIl2CppObject(obj);
|
||||
return BoxIl2CppObject(obj).TryCast(castTo);
|
||||
}
|
||||
else
|
||||
return obj;
|
||||
@ -295,7 +294,27 @@ namespace UnityExplorer
|
||||
try
|
||||
{
|
||||
if (toType.IsEnum)
|
||||
{
|
||||
// Check for nullable enums
|
||||
var type = cppObj.GetType();
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Il2CppSystem.Nullable<>))
|
||||
{
|
||||
var nullable = cppObj.TryCast(type);
|
||||
var nullableHasValueProperty = type.GetProperty("HasValue");
|
||||
if ((bool)nullableHasValueProperty.GetValue(nullable, null))
|
||||
{
|
||||
// nullable has a value.
|
||||
var nullableValueProperty = type.GetProperty("Value");
|
||||
return Enum.Parse(toType, nullableValueProperty.GetValue(nullable, null).ToString());
|
||||
}
|
||||
// nullable and no current value.
|
||||
return cppObj;
|
||||
}
|
||||
|
||||
return Enum.Parse(toType, cppObj.ToString());
|
||||
}
|
||||
|
||||
// Not enum, unbox with Il2CppObjectBase.Unbox
|
||||
|
||||
var name = toType.AssemblyQualifiedName;
|
||||
|
||||
|
@ -87,6 +87,7 @@ namespace UnityExplorer
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
{
|
||||
// Cache namespace if there is one
|
||||
if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace))
|
||||
{
|
||||
uniqueNamespaces.Add(type.Namespace);
|
||||
@ -100,16 +101,16 @@ namespace UnityExplorer
|
||||
AllNamespaces.Insert(i, type.Namespace);
|
||||
}
|
||||
|
||||
// Cache the type. Overwrite type if one exists with the full name
|
||||
if (AllTypes.ContainsKey(type.FullName))
|
||||
AllTypes[type.FullName] = type;
|
||||
else
|
||||
{
|
||||
AllTypes.Add(type.FullName, type);
|
||||
//allTypeNames.Add(type.FullName);
|
||||
}
|
||||
|
||||
// Invoke listener
|
||||
OnTypeLoaded?.Invoke(type);
|
||||
|
||||
// Check type inheritance cache, add this to any lists it should be in
|
||||
foreach (var key in typeInheritance.Keys)
|
||||
{
|
||||
try
|
||||
@ -154,13 +155,6 @@ namespace UnityExplorer
|
||||
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
|
||||
=> theString;
|
||||
|
||||
//// Force loading modules
|
||||
//public static bool LoadModule(string moduleName)
|
||||
// => Instance.Internal_LoadModule(moduleName);
|
||||
//
|
||||
//internal virtual bool Internal_LoadModule(string moduleName)
|
||||
// => false;
|
||||
|
||||
// Singleton finder
|
||||
|
||||
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
|
||||
|
@ -2,9 +2,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
// Project-wide namespace for accessibility
|
||||
@ -107,5 +109,16 @@ namespace UnityExplorer
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private static PropertyInfo onEndEdit;
|
||||
|
||||
public static UnityEvent<string> GetOnEndEdit(this InputField _this)
|
||||
{
|
||||
if (onEndEdit == null)
|
||||
onEndEdit = typeof(InputField).GetProperty("onEndEdit")
|
||||
?? throw new Exception("Could not get InputField.onEndEdit property!");
|
||||
|
||||
return onEndEdit.GetValue(_this, null).TryCast<UnityEvent<string>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace UnityExplorer
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "4.1.4";
|
||||
public const string VERSION = "4.1.7";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.unityexplorer";
|
||||
|
||||
|
@ -21,11 +21,7 @@ namespace UnityExplorer.Loader.BIE
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> config)
|
||||
{
|
||||
object[] tags = null;
|
||||
if (config.IsInternal)
|
||||
tags = new[] { "Advanced" };
|
||||
|
||||
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, new ConfigDescription(config.Description, null, tags));
|
||||
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, config.Description);
|
||||
|
||||
entry.SettingChanged += (object o, EventArgs e) =>
|
||||
{
|
||||
|
@ -36,6 +36,8 @@ namespace UnityExplorer.UI.CSConsole
|
||||
public static bool EnableAutoIndent { get; private set; } = true;
|
||||
public static bool EnableSuggestions { get; private set; } = true;
|
||||
|
||||
internal static string ScriptsFolder => Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Scripts");
|
||||
|
||||
internal static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
@ -51,6 +53,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
// Make sure console is supported on this platform
|
||||
try
|
||||
{
|
||||
ResetConsole(false);
|
||||
@ -63,19 +66,41 @@ namespace UnityExplorer.UI.CSConsole
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup console
|
||||
Lexer = new LexerBuilder();
|
||||
Completer = new CSAutoCompleter();
|
||||
|
||||
SetupHelpInteraction();
|
||||
|
||||
Panel.OnInputChanged += OnInputChanged;
|
||||
Panel.InputScroll.OnScroll += OnInputScrolled;
|
||||
Panel.InputScroller.OnScroll += OnInputScrolled;
|
||||
Panel.OnCompileClicked += Evaluate;
|
||||
Panel.OnResetClicked += ResetConsole;
|
||||
Panel.OnHelpDropdownChanged += HelpSelected;
|
||||
Panel.OnAutoIndentToggled += OnToggleAutoIndent;
|
||||
Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
|
||||
Panel.OnSuggestionsToggled += OnToggleSuggestions;
|
||||
Panel.OnPanelResized += OnInputScrolled;
|
||||
|
||||
// Run startup script
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(ScriptsFolder))
|
||||
Directory.CreateDirectory(ScriptsFolder);
|
||||
|
||||
var startupPath = Path.Combine(ScriptsFolder, "startup.cs");
|
||||
if (File.Exists(startupPath))
|
||||
{
|
||||
ExplorerCore.Log($"Executing startup script from '{startupPath}'...");
|
||||
var text = File.ReadAllText(startupPath);
|
||||
Input.Text = text;
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception executing startup script: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -317,7 +342,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
var charBot = charTop - CSCONSOLE_LINEHEIGHT;
|
||||
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
|
||||
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
float diff = 0f;
|
||||
if (charTop > viewportMin)
|
||||
@ -337,7 +362,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
{
|
||||
settingCaretCoroutine = true;
|
||||
Input.Component.readOnly = true;
|
||||
RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaretCoro(caretPosition));
|
||||
RuntimeProvider.Instance.StartCoroutine(SetCaretCoroutine(caretPosition));
|
||||
}
|
||||
|
||||
internal static PropertyInfo SelectionGuardProperty => selectionGuardPropInfo ?? GetSelectionGuardPropInfo();
|
||||
@ -352,7 +377,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
private static PropertyInfo selectionGuardPropInfo;
|
||||
|
||||
private static IEnumerator SetAutocompleteCaretCoro(int caretPosition)
|
||||
private static IEnumerator SetCaretCoroutine(int caretPosition)
|
||||
{
|
||||
var color = Input.Component.selectionColor;
|
||||
color.a = 0f;
|
||||
@ -376,7 +401,6 @@ namespace UnityExplorer.UI.CSConsole
|
||||
settingCaretCoroutine = false;
|
||||
}
|
||||
|
||||
|
||||
#region Lexer Highlighting
|
||||
|
||||
/// <summary>
|
||||
@ -384,43 +408,83 @@ namespace UnityExplorer.UI.CSConsole
|
||||
/// </summary>
|
||||
private static bool HighlightVisibleInput()
|
||||
{
|
||||
int startIdx = 0;
|
||||
int endIdx = Input.Text.Length - 1;
|
||||
int topLine = 0;
|
||||
|
||||
// Calculate visible text if necessary
|
||||
if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
|
||||
if (string.IsNullOrEmpty(Input.Text))
|
||||
{
|
||||
topLine = -1;
|
||||
int bottomLine = -1;
|
||||
|
||||
// the top and bottom position of the viewport in relation to the text height
|
||||
// they need the half-height adjustment to normalize against the 'line.topY' value.
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
|
||||
|
||||
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
|
||||
{
|
||||
var line = Input.TextGenerator.lines[i];
|
||||
// if not set the top line yet, and top of line is below the viewport top
|
||||
if (topLine == -1 && line.topY <= viewportMin)
|
||||
topLine = i;
|
||||
// if bottom of line is below the viewport bottom
|
||||
if ((line.topY - line.height) >= viewportMax)
|
||||
bottomLine = i;
|
||||
}
|
||||
|
||||
topLine = Math.Max(0, topLine - 1);
|
||||
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
|
||||
|
||||
startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
|
||||
endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
|
||||
? Input.Text.Length - 1
|
||||
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
|
||||
Panel.HighlightText.text = "";
|
||||
Panel.LineNumberText.text = "1";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the visible lines
|
||||
|
||||
int topLine = -1;
|
||||
int bottomLine = -1;
|
||||
|
||||
// the top and bottom position of the viewport in relation to the text height
|
||||
// they need the half-height adjustment to normalize against the 'line.topY' value.
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
|
||||
{
|
||||
var line = Input.TextGenerator.lines[i];
|
||||
// if not set the top line yet, and top of line is below the viewport top
|
||||
if (topLine == -1 && line.topY <= viewportMin)
|
||||
topLine = i;
|
||||
// if bottom of line is below the viewport bottom
|
||||
if ((line.topY - line.height) >= viewportMax)
|
||||
bottomLine = i;
|
||||
}
|
||||
|
||||
topLine = Math.Max(0, topLine - 1);
|
||||
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
|
||||
|
||||
int startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
|
||||
int endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
|
||||
? Input.Text.Length - 1
|
||||
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
|
||||
|
||||
|
||||
// Highlight the visible text with the LexerBuilder
|
||||
|
||||
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret);
|
||||
|
||||
// Set the line numbers
|
||||
|
||||
// determine true starting line number (not the same as the cached TextGenerator line numbers)
|
||||
int realStartLine = 0;
|
||||
for (int i = 0; i < startIdx; i++)
|
||||
{
|
||||
if (LexerBuilder.IsNewLine(Input.Text[i]))
|
||||
realStartLine++;
|
||||
}
|
||||
realStartLine++;
|
||||
char lastPrev = '\n';
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// append leading new lines for spacing (no point rendering line numbers we cant see)
|
||||
for (int i = 0; i < topLine; i++)
|
||||
sb.Append('\n');
|
||||
|
||||
// append the displayed line numbers
|
||||
for (int i = topLine; i <= bottomLine; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
lastPrev = Input.Text[Input.TextGenerator.lines[i].startCharIdx - 1];
|
||||
|
||||
// previous line ended with a newline character, this is an actual new line.
|
||||
if (LexerBuilder.IsNewLine(lastPrev))
|
||||
{
|
||||
sb.Append(realStartLine.ToString());
|
||||
realStartLine++;
|
||||
}
|
||||
|
||||
sb.Append('\n');
|
||||
}
|
||||
|
||||
Panel.LineNumberText.text = sb.ToString();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -555,7 +619,9 @@ If the game was built with Unity's stubbed netstandard 2.0 runtime, you can fix
|
||||
internal const string STARTUP_TEXT = @"<color=#5d8556>// Welcome to the UnityExplorer C# Console!
|
||||
|
||||
// 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.</color>";
|
||||
// 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>";
|
||||
|
||||
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.
|
||||
|
@ -13,8 +13,9 @@ namespace UnityExplorer.UI.CSConsole
|
||||
{
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public string htmlColorTag;
|
||||
public bool isStringOrComment;
|
||||
public bool matchToEndOfLine;
|
||||
public string htmlColorTag;
|
||||
}
|
||||
|
||||
public class LexerBuilder
|
||||
@ -112,12 +113,24 @@ namespace UnityExplorer.UI.CSConsole
|
||||
sb.Append(input[i]);
|
||||
sb.Append(SignatureHighlighter.CLOSE_COLOR);
|
||||
|
||||
// check caretIdx to determine inStringOrComment state
|
||||
if (caretIdx >= match.startIndex && (caretIdx <= match.endIndex || (caretIdx >= input.Length && match.endIndex >= input.Length - 1)))
|
||||
caretInStringOrComment = match.isStringOrComment;
|
||||
|
||||
// update the last unhighlighted start index
|
||||
lastUnhighlighted = match.endIndex + 1;
|
||||
|
||||
int matchEndIdx = match.endIndex;
|
||||
if (match.matchToEndOfLine)
|
||||
{
|
||||
while (input.Length - 1 >= matchEndIdx)
|
||||
{
|
||||
if (IsNewLine(input[matchEndIdx]))
|
||||
break;
|
||||
matchEndIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// check caretIdx to determine inStringOrComment state
|
||||
if (caretIdx >= match.startIndex && (caretIdx <= matchEndIdx || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
|
||||
caretInStringOrComment = match.isStringOrComment;
|
||||
|
||||
}
|
||||
|
||||
// Append trailing unhighlighted input
|
||||
|
@ -468,7 +468,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
//UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120);
|
||||
//pathApplyBtn.OnClick += () => { OnPathEndEdit(PathInput.Text); };
|
||||
|
||||
PathInput.Component.onEndEdit.AddListener((string val) => { OnPathEndEdit(val); });
|
||||
PathInput.Component.GetOnEndEdit().AddListener((string val) => { OnPathEndEdit(val); });
|
||||
|
||||
// Title and update row
|
||||
|
||||
@ -484,7 +484,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled");
|
||||
UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
|
||||
NameInput.Component.textComponent.fontSize = 15;
|
||||
NameInput.Component.onEndEdit.AddListener((string val) => { OnNameEndEdit(val); });
|
||||
NameInput.Component.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
|
||||
|
||||
// second row (toggles, instanceID, tag, buttons)
|
||||
|
||||
@ -521,7 +521,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none");
|
||||
UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
TagInput.Component.textComponent.color = Color.white;
|
||||
TagInput.Component.onEndEdit.AddListener((string val) => { OnTagEndEdit(val); });
|
||||
TagInput.Component.GetOnEndEdit().AddListener((string val) => { OnTagEndEdit(val); });
|
||||
|
||||
// Instantiate
|
||||
var instantiateBtn = UIFactory.CreateButton(secondRow, "InstantiateBtn", "Instantiate", new Color(0.2f, 0.2f, 0.2f));
|
||||
@ -644,7 +644,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
var inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
|
||||
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
|
||||
inputField.Component.onEndEdit.AddListener((string value) => { OnTransformInputEndEdit(type, value); });
|
||||
inputField.Component.GetOnEndEdit().AddListener((string value) => { OnTransformInputEndEdit(type, value); });
|
||||
|
||||
var control = new TransformControl(type, inputField);
|
||||
|
||||
|
@ -67,8 +67,12 @@ namespace UnityExplorer.UI.Inspectors
|
||||
public void StartInspect(MouseInspectMode mode)
|
||||
{
|
||||
MainCamera = Camera.main;
|
||||
if (!MainCamera)
|
||||
|
||||
if (!MainCamera && mode == MouseInspectMode.World)
|
||||
{
|
||||
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
|
||||
return;
|
||||
}
|
||||
|
||||
PanelDragger.ForceEnd();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -18,10 +19,11 @@ namespace UnityExplorer.UI.Panels
|
||||
public override int MinWidth => 750;
|
||||
public override int MinHeight => 300;
|
||||
|
||||
public InputFieldScroller InputScroll { get; private set; }
|
||||
public InputFieldRef Input => InputScroll.InputField;
|
||||
public InputFieldScroller InputScroller { get; private set; }
|
||||
public InputFieldRef Input => InputScroller.InputField;
|
||||
public Text InputText { get; private set; }
|
||||
public Text HighlightText { get; private set; }
|
||||
public Text LineNumberText { get; private set; }
|
||||
|
||||
public Dropdown HelpDropdown { get; private set; }
|
||||
|
||||
@ -33,6 +35,7 @@ namespace UnityExplorer.UI.Panels
|
||||
public Action<bool> OnCtrlRToggled;
|
||||
public Action<bool> OnSuggestionsToggled;
|
||||
public Action<bool> OnAutoIndentToggled;
|
||||
public Action OnPanelResized;
|
||||
|
||||
private void InvokeOnValueChanged(string value)
|
||||
{
|
||||
@ -60,6 +63,11 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
// UI Construction
|
||||
|
||||
public override void OnFinishResize(RectTransform panel)
|
||||
{
|
||||
OnPanelResized?.Invoke();
|
||||
}
|
||||
|
||||
protected internal override void DoSetDefaultPosAndAnchors()
|
||||
{
|
||||
Rect.localPosition = Vector2.zero;
|
||||
@ -121,19 +129,53 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
// Console Input
|
||||
|
||||
var inputArea = UIFactory.CreateUIObject("InputGroup", content);
|
||||
UIFactory.SetLayoutElement(inputArea, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(inputArea, false, true, true, true);
|
||||
inputArea.AddComponent<Image>().color = Color.white;
|
||||
inputArea.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
// line numbers
|
||||
|
||||
var linesHolder = UIFactory.CreateUIObject("LinesHolder", inputArea);
|
||||
var linesRect = linesHolder.GetComponent<RectTransform>();
|
||||
linesRect.pivot = new Vector2(0, 1);
|
||||
linesRect.anchorMin = new Vector2(0, 0);
|
||||
linesRect.anchorMax = new Vector2(0, 1);
|
||||
linesRect.sizeDelta = new Vector2(0, 305000);
|
||||
linesRect.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, 0, 50);
|
||||
linesHolder.AddComponent<Image>().color = new Color(0.05f, 0.05f, 0.05f);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(linesHolder, true, true, true, true);
|
||||
|
||||
LineNumberText = UIFactory.CreateLabel(linesHolder, "LineNumbers", "1", TextAnchor.UpperCenter, Color.grey, fontSize: 16);
|
||||
LineNumberText.font = UIManager.ConsoleFont;
|
||||
|
||||
// input field
|
||||
|
||||
int fontSize = 16;
|
||||
|
||||
var inputObj = UIFactory.CreateScrollInputField(this.content, "ConsoleInput", ConsoleController.STARTUP_TEXT, out var inputScroller, fontSize);
|
||||
InputScroll = inputScroller;
|
||||
var inputObj = UIFactory.CreateScrollInputField(inputArea, "ConsoleInput", ConsoleController.STARTUP_TEXT,
|
||||
out var inputScroller, fontSize);
|
||||
InputScroller = inputScroller;
|
||||
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
|
||||
Input.OnValueChanged += InvokeOnValueChanged;
|
||||
|
||||
// move line number text with input field
|
||||
linesRect.transform.SetParent(inputObj.transform.Find("Viewport"), false);
|
||||
inputScroller.Slider.Scrollbar.onValueChanged.AddListener((float val) => { SetLinesPosition(); });
|
||||
inputScroller.Slider.Slider.onValueChanged.AddListener((float val) => { SetLinesPosition(); });
|
||||
void SetLinesPosition()
|
||||
{
|
||||
linesRect.anchoredPosition = new Vector2(linesRect.anchoredPosition.x, inputScroller.ContentRect.anchoredPosition.y);
|
||||
//SetInputLayout();
|
||||
}
|
||||
|
||||
InputText = Input.Component.textComponent;
|
||||
InputText.supportRichText = false;
|
||||
Input.PlaceholderText.fontSize = fontSize;
|
||||
InputText.color = Color.clear;
|
||||
Input.Component.customCaretColor = true;
|
||||
Input.Component.caretColor = Color.white;
|
||||
Input.PlaceholderText.fontSize = fontSize;
|
||||
|
||||
// Lexer highlight text overlay
|
||||
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", InputText.gameObject);
|
||||
@ -154,7 +196,19 @@ namespace UnityExplorer.UI.Panels
|
||||
Input.PlaceholderText.font = UIManager.ConsoleFont;
|
||||
HighlightText.font = UIManager.ConsoleFont;
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(DelayedLayoutSetup());
|
||||
}
|
||||
|
||||
private IEnumerator DelayedLayoutSetup()
|
||||
{
|
||||
yield return null;
|
||||
SetInputLayout();
|
||||
}
|
||||
|
||||
public void SetInputLayout()
|
||||
{
|
||||
Input.Rect.offsetMin = new Vector2(52, Input.Rect.offsetMin.y);
|
||||
Input.Rect.offsetMax = new Vector2(2, Input.Rect.offsetMax.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,14 +154,13 @@ namespace UnityExplorer.UI.Panels
|
||||
// Prevent panel going oustide screen bounds
|
||||
var halfW = Screen.width * 0.5f;
|
||||
var halfH = Screen.height * 0.5f;
|
||||
pos.x = Math.Max(-halfW, Math.Min(pos.x, halfW - panel.rect.width));
|
||||
pos.y = Math.Max(-halfH + panel.rect.height, Math.Min(pos.y, halfH));
|
||||
|
||||
pos.x = Math.Max(-halfW - panel.rect.width + 50, Math.Min(pos.x, halfW - 50));
|
||||
pos.y = Math.Max(-halfH + 50, Math.Min(pos.y, halfH));
|
||||
|
||||
panel.localPosition = pos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Save Data
|
||||
|
||||
public abstract void DoSaveToConfigElement();
|
||||
|
@ -409,56 +409,49 @@ namespace UnityExplorer.UI
|
||||
/// <summary>
|
||||
/// Create a Toggle control.
|
||||
/// </summary>
|
||||
public static GameObject CreateToggle(GameObject parent, string name, out Toggle toggle, out Text text, Color bgColor = default)
|
||||
public static GameObject CreateToggle(GameObject parent, string name, out Toggle toggle, out Text text, Color bgColor = default,
|
||||
int checkWidth = 20, int checkHeight = 20)
|
||||
{
|
||||
// Main obj
|
||||
GameObject toggleObj = CreateUIObject(name, parent, _smallElementSize);
|
||||
|
||||
GameObject bgObj = CreateUIObject("Background", toggleObj);
|
||||
GameObject checkObj = CreateUIObject("Checkmark", bgObj);
|
||||
GameObject labelObj = CreateUIObject("Label", toggleObj);
|
||||
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(toggleObj, false, false, true, true, 5, 0,0,0,0, childAlignment: TextAnchor.MiddleLeft);
|
||||
toggle = toggleObj.AddComponent<Toggle>();
|
||||
toggle.isOn = true;
|
||||
SetDefaultSelectableColors(toggle);
|
||||
// need a second reference so we can use it inside the lambda, since 'toggle' is an out var.
|
||||
Toggle t2 = toggle;
|
||||
toggle.onValueChanged.AddListener((bool _) => { t2.OnDeselect(null); });
|
||||
|
||||
// second reference so we can use it inside the lambda, 'toggle' is an out var.
|
||||
Toggle toggleComp = toggle;
|
||||
toggle.onValueChanged.AddListener(Deselect);
|
||||
void Deselect(bool _)
|
||||
{
|
||||
toggleComp.OnDeselect(null);
|
||||
}
|
||||
// Check mark background
|
||||
|
||||
Image bgImage = bgObj.AddComponent<Image>();
|
||||
GameObject checkBgObj = CreateUIObject("Background", toggleObj);
|
||||
Image bgImage = checkBgObj.AddComponent<Image>();
|
||||
bgImage.color = bgColor == default ? new Color(0.04f, 0.04f, 0.04f, 0.75f) : bgColor;
|
||||
|
||||
Image checkImage = checkObj.AddComponent<Image>();
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(checkBgObj, true, true, true, true, 0, 2, 2, 2, 2);
|
||||
SetLayoutElement(checkBgObj, minWidth: checkWidth, flexibleWidth: 0, minHeight: checkHeight, flexibleHeight: 0);
|
||||
|
||||
// Check mark image
|
||||
|
||||
GameObject checkMarkObj = CreateUIObject("Checkmark", checkBgObj);
|
||||
Image checkImage = checkMarkObj.AddComponent<Image>();
|
||||
checkImage.color = new Color(0.8f, 1, 0.8f, 0.3f);
|
||||
|
||||
// Label
|
||||
|
||||
GameObject labelObj = CreateUIObject("Label", toggleObj);
|
||||
text = labelObj.AddComponent<Text>();
|
||||
text.text = "Toggle";
|
||||
text.text = "";
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
SetDefaultTextValues(text);
|
||||
|
||||
SetLayoutElement(labelObj, minWidth: 0, flexibleWidth: 0, minHeight: checkHeight, flexibleHeight: 0);
|
||||
|
||||
// References
|
||||
|
||||
toggle.graphic = checkImage;
|
||||
toggle.targetGraphic = bgImage;
|
||||
SetDefaultSelectableColors(toggle);
|
||||
|
||||
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
|
||||
bgRect.anchorMin = new Vector2(0f, 1f);
|
||||
bgRect.anchorMax = new Vector2(0f, 1f);
|
||||
bgRect.anchoredPosition = new Vector2(13f, -13f);
|
||||
bgRect.sizeDelta = new Vector2(20f, 20f);
|
||||
|
||||
RectTransform checkRect = checkObj.GetComponent<RectTransform>();
|
||||
checkRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
checkRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
checkRect.anchoredPosition = Vector2.zero;
|
||||
checkRect.sizeDelta = new Vector2(14f, 14f);
|
||||
|
||||
RectTransform labelRect = labelObj.GetComponent<RectTransform>();
|
||||
labelRect.anchorMin = new Vector2(0f, 0f);
|
||||
labelRect.anchorMax = new Vector2(1f, 1f);
|
||||
labelRect.offsetMin = new Vector2(28f, 2f);
|
||||
labelRect.offsetMax = new Vector2(-5f, -5f);
|
||||
return toggleObj;
|
||||
}
|
||||
|
||||
|
@ -378,7 +378,7 @@ namespace UnityExplorer.UI
|
||||
timeInput = UIFactory.CreateInputField(navbarPanel, "TimeInput", "timeScale");
|
||||
UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40);
|
||||
timeInput.Text = Time.timeScale.ToString("F2");
|
||||
timeInput.Component.onEndEdit.AddListener(OnTimeInputEndEdit);
|
||||
timeInput.Component.GetOnEndEdit().AddListener(OnTimeInputEndEdit);
|
||||
|
||||
pauseBtn = UIFactory.CreateButton(navbarPanel, "PauseButton", "||", new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(pauseBtn.Component.gameObject, minHeight: 25, minWidth: 25);
|
||||
|
@ -80,12 +80,12 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
if (ContentRect.rect.height < desiredHeight)
|
||||
{
|
||||
ContentRect.sizeDelta = new Vector2(0, desiredHeight);
|
||||
ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
|
||||
this.Slider.UpdateSliderHandle();
|
||||
}
|
||||
else if (ContentRect.rect.height > desiredHeight)
|
||||
{
|
||||
ContentRect.sizeDelta = new Vector2(0, desiredHeight);
|
||||
ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
|
||||
this.Slider.UpdateSliderHandle();
|
||||
}
|
||||
}
|
||||
|
@ -193,12 +193,11 @@ namespace UnityExplorer.UI.Widgets
|
||||
RuntimeProvider.Instance.StartCoroutine(InitCoroutine(onHeightChangedListener));
|
||||
}
|
||||
|
||||
private WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
|
||||
|
||||
private IEnumerator InitCoroutine(Action onHeightChangedListener)
|
||||
{
|
||||
ScrollRect.content.anchoredPosition = Vector2.zero;
|
||||
yield return waitForEndOfFrame ?? (waitForEndOfFrame = new WaitForEndOfFrame());
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
private bool m_enabled;
|
||||
|
||||
public Action<CachedTransform> OnExpandToggled;
|
||||
public Action<CachedTransform> OnEnableToggled;
|
||||
public Action<GameObject> OnGameObjectClicked;
|
||||
|
||||
public CachedTransform cachedTransform;
|
||||
@ -27,15 +28,20 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
public ButtonRef ExpandButton;
|
||||
public ButtonRef NameButton;
|
||||
public Toggle EnabledToggle;
|
||||
|
||||
public LayoutElement spacer;
|
||||
|
||||
public void OnMainButtonClicked()
|
||||
public void Enable()
|
||||
{
|
||||
if (cachedTransform.Value)
|
||||
OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject);
|
||||
else
|
||||
ExplorerCore.LogWarning("The object was destroyed!");
|
||||
m_enabled = true;
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
m_enabled = false;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void ConfigureCell(CachedTransform cached, int cellIndex)
|
||||
@ -59,6 +65,8 @@ namespace UnityExplorer.UI.Widgets
|
||||
NameButton.ButtonText.text = cached.Value.name;
|
||||
NameButton.ButtonText.color = cached.Value.gameObject.activeSelf ? Color.white : Color.grey;
|
||||
|
||||
EnabledToggle.Set(cached.Value.gameObject.activeSelf, false);
|
||||
|
||||
int childCount = cached.Value.childCount;
|
||||
if (childCount > 0)
|
||||
{
|
||||
@ -82,16 +90,12 @@ namespace UnityExplorer.UI.Widgets
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
public void OnMainButtonClicked()
|
||||
{
|
||||
m_enabled = false;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
UIRoot.SetActive(true);
|
||||
if (cachedTransform.Value)
|
||||
OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject);
|
||||
else
|
||||
ExplorerCore.LogWarning("The object was destroyed!");
|
||||
}
|
||||
|
||||
public void OnExpandClicked()
|
||||
@ -99,10 +103,15 @@ namespace UnityExplorer.UI.Widgets
|
||||
OnExpandToggled?.Invoke(cachedTransform);
|
||||
}
|
||||
|
||||
private void OnEnableClicked(bool value)
|
||||
{
|
||||
OnEnableToggled?.Invoke(cachedTransform);
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("TransformCell", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, true, true, true, true, 2, childAlignment: TextAnchor.MiddleCenter);
|
||||
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);
|
||||
@ -114,9 +123,19 @@ namespace UnityExplorer.UI.Widgets
|
||||
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
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(UIRoot, "BehaviourToggle", out EnabledToggle, out var behavText, default, 17, 17);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 17, flexibleHeight: 0, minWidth: 17);
|
||||
EnabledToggle.onValueChanged.AddListener(OnEnableClicked);
|
||||
|
||||
// Name button
|
||||
|
||||
NameButton = UIFactory.CreateButton(this.UIRoot, "NameButton", "Name", null);
|
||||
UIFactory.SetLayoutElement(NameButton.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
var nameLabel = NameButton.Component.GetComponentInChildren<Text>();
|
||||
|
@ -60,8 +60,9 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
public void OnCellBorrowed(TransformCell cell)
|
||||
{
|
||||
cell.OnExpandToggled += ToggleExpandCell;
|
||||
cell.OnExpandToggled += OnCellExpandToggled;
|
||||
cell.OnGameObjectClicked += OnGameObjectClicked;
|
||||
cell.OnEnableToggled += OnCellEnableToggled;
|
||||
}
|
||||
|
||||
private void OnGameObjectClicked(GameObject obj)
|
||||
@ -72,6 +73,24 @@ namespace UnityExplorer.UI.Widgets
|
||||
InspectorManager.Inspect(obj);
|
||||
}
|
||||
|
||||
public void OnCellExpandToggled(CachedTransform cache)
|
||||
{
|
||||
var instanceID = cache.InstanceID;
|
||||
if (expandedInstanceIDs.Contains(instanceID))
|
||||
expandedInstanceIDs.Remove(instanceID);
|
||||
else
|
||||
expandedInstanceIDs.Add(instanceID);
|
||||
|
||||
RefreshData(true);
|
||||
}
|
||||
|
||||
public void OnCellEnableToggled(CachedTransform cache)
|
||||
{
|
||||
cache.Value.gameObject.SetActive(!cache.Value.gameObject.activeSelf);
|
||||
|
||||
RefreshData(true);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ScrollPool.Initialize(this);
|
||||
@ -261,16 +280,5 @@ namespace UnityExplorer.UI.Widgets
|
||||
else
|
||||
cell.Disable();
|
||||
}
|
||||
|
||||
public void ToggleExpandCell(CachedTransform cache)
|
||||
{
|
||||
var instanceID = cache.InstanceID;
|
||||
if (expandedInstanceIDs.Contains(instanceID))
|
||||
expandedInstanceIDs.Remove(instanceID);
|
||||
else
|
||||
expandedInstanceIDs.Add(instanceID);
|
||||
|
||||
RefreshData(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user