Compare commits

...

19 Commits
4.1.4 ... 4.1.7

Author SHA1 Message Date
92447b55cd Update README.md 2021-06-22 19:48:41 +10:00
87d5d5a2de Bump version 2021-06-22 19:46:14 +10:00
08cff3386b Fix issues with Il2Cpp nullables 2021-06-22 19:46:09 +10:00
6033200579 Don't use WaitForEndOfFrame in SrollPool init coroutine 2021-06-22 19:45:57 +10:00
94ec1c4908 Update line numbers on panel resize 2021-06-21 19:50:11 +10:00
7a400e762c Cleanup 2021-06-21 19:49:44 +10:00
67f9f744bb Handle Unity 2021+ InputField.onEndEdit change 2021-06-21 19:26:05 +10:00
7a59f9a2a1 Update info about startup script -noci 2021-06-20 19:10:44 +10:00
9b42eef1b9 Line numbers and startup script 2021-06-20 19:06:52 +10:00
830000b019 Bump version 2021-06-20 19:06:33 +10:00
34910ab273 Bump version 2021-06-14 18:56:56 +10:00
86b036095e Allow panels to be dragged further outside the game window 2021-06-14 18:56:51 +10:00
b57e5be2e6 Add GameObject.activeSelf toggle to TransformTree, adjust UI Toggle design 2021-06-14 18:43:26 +10:00
2d8ae45814 Cleanup 2021-06-14 18:43:08 +10:00
66dc262a68 Move inner exception null check outside IL2CPP ppd 2021-06-11 18:12:14 +10:00
4342901206 Prevent null exceptions being used in ReflectionExToString 2021-06-11 17:36:35 +10:00
58b7c72a5c Allow UI inspect without main camera 2021-06-11 17:36:17 +10:00
623dc7b7be Clarify VS build instructions 2021-06-10 18:14:33 +10:00
e6f4939cc9 Update README.md 2021-06-07 19:50:36 +10:00
19 changed files with 328 additions and 145 deletions

View File

@ -6,11 +6,14 @@
🔍 An in-game UI for exploring, debugging and modifying Unity games. 🔍 An in-game UI for exploring, debugging and modifying Unity games.
</p> </p>
<p align="center"> <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>
<p align="center"> <p align="center">
☕ Enjoy this tool? Consider supporting me on <a href="https://ko-fi.com/sinaidev">ko-fi</a>! ☕ Enjoy this tool? Consider supporting me on <a href="https://ko-fi.com/sinaidev">ko-fi</a>!
</p> </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 [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases) # Releases [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases)
@ -88,6 +91,7 @@ The inspector is used to see detailed information on objects of any type and man
### C# Console ### C# Console
* The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code. * 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. * See the "Help" dropdown in the C# console menu for more detailed information.
### Mouse-Inspect ### Mouse-Inspect
@ -115,7 +119,7 @@ For Visual Studio:
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules. 0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
1. Open the `src\UnityExplorer.sln` project. 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. 3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
# Acknowledgments # Acknowledgments

View File

@ -99,8 +99,10 @@ namespace UnityExplorer
public static Exception GetInnerMostException(this Exception e) public static Exception GetInnerMostException(this Exception e)
{ {
while (e.InnerException != null) while (e != null)
{ {
if (e.InnerException == null)
break;
#if CPP #if CPP
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException) if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break; break;

View File

@ -132,7 +132,6 @@ namespace UnityExplorer
return null; return null;
var type = obj.GetType(); var type = obj.GetType();
try try
{ {
if (IsString(obj)) if (IsString(obj))
@ -216,7 +215,7 @@ namespace UnityExplorer
// from other structs to il2cpp object // from other structs to il2cpp object
else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{ {
return BoxIl2CppObject(obj); return BoxIl2CppObject(obj).TryCast(castTo);
} }
else else
return obj; return obj;
@ -295,7 +294,27 @@ namespace UnityExplorer
try try
{ {
if (toType.IsEnum) 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()); return Enum.Parse(toType, cppObj.ToString());
}
// Not enum, unbox with Il2CppObjectBase.Unbox
var name = toType.AssemblyQualifiedName; var name = toType.AssemblyQualifiedName;

View File

@ -87,6 +87,7 @@ namespace UnityExplorer
{ {
foreach (var type in asm.TryGetTypes()) foreach (var type in asm.TryGetTypes())
{ {
// Cache namespace if there is one
if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace)) if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace))
{ {
uniqueNamespaces.Add(type.Namespace); uniqueNamespaces.Add(type.Namespace);
@ -100,16 +101,16 @@ namespace UnityExplorer
AllNamespaces.Insert(i, type.Namespace); AllNamespaces.Insert(i, type.Namespace);
} }
// Cache the type. Overwrite type if one exists with the full name
if (AllTypes.ContainsKey(type.FullName)) if (AllTypes.ContainsKey(type.FullName))
AllTypes[type.FullName] = type; AllTypes[type.FullName] = type;
else else
{
AllTypes.Add(type.FullName, type); AllTypes.Add(type.FullName, type);
//allTypeNames.Add(type.FullName);
}
// Invoke listener
OnTypeLoaded?.Invoke(type); OnTypeLoaded?.Invoke(type);
// Check type inheritance cache, add this to any lists it should be in
foreach (var key in typeInheritance.Keys) foreach (var key in typeInheritance.Keys)
{ {
try try
@ -154,13 +155,6 @@ namespace UnityExplorer
internal virtual string Internal_ProcessTypeInString(string theString, Type type) internal virtual string Internal_ProcessTypeInString(string theString, Type type)
=> theString; => theString;
//// Force loading modules
//public static bool LoadModule(string moduleName)
// => Instance.Internal_LoadModule(moduleName);
//
//internal virtual bool Internal_LoadModule(string moduleName)
// => false;
// Singleton finder // Singleton finder
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances) public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)

View File

@ -2,9 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
// Project-wide namespace for accessibility // Project-wide namespace for accessibility
@ -107,5 +109,16 @@ namespace UnityExplorer
return color; 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>>();
}
} }
} }

View File

@ -20,7 +20,7 @@ namespace UnityExplorer
public static class ExplorerCore public static class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; 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 AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";

View File

@ -21,11 +21,7 @@ namespace UnityExplorer.Loader.BIE
public override void RegisterConfigElement<T>(ConfigElement<T> config) public override void RegisterConfigElement<T>(ConfigElement<T> config)
{ {
object[] tags = null; var entry = Config.Bind(CTG_NAME, config.Name, config.Value, config.Description);
if (config.IsInternal)
tags = new[] { "Advanced" };
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, new ConfigDescription(config.Description, null, tags));
entry.SettingChanged += (object o, EventArgs e) => entry.SettingChanged += (object o, EventArgs e) =>
{ {

View File

@ -36,6 +36,8 @@ namespace UnityExplorer.UI.CSConsole
public static bool EnableAutoIndent { get; private set; } = true; public static bool EnableAutoIndent { get; private set; } = true;
public static bool EnableSuggestions { 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[] internal static readonly string[] DefaultUsing = new string[]
{ {
"System", "System",
@ -51,6 +53,7 @@ namespace UnityExplorer.UI.CSConsole
public static void Init() public static void Init()
{ {
// Make sure console is supported on this platform
try try
{ {
ResetConsole(false); ResetConsole(false);
@ -63,19 +66,41 @@ namespace UnityExplorer.UI.CSConsole
return; return;
} }
// Setup console
Lexer = new LexerBuilder(); Lexer = new LexerBuilder();
Completer = new CSAutoCompleter(); Completer = new CSAutoCompleter();
SetupHelpInteraction(); SetupHelpInteraction();
Panel.OnInputChanged += OnInputChanged; Panel.OnInputChanged += OnInputChanged;
Panel.InputScroll.OnScroll += OnInputScrolled; Panel.InputScroller.OnScroll += OnInputScrolled;
Panel.OnCompileClicked += Evaluate; Panel.OnCompileClicked += Evaluate;
Panel.OnResetClicked += ResetConsole; Panel.OnResetClicked += ResetConsole;
Panel.OnHelpDropdownChanged += HelpSelected; Panel.OnHelpDropdownChanged += HelpSelected;
Panel.OnAutoIndentToggled += OnToggleAutoIndent; Panel.OnAutoIndentToggled += OnToggleAutoIndent;
Panel.OnCtrlRToggled += OnToggleCtrlRShortcut; Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
Panel.OnSuggestionsToggled += OnToggleSuggestions; 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 charBot = charTop - CSCONSOLE_LINEHEIGHT;
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f); 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; float diff = 0f;
if (charTop > viewportMin) if (charTop > viewportMin)
@ -337,7 +362,7 @@ namespace UnityExplorer.UI.CSConsole
{ {
settingCaretCoroutine = true; settingCaretCoroutine = true;
Input.Component.readOnly = true; Input.Component.readOnly = true;
RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaretCoro(caretPosition)); RuntimeProvider.Instance.StartCoroutine(SetCaretCoroutine(caretPosition));
} }
internal static PropertyInfo SelectionGuardProperty => selectionGuardPropInfo ?? GetSelectionGuardPropInfo(); internal static PropertyInfo SelectionGuardProperty => selectionGuardPropInfo ?? GetSelectionGuardPropInfo();
@ -352,7 +377,7 @@ namespace UnityExplorer.UI.CSConsole
private static PropertyInfo selectionGuardPropInfo; private static PropertyInfo selectionGuardPropInfo;
private static IEnumerator SetAutocompleteCaretCoro(int caretPosition) private static IEnumerator SetCaretCoroutine(int caretPosition)
{ {
var color = Input.Component.selectionColor; var color = Input.Component.selectionColor;
color.a = 0f; color.a = 0f;
@ -376,7 +401,6 @@ namespace UnityExplorer.UI.CSConsole
settingCaretCoroutine = false; settingCaretCoroutine = false;
} }
#region Lexer Highlighting #region Lexer Highlighting
/// <summary> /// <summary>
@ -384,43 +408,83 @@ namespace UnityExplorer.UI.CSConsole
/// </summary> /// </summary>
private static bool HighlightVisibleInput() private static bool HighlightVisibleInput()
{ {
int startIdx = 0; if (string.IsNullOrEmpty(Input.Text))
int endIdx = Input.Text.Length - 1;
int topLine = 0;
// Calculate visible text if necessary
if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
{ {
topLine = -1; Panel.HighlightText.text = "";
int bottomLine = -1; Panel.LineNumberText.text = "1";
return false;
// 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);
} }
// 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 // 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 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; 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! 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. // 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. 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. // It will remain in effect until you Reset the console.

View File

@ -13,8 +13,9 @@ namespace UnityExplorer.UI.CSConsole
{ {
public int startIndex; public int startIndex;
public int endIndex; public int endIndex;
public string htmlColorTag;
public bool isStringOrComment; public bool isStringOrComment;
public bool matchToEndOfLine;
public string htmlColorTag;
} }
public class LexerBuilder public class LexerBuilder
@ -112,12 +113,24 @@ namespace UnityExplorer.UI.CSConsole
sb.Append(input[i]); sb.Append(input[i]);
sb.Append(SignatureHighlighter.CLOSE_COLOR); 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 // update the last unhighlighted start index
lastUnhighlighted = match.endIndex + 1; 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 // Append trailing unhighlighted input

View File

@ -468,7 +468,7 @@ namespace UnityExplorer.UI.Inspectors
//UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120); //UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120);
//pathApplyBtn.OnClick += () => { OnPathEndEdit(PathInput.Text); }; //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 // Title and update row
@ -484,7 +484,7 @@ namespace UnityExplorer.UI.Inspectors
NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled"); NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled");
UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999); UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
NameInput.Component.textComponent.fontSize = 15; 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) // second row (toggles, instanceID, tag, buttons)
@ -521,7 +521,7 @@ namespace UnityExplorer.UI.Inspectors
TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none"); TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none");
UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999); UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
TagInput.Component.textComponent.color = Color.white; TagInput.Component.textComponent.color = Color.white;
TagInput.Component.onEndEdit.AddListener((string val) => { OnTagEndEdit(val); }); TagInput.Component.GetOnEndEdit().AddListener((string val) => { OnTagEndEdit(val); });
// Instantiate // Instantiate
var instantiateBtn = UIFactory.CreateButton(secondRow, "InstantiateBtn", "Instantiate", new Color(0.2f, 0.2f, 0.2f)); 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", "..."); var inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999); 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); var control = new TransformControl(type, inputField);

View File

@ -67,8 +67,12 @@ namespace UnityExplorer.UI.Inspectors
public void StartInspect(MouseInspectMode mode) public void StartInspect(MouseInspectMode mode)
{ {
MainCamera = Camera.main; MainCamera = Camera.main;
if (!MainCamera)
if (!MainCamera && mode == MouseInspectMode.World)
{
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
return; return;
}
PanelDragger.ForceEnd(); PanelDragger.ForceEnd();

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -18,10 +19,11 @@ namespace UnityExplorer.UI.Panels
public override int MinWidth => 750; public override int MinWidth => 750;
public override int MinHeight => 300; public override int MinHeight => 300;
public InputFieldScroller InputScroll { get; private set; } public InputFieldScroller InputScroller { get; private set; }
public InputFieldRef Input => InputScroll.InputField; public InputFieldRef Input => InputScroller.InputField;
public Text InputText { get; private set; } public Text InputText { get; private set; }
public Text HighlightText { get; private set; } public Text HighlightText { get; private set; }
public Text LineNumberText { get; private set; }
public Dropdown HelpDropdown { get; private set; } public Dropdown HelpDropdown { get; private set; }
@ -33,6 +35,7 @@ namespace UnityExplorer.UI.Panels
public Action<bool> OnCtrlRToggled; public Action<bool> OnCtrlRToggled;
public Action<bool> OnSuggestionsToggled; public Action<bool> OnSuggestionsToggled;
public Action<bool> OnAutoIndentToggled; public Action<bool> OnAutoIndentToggled;
public Action OnPanelResized;
private void InvokeOnValueChanged(string value) private void InvokeOnValueChanged(string value)
{ {
@ -60,6 +63,11 @@ namespace UnityExplorer.UI.Panels
// UI Construction // UI Construction
public override void OnFinishResize(RectTransform panel)
{
OnPanelResized?.Invoke();
}
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
Rect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
@ -121,19 +129,53 @@ namespace UnityExplorer.UI.Panels
// Console Input // 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; int fontSize = 16;
var inputObj = UIFactory.CreateScrollInputField(this.content, "ConsoleInput", ConsoleController.STARTUP_TEXT, out var inputScroller, fontSize); var inputObj = UIFactory.CreateScrollInputField(inputArea, "ConsoleInput", ConsoleController.STARTUP_TEXT,
InputScroll = inputScroller; out var inputScroller, fontSize);
InputScroller = inputScroller;
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a; ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
Input.OnValueChanged += InvokeOnValueChanged; 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 = Input.Component.textComponent;
InputText.supportRichText = false; InputText.supportRichText = false;
Input.PlaceholderText.fontSize = fontSize;
InputText.color = Color.clear; InputText.color = Color.clear;
Input.Component.customCaretColor = true; Input.Component.customCaretColor = true;
Input.Component.caretColor = Color.white; Input.Component.caretColor = Color.white;
Input.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay // Lexer highlight text overlay
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", InputText.gameObject); var highlightTextObj = UIFactory.CreateUIObject("HighlightText", InputText.gameObject);
@ -154,7 +196,19 @@ namespace UnityExplorer.UI.Panels
Input.PlaceholderText.font = UIManager.ConsoleFont; Input.PlaceholderText.font = UIManager.ConsoleFont;
HighlightText.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);
} }
} }
} }

View File

@ -154,14 +154,13 @@ namespace UnityExplorer.UI.Panels
// Prevent panel going oustide screen bounds // Prevent panel going oustide screen bounds
var halfW = Screen.width * 0.5f; var halfW = Screen.width * 0.5f;
var halfH = Screen.height * 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; panel.localPosition = pos;
} }
#region Save Data #region Save Data
public abstract void DoSaveToConfigElement(); public abstract void DoSaveToConfigElement();

View File

@ -409,56 +409,49 @@ namespace UnityExplorer.UI
/// <summary> /// <summary>
/// Create a Toggle control. /// Create a Toggle control.
/// </summary> /// </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 toggleObj = CreateUIObject(name, parent, _smallElementSize);
SetLayoutGroup<HorizontalLayoutGroup>(toggleObj, false, false, true, true, 5, 0,0,0,0, childAlignment: TextAnchor.MiddleLeft);
GameObject bgObj = CreateUIObject("Background", toggleObj);
GameObject checkObj = CreateUIObject("Checkmark", bgObj);
GameObject labelObj = CreateUIObject("Label", toggleObj);
toggle = toggleObj.AddComponent<Toggle>(); toggle = toggleObj.AddComponent<Toggle>();
toggle.isOn = true; 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. // Check mark background
Toggle toggleComp = toggle;
toggle.onValueChanged.AddListener(Deselect);
void Deselect(bool _)
{
toggleComp.OnDeselect(null);
}
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; 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); checkImage.color = new Color(0.8f, 1, 0.8f, 0.3f);
// Label
GameObject labelObj = CreateUIObject("Label", toggleObj);
text = labelObj.AddComponent<Text>(); text = labelObj.AddComponent<Text>();
text.text = "Toggle"; text.text = "";
text.alignment = TextAnchor.MiddleLeft;
SetDefaultTextValues(text); SetDefaultTextValues(text);
SetLayoutElement(labelObj, minWidth: 0, flexibleWidth: 0, minHeight: checkHeight, flexibleHeight: 0);
// References
toggle.graphic = checkImage; toggle.graphic = checkImage;
toggle.targetGraphic = bgImage; 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; return toggleObj;
} }

View File

@ -378,7 +378,7 @@ namespace UnityExplorer.UI
timeInput = UIFactory.CreateInputField(navbarPanel, "TimeInput", "timeScale"); timeInput = UIFactory.CreateInputField(navbarPanel, "TimeInput", "timeScale");
UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40); UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40);
timeInput.Text = Time.timeScale.ToString("F2"); 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)); pauseBtn = UIFactory.CreateButton(navbarPanel, "PauseButton", "||", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(pauseBtn.Component.gameObject, minHeight: 25, minWidth: 25); UIFactory.SetLayoutElement(pauseBtn.Component.gameObject, minHeight: 25, minWidth: 25);

View File

@ -80,12 +80,12 @@ namespace UnityExplorer.UI.Widgets
if (ContentRect.rect.height < desiredHeight) if (ContentRect.rect.height < desiredHeight)
{ {
ContentRect.sizeDelta = new Vector2(0, desiredHeight); ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
this.Slider.UpdateSliderHandle(); this.Slider.UpdateSliderHandle();
} }
else if (ContentRect.rect.height > desiredHeight) else if (ContentRect.rect.height > desiredHeight)
{ {
ContentRect.sizeDelta = new Vector2(0, desiredHeight); ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
this.Slider.UpdateSliderHandle(); this.Slider.UpdateSliderHandle();
} }
} }

View File

@ -193,12 +193,11 @@ namespace UnityExplorer.UI.Widgets
RuntimeProvider.Instance.StartCoroutine(InitCoroutine(onHeightChangedListener)); RuntimeProvider.Instance.StartCoroutine(InitCoroutine(onHeightChangedListener));
} }
private WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
private IEnumerator InitCoroutine(Action onHeightChangedListener) private IEnumerator InitCoroutine(Action onHeightChangedListener)
{ {
ScrollRect.content.anchoredPosition = Vector2.zero; ScrollRect.content.anchoredPosition = Vector2.zero;
yield return waitForEndOfFrame ?? (waitForEndOfFrame = new WaitForEndOfFrame()); yield return null;
yield return null;
LayoutRebuilder.ForceRebuildLayoutImmediate(Content); LayoutRebuilder.ForceRebuildLayoutImmediate(Content);

View File

@ -17,6 +17,7 @@ namespace UnityExplorer.UI.Widgets
private bool m_enabled; private bool m_enabled;
public Action<CachedTransform> OnExpandToggled; public Action<CachedTransform> OnExpandToggled;
public Action<CachedTransform> OnEnableToggled;
public Action<GameObject> OnGameObjectClicked; public Action<GameObject> OnGameObjectClicked;
public CachedTransform cachedTransform; public CachedTransform cachedTransform;
@ -27,15 +28,20 @@ namespace UnityExplorer.UI.Widgets
public ButtonRef ExpandButton; public ButtonRef ExpandButton;
public ButtonRef NameButton; public ButtonRef NameButton;
public Toggle EnabledToggle;
public LayoutElement spacer; public LayoutElement spacer;
public void OnMainButtonClicked() public void Enable()
{ {
if (cachedTransform.Value) m_enabled = true;
OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject); UIRoot.SetActive(true);
else }
ExplorerCore.LogWarning("The object was destroyed!");
public void Disable()
{
m_enabled = false;
UIRoot.SetActive(false);
} }
public void ConfigureCell(CachedTransform cached, int cellIndex) public void ConfigureCell(CachedTransform cached, int cellIndex)
@ -59,6 +65,8 @@ namespace UnityExplorer.UI.Widgets
NameButton.ButtonText.text = cached.Value.name; NameButton.ButtonText.text = cached.Value.name;
NameButton.ButtonText.color = cached.Value.gameObject.activeSelf ? Color.white : Color.grey; NameButton.ButtonText.color = cached.Value.gameObject.activeSelf ? Color.white : Color.grey;
EnabledToggle.Set(cached.Value.gameObject.activeSelf, false);
int childCount = cached.Value.childCount; int childCount = cached.Value.childCount;
if (childCount > 0) if (childCount > 0)
{ {
@ -82,16 +90,12 @@ namespace UnityExplorer.UI.Widgets
} }
} }
public void Disable() public void OnMainButtonClicked()
{ {
m_enabled = false; if (cachedTransform.Value)
UIRoot.SetActive(false); OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject);
} else
ExplorerCore.LogWarning("The object was destroyed!");
public void Enable()
{
m_enabled = true;
UIRoot.SetActive(true);
} }
public void OnExpandClicked() public void OnExpandClicked()
@ -99,10 +103,15 @@ namespace UnityExplorer.UI.Widgets
OnExpandToggled?.Invoke(cachedTransform); OnExpandToggled?.Invoke(cachedTransform);
} }
private void OnEnableClicked(bool value)
{
OnEnableToggled?.Invoke(cachedTransform);
}
public GameObject CreateContent(GameObject parent) public GameObject CreateContent(GameObject parent)
{ {
UIRoot = UIFactory.CreateUIObject("TransformCell", 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 = UIRoot.GetComponent<RectTransform>();
Rect.anchorMin = new Vector2(0, 1); Rect.anchorMin = new Vector2(0, 1);
Rect.anchorMax = 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); UIFactory.SetLayoutElement(spacerObj, minWidth: 0, flexibleWidth: 0, minHeight: 0, flexibleHeight: 0);
this.spacer = spacerObj.GetComponent<LayoutElement>(); this.spacer = spacerObj.GetComponent<LayoutElement>();
// Expand arrow
ExpandButton = UIFactory.CreateButton(this.UIRoot, "ExpandButton", "►"); ExpandButton = UIFactory.CreateButton(this.UIRoot, "ExpandButton", "►");
UIFactory.SetLayoutElement(ExpandButton.Component.gameObject, minWidth: 15, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); 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); NameButton = UIFactory.CreateButton(this.UIRoot, "NameButton", "Name", null);
UIFactory.SetLayoutElement(NameButton.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0); UIFactory.SetLayoutElement(NameButton.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
var nameLabel = NameButton.Component.GetComponentInChildren<Text>(); var nameLabel = NameButton.Component.GetComponentInChildren<Text>();

View File

@ -60,8 +60,9 @@ namespace UnityExplorer.UI.Widgets
public void OnCellBorrowed(TransformCell cell) public void OnCellBorrowed(TransformCell cell)
{ {
cell.OnExpandToggled += ToggleExpandCell; cell.OnExpandToggled += OnCellExpandToggled;
cell.OnGameObjectClicked += OnGameObjectClicked; cell.OnGameObjectClicked += OnGameObjectClicked;
cell.OnEnableToggled += OnCellEnableToggled;
} }
private void OnGameObjectClicked(GameObject obj) private void OnGameObjectClicked(GameObject obj)
@ -72,6 +73,24 @@ namespace UnityExplorer.UI.Widgets
InspectorManager.Inspect(obj); 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() public void Init()
{ {
ScrollPool.Initialize(this); ScrollPool.Initialize(this);
@ -261,16 +280,5 @@ namespace UnityExplorer.UI.Widgets
else else
cell.Disable(); cell.Disable();
} }
public void ToggleExpandCell(CachedTransform cache)
{
var instanceID = cache.InstanceID;
if (expandedInstanceIDs.Contains(instanceID))
expandedInstanceIDs.Remove(instanceID);
else
expandedInstanceIDs.Add(instanceID);
RefreshData(true);
}
} }
} }