mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-01-09 10:38:59 +08:00
Merge pull request #22 from sinai-dev/3.0.0-rewrite
3.0.0 rewrite, see release for details
This commit is contained in:
commit
48e98a1d33
99
README.md
99
README.md
@ -3,8 +3,9 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> and <b>Mono</b> Unity games, using <a href="https://github.com/HerpDerpinstine/MelonLoader">MelonLoader</a> and <a href="https://github.com/BepInEx/BepInEx">BepInEx</a>.<br><br>
|
||||
|
||||
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> and <b>Mono</b> Unity games, to aid with modding development.
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="../../releases/latest">
|
||||
<img src="https://img.shields.io/github/release/sinai-dev/Explorer.svg" />
|
||||
</a>
|
||||
@ -22,20 +23,19 @@
|
||||
|
||||
## Releases
|
||||
|
||||
| Mod Loader | Il2Cpp | Mono |
|
||||
| Mod Loader | IL2CPP | Mono |
|
||||
| ----------- | ------ | ---- |
|
||||
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Mono.zip) |
|
||||
| [BepInEx](https://github.com/BepInEx/BepInEx) | ❔ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Mono.zip) |
|
||||
|
||||
<b>Il2Cpp Issues:</b>
|
||||
<b>IL2CPP Issues:</b>
|
||||
* Some methods may still fail with a `MissingMethodException`, please let me know if you experience this (with full debug log please).
|
||||
* Reflection may fail with certain types, see [here](https://github.com/knah/Il2CppAssemblyUnhollower#known-issues) for more details.
|
||||
* Scrolling with mouse wheel in the Explorer menu may not work on all games at the moment.
|
||||
* Reflection may fail with certain types, see [here](https://github.com/knah/IL2CPPAssemblyUnhollower#known-issues) for more details.
|
||||
|
||||
## Features
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/sinai-dev/Explorer/master/overview.png">
|
||||
<img src="overview.png">
|
||||
</p>
|
||||
|
||||
* <b>Scene Explorer</b>: Simple menu to traverse the Transform heirarchy of the scene.
|
||||
@ -47,79 +47,54 @@
|
||||
|
||||
## How to install
|
||||
|
||||
### MelonLoader
|
||||
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
|
||||
|
||||
1. Download the relevant release from above.
|
||||
2. Unzip the file into the `Mods` folder in your game's installation directory, created by MelonLoader.
|
||||
3. Make sure it's not in a sub-folder, `Explorer.dll` should be directly in the `Mods\` folder.
|
||||
|
||||
### BepInEx
|
||||
Requires [BepInEx](https://github.com/BepInEx/BepInEx) to be installed for your game.
|
||||
|
||||
1. Download the relevant release from above.
|
||||
2. Unzip the file into the `BepInEx\plugins\` folder in your game's installation directory, created by BepInEx.
|
||||
3. Make sure it's not in a sub-folder, `Explorer.dll` should be directly in the `plugins\` folder.
|
||||
0. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game.
|
||||
1. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
|
||||
2. Take the `UnityExplorer.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
|
||||
3. Take the `UnityExplorer\` folder (with `explorerui.bundle`) and put it in `[GameFolder]\Mods\`, so it looks like `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
|
||||
4. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unhollowed\base\` folder.
|
||||
|
||||
### MelonLoader
|
||||
|
||||
0. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) for your game.
|
||||
1. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
|
||||
2. Take the contents of the release and put it in the `[GameFolder]\Mods\` folder. It should look like `[GameFolder]\Mods\UnityExplorer.dll` and `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
|
||||
|
||||
## Mod Config
|
||||
|
||||
There is a simple Mod Config for the Explorer. You can access the settings via the "Options" page of the main menu.
|
||||
You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.xml` (generated after first launch).
|
||||
|
||||
`Main Menu Toggle` (KeyCode) | Default: `F7`
|
||||
`Main Menu Toggle` (KeyCode)
|
||||
* Default: `F7`
|
||||
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
|
||||
|
||||
`Default Window Size` (Vector2) | Default: `x: 550, y: 700`
|
||||
* Sets the default width and height for all Explorer windows when created.
|
||||
`Force Unlock Mouse` (bool)
|
||||
* Default: `true`
|
||||
* Forces the cursor to be unlocked and visible while the UnityExplorer menu is open, and prevents anything else taking control.
|
||||
|
||||
`Default Items per Page` (int) | Default: `20`
|
||||
`Default Page Limit` (int)
|
||||
* Default: `25`
|
||||
* Sets the default items per page when viewing lists or search results.
|
||||
* <b>Requires a restart to take effect</b>, apart from Reflection Inspector tabs.
|
||||
|
||||
`Enable Bitwise Editing` (bool) | Default: `false`
|
||||
* Whether or not to show the Bitwise Editing helper when inspecting integers
|
||||
|
||||
`Enable Tab View` (bool) | Default: `true`
|
||||
* Whether or not all inspector windows a grouped into a single window with tabs.
|
||||
|
||||
`Default Output Path` (string) | Default: `Mods\Explorer`
|
||||
`Default Output Path` (string)
|
||||
* Default: `Mods\Explorer`
|
||||
* Where output is generated to, by default (for Texture PNG saving, etc).
|
||||
|
||||
## Mouse Control
|
||||
|
||||
Explorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). Explorer also attempts to prevent clicking-through onto the game behind the Explorer menu.
|
||||
|
||||
If you need more mouse control:
|
||||
|
||||
* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinai-dev/VRCExplorerMouseControl)
|
||||
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
|
||||
* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
|
||||
|
||||
For example:
|
||||
```csharp
|
||||
using Explorer;
|
||||
using Harmony; // or 'using HarmonyLib;' for BepInEx
|
||||
// ...
|
||||
// You will need to figure out the relevant Class and Method for your game using dnSpy.
|
||||
[HarmonyPatch(typeof(MyGame.InputManager), nameof(MyGame.InputManager.Update))]
|
||||
public class InputManager_Update
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix()
|
||||
{
|
||||
// prevent method running if menu open, let it run if not.
|
||||
return !ExplorerCore.ShowMenu;
|
||||
}
|
||||
}
|
||||
```
|
||||
`Log Unity Debug` (bool)
|
||||
* Default: `false`
|
||||
* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log.
|
||||
|
||||
## Building
|
||||
|
||||
If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one Il2Cpp and one Mono game, with BepInEx and MelonLoader installed for both.
|
||||
If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both.
|
||||
|
||||
1. Install MelonLoader or BepInEx for your game.
|
||||
2. Open the `src\Explorer.csproj` file in a text editor.
|
||||
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader Il2Cpp game.
|
||||
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader IL2CPP game.
|
||||
4. Open the `src\Explorer.sln` project.
|
||||
5. Select `Solution 'Explorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
|
||||
5. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
|
||||
5. The DLLs are built to the `Release\` folder in the root of the repository.
|
||||
6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
|
||||
|
||||
@ -128,5 +103,5 @@ If you'd like to build this yourself, you will need to have installed BepInEx an
|
||||
Written by Sinai.
|
||||
|
||||
Thanks to:
|
||||
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
|
||||
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted.
|
||||
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for some aspects of the C# Console and Auto-Complete features.
|
||||
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted in IL2CPP.
|
||||
|
BIN
icon.png
BIN
icon.png
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 30 KiB |
BIN
lib/UnityEngine.UI.dll
Normal file
BIN
lib/UnityEngine.UI.dll
Normal file
Binary file not shown.
BIN
overview.png
BIN
overview.png
Binary file not shown.
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 435 KiB |
BIN
resources/explorerui.bundle
Normal file
BIN
resources/explorerui.bundle
Normal file
Binary file not shown.
314
src/CSConsole/AutoCompleter.cs
Normal file
314
src/CSConsole/AutoCompleter.cs
Normal file
@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class AutoCompleter
|
||||
{
|
||||
public static AutoCompleter Instance;
|
||||
|
||||
public const int MAX_LABELS = 500;
|
||||
private const int UPDATES_PER_BATCH = 100;
|
||||
|
||||
public static GameObject m_mainObj;
|
||||
//private static RectTransform m_thisRect;
|
||||
|
||||
private static readonly List<GameObject> m_suggestionButtons = new List<GameObject>();
|
||||
private static readonly List<Text> m_suggestionTexts = new List<Text>();
|
||||
private static readonly List<Text> m_hiddenSuggestionTexts = new List<Text>();
|
||||
|
||||
private static bool m_suggestionsDirty;
|
||||
private static Suggestion[] m_suggestions = new Suggestion[0];
|
||||
private static int m_lastBatchIndex;
|
||||
|
||||
private static string m_prevInput = "NULL";
|
||||
private static int m_lastCaretPos;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
ConstructUI();
|
||||
|
||||
m_mainObj.SetActive(false);
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (!m_mainObj)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CodeEditor.EnableAutocompletes)
|
||||
{
|
||||
if (m_mainObj.activeSelf)
|
||||
{
|
||||
m_mainObj.SetActive(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshButtons();
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
public static void SetSuggestions(Suggestion[] suggestions)
|
||||
{
|
||||
m_suggestions = suggestions;
|
||||
|
||||
m_suggestionsDirty = true;
|
||||
m_lastBatchIndex = 0;
|
||||
}
|
||||
|
||||
private static void RefreshButtons()
|
||||
{
|
||||
if (!m_suggestionsDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_suggestions.Length < 1)
|
||||
{
|
||||
if (m_mainObj.activeSelf)
|
||||
{
|
||||
m_mainObj?.SetActive(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_mainObj.activeSelf)
|
||||
{
|
||||
m_mainObj.SetActive(true);
|
||||
}
|
||||
|
||||
if (m_suggestions.Length < 1 || m_lastBatchIndex >= MAX_LABELS)
|
||||
{
|
||||
m_suggestionsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int end = m_lastBatchIndex + UPDATES_PER_BATCH;
|
||||
for (int i = m_lastBatchIndex; i < end && i < MAX_LABELS; i++)
|
||||
{
|
||||
if (i >= m_suggestions.Length)
|
||||
{
|
||||
if (m_suggestionButtons[i].activeSelf)
|
||||
{
|
||||
m_suggestionButtons[i].SetActive(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_suggestionButtons[i].activeSelf)
|
||||
{
|
||||
m_suggestionButtons[i].SetActive(true);
|
||||
}
|
||||
|
||||
var suggestion = m_suggestions[i];
|
||||
var label = m_suggestionTexts[i];
|
||||
var hiddenLabel = m_hiddenSuggestionTexts[i];
|
||||
|
||||
label.text = suggestion.Full;
|
||||
hiddenLabel.text = suggestion.Addition;
|
||||
|
||||
label.color = suggestion.TextColor;
|
||||
}
|
||||
|
||||
m_lastBatchIndex = i;
|
||||
}
|
||||
|
||||
m_lastBatchIndex++;
|
||||
}
|
||||
|
||||
private static void UpdatePosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
var editor = CSConsolePage.Instance.m_codeEditor;
|
||||
|
||||
if (!editor.InputField.isFocused)
|
||||
return;
|
||||
|
||||
var textGen = editor.InputText.cachedTextGenerator;
|
||||
int caretPos = editor.m_lastCaretPos;
|
||||
|
||||
if (caretPos == m_lastCaretPos)
|
||||
return;
|
||||
|
||||
m_lastCaretPos = caretPos;
|
||||
|
||||
if (caretPos >= 1)
|
||||
caretPos--;
|
||||
|
||||
var pos = textGen.characters[caretPos].cursorPos;
|
||||
|
||||
pos = editor.InputField.transform.TransformPoint(pos);
|
||||
|
||||
m_mainObj.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
|
||||
}
|
||||
catch //(Exception e)
|
||||
{
|
||||
//ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] splitChars = new[] { '{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?' };
|
||||
|
||||
public static void CheckAutocomplete()
|
||||
{
|
||||
var m_codeEditor = CSConsolePage.Instance.m_codeEditor;
|
||||
string input = m_codeEditor.InputField.text;
|
||||
int caretIndex = m_codeEditor.InputField.caretPosition;
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
int start = caretIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, caretIndex - 1) + 1;
|
||||
input = input.Substring(start, caretIndex - start).Trim();
|
||||
}
|
||||
catch (ArgumentException) { }
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(input) && input != m_prevInput)
|
||||
{
|
||||
GetAutocompletes(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearAutocompletes();
|
||||
}
|
||||
|
||||
m_prevInput = input;
|
||||
}
|
||||
|
||||
public static void ClearAutocompletes()
|
||||
{
|
||||
if (CodeEditor.AutoCompletes.Any())
|
||||
{
|
||||
CodeEditor.AutoCompletes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetAutocompletes(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Credit ManylMarco
|
||||
CodeEditor.AutoCompletes.Clear();
|
||||
string[] completions = CSConsolePage.Instance.m_evaluator.GetCompletions(input, out string prefix);
|
||||
if (completions != null)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = input;
|
||||
}
|
||||
|
||||
CodeEditor.AutoCompletes.AddRange(completions
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.Select(x => new Suggestion(x, prefix, Suggestion.Contexts.Other))
|
||||
);
|
||||
}
|
||||
|
||||
string trimmed = input.Trim();
|
||||
if (trimmed.StartsWith("using"))
|
||||
{
|
||||
trimmed = trimmed.Remove(0, 5).Trim();
|
||||
}
|
||||
|
||||
IEnumerable<Suggestion> namespaces = Suggestion.Namespaces
|
||||
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||
.Select(x => new Suggestion(
|
||||
x.Substring(trimmed.Length),
|
||||
x.Substring(0, trimmed.Length),
|
||||
Suggestion.Contexts.Namespace));
|
||||
|
||||
CodeEditor.AutoCompletes.AddRange(namespaces);
|
||||
|
||||
IEnumerable<Suggestion> keywords = Suggestion.Keywords
|
||||
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||
.Select(x => new Suggestion(
|
||||
x.Substring(trimmed.Length),
|
||||
x.Substring(0, trimmed.Length),
|
||||
Suggestion.Contexts.Keyword));
|
||||
|
||||
CodeEditor.AutoCompletes.AddRange(keywords);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log("Autocomplete error:\r\n" + ex.ToString());
|
||||
ClearAutocompletes();
|
||||
}
|
||||
}
|
||||
|
||||
#region UI Construction
|
||||
|
||||
private static void ConstructUI()
|
||||
{
|
||||
var parent = UIManager.CanvasRoot;
|
||||
|
||||
var obj = UIFactory.CreateScrollView(parent, out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
|
||||
|
||||
m_mainObj = obj;
|
||||
|
||||
var mainRect = obj.GetComponent<RectTransform>();
|
||||
//m_thisRect = mainRect;
|
||||
mainRect.pivot = new Vector2(0f, 1f);
|
||||
mainRect.anchorMin = new Vector2(0.45f, 0.45f);
|
||||
mainRect.anchorMax = new Vector2(0.65f, 0.6f);
|
||||
mainRect.offsetMin = Vector2.zero;
|
||||
mainRect.offsetMax = Vector2.zero;
|
||||
|
||||
var mainGroup = content.GetComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childControlHeight = false;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = false;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
for (int i = 0; i < MAX_LABELS; i++)
|
||||
{
|
||||
var buttonObj = UIFactory.CreateButton(content);
|
||||
Button btn = buttonObj.GetComponent<Button>();
|
||||
ColorBlock btnColors = btn.colors;
|
||||
btnColors.normalColor = new Color(0f, 0f, 0f, 0f);
|
||||
btnColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
btn.colors = btnColors;
|
||||
|
||||
var nav = btn.navigation;
|
||||
nav.mode = Navigation.Mode.Vertical;
|
||||
btn.navigation = nav;
|
||||
|
||||
var btnLayout = buttonObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minHeight = 20;
|
||||
|
||||
var text = btn.GetComponentInChildren<Text>();
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
text.color = Color.white;
|
||||
|
||||
var hiddenChild = UIFactory.CreateUIObject("HiddenText", buttonObj);
|
||||
hiddenChild.SetActive(false);
|
||||
var hiddenText = hiddenChild.AddComponent<Text>();
|
||||
m_hiddenSuggestionTexts.Add(hiddenText);
|
||||
btn.onClick.AddListener(UseAutocompleteButton);
|
||||
|
||||
void UseAutocompleteButton()
|
||||
{
|
||||
CSConsolePage.Instance.m_codeEditor.UseAutocomplete(hiddenText.text);
|
||||
}
|
||||
|
||||
m_suggestionButtons.Add(buttonObj);
|
||||
m_suggestionTexts.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
308
src/CSConsole/CSharpLexer.cs
Normal file
308
src/CSConsole/CSharpLexer.cs
Normal file
@ -0,0 +1,308 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.CSConsole.Lexer;
|
||||
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public struct LexerMatchInfo
|
||||
{
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public string htmlColor;
|
||||
}
|
||||
|
||||
public enum DelimiterType
|
||||
{
|
||||
Start,
|
||||
End,
|
||||
};
|
||||
|
||||
public class CSharpLexer
|
||||
{
|
||||
private string inputString;
|
||||
private readonly Matcher[] matchers;
|
||||
private readonly HashSet<char> startDelimiters;
|
||||
private readonly HashSet<char> endDelimiters;
|
||||
private int currentIndex;
|
||||
private int currentLookaheadIndex;
|
||||
|
||||
public char Current { get; private set; }
|
||||
public char Previous { get; private set; }
|
||||
|
||||
public bool EndOfStream => currentLookaheadIndex >= inputString.Length;
|
||||
|
||||
public static char indentOpen = '{';
|
||||
public static char indentClose = '}';
|
||||
private static StringBuilder indentBuilder = new StringBuilder();
|
||||
|
||||
public static char[] delimiters = new[]
|
||||
{
|
||||
'[', ']', '(', ')', '{', '}', ';', ':', ',', '.'
|
||||
};
|
||||
public static CommentMatch commentMatcher = new CommentMatch();
|
||||
public static SymbolMatch symbolMatcher = new SymbolMatch();
|
||||
public static NumberMatch numberMatcher = new NumberMatch();
|
||||
public static StringMatch stringMatcher = new StringMatch();
|
||||
|
||||
public static KeywordMatch validKeywordMatcher = new KeywordMatch
|
||||
{
|
||||
highlightColor = new Color(0.33f, 0.61f, 0.83f, 1.0f),
|
||||
Keywords = new[] { "add", "as", "ascending", "await", "bool", "break", "by", "byte",
|
||||
"case", "catch", "char", "checked", "const", "continue", "decimal", "default", "descending", "do", "dynamic",
|
||||
"else", "equals", "false", "finally", "float", "for", "foreach", "from", "global", "goto", "group",
|
||||
"if", "in", "int", "into", "is", "join", "let", "lock", "long", "new", "null", "object", "on", "orderby", "out",
|
||||
"ref", "remove", "return", "sbyte", "select", "short", "sizeof", "stackalloc", "string",
|
||||
"switch", "throw", "true", "try", "typeof", "uint", "ulong", "ushort", "var", "where", "while", "yield" }
|
||||
};
|
||||
|
||||
public static KeywordMatch invalidKeywordMatcher = new KeywordMatch()
|
||||
{
|
||||
highlightColor = new Color(0.95f, 0.10f, 0.10f, 1.0f),
|
||||
Keywords = new[] { "abstract", "async", "base", "class", "delegate", "enum", "explicit", "extern", "fixed", "get",
|
||||
"implicit", "interface", "internal", "namespace", "operator", "override", "params", "private", "protected", "public",
|
||||
"using", "partial", "readonly", "sealed", "set", "static", "struct", "this", "unchecked", "unsafe", "value", "virtual", "volatile", "void" }
|
||||
};
|
||||
|
||||
// ~~~~~~~ ctor ~~~~~~~
|
||||
|
||||
public CSharpLexer()
|
||||
{
|
||||
startDelimiters = new HashSet<char>(delimiters);
|
||||
endDelimiters = new HashSet<char>(delimiters);
|
||||
|
||||
this.matchers = new Matcher[]
|
||||
{
|
||||
commentMatcher,
|
||||
symbolMatcher,
|
||||
numberMatcher,
|
||||
stringMatcher,
|
||||
validKeywordMatcher,
|
||||
invalidKeywordMatcher,
|
||||
};
|
||||
|
||||
foreach (Matcher lexer in matchers)
|
||||
{
|
||||
foreach (char c in lexer.StartChars)
|
||||
{
|
||||
if (!startDelimiters.Contains(c))
|
||||
startDelimiters.Add(c);
|
||||
}
|
||||
|
||||
foreach (char c in lexer.EndChars)
|
||||
{
|
||||
if (!endDelimiters.Contains(c))
|
||||
endDelimiters.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~ Lex Matching ~~~~~~~
|
||||
|
||||
public IEnumerable<LexerMatchInfo> GetMatches(string input)
|
||||
{
|
||||
if (input == null || matchers == null || matchers.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
inputString = input;
|
||||
Current = ' ';
|
||||
Previous = ' ';
|
||||
currentIndex = 0;
|
||||
currentLookaheadIndex = 0;
|
||||
|
||||
while (!EndOfStream)
|
||||
{
|
||||
bool didMatchLexer = false;
|
||||
|
||||
ReadWhiteSpace();
|
||||
|
||||
foreach (Matcher matcher in matchers)
|
||||
{
|
||||
int startIndex = currentIndex;
|
||||
|
||||
bool isMatched = matcher.IsMatch(this);
|
||||
|
||||
if (isMatched)
|
||||
{
|
||||
int endIndex = currentIndex;
|
||||
|
||||
didMatchLexer = true;
|
||||
|
||||
yield return new LexerMatchInfo
|
||||
{
|
||||
startIndex = startIndex,
|
||||
endIndex = endIndex,
|
||||
htmlColor = matcher.HexColor,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didMatchLexer)
|
||||
{
|
||||
ReadNext();
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~ Indent ~~~~~~~
|
||||
|
||||
public static string GetIndentForInput(string input, int indent, out int caretPosition)
|
||||
{
|
||||
indentBuilder = new StringBuilder();
|
||||
|
||||
indent += 1;
|
||||
|
||||
bool stringState = false;
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] == '"')
|
||||
{
|
||||
stringState = !stringState;
|
||||
}
|
||||
|
||||
if (input[i] == '\n')
|
||||
{
|
||||
indentBuilder.Append('\n');
|
||||
for (int j = 0; j < indent; j++)
|
||||
{
|
||||
indentBuilder.Append("\t");
|
||||
}
|
||||
}
|
||||
else if (input[i] == '\t')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (!stringState && input[i] == indentOpen)
|
||||
{
|
||||
indentBuilder.Append(indentOpen);
|
||||
indent++;
|
||||
}
|
||||
else if (!stringState && input[i] == indentClose)
|
||||
{
|
||||
indentBuilder.Append(indentClose);
|
||||
indent--;
|
||||
}
|
||||
else
|
||||
{
|
||||
indentBuilder.Append(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string formattedSection = indentBuilder.ToString();
|
||||
|
||||
caretPosition = formattedSection.Length - 1;
|
||||
|
||||
for (int i = formattedSection.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (formattedSection[i] == '\n')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
caretPosition = i;
|
||||
break;
|
||||
}
|
||||
|
||||
return formattedSection;
|
||||
}
|
||||
|
||||
public static int GetIndentLevel(string inputString, int startIndex, int endIndex)
|
||||
{
|
||||
int indent = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (inputString[i] == '\t')
|
||||
{
|
||||
indent++;
|
||||
}
|
||||
|
||||
// Check for end line or other characters
|
||||
if (inputString[i] == '\n' || inputString[i] != ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return indent;
|
||||
}
|
||||
|
||||
// Lexer reading
|
||||
|
||||
public char ReadNext()
|
||||
{
|
||||
if (EndOfStream)
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
|
||||
Previous = Current;
|
||||
|
||||
Current = inputString[currentLookaheadIndex];
|
||||
currentLookaheadIndex++;
|
||||
|
||||
return Current;
|
||||
}
|
||||
|
||||
public void Rollback(int amount = -1)
|
||||
{
|
||||
if (amount == -1)
|
||||
{
|
||||
currentLookaheadIndex = currentIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentLookaheadIndex > currentIndex)
|
||||
{
|
||||
currentLookaheadIndex -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
int previousIndex = currentLookaheadIndex - 1;
|
||||
|
||||
if (previousIndex >= inputString.Length)
|
||||
{
|
||||
Previous = inputString[inputString.Length - 1];
|
||||
}
|
||||
else if (previousIndex >= 0)
|
||||
{
|
||||
Previous = inputString[previousIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
Previous = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
currentIndex = currentLookaheadIndex;
|
||||
}
|
||||
|
||||
public bool IsSpecialSymbol(char character, DelimiterType position = DelimiterType.Start)
|
||||
{
|
||||
if (position == DelimiterType.Start)
|
||||
{
|
||||
return startDelimiters.Contains(character);
|
||||
}
|
||||
|
||||
return endDelimiters.Contains(character);
|
||||
}
|
||||
|
||||
private void ReadWhiteSpace()
|
||||
{
|
||||
while (char.IsWhiteSpace(ReadNext()) == true)
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
|
||||
Rollback();
|
||||
}
|
||||
}
|
||||
}
|
481
src/CSConsole/CodeEditor.cs
Normal file
481
src/CSConsole/CodeEditor.cs
Normal file
@ -0,0 +1,481 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Input;
|
||||
using UnityExplorer.CSConsole.Lexer;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
// Handles most of the UI side of the C# console, including syntax highlighting.
|
||||
|
||||
public class CodeEditor
|
||||
{
|
||||
public InputField InputField { get; internal set; }
|
||||
public Text InputText { get; internal set; }
|
||||
public int CurrentIndent { get; private set; }
|
||||
|
||||
public static bool EnableCtrlRShortcut { get; set; } = true;
|
||||
public static bool EnableAutoIndent { get; set; } = true;
|
||||
public static bool EnableAutocompletes { get; set; } = true;
|
||||
public static List<Suggestion> AutoCompletes = new List<Suggestion>();
|
||||
|
||||
public string HighlightedText => inputHighlightText.text;
|
||||
private Text inputHighlightText;
|
||||
|
||||
private readonly CSharpLexer highlightLexer;
|
||||
private readonly StringBuilder sbHighlight;
|
||||
|
||||
internal int m_lastCaretPos;
|
||||
internal int m_fixCaretPos;
|
||||
internal bool m_fixwanted;
|
||||
internal float m_lastSelectAlpha;
|
||||
|
||||
private static readonly KeyCode[] onFocusKeys =
|
||||
{
|
||||
KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow,
|
||||
KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow
|
||||
};
|
||||
|
||||
internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console.
|
||||
|
||||
The following helper methods are available:
|
||||
|
||||
* <color=#add490>Log(""message"")</color> logs a message to the debug console
|
||||
|
||||
* <color=#add490>CurrentTarget()</color> returns the currently inspected target on the Home page
|
||||
|
||||
* <color=#add490>AllTargets()</color> returns an object[] array containing all inspected instances
|
||||
|
||||
* <color=#add490>Inspect(someObject)</color> to inspect an instance, eg. Inspect(Camera.main);
|
||||
|
||||
* <color=#add490>Inspect(typeof(SomeClass))</color> to inspect a Class with static reflection
|
||||
|
||||
* <color=#add490>AddUsing(""SomeNamespace"")</color> adds a using directive to the C# console
|
||||
|
||||
* <color=#add490>GetUsing()</color> logs the current using directives to the debug console
|
||||
|
||||
* <color=#add490>Reset()</color> resets all using directives and variables
|
||||
";
|
||||
|
||||
public CodeEditor()
|
||||
{
|
||||
sbHighlight = new StringBuilder();
|
||||
highlightLexer = new CSharpLexer();
|
||||
|
||||
ConstructUI();
|
||||
|
||||
InputField.onValueChanged.AddListener((string s) => { OnInputChanged(s); });
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (EnableCtrlRShortcut)
|
||||
{
|
||||
if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
|
||||
&& InputManager.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
var text = InputField.text.Trim();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
CSConsolePage.Instance.Evaluate(text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return))
|
||||
AutoIndentCaret();
|
||||
|
||||
if (EnableAutocompletes && InputField.isFocused)
|
||||
{
|
||||
if (InputManager.GetMouseButton(0) || onFocusKeys.Any(it => InputManager.GetKeyDown(it)))
|
||||
UpdateAutocompletes();
|
||||
}
|
||||
|
||||
if (m_fixCaretPos > 0)
|
||||
{
|
||||
if (!m_fixwanted)
|
||||
{
|
||||
EventSystem.current.SetSelectedGameObject(InputField.gameObject, null);
|
||||
m_fixwanted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputField.caretPosition = m_fixCaretPos;
|
||||
InputField.selectionFocusPosition = m_fixCaretPos;
|
||||
|
||||
m_fixwanted = false;
|
||||
m_fixCaretPos = -1;
|
||||
|
||||
var color = InputField.selectionColor;
|
||||
color.a = m_lastSelectAlpha;
|
||||
InputField.selectionColor = color;
|
||||
}
|
||||
}
|
||||
else if (InputField.caretPosition > 0)
|
||||
{
|
||||
m_lastCaretPos = InputField.caretPosition;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateAutocompletes()
|
||||
{
|
||||
AutoCompleter.CheckAutocomplete();
|
||||
AutoCompleter.SetSuggestions(AutoCompletes.ToArray());
|
||||
}
|
||||
|
||||
public void UseAutocomplete(string suggestion)
|
||||
{
|
||||
string input = InputField.text;
|
||||
input = input.Insert(m_lastCaretPos, suggestion);
|
||||
InputField.text = input;
|
||||
|
||||
m_fixCaretPos = m_lastCaretPos += suggestion.Length;
|
||||
|
||||
var color = InputField.selectionColor;
|
||||
m_lastSelectAlpha = color.a;
|
||||
color.a = 0f;
|
||||
InputField.selectionColor = color;
|
||||
|
||||
AutoCompleter.ClearAutocompletes();
|
||||
}
|
||||
|
||||
public void OnInputChanged(string newInput, bool forceUpdate = false)
|
||||
{
|
||||
string newText = newInput;
|
||||
|
||||
UpdateIndent(newInput);
|
||||
|
||||
if (!forceUpdate && string.IsNullOrEmpty(newText))
|
||||
inputHighlightText.text = string.Empty;
|
||||
else
|
||||
inputHighlightText.text = SyntaxHighlightContent(newText);
|
||||
|
||||
UpdateAutocompletes();
|
||||
}
|
||||
|
||||
private void UpdateIndent(string newText)
|
||||
{
|
||||
int caret = InputField.caretPosition;
|
||||
|
||||
int len = newText.Length;
|
||||
if (caret < 0 || caret >= len)
|
||||
{
|
||||
while (caret >= 0 && caret >= len)
|
||||
caret--;
|
||||
|
||||
if (caret < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentIndent = 0;
|
||||
|
||||
bool stringState = false;
|
||||
|
||||
for (int i = 0; i < caret && i < newText.Length; i++)
|
||||
{
|
||||
char character = newText[i];
|
||||
|
||||
if (character == '"')
|
||||
stringState = !stringState;
|
||||
else if (!stringState && character == CSharpLexer.indentOpen)
|
||||
CurrentIndent++;
|
||||
else if (!stringState && character == CSharpLexer.indentClose)
|
||||
CurrentIndent--;
|
||||
}
|
||||
|
||||
if (CurrentIndent < 0)
|
||||
CurrentIndent = 0;
|
||||
}
|
||||
|
||||
private const string CLOSE_COLOR_TAG = "</color>";
|
||||
|
||||
private string SyntaxHighlightContent(string inputText)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
sbHighlight.Length = 0;
|
||||
|
||||
foreach (LexerMatchInfo match in highlightLexer.GetMatches(inputText))
|
||||
{
|
||||
for (int i = offset; i < match.startIndex; i++)
|
||||
{
|
||||
sbHighlight.Append(inputText[i]);
|
||||
}
|
||||
|
||||
sbHighlight.Append($"{match.htmlColor}");
|
||||
|
||||
for (int i = match.startIndex; i < match.endIndex; i++)
|
||||
{
|
||||
sbHighlight.Append(inputText[i]);
|
||||
}
|
||||
|
||||
sbHighlight.Append(CLOSE_COLOR_TAG);
|
||||
|
||||
offset = match.endIndex;
|
||||
}
|
||||
|
||||
for (int i = offset; i < inputText.Length; i++)
|
||||
{
|
||||
sbHighlight.Append(inputText[i]);
|
||||
}
|
||||
|
||||
inputText = sbHighlight.ToString();
|
||||
|
||||
return inputText;
|
||||
}
|
||||
|
||||
private void AutoIndentCaret()
|
||||
{
|
||||
if (CurrentIndent > 0)
|
||||
{
|
||||
string indent = GetAutoIndentTab(CurrentIndent);
|
||||
|
||||
if (indent.Length > 0)
|
||||
{
|
||||
int caretPos = InputField.caretPosition;
|
||||
|
||||
string indentMinusOne = indent.Substring(0, indent.Length - 1);
|
||||
|
||||
// get last index of {
|
||||
// chuck it on the next line if its not already
|
||||
string text = InputField.text;
|
||||
string sub = InputField.text.Substring(0, InputField.caretPosition);
|
||||
int lastIndex = sub.LastIndexOf("{");
|
||||
int offset = lastIndex - 1;
|
||||
if (offset >= 0 && text[offset] != '\n' && text[offset] != '\t')
|
||||
{
|
||||
string open = "\n" + indentMinusOne;
|
||||
|
||||
InputField.text = text.Insert(offset + 1, open);
|
||||
|
||||
caretPos += open.Length;
|
||||
}
|
||||
|
||||
// check if should add auto-close }
|
||||
int numOpen = InputField.text.Where(x => x == CSharpLexer.indentOpen).Count();
|
||||
int numClose = InputField.text.Where(x => x == CSharpLexer.indentClose).Count();
|
||||
|
||||
if (numOpen > numClose)
|
||||
{
|
||||
// add auto-indent closing
|
||||
indentMinusOne = $"\n{indentMinusOne}}}";
|
||||
InputField.text = InputField.text.Insert(caretPos, indentMinusOne);
|
||||
}
|
||||
|
||||
// insert the actual auto indent now
|
||||
InputField.text = InputField.text.Insert(caretPos, indent);
|
||||
|
||||
//InputField.stringPosition = caretPos + indent.Length;
|
||||
InputField.caretPosition = caretPos + indent.Length;
|
||||
}
|
||||
}
|
||||
|
||||
// Update line column and indent positions
|
||||
UpdateIndent(InputField.text);
|
||||
|
||||
InputText.text = InputField.text;
|
||||
//inputText.SetText(InputField.text, true);
|
||||
InputText.Rebuild(CanvasUpdate.Prelayout);
|
||||
InputField.ForceLabelUpdate();
|
||||
InputField.Rebuild(CanvasUpdate.Prelayout);
|
||||
|
||||
OnInputChanged(InputText.text, true);
|
||||
}
|
||||
|
||||
private string GetAutoIndentTab(int amount)
|
||||
{
|
||||
string tab = string.Empty;
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
tab += "\t";
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
// ========== UI CONSTRUCTION =========== //
|
||||
|
||||
public void ConstructUI()
|
||||
{
|
||||
CSConsolePage.Instance.Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport);
|
||||
|
||||
var mainLayout = CSConsolePage.Instance.Content.AddComponent<LayoutElement>();
|
||||
mainLayout.preferredHeight = 500;
|
||||
mainLayout.flexibleHeight = 9000;
|
||||
|
||||
var mainGroup = CSConsolePage.Instance.Content.AddComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
#region TOP BAR
|
||||
|
||||
// Main group object
|
||||
|
||||
var topBarObj = UIFactory.CreateHorizontalGroup(CSConsolePage.Instance.Content);
|
||||
LayoutElement topBarLayout = topBarObj.AddComponent<LayoutElement>();
|
||||
topBarLayout.minHeight = 50;
|
||||
topBarLayout.flexibleHeight = 0;
|
||||
|
||||
var topBarGroup = topBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||
topBarGroup.padding.left = 30;
|
||||
topBarGroup.padding.right = 30;
|
||||
topBarGroup.padding.top = 8;
|
||||
topBarGroup.padding.bottom = 8;
|
||||
topBarGroup.spacing = 10;
|
||||
topBarGroup.childForceExpandHeight = true;
|
||||
topBarGroup.childForceExpandWidth = true;
|
||||
topBarGroup.childControlWidth = true;
|
||||
topBarGroup.childControlHeight = true;
|
||||
topBarGroup.childAlignment = TextAnchor.LowerCenter;
|
||||
|
||||
var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
|
||||
var topBarLabelLayout = topBarLabel.AddComponent<LayoutElement>();
|
||||
topBarLabelLayout.preferredWidth = 150;
|
||||
topBarLabelLayout.flexibleWidth = 5000;
|
||||
var topBarText = topBarLabel.GetComponent<Text>();
|
||||
topBarText.text = "C# Console";
|
||||
topBarText.fontSize = 20;
|
||||
|
||||
// Enable Ctrl+R toggle
|
||||
|
||||
var ctrlRToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle ctrlRToggle, out Text ctrlRToggleText);
|
||||
ctrlRToggle.onValueChanged.AddListener(CtrlRToggleCallback);
|
||||
void CtrlRToggleCallback(bool val)
|
||||
{
|
||||
EnableCtrlRShortcut = val;
|
||||
}
|
||||
|
||||
ctrlRToggleText.text = "Run on Ctrl+R";
|
||||
ctrlRToggleText.alignment = TextAnchor.UpperLeft;
|
||||
var ctrlRLayout = ctrlRToggleObj.AddComponent<LayoutElement>();
|
||||
ctrlRLayout.minWidth = 140;
|
||||
ctrlRLayout.flexibleWidth = 0;
|
||||
ctrlRLayout.minHeight = 25;
|
||||
|
||||
// Enable Suggestions toggle
|
||||
|
||||
var suggestToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle suggestToggle, out Text suggestToggleText);
|
||||
suggestToggle.onValueChanged.AddListener(SuggestToggleCallback);
|
||||
void SuggestToggleCallback(bool val)
|
||||
{
|
||||
EnableAutocompletes = val;
|
||||
AutoCompleter.Update();
|
||||
}
|
||||
|
||||
suggestToggleText.text = "Suggestions";
|
||||
suggestToggleText.alignment = TextAnchor.UpperLeft;
|
||||
var suggestLayout = suggestToggleObj.AddComponent<LayoutElement>();
|
||||
suggestLayout.minWidth = 120;
|
||||
suggestLayout.flexibleWidth = 0;
|
||||
suggestLayout.minHeight = 25;
|
||||
|
||||
// Enable Auto-indent toggle
|
||||
|
||||
var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle autoIndentToggle, out Text autoIndentToggleText);
|
||||
autoIndentToggle.onValueChanged.AddListener(OnIndentChanged);
|
||||
void OnIndentChanged(bool val) => EnableAutoIndent = val;
|
||||
|
||||
autoIndentToggleText.text = "Auto-indent on Enter";
|
||||
autoIndentToggleText.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
var autoIndentLayout = autoIndentToggleObj.AddComponent<LayoutElement>();
|
||||
autoIndentLayout.minWidth = 180;
|
||||
autoIndentLayout.flexibleWidth = 0;
|
||||
autoIndentLayout.minHeight = 25;
|
||||
|
||||
#endregion
|
||||
|
||||
#region CONSOLE INPUT
|
||||
|
||||
int fontSize = 16;
|
||||
|
||||
var inputObj = UIFactory.CreateSrollInputField(CSConsolePage.Instance.Content, out InputFieldScroller consoleScroll, fontSize);
|
||||
|
||||
var inputField = consoleScroll.inputField;
|
||||
|
||||
var mainTextObj = inputField.textComponent.gameObject;
|
||||
var mainTextInput = inputField.textComponent;
|
||||
mainTextInput.supportRichText = false;
|
||||
mainTextInput.color = new Color(1, 1, 1, 0.5f);
|
||||
|
||||
var placeHolderText = inputField.placeholder.GetComponent<Text>();
|
||||
placeHolderText.text = STARTUP_TEXT;
|
||||
placeHolderText.fontSize = fontSize;
|
||||
|
||||
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject);
|
||||
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||
highlightTextRect.pivot = new Vector2(0, 1);
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = new Vector2(20, 0);
|
||||
highlightTextRect.offsetMax = new Vector2(14, 0);
|
||||
|
||||
var highlightTextInput = highlightTextObj.AddComponent<Text>();
|
||||
highlightTextInput.supportRichText = true;
|
||||
highlightTextInput.fontSize = fontSize;
|
||||
|
||||
#endregion
|
||||
|
||||
#region COMPILE BUTTON
|
||||
|
||||
var compileBtnObj = UIFactory.CreateButton(CSConsolePage.Instance.Content);
|
||||
var compileBtnLayout = compileBtnObj.AddComponent<LayoutElement>();
|
||||
compileBtnLayout.preferredWidth = 80;
|
||||
compileBtnLayout.flexibleWidth = 0;
|
||||
compileBtnLayout.minHeight = 45;
|
||||
compileBtnLayout.flexibleHeight = 0;
|
||||
var compileButton = compileBtnObj.GetComponent<Button>();
|
||||
var compileBtnColors = compileButton.colors;
|
||||
compileBtnColors.normalColor = new Color(14f / 255f, 80f / 255f, 14f / 255f);
|
||||
compileButton.colors = compileBtnColors;
|
||||
var btnText = compileBtnObj.GetComponentInChildren<Text>();
|
||||
btnText.text = "Run";
|
||||
btnText.fontSize = 18;
|
||||
btnText.color = Color.white;
|
||||
|
||||
// Set compile button callback now that we have the Input Field reference
|
||||
compileButton.onClick.AddListener(CompileCallback);
|
||||
void CompileCallback()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(inputField.text))
|
||||
{
|
||||
CSConsolePage.Instance.Evaluate(inputField.text.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//mainTextInput.supportRichText = false;
|
||||
|
||||
mainTextInput.font = UIManager.ConsoleFont;
|
||||
placeHolderText.font = UIManager.ConsoleFont;
|
||||
highlightTextInput.font = UIManager.ConsoleFont;
|
||||
|
||||
// reset this after formatting finalized
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
// assign references
|
||||
|
||||
this.InputField = inputField;
|
||||
|
||||
this.InputText = mainTextInput;
|
||||
this.inputHighlightText = highlightTextInput;
|
||||
}
|
||||
}
|
||||
}
|
46
src/CSConsole/Lexer/CommentMatch.cs
Normal file
46
src/CSConsole/Lexer/CommentMatch.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
public class CommentMatch : Matcher
|
||||
{
|
||||
public string lineCommentStart = @"//";
|
||||
public string blockCommentStart = @"/*";
|
||||
public string blockCommentEnd = @"*/";
|
||||
|
||||
public override Color HighlightColor => new Color(0.34f, 0.65f, 0.29f, 1.0f);
|
||||
public override IEnumerable<char> StartChars => new char[] { lineCommentStart[0], blockCommentStart[0] };
|
||||
public override IEnumerable<char> EndChars => new char[] { blockCommentEnd[0] };
|
||||
public override bool IsImplicitMatch(CSharpLexer lexer) => IsMatch(lexer, lineCommentStart) || IsMatch(lexer, blockCommentStart);
|
||||
|
||||
private bool IsMatch(CSharpLexer lexer, string commentType)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(commentType))
|
||||
{
|
||||
lexer.Rollback();
|
||||
|
||||
bool match = true;
|
||||
for (int i = 0; i < commentType.Length; i++)
|
||||
{
|
||||
if (commentType[i] != lexer.ReadNext())
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
// Read until end of line or file
|
||||
while (!IsEndLineOrEndFile(lexer, lexer.ReadNext())) { }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsEndLineOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '\n' || character == '\r';
|
||||
}
|
||||
}
|
97
src/CSConsole/Lexer/KeywordMatch.cs
Normal file
97
src/CSConsole/Lexer/KeywordMatch.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
// I use two different KeywordMatch instances (valid and invalid).
|
||||
// This class just contains common implementations.
|
||||
public class KeywordMatch : Matcher
|
||||
{
|
||||
public string[] Keywords;
|
||||
|
||||
public override Color HighlightColor => highlightColor;
|
||||
public Color highlightColor;
|
||||
|
||||
private readonly HashSet<string> shortlist = new HashSet<string>();
|
||||
private readonly Stack<string> removeList = new Stack<string>();
|
||||
|
||||
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||
{
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shortlist.Clear();
|
||||
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
for (int i = 0; i < Keywords.Length; i++)
|
||||
{
|
||||
if (Keywords[i][0] == currentChar)
|
||||
{
|
||||
shortlist.Add(Keywords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (shortlist.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (lexer.EndOfStream)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
if (char.IsWhiteSpace(currentChar) ||
|
||||
lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
|
||||
{
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (currentIndex >= keyword.Length || keyword[currentIndex] != currentChar)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
src/CSConsole/Lexer/Matcher.cs
Normal file
32
src/CSConsole/Lexer/Matcher.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityExplorer.Unstrip;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
public abstract class Matcher
|
||||
{
|
||||
public abstract Color HighlightColor { get; }
|
||||
|
||||
public string HexColor => htmlColor ?? (htmlColor = "<color=#" + HighlightColor.ToHex() + ">");
|
||||
private string htmlColor;
|
||||
|
||||
public virtual IEnumerable<char> StartChars => Enumerable.Empty<char>();
|
||||
public virtual IEnumerable<char> EndChars => Enumerable.Empty<char>();
|
||||
|
||||
public abstract bool IsImplicitMatch(CSharpLexer lexer);
|
||||
|
||||
public bool IsMatch(CSharpLexer lexer)
|
||||
{
|
||||
if (IsImplicitMatch(lexer))
|
||||
{
|
||||
lexer.Commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
lexer.Rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
39
src/CSConsole/Lexer/NumberMatch.cs
Normal file
39
src/CSConsole/Lexer/NumberMatch.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
public class NumberMatch : Matcher
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.58f, 0.33f, 0.33f, 1.0f);
|
||||
|
||||
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||
{
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matchedNumber = false;
|
||||
|
||||
while (!lexer.EndOfStream)
|
||||
{
|
||||
if (IsNumberOrDecimalPoint(lexer.ReadNext()))
|
||||
{
|
||||
matchedNumber = true;
|
||||
lexer.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
lexer.Rollback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedNumber;
|
||||
}
|
||||
|
||||
private bool IsNumberOrDecimalPoint(char character) => char.IsNumber(character) || character == '.';
|
||||
}
|
||||
|
||||
}
|
26
src/CSConsole/Lexer/StringMatch.cs
Normal file
26
src/CSConsole/Lexer/StringMatch.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
public class StringMatch : Matcher
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.79f, 0.52f, 0.32f, 1.0f);
|
||||
|
||||
public override IEnumerable<char> StartChars => new[] { '"' };
|
||||
public override IEnumerable<char> EndChars => new[] { '"' };
|
||||
|
||||
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||
{
|
||||
if (lexer.ReadNext() == '"')
|
||||
{
|
||||
while (!IsClosingQuoteOrEndFile(lexer, lexer.ReadNext())) { }
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsClosingQuoteOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '"';
|
||||
}
|
||||
}
|
106
src/CSConsole/Lexer/SymbolMatch.cs
Normal file
106
src/CSConsole/Lexer/SymbolMatch.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.CSConsole.Lexer
|
||||
{
|
||||
public class SymbolMatch : Matcher
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.58f, 0.47f, 0.37f, 1.0f);
|
||||
|
||||
private readonly string[] symbols = new[]
|
||||
{
|
||||
"[", "]", "(", ")", ".", "?", ":", "+", "-", "*", "/", "%", "&", "|", "^", "~", "=", "<", ">",
|
||||
"++", "--", "&&", "||", "<<", ">>", "==", "!=", "<=", ">=", "+=", "-=", "*=", "/=", "%=", "&=",
|
||||
"|=", "^=", "<<=", ">>=", "->", "??", "=>",
|
||||
};
|
||||
|
||||
private static readonly List<string> shortlist = new List<string>();
|
||||
private static readonly Stack<string> removeList = new Stack<string>();
|
||||
|
||||
public override IEnumerable<char> StartChars => symbols.Select(s => s[0]);
|
||||
public override IEnumerable<char> EndChars => symbols.Select(s => s[0]);
|
||||
|
||||
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||
{
|
||||
if (lexer == null)
|
||||
return false;
|
||||
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!char.IsLetter(lexer.Previous) &&
|
||||
!char.IsDigit(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shortlist.Clear();
|
||||
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
for (int i = symbols.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (symbols[i][0] == currentChar)
|
||||
shortlist.Add(symbols[i]);
|
||||
}
|
||||
|
||||
if (shortlist.Count == 0)
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
if (lexer.EndOfStream)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
if (char.IsWhiteSpace(currentChar) ||
|
||||
char.IsLetter(currentChar) ||
|
||||
char.IsDigit(currentChar) ||
|
||||
lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
|
||||
{
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (string symbol in shortlist)
|
||||
{
|
||||
if (currentIndex >= symbol.Length || symbol[currentIndex] != currentChar)
|
||||
{
|
||||
removeList.Push(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,20 +6,20 @@ using Mono.CSharp;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
internal class ScriptEvaluator : Evaluator, IDisposable
|
||||
public class ScriptEvaluator : Evaluator, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> StdLib = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||
private static readonly HashSet<string> StdLib = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
"mscorlib", "System.Core", "System", "System.Xml"
|
||||
"mscorlib", "System.Core", "System", "System.Xml"
|
||||
};
|
||||
|
||||
private readonly TextWriter _logger;
|
||||
private readonly TextWriter tw;
|
||||
|
||||
public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
|
||||
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
|
||||
{
|
||||
_logger = logger;
|
||||
this.tw = tw;
|
||||
|
||||
ImportAppdomainAssemblies(ReferenceAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
||||
@ -28,14 +28,17 @@ namespace Explorer.UI.Main
|
||||
public void Dispose()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
||||
_logger.Dispose();
|
||||
tw.Dispose();
|
||||
}
|
||||
|
||||
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
string name = args.LoadedAssembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReferenceAssembly(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
@ -58,11 +61,14 @@ namespace Explorer.UI.Main
|
||||
|
||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
string name = assembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
import(assembly);
|
||||
}
|
||||
}
|
57
src/CSConsole/ScriptInteraction.cs
Normal file
57
src/CSConsole/ScriptInteraction.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using Mono.CSharp;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityExplorer.Inspectors;
|
||||
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
public static void AddUsing(string directive)
|
||||
{
|
||||
CSConsolePage.Instance.AddUsing(directive);
|
||||
}
|
||||
|
||||
public static void GetUsing()
|
||||
{
|
||||
ExplorerCore.Log(CSConsolePage.Instance.m_evaluator.GetUsing());
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
CSConsolePage.Instance.ResetConsole();
|
||||
}
|
||||
|
||||
public static object CurrentTarget()
|
||||
{
|
||||
return InspectorManager.Instance?.m_activeInspector?.Target;
|
||||
}
|
||||
|
||||
public static object[] AllTargets()
|
||||
{
|
||||
int count = InspectorManager.Instance?.m_currentInspectors.Count ?? 0;
|
||||
object[] ret = new object[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ret[i] = InspectorManager.Instance?.m_currentInspectors[i].Target;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void Inspect(object obj)
|
||||
{
|
||||
InspectorManager.Instance.Inspect(obj);
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
InspectorManager.Instance.Inspect(type);
|
||||
}
|
||||
}
|
||||
}
|
69
src/CSConsole/Suggestion.cs
Normal file
69
src/CSConsole/Suggestion.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public struct Suggestion
|
||||
{
|
||||
public enum Contexts
|
||||
{
|
||||
Namespace,
|
||||
Keyword,
|
||||
Other
|
||||
}
|
||||
|
||||
// ~~~~ Instance ~~~~
|
||||
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Contexts Context;
|
||||
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public Color TextColor => GetTextColor();
|
||||
|
||||
public Suggestion(string addition, string prefix, Contexts type)
|
||||
{
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
Context = type;
|
||||
}
|
||||
|
||||
private Color GetTextColor()
|
||||
{
|
||||
switch (Context)
|
||||
{
|
||||
case Contexts.Namespace: return Color.grey;
|
||||
case Contexts.Keyword: return keywordColor;
|
||||
default: return Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~ Static ~~~~
|
||||
|
||||
public static HashSet<string> Namespaces => m_namspaces ?? GetNamespaces();
|
||||
private static HashSet<string> m_namspaces;
|
||||
|
||||
public static HashSet<string> Keywords => m_keywords ?? (m_keywords = new HashSet<string>(CSharpLexer.validKeywordMatcher.Keywords));
|
||||
private static HashSet<string> m_keywords;
|
||||
|
||||
private static readonly Color keywordColor = new Color(80f / 255f, 150f / 255f, 215f / 255f);
|
||||
|
||||
private static HashSet<string> GetNamespaces()
|
||||
{
|
||||
HashSet<string> set = new HashSet<string>(
|
||||
AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(GetTypes)
|
||||
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||
.Select(x => x.Namespace));
|
||||
|
||||
return m_namspaces = set;
|
||||
|
||||
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.UI;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheEnumerated : CacheObjectBase
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public IList RefIList { get; set; }
|
||||
public InteractiveEnumerable ParentEnumeration { get; set; }
|
||||
|
||||
public override bool CanWrite => RefIList != null && ParentEnumeration.OwnerCacheObject.CanWrite;
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
RefIList[Index] = IValue.Value;
|
||||
ParentEnumeration.Value = RefIList;
|
||||
|
||||
ParentEnumeration.OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Explorer.CacheObject;
|
||||
using UnityEngine;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class CacheFactory
|
||||
{
|
||||
public static CacheObjectBase GetCacheObject(object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
return GetCacheObject(obj, ReflectionHelpers.GetActualType(obj));
|
||||
}
|
||||
|
||||
public static CacheObjectBase GetCacheObject(object obj, Type type)
|
||||
{
|
||||
var ret = new CacheObjectBase();
|
||||
ret.Init(obj, type);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static CacheMember GetCacheObject(MemberInfo member, object declaringInstance)
|
||||
{
|
||||
CacheMember ret;
|
||||
|
||||
if (member is MethodInfo mi && CanProcessArgs(mi.GetParameters()))
|
||||
{
|
||||
ret = new CacheMethod();
|
||||
ret.InitMember(mi, declaringInstance);
|
||||
}
|
||||
else if (member is PropertyInfo pi && CanProcessArgs(pi.GetIndexParameters()))
|
||||
{
|
||||
ret = new CacheProperty();
|
||||
ret.InitMember(pi, declaringInstance);
|
||||
}
|
||||
else if (member is FieldInfo fi)
|
||||
{
|
||||
ret = new CacheField();
|
||||
ret.InitMember(fi, declaringInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool CanProcessArgs(ParameterInfo[] parameters)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var pType = param.ParameterType;
|
||||
|
||||
if (pType.IsByRef && pType.HasElementType)
|
||||
{
|
||||
pType = pType.GetElementType();
|
||||
}
|
||||
|
||||
if (pType.IsPrimitive || pType == typeof(string))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Explorer.UI;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheField : CacheMember
|
||||
{
|
||||
public override bool IsStatic => (MemInfo as FieldInfo).IsStatic;
|
||||
|
||||
public override void InitMember(MemberInfo member, object declaringInstance)
|
||||
{
|
||||
base.InitMember(member, declaringInstance);
|
||||
|
||||
base.Init(null, (member as FieldInfo).FieldType);
|
||||
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
if (IValue is InteractiveDictionary iDict)
|
||||
{
|
||||
if (!iDict.EnsureDictionaryIsSupported())
|
||||
{
|
||||
ReflectionException = "Not supported due to TypeInitializationException";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fi = MemInfo as FieldInfo;
|
||||
IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||
|
||||
base.UpdateValue();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
var fi = MemInfo as FieldInfo;
|
||||
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI;
|
||||
using Explorer.UI.Shared;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheMember : CacheObjectBase
|
||||
{
|
||||
public MemberInfo MemInfo { get; set; }
|
||||
public Type DeclaringType { get; set; }
|
||||
public object DeclaringInstance { get; set; }
|
||||
|
||||
public virtual bool IsStatic { get; private set; }
|
||||
|
||||
public override bool HasParameters => m_arguments != null && m_arguments.Length > 0;
|
||||
public override bool IsMember => true;
|
||||
|
||||
public string RichTextName => m_richTextName ?? GetRichTextName();
|
||||
private string m_richTextName;
|
||||
|
||||
public override bool CanWrite => m_canWrite ?? GetCanWrite();
|
||||
private bool? m_canWrite;
|
||||
|
||||
public string ReflectionException { get; set; }
|
||||
|
||||
public bool m_evaluated = false;
|
||||
public bool m_isEvaluating;
|
||||
public ParameterInfo[] m_arguments = new ParameterInfo[0];
|
||||
public string[] m_argumentInput = new string[0];
|
||||
|
||||
public virtual void InitMember(MemberInfo member, object declaringInstance)
|
||||
{
|
||||
MemInfo = member;
|
||||
DeclaringInstance = declaringInstance;
|
||||
DeclaringType = member.DeclaringType;
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public object[] ParseArguments()
|
||||
{
|
||||
if (m_arguments.Length < 1)
|
||||
{
|
||||
return new object[0];
|
||||
}
|
||||
|
||||
var parsedArgs = new List<object>();
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType;
|
||||
|
||||
if (type.IsByRef)
|
||||
{
|
||||
type = type.GetElementType();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
parsedArgs.Add(input);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
|
||||
.Invoke(null, new object[] { input });
|
||||
|
||||
parsedArgs.Add(arg);
|
||||
continue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No input, see if there is a default value.
|
||||
if (HasDefaultValue(m_arguments[i]))
|
||||
{
|
||||
parsedArgs.Add(m_arguments[i].DefaultValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try add a null arg I guess
|
||||
parsedArgs.Add(null);
|
||||
}
|
||||
|
||||
return parsedArgs.ToArray();
|
||||
}
|
||||
|
||||
public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value;
|
||||
|
||||
public void DrawArgsInput()
|
||||
{
|
||||
GUILayout.Label($"<b><color=orange>Arguments:</color></b>", new GUILayoutOption[0]);
|
||||
for (int i = 0; i < this.m_arguments.Length; i++)
|
||||
{
|
||||
var name = this.m_arguments[i].Name;
|
||||
var input = this.m_argumentInput[i];
|
||||
var type = this.m_arguments[i].ParameterType.Name;
|
||||
|
||||
var label = $"<color={Syntax.Class_Instance}>{type}</color> ";
|
||||
label += $"<color={Syntax.Local}>{name}</color>";
|
||||
if (HasDefaultValue(this.m_arguments[i]))
|
||||
{
|
||||
label = $"<i>[{label} = {this.m_arguments[i].DefaultValue ?? "null"}]</i>";
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) });
|
||||
GUILayout.Label(label, new GUILayoutOption[] { GUIHelper.ExpandWidth(false) });
|
||||
this.m_argumentInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUIHelper.ExpandWidth(true) });
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetCanWrite()
|
||||
{
|
||||
if (MemInfo is FieldInfo fi)
|
||||
m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
|
||||
else if (MemInfo is PropertyInfo pi)
|
||||
m_canWrite = pi.CanWrite;
|
||||
else
|
||||
m_canWrite = false;
|
||||
|
||||
return (bool)m_canWrite;
|
||||
}
|
||||
|
||||
private string GetRichTextName()
|
||||
{
|
||||
string memberColor = "";
|
||||
bool isStatic = false;
|
||||
|
||||
if (MemInfo is FieldInfo fi)
|
||||
{
|
||||
if (fi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
memberColor = Syntax.Field_Static;
|
||||
}
|
||||
else
|
||||
memberColor = Syntax.Field_Instance;
|
||||
}
|
||||
else if (MemInfo is MethodInfo mi)
|
||||
{
|
||||
if (mi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
memberColor = Syntax.Method_Static;
|
||||
}
|
||||
else
|
||||
memberColor = Syntax.Method_Instance;
|
||||
}
|
||||
else if (MemInfo is PropertyInfo pi)
|
||||
{
|
||||
if (pi.GetAccessors()[0].IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
memberColor = Syntax.Prop_Static;
|
||||
}
|
||||
else
|
||||
memberColor = Syntax.Prop_Instance;
|
||||
}
|
||||
|
||||
string classColor;
|
||||
if (MemInfo.DeclaringType.IsValueType)
|
||||
{
|
||||
classColor = Syntax.StructGreen;
|
||||
}
|
||||
else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed)
|
||||
{
|
||||
classColor = Syntax.Class_Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
classColor = Syntax.Class_Instance;
|
||||
}
|
||||
|
||||
m_richTextName = $"<color={classColor}>{MemInfo.DeclaringType.Name}</color>.";
|
||||
if (isStatic) m_richTextName += "<i>";
|
||||
m_richTextName += $"<color={memberColor}>{MemInfo.Name}</color>";
|
||||
if (isStatic) m_richTextName += "</i>";
|
||||
|
||||
// generic method args
|
||||
if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
|
||||
{
|
||||
m_richTextName += "<";
|
||||
|
||||
var args = "";
|
||||
for (int i = 0; i < cm.GenericArgs.Length; i++)
|
||||
{
|
||||
if (args != "") args += ", ";
|
||||
args += $"<color={Syntax.StructGreen}>{cm.GenericArgs[i].Name}</color>";
|
||||
}
|
||||
m_richTextName += args;
|
||||
|
||||
m_richTextName += ">";
|
||||
}
|
||||
|
||||
return m_richTextName;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheMethod : CacheMember
|
||||
{
|
||||
private CacheObjectBase m_cachedReturnValue;
|
||||
|
||||
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
|
||||
|
||||
public override bool IsStatic => (MemInfo as MethodInfo).IsStatic;
|
||||
|
||||
public Type[] GenericArgs { get; private set; }
|
||||
public Type[][] GenericConstraints { get; private set; }
|
||||
|
||||
public string[] GenericArgInput = new string[0];
|
||||
|
||||
public override void InitMember(MemberInfo member, object declaringInstance)
|
||||
{
|
||||
base.InitMember(member, declaringInstance);
|
||||
|
||||
var mi = MemInfo as MethodInfo;
|
||||
GenericArgs = mi.GetGenericArguments();
|
||||
|
||||
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
|
||||
.ToArray();
|
||||
|
||||
GenericArgInput = new string[GenericArgs.Length];
|
||||
|
||||
m_arguments = mi.GetParameters();
|
||||
m_argumentInput = new string[m_arguments.Length];
|
||||
|
||||
base.Init(null, mi.ReturnType);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
// CacheMethod cannot UpdateValue directly. Need to Evaluate.
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
MethodInfo mi;
|
||||
if (GenericArgs.Length > 0)
|
||||
{
|
||||
mi = MakeGenericMethodFromInput();
|
||||
if (mi == null) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
mi = MemInfo as MethodInfo;
|
||||
}
|
||||
|
||||
object ret = null;
|
||||
|
||||
try
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
|
||||
m_evaluated = true;
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
}
|
||||
|
||||
if (ret != null)
|
||||
{
|
||||
//m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret);
|
||||
m_cachedReturnValue = CacheFactory.GetCacheObject(ret);
|
||||
m_cachedReturnValue.UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedReturnValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo MakeGenericMethodFromInput()
|
||||
{
|
||||
var mi = MemInfo as MethodInfo;
|
||||
|
||||
var list = new List<Type>();
|
||||
for (int i = 0; i < GenericArgs.Length; i++)
|
||||
{
|
||||
var input = GenericArgInput[i];
|
||||
if (ReflectionHelpers.GetTypeByName(input) is Type t)
|
||||
{
|
||||
if (GenericConstraints[i].Length == 0)
|
||||
{
|
||||
list.Add(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var constraint in GenericConstraints[i].Where(x => x != null))
|
||||
{
|
||||
if (!constraint.IsAssignableFrom(t))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
|
||||
$" Make sure you use the full name, including the NameSpace.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// make into a generic with type list
|
||||
mi = mi.MakeGenericMethod(list.ToArray());
|
||||
|
||||
return mi;
|
||||
}
|
||||
|
||||
// ==== GUI DRAW ====
|
||||
|
||||
//public override void Draw(Rect window, float width)
|
||||
//{
|
||||
// base.Draw(window, width);
|
||||
//}
|
||||
|
||||
public void DrawValue(Rect window, float width)
|
||||
{
|
||||
string typeLabel = $"<color={Syntax.Class_Instance}>{IValue.ValueType.FullName}</color>";
|
||||
|
||||
if (m_evaluated)
|
||||
{
|
||||
if (m_cachedReturnValue != null)
|
||||
{
|
||||
m_cachedReturnValue.IValue.DrawValue(window, width);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> ({typeLabel})", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGenericArgsInput()
|
||||
{
|
||||
GUILayout.Label($"<b><color=orange>Generic Arguments:</color></b>", new GUILayoutOption[0]);
|
||||
|
||||
for (int i = 0; i < this.GenericArgs.Length; i++)
|
||||
{
|
||||
string types = "";
|
||||
if (this.GenericConstraints[i].Length > 0)
|
||||
{
|
||||
foreach (var constraint in this.GenericConstraints[i])
|
||||
{
|
||||
if (types != "") types += ", ";
|
||||
|
||||
string type;
|
||||
|
||||
if (constraint == null)
|
||||
type = "Any";
|
||||
else
|
||||
type = constraint.ToString();
|
||||
|
||||
types += $"<color={Syntax.Class_Instance}>{type}</color>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
types = $"<color={Syntax.Class_Instance}>Any</color>";
|
||||
}
|
||||
var input = this.GenericArgInput[i];
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label(
|
||||
$"<color={Syntax.StructGreen}>{this.GenericArgs[i].Name}</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(15) }
|
||||
);
|
||||
this.GenericArgInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
GUILayout.Label(types, new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheObjectBase
|
||||
{
|
||||
public InteractiveValue IValue;
|
||||
|
||||
public virtual bool CanWrite => false;
|
||||
public virtual bool HasParameters => false;
|
||||
public virtual bool IsMember => false;
|
||||
|
||||
public bool IsStaticClassSearchResult { get; set; }
|
||||
|
||||
public virtual void Init(object obj, Type valueType)
|
||||
{
|
||||
if (valueType == null && obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName);
|
||||
|
||||
InteractiveValue interactive;
|
||||
|
||||
if (valueType == typeof(GameObject) || valueType == typeof(Transform))
|
||||
{
|
||||
interactive = new InteractiveGameObject();
|
||||
}
|
||||
else if (valueType == typeof(Texture2D))
|
||||
{
|
||||
interactive = new InteractiveTexture2D();
|
||||
}
|
||||
else if (valueType == typeof(Texture))
|
||||
{
|
||||
interactive = new InteractiveTexture();
|
||||
}
|
||||
else if (valueType == typeof(Sprite))
|
||||
{
|
||||
interactive = new InteractiveSprite();
|
||||
}
|
||||
else if (valueType.IsPrimitive || valueType == typeof(string))
|
||||
{
|
||||
interactive = new InteractivePrimitive();
|
||||
}
|
||||
else if (valueType.IsEnum)
|
||||
{
|
||||
if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
|
||||
{
|
||||
interactive = new InteractiveFlags();
|
||||
}
|
||||
else
|
||||
{
|
||||
interactive = new InteractiveEnum();
|
||||
}
|
||||
}
|
||||
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
|
||||
{
|
||||
interactive = new InteractiveVector();
|
||||
}
|
||||
else if (valueType == typeof(Quaternion))
|
||||
{
|
||||
interactive = new InteractiveQuaternion();
|
||||
}
|
||||
else if (valueType == typeof(Color))
|
||||
{
|
||||
interactive = new InteractiveColor();
|
||||
}
|
||||
else if (valueType == typeof(Rect))
|
||||
{
|
||||
interactive = new InteractiveRect();
|
||||
}
|
||||
// must check this before IsEnumerable
|
||||
else if (ReflectionHelpers.IsDictionary(valueType))
|
||||
{
|
||||
interactive = new InteractiveDictionary();
|
||||
}
|
||||
else if (ReflectionHelpers.IsEnumerable(valueType))
|
||||
{
|
||||
interactive = new InteractiveEnumerable();
|
||||
}
|
||||
else
|
||||
{
|
||||
interactive = new InteractiveValue();
|
||||
}
|
||||
|
||||
interactive.Value = obj;
|
||||
interactive.ValueType = valueType;
|
||||
|
||||
this.IValue = interactive;
|
||||
this.IValue.OwnerCacheObject = this;
|
||||
|
||||
UpdateValue();
|
||||
|
||||
this.IValue.Init();
|
||||
}
|
||||
|
||||
public virtual void Draw(Rect window, float width)
|
||||
{
|
||||
IValue.Draw(window, width);
|
||||
}
|
||||
|
||||
public virtual void UpdateValue()
|
||||
{
|
||||
IValue.UpdateValue();
|
||||
}
|
||||
|
||||
public virtual void SetValue() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using Explorer.UI;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.CacheObject
|
||||
{
|
||||
public class CacheProperty : CacheMember
|
||||
{
|
||||
public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors()[0].IsStatic;
|
||||
|
||||
public override void InitMember(MemberInfo member, object declaringInstance)
|
||||
{
|
||||
base.InitMember(member, declaringInstance);
|
||||
|
||||
var pi = member as PropertyInfo;
|
||||
|
||||
this.m_arguments = pi.GetIndexParameters();
|
||||
this.m_argumentInput = new string[m_arguments.Length];
|
||||
|
||||
base.Init(null, pi.PropertyType);
|
||||
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
if (HasParameters && !m_isEvaluating)
|
||||
{
|
||||
// Need to enter parameters first.
|
||||
return;
|
||||
}
|
||||
|
||||
if (IValue is InteractiveDictionary iDict)
|
||||
{
|
||||
if (!iDict.EnsureDictionaryIsSupported())
|
||||
{
|
||||
ReflectionException = "Not supported due to TypeInitializationException";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
|
||||
if (pi.CanRead)
|
||||
{
|
||||
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
|
||||
|
||||
IValue.Value = pi.GetValue(target, ParseArguments());
|
||||
|
||||
base.UpdateValue();
|
||||
}
|
||||
else // create a dummy value for Write-Only properties.
|
||||
{
|
||||
if (IValue.ValueType == typeof(string))
|
||||
{
|
||||
IValue.Value = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
IValue.Value = Activator.CreateInstance(IValue.ValueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
|
||||
|
||||
pi.SetValue(target, IValue.Value, ParseArguments());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +1,43 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.Config
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public class ModConfig
|
||||
{
|
||||
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
|
||||
|
||||
[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\Explorer";
|
||||
[XmlIgnore] private const string SETTINGS_PATH = EXPLORER_FOLDER + @"\config.xml";
|
||||
//[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
|
||||
[XmlIgnore] private const string SETTINGS_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.xml";
|
||||
|
||||
[XmlIgnore] public static ModConfig Instance;
|
||||
|
||||
// Actual configs
|
||||
public KeyCode Main_Menu_Toggle = KeyCode.F7;
|
||||
public Vector2 Default_Window_Size = new Vector2(550, 700);
|
||||
public int Default_Page_Limit = 20;
|
||||
public bool Bitwise_Support = false;
|
||||
public bool Tab_View = true;
|
||||
public string Default_Output_Path = @"Mods\Explorer";
|
||||
public KeyCode Main_Menu_Toggle = KeyCode.F7;
|
||||
public bool Force_Unlock_Mouse = true;
|
||||
public int Default_Page_Limit = 25;
|
||||
public string Default_Output_Path = ExplorerCore.EXPLORER_FOLDER;
|
||||
public bool Log_Unity_Debug = false;
|
||||
public bool Save_Logs_To_Disk = true;
|
||||
|
||||
public static event Action OnConfigChanged;
|
||||
|
||||
internal static void InvokeConfigChanged()
|
||||
{
|
||||
OnConfigChanged?.Invoke();
|
||||
}
|
||||
|
||||
public static void OnLoad()
|
||||
{
|
||||
if (!Directory.Exists(EXPLORER_FOLDER))
|
||||
{
|
||||
Directory.CreateDirectory(EXPLORER_FOLDER);
|
||||
}
|
||||
|
||||
if (LoadSettings()) return;
|
||||
if (LoadSettings())
|
||||
return;
|
||||
|
||||
Instance = new ModConfig();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
// returns true if settings successfully loaded
|
||||
public static bool LoadSettings()
|
||||
{
|
||||
if (!File.Exists(SETTINGS_PATH))
|
||||
@ -43,9 +46,7 @@ namespace Explorer.Config
|
||||
try
|
||||
{
|
||||
using (var file = File.OpenRead(SETTINGS_PATH))
|
||||
{
|
||||
Instance = (ModConfig)Serializer.Deserialize(file);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -61,9 +62,7 @@ namespace Explorer.Config
|
||||
File.Delete(SETTINGS_PATH);
|
||||
|
||||
using (var file = File.Create(SETTINGS_PATH))
|
||||
{
|
||||
Serializer.Serialize(file, Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,72 +7,71 @@ using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
using BepInEx.IL2CPP;
|
||||
#endif
|
||||
|
||||
namespace Explorer
|
||||
namespace UnityExplorer
|
||||
{
|
||||
[BepInPlugin(ExplorerCore.GUID, "Explorer", ExplorerCore.VERSION)]
|
||||
#if CPP
|
||||
public class ExplorerBepInPlugin : BasePlugin
|
||||
#else
|
||||
#if MONO
|
||||
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||
public class ExplorerBepInPlugin : BaseUnityPlugin
|
||||
#endif
|
||||
{
|
||||
public static ExplorerBepInPlugin Instance;
|
||||
|
||||
public static ManualLogSource Logging =>
|
||||
#if CPP
|
||||
Instance?.Log;
|
||||
#else
|
||||
Instance?.Logger;
|
||||
#endif
|
||||
public static ManualLogSource Logging => Instance?.Logger;
|
||||
|
||||
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
|
||||
|
||||
#if CPP
|
||||
// temporary for Il2Cpp until scene change delegate works
|
||||
private static string lastSceneName;
|
||||
#endif
|
||||
|
||||
// Init
|
||||
#if CPP
|
||||
public override void Load()
|
||||
{
|
||||
#else
|
||||
internal void Awake()
|
||||
{
|
||||
#endif
|
||||
Instance = this;
|
||||
|
||||
new ExplorerCore();
|
||||
|
||||
// HarmonyInstance.PatchAll();
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||
public class ExplorerBepInPlugin : BasePlugin
|
||||
{
|
||||
public static ExplorerBepInPlugin Instance;
|
||||
|
||||
public static ManualLogSource Logging => Instance?.Log;
|
||||
|
||||
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
|
||||
|
||||
// Init
|
||||
public override void Load()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
|
||||
|
||||
var obj = new GameObject(
|
||||
"ExplorerBehaviour",
|
||||
new Il2CppSystem.Type[]
|
||||
{
|
||||
Il2CppType.Of<ExplorerBehaviour>()
|
||||
}
|
||||
new Il2CppSystem.Type[] { Il2CppType.Of<ExplorerBehaviour>() }
|
||||
);
|
||||
obj.hideFlags = HideFlags.HideAndDontSave;
|
||||
GameObject.DontDestroyOnLoad(obj);
|
||||
#else
|
||||
SceneManager.activeSceneChanged += DoSceneChange;
|
||||
#endif
|
||||
|
||||
new ExplorerCore();
|
||||
|
||||
//HarmonyInstance.PatchAll();
|
||||
// HarmonyInstance.PatchAll();
|
||||
}
|
||||
|
||||
internal static void DoSceneChange(Scene arg0, Scene arg1)
|
||||
{
|
||||
ExplorerCore.OnSceneChange();
|
||||
}
|
||||
|
||||
#if CPP // BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
|
||||
// BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
|
||||
public class ExplorerBehaviour : MonoBehaviour
|
||||
{
|
||||
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
|
||||
@ -82,28 +81,12 @@ namespace Explorer
|
||||
Logging.LogMessage("ExplorerBehaviour.Awake");
|
||||
}
|
||||
|
||||
#endif
|
||||
internal void Update()
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
|
||||
#if CPP
|
||||
var scene = SceneManager.GetActiveScene();
|
||||
if (scene.name != lastSceneName)
|
||||
{
|
||||
lastSceneName = scene.name;
|
||||
DoSceneChange(scene, scene);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal void OnGUI()
|
||||
{
|
||||
ExplorerCore.OnGUI();
|
||||
}
|
||||
#if CPP
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
@ -1,36 +1,36 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Explorer.Config;
|
||||
using Explorer.UI;
|
||||
using Explorer.UI.Inspectors;
|
||||
using Explorer.UI.Main;
|
||||
using Explorer.UI.Shared;
|
||||
using System;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.Input;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Inspectors;
|
||||
using System.IO;
|
||||
using UnityExplorer.Unstrip;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Explorer
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ExplorerCore
|
||||
{
|
||||
public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")";
|
||||
public const string VERSION = "2.1.0";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.explorer";
|
||||
|
||||
public const string PLATFORM =
|
||||
#if CPP
|
||||
"Il2Cpp";
|
||||
#else
|
||||
"Mono";
|
||||
#endif
|
||||
public const string MODLOADER =
|
||||
#if ML
|
||||
"MelonLoader";
|
||||
#else
|
||||
"BepInEx";
|
||||
#endif
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "3.0.0";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.unityexplorer";
|
||||
public const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
|
||||
|
||||
public static ExplorerCore Instance { get; private set; }
|
||||
|
||||
public static bool ShowMenu
|
||||
{
|
||||
get => s_showMenu;
|
||||
set => SetShowMenu(value);
|
||||
}
|
||||
public static bool s_showMenu;
|
||||
|
||||
private static bool s_doneUIInit;
|
||||
private static float s_timeSinceStartup;
|
||||
|
||||
public ExplorerCore()
|
||||
{
|
||||
if (Instance != null)
|
||||
@ -41,76 +41,129 @@ namespace Explorer
|
||||
|
||||
Instance = this;
|
||||
|
||||
ModConfig.OnLoad();
|
||||
if (!Directory.Exists(EXPLORER_FOLDER))
|
||||
Directory.CreateDirectory(EXPLORER_FOLDER);
|
||||
|
||||
new MainMenu();
|
||||
new WindowManager();
|
||||
ModConfig.OnLoad();
|
||||
|
||||
InputManager.Init();
|
||||
ForceUnlockCursor.Init();
|
||||
|
||||
SetupEvents();
|
||||
|
||||
ShowMenu = true;
|
||||
|
||||
Log($"{NAME} initialized.");
|
||||
}
|
||||
|
||||
public static bool ShowMenu
|
||||
{
|
||||
get => m_showMenu;
|
||||
set => SetShowMenu(value);
|
||||
}
|
||||
public static bool m_showMenu;
|
||||
|
||||
private static void SetShowMenu(bool show)
|
||||
{
|
||||
m_showMenu = show;
|
||||
ForceUnlockCursor.UpdateCursorControl();
|
||||
Log($"{NAME} {VERSION} initialized.");
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||
{
|
||||
ShowMenu = !ShowMenu;
|
||||
}
|
||||
if (!s_doneUIInit)
|
||||
CheckUIInit();
|
||||
|
||||
if (ShowMenu)
|
||||
if (MouseInspector.Enabled)
|
||||
MouseInspector.UpdateInspect();
|
||||
else
|
||||
{
|
||||
ForceUnlockCursor.Update();
|
||||
InspectUnderMouse.Update();
|
||||
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||
ShowMenu = !ShowMenu;
|
||||
|
||||
MainMenu.Instance.Update();
|
||||
WindowManager.Instance.Update();
|
||||
if (ShowMenu && s_doneUIInit)
|
||||
UIManager.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnGUI()
|
||||
private static void CheckUIInit()
|
||||
{
|
||||
if (!ShowMenu) return;
|
||||
s_timeSinceStartup += Time.deltaTime;
|
||||
|
||||
var origSkin = GUI.skin;
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
|
||||
MainMenu.Instance.OnGUI();
|
||||
WindowManager.Instance.OnGUI();
|
||||
InspectUnderMouse.OnGUI();
|
||||
|
||||
if (!ResizeDrag.IsMouseInResizeArea && WindowManager.IsMouseInWindow)
|
||||
if (s_timeSinceStartup > 0.1f)
|
||||
{
|
||||
InputManager.ResetInputAxes();
|
||||
s_doneUIInit = true;
|
||||
try
|
||||
{
|
||||
UIManager.Init();
|
||||
Log("Initialized UnityExplorer UI.");
|
||||
|
||||
// temp debug
|
||||
InspectorManager.Instance.Inspect(Tests.TestClass.Instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogWarning($"Exception setting up UI: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
{
|
||||
#if CPP
|
||||
try
|
||||
{
|
||||
Application.add_logMessageReceived(new Action<string, string, LogType>(OnUnityLog));
|
||||
SceneManager.add_sceneLoaded(new Action<Scene, LoadSceneMode>((Scene a, LoadSceneMode b) => { OnSceneLoaded(); }));
|
||||
SceneManager.add_activeSceneChanged(new Action<Scene, Scene>((Scene a, Scene b) => { OnSceneLoaded(); }));
|
||||
}
|
||||
catch { }
|
||||
#else
|
||||
Application.logMessageReceived += OnUnityLog;
|
||||
SceneManager.sceneLoaded += (Scene a, LoadSceneMode b) => { OnSceneLoaded(); };
|
||||
SceneManager.activeSceneChanged += (Scene a, Scene b) => { OnSceneLoaded(); };
|
||||
#endif
|
||||
}
|
||||
|
||||
internal void OnSceneLoaded()
|
||||
{
|
||||
UIManager.OnSceneChange();
|
||||
}
|
||||
|
||||
private static void SetShowMenu(bool show)
|
||||
{
|
||||
s_showMenu = show;
|
||||
|
||||
if (UIManager.CanvasRoot)
|
||||
{
|
||||
UIManager.CanvasRoot.SetActive(show);
|
||||
|
||||
if (show)
|
||||
ForceUnlockCursor.SetEventSystem();
|
||||
else
|
||||
ForceUnlockCursor.ReleaseEventSystem();
|
||||
}
|
||||
|
||||
GUI.skin = origSkin;
|
||||
ForceUnlockCursor.UpdateCursorControl();
|
||||
}
|
||||
|
||||
public static void OnSceneChange()
|
||||
private void OnUnityLog(string message, string stackTrace, LogType type)
|
||||
{
|
||||
ScenePage.Instance?.OnSceneChange();
|
||||
SearchPage.Instance?.OnSceneChange();
|
||||
if (!DebugConsole.LogUnity)
|
||||
return;
|
||||
|
||||
message = $"[UNITY] {message}";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case LogType.Assert:
|
||||
case LogType.Log:
|
||||
Log(message, true);
|
||||
break;
|
||||
case LogType.Warning:
|
||||
LogWarning(message, true);
|
||||
break;
|
||||
case LogType.Exception:
|
||||
case LogType.Error:
|
||||
LogError(message, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Log(object message)
|
||||
public static void Log(object message, bool unity = false)
|
||||
{
|
||||
DebugConsole.Log(message?.ToString());
|
||||
|
||||
if (unity)
|
||||
return;
|
||||
|
||||
#if ML
|
||||
MelonLoader.MelonLogger.Log(message?.ToString());
|
||||
#else
|
||||
@ -118,22 +171,43 @@ namespace Explorer
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogWarning(object message)
|
||||
public static void LogWarning(object message, bool unity = false)
|
||||
{
|
||||
DebugConsole.Log(message?.ToString(), "FFFF00");
|
||||
|
||||
if (unity)
|
||||
return;
|
||||
|
||||
#if ML
|
||||
MelonLoader.MelonLogger.LogWarning(message?.ToString());
|
||||
#else
|
||||
ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
|
||||
ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogError(object message)
|
||||
public static void LogError(object message, bool unity = false)
|
||||
{
|
||||
DebugConsole.Log(message?.ToString(), "FF0000");
|
||||
|
||||
if (unity)
|
||||
return;
|
||||
|
||||
#if ML
|
||||
MelonLoader.MelonLogger.LogError(message?.ToString());
|
||||
#else
|
||||
ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
|
||||
ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public static string RemoveInvalidFilenameChars(string s)
|
||||
{
|
||||
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||
foreach (var c in invalid)
|
||||
{
|
||||
s = s.Replace(c.ToString(), "");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
#if ML
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ExplorerMelonMod : MelonMod
|
||||
{
|
||||
@ -18,19 +15,14 @@ namespace Explorer
|
||||
new ExplorerCore();
|
||||
}
|
||||
|
||||
public override void OnLevelWasLoaded(int level)
|
||||
{
|
||||
ExplorerCore.OnSceneChange();
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
public override void OnLevelWasLoaded(int level)
|
||||
{
|
||||
ExplorerCore.OnGUI();
|
||||
ExplorerCore.Instance.OnSceneLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
#if CPP
|
||||
public static object Il2CppCast(this object obj, Type castTo)
|
||||
{
|
||||
return ReflectionHelpers.Il2CppCast(obj, castTo);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
|
||||
{
|
||||
try
|
||||
{
|
||||
return asm.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
return asm.GetExportedTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return e.Types.Where(t => t != null);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class UnityExtensions
|
||||
{
|
||||
public static string GetGameObjectPath(this Transform _transform)
|
||||
{
|
||||
return GetGameObjectPath(_transform, true);
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(this Transform _transform, bool _includeThisName)
|
||||
{
|
||||
string path = _includeThisName ? ("/" + _transform.name) : "";
|
||||
GameObject gameObject = _transform.gameObject;
|
||||
while (gameObject.transform.parent != null)
|
||||
{
|
||||
gameObject = gameObject.transform.parent.gameObject;
|
||||
path = "/" + gameObject.name + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
33
src/Helpers/EventHelper.cs
Normal file
33
src/Helpers/EventHelper.cs
Normal file
@ -0,0 +1,33 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityExplorer.Helpers
|
||||
{
|
||||
// Possibly temporary, just so Il2Cpp can do the same style "AddListener" as Mono.
|
||||
// Just saves me having a preprocessor directive for every single AddListener.
|
||||
|
||||
public static class EventHelper
|
||||
{
|
||||
public static void AddListener(this UnityEvent action, Action listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
|
||||
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
|
||||
public static void AddListener<T0, T1>(this UnityEvent<T0, T1> action, Action<T0, T1> listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
|
||||
public static void AddListener<T0, T1, T2>(this UnityEvent<T0, T1, T2> action, Action<T0, T1, T2> listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,13 +1,10 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Explorer.Helpers
|
||||
namespace UnityExplorer.Helpers
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||
public static class ICallHelper
|
||||
@ -17,18 +14,16 @@ namespace Explorer.Helpers
|
||||
public static T GetICall<T>(string iCallName) where T : Delegate
|
||||
{
|
||||
if (iCallCache.ContainsKey(iCallName))
|
||||
{
|
||||
return (T)iCallCache[iCallName];
|
||||
}
|
||||
|
||||
var ptr = il2cpp_resolve_icall(iCallName);
|
||||
IntPtr ptr = il2cpp_resolve_icall(iCallName);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
throw new MissingMethodException($"Could not resolve internal call by name '{iCallName}'!");
|
||||
}
|
||||
|
||||
var iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
|
||||
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
|
||||
iCallCache.Add(iCallName, iCall);
|
||||
|
||||
return (T)iCall;
|
||||
|
@ -2,86 +2,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if CPP
|
||||
using ILType = Il2CppSystem.Type;
|
||||
using CppType = Il2CppSystem.Type;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace Explorer.Helpers
|
||||
namespace UnityExplorer.Helpers
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||
public class ReflectionHelpers
|
||||
public static class ReflectionHelpers
|
||||
{
|
||||
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||
|
||||
#if CPP
|
||||
public static ILType GameObjectType => Il2CppType.Of<GameObject>();
|
||||
public static ILType TransformType => Il2CppType.Of<Transform>();
|
||||
public static ILType ObjectType => Il2CppType.Of<UnityEngine.Object>();
|
||||
public static ILType ComponentType => Il2CppType.Of<Component>();
|
||||
public static ILType BehaviourType => Il2CppType.Of<Behaviour>();
|
||||
#else
|
||||
public static Type GameObjectType => typeof(GameObject);
|
||||
public static Type TransformType => typeof(Transform);
|
||||
public static Type ObjectType => typeof(UnityEngine.Object);
|
||||
public static Type ComponentType => typeof(Component);
|
||||
public static Type BehaviourType => typeof(Behaviour);
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
private static readonly Dictionary<Type, IntPtr> ClassPointers = new Dictionary<Type, IntPtr>();
|
||||
|
||||
public static object Il2CppCast(object obj, Type castTo)
|
||||
{
|
||||
if (!(obj is Il2CppSystem.Object ilObj))
|
||||
return obj;
|
||||
|
||||
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
|
||||
return obj as System.Object;
|
||||
|
||||
IntPtr castToPtr;
|
||||
if (!ClassPointers.ContainsKey(castTo))
|
||||
{
|
||||
castToPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||
.MakeGenericType(new Type[] { castTo })
|
||||
.GetField("NativeClassPtr", BF.Public | BF.Static)
|
||||
.GetValue(null);
|
||||
|
||||
ClassPointers.Add(castTo, castToPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
castToPtr = ClassPointers[castTo];
|
||||
}
|
||||
|
||||
if (castToPtr == IntPtr.Zero)
|
||||
return obj;
|
||||
|
||||
var classPtr = il2cpp_object_get_class(ilObj.Pointer);
|
||||
|
||||
if (!il2cpp_class_is_assignable_from(castToPtr, classPtr))
|
||||
return obj;
|
||||
|
||||
if (RuntimeSpecificsStore.IsInjected(castToPtr))
|
||||
return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
|
||||
|
||||
return Activator.CreateInstance(castTo, ilObj.Pointer);
|
||||
}
|
||||
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
|
||||
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
|
||||
|
||||
#endif
|
||||
|
||||
public static Type GetTypeByName(string fullName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
@ -98,33 +37,11 @@ namespace Explorer.Helpers
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetActualType(object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
#if CPP
|
||||
// Need to use GetIl2CppType for Il2CppSystem Objects
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
// Prevent weird behaviour when inspecting an Il2CppSystem.Type object.
|
||||
if (ilObject is ILType)
|
||||
{
|
||||
return typeof(ILType);
|
||||
}
|
||||
|
||||
return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType();
|
||||
}
|
||||
#endif
|
||||
|
||||
// It's a normal object, this is fine
|
||||
return obj.GetType();
|
||||
}
|
||||
|
||||
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj));
|
||||
|
||||
public static Type[] GetAllBaseTypes(Type type)
|
||||
{
|
||||
var list = new List<Type>();
|
||||
List<Type> list = new List<Type>();
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
@ -135,15 +52,147 @@ namespace Explorer.Helpers
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static Type GetActualType(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var type = obj.GetType();
|
||||
#if CPP
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
if (ilObject is CppType)
|
||||
return typeof(CppType);
|
||||
|
||||
if (!string.IsNullOrEmpty(type.Namespace))
|
||||
{
|
||||
// Il2CppSystem-namespace objects should just return GetType,
|
||||
// because using GetIl2CppType returns the System namespace type instead.
|
||||
if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem."))
|
||||
return ilObject.GetType();
|
||||
}
|
||||
|
||||
var il2cppType = ilObject.GetIl2CppType();
|
||||
|
||||
// check if type is injected
|
||||
IntPtr classPtr = il2cpp_object_get_class(ilObject.Pointer);
|
||||
if (RuntimeSpecificsStore.IsInjected(classPtr))
|
||||
{
|
||||
var typeByName = GetTypeByName(il2cppType.FullName);
|
||||
if (typeByName != null)
|
||||
return typeByName;
|
||||
}
|
||||
|
||||
// this should be fine for all other il2cpp objects
|
||||
var getType = GetMonoType(il2cppType);
|
||||
if (getType != null)
|
||||
return getType;
|
||||
}
|
||||
#endif
|
||||
return type;
|
||||
}
|
||||
|
||||
#if CPP
|
||||
private static readonly Dictionary<CppType, Type> Il2CppToMonoType = new Dictionary<CppType, Type>();
|
||||
|
||||
public static Type GetMonoType(CppType cppType)
|
||||
{
|
||||
if (Il2CppToMonoType.ContainsKey(cppType))
|
||||
return Il2CppToMonoType[cppType];
|
||||
|
||||
var getType = Type.GetType(cppType.AssemblyQualifiedName);
|
||||
Il2CppToMonoType.Add(cppType, getType);
|
||||
return getType;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, IntPtr> ClassPointers = new Dictionary<Type, IntPtr>();
|
||||
|
||||
public static object Il2CppCast(this object obj, Type castTo)
|
||||
{
|
||||
if (!(obj is Il2CppSystem.Object ilObj))
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
|
||||
return obj;
|
||||
|
||||
IntPtr classPtr = il2cpp_object_get_class(ilObj.Pointer);
|
||||
|
||||
if (!il2cpp_class_is_assignable_from(castToPtr, classPtr))
|
||||
return obj;
|
||||
|
||||
if (RuntimeSpecificsStore.IsInjected(castToPtr))
|
||||
return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
|
||||
|
||||
return Activator.CreateInstance(castTo, ilObj.Pointer);
|
||||
}
|
||||
|
||||
public static bool Il2CppTypeNotNull(Type type)
|
||||
{
|
||||
return Il2CppTypeNotNull(type, out _);
|
||||
}
|
||||
|
||||
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
|
||||
{
|
||||
if (!ClassPointers.ContainsKey(type))
|
||||
{
|
||||
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||
.MakeGenericType(new Type[] { type })
|
||||
.GetField("NativeClassPtr", BF.Public | BF.Static)
|
||||
.GetValue(null);
|
||||
|
||||
ClassPointers.Add(type, il2cppPtr);
|
||||
}
|
||||
else
|
||||
il2cppPtr = ClassPointers[type];
|
||||
|
||||
return il2cppPtr != IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
|
||||
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
|
||||
|
||||
#endif
|
||||
|
||||
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
|
||||
{
|
||||
try
|
||||
{
|
||||
return asm.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
return asm.GetExportedTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return e.Types.Where(t => t != null);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LoadModule(string module)
|
||||
{
|
||||
#if CPP
|
||||
#if ML
|
||||
var path = $@"MelonLoader\Managed\{module}.dll";
|
||||
string path = $@"MelonLoader\Managed\{module}.dll";
|
||||
#else
|
||||
var path = $@"BepInEx\unhollowed\{module}.dll";
|
||||
#endif
|
||||
if (!File.Exists(path)) return false;
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@ -204,8 +253,17 @@ namespace Explorer.Helpers
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string ExceptionToString(Exception e)
|
||||
public static string ExceptionToString(Exception e, bool innerMost = false)
|
||||
{
|
||||
while (innerMost && e.InnerException != null)
|
||||
{
|
||||
#if CPP
|
||||
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException runtimeEx)
|
||||
break;
|
||||
#endif
|
||||
e = e.InnerException;
|
||||
}
|
||||
|
||||
return e.GetType() + ", " + e.Message;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
#if CPP
|
||||
using Explorer.Unstrip.ImageConversion;
|
||||
using UnityExplorer.Unstrip;
|
||||
#endif
|
||||
|
||||
namespace Explorer.Helpers
|
||||
namespace UnityExplorer.Helpers
|
||||
{
|
||||
public static class Texture2DHelpers
|
||||
{
|
||||
@ -56,14 +53,12 @@ namespace Explorer.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture2D Copy(Texture2D orig, Rect rect, bool isDTXnmNormal = false)
|
||||
public static Texture2D Copy(Texture2D orig, Rect rect) //, bool isDTXnmNormal = false)
|
||||
{
|
||||
Color[] pixels;
|
||||
|
||||
if (!orig.IsReadable())
|
||||
{
|
||||
orig = ForceReadTexture(orig);
|
||||
}
|
||||
|
||||
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
|
||||
@ -77,7 +72,7 @@ namespace Explorer.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
var origFilter = tex.filterMode;
|
||||
FilterMode origFilter = tex.filterMode;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
|
||||
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
|
||||
@ -105,12 +100,10 @@ namespace Explorer.Helpers
|
||||
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
var savepath = dir + @"\" + name + ".png";
|
||||
string savepath = dir + @"\" + name + ".png";
|
||||
|
||||
// Make sure we can EncodeToPNG it.
|
||||
if (tex.format != TextureFormat.ARGB32 || !tex.IsReadable())
|
||||
@ -154,12 +147,12 @@ namespace Explorer.Helpers
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
Color c = colors[i];
|
||||
var c = colors[i];
|
||||
|
||||
c.r = c.a * 2 - 1; // red <- alpha
|
||||
c.g = c.g * 2 - 1; // green is always the same
|
||||
|
||||
Vector2 rg = new Vector2(c.r, c.g); //this is the red-green vector
|
||||
var rg = new Vector2(c.r, c.g); //this is the red-green vector
|
||||
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
|
||||
|
||||
colors[i] = new Color(
|
||||
|
@ -1,8 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.Helpers
|
||||
namespace UnityExplorer.Helpers
|
||||
{
|
||||
public class UnityHelpers
|
||||
public static class UnityHelpers
|
||||
{
|
||||
private static Camera m_mainCamera;
|
||||
|
||||
@ -18,12 +18,45 @@ namespace Explorer.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static string ActiveSceneName
|
||||
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
|
||||
{
|
||||
get
|
||||
var unityObj = obj as Object;
|
||||
if (obj == null)
|
||||
{
|
||||
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target instance is null!");
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (obj is Object)
|
||||
{
|
||||
if (!unityObj)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ToStringLong(this Vector3 vec)
|
||||
{
|
||||
return $"{vec.x:F3}, {vec.y:F3}, {vec.z:F3}";
|
||||
}
|
||||
|
||||
public static string GetTransformPath(this Transform t, bool includeThisName = false)
|
||||
{
|
||||
string path = includeThisName ? t.transform.name : "";
|
||||
|
||||
while (t.parent != null)
|
||||
{
|
||||
t = t.parent;
|
||||
path = $"{t.name}/{path}";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.Input
|
||||
namespace UnityExplorer.Input
|
||||
{
|
||||
public interface IAbstractInput
|
||||
public interface IHandleInput
|
||||
{
|
||||
void Init();
|
||||
|
||||
Vector2 MousePosition { get; }
|
||||
|
||||
bool GetKeyDown(KeyCode key);
|
@ -1,19 +1,16 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.Input;
|
||||
using Explorer.Helpers;
|
||||
using UnityExplorer.Helpers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer
|
||||
namespace UnityExplorer.Input
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Unity style")]
|
||||
public static class InputManager
|
||||
{
|
||||
private static IAbstractInput m_inputModule;
|
||||
private static IHandleInput m_inputModule;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
@ -31,8 +28,6 @@ namespace Explorer
|
||||
ExplorerCore.LogWarning("Could not find any Input module!");
|
||||
m_inputModule = new NoInput();
|
||||
}
|
||||
|
||||
m_inputModule.Init();
|
||||
}
|
||||
|
||||
public static Vector3 MousePosition => m_inputModule.MousePosition;
|
||||
@ -42,47 +37,5 @@ namespace Explorer
|
||||
|
||||
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
|
||||
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
|
||||
|
||||
#if CPP
|
||||
internal delegate void d_ResetInputAxes();
|
||||
public static void ResetInputAxes() => ICallHelper.GetICall<d_ResetInputAxes>("UnityEngine.Input::ResetInputAxes").Invoke();
|
||||
#else
|
||||
public static void ResetInputAxes() => UnityEngine.Input.ResetInputAxes();
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
// public extern static string compositionString { get; }
|
||||
|
||||
internal delegate IntPtr d_get_compositionString();
|
||||
|
||||
public static string compositionString
|
||||
{
|
||||
get
|
||||
{
|
||||
var iCall = ICallHelper.GetICall<d_get_compositionString>("UnityEngine.Input::get_compositionString");
|
||||
return IL2CPP.Il2CppStringToManaged(iCall.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
// public extern static Vector2 compositionCursorPos { get; set; }
|
||||
|
||||
internal delegate void d_get_compositionCursorPos(out Vector2 ret);
|
||||
internal delegate void d_set_compositionCursorPos(ref Vector2 value);
|
||||
|
||||
public static Vector2 compositionCursorPos
|
||||
{
|
||||
get
|
||||
{
|
||||
var iCall = ICallHelper.GetICall<d_get_compositionCursorPos>("UnityEngine.Input::get_compositionCursorPos_Injected");
|
||||
iCall.Invoke(out Vector2 ret);
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
var iCall = ICallHelper.GetICall<d_set_compositionCursorPos>("UnityEngine.Input::set_compositionCursorPos_Injected");
|
||||
iCall.Invoke(ref value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -1,15 +1,35 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityEngine;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.Input
|
||||
namespace UnityExplorer.Input
|
||||
{
|
||||
public class InputSystem : IAbstractInput
|
||||
public class InputSystem : IHandleInput
|
||||
{
|
||||
public InputSystem()
|
||||
{
|
||||
ExplorerCore.Log("Initializing new InputSystem support...");
|
||||
|
||||
m_kbCurrentProp = TKeyboard.GetProperty("current");
|
||||
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
|
||||
|
||||
var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
|
||||
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
|
||||
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
|
||||
|
||||
m_mouseCurrentProp = TMouse.GetProperty("current");
|
||||
m_leftButtonProp = TMouse.GetProperty("leftButton");
|
||||
m_rightButtonProp = TMouse.GetProperty("rightButton");
|
||||
|
||||
m_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
|
||||
.GetProperty("position");
|
||||
|
||||
m_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
|
||||
.MakeGenericType(typeof(Vector2))
|
||||
.GetMethod("ReadValue");
|
||||
}
|
||||
|
||||
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
|
||||
private static Type m_tKeyboard;
|
||||
|
||||
@ -83,28 +103,5 @@ namespace Explorer.Input
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ExplorerCore.Log("Initializing new InputSystem support...");
|
||||
|
||||
m_kbCurrentProp = TKeyboard.GetProperty("current");
|
||||
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
|
||||
|
||||
var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
|
||||
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
|
||||
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
|
||||
|
||||
m_mouseCurrentProp = TMouse.GetProperty("current");
|
||||
m_leftButtonProp = TMouse.GetProperty("leftButton");
|
||||
m_rightButtonProp = TMouse.GetProperty("rightButton");
|
||||
|
||||
m_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
|
||||
.GetProperty("position");
|
||||
|
||||
m_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
|
||||
.MakeGenericType(typeof(Vector2))
|
||||
.GetMethod("ReadValue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,23 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityEngine;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.Input
|
||||
namespace UnityExplorer.Input
|
||||
{
|
||||
public class LegacyInput : IAbstractInput
|
||||
public class LegacyInput : IHandleInput
|
||||
{
|
||||
public LegacyInput()
|
||||
{
|
||||
ExplorerCore.Log("Initializing Legacy Input support...");
|
||||
|
||||
m_mousePositionProp = TInput.GetProperty("mousePosition");
|
||||
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
|
||||
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
|
||||
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
|
||||
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||
}
|
||||
|
||||
public static Type TInput => m_tInput ?? (m_tInput = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
|
||||
private static Type m_tInput;
|
||||
|
||||
@ -28,16 +36,5 @@ namespace Explorer.Input
|
||||
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
|
||||
|
||||
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ExplorerCore.Log("Initializing Legacy Input support...");
|
||||
|
||||
m_mousePositionProp = TInput.GetProperty("mousePosition");
|
||||
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
|
||||
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
|
||||
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
|
||||
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.Input
|
||||
namespace UnityExplorer.Input
|
||||
{
|
||||
// Just a stub for games where no Input module was able to load at all.
|
||||
|
||||
public class NoInput : IAbstractInput
|
||||
public class NoInput : IHandleInput
|
||||
{
|
||||
public Vector2 MousePosition => Vector2.zero;
|
||||
|
||||
@ -17,7 +13,5 @@ namespace Explorer.Input
|
||||
|
||||
public bool GetMouseButton(int btn) => false;
|
||||
public bool GetMouseButtonDown(int btn) => false;
|
||||
|
||||
public void Init() { }
|
||||
}
|
||||
}
|
||||
|
219
src/Inspectors/GameObjects/ChildList.cs
Normal file
219
src/Inspectors/GameObjects/ChildList.cs
Normal file
@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Input;
|
||||
|
||||
namespace UnityExplorer.Inspectors.GameObjects
|
||||
{
|
||||
public class ChildList
|
||||
{
|
||||
internal static ChildList Instance;
|
||||
|
||||
public ChildList()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static PageHandler s_childListPageHandler;
|
||||
private static GameObject s_childListContent;
|
||||
|
||||
private static GameObject[] s_allChildren = new GameObject[0];
|
||||
private static readonly List<GameObject> s_childrenShortlist = new List<GameObject>();
|
||||
private static int s_lastChildCount;
|
||||
|
||||
private static readonly List<Text> s_childListTexts = new List<Text>();
|
||||
private static readonly List<Toggle> s_childListToggles = new List<Toggle>();
|
||||
|
||||
internal void RefreshChildObjectList()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
|
||||
s_allChildren = new GameObject[go.transform.childCount];
|
||||
for (int i = 0; i < go.transform.childCount; i++)
|
||||
{
|
||||
var child = go.transform.GetChild(i);
|
||||
s_allChildren[i] = child.gameObject;
|
||||
}
|
||||
|
||||
var objects = s_allChildren;
|
||||
s_childListPageHandler.ListCount = objects.Length;
|
||||
|
||||
int newCount = 0;
|
||||
|
||||
foreach (var itemIndex in s_childListPageHandler)
|
||||
{
|
||||
newCount++;
|
||||
|
||||
// normalized index starting from 0
|
||||
var i = itemIndex - s_childListPageHandler.StartIndex;
|
||||
|
||||
if (itemIndex >= objects.Length)
|
||||
{
|
||||
if (i > s_lastChildCount || i >= s_childListTexts.Count)
|
||||
break;
|
||||
|
||||
GameObject label = s_childListTexts[i].transform.parent.parent.gameObject;
|
||||
if (label.activeSelf)
|
||||
label.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject obj = objects[itemIndex];
|
||||
|
||||
if (!obj)
|
||||
continue;
|
||||
|
||||
if (i >= s_childrenShortlist.Count)
|
||||
{
|
||||
s_childrenShortlist.Add(obj);
|
||||
AddChildListButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
s_childrenShortlist[i] = obj;
|
||||
}
|
||||
|
||||
var text = s_childListTexts[i];
|
||||
|
||||
var name = obj.name;
|
||||
|
||||
if (obj.transform.childCount > 0)
|
||||
name = $"<color=grey>[{obj.transform.childCount}]</color> {name}";
|
||||
|
||||
text.text = name;
|
||||
text.color = obj.activeSelf ? Color.green : Color.red;
|
||||
|
||||
var tog = s_childListToggles[i];
|
||||
tog.isOn = obj.activeSelf;
|
||||
|
||||
var label = text.transform.parent.parent.gameObject;
|
||||
if (!label.activeSelf)
|
||||
{
|
||||
label.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_lastChildCount = newCount;
|
||||
}
|
||||
|
||||
internal static void OnChildListObjectClicked(int index)
|
||||
{
|
||||
if (GameObjectInspector.ActiveInstance == null)
|
||||
return;
|
||||
|
||||
if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
|
||||
return;
|
||||
|
||||
GameObjectInspector.ActiveInstance.ChangeInspectorTarget(s_childrenShortlist[index]);
|
||||
GameObjectInspector.ActiveInstance.Update();
|
||||
}
|
||||
|
||||
internal static void OnChildListPageTurn()
|
||||
{
|
||||
if (Instance == null)
|
||||
return;
|
||||
|
||||
Instance.RefreshChildObjectList();
|
||||
}
|
||||
|
||||
internal static void OnToggleClicked(int index, bool newVal)
|
||||
{
|
||||
if (GameObjectInspector.ActiveInstance == null)
|
||||
return;
|
||||
|
||||
if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
|
||||
return;
|
||||
|
||||
var obj = s_childrenShortlist[index];
|
||||
obj.SetActive(newVal);
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal void ConstructChildList(GameObject parent)
|
||||
{
|
||||
var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var vertGroup = vertGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||
vertGroup.childForceExpandHeight = false;
|
||||
vertGroup.childForceExpandWidth = false;
|
||||
vertGroup.childControlWidth = true;
|
||||
vertGroup.spacing = 5;
|
||||
var vertLayout = vertGroupObj.AddComponent<LayoutElement>();
|
||||
vertLayout.minWidth = 120;
|
||||
vertLayout.flexibleWidth = 25000;
|
||||
vertLayout.minHeight = 200;
|
||||
vertLayout.flexibleHeight = 5000;
|
||||
|
||||
var childTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
|
||||
var childTitleText = childTitleObj.GetComponent<Text>();
|
||||
childTitleText.text = "Children";
|
||||
childTitleText.color = Color.grey;
|
||||
childTitleText.fontSize = 14;
|
||||
var childTitleLayout = childTitleObj.AddComponent<LayoutElement>();
|
||||
childTitleLayout.minHeight = 30;
|
||||
|
||||
var childrenScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_childListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
|
||||
var contentLayout = childrenScrollObj.GetComponent<LayoutElement>();
|
||||
contentLayout.minHeight = 50;
|
||||
|
||||
s_childListPageHandler = new PageHandler(scroller);
|
||||
s_childListPageHandler.ConstructUI(vertGroupObj);
|
||||
s_childListPageHandler.OnPageChanged += OnChildListPageTurn;
|
||||
}
|
||||
|
||||
internal void AddChildListButton()
|
||||
{
|
||||
int thisIndex = s_childListTexts.Count;
|
||||
|
||||
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_childListContent, new Color(0.07f, 0.07f, 0.07f));
|
||||
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
btnGroup.childForceExpandWidth = true;
|
||||
btnGroup.childControlWidth = true;
|
||||
btnGroup.childForceExpandHeight = false;
|
||||
btnGroup.childControlHeight = true;
|
||||
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||
btnLayout.flexibleWidth = 320;
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.flexibleHeight = 0;
|
||||
btnGroupObj.AddComponent<Mask>();
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
|
||||
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minHeight = 25;
|
||||
toggleLayout.minWidth = 25;
|
||||
toggleText.text = "";
|
||||
toggle.isOn = false;
|
||||
s_childListToggles.Add(toggle);
|
||||
toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); });
|
||||
|
||||
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||
mainBtnLayout.minHeight = 25;
|
||||
mainBtnLayout.flexibleHeight = 0;
|
||||
mainBtnLayout.minWidth = 25;
|
||||
mainBtnLayout.flexibleWidth = 999;
|
||||
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||
ColorBlock mainColors = mainBtn.colors;
|
||||
mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
|
||||
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||
mainBtn.colors = mainColors;
|
||||
mainBtn.onClick.AddListener(() => { OnChildListObjectClicked(thisIndex); });
|
||||
|
||||
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||
mainText.alignment = TextAnchor.MiddleLeft;
|
||||
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
mainText.resizeTextForBestFit = true;
|
||||
mainText.resizeTextMaxSize = 14;
|
||||
mainText.resizeTextMinSize = 10;
|
||||
s_childListTexts.Add(mainText);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
228
src/Inspectors/GameObjects/ComponentList.cs
Normal file
228
src/Inspectors/GameObjects/ComponentList.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityExplorer.Unstrip;
|
||||
//using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Input;
|
||||
|
||||
namespace UnityExplorer.Inspectors.GameObjects
|
||||
{
|
||||
public class ComponentList
|
||||
{
|
||||
internal static ComponentList Instance;
|
||||
|
||||
public ComponentList()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static PageHandler s_compListPageHandler;
|
||||
private static Component[] s_allComps = new Component[0];
|
||||
private static readonly List<Component> s_compShortlist = new List<Component>();
|
||||
private static GameObject s_compListContent;
|
||||
private static readonly List<Text> s_compListTexts = new List<Text>();
|
||||
private static int s_lastCompCount;
|
||||
public static readonly List<Toggle> s_compToggles = new List<Toggle>();
|
||||
|
||||
internal void RefreshComponentList()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
|
||||
s_allComps = go.GetComponents<Component>().ToArray();
|
||||
|
||||
var components = s_allComps;
|
||||
s_compListPageHandler.ListCount = components.Length;
|
||||
|
||||
//int startIndex = m_sceneListPageHandler.StartIndex;
|
||||
|
||||
int newCount = 0;
|
||||
|
||||
foreach (var itemIndex in s_compListPageHandler)
|
||||
{
|
||||
newCount++;
|
||||
|
||||
// normalized index starting from 0
|
||||
var i = itemIndex - s_compListPageHandler.StartIndex;
|
||||
|
||||
if (itemIndex >= components.Length)
|
||||
{
|
||||
if (i > s_lastCompCount || i >= s_compListTexts.Count)
|
||||
break;
|
||||
|
||||
GameObject label = s_compListTexts[i].transform.parent.parent.gameObject;
|
||||
if (label.activeSelf)
|
||||
label.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Component comp = components[itemIndex];
|
||||
|
||||
if (!comp)
|
||||
continue;
|
||||
|
||||
if (i >= s_compShortlist.Count)
|
||||
{
|
||||
s_compShortlist.Add(comp);
|
||||
AddCompListButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
s_compShortlist[i] = comp;
|
||||
}
|
||||
|
||||
var text = s_compListTexts[i];
|
||||
|
||||
text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true);
|
||||
|
||||
var toggle = s_compToggles[i];
|
||||
if (comp is Behaviour behaviour)
|
||||
{
|
||||
if (!toggle.gameObject.activeSelf)
|
||||
toggle.gameObject.SetActive(true);
|
||||
|
||||
toggle.isOn = behaviour.enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toggle.gameObject.activeSelf)
|
||||
toggle.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
var label = text.transform.parent.parent.gameObject;
|
||||
if (!label.activeSelf)
|
||||
{
|
||||
label.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_lastCompCount = newCount;
|
||||
}
|
||||
|
||||
internal static void OnCompToggleClicked(int index, bool value)
|
||||
{
|
||||
var comp = s_compShortlist[index];
|
||||
|
||||
(comp as Behaviour).enabled = value;
|
||||
}
|
||||
|
||||
internal static void OnCompListObjectClicked(int index)
|
||||
{
|
||||
if (index >= s_compShortlist.Count || !s_compShortlist[index])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InspectorManager.Instance.Inspect(s_compShortlist[index]);
|
||||
}
|
||||
|
||||
internal static void OnCompListPageTurn()
|
||||
{
|
||||
if (Instance == null)
|
||||
return;
|
||||
|
||||
Instance.RefreshComponentList();
|
||||
}
|
||||
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal void ConstructCompList(GameObject parent)
|
||||
{
|
||||
var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var vertGroup = vertGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||
vertGroup.childForceExpandHeight = false;
|
||||
vertGroup.childForceExpandWidth = false;
|
||||
vertGroup.childControlWidth = true;
|
||||
vertGroup.spacing = 5;
|
||||
var vertLayout = vertGroupObj.AddComponent<LayoutElement>();
|
||||
vertLayout.minWidth = 120;
|
||||
vertLayout.flexibleWidth = 25000;
|
||||
vertLayout.minHeight = 200;
|
||||
vertLayout.flexibleHeight = 5000;
|
||||
|
||||
var compTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
|
||||
var compTitleText = compTitleObj.GetComponent<Text>();
|
||||
compTitleText.text = "Components";
|
||||
compTitleText.color = Color.grey;
|
||||
compTitleText.fontSize = 14;
|
||||
var childTitleLayout = compTitleObj.AddComponent<LayoutElement>();
|
||||
childTitleLayout.minHeight = 30;
|
||||
|
||||
var compScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_compListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
|
||||
var contentLayout = compScrollObj.AddComponent<LayoutElement>();
|
||||
contentLayout.minHeight = 50;
|
||||
|
||||
s_compListPageHandler = new PageHandler(scroller);
|
||||
s_compListPageHandler.ConstructUI(vertGroupObj);
|
||||
s_compListPageHandler.OnPageChanged += OnCompListPageTurn;
|
||||
}
|
||||
|
||||
internal void AddCompListButton()
|
||||
{
|
||||
int thisIndex = s_compListTexts.Count;
|
||||
|
||||
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
|
||||
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
btnGroup.childForceExpandWidth = true;
|
||||
btnGroup.childControlWidth = true;
|
||||
btnGroup.childForceExpandHeight = false;
|
||||
btnGroup.childControlHeight = true;
|
||||
btnGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minWidth = 25;
|
||||
btnLayout.flexibleWidth = 999;
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.flexibleHeight = 0;
|
||||
btnGroupObj.AddComponent<Mask>();
|
||||
|
||||
// Behaviour enabled toggle
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
|
||||
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minHeight = 25;
|
||||
toggleLayout.minWidth = 25;
|
||||
toggleText.text = "";
|
||||
toggle.isOn = false;
|
||||
s_compToggles.Add(toggle);
|
||||
toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); });
|
||||
|
||||
// Main component button
|
||||
|
||||
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||
mainBtnLayout.minHeight = 25;
|
||||
mainBtnLayout.flexibleHeight = 0;
|
||||
mainBtnLayout.minWidth = 25;
|
||||
mainBtnLayout.flexibleWidth = 999;
|
||||
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||
ColorBlock mainColors = mainBtn.colors;
|
||||
mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
|
||||
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||
mainBtn.colors = mainColors;
|
||||
mainBtn.onClick.AddListener(() => { OnCompListObjectClicked(thisIndex); });
|
||||
|
||||
// Component button text
|
||||
|
||||
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||
mainText.alignment = TextAnchor.MiddleLeft;
|
||||
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
//mainText.color = SyntaxColors.Class_Instance.ToColor();
|
||||
mainText.resizeTextForBestFit = true;
|
||||
mainText.resizeTextMaxSize = 14;
|
||||
mainText.resizeTextMinSize = 8;
|
||||
|
||||
s_compListTexts.Add(mainText);
|
||||
|
||||
// TODO remove component button
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
630
src/Inspectors/GameObjects/GameObjectControls.cs
Normal file
630
src/Inspectors/GameObjects/GameObjectControls.cs
Normal file
@ -0,0 +1,630 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
//using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Input;
|
||||
using UnityExplorer.Unstrip;
|
||||
|
||||
namespace UnityExplorer.Inspectors.GameObjects
|
||||
{
|
||||
public class GameObjectControls
|
||||
{
|
||||
internal static GameObjectControls Instance;
|
||||
|
||||
public GameObjectControls()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private static InputField s_setParentInput;
|
||||
|
||||
private static ControlEditor s_positionControl;
|
||||
private static ControlEditor s_localPosControl;
|
||||
private static ControlEditor s_rotationControl;
|
||||
private static ControlEditor s_scaleControl;
|
||||
|
||||
// Transform Vector editors
|
||||
|
||||
internal struct ControlEditor
|
||||
{
|
||||
public InputField fullValue;
|
||||
public Slider[] sliders;
|
||||
public InputField[] inputs;
|
||||
public Text[] values;
|
||||
}
|
||||
|
||||
internal static bool s_sliderChangedWanted;
|
||||
private static Slider s_currentSlider;
|
||||
private static ControlType s_currentSliderType;
|
||||
private static VectorValue s_currentSliderValueType;
|
||||
private static float s_currentSliderValue;
|
||||
|
||||
internal enum ControlType
|
||||
{
|
||||
position,
|
||||
localPosition,
|
||||
eulerAngles,
|
||||
localScale
|
||||
}
|
||||
|
||||
internal enum VectorValue
|
||||
{
|
||||
x, y, z
|
||||
};
|
||||
|
||||
internal void RefreshControls()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
|
||||
s_positionControl.fullValue.text = go.transform.position.ToStringLong();
|
||||
s_positionControl.values[0].text = go.transform.position.x.ToString("F3");
|
||||
s_positionControl.values[1].text = go.transform.position.y.ToString("F3");
|
||||
s_positionControl.values[2].text = go.transform.position.z.ToString("F3");
|
||||
|
||||
s_localPosControl.fullValue.text = go.transform.localPosition.ToStringLong();
|
||||
s_localPosControl.values[0].text = go.transform.localPosition.x.ToString("F3");
|
||||
s_localPosControl.values[1].text = go.transform.localPosition.y.ToString("F3");
|
||||
s_localPosControl.values[2].text = go.transform.localPosition.z.ToString("F3");
|
||||
|
||||
s_rotationControl.fullValue.text = go.transform.eulerAngles.ToStringLong();
|
||||
s_rotationControl.values[0].text = go.transform.eulerAngles.x.ToString("F3");
|
||||
s_rotationControl.values[1].text = go.transform.eulerAngles.y.ToString("F3");
|
||||
s_rotationControl.values[2].text = go.transform.eulerAngles.z.ToString("F3");
|
||||
|
||||
s_scaleControl.fullValue.text = go.transform.localScale.ToStringLong();
|
||||
s_scaleControl.values[0].text = go.transform.localScale.x.ToString("F3");
|
||||
s_scaleControl.values[1].text = go.transform.localScale.y.ToString("F3");
|
||||
s_scaleControl.values[2].text = go.transform.localScale.z.ToString("F3");
|
||||
|
||||
}
|
||||
|
||||
internal static void OnSetParentClicked()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
|
||||
if (!go)
|
||||
return;
|
||||
|
||||
var input = s_setParentInput.text;
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
go.transform.parent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameObject.Find(input) is GameObject newParent)
|
||||
{
|
||||
go.transform.parent = newParent.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.Log($"Could not find any GameObject from name or path '{input}'! Note: The target must be enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnSliderControlChanged(float value, Slider slider, ControlType controlType, VectorValue vectorValue)
|
||||
{
|
||||
if (value == 0)
|
||||
s_sliderChangedWanted = false;
|
||||
else
|
||||
{
|
||||
if (!s_sliderChangedWanted)
|
||||
{
|
||||
s_sliderChangedWanted = true;
|
||||
s_currentSlider = slider;
|
||||
s_currentSliderType = controlType;
|
||||
s_currentSliderValueType = vectorValue;
|
||||
}
|
||||
|
||||
s_currentSliderValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UpdateSliderControl()
|
||||
{
|
||||
if (!InputManager.GetMouseButton(0))
|
||||
{
|
||||
s_sliderChangedWanted = false;
|
||||
s_currentSlider.value = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameObjectInspector.ActiveInstance == null) return;
|
||||
|
||||
var transform = GameObjectInspector.ActiveInstance.TargetGO.transform;
|
||||
|
||||
// get the current vector for the control type
|
||||
Vector3 vector = Vector2.zero;
|
||||
switch (s_currentSliderType)
|
||||
{
|
||||
case ControlType.position:
|
||||
vector = transform.position; break;
|
||||
case ControlType.localPosition:
|
||||
vector = transform.localPosition; break;
|
||||
case ControlType.eulerAngles:
|
||||
vector = transform.eulerAngles; break;
|
||||
case ControlType.localScale:
|
||||
vector = transform.localScale; break;
|
||||
}
|
||||
|
||||
// apply vector value change
|
||||
switch (s_currentSliderValueType)
|
||||
{
|
||||
case VectorValue.x:
|
||||
vector.x += s_currentSliderValue; break;
|
||||
case VectorValue.y:
|
||||
vector.y += s_currentSliderValue; break;
|
||||
case VectorValue.z:
|
||||
vector.z += s_currentSliderValue; break;
|
||||
}
|
||||
|
||||
// set vector to transform member
|
||||
switch (s_currentSliderType)
|
||||
{
|
||||
case ControlType.position:
|
||||
transform.position = vector; break;
|
||||
case ControlType.localPosition:
|
||||
transform.localPosition = vector; break;
|
||||
case ControlType.eulerAngles:
|
||||
transform.eulerAngles = vector; break;
|
||||
case ControlType.localScale:
|
||||
transform.localScale = vector; break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnVectorControlInputApplied(ControlType controlType, VectorValue vectorValue)
|
||||
{
|
||||
if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return;
|
||||
|
||||
// get relevant input for controltype + value
|
||||
|
||||
InputField[] inputs = null;
|
||||
switch (controlType)
|
||||
{
|
||||
case ControlType.position:
|
||||
inputs = s_positionControl.inputs; break;
|
||||
case ControlType.localPosition:
|
||||
inputs = s_localPosControl.inputs; break;
|
||||
case ControlType.eulerAngles:
|
||||
inputs = s_rotationControl.inputs; break;
|
||||
case ControlType.localScale:
|
||||
inputs = s_scaleControl.inputs; break;
|
||||
}
|
||||
InputField input = inputs[(int)vectorValue];
|
||||
|
||||
float val = float.Parse(input.text);
|
||||
|
||||
// apply transform value
|
||||
|
||||
Vector3 vector = Vector3.zero;
|
||||
var transform = instance.TargetGO.transform;
|
||||
switch (controlType)
|
||||
{
|
||||
case ControlType.position:
|
||||
vector = transform.position; break;
|
||||
case ControlType.localPosition:
|
||||
vector = transform.localPosition; break;
|
||||
case ControlType.eulerAngles:
|
||||
vector = transform.eulerAngles; break;
|
||||
case ControlType.localScale:
|
||||
vector = transform.localScale; break;
|
||||
}
|
||||
|
||||
switch (vectorValue)
|
||||
{
|
||||
case VectorValue.x:
|
||||
vector.x = val; break;
|
||||
case VectorValue.y:
|
||||
vector.y = val; break;
|
||||
case VectorValue.z:
|
||||
vector.z = val; break;
|
||||
}
|
||||
|
||||
// set back to transform
|
||||
switch (controlType)
|
||||
{
|
||||
case ControlType.position:
|
||||
transform.position = vector; break;
|
||||
case ControlType.localPosition:
|
||||
transform.localPosition = vector; break;
|
||||
case ControlType.eulerAngles:
|
||||
transform.eulerAngles = vector; break;
|
||||
case ControlType.localScale:
|
||||
transform.localScale = vector; break;
|
||||
}
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal void ConstructControls(GameObject parent)
|
||||
{
|
||||
var controlsObj = UIFactory.CreateVerticalGroup(parent, new Color(0.07f, 0.07f, 0.07f));
|
||||
var controlsGroup = controlsObj.GetComponent<VerticalLayoutGroup>();
|
||||
controlsGroup.childForceExpandWidth = false;
|
||||
controlsGroup.childControlWidth = true;
|
||||
controlsGroup.childForceExpandHeight = false;
|
||||
controlsGroup.spacing = 5;
|
||||
controlsGroup.padding.top = 4;
|
||||
controlsGroup.padding.left = 4;
|
||||
controlsGroup.padding.right = 4;
|
||||
controlsGroup.padding.bottom = 4;
|
||||
|
||||
// ~~~~~~ Top row ~~~~~~
|
||||
|
||||
var topRow = UIFactory.CreateHorizontalGroup(controlsObj, new Color(1, 1, 1, 0));
|
||||
var topRowGroup = topRow.GetComponent<HorizontalLayoutGroup>();
|
||||
topRowGroup.childForceExpandWidth = false;
|
||||
topRowGroup.childControlWidth = true;
|
||||
topRowGroup.childForceExpandHeight = false;
|
||||
topRowGroup.childControlHeight = true;
|
||||
topRowGroup.spacing = 5;
|
||||
|
||||
var hideButtonObj = UIFactory.CreateButton(topRow);
|
||||
var hideButton = hideButtonObj.GetComponent<Button>();
|
||||
var hideColors = hideButton.colors;
|
||||
hideColors.normalColor = new Color(0.16f, 0.16f, 0.16f);
|
||||
hideButton.colors = hideColors;
|
||||
var hideText = hideButtonObj.GetComponentInChildren<Text>();
|
||||
hideText.text = "Show";
|
||||
hideText.fontSize = 14;
|
||||
var hideButtonLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||
hideButtonLayout.minWidth = 40;
|
||||
hideButtonLayout.flexibleWidth = 0;
|
||||
hideButtonLayout.minHeight = 25;
|
||||
hideButtonLayout.flexibleHeight = 0;
|
||||
|
||||
var topTitle = UIFactory.CreateLabel(topRow, TextAnchor.MiddleLeft);
|
||||
var topText = topTitle.GetComponent<Text>();
|
||||
topText.text = "Controls";
|
||||
var titleLayout = topTitle.AddComponent<LayoutElement>();
|
||||
titleLayout.minWidth = 100;
|
||||
titleLayout.flexibleWidth = 9500;
|
||||
titleLayout.minHeight = 25;
|
||||
|
||||
//// ~~~~~~~~ Content ~~~~~~~~ //
|
||||
|
||||
var contentObj = UIFactory.CreateVerticalGroup(controlsObj, new Color(1, 1, 1, 0));
|
||||
var contentGroup = contentObj.GetComponent<VerticalLayoutGroup>();
|
||||
contentGroup.childForceExpandHeight = false;
|
||||
contentGroup.childControlHeight = true;
|
||||
contentGroup.spacing = 5;
|
||||
contentGroup.childForceExpandWidth = true;
|
||||
contentGroup.childControlWidth = true;
|
||||
|
||||
// ~~ add hide button callback now that we have scroll reference ~~
|
||||
hideButton.onClick.AddListener(OnHideClicked);
|
||||
void OnHideClicked()
|
||||
{
|
||||
if (hideText.text == "Show")
|
||||
{
|
||||
hideText.text = "Hide";
|
||||
contentObj.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
hideText.text = "Show";
|
||||
contentObj.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// transform controls
|
||||
ConstructVector3Editor(contentObj, "Position", ControlType.position, out s_positionControl);
|
||||
ConstructVector3Editor(contentObj, "Local Position", ControlType.localPosition, out s_localPosControl);
|
||||
ConstructVector3Editor(contentObj, "Rotation", ControlType.eulerAngles, out s_rotationControl);
|
||||
ConstructVector3Editor(contentObj, "Scale", ControlType.localScale, out s_scaleControl);
|
||||
|
||||
// set parent
|
||||
ConstructSetParent(contentObj);
|
||||
|
||||
// bottom row buttons
|
||||
ConstructBottomButtons(contentObj);
|
||||
|
||||
// set controls content inactive now that content is made (otherwise TMP font size goes way too big?)
|
||||
contentObj.SetActive(false);
|
||||
}
|
||||
|
||||
internal void ConstructSetParent(GameObject contentObj)
|
||||
{
|
||||
var setParentGroupObj = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
|
||||
var setParentGroup = setParentGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
setParentGroup.childForceExpandHeight = false;
|
||||
setParentGroup.childControlHeight = true;
|
||||
setParentGroup.childForceExpandWidth = false;
|
||||
setParentGroup.childControlWidth = true;
|
||||
setParentGroup.spacing = 5;
|
||||
var setParentLayout = setParentGroupObj.AddComponent<LayoutElement>();
|
||||
setParentLayout.minHeight = 25;
|
||||
setParentLayout.flexibleHeight = 0;
|
||||
|
||||
var setParentLabelObj = UIFactory.CreateLabel(setParentGroupObj, TextAnchor.MiddleLeft);
|
||||
var setParentLabel = setParentLabelObj.GetComponent<Text>();
|
||||
setParentLabel.text = "Set Parent:";
|
||||
setParentLabel.color = Color.grey;
|
||||
setParentLabel.fontSize = 14;
|
||||
var setParentLabelLayout = setParentLabelObj.AddComponent<LayoutElement>();
|
||||
setParentLabelLayout.minWidth = 110;
|
||||
setParentLabelLayout.minHeight = 25;
|
||||
setParentLabelLayout.flexibleWidth = 0;
|
||||
|
||||
var setParentInputObj = UIFactory.CreateInputField(setParentGroupObj);
|
||||
s_setParentInput = setParentInputObj.GetComponent<InputField>();
|
||||
var placeholderInput = s_setParentInput.placeholder.GetComponent<Text>();
|
||||
placeholderInput.text = "Enter a GameObject name or path...";
|
||||
var setParentInputLayout = setParentInputObj.AddComponent<LayoutElement>();
|
||||
setParentInputLayout.minHeight = 25;
|
||||
setParentInputLayout.preferredWidth = 400;
|
||||
setParentInputLayout.flexibleWidth = 9999;
|
||||
|
||||
var applyButtonObj = UIFactory.CreateButton(setParentGroupObj);
|
||||
var applyButton = applyButtonObj.GetComponent<Button>();
|
||||
applyButton.onClick.AddListener(OnSetParentClicked);
|
||||
var applyText = applyButtonObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
var applyLayout = applyButtonObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 55;
|
||||
applyLayout.flexibleWidth = 0;
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.flexibleHeight = 0;
|
||||
}
|
||||
|
||||
internal void ConstructVector3Editor(GameObject parent, string title, ControlType type, out ControlEditor editor)
|
||||
{
|
||||
editor = new ControlEditor();
|
||||
|
||||
var topBarObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var topGroup = topBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||
topGroup.childForceExpandWidth = false;
|
||||
topGroup.childControlWidth = true;
|
||||
topGroup.childForceExpandHeight = false;
|
||||
topGroup.childControlHeight = true;
|
||||
topGroup.spacing = 5;
|
||||
var topLayout = topBarObj.AddComponent<LayoutElement>();
|
||||
topLayout.minHeight = 25;
|
||||
topLayout.flexibleHeight = 0;
|
||||
|
||||
var titleObj = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
|
||||
var titleText = titleObj.GetComponent<Text>();
|
||||
titleText.text = title;
|
||||
titleText.color = Color.grey;
|
||||
titleText.fontSize = 14;
|
||||
var titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||
titleLayout.minWidth = 110;
|
||||
titleLayout.flexibleWidth = 0;
|
||||
titleLayout.minHeight = 25;
|
||||
|
||||
// expand button
|
||||
var expandButtonObj = UIFactory.CreateButton(topBarObj);
|
||||
var expandButton = expandButtonObj.GetComponent<Button>();
|
||||
var expandText = expandButtonObj.GetComponentInChildren<Text>();
|
||||
expandText.text = "▼";
|
||||
expandText.fontSize = 12;
|
||||
var btnLayout = expandButtonObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minWidth = 35;
|
||||
btnLayout.flexibleWidth = 0;
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.flexibleHeight = 0;
|
||||
|
||||
// readonly value input
|
||||
|
||||
var valueInputObj = UIFactory.CreateInputField(topBarObj);
|
||||
var valueInput = valueInputObj.GetComponent<InputField>();
|
||||
valueInput.readOnly = true;
|
||||
//valueInput.richText = true;
|
||||
//valueInput.isRichTextEditingAllowed = true;
|
||||
var valueInputLayout = valueInputObj.AddComponent<LayoutElement>();
|
||||
valueInputLayout.minHeight = 25;
|
||||
valueInputLayout.flexibleHeight = 0;
|
||||
valueInputLayout.preferredWidth = 400;
|
||||
valueInputLayout.flexibleWidth = 9999;
|
||||
|
||||
editor.fullValue = valueInput;
|
||||
|
||||
editor.sliders = new Slider[3];
|
||||
editor.inputs = new InputField[3];
|
||||
editor.values = new Text[3];
|
||||
|
||||
var xRow = ConstructEditorRow(parent, editor, type, VectorValue.x);
|
||||
xRow.SetActive(false);
|
||||
var yRow = ConstructEditorRow(parent, editor, type, VectorValue.y);
|
||||
yRow.SetActive(false);
|
||||
var zRow = ConstructEditorRow(parent, editor, type, VectorValue.z);
|
||||
zRow.SetActive(false);
|
||||
|
||||
// add expand callback now that we have group reference
|
||||
expandButton.onClick.AddListener(ToggleExpand);
|
||||
void ToggleExpand()
|
||||
{
|
||||
if (xRow.activeSelf)
|
||||
{
|
||||
xRow.SetActive(false);
|
||||
yRow.SetActive(false);
|
||||
zRow.SetActive(false);
|
||||
expandText.text = "▼";
|
||||
}
|
||||
else
|
||||
{
|
||||
xRow.SetActive(true);
|
||||
yRow.SetActive(true);
|
||||
zRow.SetActive(true);
|
||||
expandText.text = "▲";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal GameObject ConstructEditorRow(GameObject parent, ControlEditor editor, ControlType type, VectorValue vectorValue)
|
||||
{
|
||||
var rowObject = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var rowGroup = rowObject.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.childForceExpandWidth = false;
|
||||
rowGroup.childControlWidth = true;
|
||||
rowGroup.childForceExpandHeight = false;
|
||||
rowGroup.childControlHeight = true;
|
||||
rowGroup.spacing = 5;
|
||||
var rowLayout = rowObject.AddComponent<LayoutElement>();
|
||||
rowLayout.minHeight = 25;
|
||||
rowLayout.flexibleHeight = 0;
|
||||
rowLayout.minWidth = 100;
|
||||
|
||||
// Value labels
|
||||
|
||||
var labelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
|
||||
var labelText = labelObj.GetComponent<Text>();
|
||||
labelText.color = Color.cyan;
|
||||
labelText.text = $"{vectorValue.ToString().ToUpper()}:";
|
||||
labelText.fontSize = 14;
|
||||
labelText.resizeTextMaxSize = 14;
|
||||
labelText.resizeTextForBestFit = true;
|
||||
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||
labelLayout.minHeight = 25;
|
||||
labelLayout.flexibleHeight = 0;
|
||||
labelLayout.minWidth = 25;
|
||||
labelLayout.flexibleWidth = 0;
|
||||
|
||||
// actual value label
|
||||
var valueLabelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
|
||||
var valueLabel = valueLabelObj.GetComponent<Text>();
|
||||
editor.values[(int)vectorValue] = valueLabel;
|
||||
var valueLabelLayout = valueLabelObj.AddComponent<LayoutElement>();
|
||||
valueLabelLayout.minWidth = 85;
|
||||
valueLabelLayout.flexibleWidth = 0;
|
||||
valueLabelLayout.minHeight = 25;
|
||||
|
||||
// input field
|
||||
|
||||
var inputHolder = UIFactory.CreateVerticalGroup(rowObject, new Color(1, 1, 1, 0));
|
||||
var inputHolderGroup = inputHolder.GetComponent<VerticalLayoutGroup>();
|
||||
inputHolderGroup.childForceExpandHeight = false;
|
||||
inputHolderGroup.childControlHeight = true;
|
||||
inputHolderGroup.childForceExpandWidth = false;
|
||||
inputHolderGroup.childControlWidth = true;
|
||||
|
||||
var inputObj = UIFactory.CreateInputField(inputHolder);
|
||||
var input = inputObj.GetComponent<InputField>();
|
||||
input.characterValidation = InputField.CharacterValidation.Decimal;
|
||||
|
||||
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||
inputLayout.minHeight = 25;
|
||||
inputLayout.flexibleHeight = 0;
|
||||
inputLayout.minWidth = 90;
|
||||
inputLayout.flexibleWidth = 50;
|
||||
|
||||
editor.inputs[(int)vectorValue] = input;
|
||||
|
||||
// apply button
|
||||
|
||||
var applyBtnObj = UIFactory.CreateButton(rowObject);
|
||||
var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
applyText.fontSize = 14;
|
||||
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 60;
|
||||
applyLayout.minHeight = 25;
|
||||
|
||||
applyBtn.onClick.AddListener(() => { OnVectorControlInputApplied(type, vectorValue); });
|
||||
|
||||
// Slider
|
||||
|
||||
var sliderObj = UIFactory.CreateSlider(rowObject);
|
||||
sliderObj.transform.Find("Fill Area").gameObject.SetActive(false);
|
||||
var sliderLayout = sliderObj.AddComponent<LayoutElement>();
|
||||
sliderLayout.minHeight = 20;
|
||||
sliderLayout.flexibleHeight = 0;
|
||||
sliderLayout.minWidth = 200;
|
||||
sliderLayout.flexibleWidth = 9000;
|
||||
var slider = sliderObj.GetComponent<Slider>();
|
||||
var sliderColors = slider.colors;
|
||||
sliderColors.normalColor = new Color(0.65f, 0.65f, 0.65f);
|
||||
slider.colors = sliderColors;
|
||||
slider.minValue = -2;
|
||||
slider.maxValue = 2;
|
||||
slider.value = 0;
|
||||
slider.onValueChanged.AddListener((float val) => { OnSliderControlChanged(val, slider, type, vectorValue); });
|
||||
editor.sliders[(int)vectorValue] = slider;
|
||||
|
||||
return rowObject;
|
||||
}
|
||||
|
||||
internal void ConstructBottomButtons(GameObject contentObj)
|
||||
{
|
||||
var bottomRow = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
|
||||
var bottomGroup = bottomRow.GetComponent<HorizontalLayoutGroup>();
|
||||
bottomGroup.childForceExpandWidth = true;
|
||||
bottomGroup.childControlWidth = true;
|
||||
bottomGroup.spacing = 4;
|
||||
var bottomLayout = bottomRow.AddComponent<LayoutElement>();
|
||||
bottomLayout.minHeight = 25;
|
||||
|
||||
var instantiateBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||
var instantiateBtn = instantiateBtnObj.GetComponent<Button>();
|
||||
|
||||
instantiateBtn.onClick.AddListener(InstantiateBtn);
|
||||
|
||||
var instantiateText = instantiateBtnObj.GetComponentInChildren<Text>();
|
||||
instantiateText.text = "Instantiate";
|
||||
instantiateText.fontSize = 14;
|
||||
var instantiateLayout = instantiateBtnObj.AddComponent<LayoutElement>();
|
||||
instantiateLayout.minWidth = 150;
|
||||
|
||||
void InstantiateBtn()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
if (!go)
|
||||
return;
|
||||
|
||||
var clone = GameObject.Instantiate(go);
|
||||
InspectorManager.Instance.Inspect(clone);
|
||||
}
|
||||
|
||||
var dontDestroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||
var dontDestroyBtn = dontDestroyBtnObj.GetComponent<Button>();
|
||||
|
||||
dontDestroyBtn.onClick.AddListener(DontDestroyOnLoadBtn);
|
||||
|
||||
var dontDestroyText = dontDestroyBtnObj.GetComponentInChildren<Text>();
|
||||
dontDestroyText.text = "Set DontDestroyOnLoad";
|
||||
dontDestroyText.fontSize = 14;
|
||||
var dontDestroyLayout = dontDestroyBtnObj.AddComponent<LayoutElement>();
|
||||
dontDestroyLayout.flexibleWidth = 5000;
|
||||
|
||||
void DontDestroyOnLoadBtn()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
if (!go)
|
||||
return;
|
||||
|
||||
GameObject.DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
var destroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||
var destroyBtn = destroyBtnObj.GetComponent<Button>();
|
||||
|
||||
destroyBtn.onClick.AddListener(DestroyBtn);
|
||||
|
||||
var destroyText = destroyBtnObj.GetComponentInChildren<Text>();
|
||||
destroyText.text = "Destroy";
|
||||
destroyText.fontSize = 14;
|
||||
destroyText.color = Color.red;
|
||||
var destroyLayout = destroyBtnObj.AddComponent<LayoutElement>();
|
||||
destroyLayout.minWidth = 150;
|
||||
|
||||
void DestroyBtn()
|
||||
{
|
||||
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||
if (!go)
|
||||
return;
|
||||
|
||||
GameObject.Destroy(go);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
444
src/Inspectors/GameObjects/GameObjectInspector.cs
Normal file
444
src/Inspectors/GameObjects/GameObjectInspector.cs
Normal file
@ -0,0 +1,444 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.Unstrip;
|
||||
//using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors.GameObjects;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectInspector : InspectorBase
|
||||
{
|
||||
public override string TabLabel => $" <color=cyan>[G]</color> {TargetGO?.name}";
|
||||
|
||||
public static GameObjectInspector ActiveInstance { get; private set; }
|
||||
|
||||
public GameObject TargetGO;
|
||||
|
||||
// sub modules
|
||||
private static ChildList s_childList;
|
||||
private static ComponentList s_compList;
|
||||
private static GameObjectControls s_controls;
|
||||
|
||||
// static UI elements (only constructed once)
|
||||
|
||||
private static bool m_UIConstructed;
|
||||
|
||||
private static GameObject s_content;
|
||||
public override GameObject Content
|
||||
{
|
||||
get => s_content;
|
||||
set => s_content = value;
|
||||
}
|
||||
|
||||
private static string m_lastName;
|
||||
public static InputField m_nameInput;
|
||||
|
||||
private static string m_lastPath;
|
||||
public static InputField m_pathInput;
|
||||
private static RectTransform m_pathInputRect;
|
||||
private static GameObject m_pathGroupObj;
|
||||
private static Text m_hiddenPathText;
|
||||
private static RectTransform m_hiddenPathRect;
|
||||
|
||||
private static Toggle m_enabledToggle;
|
||||
private static Text m_enabledText;
|
||||
private static bool? m_lastEnabledState;
|
||||
|
||||
private static Dropdown m_layerDropdown;
|
||||
private static int m_lastLayer = -1;
|
||||
|
||||
private static Text m_sceneText;
|
||||
private static string m_lastScene;
|
||||
|
||||
public GameObjectInspector(GameObject target) : base(target)
|
||||
{
|
||||
ActiveInstance = this;
|
||||
|
||||
TargetGO = target;
|
||||
|
||||
if (!TargetGO)
|
||||
{
|
||||
ExplorerCore.LogWarning("Target GameObject is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// one UI is used for all gameobject inspectors. no point recreating it.
|
||||
if (!m_UIConstructed)
|
||||
{
|
||||
m_UIConstructed = true;
|
||||
|
||||
s_childList = new ChildList();
|
||||
s_compList = new ComponentList();
|
||||
s_controls = new GameObjectControls();
|
||||
|
||||
ConstructUI();
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetActive()
|
||||
{
|
||||
base.SetActive();
|
||||
ActiveInstance = this;
|
||||
}
|
||||
|
||||
public override void SetInactive()
|
||||
{
|
||||
base.SetInactive();
|
||||
ActiveInstance = null;
|
||||
}
|
||||
|
||||
internal void ChangeInspectorTarget(GameObject newTarget)
|
||||
{
|
||||
if (!newTarget)
|
||||
return;
|
||||
|
||||
this.Target = this.TargetGO = newTarget;
|
||||
}
|
||||
|
||||
// Update
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (m_pendingDestroy || !this.IsActive)
|
||||
return;
|
||||
|
||||
RefreshTopInfo();
|
||||
|
||||
s_childList.RefreshChildObjectList();
|
||||
|
||||
s_compList.RefreshComponentList();
|
||||
|
||||
s_controls.RefreshControls();
|
||||
|
||||
if (GameObjectControls.s_sliderChangedWanted)
|
||||
GameObjectControls.UpdateSliderControl();
|
||||
}
|
||||
|
||||
private void RefreshTopInfo()
|
||||
{
|
||||
if (m_lastName != TargetGO.name)
|
||||
{
|
||||
m_lastName = TargetGO.name;
|
||||
m_nameInput.text = m_lastName;
|
||||
}
|
||||
|
||||
if (TargetGO.transform.parent)
|
||||
{
|
||||
if (!m_pathGroupObj.activeSelf)
|
||||
m_pathGroupObj.SetActive(true);
|
||||
|
||||
var path = TargetGO.transform.GetTransformPath(true);
|
||||
if (m_lastPath != path)
|
||||
{
|
||||
m_lastPath = path;
|
||||
|
||||
m_pathInput.text = path;
|
||||
m_hiddenPathText.text = path;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(m_pathInputRect);
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(m_hiddenPathRect);
|
||||
}
|
||||
}
|
||||
else if (m_pathGroupObj.activeSelf)
|
||||
m_pathGroupObj.SetActive(false);
|
||||
|
||||
if (m_lastEnabledState != TargetGO.activeSelf)
|
||||
{
|
||||
m_lastEnabledState = TargetGO.activeSelf;
|
||||
|
||||
m_enabledToggle.isOn = TargetGO.activeSelf;
|
||||
m_enabledText.text = TargetGO.activeSelf ? "Enabled" : "Disabled";
|
||||
m_enabledText.color = TargetGO.activeSelf ? Color.green : Color.red;
|
||||
}
|
||||
|
||||
if (m_lastLayer != TargetGO.layer)
|
||||
{
|
||||
m_lastLayer = TargetGO.layer;
|
||||
m_layerDropdown.value = TargetGO.layer;
|
||||
}
|
||||
|
||||
if (m_lastScene != TargetGO.scene.name)
|
||||
{
|
||||
m_lastScene = TargetGO.scene.name;
|
||||
|
||||
if (!string.IsNullOrEmpty(TargetGO.scene.name))
|
||||
m_sceneText.text = m_lastScene;
|
||||
else
|
||||
m_sceneText.text = "None (Asset/Resource)";
|
||||
}
|
||||
}
|
||||
|
||||
// UI Callbacks
|
||||
|
||||
private static void OnApplyNameClicked()
|
||||
{
|
||||
if (ActiveInstance == null)
|
||||
return;
|
||||
|
||||
ActiveInstance.TargetGO.name = m_nameInput.text;
|
||||
}
|
||||
|
||||
private static void OnEnableToggled(bool enabled)
|
||||
{
|
||||
if (ActiveInstance == null)
|
||||
return;
|
||||
|
||||
ActiveInstance.TargetGO.SetActive(enabled);
|
||||
}
|
||||
|
||||
private static void OnLayerSelected(int layer)
|
||||
{
|
||||
if (ActiveInstance == null)
|
||||
return;
|
||||
|
||||
ActiveInstance.TargetGO.layer = layer;
|
||||
}
|
||||
|
||||
internal static void OnBackButtonClicked()
|
||||
{
|
||||
if (ActiveInstance == null)
|
||||
return;
|
||||
|
||||
ActiveInstance.ChangeInspectorTarget(ActiveInstance.TargetGO.transform.parent.gameObject);
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
private void ConstructUI()
|
||||
{
|
||||
var parent = InspectorManager.Instance.m_inspectorContent;
|
||||
|
||||
s_content = UIFactory.CreateScrollView(parent, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||
|
||||
var scrollGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||
scrollGroup.childForceExpandHeight = true;
|
||||
scrollGroup.childControlHeight = true;
|
||||
scrollGroup.spacing = 5;
|
||||
|
||||
ConstructTopArea(scrollContent);
|
||||
|
||||
s_controls.ConstructControls(scrollContent);
|
||||
|
||||
var midGroupObj = ConstructMidGroup(scrollContent);
|
||||
|
||||
s_childList.ConstructChildList(midGroupObj);
|
||||
s_compList.ConstructCompList(midGroupObj);
|
||||
}
|
||||
|
||||
private void ConstructTopArea(GameObject scrollContent)
|
||||
{
|
||||
// path row
|
||||
|
||||
m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||
var pathGroup = m_pathGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
pathGroup.childForceExpandHeight = false;
|
||||
pathGroup.childForceExpandWidth = false;
|
||||
pathGroup.childControlHeight = false;
|
||||
pathGroup.childControlWidth = true;
|
||||
pathGroup.spacing = 5;
|
||||
var pathRect = m_pathGroupObj.GetComponent<RectTransform>();
|
||||
pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20);
|
||||
var pathLayout = m_pathGroupObj.AddComponent<LayoutElement>();
|
||||
pathLayout.minHeight = 20;
|
||||
pathLayout.flexibleHeight = 75;
|
||||
|
||||
var backButtonObj = UIFactory.CreateButton(m_pathGroupObj);
|
||||
var backButton = backButtonObj.GetComponent<Button>();
|
||||
|
||||
backButton.onClick.AddListener(OnBackButtonClicked);
|
||||
|
||||
var backColors = backButton.colors;
|
||||
backColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||
backButton.colors = backColors;
|
||||
var backText = backButtonObj.GetComponentInChildren<Text>();
|
||||
backText.text = "◄";
|
||||
var backLayout = backButtonObj.AddComponent<LayoutElement>();
|
||||
backLayout.minWidth = 55;
|
||||
backLayout.flexibleWidth = 0;
|
||||
backLayout.minHeight = 25;
|
||||
backLayout.flexibleHeight = 0;
|
||||
|
||||
var pathHiddenTextObj = UIFactory.CreateLabel(m_pathGroupObj, TextAnchor.MiddleLeft);
|
||||
m_hiddenPathText = pathHiddenTextObj.GetComponent<Text>();
|
||||
m_hiddenPathText.color = Color.clear;
|
||||
m_hiddenPathText.fontSize = 14;
|
||||
//m_hiddenPathText.lineSpacing = 1.5f;
|
||||
m_hiddenPathText.raycastTarget = false;
|
||||
var hiddenFitter = pathHiddenTextObj.AddComponent<ContentSizeFitter>();
|
||||
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
var hiddenLayout = pathHiddenTextObj.AddComponent<LayoutElement>();
|
||||
hiddenLayout.minHeight = 25;
|
||||
hiddenLayout.flexibleHeight = 125;
|
||||
hiddenLayout.minWidth = 250;
|
||||
hiddenLayout.flexibleWidth = 9000;
|
||||
var hiddenGroup = pathHiddenTextObj.AddComponent<HorizontalLayoutGroup>();
|
||||
hiddenGroup.childForceExpandWidth = true;
|
||||
hiddenGroup.childControlWidth = true;
|
||||
hiddenGroup.childForceExpandHeight = true;
|
||||
hiddenGroup.childControlHeight = true;
|
||||
|
||||
var pathInputObj = UIFactory.CreateInputField(pathHiddenTextObj);
|
||||
var pathInputRect = pathInputObj.GetComponent<RectTransform>();
|
||||
pathInputRect.sizeDelta = new Vector2(pathInputRect.sizeDelta.x, 25);
|
||||
m_pathInput = pathInputObj.GetComponent<InputField>();
|
||||
m_pathInput.text = TargetGO.transform.GetTransformPath();
|
||||
m_pathInput.readOnly = true;
|
||||
m_pathInput.lineType = InputField.LineType.MultiLineNewline;
|
||||
var pathInputLayout = pathInputObj.AddComponent<LayoutElement>();
|
||||
pathInputLayout.minHeight = 25;
|
||||
pathInputLayout.flexibleHeight = 75;
|
||||
pathInputLayout.preferredWidth = 400;
|
||||
pathInputLayout.flexibleWidth = 9999;
|
||||
var textRect = m_pathInput.textComponent.GetComponent<RectTransform>();
|
||||
textRect.offsetMin = new Vector2(3, 3);
|
||||
textRect.offsetMax = new Vector2(3, 3);
|
||||
m_pathInput.textComponent.color = new Color(0.75f, 0.75f, 0.75f);
|
||||
//m_pathInput.textComponent.lineSpacing = 1.5f;
|
||||
|
||||
m_pathInputRect = m_pathInput.GetComponent<RectTransform>();
|
||||
m_hiddenPathRect = m_hiddenPathText.GetComponent<RectTransform>();
|
||||
|
||||
// name row
|
||||
|
||||
var nameRowObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||
var nameGroup = nameRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
nameGroup.childForceExpandHeight = false;
|
||||
nameGroup.childForceExpandWidth = false;
|
||||
nameGroup.childControlHeight = true;
|
||||
nameGroup.childControlWidth = true;
|
||||
nameGroup.spacing = 5;
|
||||
var nameRect = nameRowObj.GetComponent<RectTransform>();
|
||||
nameRect.sizeDelta = new Vector2(nameRect.sizeDelta.x, 25);
|
||||
var nameLayout = nameRowObj.AddComponent<LayoutElement>();
|
||||
nameLayout.minHeight = 25;
|
||||
nameLayout.flexibleHeight = 0;
|
||||
|
||||
var nameTextObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
|
||||
var nameTextText = nameTextObj.GetComponent<Text>();
|
||||
nameTextText.text = "Name:";
|
||||
nameTextText.fontSize = 14;
|
||||
nameTextText.color = Color.grey;
|
||||
var nameTextLayout = nameTextObj.AddComponent<LayoutElement>();
|
||||
nameTextLayout.minHeight = 25;
|
||||
nameTextLayout.flexibleHeight = 0;
|
||||
nameTextLayout.minWidth = 55;
|
||||
nameTextLayout.flexibleWidth = 0;
|
||||
|
||||
var nameInputObj = UIFactory.CreateInputField(nameRowObj);
|
||||
var nameInputRect = nameInputObj.GetComponent<RectTransform>();
|
||||
nameInputRect.sizeDelta = new Vector2(nameInputRect.sizeDelta.x, 25);
|
||||
m_nameInput = nameInputObj.GetComponent<InputField>();
|
||||
m_nameInput.text = TargetGO.name;
|
||||
|
||||
var applyNameBtnObj = UIFactory.CreateButton(nameRowObj);
|
||||
var applyNameBtn = applyNameBtnObj.GetComponent<Button>();
|
||||
|
||||
applyNameBtn.onClick.AddListener(OnApplyNameClicked);
|
||||
|
||||
var applyNameText = applyNameBtnObj.GetComponentInChildren<Text>();
|
||||
applyNameText.text = "Apply";
|
||||
applyNameText.fontSize = 14;
|
||||
var applyNameLayout = applyNameBtnObj.AddComponent<LayoutElement>();
|
||||
applyNameLayout.minWidth = 65;
|
||||
applyNameLayout.minHeight = 25;
|
||||
applyNameLayout.flexibleHeight = 0;
|
||||
var applyNameRect = applyNameBtnObj.GetComponent<RectTransform>();
|
||||
applyNameRect.sizeDelta = new Vector2(applyNameRect.sizeDelta.x, 25);
|
||||
|
||||
var activeLabel = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
|
||||
var activeLabelLayout = activeLabel.AddComponent<LayoutElement>();
|
||||
activeLabelLayout.minWidth = 55;
|
||||
activeLabelLayout.minHeight = 25;
|
||||
var activeText = activeLabel.GetComponent<Text>();
|
||||
activeText.text = "Active:";
|
||||
activeText.color = Color.grey;
|
||||
activeText.fontSize = 14;
|
||||
|
||||
var enabledToggleObj = UIFactory.CreateToggle(nameRowObj, out m_enabledToggle, out m_enabledText);
|
||||
var toggleLayout = enabledToggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minHeight = 25;
|
||||
toggleLayout.minWidth = 100;
|
||||
toggleLayout.flexibleWidth = 0;
|
||||
m_enabledText.text = "Enabled";
|
||||
m_enabledText.color = Color.green;
|
||||
|
||||
m_enabledToggle.onValueChanged.AddListener(OnEnableToggled);
|
||||
|
||||
// layer and scene row
|
||||
|
||||
var sceneLayerRow = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||
var sceneLayerGroup = sceneLayerRow.GetComponent<HorizontalLayoutGroup>();
|
||||
sceneLayerGroup.childForceExpandWidth = false;
|
||||
sceneLayerGroup.childControlWidth = true;
|
||||
sceneLayerGroup.spacing = 5;
|
||||
|
||||
var layerLabel = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
|
||||
var layerText = layerLabel.GetComponent<Text>();
|
||||
layerText.text = "Layer:";
|
||||
layerText.fontSize = 14;
|
||||
layerText.color = Color.grey;
|
||||
var layerTextLayout = layerLabel.AddComponent<LayoutElement>();
|
||||
layerTextLayout.minWidth = 55;
|
||||
layerTextLayout.flexibleWidth = 0;
|
||||
|
||||
var layerDropdownObj = UIFactory.CreateDropdown(sceneLayerRow, out m_layerDropdown);
|
||||
m_layerDropdown.options.Clear();
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
var layer = LayerMaskUnstrip.LayerToName(i);
|
||||
m_layerDropdown.options.Add(new Dropdown.OptionData { text = $"{i}: {layer}" });
|
||||
}
|
||||
//var itemText = layerDropdownObj.transform.Find("Label").GetComponent<Text>();
|
||||
//itemText.resizeTextForBestFit = true;
|
||||
var layerDropdownLayout = layerDropdownObj.AddComponent<LayoutElement>();
|
||||
layerDropdownLayout.minWidth = 120;
|
||||
layerDropdownLayout.flexibleWidth = 2000;
|
||||
layerDropdownLayout.minHeight = 25;
|
||||
|
||||
m_layerDropdown.onValueChanged.AddListener(OnLayerSelected);
|
||||
|
||||
var scenelabelObj = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
|
||||
var sceneLabel = scenelabelObj.GetComponent<Text>();
|
||||
sceneLabel.text = "Scene:";
|
||||
sceneLabel.color = Color.grey;
|
||||
sceneLabel.fontSize = 14;
|
||||
var sceneLabelLayout = scenelabelObj.AddComponent<LayoutElement>();
|
||||
sceneLabelLayout.minWidth = 55;
|
||||
sceneLabelLayout.flexibleWidth = 0;
|
||||
|
||||
var objectSceneText = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleLeft);
|
||||
m_sceneText = objectSceneText.GetComponent<Text>();
|
||||
m_sceneText.fontSize = 14;
|
||||
m_sceneText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
var sceneTextLayout = objectSceneText.AddComponent<LayoutElement>();
|
||||
sceneTextLayout.minWidth = 120;
|
||||
sceneTextLayout.flexibleWidth = 2000;
|
||||
}
|
||||
|
||||
private GameObject ConstructMidGroup(GameObject parent)
|
||||
{
|
||||
var midGroupObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var midGroup = midGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
midGroup.spacing = 5;
|
||||
midGroup.childForceExpandWidth = true;
|
||||
midGroup.childControlWidth = true;
|
||||
midGroup.childForceExpandHeight = true;
|
||||
midGroup.childControlHeight = true;
|
||||
var midlayout = midGroupObj.AddComponent<LayoutElement>();
|
||||
midlayout.minHeight = 350;
|
||||
midlayout.flexibleHeight = 10000;
|
||||
midlayout.minWidth = 200;
|
||||
midlayout.flexibleWidth = 25000;
|
||||
|
||||
return midGroupObj;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
138
src/Inspectors/InspectorBase.cs
Normal file
138
src/Inspectors/InspectorBase.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public abstract class InspectorBase
|
||||
{
|
||||
public object Target;
|
||||
|
||||
public abstract string TabLabel { get; }
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
public abstract GameObject Content { get; set; }
|
||||
public Button tabButton;
|
||||
public Text tabText;
|
||||
|
||||
internal bool m_pendingDestroy;
|
||||
|
||||
public InspectorBase(object target)
|
||||
{
|
||||
Target = target;
|
||||
|
||||
if (Target.IsNullOrDestroyed(false))
|
||||
{
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
AddInspectorTab();
|
||||
}
|
||||
|
||||
public virtual void SetActive()
|
||||
{
|
||||
this.IsActive = true;
|
||||
Content?.SetActive(true);
|
||||
}
|
||||
|
||||
public virtual void SetInactive()
|
||||
{
|
||||
this.IsActive = false;
|
||||
Content?.SetActive(false);
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
if (Target.IsNullOrDestroyed(false))
|
||||
{
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
tabText.text = TabLabel;
|
||||
}
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
m_pendingDestroy = true;
|
||||
|
||||
GameObject tabGroup = tabButton?.transform.parent.gameObject;
|
||||
|
||||
if (tabGroup)
|
||||
{
|
||||
GameObject.Destroy(tabGroup);
|
||||
}
|
||||
|
||||
int thisIndex = -1;
|
||||
if (InspectorManager.Instance.m_currentInspectors.Contains(this))
|
||||
{
|
||||
thisIndex = InspectorManager.Instance.m_currentInspectors.IndexOf(this);
|
||||
InspectorManager.Instance.m_currentInspectors.Remove(this);
|
||||
}
|
||||
|
||||
if (ReferenceEquals(InspectorManager.Instance.m_activeInspector, this))
|
||||
{
|
||||
InspectorManager.Instance.UnsetInspectorTab();
|
||||
|
||||
if (InspectorManager.Instance.m_currentInspectors.Count > 0)
|
||||
{
|
||||
var prevTab = InspectorManager.Instance.m_currentInspectors[thisIndex > 0 ? thisIndex - 1 : 0];
|
||||
InspectorManager.Instance.SetInspectorTab(prevTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
public void AddInspectorTab()
|
||||
{
|
||||
var tabContent = InspectorManager.Instance.m_tabBarContent;
|
||||
|
||||
var tabGroupObj = UIFactory.CreateHorizontalGroup(tabContent);
|
||||
var tabGroup = tabGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
tabGroup.childForceExpandWidth = true;
|
||||
tabGroup.childControlWidth = true;
|
||||
var tabLayout = tabGroupObj.AddComponent<LayoutElement>();
|
||||
tabLayout.minWidth = 185;
|
||||
tabLayout.flexibleWidth = 0;
|
||||
tabGroupObj.AddComponent<Mask>();
|
||||
|
||||
var targetButtonObj = UIFactory.CreateButton(tabGroupObj);
|
||||
targetButtonObj.AddComponent<Mask>();
|
||||
var targetButtonLayout = targetButtonObj.AddComponent<LayoutElement>();
|
||||
targetButtonLayout.minWidth = 165;
|
||||
targetButtonLayout.flexibleWidth = 0;
|
||||
|
||||
tabText = targetButtonObj.GetComponentInChildren<Text>();
|
||||
tabText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
tabText.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
tabButton = targetButtonObj.GetComponent<Button>();
|
||||
|
||||
tabButton.onClick.AddListener(() => { InspectorManager.Instance.SetInspectorTab(this); });
|
||||
|
||||
var closeBtnObj = UIFactory.CreateButton(tabGroupObj);
|
||||
var closeBtnLayout = closeBtnObj.AddComponent<LayoutElement>();
|
||||
closeBtnLayout.minWidth = 20;
|
||||
closeBtnLayout.flexibleWidth = 0;
|
||||
var closeBtnText = closeBtnObj.GetComponentInChildren<Text>();
|
||||
closeBtnText.text = "X";
|
||||
closeBtnText.color = new Color(1, 0, 0, 1);
|
||||
|
||||
var closeBtn = closeBtnObj.GetComponent<Button>();
|
||||
|
||||
closeBtn.onClick.AddListener(Destroy);
|
||||
|
||||
var closeColors = closeBtn.colors;
|
||||
closeColors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||
closeBtn.colors = closeColors;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
335
src/Inspectors/InspectorManager.cs
Normal file
335
src/Inspectors/InspectorManager.cs
Normal file
@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors.Reflection;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class InspectorManager
|
||||
{
|
||||
public static InspectorManager Instance { get; private set; }
|
||||
|
||||
public InspectorManager()
|
||||
{
|
||||
Instance = this;
|
||||
ConstructInspectorPane();
|
||||
}
|
||||
|
||||
public InspectorBase m_activeInspector;
|
||||
public readonly List<InspectorBase> m_currentInspectors = new List<InspectorBase>();
|
||||
|
||||
public GameObject m_tabBarContent;
|
||||
public GameObject m_inspectorContent;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
for (int i = 0; i < m_currentInspectors.Count; i++)
|
||||
{
|
||||
if (i >= m_currentInspectors.Count)
|
||||
break;
|
||||
|
||||
m_currentInspectors[i].Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void Inspect(object obj)
|
||||
{
|
||||
#if CPP
|
||||
obj = obj.Il2CppCast(ReflectionHelpers.GetActualType(obj));
|
||||
#endif
|
||||
UnityEngine.Object unityObj = obj as UnityEngine.Object;
|
||||
|
||||
if (obj.IsNullOrDestroyed(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check if currently inspecting this object
|
||||
foreach (InspectorBase tab in m_currentInspectors)
|
||||
{
|
||||
if (ReferenceEquals(obj, tab.Target))
|
||||
{
|
||||
SetInspectorTab(tab);
|
||||
return;
|
||||
}
|
||||
#if CPP
|
||||
else if (unityObj && tab.Target is UnityEngine.Object uTabObj)
|
||||
{
|
||||
if (unityObj.m_CachedPtr == uTabObj.m_CachedPtr)
|
||||
{
|
||||
SetInspectorTab(tab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
InspectorBase inspector;
|
||||
if (obj is GameObject go)
|
||||
inspector = new GameObjectInspector(go);
|
||||
else
|
||||
inspector = new InstanceInspector(obj);
|
||||
|
||||
m_currentInspectors.Add(inspector);
|
||||
SetInspectorTab(inspector);
|
||||
}
|
||||
|
||||
public void Inspect(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("The provided type was null!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var tab in m_currentInspectors.Where(x => x is StaticInspector))
|
||||
{
|
||||
if (ReferenceEquals(tab.Target as Type, type))
|
||||
{
|
||||
SetInspectorTab(tab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var inspector = new StaticInspector(type);
|
||||
|
||||
m_currentInspectors.Add(inspector);
|
||||
SetInspectorTab(inspector);
|
||||
}
|
||||
|
||||
public void SetInspectorTab(InspectorBase inspector)
|
||||
{
|
||||
MainMenu.Instance.SetPage(HomePage.Instance);
|
||||
|
||||
if (m_activeInspector == inspector)
|
||||
return;
|
||||
|
||||
UnsetInspectorTab();
|
||||
|
||||
m_activeInspector = inspector;
|
||||
inspector.SetActive();
|
||||
|
||||
Color activeColor = new Color(0, 0.25f, 0, 1);
|
||||
ColorBlock colors = inspector.tabButton.colors;
|
||||
colors.normalColor = activeColor;
|
||||
colors.highlightedColor = activeColor;
|
||||
inspector.tabButton.colors = colors;
|
||||
}
|
||||
|
||||
public void UnsetInspectorTab()
|
||||
{
|
||||
if (m_activeInspector == null)
|
||||
return;
|
||||
|
||||
m_activeInspector.SetInactive();
|
||||
|
||||
ColorBlock colors = m_activeInspector.tabButton.colors;
|
||||
colors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||
colors.highlightedColor = new Color(0.1f, 0.3f, 0.1f, 1);
|
||||
m_activeInspector.tabButton.colors = colors;
|
||||
|
||||
m_activeInspector = null;
|
||||
}
|
||||
|
||||
#region INSPECTOR PANE
|
||||
|
||||
public void ConstructInspectorPane()
|
||||
{
|
||||
var mainObj = UIFactory.CreateVerticalGroup(HomePage.Instance.Content, new Color(72f / 255f, 72f / 255f, 72f / 255f));
|
||||
LayoutElement mainLayout = mainObj.AddComponent<LayoutElement>();
|
||||
mainLayout.preferredHeight = 400;
|
||||
mainLayout.flexibleHeight = 9000;
|
||||
mainLayout.preferredWidth = 620;
|
||||
mainLayout.flexibleWidth = 9000;
|
||||
|
||||
var mainGroup = mainObj.GetComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.spacing = 4;
|
||||
mainGroup.padding.left = 4;
|
||||
mainGroup.padding.right = 4;
|
||||
mainGroup.padding.top = 4;
|
||||
mainGroup.padding.bottom = 4;
|
||||
|
||||
var topRowObj = UIFactory.CreateHorizontalGroup(mainObj, new Color(1, 1, 1, 0));
|
||||
var topRowGroup = topRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
topRowGroup.childForceExpandWidth = false;
|
||||
topRowGroup.childControlWidth = true;
|
||||
topRowGroup.childForceExpandHeight = true;
|
||||
topRowGroup.childControlHeight = true;
|
||||
topRowGroup.spacing = 15;
|
||||
|
||||
var inspectorTitle = UIFactory.CreateLabel(topRowObj, TextAnchor.MiddleLeft);
|
||||
Text title = inspectorTitle.GetComponent<Text>();
|
||||
title.text = "Inspector";
|
||||
title.fontSize = 20;
|
||||
var titleLayout = inspectorTitle.AddComponent<LayoutElement>();
|
||||
titleLayout.minHeight = 30;
|
||||
titleLayout.flexibleHeight = 0;
|
||||
titleLayout.minWidth = 90;
|
||||
titleLayout.flexibleWidth = 20000;
|
||||
|
||||
ConstructToolbar(topRowObj);
|
||||
|
||||
// inspector tab bar
|
||||
|
||||
m_tabBarContent = UIFactory.CreateGridGroup(mainObj, new Vector2(185, 20), new Vector2(5, 2), new Color(0.1f, 0.1f, 0.1f, 1));
|
||||
|
||||
var gridGroup = m_tabBarContent.GetComponent<GridLayoutGroup>();
|
||||
gridGroup.padding.top = 3;
|
||||
gridGroup.padding.left = 3;
|
||||
gridGroup.padding.right = 3;
|
||||
gridGroup.padding.bottom = 3;
|
||||
|
||||
// inspector content area
|
||||
|
||||
m_inspectorContent = UIFactory.CreateVerticalGroup(mainObj, new Color(0.1f, 0.1f, 0.1f));
|
||||
var inspectorGroup = m_inspectorContent.GetComponent<VerticalLayoutGroup>();
|
||||
inspectorGroup.childForceExpandHeight = true;
|
||||
inspectorGroup.childForceExpandWidth = true;
|
||||
inspectorGroup.childControlHeight = true;
|
||||
inspectorGroup.childControlWidth = true;
|
||||
|
||||
m_inspectorContent = UIFactory.CreateVerticalGroup(mainObj, new Color(0.1f, 0.1f, 0.1f));
|
||||
var contentGroup = m_inspectorContent.GetComponent<VerticalLayoutGroup>();
|
||||
contentGroup.childForceExpandHeight = true;
|
||||
contentGroup.childForceExpandWidth = true;
|
||||
contentGroup.childControlHeight = true;
|
||||
contentGroup.childControlWidth = true;
|
||||
contentGroup.padding.top = 2;
|
||||
contentGroup.padding.left = 2;
|
||||
contentGroup.padding.right = 2;
|
||||
contentGroup.padding.bottom = 2;
|
||||
|
||||
var contentLayout = m_inspectorContent.AddComponent<LayoutElement>();
|
||||
contentLayout.preferredHeight = 900;
|
||||
contentLayout.flexibleHeight = 10000;
|
||||
contentLayout.preferredWidth = 600;
|
||||
contentLayout.flexibleWidth = 10000;
|
||||
}
|
||||
|
||||
private static void ConstructToolbar(GameObject topRowObj)
|
||||
{
|
||||
var invisObj = UIFactory.CreateHorizontalGroup(topRowObj, new Color(1, 1, 1, 0));
|
||||
var invisGroup = invisObj.GetComponent<HorizontalLayoutGroup>();
|
||||
invisGroup.childForceExpandWidth = false;
|
||||
invisGroup.childForceExpandHeight = false;
|
||||
invisGroup.childControlWidth = true;
|
||||
invisGroup.childControlHeight = true;
|
||||
invisGroup.padding.top = 2;
|
||||
invisGroup.padding.bottom = 2;
|
||||
invisGroup.padding.left = 2;
|
||||
invisGroup.padding.right = 2;
|
||||
invisGroup.spacing = 10;
|
||||
|
||||
// // time scale group
|
||||
|
||||
// var timeGroupObj = UIFactory.CreateHorizontalGroup(invisObj, new Color(1, 1, 1, 0));
|
||||
// var timeGroup = timeGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
// timeGroup.childForceExpandWidth = false;
|
||||
// timeGroup.childControlWidth = true;
|
||||
// timeGroup.childForceExpandHeight = false;
|
||||
// timeGroup.childControlHeight = true;
|
||||
// timeGroup.padding.top = 2;
|
||||
// timeGroup.padding.left = 5;
|
||||
// timeGroup.padding.right = 2;
|
||||
// timeGroup.padding.bottom = 2;
|
||||
// timeGroup.spacing = 5;
|
||||
// timeGroup.childAlignment = TextAnchor.MiddleCenter;
|
||||
// var timeGroupLayout = timeGroupObj.AddComponent<LayoutElement>();
|
||||
// timeGroupLayout.minWidth = 100;
|
||||
// timeGroupLayout.flexibleWidth = 300;
|
||||
// timeGroupLayout.minHeight = 25;
|
||||
// timeGroupLayout.flexibleHeight = 0;
|
||||
|
||||
// // time scale title
|
||||
|
||||
// var timeTitleObj = UIFactory.CreateLabel(timeGroupObj, TextAnchor.MiddleLeft);
|
||||
// var timeTitle = timeTitleObj.GetComponent<Text>();
|
||||
// timeTitle.text = "Time Scale:";
|
||||
// timeTitle.color = new Color(21f / 255f, 192f / 255f, 235f / 255f);
|
||||
// var titleLayout = timeTitleObj.AddComponent<LayoutElement>();
|
||||
// titleLayout.minHeight = 25;
|
||||
// titleLayout.minWidth = 80;
|
||||
// titleLayout.flexibleHeight = 0;
|
||||
// timeTitle.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
// // actual active time label
|
||||
|
||||
// var timeLabelObj = UIFactory.CreateLabel(timeGroupObj, TextAnchor.MiddleLeft);
|
||||
// var timeLabelLayout = timeLabelObj.AddComponent<LayoutElement>();
|
||||
// timeLabelLayout.minWidth = 40;
|
||||
// timeLabelLayout.minHeight = 25;
|
||||
// timeLabelLayout.flexibleHeight = 0;
|
||||
|
||||
// // todo make static and update
|
||||
// var s_timeText = timeLabelObj.GetComponent<Text>();
|
||||
// s_timeText.text = Time.timeScale.ToString("F1");
|
||||
|
||||
// // time scale input
|
||||
|
||||
// var timeInputObj = UIFactory.CreateInputField(timeGroupObj);
|
||||
// var timeInput = timeInputObj.GetComponent<InputField>();
|
||||
// timeInput.characterValidation = InputField.CharacterValidation.Decimal;
|
||||
// var timeInputLayout = timeInputObj.AddComponent<LayoutElement>();
|
||||
// timeInputLayout.minWidth = 90;
|
||||
// timeInputLayout.flexibleWidth = 0;
|
||||
// timeInputLayout.minHeight = 25;
|
||||
// timeInputLayout.flexibleHeight = 0;
|
||||
|
||||
// // time scale apply button
|
||||
|
||||
// var applyBtnObj = UIFactory.CreateButton(timeGroupObj);
|
||||
// var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
|
||||
// applyBtn.onClick.AddListener(SetTimeScale);
|
||||
|
||||
// var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
// applyText.text = "Apply";
|
||||
// applyText.fontSize = 14;
|
||||
// var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
// applyLayout.minWidth = 50;
|
||||
// applyLayout.minHeight = 25;
|
||||
// applyLayout.flexibleHeight = 0;
|
||||
|
||||
// void SetTimeScale()
|
||||
// {
|
||||
// var scale = float.Parse(timeInput.text);
|
||||
// Time.timeScale = scale;
|
||||
// s_timeText.text = Time.timeScale.ToString("F1");
|
||||
// }
|
||||
|
||||
|
||||
// inspect under mouse button
|
||||
|
||||
var inspectObj = UIFactory.CreateButton(topRowObj);
|
||||
var inspectLayout = inspectObj.AddComponent<LayoutElement>();
|
||||
inspectLayout.minWidth = 120;
|
||||
inspectLayout.flexibleWidth = 0;
|
||||
var inspectBtn = inspectObj.GetComponent<Button>();
|
||||
var inspectColors = inspectBtn.colors;
|
||||
inspectColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||
inspectBtn.colors = inspectColors;
|
||||
var inspectText = inspectObj.GetComponentInChildren<Text>();
|
||||
inspectText.text = "Mouse Inspect";
|
||||
inspectText.fontSize = 13;
|
||||
|
||||
inspectBtn.onClick.AddListener(OnInspectMouseClicked);
|
||||
|
||||
void OnInspectMouseClicked()
|
||||
{
|
||||
MouseInspector.StartInspect();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
143
src/Inspectors/MouseInspector.cs
Normal file
143
src/Inspectors/MouseInspector.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.Input;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class MouseInspector
|
||||
{
|
||||
public static bool Enabled { get; set; }
|
||||
|
||||
//internal static Text s_objUnderMouseName;
|
||||
internal static Text s_objNameLabel;
|
||||
internal static Text s_objPathLabel;
|
||||
internal static Text s_mousePosLabel;
|
||||
|
||||
private static GameObject s_lastHit;
|
||||
private static Vector3 s_lastMousePos;
|
||||
|
||||
internal static GameObject s_UIContent;
|
||||
|
||||
public static void StartInspect()
|
||||
{
|
||||
Enabled = true;
|
||||
MainMenu.Instance.MainPanel.SetActive(false);
|
||||
s_UIContent.SetActive(true);
|
||||
}
|
||||
|
||||
public static void StopInspect()
|
||||
{
|
||||
Enabled = false;
|
||||
MainMenu.Instance.MainPanel.SetActive(true);
|
||||
s_UIContent.SetActive(false);
|
||||
|
||||
ClearHitData();
|
||||
}
|
||||
|
||||
public static void UpdateInspect()
|
||||
{
|
||||
if (InputManager.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
StopInspect();
|
||||
}
|
||||
|
||||
var mousePos = InputManager.MousePosition;
|
||||
|
||||
if (mousePos != s_lastMousePos)
|
||||
{
|
||||
s_lastMousePos = mousePos;
|
||||
|
||||
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
|
||||
|
||||
s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {((Vector2)InputManager.MousePosition).ToString()}";
|
||||
|
||||
float yFix = mousePos.y < 120 ? 80 : -80;
|
||||
|
||||
s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
|
||||
}
|
||||
|
||||
if (!UnityHelpers.MainCamera)
|
||||
return;
|
||||
|
||||
// actual inspect raycast
|
||||
var ray = UnityHelpers.MainCamera.ScreenPointToRay(mousePos);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
|
||||
if (obj != s_lastHit)
|
||||
{
|
||||
s_lastHit = obj;
|
||||
s_objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
|
||||
s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
|
||||
}
|
||||
|
||||
if (InputManager.GetMouseButtonDown(0))
|
||||
{
|
||||
StopInspect();
|
||||
InspectorManager.Instance.Inspect(obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_lastHit)
|
||||
ClearHitData();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ClearHitData()
|
||||
{
|
||||
s_lastHit = null;
|
||||
s_objNameLabel.text = "No hits...";
|
||||
s_objPathLabel.text = "";
|
||||
}
|
||||
|
||||
#region UI Construction
|
||||
|
||||
internal static void ConstructUI()
|
||||
{
|
||||
s_UIContent = UIFactory.CreatePanel(UIManager.CanvasRoot, "MouseInspect", out GameObject content);
|
||||
|
||||
s_UIContent.AddComponent<Mask>();
|
||||
|
||||
var baseRect = s_UIContent.GetComponent<RectTransform>();
|
||||
var half = new Vector2(0.5f, 0.5f);
|
||||
baseRect.anchorMin = half;
|
||||
baseRect.anchorMax = half;
|
||||
baseRect.pivot = half;
|
||||
baseRect.sizeDelta = new Vector2(700, 100);
|
||||
|
||||
// Title text
|
||||
|
||||
var titleObj = UIFactory.CreateLabel(content, TextAnchor.MiddleCenter);
|
||||
var titleText = titleObj.GetComponent<Text>();
|
||||
titleText.text = "<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)";
|
||||
|
||||
var mousePosObj = UIFactory.CreateLabel(content, TextAnchor.MiddleCenter);
|
||||
s_mousePosLabel = mousePosObj.GetComponent<Text>();
|
||||
s_mousePosLabel.text = "Mouse Position:";
|
||||
|
||||
var hitLabelObj = UIFactory.CreateLabel(content, TextAnchor.MiddleLeft);
|
||||
s_objNameLabel = hitLabelObj.GetComponent<Text>();
|
||||
s_objNameLabel.text = "No hits...";
|
||||
s_objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
var pathLabelObj = UIFactory.CreateLabel(content, TextAnchor.MiddleLeft);
|
||||
s_objPathLabel = pathLabelObj.GetComponent<Text>();
|
||||
s_objPathLabel.color = Color.grey;
|
||||
s_objPathLabel.fontStyle = FontStyle.Italic;
|
||||
s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
s_UIContent.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
63
src/Inspectors/Reflection/CacheObject/CacheEnumerated.cs
Normal file
63
src/Inspectors/Reflection/CacheObject/CacheEnumerated.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class CacheEnumerated : CacheObjectBase
|
||||
{
|
||||
public override Type FallbackType => ParentEnumeration.m_baseEntryType;
|
||||
public override bool CanWrite => RefIList != null && ParentEnumeration.Owner.CanWrite;
|
||||
|
||||
public int Index { get; set; }
|
||||
public IList RefIList { get; set; }
|
||||
public InteractiveEnumerable ParentEnumeration { get; set; }
|
||||
|
||||
public CacheEnumerated(int index, InteractiveEnumerable parentEnumeration, IList refIList, GameObject parentContent)
|
||||
{
|
||||
this.ParentEnumeration = parentEnumeration;
|
||||
this.Index = index;
|
||||
this.RefIList = refIList;
|
||||
this.m_parentContent = parentContent;
|
||||
}
|
||||
|
||||
public override void CreateIValue(object value, Type fallbackType)
|
||||
{
|
||||
IValue = InteractiveValue.Create(value, fallbackType);
|
||||
IValue.Owner = this;
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
RefIList[Index] = IValue.Value;
|
||||
ParentEnumeration.Value = RefIList;
|
||||
|
||||
ParentEnumeration.Owner.SetValue();
|
||||
}
|
||||
|
||||
internal override void ConstructUI()
|
||||
{
|
||||
base.ConstructUI();
|
||||
|
||||
var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.padding.left = 5;
|
||||
rowGroup.padding.right = 2;
|
||||
|
||||
var indexLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
var indexLayout = indexLabelObj.AddComponent<LayoutElement>();
|
||||
indexLayout.minWidth = 20;
|
||||
indexLayout.flexibleWidth = 30;
|
||||
indexLayout.minHeight = 25;
|
||||
var indexText = indexLabelObj.GetComponent<Text>();
|
||||
indexText.text = this.Index + ":";
|
||||
|
||||
IValue.m_mainContentParent = rowObj;
|
||||
}
|
||||
}
|
||||
}
|
37
src/Inspectors/Reflection/CacheObject/CacheField.cs
Normal file
37
src/Inspectors/Reflection/CacheObject/CacheField.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class CacheField : CacheMember
|
||||
{
|
||||
public override bool IsStatic => (MemInfo as FieldInfo).IsStatic;
|
||||
|
||||
public override Type FallbackType => (MemInfo as FieldInfo).FieldType;
|
||||
|
||||
public CacheField(FieldInfo fieldInfo, object declaringInstance) : base(fieldInfo, declaringInstance)
|
||||
{
|
||||
CreateIValue(null, fieldInfo.FieldType);
|
||||
}
|
||||
|
||||
public override void UpdateReflection()
|
||||
{
|
||||
var fi = MemInfo as FieldInfo;
|
||||
IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||
|
||||
m_evaluated = true;
|
||||
ReflectionException = null;
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
var fi = MemInfo as FieldInfo;
|
||||
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
|
||||
}
|
||||
}
|
||||
}
|
502
src/Inspectors/Reflection/CacheObject/CacheMember.cs
Normal file
502
src/Inspectors/Reflection/CacheObject/CacheMember.cs
Normal file
@ -0,0 +1,502 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityExplorer.Helpers;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public abstract class CacheMember : CacheObjectBase
|
||||
{
|
||||
public override bool IsMember => true;
|
||||
|
||||
public override Type FallbackType { get; }
|
||||
|
||||
public MemberInfo MemInfo { get; set; }
|
||||
public Type DeclaringType { get; set; }
|
||||
public object DeclaringInstance { get; set; }
|
||||
public virtual bool IsStatic { get; private set; }
|
||||
|
||||
public string ReflectionException { get; set; }
|
||||
|
||||
public override bool CanWrite => m_canWrite ?? GetCanWrite();
|
||||
private bool? m_canWrite;
|
||||
|
||||
public override bool HasParameters => ParamCount > 0;
|
||||
public virtual int ParamCount => m_arguments.Length;
|
||||
public override bool HasEvaluated => m_evaluated;
|
||||
public bool m_evaluated = false;
|
||||
public bool m_isEvaluating;
|
||||
public ParameterInfo[] m_arguments = new ParameterInfo[0];
|
||||
public string[] m_argumentInput = new string[0];
|
||||
|
||||
public string NameForFiltering => m_nameForFilter ?? (m_nameForFilter = $"{MemInfo.DeclaringType.Name}.{MemInfo.Name}".ToLower());
|
||||
private string m_nameForFilter;
|
||||
|
||||
public string RichTextName => m_richTextName ?? GetRichTextName();
|
||||
private string m_richTextName;
|
||||
|
||||
public CacheMember(MemberInfo memberInfo, object declaringInstance)
|
||||
{
|
||||
MemInfo = memberInfo;
|
||||
DeclaringType = memberInfo.DeclaringType;
|
||||
DeclaringInstance = declaringInstance;
|
||||
#if CPP
|
||||
if (DeclaringInstance != null)
|
||||
DeclaringInstance = DeclaringInstance.Il2CppCast(DeclaringType);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool CanProcessArgs(ParameterInfo[] parameters)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var pType = param.ParameterType;
|
||||
|
||||
if (pType.IsByRef && pType.HasElementType)
|
||||
pType = pType.GetElementType();
|
||||
|
||||
if (pType != null && (pType.IsPrimitive || pType == typeof(string)))
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void CreateIValue(object value, Type fallbackType)
|
||||
{
|
||||
IValue = InteractiveValue.Create(value, fallbackType);
|
||||
IValue.Owner = this;
|
||||
IValue.m_mainContentParent = this.m_rightGroup;
|
||||
IValue.m_subContentParent = this.m_subContent;
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
if (!HasParameters || m_isEvaluating)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if CPP
|
||||
if (!IsReflectionSupported())
|
||||
throw new Exception("Type not supported with Reflection");
|
||||
#endif
|
||||
UpdateReflection();
|
||||
#if CPP
|
||||
if (IValue.Value != null)
|
||||
IValue.Value = IValue.Value.Il2CppCast(ReflectionHelpers.GetActualType(IValue.Value));
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e, true);
|
||||
}
|
||||
}
|
||||
|
||||
base.UpdateValue();
|
||||
}
|
||||
|
||||
public abstract void UpdateReflection();
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
// no implementation for base class
|
||||
}
|
||||
|
||||
public object[] ParseArguments()
|
||||
{
|
||||
if (m_arguments.Length < 1)
|
||||
return new object[0];
|
||||
|
||||
var parsedArgs = new List<object>();
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType;
|
||||
|
||||
if (type.IsByRef)
|
||||
type = type.GetElementType();
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
parsedArgs.Add(input);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
|
||||
.Invoke(null, new object[] { input });
|
||||
|
||||
parsedArgs.Add(arg);
|
||||
continue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.Log($"Could not parse input '{input}' for argument #{i} '{m_arguments[i].Name}' ({type.FullName})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No input, see if there is a default value.
|
||||
if (m_arguments[i].IsOptional)
|
||||
{
|
||||
parsedArgs.Add(m_arguments[i].DefaultValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try add a null arg I guess
|
||||
parsedArgs.Add(null);
|
||||
}
|
||||
|
||||
return parsedArgs.ToArray();
|
||||
}
|
||||
|
||||
private bool GetCanWrite()
|
||||
{
|
||||
if (MemInfo is FieldInfo fi)
|
||||
m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
|
||||
else if (MemInfo is PropertyInfo pi)
|
||||
m_canWrite = pi.CanWrite;
|
||||
else
|
||||
m_canWrite = false;
|
||||
|
||||
return (bool)m_canWrite;
|
||||
}
|
||||
|
||||
private string GetRichTextName()
|
||||
{
|
||||
return m_richTextName = UISyntaxHighlight.ParseFullSyntax(MemInfo.DeclaringType, false, MemInfo);
|
||||
}
|
||||
|
||||
#if CPP
|
||||
internal bool IsReflectionSupported()
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseType = ReflectionHelpers.GetActualType(IValue.Value) ?? IValue.FallbackType;
|
||||
|
||||
var gArgs = baseType.GetGenericArguments();
|
||||
if (gArgs.Length < 1)
|
||||
return true;
|
||||
|
||||
foreach (var arg in gArgs)
|
||||
{
|
||||
if (!Check(arg))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
bool Check(Type type)
|
||||
{
|
||||
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
if (!ReflectionHelpers.Il2CppTypeNotNull(type, out IntPtr ptr))
|
||||
return false;
|
||||
|
||||
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#region UI
|
||||
|
||||
internal float GetMemberLabelWidth(RectTransform scrollRect)
|
||||
{
|
||||
var textGenSettings = m_memLabelText.GetGenerationSettings(m_topRowRect.rect.size);
|
||||
textGenSettings.scaleFactor = InputFieldScroller.canvasScaler.scaleFactor;
|
||||
|
||||
var textGen = m_memLabelText.cachedTextGeneratorForLayout;
|
||||
float preferredWidth = textGen.GetPreferredWidth(RichTextName, textGenSettings);
|
||||
|
||||
float max = scrollRect.rect.width * 0.4f;
|
||||
|
||||
if (preferredWidth > max) preferredWidth = max;
|
||||
|
||||
return preferredWidth < 125f ? 125f : preferredWidth;
|
||||
}
|
||||
|
||||
internal void SetWidths(float labelWidth, float valueWidth)
|
||||
{
|
||||
m_leftLayout.preferredWidth = labelWidth;
|
||||
m_rightLayout.preferredWidth = valueWidth;
|
||||
}
|
||||
|
||||
internal RectTransform m_topRowRect;
|
||||
internal Text m_memLabelText;
|
||||
internal GameObject m_leftGroup;
|
||||
internal LayoutElement m_leftLayout;
|
||||
internal GameObject m_rightGroup;
|
||||
internal LayoutElement m_rightLayout;
|
||||
|
||||
internal override void ConstructUI()
|
||||
{
|
||||
base.ConstructUI();
|
||||
|
||||
var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||
m_topRowRect = topGroupObj.GetComponent<RectTransform>();
|
||||
var topLayout = topGroupObj.AddComponent<LayoutElement>();
|
||||
topLayout.minHeight = 25;
|
||||
topLayout.flexibleHeight = 0;
|
||||
topLayout.minWidth = 300;
|
||||
topLayout.flexibleWidth = 5000;
|
||||
var topGroup = topGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
topGroup.childForceExpandHeight = false;
|
||||
topGroup.childForceExpandWidth = false;
|
||||
topGroup.childControlHeight = true;
|
||||
topGroup.childControlWidth = true;
|
||||
topGroup.spacing = 10;
|
||||
topGroup.padding.left = 3;
|
||||
topGroup.padding.right = 3;
|
||||
topGroup.padding.top = 0;
|
||||
topGroup.padding.bottom = 0;
|
||||
|
||||
// left group
|
||||
|
||||
m_leftGroup = UIFactory.CreateHorizontalGroup(topGroupObj, new Color(1, 1, 1, 0));
|
||||
var leftLayout = m_leftGroup.AddComponent<LayoutElement>();
|
||||
leftLayout.minHeight = 25;
|
||||
leftLayout.flexibleHeight = 0;
|
||||
leftLayout.minWidth = 125;
|
||||
leftLayout.flexibleWidth = 200;
|
||||
var leftGroup = m_leftGroup.GetComponent<HorizontalLayoutGroup>();
|
||||
leftGroup.childForceExpandHeight = true;
|
||||
leftGroup.childForceExpandWidth = false;
|
||||
leftGroup.childControlHeight = true;
|
||||
leftGroup.childControlWidth = true;
|
||||
leftGroup.spacing = 4;
|
||||
|
||||
// member label
|
||||
|
||||
var labelObj = UIFactory.CreateLabel(m_leftGroup, TextAnchor.MiddleLeft);
|
||||
var leftRect = labelObj.GetComponent<RectTransform>();
|
||||
leftRect.anchorMin = Vector2.zero;
|
||||
leftRect.anchorMax = Vector2.one;
|
||||
leftRect.offsetMin = Vector2.zero;
|
||||
leftRect.offsetMax = Vector2.zero;
|
||||
leftRect.sizeDelta = Vector2.zero;
|
||||
m_leftLayout = labelObj.AddComponent<LayoutElement>();
|
||||
m_leftLayout.preferredWidth = 125;
|
||||
m_leftLayout.minHeight = 25;
|
||||
m_leftLayout.flexibleHeight = 100;
|
||||
var labelFitter = labelObj.AddComponent<ContentSizeFitter>();
|
||||
labelFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
labelFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
m_memLabelText = labelObj.GetComponent<Text>();
|
||||
m_memLabelText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
m_memLabelText.text = this.RichTextName;
|
||||
|
||||
// right group
|
||||
|
||||
m_rightGroup = UIFactory.CreateVerticalGroup(topGroupObj, new Color(1, 1, 1, 0));
|
||||
m_rightLayout = m_rightGroup.AddComponent<LayoutElement>();
|
||||
m_rightLayout.minHeight = 25;
|
||||
m_rightLayout.flexibleHeight = 480;
|
||||
m_rightLayout.minWidth = 125;
|
||||
m_rightLayout.flexibleWidth = 5000;
|
||||
var rightGroup = m_rightGroup.GetComponent<VerticalLayoutGroup>();
|
||||
rightGroup.childForceExpandHeight = true;
|
||||
rightGroup.childForceExpandWidth = false;
|
||||
rightGroup.childControlHeight = true;
|
||||
rightGroup.childControlWidth = true;
|
||||
rightGroup.spacing = 2;
|
||||
rightGroup.padding.top = 4;
|
||||
rightGroup.padding.bottom = 2;
|
||||
|
||||
ConstructArgInput(out GameObject argsHolder);
|
||||
|
||||
ConstructEvaluateButtons(argsHolder);
|
||||
|
||||
IValue.m_mainContentParent = m_rightGroup;
|
||||
}
|
||||
|
||||
internal void ConstructArgInput(out GameObject argsHolder)
|
||||
{
|
||||
argsHolder = null;
|
||||
|
||||
if (HasParameters)
|
||||
{
|
||||
argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, new Color(1, 1, 1, 0));
|
||||
var argsGroup = argsHolder.GetComponent<VerticalLayoutGroup>();
|
||||
argsGroup.spacing = 4;
|
||||
|
||||
if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
|
||||
{
|
||||
cm.ConstructGenericArgInput(argsHolder);
|
||||
}
|
||||
|
||||
// todo normal args
|
||||
|
||||
if (m_arguments.Length > 0)
|
||||
{
|
||||
var titleObj = UIFactory.CreateLabel(argsHolder, TextAnchor.MiddleLeft);
|
||||
var titleText = titleObj.GetComponent<Text>();
|
||||
titleText.text = "<b>Arguments:</b>";
|
||||
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
AddArgRow(i, argsHolder);
|
||||
}
|
||||
}
|
||||
|
||||
argsHolder.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddArgRow(int i, GameObject parent)
|
||||
{
|
||||
var arg = m_arguments[i];
|
||||
|
||||
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||
rowLayout.minHeight = 25;
|
||||
rowLayout.flexibleWidth = 5000;
|
||||
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.childForceExpandHeight = false;
|
||||
rowGroup.childForceExpandWidth = true;
|
||||
rowGroup.spacing = 4;
|
||||
|
||||
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
var argLabelLayout = argLabelObj.AddComponent<LayoutElement>();
|
||||
argLabelLayout.minHeight = 25;
|
||||
var argText = argLabelObj.GetComponent<Text>();
|
||||
var argTypeTxt = UISyntaxHighlight.ParseFullSyntax(arg.ParameterType, false);
|
||||
argText.text = $"{argTypeTxt} <color={UISyntaxHighlight.Local}>{arg.Name}</color>";
|
||||
|
||||
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
|
||||
var argInputLayout = argInputObj.AddComponent<LayoutElement>();
|
||||
argInputLayout.flexibleWidth = 1200;
|
||||
argInputLayout.preferredWidth = 150;
|
||||
argInputLayout.minWidth = 20;
|
||||
argInputLayout.minHeight = 25;
|
||||
argInputLayout.flexibleHeight = 0;
|
||||
//argInputLayout.layoutPriority = 2;
|
||||
|
||||
var argInput = argInputObj.GetComponent<InputField>();
|
||||
argInput.onValueChanged.AddListener((string val) => { m_argumentInput[i] = val; });
|
||||
|
||||
if (arg.IsOptional)
|
||||
{
|
||||
var phInput = argInput.placeholder.GetComponent<Text>();
|
||||
phInput.text = " = " + arg.DefaultValue?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
|
||||
internal void ConstructEvaluateButtons(GameObject argsHolder)
|
||||
{
|
||||
if (HasParameters)
|
||||
{
|
||||
var evalGroupObj = UIFactory.CreateHorizontalGroup(m_rightGroup, new Color(1, 1, 1, 0));
|
||||
var evalGroup = evalGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
evalGroup.childForceExpandWidth = false;
|
||||
evalGroup.childForceExpandHeight = false;
|
||||
evalGroup.spacing = 5;
|
||||
var evalGroupLayout = evalGroupObj.AddComponent<LayoutElement>();
|
||||
evalGroupLayout.minHeight = 25;
|
||||
evalGroupLayout.flexibleHeight = 0;
|
||||
evalGroupLayout.flexibleWidth = 5000;
|
||||
|
||||
var evalButtonObj = UIFactory.CreateButton(evalGroupObj, new Color(0.4f, 0.4f, 0.4f));
|
||||
var evalLayout = evalButtonObj.AddComponent<LayoutElement>();
|
||||
evalLayout.minWidth = 100;
|
||||
evalLayout.minHeight = 22;
|
||||
evalLayout.flexibleWidth = 0;
|
||||
var evalText = evalButtonObj.GetComponentInChildren<Text>();
|
||||
evalText.text = $"Evaluate ({ParamCount})";
|
||||
|
||||
var evalButton = evalButtonObj.GetComponent<Button>();
|
||||
var colors = evalButton.colors;
|
||||
colors.highlightedColor = new Color(0.4f, 0.7f, 0.4f);
|
||||
evalButton.colors = colors;
|
||||
|
||||
var cancelButtonObj = UIFactory.CreateButton(evalGroupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||
var cancelLayout = cancelButtonObj.AddComponent<LayoutElement>();
|
||||
cancelLayout.minWidth = 100;
|
||||
cancelLayout.minHeight = 22;
|
||||
cancelLayout.flexibleWidth = 0;
|
||||
var cancelText = cancelButtonObj.GetComponentInChildren<Text>();
|
||||
cancelText.text = "Close";
|
||||
|
||||
cancelButtonObj.SetActive(false);
|
||||
|
||||
evalButton.onClick.AddListener(() =>
|
||||
{
|
||||
if (!m_isEvaluating)
|
||||
{
|
||||
argsHolder.SetActive(true);
|
||||
m_isEvaluating = true;
|
||||
evalText.text = "Evaluate";
|
||||
colors = evalButton.colors;
|
||||
colors.normalColor = new Color(0.3f, 0.6f, 0.3f);
|
||||
evalButton.colors = colors;
|
||||
|
||||
cancelButtonObj.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this is CacheMethod cm)
|
||||
cm.Evaluate();
|
||||
else
|
||||
UpdateValue();
|
||||
}
|
||||
});
|
||||
|
||||
var cancelButton = cancelButtonObj.GetComponent<Button>();
|
||||
cancelButton.onClick.AddListener(() =>
|
||||
{
|
||||
cancelButtonObj.SetActive(false);
|
||||
argsHolder.SetActive(false);
|
||||
m_isEvaluating = false;
|
||||
|
||||
evalText.text = $"Evaluate ({ParamCount})";
|
||||
colors = evalButton.colors;
|
||||
colors.normalColor = new Color(0.4f, 0.4f, 0.4f);
|
||||
evalButton.colors = colors;
|
||||
});
|
||||
}
|
||||
else if (this is CacheMethod)
|
||||
{
|
||||
// simple method evaluate button
|
||||
|
||||
var evalButtonObj = UIFactory.CreateButton(m_rightGroup, new Color(0.3f, 0.6f, 0.3f));
|
||||
var evalLayout = evalButtonObj.AddComponent<LayoutElement>();
|
||||
evalLayout.minWidth = 100;
|
||||
evalLayout.minHeight = 22;
|
||||
evalLayout.flexibleWidth = 0;
|
||||
var evalText = evalButtonObj.GetComponentInChildren<Text>();
|
||||
evalText.text = "Evaluate";
|
||||
|
||||
var evalButton = evalButtonObj.GetComponent<Button>();
|
||||
var colors = evalButton.colors;
|
||||
colors.highlightedColor = new Color(0.4f, 0.7f, 0.4f);
|
||||
evalButton.colors = colors;
|
||||
|
||||
evalButton.onClick.AddListener(OnMainEvaluateButton);
|
||||
void OnMainEvaluateButton()
|
||||
{
|
||||
(this as CacheMethod).Evaluate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
187
src/Inspectors/Reflection/CacheObject/CacheMethod.cs
Normal file
187
src/Inspectors/Reflection/CacheObject/CacheMethod.cs
Normal file
@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class CacheMethod : CacheMember
|
||||
{
|
||||
//private CacheObjectBase m_cachedReturnValue;
|
||||
|
||||
public override Type FallbackType => (MemInfo as MethodInfo).ReturnType;
|
||||
|
||||
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
|
||||
|
||||
public override bool IsStatic => (MemInfo as MethodInfo).IsStatic;
|
||||
|
||||
public override int ParamCount => base.ParamCount + m_genericArgInput.Length;
|
||||
|
||||
public Type[] GenericArgs { get; private set; }
|
||||
public Type[][] GenericConstraints { get; private set; }
|
||||
|
||||
public string[] m_genericArgInput = new string[0];
|
||||
|
||||
public CacheMethod(MethodInfo methodInfo, object declaringInstance) : base(methodInfo, declaringInstance)
|
||||
{
|
||||
GenericArgs = methodInfo.GetGenericArguments();
|
||||
|
||||
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
m_genericArgInput = new string[GenericArgs.Length];
|
||||
|
||||
m_arguments = methodInfo.GetParameters();
|
||||
m_argumentInput = new string[m_arguments.Length];
|
||||
|
||||
CreateIValue(null, methodInfo.ReturnType);
|
||||
}
|
||||
|
||||
public override void UpdateReflection()
|
||||
{
|
||||
// CacheMethod cannot UpdateValue directly. Need to Evaluate.
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
MethodInfo mi;
|
||||
if (GenericArgs.Length > 0)
|
||||
{
|
||||
mi = MakeGenericMethodFromInput();
|
||||
if (mi == null) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
mi = MemInfo as MethodInfo;
|
||||
}
|
||||
|
||||
object ret = null;
|
||||
|
||||
try
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
|
||||
m_evaluated = true;
|
||||
m_isEvaluating = false;
|
||||
ReflectionException = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
}
|
||||
|
||||
IValue.Value = ret;
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
private MethodInfo MakeGenericMethodFromInput()
|
||||
{
|
||||
var mi = MemInfo as MethodInfo;
|
||||
|
||||
var list = new List<Type>();
|
||||
for (int i = 0; i < GenericArgs.Length; i++)
|
||||
{
|
||||
var input = m_genericArgInput[i];
|
||||
if (ReflectionHelpers.GetTypeByName(input) is Type t)
|
||||
{
|
||||
if (GenericConstraints[i].Length == 0)
|
||||
{
|
||||
list.Add(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var constraint in GenericConstraints[i].Where(x => x != null))
|
||||
{
|
||||
if (!constraint.IsAssignableFrom(t))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
|
||||
$" Make sure you use the full name including the namespace.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// make into a generic with type list
|
||||
mi = mi.MakeGenericMethod(list.ToArray());
|
||||
|
||||
return mi;
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal void ConstructGenericArgInput(GameObject parent)
|
||||
{
|
||||
var titleObj = UIFactory.CreateLabel(parent, TextAnchor.MiddleLeft);
|
||||
var titleText = titleObj.GetComponent<Text>();
|
||||
titleText.text = "<b>Generic Arguments:</b>";
|
||||
|
||||
for (int i = 0; i < GenericArgs.Length; i++)
|
||||
{
|
||||
AddGenericArgRow(i, parent);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddGenericArgRow(int i, GameObject parent)
|
||||
{
|
||||
var arg = GenericArgs[i];
|
||||
|
||||
string constrainTxt = "";
|
||||
if (this.GenericConstraints[i].Length > 0)
|
||||
{
|
||||
foreach (var constraint in this.GenericConstraints[i])
|
||||
{
|
||||
if (constrainTxt != "")
|
||||
constrainTxt += ", ";
|
||||
|
||||
constrainTxt += $"{UISyntaxHighlight.ParseFullSyntax(constraint, false)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
constrainTxt = $"Any";
|
||||
|
||||
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||
rowLayout.minHeight = 25;
|
||||
rowLayout.flexibleWidth = 5000;
|
||||
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.childForceExpandHeight = true;
|
||||
rowGroup.spacing = 4;
|
||||
|
||||
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
//var argLayout = argLabelObj.AddComponent<LayoutElement>();
|
||||
//argLayout.minWidth = 20;
|
||||
var argText = argLabelObj.GetComponent<Text>();
|
||||
argText.text = $"{constrainTxt} <color={UISyntaxHighlight.Enum}>{arg.Name}</color>";
|
||||
|
||||
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
|
||||
var argInputLayout = argInputObj.AddComponent<LayoutElement>();
|
||||
argInputLayout.flexibleWidth = 1200;
|
||||
|
||||
var argInput = argInputObj.GetComponent<InputField>();
|
||||
argInput.onValueChanged.AddListener((string val) => { m_genericArgInput[i] = val; });
|
||||
|
||||
//var constraintLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
//var constraintLayout = constraintLabelObj.AddComponent<LayoutElement>();
|
||||
//constraintLayout.minWidth = 60;
|
||||
//constraintLayout.flexibleWidth = 100;
|
||||
//var constraintText = constraintLabelObj.GetComponent<Text>();
|
||||
//constraintText.text = ;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
124
src/Inspectors/Reflection/CacheObject/CacheObjectBase.cs
Normal file
124
src/Inspectors/Reflection/CacheObject/CacheObjectBase.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public abstract class CacheObjectBase
|
||||
{
|
||||
public InteractiveValue IValue;
|
||||
|
||||
public virtual bool CanWrite => false;
|
||||
public virtual bool HasParameters => false;
|
||||
public virtual bool IsMember => false;
|
||||
public virtual bool HasEvaluated => true;
|
||||
|
||||
public abstract Type FallbackType { get; }
|
||||
|
||||
public abstract void CreateIValue(object value, Type fallbackType);
|
||||
|
||||
public virtual void Enable()
|
||||
{
|
||||
if (!m_constructedUI)
|
||||
{
|
||||
ConstructUI();
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
m_mainContent.SetActive(true);
|
||||
m_mainContent.transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
public virtual void Disable()
|
||||
{
|
||||
if (m_mainContent)
|
||||
m_mainContent.SetActive(false);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (this.m_mainContent)
|
||||
GameObject.Destroy(this.m_mainContent);
|
||||
}
|
||||
|
||||
public virtual void UpdateValue()
|
||||
{
|
||||
var value = IValue.Value;
|
||||
|
||||
// if the type has changed fundamentally, make a new interactivevalue for it
|
||||
var type = value == null
|
||||
? FallbackType
|
||||
: ReflectionHelpers.GetActualType(value);
|
||||
|
||||
var ivalueType = InteractiveValue.GetIValueForType(type);
|
||||
|
||||
if (ivalueType != IValue.GetType())
|
||||
{
|
||||
IValue.OnDestroy();
|
||||
CreateIValue(value, FallbackType);
|
||||
m_subContent.SetActive(false);
|
||||
}
|
||||
|
||||
IValue.OnValueUpdated();
|
||||
|
||||
IValue.RefreshElementsAfterUpdate();
|
||||
}
|
||||
|
||||
public virtual void SetValue() => throw new NotImplementedException();
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal bool m_constructedUI;
|
||||
internal GameObject m_parentContent;
|
||||
internal RectTransform m_mainRect;
|
||||
internal GameObject m_mainContent;
|
||||
internal GameObject m_subContent;
|
||||
|
||||
// Make base UI holder for CacheObject, this doesnt actually display anything.
|
||||
internal virtual void ConstructUI()
|
||||
{
|
||||
m_constructedUI = true;
|
||||
|
||||
m_mainContent = UIFactory.CreateVerticalGroup(m_parentContent, new Color(0.1f, 0.1f, 0.1f));
|
||||
m_mainContent.name = "CacheObjectBase.MainContent";
|
||||
m_mainRect = m_mainContent.GetComponent<RectTransform>();
|
||||
m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||
var mainGroup = m_mainContent.GetComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childControlHeight = true;
|
||||
var mainLayout = m_mainContent.AddComponent<LayoutElement>();
|
||||
mainLayout.minHeight = 25;
|
||||
mainLayout.flexibleHeight = 9999;
|
||||
mainLayout.minWidth = 200;
|
||||
mainLayout.flexibleWidth = 5000;
|
||||
|
||||
// subcontent
|
||||
|
||||
m_subContent = UIFactory.CreateVerticalGroup(m_mainContent, new Color(0.085f, 0.085f, 0.085f));
|
||||
m_subContent.name = "CacheObjectBase.SubContent";
|
||||
var subGroup = m_subContent.GetComponent<VerticalLayoutGroup>();
|
||||
subGroup.childForceExpandWidth = true;
|
||||
subGroup.childForceExpandHeight = false;
|
||||
var subLayout = m_subContent.AddComponent<LayoutElement>();
|
||||
subLayout.minHeight = 30;
|
||||
subLayout.flexibleHeight = 9999;
|
||||
subLayout.minWidth = 125;
|
||||
subLayout.flexibleWidth = 9000;
|
||||
|
||||
m_subContent.SetActive(false);
|
||||
|
||||
IValue.m_subContentParent = m_subContent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
71
src/Inspectors/Reflection/CacheObject/CachePaired.cs
Normal file
71
src/Inspectors/Reflection/CacheObject/CachePaired.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public enum PairTypes
|
||||
{
|
||||
Key,
|
||||
Value
|
||||
}
|
||||
|
||||
public class CachePaired : CacheObjectBase
|
||||
{
|
||||
public override Type FallbackType => PairType == PairTypes.Key
|
||||
? ParentDictionary.m_typeOfKeys
|
||||
: ParentDictionary.m_typeofValues;
|
||||
|
||||
public override bool CanWrite => false; // todo?
|
||||
|
||||
public PairTypes PairType;
|
||||
public int Index { get; private set; }
|
||||
public InteractiveDictionary ParentDictionary { get; private set; }
|
||||
internal IDictionary RefIDict;
|
||||
|
||||
public CachePaired(int index, InteractiveDictionary parentDict, IDictionary refIDict, PairTypes pairType, GameObject parentContent)
|
||||
{
|
||||
Index = index;
|
||||
ParentDictionary = parentDict;
|
||||
RefIDict = refIDict;
|
||||
this.PairType = pairType;
|
||||
this.m_parentContent = parentContent;
|
||||
}
|
||||
|
||||
public override void CreateIValue(object value, Type fallbackType)
|
||||
{
|
||||
IValue = InteractiveValue.Create(value, fallbackType);
|
||||
IValue.Owner = this;
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal override void ConstructUI()
|
||||
{
|
||||
base.ConstructUI();
|
||||
|
||||
var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.padding.left = 5;
|
||||
rowGroup.padding.right = 2;
|
||||
|
||||
var indexLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
var indexLayout = indexLabelObj.AddComponent<LayoutElement>();
|
||||
indexLayout.minWidth = 80;
|
||||
indexLayout.flexibleWidth = 30;
|
||||
indexLayout.minHeight = 25;
|
||||
var indexText = indexLabelObj.GetComponent<Text>();
|
||||
indexText.text = $"{this.PairType} {this.Index}:";
|
||||
|
||||
IValue.m_mainContentParent = rowObj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
67
src/Inspectors/Reflection/CacheObject/CacheProperty.cs
Normal file
67
src/Inspectors/Reflection/CacheObject/CacheProperty.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class CacheProperty : CacheMember
|
||||
{
|
||||
public override Type FallbackType => (MemInfo as PropertyInfo).PropertyType;
|
||||
|
||||
public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors(true)[0].IsStatic;
|
||||
|
||||
public CacheProperty(PropertyInfo propertyInfo, object declaringInstance) : base(propertyInfo, declaringInstance)
|
||||
{
|
||||
this.m_arguments = propertyInfo.GetIndexParameters();
|
||||
this.m_argumentInput = new string[m_arguments.Length];
|
||||
|
||||
CreateIValue(null, propertyInfo.PropertyType);
|
||||
}
|
||||
|
||||
public override void UpdateReflection()
|
||||
{
|
||||
if (HasParameters && !m_isEvaluating)
|
||||
{
|
||||
// Need to enter parameters first.
|
||||
return;
|
||||
}
|
||||
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
|
||||
if (pi.CanRead)
|
||||
{
|
||||
var target = pi.GetAccessors(true)[0].IsStatic ? null : DeclaringInstance;
|
||||
|
||||
IValue.Value = pi.GetValue(target, ParseArguments());
|
||||
|
||||
m_evaluated = true;
|
||||
ReflectionException = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FallbackType == typeof(string))
|
||||
{
|
||||
IValue.Value = "";
|
||||
}
|
||||
else if (FallbackType.IsPrimitive)
|
||||
{
|
||||
IValue.Value = Activator.CreateInstance(FallbackType);
|
||||
}
|
||||
m_evaluated = true;
|
||||
ReflectionException = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
|
||||
|
||||
pi.SetValue(target, IValue.Value, ParseArguments());
|
||||
}
|
||||
}
|
||||
}
|
208
src/Inspectors/Reflection/InstanceInspector.cs
Normal file
208
src/Inspectors/Reflection/InstanceInspector.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public enum MemberScopes
|
||||
{
|
||||
All,
|
||||
Instance,
|
||||
Static
|
||||
}
|
||||
|
||||
public class InstanceInspector : ReflectionInspector
|
||||
{
|
||||
public override string TabLabel => $" <color=cyan>[R]</color> {base.TabLabel}";
|
||||
|
||||
internal MemberScopes m_scopeFilter;
|
||||
internal Button m_lastActiveScopeButton;
|
||||
|
||||
public InstanceInspector(object target) : base(target) { }
|
||||
|
||||
private void OnScopeFilterClicked(MemberScopes type, Button button)
|
||||
{
|
||||
if (m_lastActiveScopeButton)
|
||||
{
|
||||
var lastColors = m_lastActiveScopeButton.colors;
|
||||
lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||
m_lastActiveScopeButton.colors = lastColors;
|
||||
}
|
||||
|
||||
m_scopeFilter = type;
|
||||
m_lastActiveScopeButton = button;
|
||||
|
||||
var colors = m_lastActiveScopeButton.colors;
|
||||
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||
m_lastActiveScopeButton.colors = colors;
|
||||
|
||||
FilterMembers(null, true);
|
||||
m_sliderScroller.m_slider.value = 1f;
|
||||
}
|
||||
|
||||
public void ConstructInstanceHelpers()
|
||||
{
|
||||
// WIP
|
||||
|
||||
//if (m_targetType == typeof(Texture2D))
|
||||
// ConstructTextureHelper();
|
||||
|
||||
// todo other helpers
|
||||
|
||||
//if (typeof(Component).IsAssignableFrom(m_targetType))
|
||||
//{
|
||||
//}
|
||||
//else if (typeof(UnityEngine.Object).IsAssignableFrom(m_targetType))
|
||||
//{
|
||||
//}
|
||||
}
|
||||
|
||||
//internal bool showingTextureHelper;
|
||||
//internal bool constructedTextureViewer;
|
||||
|
||||
//internal void ConstructTextureHelper()
|
||||
//{
|
||||
// var rowObj = UIFactory.CreateHorizontalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||
// var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||
// rowLayout.minHeight = 25;
|
||||
// rowLayout.flexibleHeight = 0;
|
||||
// var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
// rowGroup.childForceExpandHeight = true;
|
||||
// rowGroup.childForceExpandWidth = false;
|
||||
// rowGroup.padding.top = 3;
|
||||
// rowGroup.padding.left = 3;
|
||||
// rowGroup.padding.bottom = 3;
|
||||
// rowGroup.padding.right = 3;
|
||||
// rowGroup.spacing = 5;
|
||||
|
||||
// var showBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.2f, 0.2f));
|
||||
// var showBtnLayout = showBtnObj.AddComponent<LayoutElement>();
|
||||
// showBtnLayout.minWidth = 50;
|
||||
// showBtnLayout.flexibleWidth = 0;
|
||||
// var showText = showBtnObj.GetComponentInChildren<Text>();
|
||||
// showText.text = "Show";
|
||||
// var showBtn = showBtnObj.GetComponent<Button>();
|
||||
|
||||
// var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||
// var labelText = labelObj.GetComponent<Text>();
|
||||
// labelText.text = "Texture Viewer";
|
||||
|
||||
// var textureViewerObj = UIFactory.CreateScrollView(Content, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||
// var viewerGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||
// viewerGroup.childForceExpandHeight = false;
|
||||
// viewerGroup.childForceExpandWidth = false;
|
||||
// viewerGroup.childControlHeight = true;
|
||||
// viewerGroup.childControlWidth = true;
|
||||
// var mainLayout = textureViewerObj.GetComponent<LayoutElement>();
|
||||
// mainLayout.flexibleHeight = -1;
|
||||
// mainLayout.flexibleWidth = 2000;
|
||||
// mainLayout.minHeight = 25;
|
||||
|
||||
// textureViewerObj.SetActive(false);
|
||||
|
||||
// showBtn.onClick.AddListener(() =>
|
||||
// {
|
||||
// showingTextureHelper = !showingTextureHelper;
|
||||
|
||||
// if (showingTextureHelper)
|
||||
// {
|
||||
// if (!constructedTextureViewer)
|
||||
// ConstructTextureViewerArea(scrollContent);
|
||||
|
||||
// showText.text = "Hide";
|
||||
// textureViewerObj.SetActive(true);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// showText.text = "Show";
|
||||
// textureViewerObj.SetActive(false);
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
|
||||
//internal void ConstructTextureViewerArea(GameObject parent)
|
||||
//{
|
||||
// constructedTextureViewer = true;
|
||||
|
||||
// var tex = Target as Texture2D;
|
||||
|
||||
// if (!tex)
|
||||
// {
|
||||
// ExplorerCore.LogWarning("Could not cast the target instance to Texture2D!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent, new Vector2(1, 1));
|
||||
// var image = imageObj.AddComponent<Image>();
|
||||
// var sprite = UIManager.CreateSprite(tex);
|
||||
// image.sprite = sprite;
|
||||
|
||||
// var fitter = imageObj.AddComponent<ContentSizeFitter>();
|
||||
// fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
// //fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
// var imageLayout = imageObj.AddComponent<LayoutElement>();
|
||||
// imageLayout.preferredHeight = sprite.rect.height;
|
||||
// imageLayout.preferredWidth = sprite.rect.width;
|
||||
//}
|
||||
|
||||
public void ConstructInstanceFilters(GameObject parent)
|
||||
{
|
||||
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
var memFilterGroup = memberFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
memFilterGroup.childForceExpandHeight = false;
|
||||
memFilterGroup.childForceExpandWidth = false;
|
||||
memFilterGroup.childControlWidth = true;
|
||||
memFilterGroup.childControlHeight = true;
|
||||
memFilterGroup.spacing = 5;
|
||||
var memFilterLayout = memberFilterRowObj.AddComponent<LayoutElement>();
|
||||
memFilterLayout.minHeight = 25;
|
||||
memFilterLayout.flexibleHeight = 0;
|
||||
memFilterLayout.flexibleWidth = 5000;
|
||||
|
||||
var memLabelObj = UIFactory.CreateLabel(memberFilterRowObj, TextAnchor.MiddleLeft);
|
||||
var memLabelLayout = memLabelObj.AddComponent<LayoutElement>();
|
||||
memLabelLayout.minWidth = 100;
|
||||
memLabelLayout.minHeight = 25;
|
||||
memLabelLayout.flexibleWidth = 0;
|
||||
var memLabelText = memLabelObj.GetComponent<Text>();
|
||||
memLabelText.text = "Filter scope:";
|
||||
memLabelText.color = Color.grey;
|
||||
|
||||
AddFilterButton(memberFilterRowObj, MemberScopes.All, true);
|
||||
AddFilterButton(memberFilterRowObj, MemberScopes.Instance);
|
||||
AddFilterButton(memberFilterRowObj, MemberScopes.Static);
|
||||
}
|
||||
|
||||
private void AddFilterButton(GameObject parent, MemberScopes type, bool setEnabled = false)
|
||||
{
|
||||
var btnObj = UIFactory.CreateButton(parent, new Color(0.2f, 0.2f, 0.2f));
|
||||
|
||||
var btnLayout = btnObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.minWidth = 70;
|
||||
|
||||
var text = btnObj.GetComponentInChildren<Text>();
|
||||
text.text = type.ToString();
|
||||
|
||||
var btn = btnObj.GetComponent<Button>();
|
||||
|
||||
btn.onClick.AddListener(() => { OnScopeFilterClicked(type, btn); });
|
||||
|
||||
var colors = btn.colors;
|
||||
colors.highlightedColor = new Color(0.3f, 0.7f, 0.3f);
|
||||
|
||||
if (setEnabled)
|
||||
{
|
||||
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||
m_scopeFilter = type;
|
||||
m_lastActiveScopeButton = btn;
|
||||
}
|
||||
|
||||
btn.colors = colors;
|
||||
}
|
||||
}
|
||||
}
|
114
src/Inspectors/Reflection/InteractiveValue/InteractiveBool.cs
Normal file
114
src/Inspectors/Reflection/InteractiveValue/InteractiveBool.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveBool : InteractiveValue
|
||||
{
|
||||
public InteractiveBool(object value, Type valueType) : base(value, valueType) { }
|
||||
|
||||
public override bool HasSubContent => false;
|
||||
public override bool SubContentWanted => false;
|
||||
public override bool WantInspectBtn => false;
|
||||
|
||||
internal Toggle m_toggle;
|
||||
internal Button m_applyBtn;
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel();
|
||||
|
||||
if (Owner.HasEvaluated)
|
||||
{
|
||||
var val = (bool)Value;
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
if (!m_toggle.gameObject.activeSelf)
|
||||
m_toggle.gameObject.SetActive(true);
|
||||
|
||||
if (!m_applyBtn.gameObject.activeSelf)
|
||||
m_applyBtn.gameObject.SetActive(true);
|
||||
|
||||
if (val != m_toggle.isOn)
|
||||
m_toggle.isOn = val;
|
||||
}
|
||||
|
||||
var color = val
|
||||
? "6bc981" // on
|
||||
: "c96b6b"; // off
|
||||
|
||||
m_baseLabel.text = $"<color=#{color}>{val}</color>";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnException(CacheMember member)
|
||||
{
|
||||
base.OnException(member);
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
if (m_toggle.gameObject.activeSelf)
|
||||
m_toggle.gameObject.SetActive(false);
|
||||
|
||||
if (m_applyBtn.gameObject.activeSelf)
|
||||
m_applyBtn.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnToggleValueChanged(bool val)
|
||||
{
|
||||
Value = val;
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
|
||||
var baseLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||
baseLayout.flexibleWidth = 0;
|
||||
baseLayout.minWidth = 50;
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var toggleObj = UIFactory.CreateToggle(m_valueContent, out m_toggle, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minWidth = 24;
|
||||
|
||||
m_toggle.onValueChanged.AddListener(OnToggleValueChanged);
|
||||
|
||||
m_baseLabel.transform.SetAsLastSibling();
|
||||
|
||||
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 50;
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.flexibleWidth = 0;
|
||||
m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
m_applyBtn.onClick.AddListener(() => { Owner.SetValue(); });
|
||||
|
||||
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
|
||||
toggleObj.SetActive(false);
|
||||
applyBtnObj.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveDictionary : InteractiveValue
|
||||
{
|
||||
public InteractiveDictionary(object value, Type valueType) : base(value, valueType)
|
||||
{
|
||||
if (valueType.IsGenericType)
|
||||
{
|
||||
var gArgs = valueType.GetGenericArguments();
|
||||
m_typeOfKeys = gArgs[0];
|
||||
m_typeofValues = gArgs[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_typeOfKeys = typeof(object);
|
||||
m_typeofValues = typeof(object);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool WantInspectBtn => false;
|
||||
public override bool HasSubContent => true;
|
||||
// todo fix for il2cpp
|
||||
public override bool SubContentWanted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_recacheWanted)
|
||||
return true;
|
||||
else return m_entries.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal IDictionary RefIDictionary;
|
||||
|
||||
internal Type m_typeOfKeys;
|
||||
internal Type m_typeofValues;
|
||||
|
||||
internal readonly List<KeyValuePair<CachePaired, CachePaired>> m_entries
|
||||
= new List<KeyValuePair<CachePaired, CachePaired>>();
|
||||
|
||||
internal readonly KeyValuePair<CachePaired, CachePaired>[] m_displayedEntries
|
||||
= new KeyValuePair<CachePaired, CachePaired>[ModConfig.Instance.Default_Page_Limit];
|
||||
|
||||
internal bool m_recacheWanted = true;
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
RefIDictionary = Value as IDictionary;
|
||||
|
||||
if (m_subContentParent.activeSelf)
|
||||
{
|
||||
GetCacheEntries();
|
||||
RefreshDisplay();
|
||||
}
|
||||
else
|
||||
m_recacheWanted = true;
|
||||
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
internal void OnPageTurned()
|
||||
{
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel();
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
string count = "?";
|
||||
if (m_recacheWanted && RefIDictionary != null)
|
||||
count = RefIDictionary.Count.ToString();
|
||||
else if (!m_recacheWanted)
|
||||
count = m_entries.Count.ToString();
|
||||
|
||||
m_baseLabel.text = $"[{count}] {m_richValueType}";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public void GetCacheEntries()
|
||||
{
|
||||
if (m_entries.Any())
|
||||
{
|
||||
// maybe improve this, probably could be more efficient i guess
|
||||
|
||||
foreach (var pair in m_entries)
|
||||
{
|
||||
pair.Key.Destroy();
|
||||
pair.Value.Destroy();
|
||||
}
|
||||
|
||||
m_entries.Clear();
|
||||
}
|
||||
|
||||
#if CPP
|
||||
if (RefIDictionary == null && Value != null)
|
||||
RefIDictionary = EnumerateWithReflection();
|
||||
#endif
|
||||
|
||||
if (RefIDictionary != null)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
foreach (var key in RefIDictionary.Keys)
|
||||
{
|
||||
var value = RefIDictionary[key];
|
||||
|
||||
//if (index >= m_rowHolders.Count)
|
||||
//{
|
||||
// AddRowHolder();
|
||||
//}
|
||||
|
||||
//var holder = m_rowHolders[index];
|
||||
|
||||
var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent);
|
||||
cacheKey.CreateIValue(key, this.m_typeOfKeys);
|
||||
cacheKey.Disable();
|
||||
|
||||
var cacheValue = new CachePaired(index, this, this.RefIDictionary, PairTypes.Value, m_listContent);
|
||||
cacheValue.CreateIValue(value, this.m_typeofValues);
|
||||
cacheValue.Disable();
|
||||
|
||||
//holder.SetActive(false);
|
||||
|
||||
m_entries.Add(new KeyValuePair<CachePaired, CachePaired>(cacheKey, cacheValue));
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
public void RefreshDisplay()
|
||||
{
|
||||
var entries = m_entries;
|
||||
m_pageHandler.ListCount = entries.Count;
|
||||
|
||||
for (int i = 0; i < m_displayedEntries.Length; i++)
|
||||
{
|
||||
var entry = m_displayedEntries[i];
|
||||
if (entry.Key != null && entry.Value != null)
|
||||
{
|
||||
//m_rowHolders[i].SetActive(false);
|
||||
entry.Key.Disable();
|
||||
entry.Value.Disable();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (entries.Count < 1)
|
||||
return;
|
||||
|
||||
foreach (var itemIndex in m_pageHandler)
|
||||
{
|
||||
if (itemIndex >= entries.Count)
|
||||
break;
|
||||
|
||||
var entry = entries[itemIndex];
|
||||
m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry;
|
||||
|
||||
//m_rowHolders[itemIndex].SetActive(true);
|
||||
entry.Key.Enable();
|
||||
entry.Value.Enable();
|
||||
}
|
||||
|
||||
//UpdateSubcontentHeight();
|
||||
}
|
||||
|
||||
internal override void OnToggleSubcontent(bool active)
|
||||
{
|
||||
base.OnToggleSubcontent(active);
|
||||
|
||||
if (active && m_recacheWanted)
|
||||
{
|
||||
m_recacheWanted = false;
|
||||
GetCacheEntries();
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
#region CPP fixes
|
||||
#if CPP
|
||||
// temp fix for Il2Cpp IDictionary until interfaces are fixed
|
||||
private IDictionary EnumerateWithReflection()
|
||||
{
|
||||
var valueType = Value?.GetType() ?? FallbackType;
|
||||
|
||||
// get keys and values
|
||||
var keys = valueType.GetProperty("Keys").GetValue(Value, null);
|
||||
var values = valueType.GetProperty("Values").GetValue(Value, null);
|
||||
|
||||
// create lists to hold them
|
||||
var keyList = new List<object>();
|
||||
var valueList = new List<object>();
|
||||
|
||||
// store entries with reflection
|
||||
EnumerateWithReflection(keys, keyList);
|
||||
EnumerateWithReflection(values, valueList);
|
||||
|
||||
// make actual mono dictionary
|
||||
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
||||
.MakeGenericType(m_typeOfKeys, m_typeofValues));
|
||||
|
||||
// finally iterate into mono dictionary
|
||||
for (int i = 0; i < keyList.Count; i++)
|
||||
dict.Add(keyList[i], valueList[i]);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private void EnumerateWithReflection(object collection, List<object> list)
|
||||
{
|
||||
// invoke GetEnumerator
|
||||
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
list.Add(current.GetValue(enumerator, null));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal GameObject m_listContent;
|
||||
internal LayoutElement m_listLayout;
|
||||
|
||||
internal PageHandler m_pageHandler;
|
||||
|
||||
//internal List<GameObject> m_rowHolders = new List<GameObject>();
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
}
|
||||
|
||||
public override void ConstructSubcontent()
|
||||
{
|
||||
base.ConstructSubcontent();
|
||||
|
||||
m_pageHandler = new PageHandler(null);
|
||||
m_pageHandler.ConstructUI(m_subContentParent);
|
||||
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||
|
||||
var scrollObj = UIFactory.CreateVerticalGroup(this.m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||
m_listContent = scrollObj;
|
||||
|
||||
var scrollRect = scrollObj.GetComponent<RectTransform>();
|
||||
scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
|
||||
|
||||
m_listLayout = Owner.m_mainContent.GetComponent<LayoutElement>();
|
||||
m_listLayout.minHeight = 25;
|
||||
m_listLayout.flexibleHeight = 0;
|
||||
Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||
|
||||
var scrollGroup = m_listContent.GetComponent<VerticalLayoutGroup>();
|
||||
scrollGroup.childForceExpandHeight = true;
|
||||
scrollGroup.childControlHeight = true;
|
||||
scrollGroup.spacing = 2;
|
||||
scrollGroup.padding.top = 5;
|
||||
scrollGroup.padding.left = 5;
|
||||
scrollGroup.padding.right = 5;
|
||||
scrollGroup.padding.bottom = 5;
|
||||
|
||||
var contentFitter = scrollObj.AddComponent<ContentSizeFitter>();
|
||||
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
|
||||
//internal void AddRowHolder()
|
||||
//{
|
||||
// var obj = UIFactory.CreateHorizontalGroup(m_listContent, new Color(0.15f, 0.15f, 0.15f));
|
||||
|
||||
// m_rowHolders.Add(obj);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
161
src/Inspectors/Reflection/InteractiveValue/InteractiveEnum.cs
Normal file
161
src/Inspectors/Reflection/InteractiveValue/InteractiveEnum.cs
Normal file
@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveEnum : InteractiveValue
|
||||
{
|
||||
internal static Dictionary<Type, KeyValuePair<int,string>[]> s_enumNamesCache = new Dictionary<Type, KeyValuePair<int, string>[]>();
|
||||
|
||||
public InteractiveEnum(object value, Type valueType) : base(value, valueType)
|
||||
{
|
||||
GetNames();
|
||||
}
|
||||
|
||||
public override bool HasSubContent => true;
|
||||
public override bool SubContentWanted => Owner.CanWrite;
|
||||
public override bool WantInspectBtn => false;
|
||||
|
||||
internal KeyValuePair<int,string>[] m_values = new KeyValuePair<int, string>[0];
|
||||
|
||||
internal Type m_lastEnumType;
|
||||
|
||||
internal void GetNames()
|
||||
{
|
||||
var type = Value?.GetType() ?? FallbackType;
|
||||
|
||||
if (m_lastEnumType == type)
|
||||
return;
|
||||
|
||||
m_lastEnumType = type;
|
||||
|
||||
if (m_subContentConstructed)
|
||||
{
|
||||
// changing types, destroy subcontent
|
||||
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
|
||||
{
|
||||
var child = m_subContentParent.transform.GetChild(i);
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
m_subContentConstructed = false;
|
||||
}
|
||||
|
||||
if (!s_enumNamesCache.ContainsKey(type))
|
||||
{
|
||||
// using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags)
|
||||
var values = Enum.GetValues(type);
|
||||
|
||||
var list = new List<KeyValuePair<int, string>>();
|
||||
var set = new HashSet<string>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
var name = value.ToString();
|
||||
if (set.Contains(name))
|
||||
continue;
|
||||
set.Add(name);
|
||||
list.Add(new KeyValuePair<int, string>((int)value, name));
|
||||
}
|
||||
|
||||
s_enumNamesCache.Add(type, list.ToArray());
|
||||
}
|
||||
|
||||
m_values = s_enumNamesCache[type];
|
||||
}
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
GetNames();
|
||||
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
base.RefreshUIForValue();
|
||||
|
||||
if (m_subContentConstructed)
|
||||
{
|
||||
m_dropdownText.text = Value?.ToString() ?? "<no value set>";
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnToggleSubcontent(bool toggle)
|
||||
{
|
||||
base.OnToggleSubcontent(toggle);
|
||||
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
private void SetValueFromDropdown()
|
||||
{
|
||||
var type = Value?.GetType() ?? FallbackType;
|
||||
var value = Enum.Parse(type, m_dropdownText.text);
|
||||
if (value != null)
|
||||
{
|
||||
Value = value;
|
||||
Owner.SetValue();
|
||||
RefreshUIForValue();
|
||||
}
|
||||
}
|
||||
|
||||
internal Dropdown m_dropdown;
|
||||
internal Text m_dropdownText;
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
}
|
||||
|
||||
public override void ConstructSubcontent()
|
||||
{
|
||||
base.ConstructSubcontent();
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var groupObj = UIFactory.CreateHorizontalGroup(m_subContentParent, new Color(1, 1, 1, 0));
|
||||
var group = groupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
group.padding.top = 3;
|
||||
group.padding.left = 3;
|
||||
group.padding.right = 3;
|
||||
group.padding.bottom = 3;
|
||||
group.spacing = 5;
|
||||
|
||||
// apply button
|
||||
|
||||
var applyObj = UIFactory.CreateButton(groupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||
var applyLayout = applyObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.minWidth = 50;
|
||||
var applyText = applyObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
var applyBtn = applyObj.GetComponent<Button>();
|
||||
applyBtn.onClick.AddListener(SetValueFromDropdown);
|
||||
|
||||
// dropdown
|
||||
|
||||
var dropdownObj = UIFactory.CreateDropdown(groupObj, out m_dropdown);
|
||||
var dropLayout = dropdownObj.AddComponent<LayoutElement>();
|
||||
dropLayout.minWidth = 150;
|
||||
dropLayout.minHeight = 25;
|
||||
dropLayout.flexibleWidth = 120;
|
||||
|
||||
foreach (var kvp in m_values)
|
||||
{
|
||||
m_dropdown.options.Add(new Dropdown.OptionData
|
||||
{
|
||||
text = $"{kvp.Key}: {kvp.Value}"
|
||||
});
|
||||
}
|
||||
|
||||
m_dropdownText = m_dropdown.transform.Find("Label").GetComponent<Text>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveEnumerable : InteractiveValue
|
||||
{
|
||||
public InteractiveEnumerable(object value, Type valueType) : base(value, valueType)
|
||||
{
|
||||
if (valueType.IsGenericType)
|
||||
m_baseEntryType = valueType.GetGenericArguments()[0];
|
||||
else
|
||||
m_baseEntryType = typeof(object);
|
||||
}
|
||||
|
||||
public override bool WantInspectBtn => false;
|
||||
public override bool HasSubContent => true;
|
||||
public override bool SubContentWanted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_recacheWanted)
|
||||
return true;
|
||||
else return m_entries.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable RefIEnumerable;
|
||||
internal IList RefIList;
|
||||
|
||||
internal readonly Type m_baseEntryType;
|
||||
|
||||
internal readonly List<CacheEnumerated> m_entries = new List<CacheEnumerated>();
|
||||
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ModConfig.Instance.Default_Page_Limit];
|
||||
internal bool m_recacheWanted = true;
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
RefIEnumerable = Value as IEnumerable;
|
||||
RefIList = Value as IList;
|
||||
|
||||
if (m_subContentParent.activeSelf)
|
||||
{
|
||||
GetCacheEntries();
|
||||
RefreshDisplay();
|
||||
}
|
||||
else
|
||||
m_recacheWanted = true;
|
||||
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
public override void OnException(CacheMember member)
|
||||
{
|
||||
base.OnException(member);
|
||||
}
|
||||
|
||||
private void OnPageTurned()
|
||||
{
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel();
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
string count = "?";
|
||||
if (m_recacheWanted && RefIList != null)
|
||||
count = RefIList.Count.ToString();
|
||||
else if (!m_recacheWanted)
|
||||
count = m_entries.Count.ToString();
|
||||
|
||||
m_baseLabel.text = $"[{count}] {m_richValueType}";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public void GetCacheEntries()
|
||||
{
|
||||
if (m_entries.Any())
|
||||
{
|
||||
// maybe improve this, probably could be more efficient i guess
|
||||
|
||||
foreach (var entry in m_entries)
|
||||
entry.Destroy();
|
||||
|
||||
m_entries.Clear();
|
||||
}
|
||||
|
||||
#if CPP
|
||||
if (RefIEnumerable == null && Value != null)
|
||||
RefIEnumerable = EnumerateWithReflection();
|
||||
#endif
|
||||
|
||||
if (RefIEnumerable != null)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var entry in RefIEnumerable)
|
||||
{
|
||||
var cache = new CacheEnumerated(index, this, RefIList, this.m_listContent);
|
||||
cache.CreateIValue(entry, m_baseEntryType);
|
||||
m_entries.Add(cache);
|
||||
|
||||
cache.Disable();
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
public void RefreshDisplay()
|
||||
{
|
||||
var entries = m_entries;
|
||||
m_pageHandler.ListCount = entries.Count;
|
||||
|
||||
for (int i = 0; i < m_displayedEntries.Length; i++)
|
||||
{
|
||||
var entry = m_displayedEntries[i];
|
||||
if (entry != null)
|
||||
entry.Disable();
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (entries.Count < 1)
|
||||
return;
|
||||
|
||||
foreach (var itemIndex in m_pageHandler)
|
||||
{
|
||||
if (itemIndex >= entries.Count)
|
||||
break;
|
||||
|
||||
CacheEnumerated entry = entries[itemIndex];
|
||||
m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry;
|
||||
entry.Enable();
|
||||
}
|
||||
|
||||
//UpdateSubcontentHeight();
|
||||
}
|
||||
|
||||
internal override void OnToggleSubcontent(bool active)
|
||||
{
|
||||
base.OnToggleSubcontent(active);
|
||||
|
||||
if (active && m_recacheWanted)
|
||||
{
|
||||
m_recacheWanted = false;
|
||||
GetCacheEntries();
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
#region CPP Helpers
|
||||
|
||||
#if CPP
|
||||
// some temp fixes for Il2Cpp IEnumerables until interfaces are fixed
|
||||
|
||||
private IEnumerable EnumerateWithReflection()
|
||||
{
|
||||
if (Value.IsNullOrDestroyed())
|
||||
return null;
|
||||
|
||||
var genericDef = Value.GetType().GetGenericTypeDefinition();
|
||||
|
||||
if (genericDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||
return CppListToMono(genericDef);
|
||||
else if (genericDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
|
||||
return CppHashSetToMono();
|
||||
else
|
||||
return CppIListToMono();
|
||||
}
|
||||
|
||||
// List<T>.ToArray()
|
||||
private IEnumerable CppListToMono(Type genericTypeDef)
|
||||
{
|
||||
if (genericTypeDef == null) return null;
|
||||
|
||||
return genericTypeDef
|
||||
.MakeGenericType(new Type[] { this.m_baseEntryType })
|
||||
.GetMethod("ToArray")
|
||||
.Invoke(Value, new object[0]) as IEnumerable;
|
||||
}
|
||||
|
||||
// HashSet.GetEnumerator
|
||||
private IEnumerable CppHashSetToMono()
|
||||
{
|
||||
var set = new HashSet<object>();
|
||||
|
||||
// invoke GetEnumerator
|
||||
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
set.Add(current.GetValue(enumerator));
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
// IList.Item
|
||||
private IList CppIListToMono()
|
||||
{
|
||||
try
|
||||
{
|
||||
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.m_baseEntryType });
|
||||
var list = (IList)Activator.CreateInstance(genericType);
|
||||
|
||||
for (int i = 0; ; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var itm = Value?.GetType()
|
||||
.GetProperty("Item")
|
||||
.GetValue(Value, new object[] { i });
|
||||
list.Add(itm);
|
||||
}
|
||||
catch { break; }
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal GameObject m_listContent;
|
||||
internal LayoutElement m_listLayout;
|
||||
|
||||
internal PageHandler m_pageHandler;
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
}
|
||||
|
||||
public override void ConstructSubcontent()
|
||||
{
|
||||
base.ConstructSubcontent();
|
||||
|
||||
m_pageHandler = new PageHandler(null);
|
||||
m_pageHandler.ConstructUI(m_subContentParent);
|
||||
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||
|
||||
var scrollObj = UIFactory.CreateVerticalGroup(this.m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||
m_listContent = scrollObj;
|
||||
|
||||
var scrollRect = scrollObj.GetComponent<RectTransform>();
|
||||
scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
|
||||
|
||||
m_listLayout = Owner.m_mainContent.GetComponent<LayoutElement>();
|
||||
m_listLayout.minHeight = 25;
|
||||
m_listLayout.flexibleHeight = 0;
|
||||
Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||
|
||||
var scrollGroup = m_listContent.GetComponent<VerticalLayoutGroup>();
|
||||
scrollGroup.childForceExpandHeight = true;
|
||||
scrollGroup.childControlHeight = true;
|
||||
scrollGroup.spacing = 2;
|
||||
scrollGroup.padding.top = 5;
|
||||
scrollGroup.padding.left = 5;
|
||||
scrollGroup.padding.right = 5;
|
||||
scrollGroup.padding.bottom = 5;
|
||||
|
||||
var contentFitter = scrollObj.AddComponent<ContentSizeFitter>();
|
||||
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
146
src/Inspectors/Reflection/InteractiveValue/InteractiveFlags.cs
Normal file
146
src/Inspectors/Reflection/InteractiveValue/InteractiveFlags.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveFlags : InteractiveEnum
|
||||
{
|
||||
public InteractiveFlags(object value, Type valueType) : base(value, valueType)
|
||||
{
|
||||
m_toggles = new Toggle[m_values.Length];
|
||||
m_enabledFlags = new bool[m_values.Length];
|
||||
}
|
||||
|
||||
public override bool HasSubContent => true;
|
||||
public override bool SubContentWanted => Owner.CanWrite;
|
||||
public override bool WantInspectBtn => false;
|
||||
|
||||
internal bool[] m_enabledFlags;
|
||||
internal Toggle[] m_toggles;
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
base.OnValueUpdated();
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var enabledNames = new List<string>();
|
||||
|
||||
var enabled = Value?.ToString().Split(',').Select(it => it.Trim());
|
||||
if (enabled != null)
|
||||
enabledNames.AddRange(enabled);
|
||||
|
||||
for (int i = 0; i < m_values.Length; i++)
|
||||
{
|
||||
m_enabledFlags[i] = enabledNames.Contains(m_values[i].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel();
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
|
||||
if (m_subContentConstructed)
|
||||
{
|
||||
for (int i = 0; i < m_values.Length; i++)
|
||||
{
|
||||
var toggle = m_toggles[i];
|
||||
if (toggle.isOn != m_enabledFlags[i])
|
||||
toggle.isOn = m_enabledFlags[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromToggles()
|
||||
{
|
||||
string val = "";
|
||||
for (int i = 0; i < m_values.Length; i++)
|
||||
{
|
||||
if (m_enabledFlags[i])
|
||||
{
|
||||
if (val != "") val += ", ";
|
||||
val += m_values[i].Value;
|
||||
}
|
||||
}
|
||||
var type = Value?.GetType() ?? FallbackType;
|
||||
Value = Enum.Parse(type, val);
|
||||
RefreshUIForValue();
|
||||
Owner.SetValue();
|
||||
}
|
||||
|
||||
internal override void OnToggleSubcontent(bool toggle)
|
||||
{
|
||||
base.OnToggleSubcontent(toggle);
|
||||
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
}
|
||||
|
||||
public override void ConstructSubcontent()
|
||||
{
|
||||
m_subContentConstructed = true;
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(1, 1, 1, 0));
|
||||
var group = groupObj.GetComponent<VerticalLayoutGroup>();
|
||||
group.childForceExpandHeight = true;
|
||||
group.childForceExpandWidth = false;
|
||||
group.childControlHeight = true;
|
||||
group.childControlWidth = true;
|
||||
group.padding.top = 3;
|
||||
group.padding.left = 3;
|
||||
group.padding.right = 3;
|
||||
group.padding.bottom = 3;
|
||||
group.spacing = 5;
|
||||
|
||||
// apply button
|
||||
|
||||
var applyObj = UIFactory.CreateButton(groupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||
var applyLayout = applyObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.minWidth = 50;
|
||||
var applyText = applyObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
var applyBtn = applyObj.GetComponent<Button>();
|
||||
applyBtn.onClick.AddListener(SetValueFromToggles);
|
||||
|
||||
// toggles
|
||||
|
||||
for (int i = 0; i < m_values.Length; i++)
|
||||
{
|
||||
AddToggle(i, groupObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddToggle(int index, GameObject groupObj)
|
||||
{
|
||||
var value = m_values[index];
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(groupObj, out Toggle toggle, out Text text, new Color(0.1f, 0.1f, 0.1f));
|
||||
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minWidth = 100;
|
||||
toggleLayout.flexibleWidth = 2000;
|
||||
toggleLayout.minHeight = 25;
|
||||
|
||||
m_toggles[index] = toggle;
|
||||
|
||||
toggle.onValueChanged.AddListener((bool val) => { m_enabledFlags[index] = val; });
|
||||
|
||||
text.text = $"{value.Key}: {value.Value}";
|
||||
}
|
||||
}
|
||||
}
|
126
src/Inspectors/Reflection/InteractiveValue/InteractiveNumber.cs
Normal file
126
src/Inspectors/Reflection/InteractiveValue/InteractiveNumber.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveNumber : InteractiveValue
|
||||
{
|
||||
public InteractiveNumber(object value, Type valueType) : base(value, valueType) { }
|
||||
|
||||
public override bool HasSubContent => false;
|
||||
public override bool SubContentWanted => false;
|
||||
public override bool WantInspectBtn => false;
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
public override void OnException(CacheMember member)
|
||||
{
|
||||
base.OnException(member);
|
||||
|
||||
if (m_valueInput.gameObject.activeSelf)
|
||||
m_valueInput.gameObject.SetActive(false);
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
if (m_applyBtn.gameObject.activeSelf)
|
||||
m_applyBtn.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
if (!Owner.HasEvaluated)
|
||||
{
|
||||
GetDefaultLabel();
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
return;
|
||||
}
|
||||
|
||||
m_baseLabel.text = UISyntaxHighlight.ParseFullSyntax(FallbackType, false);
|
||||
m_valueInput.text = Value.ToString();
|
||||
|
||||
var type = Value.GetType();
|
||||
if (type == typeof(float)
|
||||
|| type == typeof(double)
|
||||
|| type == typeof(decimal))
|
||||
{
|
||||
m_valueInput.characterValidation = InputField.CharacterValidation.Decimal;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_valueInput.characterValidation = InputField.CharacterValidation.Integer;
|
||||
}
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
if (!m_applyBtn.gameObject.activeSelf)
|
||||
m_applyBtn.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
if (!m_valueInput.gameObject.activeSelf)
|
||||
m_valueInput.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
|
||||
private MethodInfo m_parseMethod;
|
||||
|
||||
internal void OnApplyClicked()
|
||||
{
|
||||
try
|
||||
{
|
||||
Value = ParseMethod.Invoke(null, new object[] { m_valueInput.text });
|
||||
Owner.SetValue();
|
||||
RefreshUIForValue();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning("Could not parse input! " + ReflectionHelpers.ExceptionToString(e, true));
|
||||
}
|
||||
}
|
||||
|
||||
internal InputField m_valueInput;
|
||||
internal Button m_applyBtn;
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
|
||||
var labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||
labelLayout.minWidth = 50;
|
||||
labelLayout.flexibleWidth = 0;
|
||||
|
||||
var inputObj = UIFactory.CreateInputField(m_valueContent);
|
||||
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||
inputLayout.minWidth = 120;
|
||||
inputLayout.minHeight = 25;
|
||||
inputLayout.flexibleWidth = 0;
|
||||
|
||||
m_valueInput = inputObj.GetComponent<InputField>();
|
||||
m_valueInput.gameObject.SetActive(false);
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 50;
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.flexibleWidth = 0;
|
||||
m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
m_applyBtn.onClick.AddListener(OnApplyClicked);
|
||||
|
||||
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
src/Inspectors/Reflection/InteractiveValue/InteractiveString.cs
Normal file
149
src/Inspectors/Reflection/InteractiveValue/InteractiveString.cs
Normal file
@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveString : InteractiveValue
|
||||
{
|
||||
public InteractiveString(object value, Type valueType) : base(value, valueType) { }
|
||||
|
||||
public override bool HasSubContent => false;
|
||||
public override bool SubContentWanted => false;
|
||||
public override bool WantInspectBtn => false;
|
||||
|
||||
public override void OnValueUpdated()
|
||||
{
|
||||
base.OnValueUpdated();
|
||||
}
|
||||
|
||||
public override void OnException(CacheMember member)
|
||||
{
|
||||
base.OnException(member);
|
||||
|
||||
if (m_hiddenObj.gameObject.activeSelf)
|
||||
m_hiddenObj.gameObject.SetActive(false);
|
||||
|
||||
// m_baseLabel.text = DefaultLabel;
|
||||
m_labelLayout.minWidth = 200;
|
||||
m_labelLayout.flexibleWidth = 5000;
|
||||
}
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel(false);
|
||||
|
||||
if (!Owner.HasEvaluated)
|
||||
{
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_hiddenObj.gameObject.activeSelf)
|
||||
m_hiddenObj.gameObject.SetActive(true);
|
||||
|
||||
m_baseLabel.text = m_richValueType;
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
var toString = Value.ToString();
|
||||
if (toString.Length > 15000)
|
||||
toString = toString.Substring(0, 15000);
|
||||
|
||||
m_valueInput.text = toString;
|
||||
m_placeholderText.text = toString;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_valueInput.text = "";
|
||||
m_placeholderText.text = "null";
|
||||
}
|
||||
|
||||
m_labelLayout.minWidth = 50;
|
||||
m_labelLayout.flexibleWidth = 0;
|
||||
}
|
||||
|
||||
|
||||
internal void OnApplyClicked()
|
||||
{
|
||||
Value = m_valueInput.text;
|
||||
Owner.SetValue();
|
||||
}
|
||||
|
||||
internal InputField m_valueInput;
|
||||
internal LayoutElement m_labelLayout;
|
||||
internal GameObject m_hiddenObj;
|
||||
internal Text m_placeholderText;
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
|
||||
GetDefaultLabel(false);
|
||||
m_richValueType = UISyntaxHighlight.ParseFullSyntax(FallbackType, false);
|
||||
|
||||
m_labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||
|
||||
m_hiddenObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
|
||||
m_hiddenObj.SetActive(false);
|
||||
var hiddenText = m_hiddenObj.GetComponent<Text>();
|
||||
hiddenText.color = Color.clear;
|
||||
hiddenText.fontSize = 14;
|
||||
hiddenText.raycastTarget = false;
|
||||
var hiddenFitter = m_hiddenObj.AddComponent<ContentSizeFitter>();
|
||||
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
var hiddenLayout = m_hiddenObj.AddComponent<LayoutElement>();
|
||||
hiddenLayout.minHeight = 25;
|
||||
hiddenLayout.flexibleHeight = 500;
|
||||
hiddenLayout.minWidth = 250;
|
||||
hiddenLayout.flexibleWidth = 9000;
|
||||
var hiddenGroup = m_hiddenObj.AddComponent<HorizontalLayoutGroup>();
|
||||
hiddenGroup.childForceExpandWidth = true;
|
||||
hiddenGroup.childControlWidth = true;
|
||||
hiddenGroup.childForceExpandHeight = true;
|
||||
hiddenGroup.childControlHeight = true;
|
||||
|
||||
var inputObj = UIFactory.CreateInputField(m_hiddenObj, 14, 3);
|
||||
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||
inputLayout.minWidth = 120;
|
||||
inputLayout.minHeight = 25;
|
||||
inputLayout.flexibleWidth = 5000;
|
||||
inputLayout.flexibleHeight = 5000;
|
||||
|
||||
m_valueInput = inputObj.GetComponent<InputField>();
|
||||
m_valueInput.lineType = InputField.LineType.MultiLineNewline;
|
||||
|
||||
m_placeholderText = m_valueInput.placeholder.GetComponent<Text>();
|
||||
|
||||
m_valueInput.onValueChanged.AddListener((string val) =>
|
||||
{
|
||||
hiddenText.text = val;
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Owner.m_mainRect);
|
||||
});
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 50;
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.flexibleWidth = 0;
|
||||
var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
applyBtn.onClick.AddListener(OnApplyClicked);
|
||||
|
||||
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_valueInput.readOnly = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
#region IStructInfo helper
|
||||
|
||||
public interface IStructInfo
|
||||
{
|
||||
string[] FieldNames { get; }
|
||||
object SetValue(ref object value, int fieldIndex, float val);
|
||||
void RefreshUI(InputField[] inputs, object value);
|
||||
}
|
||||
|
||||
public class StructInfo<T> : IStructInfo where T : struct
|
||||
{
|
||||
public string[] FieldNames { get; set; }
|
||||
|
||||
public delegate void SetMethod(ref T value, int fieldIndex, float val);
|
||||
public SetMethod SetValueMethod;
|
||||
|
||||
public delegate void UpdateMethod(InputField[] inputs, object value);
|
||||
public UpdateMethod UpdateUIMethod;
|
||||
|
||||
public object SetValue(ref object value, int fieldIndex, float val)
|
||||
{
|
||||
var box = (T)value;
|
||||
SetValueMethod.Invoke(ref box, fieldIndex, val);
|
||||
return box;
|
||||
}
|
||||
|
||||
public void RefreshUI(InputField[] inputs, object value)
|
||||
{
|
||||
UpdateUIMethod.Invoke(inputs, value);
|
||||
}
|
||||
}
|
||||
|
||||
// This part is a bit ugly, but everything else is generalized above.
|
||||
// I could generalize it more with reflection, but it would be different for
|
||||
// mono/il2cpp and also slower.
|
||||
public static class StructInfoFactory
|
||||
{
|
||||
public static IStructInfo Create(Type type)
|
||||
{
|
||||
if (type == typeof(Vector2))
|
||||
{
|
||||
return new StructInfo<Vector2>()
|
||||
{
|
||||
FieldNames = new[] { "x", "y", },
|
||||
SetValueMethod = (ref Vector2 vec, int fieldIndex, float val) =>
|
||||
{
|
||||
switch (fieldIndex)
|
||||
{
|
||||
case 0: vec.x = val; break;
|
||||
case 1: vec.y = val; break;
|
||||
}
|
||||
},
|
||||
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||
{
|
||||
Vector2 vec = (Vector2)value;
|
||||
inputs[0].text = vec.x.ToString();
|
||||
inputs[1].text = vec.y.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (type == typeof(Vector3))
|
||||
{
|
||||
return new StructInfo<Vector3>()
|
||||
{
|
||||
FieldNames = new[] { "x", "y", "z" },
|
||||
SetValueMethod = (ref Vector3 vec, int fieldIndex, float val) =>
|
||||
{
|
||||
switch (fieldIndex)
|
||||
{
|
||||
case 0: vec.x = val; break;
|
||||
case 1: vec.y = val; break;
|
||||
case 2: vec.z = val; break;
|
||||
}
|
||||
},
|
||||
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||
{
|
||||
Vector3 vec = (Vector3)value;
|
||||
inputs[0].text = vec.x.ToString();
|
||||
inputs[1].text = vec.y.ToString();
|
||||
inputs[2].text = vec.z.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (type == typeof(Vector4))
|
||||
{
|
||||
return new StructInfo<Vector4>()
|
||||
{
|
||||
FieldNames = new[] { "x", "y", "z", "w" },
|
||||
SetValueMethod = (ref Vector4 vec, int fieldIndex, float val) =>
|
||||
{
|
||||
switch (fieldIndex)
|
||||
{
|
||||
case 0: vec.x = val; break;
|
||||
case 1: vec.y = val; break;
|
||||
case 2: vec.z = val; break;
|
||||
case 3: vec.w = val; break;
|
||||
}
|
||||
},
|
||||
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||
{
|
||||
Vector4 vec = (Vector4)value;
|
||||
inputs[0].text = vec.x.ToString();
|
||||
inputs[1].text = vec.y.ToString();
|
||||
inputs[2].text = vec.z.ToString();
|
||||
inputs[3].text = vec.w.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (type == typeof(Rect))
|
||||
{
|
||||
return new StructInfo<Rect>()
|
||||
{
|
||||
FieldNames = new[] { "x", "y", "width", "height" },
|
||||
SetValueMethod = (ref Rect vec, int fieldIndex, float val) =>
|
||||
{
|
||||
switch (fieldIndex)
|
||||
{
|
||||
case 0: vec.x = val; break;
|
||||
case 1: vec.y = val; break;
|
||||
case 2: vec.width = val; break;
|
||||
case 3: vec.height = val; break;
|
||||
}
|
||||
},
|
||||
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||
{
|
||||
Rect vec = (Rect)value;
|
||||
inputs[0].text = vec.x.ToString();
|
||||
inputs[1].text = vec.y.ToString();
|
||||
inputs[2].text = vec.width.ToString();
|
||||
inputs[3].text = vec.height.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (type == typeof(Color))
|
||||
{
|
||||
return new StructInfo<Color>()
|
||||
{
|
||||
FieldNames = new[] { "r", "g", "b", "a" },
|
||||
SetValueMethod = (ref Color vec, int fieldIndex, float val) =>
|
||||
{
|
||||
switch (fieldIndex)
|
||||
{
|
||||
case 0: vec.r = val; break;
|
||||
case 1: vec.g = val; break;
|
||||
case 2: vec.b = val; break;
|
||||
case 3: vec.a = val; break;
|
||||
}
|
||||
},
|
||||
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||
{
|
||||
Color vec = (Color)value;
|
||||
inputs[0].text = vec.r.ToString();
|
||||
inputs[1].text = vec.g.ToString();
|
||||
inputs[2].text = vec.b.ToString();
|
||||
inputs[3].text = vec.a.ToString();
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class InteractiveUnityStruct : InteractiveValue
|
||||
{
|
||||
public static bool SupportsType(Type type) => s_supportedTypes.Contains(type);
|
||||
private static readonly HashSet<Type> s_supportedTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector4),
|
||||
typeof(Rect),
|
||||
typeof(Color) // todo might make a special editor for colors
|
||||
};
|
||||
|
||||
//~~~~~~~~~ Instance ~~~~~~~~~~
|
||||
|
||||
public InteractiveUnityStruct(object value, Type valueType) : base(value, valueType) { }
|
||||
|
||||
public override bool HasSubContent => true;
|
||||
public override bool SubContentWanted => true;
|
||||
public override bool WantInspectBtn => true;
|
||||
|
||||
public IStructInfo StructInfo;
|
||||
|
||||
public override void RefreshUIForValue()
|
||||
{
|
||||
InitializeStructInfo();
|
||||
|
||||
base.RefreshUIForValue();
|
||||
|
||||
if (m_subContentConstructed)
|
||||
StructInfo.RefreshUI(m_inputs, this.Value);
|
||||
}
|
||||
|
||||
internal override void OnToggleSubcontent(bool toggle)
|
||||
{
|
||||
InitializeStructInfo();
|
||||
|
||||
base.OnToggleSubcontent(toggle);
|
||||
|
||||
StructInfo.RefreshUI(m_inputs, this.Value);
|
||||
}
|
||||
|
||||
internal Type m_lastStructType;
|
||||
|
||||
internal void InitializeStructInfo()
|
||||
{
|
||||
var type = Value?.GetType() ?? FallbackType;
|
||||
|
||||
if (StructInfo != null && type == m_lastStructType)
|
||||
return;
|
||||
|
||||
if (StructInfo != null)
|
||||
{
|
||||
// changing types, destroy subcontent
|
||||
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
|
||||
{
|
||||
var child = m_subContentParent.transform.GetChild(i);
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
m_UIConstructed = false;
|
||||
}
|
||||
|
||||
m_lastStructType = type;
|
||||
|
||||
StructInfo = StructInfoFactory.Create(type);
|
||||
|
||||
if (m_subContentParent.activeSelf)
|
||||
{
|
||||
ConstructSubcontent();
|
||||
}
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal InputField[] m_inputs;
|
||||
|
||||
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
base.ConstructUI(parent, subGroup);
|
||||
}
|
||||
|
||||
public override void ConstructSubcontent()
|
||||
{
|
||||
base.ConstructSubcontent();
|
||||
|
||||
if (StructInfo == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Setting up subcontent but structinfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var editorContainer = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||
var editorGroup = editorContainer.GetComponent<VerticalLayoutGroup>();
|
||||
editorGroup.childForceExpandWidth = false;
|
||||
editorGroup.padding.top = 4;
|
||||
editorGroup.padding.right = 4;
|
||||
editorGroup.padding.left = 4;
|
||||
editorGroup.padding.bottom = 4;
|
||||
editorGroup.spacing = 2;
|
||||
|
||||
m_inputs = new InputField[StructInfo.FieldNames.Length];
|
||||
|
||||
for (int i = 0; i < StructInfo.FieldNames.Length; i++)
|
||||
{
|
||||
AddEditorRow(i, editorContainer);
|
||||
}
|
||||
|
||||
if (Owner.CanWrite)
|
||||
{
|
||||
var applyBtnObj = UIFactory.CreateButton(editorContainer, new Color(0.2f, 0.2f, 0.2f));
|
||||
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||
applyLayout.minWidth = 175;
|
||||
applyLayout.minHeight = 25;
|
||||
applyLayout.flexibleWidth = 0;
|
||||
var m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||
m_applyBtn.onClick.AddListener(OnSetValue);
|
||||
|
||||
void OnSetValue()
|
||||
{
|
||||
Owner.SetValue();
|
||||
RefreshUIForValue();
|
||||
}
|
||||
|
||||
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||
applyText.text = "Apply";
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddEditorRow(int index, GameObject groupObj)
|
||||
{
|
||||
var rowObj = UIFactory.CreateHorizontalGroup(groupObj, new Color(1, 1, 1, 0));
|
||||
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
rowGroup.childForceExpandHeight = true;
|
||||
rowGroup.childForceExpandWidth = false;
|
||||
rowGroup.spacing = 5;
|
||||
|
||||
var label = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleRight);
|
||||
var labelLayout = label.AddComponent<LayoutElement>();
|
||||
labelLayout.minWidth = 50;
|
||||
labelLayout.flexibleWidth = 0;
|
||||
labelLayout.minHeight = 25;
|
||||
var labelText = label.GetComponent<Text>();
|
||||
labelText.text = $"{StructInfo.FieldNames[index]}:";
|
||||
labelText.color = Color.cyan;
|
||||
|
||||
var inputFieldObj = UIFactory.CreateInputField(rowObj, 14, 3, 1);
|
||||
var inputField = inputFieldObj.GetComponent<InputField>();
|
||||
inputField.characterValidation = InputField.CharacterValidation.Decimal;
|
||||
var inputLayout = inputFieldObj.AddComponent<LayoutElement>();
|
||||
inputLayout.flexibleWidth = 0;
|
||||
inputLayout.minWidth = 120;
|
||||
inputLayout.minHeight = 25;
|
||||
|
||||
m_inputs[index] = inputField;
|
||||
|
||||
inputField.onValueChanged.AddListener((string val) => { Value = StructInfo.SetValue(ref this.Value, index, float.Parse(val)); });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
318
src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs
Normal file
318
src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs
Normal file
@ -0,0 +1,318 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class InteractiveValue
|
||||
{
|
||||
public static Type GetIValueForType(Type type)
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
return typeof(InteractiveBool);
|
||||
else if (type == typeof(string))
|
||||
return typeof(InteractiveString);
|
||||
else if (type.IsPrimitive)
|
||||
return typeof(InteractiveNumber);
|
||||
else if (typeof(Enum).IsAssignableFrom(type))
|
||||
{
|
||||
if (type.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
|
||||
return typeof(InteractiveFlags);
|
||||
else
|
||||
return typeof(InteractiveEnum);
|
||||
}
|
||||
else if (InteractiveUnityStruct.SupportsType(type))
|
||||
return typeof(InteractiveUnityStruct);
|
||||
else if (typeof(Transform).IsAssignableFrom(type))
|
||||
return typeof(InteractiveValue);
|
||||
else if (ReflectionHelpers.IsDictionary(type))
|
||||
return typeof(InteractiveDictionary);
|
||||
else if (ReflectionHelpers.IsEnumerable(type))
|
||||
return typeof(InteractiveEnumerable);
|
||||
else
|
||||
return typeof(InteractiveValue);
|
||||
}
|
||||
|
||||
public static InteractiveValue Create(object value, Type fallbackType)
|
||||
{
|
||||
var type = ReflectionHelpers.GetActualType(value) ?? fallbackType;
|
||||
var iType = GetIValueForType(type);
|
||||
|
||||
return (InteractiveValue)Activator.CreateInstance(iType, new object[] { value, type });
|
||||
}
|
||||
|
||||
// ~~~~~~~~~ Instance ~~~~~~~~~
|
||||
|
||||
public InteractiveValue(object value, Type valueType)
|
||||
{
|
||||
this.Value = value;
|
||||
this.FallbackType = valueType;
|
||||
}
|
||||
|
||||
public CacheObjectBase Owner;
|
||||
|
||||
public object Value;
|
||||
public readonly Type FallbackType;
|
||||
|
||||
public virtual bool HasSubContent => false;
|
||||
public virtual bool SubContentWanted => false;
|
||||
public virtual bool WantInspectBtn => true;
|
||||
|
||||
public string DefaultLabel => m_defaultLabel ?? GetDefaultLabel();
|
||||
internal string m_defaultLabel;
|
||||
internal string m_richValueType;
|
||||
|
||||
public bool m_UIConstructed;
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
if (this.m_valueContent)
|
||||
{
|
||||
m_valueContent.transform.SetParent(null, false);
|
||||
m_valueContent.SetActive(false);
|
||||
GameObject.Destroy(this.m_valueContent.gameObject);
|
||||
}
|
||||
if (this.m_subContentParent && SubContentWanted)
|
||||
{
|
||||
for (int i = 0; i < this.m_subContentParent.transform.childCount; i++)
|
||||
{
|
||||
var child = m_subContentParent.transform.GetChild(i);
|
||||
if (child)
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnValueUpdated()
|
||||
{
|
||||
if (!m_UIConstructed)
|
||||
ConstructUI(m_mainContentParent, m_subContentParent);
|
||||
|
||||
if (Owner is CacheMember ownerMember && !string.IsNullOrEmpty(ownerMember.ReflectionException))
|
||||
{
|
||||
OnException(ownerMember);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshUIForValue();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnException(CacheMember member)
|
||||
{
|
||||
m_baseLabel.text = "<color=red>" + member.ReflectionException + "</color>";
|
||||
Value = null;
|
||||
}
|
||||
|
||||
public virtual void RefreshUIForValue()
|
||||
{
|
||||
GetDefaultLabel();
|
||||
m_baseLabel.text = DefaultLabel;
|
||||
}
|
||||
|
||||
public void RefreshElementsAfterUpdate()
|
||||
{
|
||||
if (WantInspectBtn)
|
||||
{
|
||||
bool shouldShowInspect = !Value.IsNullOrDestroyed();
|
||||
|
||||
if (m_inspectButton.activeSelf != shouldShowInspect)
|
||||
m_inspectButton.SetActive(shouldShowInspect);
|
||||
}
|
||||
|
||||
bool subContentWanted = SubContentWanted;
|
||||
if (Owner is CacheMember cm && (!cm.HasEvaluated || !string.IsNullOrEmpty(cm.ReflectionException)))
|
||||
subContentWanted = false;
|
||||
|
||||
if (HasSubContent)
|
||||
{
|
||||
if (m_subExpandBtn.gameObject.activeSelf != subContentWanted)
|
||||
m_subExpandBtn.gameObject.SetActive(subContentWanted);
|
||||
|
||||
if (!subContentWanted && m_subContentParent.activeSelf)
|
||||
ToggleSubcontent();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ConstructSubcontent()
|
||||
{
|
||||
m_subContentConstructed = true;
|
||||
}
|
||||
|
||||
public void ToggleSubcontent()
|
||||
{
|
||||
if (!this.m_subContentParent.activeSelf)
|
||||
{
|
||||
this.m_subContentParent.SetActive(true);
|
||||
this.m_subContentParent.transform.SetAsLastSibling();
|
||||
m_subExpandBtn.GetComponentInChildren<Text>().text = "▼";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_subContentParent.SetActive(false);
|
||||
m_subExpandBtn.GetComponentInChildren<Text>().text = "▲";
|
||||
}
|
||||
|
||||
OnToggleSubcontent(m_subContentParent.activeSelf);
|
||||
|
||||
RefreshElementsAfterUpdate();
|
||||
}
|
||||
|
||||
internal virtual void OnToggleSubcontent(bool toggle)
|
||||
{
|
||||
if (!m_subContentConstructed)
|
||||
ConstructSubcontent();
|
||||
}
|
||||
|
||||
public string GetDefaultLabel(bool updateType = true)
|
||||
{
|
||||
var valueType = Value?.GetType() ?? this.FallbackType;
|
||||
if (updateType)
|
||||
m_richValueType = UISyntaxHighlight.ParseFullSyntax(valueType, true);
|
||||
|
||||
if (!Owner.HasEvaluated)
|
||||
return m_defaultLabel = $"<i><color=grey>Not yet evaluated</color> ({m_richValueType})</i>";
|
||||
|
||||
if (Value.IsNullOrDestroyed())
|
||||
return m_defaultLabel = $"<color=grey>null</color> ({m_richValueType})";
|
||||
|
||||
string label;
|
||||
|
||||
if (Value is TextAsset textAsset)
|
||||
{
|
||||
label = textAsset.text;
|
||||
|
||||
if (label.Length > 10)
|
||||
label = $"{label.Substring(0, 10)}...";
|
||||
|
||||
label = $"\"{label}\" {textAsset.name} ({m_richValueType})";
|
||||
}
|
||||
else if (Value is EventSystem)
|
||||
{
|
||||
label = m_richValueType;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toString = (string)valueType.GetMethod("ToString", new Type[0])?.Invoke(Value, null)
|
||||
?? Value.ToString();
|
||||
|
||||
var fullnametemp = valueType.ToString();
|
||||
if (fullnametemp.StartsWith("Il2CppSystem"))
|
||||
fullnametemp = fullnametemp.Substring(6, fullnametemp.Length - 6);
|
||||
|
||||
var temp = toString.Replace(fullnametemp, "").Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(temp))
|
||||
{
|
||||
label = m_richValueType;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toString.Length > 200)
|
||||
toString = toString.Substring(0, 200) + "...";
|
||||
|
||||
label = toString;
|
||||
|
||||
var unityType = $"({valueType.FullName})";
|
||||
if (Value is UnityEngine.Object && label.Contains(unityType))
|
||||
label = label.Replace(unityType, $"({m_richValueType})");
|
||||
else
|
||||
label += $" ({m_richValueType})";
|
||||
}
|
||||
}
|
||||
|
||||
return m_defaultLabel = label;
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal GameObject m_mainContentParent;
|
||||
internal GameObject m_subContentParent;
|
||||
|
||||
internal GameObject m_valueContent;
|
||||
internal GameObject m_inspectButton;
|
||||
internal Text m_baseLabel;
|
||||
|
||||
internal Button m_subExpandBtn;
|
||||
internal bool m_subContentConstructed;
|
||||
|
||||
public virtual void ConstructUI(GameObject parent, GameObject subGroup)
|
||||
{
|
||||
m_UIConstructed = true;
|
||||
|
||||
m_valueContent = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||
m_valueContent.name = "InteractiveValue.ValueContent";
|
||||
var mainRect = m_valueContent.GetComponent<RectTransform>();
|
||||
mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||
var mainGroup = m_valueContent.GetComponent<HorizontalLayoutGroup>();
|
||||
mainGroup.childForceExpandWidth = false;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = false;
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.spacing = 4;
|
||||
mainGroup.childAlignment = TextAnchor.UpperLeft;
|
||||
var mainLayout = m_valueContent.AddComponent<LayoutElement>();
|
||||
mainLayout.flexibleWidth = 9000;
|
||||
mainLayout.minWidth = 175;
|
||||
mainLayout.minHeight = 25;
|
||||
mainLayout.flexibleHeight = 0;
|
||||
|
||||
// subcontent expand button TODO
|
||||
if (HasSubContent)
|
||||
{
|
||||
var subBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.3f, 0.3f, 0.3f));
|
||||
var btnLayout = subBtnObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.minWidth = 25;
|
||||
btnLayout.flexibleWidth = 0;
|
||||
btnLayout.flexibleHeight = 0;
|
||||
var btnText = subBtnObj.GetComponentInChildren<Text>();
|
||||
btnText.text = "▲";
|
||||
m_subExpandBtn = subBtnObj.GetComponent<Button>();
|
||||
m_subExpandBtn.onClick.AddListener(() =>
|
||||
{
|
||||
ToggleSubcontent();
|
||||
});
|
||||
}
|
||||
|
||||
// inspect button
|
||||
|
||||
m_inspectButton = UIFactory.CreateButton(m_valueContent, new Color(0.3f, 0.3f, 0.3f, 0.2f));
|
||||
var inspectLayout = m_inspectButton.AddComponent<LayoutElement>();
|
||||
inspectLayout.minWidth = 60;
|
||||
inspectLayout.minHeight = 25;
|
||||
inspectLayout.flexibleHeight = 0;
|
||||
inspectLayout.flexibleWidth = 0;
|
||||
var inspectText = m_inspectButton.GetComponentInChildren<Text>();
|
||||
inspectText.text = "Inspect";
|
||||
var inspectBtn = m_inspectButton.GetComponent<Button>();
|
||||
|
||||
inspectBtn.onClick.AddListener(OnInspectClicked);
|
||||
void OnInspectClicked()
|
||||
{
|
||||
if (!Value.IsNullOrDestroyed(false))
|
||||
InspectorManager.Instance.Inspect(this.Value);
|
||||
}
|
||||
|
||||
m_inspectButton.SetActive(false);
|
||||
|
||||
// value label
|
||||
|
||||
var labelObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
|
||||
m_baseLabel = labelObj.GetComponent<Text>();
|
||||
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||
labelLayout.flexibleWidth = 9000;
|
||||
labelLayout.minHeight = 25;
|
||||
|
||||
m_subContentParent = subGroup;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
639
src/Inspectors/Reflection/ReflectionInspector.cs
Normal file
639
src/Inspectors/Reflection/ReflectionInspector.cs
Normal file
@ -0,0 +1,639 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Inspectors.Reflection;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.UI;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Config;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ReflectionInspector : InspectorBase
|
||||
{
|
||||
#region STATIC
|
||||
|
||||
public static ReflectionInspector ActiveInstance { get; private set; }
|
||||
|
||||
static ReflectionInspector()
|
||||
{
|
||||
PanelDragger.OnFinishResize += OnContainerResized;
|
||||
SceneExplorer.OnToggleShow += OnContainerResized;
|
||||
}
|
||||
|
||||
private static void OnContainerResized()
|
||||
{
|
||||
if (ActiveInstance == null)
|
||||
return;
|
||||
|
||||
ActiveInstance.m_widthUpdateWanted = true;
|
||||
}
|
||||
|
||||
// Blacklists
|
||||
private static readonly HashSet<string> s_typeAndMemberBlacklist = new HashSet<string>
|
||||
{
|
||||
#if CPP
|
||||
// these cause a crash in IL2CPP
|
||||
"Type.DeclaringMethod",
|
||||
"Rigidbody2D.Cast",
|
||||
"Collider2D.Cast",
|
||||
"Collider2D.Raycast",
|
||||
"Texture2D.SetPixelDataImpl",
|
||||
#endif
|
||||
};
|
||||
private static readonly HashSet<string> s_methodStartsWithBlacklist = new HashSet<string>
|
||||
{
|
||||
// these are redundant
|
||||
"get_",
|
||||
"set_",
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region INSTANCE
|
||||
|
||||
public override string TabLabel => m_targetTypeShortName;
|
||||
|
||||
internal readonly Type m_targetType;
|
||||
internal readonly string m_targetTypeShortName;
|
||||
|
||||
// all cached members of the target
|
||||
internal CacheMember[] m_allMembers;
|
||||
// filtered members based on current filters
|
||||
internal readonly List<CacheMember> m_membersFiltered = new List<CacheMember>();
|
||||
// actual shortlist of displayed members
|
||||
internal readonly CacheMember[] m_displayedMembers = new CacheMember[ModConfig.Instance.Default_Page_Limit];
|
||||
|
||||
internal bool m_autoUpdate;
|
||||
|
||||
// UI members
|
||||
|
||||
private GameObject m_content;
|
||||
public override GameObject Content
|
||||
{
|
||||
get => m_content;
|
||||
set => m_content = value;
|
||||
}
|
||||
|
||||
internal Text m_nameFilterText;
|
||||
internal MemberTypes m_memberFilter;
|
||||
internal Button m_lastActiveMemButton;
|
||||
|
||||
internal PageHandler m_pageHandler;
|
||||
internal SliderScrollbar m_sliderScroller;
|
||||
internal GameObject m_scrollContent;
|
||||
internal RectTransform m_scrollContentRect;
|
||||
|
||||
internal bool m_widthUpdateWanted;
|
||||
internal bool m_widthUpdateWaiting;
|
||||
|
||||
public ReflectionInspector(object target) : base(target)
|
||||
{
|
||||
if (this is StaticInspector)
|
||||
m_targetType = target as Type;
|
||||
else
|
||||
m_targetType = ReflectionHelpers.GetActualType(target);
|
||||
|
||||
m_targetTypeShortName = UISyntaxHighlight.ParseFullSyntax(m_targetType, false);
|
||||
|
||||
ConstructUI();
|
||||
|
||||
CacheMembers(m_targetType);
|
||||
|
||||
FilterMembers();
|
||||
}
|
||||
|
||||
public override void SetActive()
|
||||
{
|
||||
base.SetActive();
|
||||
ActiveInstance = this;
|
||||
}
|
||||
|
||||
public override void SetInactive()
|
||||
{
|
||||
base.SetInactive();
|
||||
ActiveInstance = null;
|
||||
}
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
|
||||
if (this.Content)
|
||||
GameObject.Destroy(this.Content);
|
||||
}
|
||||
|
||||
private void OnPageTurned()
|
||||
{
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
private void OnMemberFilterClicked(MemberTypes type, Button button)
|
||||
{
|
||||
if (m_lastActiveMemButton)
|
||||
{
|
||||
var lastColors = m_lastActiveMemButton.colors;
|
||||
lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||
m_lastActiveMemButton.colors = lastColors;
|
||||
}
|
||||
|
||||
m_memberFilter = type;
|
||||
m_lastActiveMemButton = button;
|
||||
|
||||
var colors = m_lastActiveMemButton.colors;
|
||||
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||
m_lastActiveMemButton.colors = colors;
|
||||
|
||||
FilterMembers(null, true);
|
||||
m_sliderScroller.m_slider.value = 1f;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
foreach (var member in m_displayedMembers)
|
||||
{
|
||||
if (member == null) break;
|
||||
member.UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_widthUpdateWanted)
|
||||
{
|
||||
if (!m_widthUpdateWaiting)
|
||||
m_widthUpdateWaiting = true;
|
||||
else
|
||||
{
|
||||
UpdateWidths();
|
||||
m_widthUpdateWaiting = false;
|
||||
m_widthUpdateWanted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void FilterMembers(string nameFilter = null, bool force = false)
|
||||
{
|
||||
int lastCount = m_membersFiltered.Count;
|
||||
m_membersFiltered.Clear();
|
||||
|
||||
nameFilter = nameFilter?.ToLower() ?? m_nameFilterText.text.ToLower();
|
||||
|
||||
foreach (var mem in m_allMembers)
|
||||
{
|
||||
// membertype filter
|
||||
if (m_memberFilter != MemberTypes.All && mem.MemInfo.MemberType != m_memberFilter)
|
||||
continue;
|
||||
|
||||
if (this is InstanceInspector ii && ii.m_scopeFilter != MemberScopes.All)
|
||||
{
|
||||
if (mem.IsStatic && ii.m_scopeFilter != MemberScopes.Static)
|
||||
continue;
|
||||
else if (!mem.IsStatic && ii.m_scopeFilter != MemberScopes.Instance)
|
||||
continue;
|
||||
}
|
||||
|
||||
// name filter
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !mem.NameForFiltering.Contains(nameFilter))
|
||||
continue;
|
||||
|
||||
m_membersFiltered.Add(mem);
|
||||
}
|
||||
|
||||
if (force || lastCount != m_membersFiltered.Count)
|
||||
RefreshDisplay();
|
||||
}
|
||||
|
||||
public void RefreshDisplay()
|
||||
{
|
||||
var members = m_membersFiltered;
|
||||
m_pageHandler.ListCount = members.Count;
|
||||
|
||||
// disable current members
|
||||
for (int i = 0; i < m_displayedMembers.Length; i++)
|
||||
{
|
||||
var mem = m_displayedMembers[i];
|
||||
if (mem != null)
|
||||
mem.Disable();
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (members.Count < 1)
|
||||
return;
|
||||
|
||||
foreach (var itemIndex in m_pageHandler)
|
||||
{
|
||||
if (itemIndex >= members.Count)
|
||||
break;
|
||||
|
||||
CacheMember member = members[itemIndex];
|
||||
m_displayedMembers[itemIndex - m_pageHandler.StartIndex] = member;
|
||||
member.Enable();
|
||||
}
|
||||
|
||||
m_widthUpdateWanted = true;
|
||||
}
|
||||
|
||||
internal void UpdateWidths()
|
||||
{
|
||||
float labelWidth = 125;
|
||||
|
||||
foreach (var cache in m_displayedMembers)
|
||||
{
|
||||
if (cache == null)
|
||||
break;
|
||||
|
||||
var width = cache.GetMemberLabelWidth(m_scrollContentRect);
|
||||
|
||||
if (width > labelWidth)
|
||||
labelWidth = width;
|
||||
}
|
||||
|
||||
float valueWidth = m_scrollContentRect.rect.width - labelWidth - 20;
|
||||
|
||||
foreach (var cache in m_displayedMembers)
|
||||
{
|
||||
if (cache == null)
|
||||
break;
|
||||
cache.SetWidths(labelWidth, valueWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public void CacheMembers(Type type)
|
||||
{
|
||||
var list = new List<CacheMember>();
|
||||
var cachedSigs = new HashSet<string>();
|
||||
|
||||
var types = ReflectionHelpers.GetAllBaseTypes(type);
|
||||
|
||||
foreach (var declaringType in types)
|
||||
{
|
||||
MemberInfo[] infos;
|
||||
try
|
||||
{
|
||||
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.Log($"Exception getting members for type: {declaringType.FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = Target;
|
||||
#if CPP
|
||||
try
|
||||
{
|
||||
target = target.Il2CppCast(declaringType);
|
||||
}
|
||||
catch //(Exception e)
|
||||
{
|
||||
//ExplorerCore.LogWarning("Excepting casting " + target.GetType().FullName + " to " + declaringType.FullName);
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var member in infos)
|
||||
{
|
||||
try
|
||||
{
|
||||
// make sure member type is Field, Method or Property (4 / 8 / 16)
|
||||
int m = (int)member.MemberType;
|
||||
if (m < 4 || m > 16)
|
||||
continue;
|
||||
|
||||
//ExplorerCore.Log($"Trying to cache member {sig}...");
|
||||
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
|
||||
|
||||
var pi = member as PropertyInfo;
|
||||
var mi = member as MethodInfo;
|
||||
|
||||
if (this is StaticInspector)
|
||||
{
|
||||
if (member is FieldInfo fi && !fi.IsStatic) continue;
|
||||
else if (pi != null && !pi.GetAccessors(true)[0].IsStatic) continue;
|
||||
else if (mi != null && !mi.IsStatic) continue;
|
||||
}
|
||||
|
||||
// check blacklisted members
|
||||
var sig = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
|
||||
if (s_typeAndMemberBlacklist.Any(it => sig.Contains(it)))
|
||||
continue;
|
||||
|
||||
if (s_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it)))
|
||||
continue;
|
||||
|
||||
if (mi != null)
|
||||
AppendParams(mi.GetParameters());
|
||||
else if (pi != null)
|
||||
AppendParams(pi.GetIndexParameters());
|
||||
|
||||
void AppendParams(ParameterInfo[] _args)
|
||||
{
|
||||
sig += " (";
|
||||
foreach (var param in _args)
|
||||
sig += $"{param.ParameterType.Name} {param.Name}, ";
|
||||
sig += ")";
|
||||
}
|
||||
|
||||
if (cachedSigs.Contains(sig))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
CacheMember cached;
|
||||
if (mi != null && CacheMember.CanProcessArgs(mi.GetParameters()))
|
||||
cached = new CacheMethod(mi, target);
|
||||
else if (pi != null && CacheMember.CanProcessArgs(pi.GetIndexParameters()))
|
||||
cached = new CacheProperty(pi, target);
|
||||
else if (member is FieldInfo fi)
|
||||
cached = new CacheField(fi, target);
|
||||
else
|
||||
continue;
|
||||
|
||||
cached.m_parentContent = m_scrollContent;
|
||||
|
||||
if (cached != null)
|
||||
{
|
||||
cachedSigs.Add(sig);
|
||||
list.Add(cached);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception caching member {sig}!");
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!");
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var typeList = types.ToList();
|
||||
|
||||
var sorted = new List<CacheMember>();
|
||||
sorted.AddRange(list.Where(it => it is CacheMethod)
|
||||
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||
.ThenBy(it => it.NameForFiltering));
|
||||
sorted.AddRange(list.Where(it => it is CacheProperty)
|
||||
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||
.ThenBy(it => it.NameForFiltering));
|
||||
sorted.AddRange(list.Where(it => it is CacheField)
|
||||
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||
.ThenBy(it => it.NameForFiltering));
|
||||
|
||||
m_allMembers = sorted.ToArray();
|
||||
|
||||
// ExplorerCore.Log("Cached " + m_allMembers.Length + " members");
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
internal void ConstructUI()
|
||||
{
|
||||
var parent = InspectorManager.Instance.m_inspectorContent;
|
||||
this.Content = UIFactory.CreateVerticalGroup(parent, new Color(0.15f, 0.15f, 0.15f));
|
||||
var mainGroup = Content.GetComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childForceExpandHeight = false;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.spacing = 5;
|
||||
mainGroup.padding.top = 4;
|
||||
mainGroup.padding.left = 4;
|
||||
mainGroup.padding.right = 4;
|
||||
mainGroup.padding.bottom = 4;
|
||||
|
||||
ConstructTopArea();
|
||||
|
||||
ConstructMemberList();
|
||||
}
|
||||
|
||||
internal void ConstructTopArea()
|
||||
{
|
||||
var nameRowObj = UIFactory.CreateHorizontalGroup(Content, new Color(1, 1, 1, 0));
|
||||
var nameRow = nameRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
nameRow.childForceExpandWidth = true;
|
||||
nameRow.childForceExpandHeight = true;
|
||||
nameRow.childControlHeight = true;
|
||||
nameRow.childControlWidth = true;
|
||||
nameRow.padding.top = 2;
|
||||
var nameRowLayout = nameRowObj.AddComponent<LayoutElement>();
|
||||
nameRowLayout.minHeight = 25;
|
||||
nameRowLayout.flexibleHeight = 0;
|
||||
nameRowLayout.minWidth = 200;
|
||||
nameRowLayout.flexibleWidth = 5000;
|
||||
|
||||
var typeLabel = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft);
|
||||
var typeLabelText = typeLabel.GetComponent<Text>();
|
||||
typeLabelText.text = "Type:";
|
||||
typeLabelText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
var typeLabelTextLayout = typeLabel.AddComponent<LayoutElement>();
|
||||
typeLabelTextLayout.minWidth = 40;
|
||||
typeLabelTextLayout.flexibleWidth = 0;
|
||||
typeLabelTextLayout.minHeight = 25;
|
||||
|
||||
var typeDisplayObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft);
|
||||
var typeDisplayText = typeDisplayObj.GetComponent<Text>();
|
||||
typeDisplayText.text = UISyntaxHighlight.ParseFullSyntax(m_targetType, true);
|
||||
var typeDisplayLayout = typeDisplayObj.AddComponent<LayoutElement>();
|
||||
typeDisplayLayout.minHeight = 25;
|
||||
typeDisplayLayout.flexibleWidth = 5000;
|
||||
|
||||
// Helper tools
|
||||
|
||||
if (this is InstanceInspector)
|
||||
{
|
||||
(this as InstanceInspector).ConstructInstanceHelpers();
|
||||
}
|
||||
|
||||
ConstructFilterArea();
|
||||
|
||||
ConstructOptionsArea();
|
||||
}
|
||||
|
||||
internal void ConstructFilterArea()
|
||||
{
|
||||
// Filters
|
||||
|
||||
var filterAreaObj = UIFactory.CreateVerticalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||
var filterLayout = filterAreaObj.AddComponent<LayoutElement>();
|
||||
filterLayout.minHeight = 60;
|
||||
var filterGroup = filterAreaObj.GetComponent<VerticalLayoutGroup>();
|
||||
filterGroup.childForceExpandWidth = true;
|
||||
filterGroup.childForceExpandHeight = true;
|
||||
filterGroup.childControlWidth = true;
|
||||
filterGroup.childControlHeight = true;
|
||||
filterGroup.spacing = 4;
|
||||
filterGroup.padding.left = 4;
|
||||
filterGroup.padding.right = 4;
|
||||
filterGroup.padding.top = 4;
|
||||
filterGroup.padding.bottom = 4;
|
||||
|
||||
// name filter
|
||||
|
||||
var nameFilterRowObj = UIFactory.CreateHorizontalGroup(filterAreaObj, new Color(1, 1, 1, 0));
|
||||
var nameFilterGroup = nameFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
nameFilterGroup.childForceExpandHeight = false;
|
||||
nameFilterGroup.childForceExpandWidth = false;
|
||||
nameFilterGroup.childControlWidth = true;
|
||||
nameFilterGroup.childControlHeight = true;
|
||||
nameFilterGroup.spacing = 5;
|
||||
var nameFilterLayout = nameFilterRowObj.AddComponent<LayoutElement>();
|
||||
nameFilterLayout.minHeight = 25;
|
||||
nameFilterLayout.flexibleHeight = 0;
|
||||
nameFilterLayout.flexibleWidth = 5000;
|
||||
|
||||
var nameLabelObj = UIFactory.CreateLabel(nameFilterRowObj, TextAnchor.MiddleLeft);
|
||||
var nameLabelLayout = nameLabelObj.AddComponent<LayoutElement>();
|
||||
nameLabelLayout.minWidth = 100;
|
||||
nameLabelLayout.minHeight = 25;
|
||||
nameLabelLayout.flexibleWidth = 0;
|
||||
var nameLabelText = nameLabelObj.GetComponent<Text>();
|
||||
nameLabelText.text = "Filter names:";
|
||||
nameLabelText.color = Color.grey;
|
||||
|
||||
var nameInputObj = UIFactory.CreateInputField(nameFilterRowObj, 14, (int)TextAnchor.MiddleLeft, (int)HorizontalWrapMode.Overflow);
|
||||
var nameInputLayout = nameInputObj.AddComponent<LayoutElement>();
|
||||
nameInputLayout.flexibleWidth = 5000;
|
||||
nameInputLayout.minWidth = 100;
|
||||
nameInputLayout.minHeight = 25;
|
||||
var nameInput = nameInputObj.GetComponent<InputField>();
|
||||
nameInput.onValueChanged.AddListener((string val) => { FilterMembers(val); });
|
||||
m_nameFilterText = nameInput.textComponent;
|
||||
|
||||
// membertype filter
|
||||
|
||||
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(filterAreaObj, new Color(1, 1, 1, 0));
|
||||
var memFilterGroup = memberFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
memFilterGroup.childForceExpandHeight = false;
|
||||
memFilterGroup.childForceExpandWidth = false;
|
||||
memFilterGroup.childControlWidth = true;
|
||||
memFilterGroup.childControlHeight = true;
|
||||
memFilterGroup.spacing = 5;
|
||||
var memFilterLayout = memberFilterRowObj.AddComponent<LayoutElement>();
|
||||
memFilterLayout.minHeight = 25;
|
||||
memFilterLayout.flexibleHeight = 0;
|
||||
memFilterLayout.flexibleWidth = 5000;
|
||||
|
||||
var memLabelObj = UIFactory.CreateLabel(memberFilterRowObj, TextAnchor.MiddleLeft);
|
||||
var memLabelLayout = memLabelObj.AddComponent<LayoutElement>();
|
||||
memLabelLayout.minWidth = 100;
|
||||
memLabelLayout.minHeight = 25;
|
||||
memLabelLayout.flexibleWidth = 0;
|
||||
var memLabelText = memLabelObj.GetComponent<Text>();
|
||||
memLabelText.text = "Filter members:";
|
||||
memLabelText.color = Color.grey;
|
||||
|
||||
AddFilterButton(memberFilterRowObj, MemberTypes.All);
|
||||
AddFilterButton(memberFilterRowObj, MemberTypes.Method);
|
||||
AddFilterButton(memberFilterRowObj, MemberTypes.Property, true);
|
||||
AddFilterButton(memberFilterRowObj, MemberTypes.Field);
|
||||
|
||||
// Instance filters
|
||||
|
||||
if (this is InstanceInspector)
|
||||
{
|
||||
(this as InstanceInspector).ConstructInstanceFilters(filterAreaObj);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFilterButton(GameObject parent, MemberTypes type, bool setEnabled = false)
|
||||
{
|
||||
var btnObj = UIFactory.CreateButton(parent, new Color(0.2f, 0.2f, 0.2f));
|
||||
|
||||
var btnLayout = btnObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.minWidth = 70;
|
||||
|
||||
var text = btnObj.GetComponentInChildren<Text>();
|
||||
text.text = type.ToString();
|
||||
|
||||
var btn = btnObj.GetComponent<Button>();
|
||||
|
||||
btn.onClick.AddListener(() => { OnMemberFilterClicked(type, btn); });
|
||||
|
||||
var colors = btn.colors;
|
||||
colors.highlightedColor = new Color(0.3f, 0.7f, 0.3f);
|
||||
|
||||
if (setEnabled)
|
||||
{
|
||||
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||
m_memberFilter = type;
|
||||
m_lastActiveMemButton = btn;
|
||||
}
|
||||
|
||||
btn.colors = colors;
|
||||
}
|
||||
|
||||
internal void ConstructOptionsArea()
|
||||
{
|
||||
var optionsRowObj = UIFactory.CreateHorizontalGroup(Content, new Color(1,1,1,0));
|
||||
var optionsLayout = optionsRowObj.AddComponent<LayoutElement>();
|
||||
optionsLayout.minHeight = 25;
|
||||
var optionsGroup = optionsRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||
optionsGroup.childForceExpandHeight = true;
|
||||
optionsGroup.childForceExpandWidth = false;
|
||||
optionsGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||
optionsGroup.spacing = 10;
|
||||
|
||||
// update button
|
||||
|
||||
var updateButtonObj = UIFactory.CreateButton(optionsRowObj, new Color(0.2f, 0.2f, 0.2f));
|
||||
var updateBtnLayout = updateButtonObj.AddComponent<LayoutElement>();
|
||||
updateBtnLayout.minWidth = 110;
|
||||
updateBtnLayout.flexibleWidth = 0;
|
||||
var updateText = updateButtonObj.GetComponentInChildren<Text>();
|
||||
updateText.text = "Update Values";
|
||||
var updateBtn = updateButtonObj.GetComponent<Button>();
|
||||
updateBtn.onClick.AddListener(() =>
|
||||
{
|
||||
bool orig = m_autoUpdate;
|
||||
m_autoUpdate = true;
|
||||
Update();
|
||||
if (!orig) m_autoUpdate = orig;
|
||||
});
|
||||
|
||||
// auto update
|
||||
|
||||
var autoUpdateObj = UIFactory.CreateToggle(optionsRowObj, out Toggle autoUpdateToggle, out Text autoUpdateText);
|
||||
var autoUpdateLayout = autoUpdateObj.AddComponent<LayoutElement>();
|
||||
autoUpdateLayout.minWidth = 150;
|
||||
autoUpdateLayout.minHeight = 25;
|
||||
autoUpdateText.text = "Auto-update?";
|
||||
autoUpdateToggle.isOn = false;
|
||||
autoUpdateToggle.onValueChanged.AddListener((bool val) => { m_autoUpdate = val; });
|
||||
}
|
||||
|
||||
internal void ConstructMemberList()
|
||||
{
|
||||
var scrollobj = UIFactory.CreateScrollView(Content, out m_scrollContent, out m_sliderScroller, new Color(0.08f, 0.08f, 0.08f));
|
||||
|
||||
m_scrollContentRect = m_scrollContent.GetComponent<RectTransform>();
|
||||
|
||||
var scrollGroup = m_scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||
scrollGroup.spacing = 3;
|
||||
scrollGroup.padding.left = 0;
|
||||
scrollGroup.padding.right = 0;
|
||||
scrollGroup.childForceExpandHeight = true;
|
||||
|
||||
m_pageHandler = new PageHandler(m_sliderScroller);
|
||||
m_pageHandler.ConstructUI(Content);
|
||||
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||
}
|
||||
|
||||
#endregion // end UI
|
||||
|
||||
#endregion // end instance
|
||||
}
|
||||
}
|
11
src/Inspectors/Reflection/StaticInspector.cs
Normal file
11
src/Inspectors/Reflection/StaticInspector.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace UnityExplorer.Inspectors.Reflection
|
||||
{
|
||||
public class StaticInspector : ReflectionInspector
|
||||
{
|
||||
public override string TabLabel => $" <color=cyan>[S]</color> {base.TabLabel}";
|
||||
|
||||
public StaticInspector(Type type) : base(type) { }
|
||||
}
|
||||
}
|
559
src/Inspectors/SceneExplorer.cs
Normal file
559
src/Inspectors/SceneExplorer.cs
Normal file
@ -0,0 +1,559 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Unstrip;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class SceneExplorer
|
||||
{
|
||||
public static SceneExplorer Instance;
|
||||
|
||||
internal static Action OnToggleShow;
|
||||
|
||||
public SceneExplorer()
|
||||
{
|
||||
Instance = this;
|
||||
ConstructScenePane();
|
||||
}
|
||||
|
||||
private static bool Hiding;
|
||||
|
||||
private const float UPDATE_INTERVAL = 1f;
|
||||
private float m_timeOfLastSceneUpdate;
|
||||
|
||||
// private int m_currentSceneHandle = -1;
|
||||
public static Scene DontDestroyScene => DontDestroyObject.scene;
|
||||
internal Scene m_currentScene;
|
||||
internal Scene[] m_currentScenes = new Scene[0];
|
||||
|
||||
private GameObject m_selectedSceneObject;
|
||||
private int m_lastCount;
|
||||
|
||||
private Dropdown m_sceneDropdown;
|
||||
private Text m_sceneDropdownText;
|
||||
private Text m_scenePathText;
|
||||
private GameObject m_mainInspectBtn;
|
||||
private GameObject m_backButtonObj;
|
||||
|
||||
public PageHandler m_pageHandler;
|
||||
private GameObject m_pageContent;
|
||||
private GameObject[] m_allObjects = new GameObject[0];
|
||||
private readonly List<GameObject> m_shortList = new List<GameObject>();
|
||||
private readonly List<Text> m_shortListTexts = new List<Text>();
|
||||
private readonly List<Toggle> m_shortListToggles = new List<Toggle>();
|
||||
|
||||
|
||||
internal static GameObject DontDestroyObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_dontDestroyObject)
|
||||
{
|
||||
m_dontDestroyObject = new GameObject("DontDestroyMe");
|
||||
GameObject.DontDestroyOnLoad(m_dontDestroyObject);
|
||||
}
|
||||
return m_dontDestroyObject;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GameObject m_dontDestroyObject;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
RefreshSceneSelector();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Hiding || Time.realtimeSinceStartup - m_timeOfLastSceneUpdate < UPDATE_INTERVAL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshSceneSelector();
|
||||
|
||||
if (!m_selectedSceneObject)
|
||||
{
|
||||
if (m_currentScene != default)
|
||||
{
|
||||
#if CPP
|
||||
SetSceneObjectList(SceneUnstrip.GetRootGameObjects(m_currentScene.handle));
|
||||
#else
|
||||
SetSceneObjectList(m_currentScene.GetRootGameObjects());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshSelectedSceneObject();
|
||||
}
|
||||
}
|
||||
|
||||
//#if CPP
|
||||
// public int GetSceneHandle(string sceneName)
|
||||
// {
|
||||
// if (sceneName == "DontDestroyOnLoad")
|
||||
// return DontDestroyScene;
|
||||
|
||||
// for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
// {
|
||||
// var scene = SceneManager.GetSceneAt(i);
|
||||
// if (scene.name == sceneName)
|
||||
// return scene.handle;
|
||||
// }
|
||||
// return -1;
|
||||
// }
|
||||
//#endif
|
||||
|
||||
internal void OnSceneChange()
|
||||
{
|
||||
m_sceneDropdown.OnCancel(null);
|
||||
RefreshSceneSelector();
|
||||
}
|
||||
|
||||
private void RefreshSceneSelector()
|
||||
{
|
||||
var names = new List<string>();
|
||||
var scenes = new List<Scene>();
|
||||
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
Scene scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
if (scene == default)
|
||||
continue;
|
||||
|
||||
scenes.Add(scene);
|
||||
names.Add(scene.name);
|
||||
}
|
||||
|
||||
names.Add("DontDestroyOnLoad");
|
||||
scenes.Add(DontDestroyScene);
|
||||
|
||||
m_sceneDropdown.options.Clear();
|
||||
|
||||
foreach (string scene in names)
|
||||
{
|
||||
m_sceneDropdown.options.Add(new Dropdown.OptionData { text = scene });
|
||||
}
|
||||
|
||||
if (!names.Contains(m_sceneDropdownText.text))
|
||||
{
|
||||
m_sceneDropdownText.text = names[0];
|
||||
SetTargetScene(scenes[0]);
|
||||
}
|
||||
|
||||
m_currentScenes = scenes.ToArray();
|
||||
}
|
||||
|
||||
//#if CPP
|
||||
// public void SetTargetScene(string name) => SetTargetScene(scene.handle);
|
||||
//#endif
|
||||
|
||||
public void SetTargetScene(Scene scene)
|
||||
{
|
||||
if (scene == default)
|
||||
return;
|
||||
|
||||
m_currentScene = scene;
|
||||
#if CPP
|
||||
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene.handle);
|
||||
#else
|
||||
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene);
|
||||
#endif
|
||||
SetSceneObjectList(rootObjs);
|
||||
|
||||
m_selectedSceneObject = null;
|
||||
|
||||
if (m_backButtonObj.activeSelf)
|
||||
{
|
||||
m_backButtonObj.SetActive(false);
|
||||
m_mainInspectBtn.SetActive(false);
|
||||
}
|
||||
|
||||
m_scenePathText.text = "Scene root:";
|
||||
//m_scenePathText.ForceMeshUpdate();
|
||||
}
|
||||
|
||||
public void SetTargetObject(GameObject obj)
|
||||
{
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
m_scenePathText.text = obj.name;
|
||||
//m_scenePathText.ForceMeshUpdate();
|
||||
|
||||
m_selectedSceneObject = obj;
|
||||
|
||||
RefreshSelectedSceneObject();
|
||||
|
||||
if (!m_backButtonObj.activeSelf)
|
||||
{
|
||||
m_backButtonObj.SetActive(true);
|
||||
m_mainInspectBtn.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSelectedSceneObject()
|
||||
{
|
||||
GameObject[] list = new GameObject[m_selectedSceneObject.transform.childCount];
|
||||
for (int i = 0; i < m_selectedSceneObject.transform.childCount; i++)
|
||||
{
|
||||
list[i] = m_selectedSceneObject.transform.GetChild(i).gameObject;
|
||||
}
|
||||
|
||||
SetSceneObjectList(list);
|
||||
}
|
||||
|
||||
private void SetSceneObjectList(GameObject[] objects)
|
||||
{
|
||||
m_allObjects = objects;
|
||||
RefreshSceneObjectList();
|
||||
}
|
||||
|
||||
private void SceneListObjectClicked(int index)
|
||||
{
|
||||
if (index >= m_shortList.Count || !m_shortList[index])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = m_shortList[index];
|
||||
if (obj.transform.childCount > 0)
|
||||
SetTargetObject(obj);
|
||||
else
|
||||
InspectorManager.Instance.Inspect(obj);
|
||||
}
|
||||
|
||||
private void OnSceneListPageTurn()
|
||||
{
|
||||
RefreshSceneObjectList();
|
||||
}
|
||||
|
||||
private void OnToggleClicked(int index, bool val)
|
||||
{
|
||||
if (index >= m_shortList.Count || !m_shortList[index])
|
||||
return;
|
||||
|
||||
var obj = m_shortList[index];
|
||||
obj.SetActive(val);
|
||||
}
|
||||
|
||||
private void RefreshSceneObjectList()
|
||||
{
|
||||
m_timeOfLastSceneUpdate = Time.realtimeSinceStartup;
|
||||
|
||||
var objects = m_allObjects;
|
||||
m_pageHandler.ListCount = objects.Length;
|
||||
|
||||
//int startIndex = m_sceneListPageHandler.StartIndex;
|
||||
|
||||
int newCount = 0;
|
||||
|
||||
foreach (var itemIndex in m_pageHandler)
|
||||
{
|
||||
newCount++;
|
||||
|
||||
// normalized index starting from 0
|
||||
var i = itemIndex - m_pageHandler.StartIndex;
|
||||
|
||||
if (itemIndex >= objects.Length)
|
||||
{
|
||||
if (i > m_lastCount || i >= m_shortListTexts.Count)
|
||||
break;
|
||||
|
||||
GameObject label = m_shortListTexts[i].transform.parent.parent.gameObject;
|
||||
if (label.activeSelf)
|
||||
label.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject obj = objects[itemIndex];
|
||||
|
||||
if (!obj)
|
||||
continue;
|
||||
|
||||
if (i >= m_shortList.Count)
|
||||
{
|
||||
m_shortList.Add(obj);
|
||||
AddObjectListButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_shortList[i] = obj;
|
||||
}
|
||||
|
||||
var text = m_shortListTexts[i];
|
||||
|
||||
var name = obj.name;
|
||||
|
||||
if (obj.transform.childCount > 0)
|
||||
name = $"<color=grey>[{obj.transform.childCount}]</color> {name}";
|
||||
|
||||
text.text = name;
|
||||
text.color = obj.activeSelf ? Color.green : Color.red;
|
||||
|
||||
var tog = m_shortListToggles[i];
|
||||
tog.isOn = obj.activeSelf;
|
||||
|
||||
var label = text.transform.parent.parent.gameObject;
|
||||
if (!label.activeSelf)
|
||||
{
|
||||
label.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_lastCount = newCount;
|
||||
}
|
||||
|
||||
#region UI CONSTRUCTION
|
||||
|
||||
public void ConstructScenePane()
|
||||
{
|
||||
GameObject leftPane = UIFactory.CreateVerticalGroup(HomePage.Instance.Content, new Color(72f / 255f, 72f / 255f, 72f / 255f));
|
||||
LayoutElement leftLayout = leftPane.AddComponent<LayoutElement>();
|
||||
leftLayout.minWidth = 350;
|
||||
leftLayout.flexibleWidth = 0;
|
||||
|
||||
VerticalLayoutGroup leftGroup = leftPane.GetComponent<VerticalLayoutGroup>();
|
||||
leftGroup.padding.left = 4;
|
||||
leftGroup.padding.right = 4;
|
||||
leftGroup.padding.top = 8;
|
||||
leftGroup.padding.bottom = 4;
|
||||
leftGroup.spacing = 4;
|
||||
leftGroup.childControlWidth = true;
|
||||
leftGroup.childControlHeight = true;
|
||||
leftGroup.childForceExpandWidth = true;
|
||||
leftGroup.childForceExpandHeight = true;
|
||||
|
||||
GameObject titleObj = UIFactory.CreateLabel(leftPane, TextAnchor.UpperLeft);
|
||||
Text titleLabel = titleObj.GetComponent<Text>();
|
||||
titleLabel.text = "Scene Explorer";
|
||||
titleLabel.fontSize = 20;
|
||||
LayoutElement titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||
titleLayout.minHeight = 30;
|
||||
titleLayout.flexibleHeight = 0;
|
||||
|
||||
GameObject sceneDropdownObj = UIFactory.CreateDropdown(leftPane, out m_sceneDropdown);
|
||||
LayoutElement dropdownLayout = sceneDropdownObj.AddComponent<LayoutElement>();
|
||||
dropdownLayout.minHeight = 40;
|
||||
dropdownLayout.flexibleHeight = 0;
|
||||
dropdownLayout.minWidth = 320;
|
||||
dropdownLayout.flexibleWidth = 2;
|
||||
|
||||
m_sceneDropdownText = m_sceneDropdown.transform.Find("Label").GetComponent<Text>();
|
||||
m_sceneDropdown.onValueChanged.AddListener((int val) => { SetSceneFromDropdown(val); });
|
||||
|
||||
void SetSceneFromDropdown(int val)
|
||||
{
|
||||
//string scene = m_sceneDropdown.options[val].text;
|
||||
SetTargetScene(m_currentScenes[val]);
|
||||
}
|
||||
|
||||
GameObject scenePathGroupObj = UIFactory.CreateHorizontalGroup(leftPane, new Color(1, 1, 1, 0f));
|
||||
HorizontalLayoutGroup scenePathGroup = scenePathGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
scenePathGroup.childControlHeight = true;
|
||||
scenePathGroup.childControlWidth = true;
|
||||
scenePathGroup.childForceExpandHeight = true;
|
||||
scenePathGroup.childForceExpandWidth = true;
|
||||
scenePathGroup.spacing = 5;
|
||||
LayoutElement scenePathLayout = scenePathGroupObj.AddComponent<LayoutElement>();
|
||||
scenePathLayout.minHeight = 20;
|
||||
scenePathLayout.minWidth = 335;
|
||||
scenePathLayout.flexibleWidth = 0;
|
||||
|
||||
m_backButtonObj = UIFactory.CreateButton(scenePathGroupObj);
|
||||
Text backButtonText = m_backButtonObj.GetComponentInChildren<Text>();
|
||||
backButtonText.text = "◄";
|
||||
LayoutElement backButtonLayout = m_backButtonObj.AddComponent<LayoutElement>();
|
||||
backButtonLayout.minWidth = 40;
|
||||
backButtonLayout.flexibleWidth = 0;
|
||||
Button backButton = m_backButtonObj.GetComponent<Button>();
|
||||
var colors = backButton.colors;
|
||||
colors.normalColor = new Color(0.12f, 0.12f, 0.12f);
|
||||
backButton.colors = colors;
|
||||
|
||||
backButton.onClick.AddListener(() => { SetSceneObjectParent(); });
|
||||
|
||||
void SetSceneObjectParent()
|
||||
{
|
||||
if (!m_selectedSceneObject || !m_selectedSceneObject.transform.parent?.gameObject)
|
||||
{
|
||||
m_selectedSceneObject = null;
|
||||
SetTargetScene(m_currentScene);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTargetObject(m_selectedSceneObject.transform.parent.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
GameObject scenePathLabel = UIFactory.CreateHorizontalGroup(scenePathGroupObj);
|
||||
Image image = scenePathLabel.GetComponent<Image>();
|
||||
image.color = Color.white;
|
||||
|
||||
LayoutElement scenePathLabelLayout = scenePathLabel.AddComponent<LayoutElement>();
|
||||
scenePathLabelLayout.minWidth = 210;
|
||||
scenePathLabelLayout.minHeight = 20;
|
||||
scenePathLabelLayout.flexibleHeight = 0;
|
||||
scenePathLabelLayout.flexibleWidth = 120;
|
||||
|
||||
scenePathLabel.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject scenePathLabelText = UIFactory.CreateLabel(scenePathLabel, TextAnchor.MiddleLeft);
|
||||
m_scenePathText = scenePathLabelText.GetComponent<Text>();
|
||||
m_scenePathText.text = "Scene root:";
|
||||
m_scenePathText.fontSize = 15;
|
||||
m_scenePathText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
LayoutElement textLayout = scenePathLabelText.gameObject.AddComponent<LayoutElement>();
|
||||
textLayout.minWidth = 210;
|
||||
textLayout.flexibleWidth = 120;
|
||||
textLayout.minHeight = 20;
|
||||
textLayout.flexibleHeight = 0;
|
||||
|
||||
m_mainInspectBtn = UIFactory.CreateButton(scenePathGroupObj);
|
||||
Text inspectButtonText = m_mainInspectBtn.GetComponentInChildren<Text>();
|
||||
inspectButtonText.text = "Inspect";
|
||||
LayoutElement inspectButtonLayout = m_mainInspectBtn.AddComponent<LayoutElement>();
|
||||
inspectButtonLayout.minWidth = 65;
|
||||
inspectButtonLayout.flexibleWidth = 0;
|
||||
Button inspectButton = m_mainInspectBtn.GetComponent<Button>();
|
||||
colors = inspectButton.colors;
|
||||
colors.normalColor = new Color(0.12f, 0.12f, 0.12f);
|
||||
inspectButton.colors = colors;
|
||||
|
||||
inspectButton.onClick.AddListener(() => { InspectorManager.Instance.Inspect(m_selectedSceneObject); });
|
||||
|
||||
GameObject scrollObj = UIFactory.CreateScrollView(leftPane, out m_pageContent, out SliderScrollbar scroller, new Color(0.1f, 0.1f, 0.1f));
|
||||
|
||||
m_pageHandler = new PageHandler(scroller);
|
||||
m_pageHandler.ConstructUI(leftPane);
|
||||
m_pageHandler.OnPageChanged += OnSceneListPageTurn;
|
||||
|
||||
// hide button
|
||||
|
||||
var hideButtonObj = UIFactory.CreateButton(leftPane);
|
||||
var hideBtn = hideButtonObj.GetComponent<Button>();
|
||||
|
||||
var hideColors = hideBtn.colors;
|
||||
hideColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||
hideBtn.colors = hideColors;
|
||||
var hideText = hideButtonObj.GetComponentInChildren<Text>();
|
||||
hideText.text = "Hide Scene Explorer";
|
||||
hideText.fontSize = 13;
|
||||
var hideLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||
hideLayout.minWidth = 20;
|
||||
hideLayout.minHeight = 20;
|
||||
|
||||
hideBtn.onClick.AddListener(OnHide);
|
||||
|
||||
void OnHide()
|
||||
{
|
||||
if (!Hiding)
|
||||
{
|
||||
Hiding = true;
|
||||
|
||||
hideText.text = "►";
|
||||
titleObj.SetActive(false);
|
||||
sceneDropdownObj.SetActive(false);
|
||||
scenePathGroupObj.SetActive(false);
|
||||
scrollObj.SetActive(false);
|
||||
m_pageHandler.Hide();
|
||||
|
||||
leftLayout.minWidth = 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hiding = false;
|
||||
|
||||
hideText.text = "Hide Scene Explorer";
|
||||
titleObj.SetActive(true);
|
||||
sceneDropdownObj.SetActive(true);
|
||||
scenePathGroupObj.SetActive(true);
|
||||
scrollObj.SetActive(true);
|
||||
|
||||
leftLayout.minWidth = 350;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
OnToggleShow?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddObjectListButton()
|
||||
{
|
||||
int thisIndex = m_shortListTexts.Count();
|
||||
|
||||
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(m_pageContent, new Color(0.1f, 0.1f, 0.1f));
|
||||
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||
btnGroup.childForceExpandWidth = true;
|
||||
btnGroup.childControlWidth = true;
|
||||
btnGroup.childForceExpandHeight = false;
|
||||
btnGroup.childControlHeight = true;
|
||||
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||
btnLayout.flexibleWidth = 320;
|
||||
btnLayout.minHeight = 25;
|
||||
btnLayout.flexibleHeight = 0;
|
||||
btnGroupObj.AddComponent<Mask>();
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.1f, 0.1f, 0.1f));
|
||||
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||
toggleLayout.minHeight = 25;
|
||||
toggleLayout.minWidth = 25;
|
||||
toggleText.text = "";
|
||||
toggle.isOn = false;
|
||||
m_shortListToggles.Add(toggle);
|
||||
toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); });
|
||||
|
||||
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||
mainBtnLayout.minHeight = 25;
|
||||
mainBtnLayout.flexibleHeight = 0;
|
||||
mainBtnLayout.minWidth = 230;
|
||||
mainBtnLayout.flexibleWidth = 0;
|
||||
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||
ColorBlock mainColors = mainBtn.colors;
|
||||
mainColors.normalColor = new Color(0.1f, 0.1f, 0.1f);
|
||||
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||
mainBtn.colors = mainColors;
|
||||
|
||||
mainBtn.onClick.AddListener(() => { SceneListObjectClicked(thisIndex); });
|
||||
|
||||
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||
mainText.alignment = TextAnchor.MiddleLeft;
|
||||
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
m_shortListTexts.Add(mainText);
|
||||
|
||||
GameObject inspectBtnObj = UIFactory.CreateButton(btnGroupObj);
|
||||
LayoutElement inspectBtnLayout = inspectBtnObj.AddComponent<LayoutElement>();
|
||||
inspectBtnLayout.minWidth = 60;
|
||||
inspectBtnLayout.flexibleWidth = 0;
|
||||
inspectBtnLayout.minHeight = 25;
|
||||
inspectBtnLayout.flexibleHeight = 0;
|
||||
Text inspectText = inspectBtnObj.GetComponentInChildren<Text>();
|
||||
inspectText.text = "Inspect";
|
||||
inspectText.color = Color.white;
|
||||
|
||||
Button inspectBtn = inspectBtnObj.GetComponent<Button>();
|
||||
ColorBlock inspectColors = inspectBtn.colors;
|
||||
inspectColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 0.5f);
|
||||
inspectBtn.colors = inspectColors;
|
||||
|
||||
inspectBtn.onClick.AddListener(() => { InspectorManager.Instance.Inspect(m_shortList[thisIndex]); });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Explorer;
|
||||
using UnityExplorer;
|
||||
|
||||
#if ML
|
||||
using MelonLoader;
|
||||
|
||||
[assembly: MelonInfo(typeof(ExplorerMelonMod), "Explorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||
[assembly: MelonInfo(typeof(ExplorerMelonMod), "UnityExplorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||
[assembly: MelonGame(null, null)]
|
||||
#endif
|
||||
|
||||
@ -40,5 +39,5 @@ using MelonLoader;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyVersion(ExplorerCore.VERSION)]
|
||||
[assembly: AssemblyFileVersion(ExplorerCore.VERSION)]
|
||||
|
@ -1,17 +1,12 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using UnityExplorer.UI;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Explorer.UI.Shared;
|
||||
using System;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Explorer.Unstrip.ImageConversion;
|
||||
#endif
|
||||
|
||||
namespace Explorer.Tests
|
||||
namespace UnityExplorer.Tests
|
||||
{
|
||||
public static class StaticTestClass
|
||||
{
|
||||
@ -29,60 +24,143 @@ namespace Explorer.Tests
|
||||
|
||||
public class TestClass
|
||||
{
|
||||
public Vector2 AATestVector2 = new Vector2(1, 2);
|
||||
public Vector3 AATestVector3 = new Vector3(1, 2, 3);
|
||||
public Vector4 AATestVector4 = new Vector4(1, 2, 3, 4);
|
||||
public Rect AATestRect = new Rect(1, 2, 3, 4);
|
||||
public Color AATestColor = new Color(0.1f, 0.2f, 0.3f, 0.4f);
|
||||
|
||||
public bool ATestBoolMethod() => false;
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
get => index % 2 == 0;
|
||||
set => m_thisBool = value;
|
||||
}
|
||||
internal bool m_thisBool;
|
||||
|
||||
static int testInt;
|
||||
public static List<string> ExceptionList
|
||||
{
|
||||
get
|
||||
{
|
||||
testInt++;
|
||||
if (testInt % 2 == 0)
|
||||
throw new Exception("its even");
|
||||
else
|
||||
return new List<string> { "one" };
|
||||
}
|
||||
}
|
||||
|
||||
static bool abool;
|
||||
public static bool ATestExceptionBool
|
||||
{
|
||||
get
|
||||
{
|
||||
abool = !abool;
|
||||
if (!abool)
|
||||
throw new Exception("false");
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExceptionString => throw new NotImplementedException();
|
||||
|
||||
public static string ANullString = null;
|
||||
public static float ATestFloat = 420.69f;
|
||||
public static int ATestInt = -1;
|
||||
public static string ATestString = "hello world";
|
||||
public static uint ATestUInt = 1u;
|
||||
public static byte ATestByte = 255;
|
||||
public static ulong AReadonlyUlong = 82934UL;
|
||||
|
||||
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
|
||||
private static TestClass m_instance;
|
||||
|
||||
public static bool ReadSetOnlyProperty => m_setOnlyProperty;
|
||||
public object AmbigObject;
|
||||
|
||||
public List<List<List<string>>> ANestedNestedList = new List<List<List<string>>>
|
||||
{
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
"three",
|
||||
"four"
|
||||
}
|
||||
},
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"five",
|
||||
"six"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static bool SetOnlyProperty
|
||||
{
|
||||
set => m_setOnlyProperty = value;
|
||||
}
|
||||
private static bool m_setOnlyProperty;
|
||||
public static bool ReadSetOnlyProperty => m_setOnlyProperty;
|
||||
|
||||
public Texture2D TestTexture = UIStyles.MakeTex(200, 200, Color.white);
|
||||
public Texture TestTexture;
|
||||
public static Sprite TestSprite;
|
||||
|
||||
public static int StaticProperty => 5;
|
||||
public static int StaticField = 5;
|
||||
public int NonStaticField;
|
||||
|
||||
#if CPP
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> ILHashSetTest;
|
||||
public static Il2CppReferenceArray<Il2CppSystem.Object> testRefArray;
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest;
|
||||
public static Il2CppSystem.Collections.Generic.List<string> CppStringTest;
|
||||
public static Il2CppSystem.Collections.IList CppIList;
|
||||
#endif
|
||||
|
||||
public TestClass()
|
||||
{
|
||||
int a = 0;
|
||||
foreach (var list in ANestedNestedList)
|
||||
{
|
||||
foreach (var list2 in list)
|
||||
{
|
||||
for (int i = 0; i < 33; i++)
|
||||
list2.Add(a++.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#if CPP
|
||||
TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600);
|
||||
TestTexture.name = "TestTexture";
|
||||
|
||||
var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
|
||||
var v2 = Vector2.zero;
|
||||
var v4 = Vector4.zero;
|
||||
TestSprite = Sprite.CreateSprite_Injected(TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
|
||||
TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
|
||||
|
||||
GameObject.DontDestroyOnLoad(TestTexture);
|
||||
GameObject.DontDestroyOnLoad(TestSprite);
|
||||
|
||||
testRefArray = new Il2CppReferenceArray<Il2CppSystem.Object>(5);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
testRefArray[i] = "hi " + i;
|
||||
}
|
||||
|
||||
//// test loading a tex from file
|
||||
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\Explorer\Tex_Nemundis_Nebula.png");
|
||||
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png");
|
||||
//ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
|
||||
|
||||
ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||
ILHashSetTest.Add("1");
|
||||
ILHashSetTest.Add("2");
|
||||
ILHashSetTest.Add("3");
|
||||
CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||
CppHashSetTest.Add("1");
|
||||
CppHashSetTest.Add("2");
|
||||
CppHashSetTest.Add("3");
|
||||
|
||||
CppStringTest = new Il2CppSystem.Collections.Generic.List<string>();
|
||||
CppStringTest.Add("1");
|
||||
CppStringTest.Add("2");
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2)
|
||||
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
|
||||
{
|
||||
arg2 = "this is arg2";
|
||||
|
@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Explorer.Helpers;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Input;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using UnityExplorer.Config;
|
||||
#if ML
|
||||
using Harmony;
|
||||
#else
|
||||
using HarmonyLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public class ForceUnlockCursor
|
||||
{
|
||||
@ -19,6 +22,12 @@ namespace Explorer.UI
|
||||
}
|
||||
private static bool m_forceUnlock;
|
||||
|
||||
private static void SetForceUnlock(bool unlock)
|
||||
{
|
||||
m_forceUnlock = unlock;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
|
||||
public static bool ShouldForceMouse => ExplorerCore.ShowMenu && Unlock;
|
||||
|
||||
private static CursorLockMode m_lastLockMode;
|
||||
@ -26,12 +35,26 @@ namespace Explorer.UI
|
||||
|
||||
private static bool m_currentlySettingCursor = false;
|
||||
|
||||
private static Type CursorType
|
||||
=> m_cursorType
|
||||
private static Type CursorType
|
||||
=> m_cursorType
|
||||
?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
|
||||
private static Type m_cursorType;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
|
||||
|
||||
SetupPatches();
|
||||
|
||||
Unlock = true;
|
||||
}
|
||||
|
||||
internal static void ModConfig_OnConfigChanged()
|
||||
{
|
||||
Unlock = ModConfig.Instance.Force_Unlock_Mouse;
|
||||
}
|
||||
|
||||
private static void SetupPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -43,31 +66,39 @@ namespace Explorer.UI
|
||||
// Get current cursor state and enable cursor
|
||||
try
|
||||
{
|
||||
m_lastLockMode = (CursorLockMode)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static).GetValue(null, null);
|
||||
m_lastVisibleState = (bool)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static).GetValue(null, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_lastLockMode = CursorLockMode.None;
|
||||
m_lastVisibleState = true;
|
||||
//m_lastLockMode = Cursor.lockState;
|
||||
m_lastLockMode = (CursorLockMode?)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
|
||||
?? CursorLockMode.None;
|
||||
|
||||
//m_lastVisibleState = Cursor.visible;
|
||||
m_lastVisibleState = (bool?)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
|
||||
?? false;
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Setup Harmony Patches
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_lockState))), true);
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_lockState))), false);
|
||||
TryPatch(typeof(EventSystem),
|
||||
"current",
|
||||
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_EventSystem_set_current))),
|
||||
true);
|
||||
|
||||
TryPatch("visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))), true);
|
||||
TryPatch("visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_visible))), false);
|
||||
TryPatch(typeof(Cursor),
|
||||
"lockState",
|
||||
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_lockState))),
|
||||
true);
|
||||
|
||||
TryPatch(typeof(Cursor),
|
||||
"visible",
|
||||
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))),
|
||||
true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
|
||||
}
|
||||
|
||||
Unlock = true;
|
||||
}
|
||||
|
||||
private static void TryPatch(string property, HarmonyMethod patch, bool setter)
|
||||
private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -77,40 +108,22 @@ namespace Explorer.UI
|
||||
#else
|
||||
ExplorerBepInPlugin.HarmonyInstance;
|
||||
#endif
|
||||
;
|
||||
|
||||
var prop = typeof(Cursor).GetProperty(property);
|
||||
System.Reflection.PropertyInfo prop = type.GetProperty(property);
|
||||
|
||||
if (setter)
|
||||
if (setter) // setter is prefix
|
||||
{
|
||||
// setter is prefix
|
||||
harmony.Patch(prop.GetSetMethod(), prefix: patch);
|
||||
}
|
||||
else
|
||||
else // getter is postfix
|
||||
{
|
||||
// getter is postfix
|
||||
harmony.Patch(prop.GetGetMethod(), postfix: patch);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch // (Exception e)
|
||||
{
|
||||
string s = setter ? "set_" : "get_" ;
|
||||
ExplorerCore.Log($"Unable to patch Cursor.{s}{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetForceUnlock(bool unlock)
|
||||
{
|
||||
m_forceUnlock = unlock;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
// Check Force-Unlock input
|
||||
if (InputManager.GetKeyDown(KeyCode.LeftAlt))
|
||||
{
|
||||
Unlock = !Unlock;
|
||||
//string suf = setter ? "set_" : "get_";
|
||||
//ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +150,45 @@ namespace Explorer.UI
|
||||
}
|
||||
}
|
||||
|
||||
// Event system overrides
|
||||
|
||||
private static bool m_settingEventSystem;
|
||||
private static EventSystem m_lastEventSystem;
|
||||
private static BaseInputModule m_lastInputModule;
|
||||
|
||||
public static void SetEventSystem()
|
||||
{
|
||||
m_settingEventSystem = true;
|
||||
UIManager.SetEventSystem();
|
||||
m_settingEventSystem = false;
|
||||
}
|
||||
|
||||
public static void ReleaseEventSystem()
|
||||
{
|
||||
if (m_lastEventSystem)
|
||||
{
|
||||
m_settingEventSystem = true;
|
||||
EventSystem.current = m_lastEventSystem;
|
||||
m_lastInputModule?.ActivateModule();
|
||||
m_settingEventSystem = false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
if (!m_settingEventSystem)
|
||||
{
|
||||
m_lastEventSystem = value;
|
||||
m_lastInputModule = value?.currentInputModule;
|
||||
|
||||
if (ExplorerCore.ShowMenu)
|
||||
{
|
||||
value = UIManager.EventSys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||
// value that we set back to when we close the menu or disable force-unlock.
|
||||
@ -168,23 +220,5 @@ namespace Explorer.UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_lockState(ref CursorLockMode __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastLockMode;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_visible(ref bool __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastVisibleState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,751 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.UI.Main;
|
||||
using Explorer.Unstrip.LayerMasks;
|
||||
using Explorer.Helpers;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI.Inspectors
|
||||
{
|
||||
public class GameObjectInspector : WindowBase
|
||||
{
|
||||
public override string Title => WindowManager.TabView
|
||||
? $"<color=cyan>[G]</color> {TargetGO.name}"
|
||||
: $"GameObject Inspector ({TargetGO.name})";
|
||||
|
||||
public GameObject TargetGO;
|
||||
|
||||
public bool pendingDestroy;
|
||||
|
||||
private static bool m_hideControls;
|
||||
|
||||
// gui element holders
|
||||
private string m_name;
|
||||
private string m_scene;
|
||||
|
||||
private Transform[] m_children;
|
||||
private Vector2 m_transformScroll = Vector2.zero;
|
||||
private readonly PageHelper ChildPages = new PageHelper();
|
||||
|
||||
private Component[] m_components;
|
||||
private Vector2 m_compScroll = Vector2.zero;
|
||||
private readonly PageHelper CompPages = new PageHelper();
|
||||
|
||||
private readonly Vector3[] m_cachedInput = new Vector3[3];
|
||||
private float m_translateAmount = 0.3f;
|
||||
private float m_rotateAmount = 50f;
|
||||
private float m_scaleAmount = 0.1f;
|
||||
private bool m_freeze;
|
||||
private Vector3 m_frozenPosition;
|
||||
private Quaternion m_frozenRotation;
|
||||
private Vector3 m_frozenScale;
|
||||
private bool m_autoApplyTransform;
|
||||
private bool m_autoUpdateTransform;
|
||||
private bool m_localContext;
|
||||
|
||||
private int m_layer;
|
||||
|
||||
private readonly List<Component> m_cachedDestroyList = new List<Component>();
|
||||
private string m_addComponentInput = "";
|
||||
|
||||
private string m_setParentInput = "Enter a GameObject name or path";
|
||||
|
||||
public bool GetObjectAsGameObject()
|
||||
{
|
||||
var targetType = Target?.GetType();
|
||||
|
||||
if (targetType == typeof(GameObject))
|
||||
{
|
||||
TargetGO = Target as GameObject;
|
||||
return true;
|
||||
}
|
||||
else if (targetType == typeof(Transform))
|
||||
{
|
||||
TargetGO = (Target as Transform).gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
ExplorerCore.Log("Error: Target is null or not a GameObject/Transform!");
|
||||
DestroyWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (!GetObjectAsGameObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_name = TargetGO.name;
|
||||
m_scene = string.IsNullOrEmpty(TargetGO.scene.name)
|
||||
? "None (Asset/Resource)"
|
||||
: TargetGO.scene.name;
|
||||
|
||||
CacheTransformValues();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private void CacheTransformValues()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_cachedInput[0] = TargetGO.transform.localPosition;
|
||||
m_cachedInput[1] = TargetGO.transform.localEulerAngles;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedInput[0] = TargetGO.transform.position;
|
||||
m_cachedInput[1] = TargetGO.transform.eulerAngles;
|
||||
}
|
||||
m_cachedInput[2] = TargetGO.transform.localScale;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pendingDestroy) return;
|
||||
|
||||
if (Target == null)
|
||||
{
|
||||
DestroyOnException(new Exception("Target was destroyed."));
|
||||
return;
|
||||
}
|
||||
if (!TargetGO && !GetObjectAsGameObject())
|
||||
{
|
||||
DestroyOnException(new Exception("Target was destroyed."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_freeze)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
TargetGO.transform.localPosition = m_frozenPosition;
|
||||
TargetGO.transform.localRotation = m_frozenRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGO.transform.position = m_frozenPosition;
|
||||
TargetGO.transform.rotation = m_frozenRotation;
|
||||
}
|
||||
TargetGO.transform.localScale = m_frozenScale;
|
||||
}
|
||||
|
||||
m_layer = TargetGO.layer;
|
||||
|
||||
// update child objects
|
||||
var childList = new List<Transform>();
|
||||
for (int i = 0; i < TargetGO.transform.childCount; i++)
|
||||
{
|
||||
childList.Add(TargetGO.transform.GetChild(i));
|
||||
}
|
||||
childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
m_children = childList.ToArray();
|
||||
|
||||
ChildPages.ItemCount = m_children.Length;
|
||||
|
||||
// update components
|
||||
#if CPP
|
||||
var compList = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
TargetGO.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
|
||||
m_components = compList.ToArray();
|
||||
#else
|
||||
m_components = TargetGO.GetComponents<Component>();
|
||||
#endif
|
||||
|
||||
CompPages.ItemCount = m_components.Length;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DestroyOnException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyOnException(Exception e)
|
||||
{
|
||||
if (pendingDestroy) return;
|
||||
|
||||
ExplorerCore.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
|
||||
pendingDestroy = true;
|
||||
DestroyWindow();
|
||||
}
|
||||
|
||||
private void InspectGameObject(Transform obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height);
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
#if CPP
|
||||
private void ReflectObject(Il2CppSystem.Object obj)
|
||||
#else
|
||||
private void ReflectObject(object obj)
|
||||
#endif
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created, true);
|
||||
|
||||
if (created)
|
||||
{
|
||||
if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100))
|
||||
{
|
||||
window.m_rect = new Rect(
|
||||
this.m_rect.x + this.m_rect.width + 20,
|
||||
this.m_rect.y,
|
||||
550,
|
||||
700);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
if (pendingDestroy) return;
|
||||
|
||||
try
|
||||
{
|
||||
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
Header();
|
||||
GUIHelper.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
|
||||
}
|
||||
|
||||
scroll = GUIHelper.BeginScrollView(scroll);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", new GUILayoutOption[0]);
|
||||
if (m_scene == UnityHelpers.ActiveSceneName)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
ScenePage.Instance.SetTransformTarget(TargetGO.transform);
|
||||
MainMenu.SetCurrentPage(0);
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
WindowManager.InspectObject(Target, out _, true);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
string pathlabel = TargetGO.transform.GetGameObjectPath();
|
||||
if (TargetGO.transform.parent != null)
|
||||
{
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
InspectGameObject(TargetGO.transform.parent);
|
||||
}
|
||||
}
|
||||
GUIHelper.TextArea(pathlabel, new GUILayoutOption[0]);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
GUIHelper.TextArea(m_name, new GUILayoutOption[0]);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
LayerControls();
|
||||
|
||||
// --- Horizontal Columns section ---
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
|
||||
TransformList(rect);
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
|
||||
ComponentList(rect);
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.EndHorizontal(); // end horiz columns
|
||||
|
||||
GameObjectControls();
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
|
||||
|
||||
GUIHelper.EndArea();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DestroyOnException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void LayerControls()
|
||||
{
|
||||
GUIHelper.BeginHorizontal();
|
||||
GUILayout.Label("Layer:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
if (m_layer > 0)
|
||||
{
|
||||
m_layer--;
|
||||
if (TargetGO) TargetGO.layer = m_layer;
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
if (m_layer < 32)
|
||||
{
|
||||
m_layer++;
|
||||
if (TargetGO) TargetGO.layer = m_layer;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"{m_layer} (<color=cyan>{LayerMaskUnstrip.LayerToName(m_layer)}</color>)",
|
||||
new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void TransformList(Rect m_rect)
|
||||
{
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
m_transformScroll = GUIHelper.BeginScrollView(m_transformScroll);
|
||||
|
||||
GUILayout.Label("<b><size=15>Children</size></b>", new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
ChildPages.DrawLimitInputArea();
|
||||
|
||||
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
|
||||
{
|
||||
ChildPages.CurrentPageLabel();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll);
|
||||
}
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (m_children != null && m_children.Length > 0)
|
||||
{
|
||||
int start = ChildPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + ChildPages.ItemsPerPage && j < ChildPages.ItemCount); j++)
|
||||
{
|
||||
var obj = m_children[j];
|
||||
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", new GUILayoutOption[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Buttons.GameObjectButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i>None</i>", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ComponentList(Rect m_rect)
|
||||
{
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
m_compScroll = GUIHelper.BeginScrollView(m_compScroll);
|
||||
GUILayout.Label("<b><size=15>Components</size></b>", new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
CompPages.DrawLimitInputArea();
|
||||
|
||||
if (CompPages.ItemCount > CompPages.ItemsPerPage)
|
||||
{
|
||||
CompPages.CurrentPageLabel();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
CompPages.TurnPage(Turn.Left, ref this.m_compScroll);
|
||||
}
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
CompPages.TurnPage(Turn.Right, ref this.m_compScroll);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
var width = m_rect.width / 2 - 135f;
|
||||
m_addComponentInput = GUIHelper.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(width) });
|
||||
if (GUILayout.Button("Add Comp", new GUILayoutOption[0]))
|
||||
{
|
||||
if (ReflectionHelpers.GetTypeByName(m_addComponentInput) is Type compType)
|
||||
{
|
||||
if (typeof(Component).IsAssignableFrom(compType))
|
||||
{
|
||||
#if CPP
|
||||
TargetGO.AddComponent(Il2CppType.From(compType));
|
||||
#else
|
||||
TargetGO.AddComponent(compType);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Type '{compType.Name}' is not assignable from Component!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find a type by the name of '{m_addComponentInput}'!");
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
m_cachedDestroyList.Clear();
|
||||
}
|
||||
|
||||
if (m_components != null)
|
||||
{
|
||||
int start = CompPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++)
|
||||
{
|
||||
var component = m_components[j];
|
||||
|
||||
if (!component) continue;
|
||||
|
||||
var type =
|
||||
#if CPP
|
||||
component.GetIl2CppType();
|
||||
#else
|
||||
component.GetType();
|
||||
#endif
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(type))
|
||||
{
|
||||
#if CPP
|
||||
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
||||
#else
|
||||
BehaviourEnabledBtn(component as Behaviour);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIHelper.Space(26);
|
||||
}
|
||||
if (GUILayout.Button("<color=cyan>" + type.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
|
||||
{
|
||||
ReflectObject(component);
|
||||
}
|
||||
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
m_cachedDestroyList.Add(component);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var comp = m_cachedDestroyList[i];
|
||||
GameObject.Destroy(comp);
|
||||
}
|
||||
}
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void BehaviourEnabledBtn(Behaviour obj)
|
||||
{
|
||||
var _col = GUI.color;
|
||||
bool _enabled = obj.enabled;
|
||||
if (_enabled)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
_enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.enabled != _enabled)
|
||||
{
|
||||
obj.enabled = _enabled;
|
||||
}
|
||||
GUI.color = _col;
|
||||
}
|
||||
|
||||
private void GameObjectControls()
|
||||
{
|
||||
if (m_hideControls)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
if (GUILayout.Button("^ Show ^", new GUILayoutOption[] { GUILayout.Width(75) }))
|
||||
{
|
||||
m_hideControls = false;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
if (GUILayout.Button("v Hide v", new GUILayoutOption[] { GUILayout.Width(75) }))
|
||||
{
|
||||
m_hideControls = true;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
bool m_active = TargetGO.activeSelf;
|
||||
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (TargetGO.activeSelf != m_active) { TargetGO.SetActive(m_active); }
|
||||
|
||||
Buttons.InstantiateButton(TargetGO, 100);
|
||||
|
||||
if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) }))
|
||||
{
|
||||
GameObject.DontDestroyOnLoad(TargetGO);
|
||||
TargetGO.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||
}
|
||||
|
||||
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
|
||||
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) }))
|
||||
{
|
||||
m_freeze = !m_freeze;
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
m_setParentInput = GUIHelper.TextField(m_setParentInput, new GUILayoutOption[0]);
|
||||
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
||||
{
|
||||
TargetGO.transform.parent = newparent.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find gameobject '{m_setParentInput}'");
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Detach from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
|
||||
{
|
||||
TargetGO.transform.parent = null;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false);
|
||||
m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true);
|
||||
m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
if (GUILayout.Button("<color=lime>Apply to Transform</color>", new GUILayoutOption[0]) || m_autoApplyTransform)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
TargetGO.transform.localPosition = m_cachedInput[0];
|
||||
TargetGO.transform.localEulerAngles = m_cachedInput[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGO.transform.position = m_cachedInput[0];
|
||||
TargetGO.transform.eulerAngles = m_cachedInput[1];
|
||||
}
|
||||
TargetGO.transform.localScale = m_cachedInput[2];
|
||||
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("<color=lime>Update from Transform</color>", new GUILayoutOption[0]) || m_autoUpdateTransform)
|
||||
{
|
||||
CacheTransformValues();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
|
||||
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
bool b = m_localContext;
|
||||
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "orange") + ">Use local transform values?</color>", new GUILayoutOption[0]);
|
||||
if (b != m_localContext)
|
||||
{
|
||||
m_localContext = b;
|
||||
CacheTransformValues();
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
GameObject.Destroy(TargetGO);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void UpdateFreeze()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_frozenPosition = TargetGO.transform.localPosition;
|
||||
m_frozenRotation = TargetGO.transform.localRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frozenPosition = TargetGO.transform.position;
|
||||
m_frozenRotation = TargetGO.transform.rotation;
|
||||
}
|
||||
m_frozenScale = TargetGO.transform.localScale;
|
||||
}
|
||||
|
||||
private void BoolToggle(ref bool value, string message)
|
||||
{
|
||||
string lbl = "<color=";
|
||||
lbl += value ? "lime" : "orange";
|
||||
lbl += $">{message}</color>";
|
||||
|
||||
value = GUILayout.Toggle(value, lbl, new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
public enum TranslateType
|
||||
{
|
||||
Position,
|
||||
Rotation,
|
||||
Scale
|
||||
}
|
||||
|
||||
private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
|
||||
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
|
||||
|
||||
var transform = TargetGO.transform;
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position:
|
||||
var pos = m_localContext ? transform.localPosition : transform.position;
|
||||
GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Rotation:
|
||||
var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles;
|
||||
GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Scale:
|
||||
GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
Vector3 input = m_cachedInput[(int)mode];
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.x, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.y, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.z, amount, multByTime);
|
||||
|
||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
var amountInput = amount.ToString("F3");
|
||||
amountInput = GUIHelper.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(amountInput, out float f))
|
||||
{
|
||||
amount = f;
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
||||
{
|
||||
string s = f.ToString("F3");
|
||||
s = GUIHelper.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(s, out float f2))
|
||||
{
|
||||
f = f2;
|
||||
}
|
||||
if (GUIHelper.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f -= multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
if (GUIHelper.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f += multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.UI.Inspectors
|
||||
{
|
||||
public class InspectUnderMouse
|
||||
{
|
||||
public static bool EnableInspect { get; set; } = false;
|
||||
|
||||
private static string m_objUnderMouseName = "";
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (ExplorerCore.ShowMenu)
|
||||
{
|
||||
if (InputManager.GetKey(KeyCode.LeftShift) && InputManager.GetMouseButtonDown(1))
|
||||
{
|
||||
EnableInspect = !EnableInspect;
|
||||
}
|
||||
|
||||
if (EnableInspect)
|
||||
{
|
||||
InspectRaycast();
|
||||
}
|
||||
}
|
||||
else if (EnableInspect)
|
||||
{
|
||||
EnableInspect = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InspectRaycast()
|
||||
{
|
||||
if (!UnityHelpers.MainCamera)
|
||||
return;
|
||||
|
||||
var ray = UnityHelpers.MainCamera.ScreenPointToRay(InputManager.MousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
|
||||
m_objUnderMouseName = obj.transform.GetGameObjectPath();
|
||||
|
||||
if (InputManager.GetMouseButtonDown(0))
|
||||
{
|
||||
EnableInspect = false;
|
||||
m_objUnderMouseName = "";
|
||||
|
||||
WindowManager.InspectObject(obj, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_objUnderMouseName = "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnGUI()
|
||||
{
|
||||
if (EnableInspect)
|
||||
{
|
||||
if (m_objUnderMouseName != "")
|
||||
{
|
||||
var pos = InputManager.MousePosition;
|
||||
var rect = new Rect(
|
||||
pos.x - (Screen.width / 2), // x
|
||||
Screen.height - pos.y - 50, // y
|
||||
Screen.width, // w
|
||||
50 // h
|
||||
);
|
||||
|
||||
var origAlign = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
//shadow text
|
||||
GUI.Label(rect, $"<color=black>{m_objUnderMouseName}</color>");
|
||||
//white text
|
||||
GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName);
|
||||
|
||||
GUI.skin.label.alignment = origAlign;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI.Inspectors
|
||||
{
|
||||
public class InstanceInspector : ReflectionInspector
|
||||
{
|
||||
public override bool IsStaticInspector => false;
|
||||
|
||||
// some extra cast-caching
|
||||
public UnityEngine.Object m_uObj;
|
||||
private Component m_component;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
// cache the extra cast-caching
|
||||
#if CPP
|
||||
if (!IsStaticInspector && Target is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var unityObj = ilObject.TryCast<UnityEngine.Object>();
|
||||
if (unityObj)
|
||||
{
|
||||
m_uObj = unityObj;
|
||||
|
||||
var component = ilObject.TryCast<Component>();
|
||||
if (component)
|
||||
{
|
||||
m_component = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!IsStaticInspector)
|
||||
{
|
||||
m_uObj = Target as UnityEngine.Object;
|
||||
m_component = Target as Component;
|
||||
}
|
||||
#endif
|
||||
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
ExplorerCore.Log("Target is null!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
if (Target is UnityEngine.Object uObj)
|
||||
{
|
||||
if (!uObj)
|
||||
{
|
||||
ExplorerCore.Log("Target was destroyed!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
public void DrawInstanceControls(Rect rect)
|
||||
{
|
||||
//if (m_uObj)
|
||||
//{
|
||||
// GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]);
|
||||
//}
|
||||
//GUILayout.EndHorizontal();
|
||||
|
||||
if (m_uObj)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
Buttons.InstantiateButton(m_uObj);
|
||||
if (m_component && m_component.gameObject is GameObject obj)
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) });
|
||||
var charWidth = obj.name.Length * 15;
|
||||
var maxWidth = rect.width - 350;
|
||||
var btnWidth = charWidth < maxWidth ? charWidth : maxWidth;
|
||||
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.Width(btnWidth) }))
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI.Inspectors
|
||||
{
|
||||
public class StaticInspector : ReflectionInspector
|
||||
{
|
||||
public override bool IsStaticInspector => true;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override bool ShouldProcessMember(CacheMember holder)
|
||||
{
|
||||
return base.ShouldProcessMember(holder);
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
base.WindowFunction(windowID);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.UI.Inspectors;
|
||||
using Explorer.Helpers;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI.Inspectors
|
||||
{
|
||||
public abstract class ReflectionInspector : WindowBase
|
||||
{
|
||||
public override string Title => WindowManager.TabView
|
||||
? $"<color=cyan>[R]</color> {TargetType.Name}"
|
||||
: $"Reflection Inspector ({TargetType.Name})";
|
||||
|
||||
public virtual bool IsStaticInspector { get; }
|
||||
|
||||
public Type TargetType;
|
||||
|
||||
private CacheMember[] m_allCachedMembers;
|
||||
private CacheMember[] m_cachedMembersFiltered;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private bool m_autoUpdate = false;
|
||||
private string m_search = "";
|
||||
public MemberTypes m_typeFilter = MemberTypes.Property;
|
||||
private bool m_hideFailedReflection = false;
|
||||
|
||||
private MemberScopes m_scopeFilter;
|
||||
private enum MemberScopes
|
||||
{
|
||||
Both,
|
||||
Instance,
|
||||
Static
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _typeAndMemberBlacklist = new HashSet<string>
|
||||
{
|
||||
// Causes a crash
|
||||
"Type.DeclaringMethod",
|
||||
// Causes a crash
|
||||
"Rigidbody2D.Cast",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _methodStartsWithBlacklist = new HashSet<string>
|
||||
{
|
||||
// Pointless (handled by Properties)
|
||||
"get_",
|
||||
"set_",
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (!IsStaticInspector)
|
||||
{
|
||||
TargetType = ReflectionHelpers.GetActualType(Target);
|
||||
CacheMembers(ReflectionHelpers.GetAllBaseTypes(Target));
|
||||
}
|
||||
else
|
||||
{
|
||||
CacheMembers(new Type[] { TargetType });
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_allCachedMembers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
foreach (var member in m_cachedMembersFiltered)
|
||||
{
|
||||
member.UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool ShouldProcessMember(CacheMember holder)
|
||||
{
|
||||
// check MemberTypes filter
|
||||
if (m_typeFilter != MemberTypes.All && m_typeFilter != holder.MemInfo?.MemberType)
|
||||
return false;
|
||||
|
||||
// hide failed reflection
|
||||
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection)
|
||||
return false;
|
||||
|
||||
// check scope filter
|
||||
if (m_scopeFilter == MemberScopes.Instance && holder.IsStatic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (m_scopeFilter == MemberScopes.Static && !holder.IsStatic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// see if we should do name search
|
||||
if (m_search == "" || holder.MemInfo == null)
|
||||
return true;
|
||||
|
||||
// ok do name search
|
||||
return (holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name)
|
||||
.ToLower()
|
||||
.Contains(m_search.ToLower());
|
||||
}
|
||||
|
||||
private void CacheMembers(Type[] types)
|
||||
{
|
||||
var list = new List<CacheMember>();
|
||||
var cachedSigs = new HashSet<string>();
|
||||
|
||||
foreach (var declaringType in types)
|
||||
{
|
||||
MemberInfo[] infos;
|
||||
try
|
||||
{
|
||||
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.Log($"Exception getting members for type: {declaringType.FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = Target;
|
||||
#if CPP
|
||||
try
|
||||
{
|
||||
target = target.Il2CppCast(declaringType);
|
||||
}
|
||||
catch //(Exception e)
|
||||
{
|
||||
//ExplorerCore.LogWarning("Excepting casting " + target.GetType().FullName + " to " + declaringType.FullName);
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var member in infos)
|
||||
{
|
||||
try
|
||||
{
|
||||
// make sure member type is Field, Method or Property (4 / 8 / 16)
|
||||
int m = (int)member.MemberType;
|
||||
if (m < 4 || m > 16)
|
||||
continue;
|
||||
|
||||
var pi = member as PropertyInfo;
|
||||
var mi = member as MethodInfo;
|
||||
|
||||
if (IsStaticInspector)
|
||||
{
|
||||
if (member is FieldInfo fi && !fi.IsStatic) continue;
|
||||
else if (pi != null && !pi.GetAccessors(true)[0].IsStatic) continue;
|
||||
else if (mi != null && !mi.IsStatic) continue;
|
||||
}
|
||||
|
||||
// check blacklisted members
|
||||
var sig = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
if (_typeAndMemberBlacklist.Any(it => it == sig))
|
||||
continue;
|
||||
|
||||
if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it)))
|
||||
continue;
|
||||
|
||||
if (mi != null)
|
||||
{
|
||||
AppendParams(mi.GetParameters());
|
||||
}
|
||||
else if (pi != null)
|
||||
{
|
||||
AppendParams(pi.GetIndexParameters());
|
||||
}
|
||||
|
||||
void AppendParams(ParameterInfo[] _args)
|
||||
{
|
||||
sig += " (";
|
||||
foreach (var param in _args)
|
||||
{
|
||||
sig += $"{param.ParameterType.Name} {param.Name}, ";
|
||||
}
|
||||
sig += ")";
|
||||
}
|
||||
|
||||
if (cachedSigs.Contains(sig))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// ExplorerCore.Log($"Trying to cache member {sig}...");
|
||||
|
||||
var cached = CacheFactory.GetCacheObject(member, target);
|
||||
|
||||
if (cached != null)
|
||||
{
|
||||
cachedSigs.Add(sig);
|
||||
list.Add(cached);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception caching member {sig}!");
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!");
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_allCachedMembers = list.ToArray();
|
||||
}
|
||||
|
||||
// =========== GUI DRAW =========== //
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ====== HEADER ======
|
||||
|
||||
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
Header();
|
||||
GUIHelper.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
|
||||
}
|
||||
|
||||
var asInstance = this as InstanceInspector;
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
var labelWidth = (asInstance != null && asInstance.m_uObj)
|
||||
? new GUILayoutOption[] { GUILayout.Width(245f) }
|
||||
: new GUILayoutOption[0];
|
||||
GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", labelWidth);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (asInstance != null)
|
||||
{
|
||||
asInstance.DrawInstanceControls(rect);
|
||||
}
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
m_search = GUIHelper.TextField(m_search, new GUILayoutOption[0]);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
FilterTypeToggle(MemberTypes.All, "All");
|
||||
FilterTypeToggle(MemberTypes.Property, "Properties");
|
||||
FilterTypeToggle(MemberTypes.Field, "Fields");
|
||||
FilterTypeToggle(MemberTypes.Method, "Methods");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (this is InstanceInspector)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b>Scope:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
FilterScopeToggle(MemberScopes.Both, "Both");
|
||||
FilterScopeToggle(MemberScopes.Instance, "Instance");
|
||||
FilterScopeToggle(MemberScopes.Static, "Static");
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
GUI.color = m_autoUpdate ? Color.green : Color.red;
|
||||
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
GUI.color = m_hideFailedReflection ? Color.green : Color.red;
|
||||
m_hideFailedReflection = GUILayout.Toggle(m_hideFailedReflection, "Hide failed Reflection?", new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUI.color = Color.white;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.Space(10);
|
||||
|
||||
Pages.ItemCount = m_cachedMembersFiltered.Length;
|
||||
|
||||
// prev/next page buttons
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// ====== BODY ======
|
||||
|
||||
scroll = GUIHelper.BeginScrollView(scroll);
|
||||
|
||||
GUIHelper.Space(10);
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
var members = this.m_cachedMembersFiltered;
|
||||
int start = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + Pages.ItemsPerPage && j < members.Length); j++)
|
||||
{
|
||||
var holder = members[j];
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||
try
|
||||
{
|
||||
holder.Draw(rect, 180f);
|
||||
}
|
||||
catch
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
continue;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// if not last element
|
||||
if (!(j == (start + Pages.ItemsPerPage - 1) || j == (members.Length - 1)))
|
||||
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUIHelper.EndScrollView();
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
|
||||
|
||||
GUIHelper.EndArea();
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e.Message.Contains("in a group with only"))
|
||||
{
|
||||
// suppress
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterTypeToggle(MemberTypes mode, string label)
|
||||
{
|
||||
if (m_typeFilter == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
m_typeFilter = mode;
|
||||
Pages.PageOffset = 0;
|
||||
scroll = Vector2.zero;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
private void FilterScopeToggle(MemberScopes mode, string label)
|
||||
{
|
||||
if (m_scopeFilter == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
m_scopeFilter = mode;
|
||||
Pages.PageOffset = 0;
|
||||
scroll = Vector2.zero;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Helpers;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveValue
|
||||
{
|
||||
public const float MAX_LABEL_WIDTH = 400f;
|
||||
public const string EVALUATE_LABEL = "<color=lime>Evaluate</color>";
|
||||
|
||||
public CacheObjectBase OwnerCacheObject;
|
||||
|
||||
public object Value { get; set; }
|
||||
public Type ValueType;
|
||||
|
||||
public string ButtonLabel => m_btnLabel ?? GetButtonLabel();
|
||||
private string m_btnLabel;
|
||||
|
||||
public MethodInfo ToStringMethod => m_toStringMethod ?? GetToStringMethod();
|
||||
private MethodInfo m_toStringMethod;
|
||||
|
||||
public virtual void Init()
|
||||
{
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
public virtual void UpdateValue()
|
||||
{
|
||||
GetButtonLabel();
|
||||
}
|
||||
|
||||
public float CalcWhitespace(Rect window)
|
||||
{
|
||||
if (!(this is IExpandHeight)) return 0f;
|
||||
|
||||
float whitespace = (this as IExpandHeight).WhiteSpace;
|
||||
if (whitespace > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref whitespace);
|
||||
}
|
||||
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
public static void ClampLabelWidth(Rect window, ref float labelWidth)
|
||||
{
|
||||
float min = window.width * 0.37f;
|
||||
if (min > MAX_LABEL_WIDTH) min = MAX_LABEL_WIDTH;
|
||||
|
||||
labelWidth = Mathf.Clamp(labelWidth, min, MAX_LABEL_WIDTH);
|
||||
}
|
||||
|
||||
public void Draw(Rect window, float labelWidth = 215f)
|
||||
{
|
||||
if (labelWidth > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref labelWidth);
|
||||
}
|
||||
|
||||
var cacheMember = OwnerCacheObject as CacheMember;
|
||||
|
||||
if (cacheMember != null && cacheMember.MemInfo != null)
|
||||
{
|
||||
GUILayout.Label(cacheMember.RichTextName, new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIHelper.Space(labelWidth);
|
||||
}
|
||||
|
||||
var cacheMethod = OwnerCacheObject as CacheMethod;
|
||||
|
||||
if (cacheMember != null && cacheMember.HasParameters)
|
||||
{
|
||||
GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.ExpandHeight(true) } );
|
||||
|
||||
if (cacheMember.m_isEvaluating)
|
||||
{
|
||||
if (cacheMethod != null && cacheMethod.GenericArgs.Length > 0)
|
||||
{
|
||||
cacheMethod.DrawGenericArgsInput();
|
||||
}
|
||||
|
||||
if (cacheMember.m_arguments.Length > 0)
|
||||
{
|
||||
cacheMember.DrawArgsInput();
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
if (cacheMethod != null)
|
||||
cacheMethod.Evaluate();
|
||||
else
|
||||
cacheMember.UpdateValue();
|
||||
}
|
||||
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
cacheMember.m_isEvaluating = false;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
var lbl = $"Evaluate (";
|
||||
int len = cacheMember.m_arguments.Length;
|
||||
if (cacheMethod != null) len += cacheMethod.GenericArgs.Length;
|
||||
lbl += len + " params)";
|
||||
|
||||
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
cacheMember.m_isEvaluating = true;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(labelWidth);
|
||||
}
|
||||
else if (cacheMethod != null)
|
||||
{
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
cacheMethod.Evaluate();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(labelWidth);
|
||||
}
|
||||
|
||||
string typeName = $"<color={Syntax.Class_Instance}>{ValueType.FullName}</color>";
|
||||
|
||||
if (cacheMember != null && !string.IsNullOrEmpty(cacheMember.ReflectionException))
|
||||
{
|
||||
GUILayout.Label("<color=red>Reflection failed!</color> (" + cacheMember.ReflectionException + ")", new GUILayoutOption[0]);
|
||||
}
|
||||
else if (cacheMember != null && (cacheMember.HasParameters || cacheMember is CacheMethod) && !cacheMember.m_evaluated)
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> ({typeName})", new GUILayoutOption[0]);
|
||||
}
|
||||
else if ((Value == null || Value is UnityEngine.Object uObj && !uObj) && !(cacheMember is CacheMethod))
|
||||
{
|
||||
GUILayout.Label($"<i>null ({typeName})</i>", new GUILayoutOption[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
float _width = window.width - labelWidth - 90;
|
||||
if (OwnerCacheObject is CacheMethod cm)
|
||||
{
|
||||
cm.DrawValue(window, _width);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawValue(window, _width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (GUILayout.Button(ButtonLabel, new GUILayoutOption[] { GUILayout.Width(width - 15) }))
|
||||
{
|
||||
if (OwnerCacheObject.IsStaticClassSearchResult)
|
||||
{
|
||||
WindowManager.InspectStaticReflection(Value as Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
|
||||
private MethodInfo GetToStringMethod()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
|
||||
?? typeof(object).GetMethod("ToString", new Type[0]);
|
||||
|
||||
// test invoke
|
||||
m_toStringMethod.Invoke(Value, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
return m_toStringMethod;
|
||||
}
|
||||
|
||||
public string GetButtonLabel()
|
||||
{
|
||||
if (Value == null) return null;
|
||||
|
||||
var valueType = ReflectionHelpers.GetActualType(Value);
|
||||
|
||||
string label;
|
||||
|
||||
if (valueType == typeof(TextAsset))
|
||||
{
|
||||
var textAsset = Value as TextAsset;
|
||||
|
||||
label = textAsset.text;
|
||||
|
||||
if (label.Length > 10)
|
||||
{
|
||||
label = $"{label.Substring(0, 10)}...";
|
||||
}
|
||||
|
||||
label = $"\"{label}\" {textAsset.name} (<color={Syntax.Class_Instance}>UnityEngine.TextAsset</color>)";
|
||||
}
|
||||
else
|
||||
{
|
||||
label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
|
||||
|
||||
if (label.Length > 100)
|
||||
{
|
||||
label = label.Substring(0, 99);
|
||||
}
|
||||
|
||||
var classColor = valueType.IsAbstract && valueType.IsSealed
|
||||
? Syntax.Class_Static
|
||||
: Syntax.Class_Instance;
|
||||
|
||||
string typeLabel = $"<color={classColor}>{valueType.FullName}</color>";
|
||||
|
||||
if (Value is UnityEngine.Object)
|
||||
{
|
||||
label = label.Replace($"({valueType.FullName})", $"({typeLabel})");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!label.Contains(valueType.FullName))
|
||||
{
|
||||
label += $" ({typeLabel})";
|
||||
}
|
||||
else
|
||||
{
|
||||
label = label.Replace(valueType.FullName, typeLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_btnLabel = label;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,312 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Helpers;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
// TODO: Re-work class using InteractiveEnumerable or maybe InteractiveCollection for the Keys/Value lists.
|
||||
// Make the keys and values editable.
|
||||
|
||||
public class InteractiveDictionary : InteractiveValue, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private CacheObjectBase[] m_cachedKeys = new CacheObjectBase[0];
|
||||
private CacheObjectBase[] m_cachedValues = new CacheObjectBase[0];
|
||||
|
||||
public Type TypeOfKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_keysType == null) GetGenericArguments();
|
||||
return m_keysType;
|
||||
}
|
||||
}
|
||||
private Type m_keysType;
|
||||
|
||||
public Type TypeOfValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_valuesType == null) GetGenericArguments();
|
||||
return m_valuesType;
|
||||
}
|
||||
}
|
||||
private Type m_valuesType;
|
||||
|
||||
public IDictionary IDict
|
||||
{
|
||||
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
|
||||
set => m_iDictionary = value;
|
||||
}
|
||||
private IDictionary m_iDictionary;
|
||||
|
||||
// ========== Methods ==========
|
||||
|
||||
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
|
||||
private IDictionary Il2CppDictionaryToMono()
|
||||
{
|
||||
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
|
||||
|
||||
// get keys and values
|
||||
var keys = ValueType.GetProperty("Keys").GetValue(Value, null);
|
||||
var values = ValueType.GetProperty("Values").GetValue(Value, null);
|
||||
|
||||
// create lists to hold them
|
||||
var keyList = new List<object>();
|
||||
var valueList = new List<object>();
|
||||
|
||||
// store entries with reflection
|
||||
EnumerateWithReflection(keys, keyList);
|
||||
EnumerateWithReflection(values, valueList);
|
||||
|
||||
// make actual mono dictionary
|
||||
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
||||
.MakeGenericType(TypeOfKeys, TypeOfValues));
|
||||
|
||||
// finally iterate into dictionary
|
||||
for (int i = 0; i < keyList.Count; i++)
|
||||
{
|
||||
dict.Add(keyList[i], valueList[i]);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private void EnumerateWithReflection(object collection, List<object> list)
|
||||
{
|
||||
// invoke GetEnumerator
|
||||
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
list.Add(current.GetValue(enumerator, null));
|
||||
}
|
||||
}
|
||||
|
||||
private void GetGenericArguments()
|
||||
{
|
||||
if (ValueType.IsGenericType)
|
||||
{
|
||||
var generics = ValueType.GetGenericArguments();
|
||||
m_keysType = generics[0];
|
||||
m_valuesType = generics[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's non-generic, just use System.Object to allow for anything.
|
||||
m_keysType = typeof(object);
|
||||
m_valuesType = typeof(object);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
// first make sure we won't run into a TypeInitializationException.
|
||||
if (!EnsureDictionaryIsSupported())
|
||||
{
|
||||
if (OwnerCacheObject is CacheMember cacheMember)
|
||||
{
|
||||
cacheMember.ReflectionException = "Dictionary Type not supported with Reflection!";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
base.UpdateValue();
|
||||
|
||||
CacheEntries();
|
||||
}
|
||||
|
||||
public void CacheEntries()
|
||||
{
|
||||
// reset
|
||||
IDict = null;
|
||||
|
||||
if (Value == null || IDict == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = new List<CacheObjectBase>();
|
||||
foreach (var key in IDict.Keys)
|
||||
{
|
||||
Type t = ReflectionHelpers.GetActualType(key) ?? TypeOfKeys;
|
||||
var cache = CacheFactory.GetCacheObject(key, t);
|
||||
keys.Add(cache);
|
||||
}
|
||||
|
||||
var values = new List<CacheObjectBase>();
|
||||
foreach (var val in IDict.Values)
|
||||
{
|
||||
Type t = ReflectionHelpers.GetActualType(val) ?? TypeOfValues;
|
||||
var cache = CacheFactory.GetCacheObject(val, t);
|
||||
values.Add(cache);
|
||||
}
|
||||
|
||||
m_cachedKeys = keys.ToArray();
|
||||
m_cachedValues = values.ToArray();
|
||||
}
|
||||
|
||||
public bool EnsureDictionaryIsSupported()
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(ValueType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#if CPP
|
||||
try
|
||||
{
|
||||
return Check(TypeOfKeys) && Check(TypeOfValues);
|
||||
|
||||
bool Check(Type type)
|
||||
{
|
||||
var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||
.MakeGenericType(type)
|
||||
.GetField("NativeClassPtr")
|
||||
.GetValue(null);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============= GUI Draw =============
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_cachedKeys == null || m_cachedValues == null)
|
||||
{
|
||||
GUILayout.Label("Cached keys or values is null!", new GUILayoutOption[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
int count = m_cachedKeys.Length;
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = $"[{count}] <color=#2df7b2>Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUIHelper.Space(5);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
Pages.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.Space(whitespace);
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
// prev/next page buttons
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left);
|
||||
}
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right);
|
||||
}
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
GUIHelper.Space(5);
|
||||
}
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
var key = m_cachedKeys[i];
|
||||
var val = m_cachedValues[i];
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
//GUIUnstrip.Space(whitespace);
|
||||
|
||||
if (key == null && val == null)
|
||||
{
|
||||
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", new GUILayoutOption[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
if (key != null)
|
||||
key.IValue.DrawValue(window, (window.width / 2) - 80f);
|
||||
else
|
||||
GUILayout.Label("<i>null</i>", new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
if (val != null)
|
||||
val.IValue.DrawValue(window, (window.width / 2) - 80f);
|
||||
else
|
||||
GUILayout.Label("<i>null</i>", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,362 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using System.Linq;
|
||||
using Explorer.Helpers;
|
||||
#if CPP
|
||||
using UnhollowerBaseLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveEnumerable : InteractiveValue, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private CacheEnumerated[] m_cachedEntries = new CacheEnumerated[0];
|
||||
|
||||
// Type of Entries in the Array
|
||||
public Type EntryType
|
||||
{
|
||||
get => GetEntryType();
|
||||
set => m_entryType = value;
|
||||
}
|
||||
private Type m_entryType;
|
||||
|
||||
// Cached IEnumerable object
|
||||
public IEnumerable Enumerable
|
||||
{
|
||||
get => GetEnumerable();
|
||||
}
|
||||
private IEnumerable m_enumerable;
|
||||
|
||||
// Generic Type Definition for Lists
|
||||
public Type GenericTypeDef
|
||||
{
|
||||
get => GetGenericTypeDef();
|
||||
}
|
||||
private Type m_genericTypeDef;
|
||||
|
||||
// Cached ToArray method for Lists
|
||||
public MethodInfo CppListToArrayMethod
|
||||
{
|
||||
get => GetGenericToArrayMethod();
|
||||
}
|
||||
private MethodInfo m_genericToArray;
|
||||
|
||||
// Cached Item Property for ILists
|
||||
public PropertyInfo ItemProperty
|
||||
{
|
||||
get => GetItemProperty();
|
||||
}
|
||||
|
||||
private PropertyInfo m_itemProperty;
|
||||
|
||||
// ========== Methods ==========
|
||||
|
||||
private IEnumerable GetEnumerable()
|
||||
{
|
||||
if (m_enumerable == null && Value != null)
|
||||
{
|
||||
m_enumerable = Value as IEnumerable ?? EnumerateWithReflection();
|
||||
}
|
||||
return m_enumerable;
|
||||
}
|
||||
|
||||
private Type GetGenericTypeDef()
|
||||
{
|
||||
if (m_genericTypeDef == null && Value != null)
|
||||
{
|
||||
var type = Value.GetType();
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
m_genericTypeDef = type.GetGenericTypeDefinition();
|
||||
}
|
||||
}
|
||||
return m_genericTypeDef;
|
||||
}
|
||||
|
||||
private MethodInfo GetGenericToArrayMethod()
|
||||
{
|
||||
if (GenericTypeDef == null) return null;
|
||||
|
||||
if (m_genericToArray == null)
|
||||
{
|
||||
m_genericToArray = GenericTypeDef
|
||||
.MakeGenericType(new Type[] { this.EntryType })
|
||||
.GetMethod("ToArray");
|
||||
}
|
||||
return m_genericToArray;
|
||||
}
|
||||
|
||||
private PropertyInfo GetItemProperty()
|
||||
{
|
||||
if (m_itemProperty == null)
|
||||
{
|
||||
m_itemProperty = Value?.GetType().GetProperty("Item");
|
||||
}
|
||||
return m_itemProperty;
|
||||
}
|
||||
|
||||
private IEnumerable EnumerateWithReflection()
|
||||
{
|
||||
if (Value == null) return null;
|
||||
|
||||
#if CPP
|
||||
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||
{
|
||||
return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]);
|
||||
}
|
||||
else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
|
||||
{
|
||||
return CppHashSetToMono();
|
||||
}
|
||||
else
|
||||
{
|
||||
return CppIListToMono();
|
||||
}
|
||||
#else
|
||||
return Value as IEnumerable;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CPP
|
||||
private IEnumerable CppHashSetToMono()
|
||||
{
|
||||
var set = new HashSet<object>();
|
||||
|
||||
// invoke GetEnumerator
|
||||
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
set.Add(current.GetValue(enumerator));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private IList CppIListToMono()
|
||||
{
|
||||
try
|
||||
{
|
||||
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.EntryType });
|
||||
var list = (IList)Activator.CreateInstance(genericType);
|
||||
|
||||
for (int i = 0; ; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var itm = ItemProperty.GetValue(Value, new object[] { i });
|
||||
list.Add(itm);
|
||||
}
|
||||
catch { break; }
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private Type GetEntryType()
|
||||
{
|
||||
if (ValueType.IsGenericType)
|
||||
{
|
||||
var gArgs = ValueType.GetGenericArguments();
|
||||
|
||||
if (ValueType.FullName.Contains("ValueCollection"))
|
||||
{
|
||||
m_entryType = gArgs[gArgs.Length - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_entryType = gArgs[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_entryType = typeof(object);
|
||||
}
|
||||
|
||||
return m_entryType;
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null || Enumerable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheEntries();
|
||||
}
|
||||
|
||||
public void CacheEntries()
|
||||
{
|
||||
var enumerator = Enumerable.GetEnumerator();
|
||||
if (enumerator == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<CacheEnumerated>();
|
||||
int index = 0;
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var obj = enumerator.Current;
|
||||
|
||||
if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t)
|
||||
{
|
||||
#if CPP
|
||||
if (obj is Il2CppSystem.Object iObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cast = iObj.Il2CppCast(t);
|
||||
if (cast != null)
|
||||
{
|
||||
obj = cast;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
#endif
|
||||
|
||||
//ExplorerCore.Log("Caching enumeration entry " + obj.ToString() + " as " + EntryType.FullName);
|
||||
|
||||
var cached = new CacheEnumerated() { Index = index, RefIList = Value as IList, ParentEnumeration = this };
|
||||
cached.Init(obj, EntryType);
|
||||
list.Add(cached);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(null);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
m_cachedEntries = list.ToArray();
|
||||
}
|
||||
|
||||
// ============= GUI Draw =============
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_cachedEntries == null)
|
||||
{
|
||||
GUILayout.Label("m_cachedEntries is null!", new GUILayoutOption[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
int count = m_cachedEntries.Length;
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = $"[{count}] <color=#2df7b2>{EntryType.FullName}</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUIHelper.Space(5);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
Pages.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.Space(whitespace);
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
// prev/next page buttons
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left);
|
||||
}
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right);
|
||||
}
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
GUIHelper.Space(5);
|
||||
}
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
var entry = m_cachedEntries[i];
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.Space(whitespace);
|
||||
|
||||
if (entry == null || entry.IValue == null)
|
||||
{
|
||||
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", new GUILayoutOption[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
entry.IValue.DrawValue(window, window.width - (whitespace + 85));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveGameObject : InteractiveValue
|
||||
{
|
||||
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
Buttons.GameObjectButton(Value, null, false, width);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.Helpers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveSprite : InteractiveTexture2D
|
||||
{
|
||||
private Sprite refSprite;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
#if CPP
|
||||
if (Value != null && Value.Il2CppCast(typeof(Sprite)) is Sprite sprite)
|
||||
{
|
||||
refSprite = sprite;
|
||||
}
|
||||
#else
|
||||
if (Value is Sprite sprite)
|
||||
{
|
||||
refSprite = sprite;
|
||||
}
|
||||
#endif
|
||||
|
||||
base.UpdateValue();
|
||||
}
|
||||
|
||||
public override void GetTexture2D()
|
||||
{
|
||||
if (refSprite && refSprite.texture)
|
||||
{
|
||||
currentTex = refSprite.texture;
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetGUIContent()
|
||||
{
|
||||
// Check if the Sprite.textureRect is just the entire texture
|
||||
if (refSprite.textureRect != new Rect(0, 0, currentTex.width, currentTex.height))
|
||||
{
|
||||
// It's not, do a sub-copy.
|
||||
currentTex = Texture2DHelpers.Copy(refSprite.texture, refSprite.textureRect);
|
||||
}
|
||||
|
||||
base.GetGUIContent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
// This class is possibly unnecessary.
|
||||
// It's just for CacheMembers that have 'Texture' as the value type, but is actually a Texture2D.
|
||||
|
||||
public class InteractiveTexture : InteractiveTexture2D
|
||||
{
|
||||
public override void GetTexture2D()
|
||||
{
|
||||
#if CPP
|
||||
if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex)
|
||||
#else
|
||||
if (Value is Texture2D tex)
|
||||
#endif
|
||||
{
|
||||
currentTex = tex;
|
||||
texContent = new GUIContent
|
||||
{
|
||||
image = currentTex
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Config;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using Explorer.Helpers;
|
||||
#if CPP
|
||||
using Explorer.Unstrip.ImageConversion;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveTexture2D : InteractiveValue, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public Texture2D currentTex;
|
||||
public GUIContent texContent;
|
||||
|
||||
private string saveFolder = ModConfig.Instance.Default_Output_Path;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
GetTexture2D();
|
||||
}
|
||||
|
||||
public virtual void GetTexture2D()
|
||||
{
|
||||
#if CPP
|
||||
if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex)
|
||||
#else
|
||||
if (Value is Texture2D tex)
|
||||
#endif
|
||||
{
|
||||
currentTex = tex;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void GetGUIContent()
|
||||
{
|
||||
texContent = new GUIContent
|
||||
{
|
||||
image = currentTex
|
||||
};
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUIHelper.BeginVertical();
|
||||
|
||||
GUIHelper.BeginHorizontal();
|
||||
|
||||
if (currentTex && !IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
GetGUIContent();
|
||||
}
|
||||
}
|
||||
else if (currentTex)
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
base.DrawValue(window, width);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (currentTex && IsExpanded)
|
||||
{
|
||||
DrawTextureControls();
|
||||
DrawTexture();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
// Temporarily disabled in BepInEx IL2CPP.
|
||||
private void DrawTexture()
|
||||
{
|
||||
#if CPP
|
||||
#if BIE
|
||||
#else
|
||||
GUILayout.Label(texContent, new GUILayoutOption[0]);
|
||||
#endif
|
||||
#else
|
||||
GUILayout.Label(texContent, new GUILayoutOption[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void DrawTextureControls()
|
||||
{
|
||||
GUIHelper.BeginHorizontal();
|
||||
|
||||
GUILayout.Label("Save folder:", new GUILayoutOption[] { GUILayout.Width(80f) });
|
||||
saveFolder = GUIHelper.TextField(saveFolder, new GUILayoutOption[0]);
|
||||
GUIHelper.Space(10f);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("Save to PNG", new GUILayoutOption[] { GUILayout.Width(100f) }))
|
||||
{
|
||||
var name = RemoveInvalidFilenameChars(currentTex.name ?? "");
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (OwnerCacheObject is CacheMember cacheMember)
|
||||
{
|
||||
name = cacheMember.MemInfo.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "UNTITLED";
|
||||
}
|
||||
}
|
||||
|
||||
Texture2DHelpers.SaveTextureAsPNG(currentTex, saveFolder, name, false);
|
||||
|
||||
ExplorerCore.Log($@"Saved to {saveFolder}\{name}.png!");
|
||||
}
|
||||
}
|
||||
|
||||
private string RemoveInvalidFilenameChars(string s)
|
||||
{
|
||||
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||
foreach (var c in invalid)
|
||||
{
|
||||
s = s.Replace(c.ToString(), "");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveColor : InteractiveValue, IExpandHeight
|
||||
{
|
||||
private string r = "0";
|
||||
private string g = "0";
|
||||
private string b = "0";
|
||||
private string a = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null) return;
|
||||
|
||||
var color = (Color)Value;
|
||||
|
||||
r = color.r.ToString();
|
||||
g = color.g.ToString();
|
||||
b = color.b.ToString();
|
||||
a = color.a.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//var c = (Color)Value;
|
||||
//GUI.color = c;
|
||||
GUILayout.Label($"<color=#2df7b2>Color:</color> {((Color)Value).ToString()}", new GUILayoutOption[0]);
|
||||
//GUI.color = Color.white;
|
||||
|
||||
if (OwnerCacheObject.CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
r = GUIHelper.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
g = GUIHelper.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
b = GUIHelper.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
a = GUIHelper.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(r, out float fR)
|
||||
&& float.TryParse(g, out float fG)
|
||||
&& float.TryParse(b, out float fB)
|
||||
&& float.TryParse(a, out float fA))
|
||||
{
|
||||
Value = new Color(fR, fG, fB, fA);
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveEnum : InteractiveValue
|
||||
{
|
||||
internal static Dictionary<Type, string[]> EnumNamesInternalCache = new Dictionary<Type, string[]>();
|
||||
|
||||
// public Type EnumType;
|
||||
public string[] EnumNames = new string[0];
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (ValueType == null && Value != null)
|
||||
{
|
||||
ValueType = Value.GetType();
|
||||
}
|
||||
|
||||
if (ValueType != null)
|
||||
{
|
||||
GetNames();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OwnerCacheObject is CacheMember cacheMember)
|
||||
{
|
||||
cacheMember.ReflectionException = "Unknown, could not get Enum names.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void GetNames()
|
||||
{
|
||||
if (!EnumNamesInternalCache.ContainsKey(ValueType))
|
||||
{
|
||||
// using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags)
|
||||
var values = Enum.GetValues(ValueType);
|
||||
|
||||
var set = new HashSet<string>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
var v = value.ToString();
|
||||
if (set.Contains(v)) continue;
|
||||
set.Add(v);
|
||||
}
|
||||
|
||||
EnumNamesInternalCache.Add(ValueType, set.ToArray());
|
||||
}
|
||||
|
||||
EnumNames = EnumNamesInternalCache[ValueType];
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(-1);
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(1);
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(Value.ToString() + $"<color={Syntax.StructGreen}><i> ({ValueType})</i></color>", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
public void SetEnum(int change)
|
||||
{
|
||||
var names = EnumNames.ToList();
|
||||
|
||||
int newindex = names.IndexOf(Value.ToString()) + change;
|
||||
|
||||
if (newindex >= 0 && newindex < names.Count)
|
||||
{
|
||||
Value = Enum.Parse(ValueType, EnumNames[newindex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveFlags : InteractiveEnum, IExpandHeight
|
||||
{
|
||||
public bool[] m_enabledFlags = new bool[0];
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var enabledNames = Value.ToString().Split(',').Select(it => it.Trim());
|
||||
|
||||
m_enabledFlags = new bool[EnumNames.Length];
|
||||
|
||||
for (int i = 0; i < EnumNames.Length; i++)
|
||||
{
|
||||
m_enabledFlags[i] = enabledNames.Contains(EnumNames[i]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(Value.ToString() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", new GUILayoutOption[0]);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
for (int i = 0; i < EnumNames.Length; i++)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
|
||||
m_enabledFlags[i] = GUILayout.Toggle(m_enabledFlags[i], EnumNames[i], new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetFlagsFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFlagsFromInput()
|
||||
{
|
||||
string val = "";
|
||||
for (int i = 0; i < EnumNames.Length; i++)
|
||||
{
|
||||
if (m_enabledFlags[i])
|
||||
{
|
||||
if (val != "") val += ", ";
|
||||
val += EnumNames[i];
|
||||
}
|
||||
}
|
||||
Value = Enum.Parse(ValueType, val);
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Config;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractivePrimitive : InteractiveValue
|
||||
{
|
||||
private string m_valueToString;
|
||||
private bool m_isBool;
|
||||
private bool m_isString;
|
||||
|
||||
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
|
||||
private MethodInfo m_parseMethod;
|
||||
|
||||
private bool m_canBitwiseOperate;
|
||||
private bool m_inBitwiseMode;
|
||||
private string m_bitwiseOperatorInput = "0";
|
||||
private string m_binaryInput;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = Value?.GetType();
|
||||
|
||||
// has to be a string at this point
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
if (ValueType == typeof(string))
|
||||
{
|
||||
m_isString = true;
|
||||
}
|
||||
else if (ValueType == typeof(bool))
|
||||
{
|
||||
m_isBool = true;
|
||||
}
|
||||
|
||||
m_canBitwiseOperate = typeof(int).IsAssignableFrom(ValueType);
|
||||
|
||||
UpdateValue();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
RefreshToString();
|
||||
}
|
||||
|
||||
private void RefreshToString()
|
||||
{
|
||||
m_valueToString = Value?.ToString();
|
||||
|
||||
if (m_canBitwiseOperate && Value != null)
|
||||
{
|
||||
var _int = (int)Value;
|
||||
m_binaryInput = Convert.ToString(_int, toBase: 2);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_isBool)
|
||||
{
|
||||
var b = (bool)Value;
|
||||
var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
|
||||
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
Value = GUILayout.Toggle(b, label, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
DrawApplyButton();
|
||||
//if (b != (bool)Value)
|
||||
//{
|
||||
// Value = b;
|
||||
// OwnerCacheObject.SetValue();
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(label, new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// all other non-bool values use TextField
|
||||
|
||||
GUIHelper.BeginVertical(new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
m_valueToString = GUIHelper.TextArea(m_valueToString, new GUILayoutOption[] { GUIHelper.ExpandWidth(true) });
|
||||
|
||||
DrawApplyButton();
|
||||
|
||||
if (ModConfig.Instance.Bitwise_Support && m_canBitwiseOperate)
|
||||
{
|
||||
m_inBitwiseMode = GUILayout.Toggle(m_inBitwiseMode, "Bitwise?", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
GUIHelper.Space(10);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (ModConfig.Instance.Bitwise_Support && m_inBitwiseMode)
|
||||
{
|
||||
DrawBitwise();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawApplyButton()
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
if (m_isBool)
|
||||
{
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBitwise()
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("RHS:", new GUILayoutOption[] { GUILayout.Width(35) });
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
if (GUILayout.Button("~", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = ~bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("<<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = (int)Value << bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button(">>", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = (int)Value >> bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("|", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = (int)Value | bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("&", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = (int)Value & bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
|
||||
{
|
||||
Value = (int)Value ^ bit;
|
||||
RefreshToString();
|
||||
}
|
||||
}
|
||||
|
||||
m_bitwiseOperatorInput = GUIHelper.TextField(m_bitwiseOperatorInput, new GUILayoutOption[] { GUILayout.Width(55) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"<color=cyan>Binary:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
m_binaryInput = GUIHelper.TextField(m_binaryInput, new GUILayoutOption[0]);
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (GUILayout.Button("Apply", new GUILayoutOption[0]))
|
||||
{
|
||||
SetValueFromBinaryInput();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void SetValueFromInput()
|
||||
{
|
||||
if (m_isString)
|
||||
{
|
||||
Value = m_valueToString;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Value = ParseMethod.Invoke(null, new object[] { m_valueToString });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
OwnerCacheObject.SetValue();
|
||||
RefreshToString();
|
||||
}
|
||||
|
||||
private void SetValueFromBinaryInput()
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = typeof(Convert).GetMethod($"To{ValueType.Name}", new Type[] { typeof(string), typeof(int) });
|
||||
Value = method.Invoke(null, new object[] { m_binaryInput, 2 });
|
||||
|
||||
OwnerCacheObject.SetValue();
|
||||
RefreshToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception setting value: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveQuaternion : InteractiveValue, IExpandHeight
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null) return;
|
||||
|
||||
var euler = ((Quaternion)Value).eulerAngles;
|
||||
|
||||
x = euler.x.ToString();
|
||||
y = euler.y.ToString();
|
||||
z = euler.z.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", new GUILayoutOption[0]);
|
||||
|
||||
if (OwnerCacheObject.CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUIHelper.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ))
|
||||
{
|
||||
Value = Quaternion.Euler(new Vector3(fX, fY, fZ));
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveRect : InteractiveValue, IExpandHeight
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string w = "0";
|
||||
private string h = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null) return;
|
||||
|
||||
var rect = (Rect)Value;
|
||||
|
||||
x = rect.x.ToString();
|
||||
y = rect.y.ToString();
|
||||
w = rect.width.ToString();
|
||||
h = rect.height.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Rect</color>: {((Rect)Value).ToString()}", new GUILayoutOption[0]);
|
||||
|
||||
if (OwnerCacheObject.CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUIHelper.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
h = GUIHelper.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(w, out float fW)
|
||||
&& float.TryParse(h, out float fH))
|
||||
{
|
||||
Value = new Rect(fX, fY, fW, fH);
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI
|
||||
{
|
||||
public class InteractiveVector : InteractiveValue, IExpandHeight
|
||||
{
|
||||
public int VectorSize = 2;
|
||||
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
private string w = "0";
|
||||
|
||||
//private MethodInfo m_toStringMethod;
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (ValueType == null && Value != null)
|
||||
{
|
||||
ValueType = Value.GetType();
|
||||
}
|
||||
|
||||
if (ValueType == typeof(Vector2))
|
||||
{
|
||||
VectorSize = 2;
|
||||
//m_toStringMethod = typeof(Vector2).GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
else if (ValueType == typeof(Vector3))
|
||||
{
|
||||
VectorSize = 3;
|
||||
//m_toStringMethod = typeof(Vector3).GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorSize = 4;
|
||||
//m_toStringMethod = typeof(Vector4).GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value is Vector2 vec2)
|
||||
{
|
||||
x = vec2.x.ToString();
|
||||
y = vec2.y.ToString();
|
||||
}
|
||||
else if (Value is Vector3 vec3)
|
||||
{
|
||||
x = vec3.x.ToString();
|
||||
y = vec3.y.ToString();
|
||||
z = vec3.z.ToString();
|
||||
}
|
||||
else if (Value is Vector4 vec4)
|
||||
{
|
||||
x = vec4.x.ToString();
|
||||
y = vec4.y.ToString();
|
||||
z = vec4.z.ToString();
|
||||
w = vec4.w.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (OwnerCacheObject.CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Vector{VectorSize}</color>: {(string)ToStringMethod.Invoke(Value, new object[0])}", new GUILayoutOption[0]);
|
||||
|
||||
if (OwnerCacheObject.CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
// always draw x and y
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (VectorSize > 2)
|
||||
{
|
||||
// draw z
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUIHelper.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
if (VectorSize > 3)
|
||||
{
|
||||
// draw w
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUIHelper.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// draw set value button
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUIHelper.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ)
|
||||
&& float.TryParse(w, out float fW))
|
||||
{
|
||||
object vector = null;
|
||||
|
||||
switch (VectorSize)
|
||||
{
|
||||
case 2: vector = new Vector2(fX, fY); break;
|
||||
case 3: vector = new Vector3(fX, fY, fZ); break;
|
||||
case 4: vector = new Vector4(fX, fY, fZ, fW); break;
|
||||
}
|
||||
|
||||
if (vector != null)
|
||||
{
|
||||
Value = vector;
|
||||
OwnerCacheObject.SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public abstract class BaseMainMenuPage
|
||||
{
|
||||
public virtual string Name { get; }
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
public abstract void Init();
|
||||
|
||||
public abstract void DrawWindow();
|
||||
|
||||
public abstract void Update();
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public struct AutoComplete
|
||||
{
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Contexts Context;
|
||||
|
||||
public Color TextColor => Context == Contexts.Namespace
|
||||
? Color.gray
|
||||
: Color.white;
|
||||
|
||||
public AutoComplete(string addition, string prefix, Contexts type)
|
||||
{
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
Context = type;
|
||||
}
|
||||
|
||||
public enum Contexts
|
||||
{
|
||||
Namespace,
|
||||
Other
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCompleteHelpers
|
||||
{
|
||||
public static HashSet<string> Namespaces => _namespaces ?? GetNamespaces();
|
||||
private static HashSet<string> _namespaces;
|
||||
|
||||
private static HashSet<string> GetNamespaces()
|
||||
{
|
||||
var set = new HashSet<string>(
|
||||
AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(GetTypes)
|
||||
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||
.Select(x => x.Namespace));
|
||||
|
||||
return _namespaces = set;
|
||||
|
||||
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Explorer.UI.Inspectors;
|
||||
using Mono.CSharp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
public static object CurrentTarget()
|
||||
{
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
ExplorerCore.Log("CurrentTarget() is only a valid method when in Tab View mode!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return WindowManager.Windows.ElementAt(TabViewWindow.Instance.TargetTabID).Target;
|
||||
}
|
||||
|
||||
public static object[] AllTargets()
|
||||
{
|
||||
var list = new List<object>();
|
||||
foreach (var window in WindowManager.Windows)
|
||||
{
|
||||
if (window.Target != null)
|
||||
{
|
||||
list.Add(window.Target);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static void Inspect(object obj)
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
WindowManager.InspectStaticReflection(type);
|
||||
}
|
||||
|
||||
public static void Help()
|
||||
{
|
||||
ExplorerCore.Log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
||||
ExplorerCore.Log(" C# Console Help ");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("The following helper methods are available:");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Log(object message)");
|
||||
ExplorerCore.Log(" prints a message to the console window and debug log");
|
||||
ExplorerCore.Log(" usage: Log(\"hello world\");");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("object CurrentTarget()");
|
||||
ExplorerCore.Log(" returns the target object of the current tab (in tab view mode only)");
|
||||
ExplorerCore.Log(" usage: var target = CurrentTarget();");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("object[] AllTargets()");
|
||||
ExplorerCore.Log(" returns an object[] array containing all currently inspected objects");
|
||||
ExplorerCore.Log(" usage: var targets = AllTargets();");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Inspect(object obj)");
|
||||
ExplorerCore.Log(" inspects the provided object in a new window.");
|
||||
ExplorerCore.Log(" usage: Inspect(Camera.main);");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Inspect(Type type)");
|
||||
ExplorerCore.Log(" attempts to inspect the provided type with static-only reflection.");
|
||||
ExplorerCore.Log(" usage: Inspect(typeof(Camera));");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,371 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Mono.CSharp;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
using TextEditor = Explorer.Unstrip.IMGUI.TextEditorUnstrip;
|
||||
using Explorer.Unstrip.IMGUI;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ConsolePage : BaseMainMenuPage
|
||||
{
|
||||
public static ConsolePage Instance { get; private set; }
|
||||
|
||||
public override string Name { get => "C# Console"; }
|
||||
|
||||
private ScriptEvaluator m_evaluator;
|
||||
|
||||
public const string INPUT_CONTROL_NAME = "consoleInput";
|
||||
private string m_input = "";
|
||||
private string m_prevInput = "";
|
||||
private string m_usingInput = "";
|
||||
|
||||
public static List<AutoComplete> AutoCompletes = new List<AutoComplete>();
|
||||
public static List<string> UsingDirectives;
|
||||
|
||||
private Vector2 inputAreaScroll;
|
||||
private Vector2 autocompleteScroll;
|
||||
public static TextEditor textEditor;
|
||||
private bool shouldRefocus;
|
||||
|
||||
public static GUIStyle AutocompleteStyle => autocompleteStyle ?? GetAutocompleteStyle();
|
||||
private static GUIStyle autocompleteStyle;
|
||||
|
||||
public static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
try
|
||||
{
|
||||
m_input = @"// For a list of helper methods, execute the 'Help();' method.
|
||||
// Enable the Console Window with your Mod Loader to see log output.
|
||||
|
||||
Help();";
|
||||
ResetConsole();
|
||||
|
||||
foreach (var use in DefaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}");
|
||||
MainMenu.SetCurrentPage(0);
|
||||
MainMenu.Pages.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
public string AsmToUsing(string asm, bool richtext = false)
|
||||
{
|
||||
if (richtext)
|
||||
{
|
||||
return $"<color=#569cd6>using</color> {asm};";
|
||||
}
|
||||
return $"using {asm};";
|
||||
}
|
||||
|
||||
public void AddUsing(string asm)
|
||||
{
|
||||
if (!UsingDirectives.Contains(asm))
|
||||
{
|
||||
UsingDirectives.Add(asm);
|
||||
Evaluate(AsmToUsing(asm), true);
|
||||
}
|
||||
}
|
||||
|
||||
public object Evaluate(string str, bool suppressWarning = false)
|
||||
{
|
||||
object ret = VoidType.Value;
|
||||
|
||||
m_evaluator.Compile(str, out var compiled);
|
||||
|
||||
try
|
||||
{
|
||||
if (compiled == null)
|
||||
{
|
||||
throw new Exception("Mono.Csharp Service was unable to compile the code provided.");
|
||||
}
|
||||
|
||||
compiled.Invoke(ref ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
{
|
||||
ExplorerCore.LogWarning(e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ResetConsole()
|
||||
{
|
||||
if (m_evaluator != null)
|
||||
{
|
||||
m_evaluator.Dispose();
|
||||
}
|
||||
|
||||
m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) };
|
||||
|
||||
UsingDirectives = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
GUILayout.Label("<b><size=15><color=cyan>C# Console</color></size></b>", new GUILayoutOption[0]);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
// SCRIPT INPUT
|
||||
|
||||
GUILayout.Label("Enter code here as though it is a method body:", new GUILayoutOption[0]);
|
||||
|
||||
inputAreaScroll = GUIHelper.BeginScrollView(
|
||||
inputAreaScroll,
|
||||
new GUILayoutOption[] { GUILayout.Height(250), GUIHelper.ExpandHeight(true) }
|
||||
);
|
||||
|
||||
GUI.SetNextControlName(INPUT_CONTROL_NAME);
|
||||
m_input = GUIHelper.TextArea(m_input, new GUILayoutOption[] { GUIHelper.ExpandHeight(true) });
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
|
||||
// EXECUTE BUTTON
|
||||
|
||||
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", new GUILayoutOption[0]))
|
||||
{
|
||||
try
|
||||
{
|
||||
m_input = m_input.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(m_input))
|
||||
{
|
||||
Evaluate(m_input);
|
||||
|
||||
//var result = Evaluate(m_input);
|
||||
|
||||
//if (result != null && !Equals(result, VoidType.Value))
|
||||
//{
|
||||
// ExplorerCore.Log("[Console Output]\r\n" + result.ToString());
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
// SUGGESTIONS
|
||||
if (AutoCompletes.Count > 0)
|
||||
{
|
||||
autocompleteScroll = GUIHelper.BeginScrollView(autocompleteScroll, new GUILayoutOption[] { GUILayout.Height(150) });
|
||||
|
||||
var origSkin = GUI.skin.button;
|
||||
GUI.skin.button = AutocompleteStyle;
|
||||
|
||||
foreach (var autocomplete in AutoCompletes)
|
||||
{
|
||||
AutocompleteStyle.normal.textColor = autocomplete.TextColor;
|
||||
if (GUILayout.Button(autocomplete.Full, new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 50) }))
|
||||
{
|
||||
UseAutocomplete(autocomplete.Addition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button = origSkin;
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
}
|
||||
|
||||
if (shouldRefocus)
|
||||
{
|
||||
GUI.FocusControl(INPUT_CONTROL_NAME);
|
||||
shouldRefocus = false;
|
||||
}
|
||||
|
||||
// USING DIRECTIVES
|
||||
|
||||
GUILayout.Label("<b>Using directives:</b>", new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
|
||||
m_usingInput = GUIHelper.TextField(m_usingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
AddUsing(m_usingInput);
|
||||
}
|
||||
if (GUILayout.Button("<b><color=red>Clear All</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
ResetConsole();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
foreach (var asm in UsingDirectives)
|
||||
{
|
||||
GUILayout.Label(AsmToUsing(asm, true), new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
CheckAutocomplete();
|
||||
}
|
||||
|
||||
private void CheckAutocomplete()
|
||||
{
|
||||
// Temporary disabling this check in BepInEx Il2Cpp.
|
||||
#if BIE
|
||||
#if CPP
|
||||
#else
|
||||
if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME)
|
||||
return;
|
||||
#endif
|
||||
#else
|
||||
if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
//textEditor = GUIUtility.GetStateObject(Il2CppType.Of<TextEditor>(), GUIUtility.keyboardControl).TryCast<TextEditor>();
|
||||
textEditor = (TextEditor)GUIUtilityUnstrip.GetMonoStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
|
||||
#else
|
||||
textEditor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
|
||||
#endif
|
||||
|
||||
var input = m_input;
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
var splitChars = new[] { ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&' };
|
||||
|
||||
// Credit ManlyMarco
|
||||
// Separate input into parts, grab only the part with cursor in it
|
||||
var cursorIndex = textEditor.cursorIndex;
|
||||
var start = cursorIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, cursorIndex - 1) + 1;
|
||||
var end = cursorIndex <= 0 ? input.Length : input.IndexOfAny(splitChars, cursorIndex - 1);
|
||||
if (end < 0 || end < start) end = input.Length;
|
||||
input = input.Substring(start, end - start);
|
||||
}
|
||||
catch (ArgumentException) { }
|
||||
|
||||
if (!string.IsNullOrEmpty(input) && input != m_prevInput)
|
||||
{
|
||||
GetAutocompletes(input);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearAutocompletes();
|
||||
}
|
||||
|
||||
m_prevInput = input;
|
||||
}
|
||||
|
||||
private void ClearAutocompletes()
|
||||
{
|
||||
if (AutoCompletes.Any())
|
||||
{
|
||||
AutoCompletes.Clear();
|
||||
shouldRefocus = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UseAutocomplete(string suggestion)
|
||||
{
|
||||
int cursorIndex = textEditor.cursorIndex;
|
||||
m_input = m_input.Insert(cursorIndex, suggestion);
|
||||
|
||||
ClearAutocompletes();
|
||||
shouldRefocus = true;
|
||||
}
|
||||
|
||||
private void GetAutocompletes(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
//ExplorerCore.Log("Fetching suggestions for input " + input);
|
||||
|
||||
// Credit ManylMarco
|
||||
AutoCompletes.Clear();
|
||||
var completions = m_evaluator.GetCompletions(input, out string prefix);
|
||||
if (completions != null)
|
||||
{
|
||||
if (prefix == null)
|
||||
prefix = input;
|
||||
|
||||
AutoCompletes.AddRange(completions
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.Select(x => new AutoComplete(x, prefix, AutoComplete.Contexts.Other))
|
||||
);
|
||||
}
|
||||
|
||||
var trimmed = input.Trim();
|
||||
if (trimmed.StartsWith("using"))
|
||||
trimmed = trimmed.Remove(0, 5).Trim();
|
||||
|
||||
var namespaces = AutoCompleteHelpers.Namespaces
|
||||
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||
.Select(x => new AutoComplete(
|
||||
x.Substring(trimmed.Length),
|
||||
x.Substring(0, trimmed.Length),
|
||||
AutoComplete.Contexts.Namespace));
|
||||
|
||||
AutoCompletes.AddRange(namespaces);
|
||||
|
||||
shouldRefocus = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log("C# Console error:\r\n" + ex);
|
||||
ClearAutocompletes();
|
||||
}
|
||||
}
|
||||
|
||||
// Credit ManlyMarco
|
||||
private static GUIStyle GetAutocompleteStyle()
|
||||
{
|
||||
var style = new GUIStyle
|
||||
{
|
||||
border = new RectOffset(),
|
||||
margin = new RectOffset(),
|
||||
padding = new RectOffset(),
|
||||
hover = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
normal = { background = null },
|
||||
focused = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
active = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
alignment = TextAnchor.MiddleLeft
|
||||
};
|
||||
|
||||
return autocompleteStyle = style;
|
||||
}
|
||||
|
||||
private class VoidType
|
||||
{
|
||||
public static readonly VoidType Value = new VoidType();
|
||||
private VoidType() { }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.Config;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class OptionsPage : BaseMainMenuPage
|
||||
{
|
||||
public override string Name => "Options";
|
||||
|
||||
public string toggleKeyInputString = "";
|
||||
public Vector2 defaultSizeInputVector;
|
||||
public int defaultPageLimit;
|
||||
public bool bitwiseSupport;
|
||||
public bool tabView;
|
||||
public string defaultOutputPath;
|
||||
|
||||
private CacheObjectBase toggleKeyInput;
|
||||
private CacheObjectBase defaultSizeInput;
|
||||
private CacheObjectBase defaultPageLimitInput;
|
||||
private CacheObjectBase bitwiseSupportInput;
|
||||
private CacheObjectBase tabViewInput;
|
||||
private CacheObjectBase defaultOutputPathInput;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
toggleKeyInputString = ModConfig.Instance.Main_Menu_Toggle.ToString();
|
||||
toggleKeyInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("toggleKeyInputString"), this);
|
||||
|
||||
defaultSizeInputVector = ModConfig.Instance.Default_Window_Size;
|
||||
defaultSizeInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultSizeInputVector"), this);
|
||||
|
||||
defaultPageLimit = ModConfig.Instance.Default_Page_Limit;
|
||||
defaultPageLimitInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultPageLimit"), this);
|
||||
|
||||
bitwiseSupport = ModConfig.Instance.Bitwise_Support;
|
||||
bitwiseSupportInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("bitwiseSupport"), this);
|
||||
|
||||
tabView = ModConfig.Instance.Tab_View;
|
||||
tabViewInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("tabView"), this);
|
||||
|
||||
defaultOutputPath = ModConfig.Instance.Default_Output_Path;
|
||||
defaultOutputPathInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultOutputPath"), this);
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<color=orange><size=16><b>Options</b></size></color>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Menu Toggle Key:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
toggleKeyInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
UIStyles.HorizontalLine(Color.black, true);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Default Window Size:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
defaultSizeInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
UIStyles.HorizontalLine(Color.black, true);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Default Items per Page:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
defaultPageLimitInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
UIStyles.HorizontalLine(Color.black, true);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Enable Bitwise Editing:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
bitwiseSupportInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
UIStyles.HorizontalLine(Color.black, true);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Enable Tab View:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
tabViewInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
UIStyles.HorizontalLine(Color.black, true);
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Default Output Path:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
defaultOutputPathInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("<color=lime><b>Apply and Save</b></color>", new GUILayoutOption[0]))
|
||||
{
|
||||
ApplyAndSave();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUIHelper.Space(10f);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<color=orange><size=16><b>Other</b></size></color>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("Inspect Test Class", new GUILayoutOption[0]))
|
||||
{
|
||||
WindowManager.InspectObject(Tests.TestClass.Instance, out bool _);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ApplyAndSave()
|
||||
{
|
||||
if (Enum.Parse(typeof(KeyCode), toggleKeyInputString) is KeyCode key)
|
||||
{
|
||||
ModConfig.Instance.Main_Menu_Toggle = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse '{toggleKeyInputString}' to KeyCode!");
|
||||
}
|
||||
|
||||
ModConfig.Instance.Default_Window_Size = defaultSizeInputVector;
|
||||
ModConfig.Instance.Default_Page_Limit = defaultPageLimit;
|
||||
ModConfig.Instance.Bitwise_Support = bitwiseSupport;
|
||||
|
||||
ModConfig.Instance.Tab_View = tabView;
|
||||
WindowManager.TabView = tabView;
|
||||
|
||||
ModConfig.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,375 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Helpers;
|
||||
using Explorer.Unstrip.Resources;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ScenePage : BaseMainMenuPage
|
||||
{
|
||||
public static ScenePage Instance;
|
||||
|
||||
public override string Name { get => "Scenes"; }
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
private const int PASSIVE_UPDATE_INTERVAL = 1;
|
||||
|
||||
private static string m_currentScene = "";
|
||||
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private readonly List<CacheObjectBase> m_objectList = new List<CacheObjectBase>();
|
||||
|
||||
// search bar
|
||||
private bool m_searching = false;
|
||||
private string m_searchInput = "";
|
||||
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
|
||||
public void SetTransformTarget(Transform t)
|
||||
{
|
||||
m_currentTransform = t;
|
||||
|
||||
if (m_searching)
|
||||
CancelSearch();
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
public void TraverseUp()
|
||||
{
|
||||
if (m_currentTransform.parent != null)
|
||||
{
|
||||
SetTransformTarget(m_currentTransform.parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Search()
|
||||
{
|
||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||
m_searching = true;
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
}
|
||||
|
||||
public void CancelSearch()
|
||||
{
|
||||
m_searching = false;
|
||||
}
|
||||
|
||||
public List<CacheObjectBase> SearchSceneObjects(string _search)
|
||||
{
|
||||
var matches = new List<CacheObjectBase>();
|
||||
|
||||
foreach (var obj in ResourcesUnstrip.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
|
||||
{
|
||||
#if CPP
|
||||
var go = obj.TryCast<GameObject>();
|
||||
#else
|
||||
var go = obj as GameObject;
|
||||
#endif
|
||||
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
|
||||
{
|
||||
matches.Add(CacheFactory.GetCacheObject(go));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_searching) return;
|
||||
|
||||
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
private void Update_Impl()
|
||||
{
|
||||
List<Transform> allTransforms = new List<Transform>();
|
||||
|
||||
// get current list of all transforms (either scene root or our current transform children)
|
||||
if (m_currentTransform)
|
||||
{
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
if (scene.name == m_currentScene)
|
||||
{
|
||||
var rootObjects =
|
||||
#if CPP
|
||||
Unstrip.Scenes.SceneUnstrip.GetRootGameObjects(scene)
|
||||
.Select(it => it.transform);
|
||||
#else
|
||||
scene.GetRootGameObjects().Select(it => it.transform);
|
||||
#endif
|
||||
allTransforms.AddRange(rootObjects);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pages.ItemCount = allTransforms.Count;
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
// sort by childcount
|
||||
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
|
||||
m_objectList.Clear();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
|
||||
{
|
||||
var child = allTransforms[i];
|
||||
m_objectList.Add(CacheFactory.GetCacheObject(child));
|
||||
}
|
||||
}
|
||||
|
||||
// --------- GUI Draw Function --------- //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
DrawHeaderArea();
|
||||
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
DrawPageButtons();
|
||||
|
||||
if (!m_searching)
|
||||
{
|
||||
DrawGameObjectList();
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSearchResultsList();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!e.Message.Contains("in a group with only"))
|
||||
{
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHeaderArea()
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
SceneChangeButtons();
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUIHelper.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
|
||||
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
|
||||
m_searchInput = GUIHelper.TextField(m_searchInput, new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.Space(5);
|
||||
}
|
||||
|
||||
private void SceneChangeButtons()
|
||||
{
|
||||
var scenes = new List<Scene>();
|
||||
var names = new List<string>();
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
names.Add(scene.name);
|
||||
scenes.Add(scene);
|
||||
}
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = names.IndexOf(m_currentScene);
|
||||
index += changeWanted;
|
||||
|
||||
if (index >= 0 && index < SceneManager.sceneCount)
|
||||
{
|
||||
m_currentScene = scenes[index].name;
|
||||
Update_Impl();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPageButtons()
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
private void DrawGameObjectList()
|
||||
{
|
||||
if (m_currentTransform != null)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
TraverseUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=cyan>" + m_currentTransform.GetGameObjectPath() + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 187f) });
|
||||
}
|
||||
|
||||
Buttons.InspectButton(m_currentTransform);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Scene Root GameObjects:", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < m_objectList.Count; i++)
|
||||
{
|
||||
var obj = m_objectList[i];
|
||||
|
||||
if (obj == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var go = obj.IValue.Value as GameObject ?? (obj.IValue.Value as Transform)?.gameObject;
|
||||
|
||||
if (!go)
|
||||
{
|
||||
string label = "<color=red><i>null";
|
||||
|
||||
if (go != null)
|
||||
{
|
||||
label += " (Destroyed)";
|
||||
}
|
||||
|
||||
label += "</i></color>";
|
||||
GUILayout.Label(label, new GUILayoutOption[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buttons.GameObjectButton(go, SetTransformTarget, true, MainMenu.MainRect.width - 170f);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSearchResultsList()
|
||||
{
|
||||
if (GUILayout.Button("<- Cancel Search", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
GUILayout.Label("Search Results:", new GUILayoutOption[0]);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < m_searchResults.Count; i++)
|
||||
{
|
||||
var obj = m_searchResults[i].IValue.Value as GameObject;
|
||||
|
||||
if (obj)
|
||||
{
|
||||
Buttons.GameObjectButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,544 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
using Explorer.Helpers;
|
||||
using Explorer.Unstrip.Resources;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class SearchPage : BaseMainMenuPage
|
||||
{
|
||||
public static SearchPage Instance;
|
||||
|
||||
public override string Name { get => "Search"; }
|
||||
|
||||
private int MaxSearchResults = 5000;
|
||||
|
||||
private string m_searchInput = "";
|
||||
private string m_typeInput = "";
|
||||
|
||||
//private bool m_cachingResults;
|
||||
|
||||
private Vector2 resultsScroll = Vector2.zero;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
public SceneFilter SceneMode = SceneFilter.Any;
|
||||
public TypeFilter TypeMode = TypeFilter.Object;
|
||||
|
||||
public enum SceneFilter
|
||||
{
|
||||
Any,
|
||||
This,
|
||||
DontDestroy,
|
||||
None
|
||||
}
|
||||
|
||||
public enum TypeFilter
|
||||
{
|
||||
Object,
|
||||
GameObject,
|
||||
Component,
|
||||
Custom
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_searchResults.Clear();
|
||||
Pages.PageOffset = 0;
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
private void CacheResults(IEnumerable results, bool isStaticClasses = false)
|
||||
{
|
||||
//m_cachingResults = true;
|
||||
|
||||
m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
foreach (var obj in results)
|
||||
{
|
||||
var toCache = obj;
|
||||
|
||||
#if CPP
|
||||
if (toCache is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var type = ReflectionHelpers.GetActualType(ilObject);
|
||||
ilObject = (Il2CppSystem.Object)ilObject.Il2CppCast(type);
|
||||
|
||||
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
||||
}
|
||||
#else
|
||||
if (toCache is GameObject || toCache is Transform)
|
||||
{
|
||||
toCache = toCache as GameObject ?? (toCache as Transform).gameObject;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (toCache is TextAsset textAsset)
|
||||
{
|
||||
if (string.IsNullOrEmpty(textAsset.text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var cache = CacheFactory.GetCacheObject(toCache);
|
||||
cache.IsStaticClassSearchResult = isStaticClasses;
|
||||
m_searchResults.Add(cache);
|
||||
}
|
||||
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
Pages.PageOffset = 0;
|
||||
|
||||
results = null;
|
||||
|
||||
//m_cachingResults = false;
|
||||
}
|
||||
|
||||
private void Search()
|
||||
{
|
||||
//if (m_cachingResults)
|
||||
//{
|
||||
// ExplorerCore.Log("Cannot search now, we are already caching results...");
|
||||
// return;
|
||||
//}
|
||||
|
||||
Pages.PageOffset = 0;
|
||||
|
||||
#if CPP
|
||||
Il2CppSystem.Type searchType = null;
|
||||
|
||||
#else
|
||||
Type searchType = null;
|
||||
#endif
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
if (ReflectionHelpers.GetTypeByName(m_typeInput) is Type t)
|
||||
{
|
||||
#if CPP
|
||||
searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
|
||||
#else
|
||||
searchType = t;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.Log($"Could not find a Type by the name of '{m_typeInput}'!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
searchType = ReflectionHelpers.ObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
searchType = ReflectionHelpers.GameObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
searchType = ReflectionHelpers.ComponentType;
|
||||
}
|
||||
|
||||
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
|
||||
{
|
||||
if (searchType != null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var matches = new List<object>();
|
||||
|
||||
var allObjectsOfType = ResourcesUnstrip.FindObjectsOfTypeAll(searchType);
|
||||
|
||||
//ExplorerCore.Log("Found count: " + allObjectsOfType.Length);
|
||||
|
||||
int i = 0;
|
||||
foreach (var obj in allObjectsOfType)
|
||||
{
|
||||
if (i >= MaxSearchResults) break;
|
||||
|
||||
if (m_searchInput != "" && !obj.name.ToLower().Contains(m_searchInput.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
|
||||
#if CPP
|
||||
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
#else
|
||||
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetType()))
|
||||
#endif
|
||||
{
|
||||
// Transforms shouldn't really be counted as Components, skip them.
|
||||
// They're more akin to GameObjects.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SceneMode != SceneFilter.Any && !FilterScene(obj, this.SceneMode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matches.Contains(obj))
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
CacheResults(matches);
|
||||
}
|
||||
|
||||
public static bool FilterScene(object obj, SceneFilter filter)
|
||||
{
|
||||
GameObject go = null;
|
||||
#if CPP
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
go = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Component>().gameObject;
|
||||
}
|
||||
#else
|
||||
if (obj is GameObject || obj is Component)
|
||||
{
|
||||
go = (obj as GameObject) ?? (obj as Component).gameObject;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!go)
|
||||
{
|
||||
// object is not on a GameObject, cannot perform scene filter operation.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter == SceneFilter.None)
|
||||
{
|
||||
return string.IsNullOrEmpty(go.scene.name);
|
||||
}
|
||||
else if (filter == SceneFilter.This)
|
||||
{
|
||||
return go.scene.name == UnityHelpers.ActiveSceneName;
|
||||
}
|
||||
else if (filter == SceneFilter.DontDestroy)
|
||||
{
|
||||
return go.scene.name == "DontDestroyOnLoad";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool FilterName(string name)
|
||||
{
|
||||
// Don't really want these instances.
|
||||
return !name.StartsWith("Mono")
|
||||
&& !name.StartsWith("System")
|
||||
&& !name.StartsWith("Il2CppSystem")
|
||||
&& !name.StartsWith("Iced");
|
||||
}
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetStaticInstances()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(t => t.TryGetTypes())
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
var flatFlags = flags | BindingFlags.FlattenHierarchy;
|
||||
|
||||
foreach (var type in query)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
var pi = type.GetProperty("Instance", flags);
|
||||
|
||||
if (pi == null)
|
||||
{
|
||||
pi = type.GetProperty("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
obj = pi.GetValue(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fi = type.GetField("Instance", flags);
|
||||
|
||||
if (fi == null)
|
||||
{
|
||||
fi = type.GetField("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (fi != null)
|
||||
{
|
||||
obj = fi.GetValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
var t = ReflectionHelpers.GetActualType(obj);
|
||||
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable GetStaticClasses()
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
var input = m_searchInput?.ToLower() ?? "";
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
{
|
||||
if (!type.IsAbstract || !type.IsSealed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
var typename = type.FullName.ToLower();
|
||||
|
||||
if (!typename.Contains(input))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(type);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// =========== GUI DRAW ============= //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// helpers
|
||||
GUIHelper.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
|
||||
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||
{
|
||||
CacheResults(GetStaticInstances());
|
||||
}
|
||||
if (GUILayout.Button("Find Static Classes", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||
{
|
||||
CacheResults(GetStaticClasses(), true);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// search box
|
||||
SearchBox();
|
||||
|
||||
// results
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Results </color></b>" + " (" + m_searchResults.Count + ")", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
int count = m_searchResults.Count;
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
// prev/next page buttons
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.resultsScroll);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.resultsScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
resultsScroll = GUIHelper.BeginScrollView(resultsScroll);
|
||||
|
||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
if (i >= m_searchResults.Count) break;
|
||||
|
||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!e.Message.Contains("in a group with only"))
|
||||
{
|
||||
ExplorerCore.Log("Exception drawing search results!");
|
||||
while (e != null)
|
||||
{
|
||||
ExplorerCore.Log(e);
|
||||
e = e.InnerException;
|
||||
}
|
||||
m_searchResults.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchBox()
|
||||
{
|
||||
GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Search</color></b>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
m_searchInput = GUIHelper.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
|
||||
GUILayout.Label("Max Results:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
var s = MaxSearchResults.ToString();
|
||||
s = GUIHelper.TextField(s, new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (int.TryParse(s, out int i))
|
||||
{
|
||||
MaxSearchResults = i;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
ClassFilterToggle(TypeFilter.Object, "Object");
|
||||
ClassFilterToggle(TypeFilter.GameObject, "GameObject");
|
||||
ClassFilterToggle(TypeFilter.Component, "Component");
|
||||
ClassFilterToggle(TypeFilter.Custom, "Custom");
|
||||
GUILayout.EndHorizontal();
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
m_typeInput = GUIHelper.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
SceneFilterToggle(SceneFilter.Any, "Any", 60);
|
||||
SceneFilterToggle(SceneFilter.This, "This Scene", 100);
|
||||
SceneFilterToggle(SceneFilter.DontDestroy, "DontDestroyOnLoad", 140);
|
||||
SceneFilterToggle(SceneFilter.None, "No Scene", 80);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("<b><color=cyan>Search</color></b>", new GUILayoutOption[0]))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
//if (m_cachingResults)
|
||||
//{
|
||||
// GUILayout.Label("Searching...", new GUILayoutOption[0]);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// if (GUILayout.Button("<b><color=cyan>Search</color></b>", new GUILayoutOption[0]))
|
||||
// {
|
||||
// Search();
|
||||
// }
|
||||
//}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ClassFilterToggle(TypeFilter mode, string label)
|
||||
{
|
||||
if (TypeMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
TypeMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
private void SceneFilterToggle(SceneFilter mode, string label, float width)
|
||||
{
|
||||
if (SceneMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
SceneMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,125 +1,288 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityEngine;
|
||||
using Explorer.Config;
|
||||
using Explorer.UI.Main;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.UI.Inspectors;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace Explorer.UI
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public class MainMenu
|
||||
{
|
||||
public static MainMenu Instance;
|
||||
public abstract class Page
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public GameObject Content;
|
||||
public Button RefNavbarButton { get; set; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => Content?.activeSelf ?? false;
|
||||
set => Content?.SetActive(true);
|
||||
}
|
||||
|
||||
|
||||
public abstract void Init();
|
||||
public abstract void Update();
|
||||
}
|
||||
|
||||
public static MainMenu Instance { get; set; }
|
||||
|
||||
public PanelDragger Dragger { get; private set; }
|
||||
|
||||
public GameObject MainPanel { get; private set; }
|
||||
public GameObject PageViewport { get; private set; }
|
||||
|
||||
public readonly List<Page> Pages = new List<Page>();
|
||||
private Page m_activePage;
|
||||
|
||||
// Navbar buttons
|
||||
private Button m_lastNavButtonPressed;
|
||||
private readonly Color m_navButtonNormal = new Color(0.3f, 0.3f, 0.3f, 1);
|
||||
private readonly Color m_navButtonHighlight = new Color(0.3f, 0.6f, 0.3f);
|
||||
private readonly Color m_navButtonSelected = new Color(0.2f, 0.5f, 0.2f, 1);
|
||||
|
||||
public MainMenu()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
Pages.Add(new ScenePage());
|
||||
Pages.Add(new SearchPage());
|
||||
Pages.Add(new ConsolePage());
|
||||
Pages.Add(new OptionsPage());
|
||||
|
||||
for (int i = 0; i < Pages.Count; i++)
|
||||
if (Instance != null)
|
||||
{
|
||||
var page = Pages[i];
|
||||
page.Init();
|
||||
|
||||
// If page failed to init, it will remove itself from the list. Lower the iterate counter.
|
||||
if (!Pages.Contains(page)) i--;
|
||||
}
|
||||
}
|
||||
|
||||
public const int MainWindowID = 5000;
|
||||
public static Rect MainRect = new Rect(5, 5, ModConfig.Instance.Default_Window_Size.x, ModConfig.Instance.Default_Window_Size.y);
|
||||
|
||||
public static readonly List<BaseMainMenuPage> Pages = new List<BaseMainMenuPage>();
|
||||
private static int m_currentPage = 0;
|
||||
|
||||
public static void SetCurrentPage(int index)
|
||||
{
|
||||
if (index < 0 || Pages.Count <= index)
|
||||
{
|
||||
ExplorerCore.Log("cannot set page " + index);
|
||||
ExplorerCore.LogWarning("An instance of MainMenu already exists, cannot create another!");
|
||||
return;
|
||||
}
|
||||
m_currentPage = index;
|
||||
GUIHelper.BringWindowToFront(MainWindowID);
|
||||
GUI.FocusWindow(MainWindowID);
|
||||
|
||||
Instance = this;
|
||||
|
||||
Pages.Add(new HomePage());
|
||||
Pages.Add(new SearchPage());
|
||||
Pages.Add(new CSConsolePage());
|
||||
Pages.Add(new OptionsPage());
|
||||
|
||||
ConstructMenu();
|
||||
|
||||
foreach (Page page in Pages)
|
||||
{
|
||||
page.Init();
|
||||
}
|
||||
|
||||
// hide menu until each page has init layout (bit of a hack)
|
||||
initPos = MainPanel.transform.position;
|
||||
MainPanel.transform.position = new Vector3(9999, 9999);
|
||||
}
|
||||
|
||||
internal Vector3 initPos;
|
||||
internal bool pageLayoutInit;
|
||||
internal int layoutInitIndex;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Pages[m_currentPage].Update();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
MainRect = GUIHelper.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, ExplorerCore.NAME);
|
||||
}
|
||||
|
||||
private void MainWindow(int id)
|
||||
{
|
||||
GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20));
|
||||
|
||||
if (GUIHelper.Button(new Rect(MainRect.width - 90, 2, 80, 20), $"Hide ({ModConfig.Instance.Main_Menu_Toggle})"))
|
||||
if (!pageLayoutInit)
|
||||
{
|
||||
ExplorerCore.ShowMenu = false;
|
||||
if (layoutInitIndex < Pages.Count)
|
||||
{
|
||||
SetPage(Pages[layoutInitIndex]);
|
||||
layoutInitIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pageLayoutInit = true;
|
||||
MainPanel.transform.position = initPos;
|
||||
SetPage(Pages[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GUIHelper.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box);
|
||||
|
||||
MainHeader();
|
||||
|
||||
var page = Pages[m_currentPage];
|
||||
|
||||
page.scroll = GUIHelper.BeginScrollView(page.scroll);
|
||||
|
||||
page.DrawWindow();
|
||||
|
||||
GUIHelper.EndScrollView();
|
||||
|
||||
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
|
||||
|
||||
GUIHelper.EndArea();
|
||||
m_activePage?.Update();
|
||||
}
|
||||
|
||||
private void MainHeader()
|
||||
public void SetPage(Page page)
|
||||
{
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
for (int i = 0; i < Pages.Count; i++)
|
||||
{
|
||||
if (m_currentPage == i)
|
||||
GUI.color = Color.green;
|
||||
else
|
||||
GUI.color = Color.white;
|
||||
if (page == null || m_activePage == page)
|
||||
return;
|
||||
|
||||
if (GUILayout.Button(Pages[i].Name, new GUILayoutOption[0]))
|
||||
{
|
||||
m_currentPage = i;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
// WIP, was going to hide current page if you press current page's button,
|
||||
// but the main panel does not resize so its just a big empty gap there.
|
||||
// Could be good if I resize that gap, not bothering for now.
|
||||
// Would need a fix in PanelDragger as well.
|
||||
|
||||
GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUI.color = Color.white;
|
||||
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", new GUILayoutOption[0]);
|
||||
//if (m_activePage == page)
|
||||
//{
|
||||
// SetButtonInactiveColors(page.RefNavbarButton);
|
||||
// m_activePage.Content.SetActive(false);
|
||||
// m_activePage = null;
|
||||
// return;
|
||||
//}
|
||||
|
||||
bool mouseState = ForceUnlockCursor.Unlock;
|
||||
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", new GUILayoutOption[0]);
|
||||
if (setMouse != mouseState) ForceUnlockCursor.Unlock = setMouse;
|
||||
m_activePage?.Content?.SetActive(false);
|
||||
|
||||
//WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", new GUILayoutOption[0]);
|
||||
GUILayout.EndHorizontal();
|
||||
// unique case for console page, at the moment this will just go here
|
||||
if (m_activePage is CSConsolePage)
|
||||
AutoCompleter.m_mainObj?.SetActive(false);
|
||||
|
||||
//GUIUnstrip.Space(10);
|
||||
GUIHelper.Space(10);
|
||||
m_activePage = page;
|
||||
|
||||
GUI.color = Color.white;
|
||||
m_activePage.Content?.SetActive(true);
|
||||
|
||||
Button button = page.RefNavbarButton;
|
||||
SetButtonActiveColors(button);
|
||||
|
||||
if (m_lastNavButtonPressed && m_lastNavButtonPressed != button)
|
||||
SetButtonInactiveColors(m_lastNavButtonPressed);
|
||||
|
||||
m_lastNavButtonPressed = button;
|
||||
}
|
||||
|
||||
internal void SetButtonActiveColors(Button button)
|
||||
{
|
||||
ColorBlock colors = button.colors;
|
||||
colors.normalColor = m_navButtonSelected;
|
||||
button.colors = colors;
|
||||
}
|
||||
|
||||
internal void SetButtonInactiveColors(Button button)
|
||||
{
|
||||
ColorBlock colors = button.colors;
|
||||
colors.normalColor = m_navButtonNormal;
|
||||
button.colors = colors;
|
||||
}
|
||||
|
||||
#region UI Construction
|
||||
|
||||
private void ConstructMenu()
|
||||
{
|
||||
MainPanel = UIFactory.CreatePanel(UIManager.CanvasRoot, "MainMenu", out GameObject content);
|
||||
|
||||
RectTransform panelRect = MainPanel.GetComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.25f, 0.1f);
|
||||
panelRect.anchorMax = new Vector2(0.78f, 0.95f);
|
||||
|
||||
MainPanel.AddComponent<Mask>();
|
||||
|
||||
ConstructTitleBar(content);
|
||||
|
||||
ConstructNavbar(content);
|
||||
|
||||
ConstructMainViewport(content);
|
||||
|
||||
new DebugConsole(content);
|
||||
}
|
||||
|
||||
private void ConstructTitleBar(GameObject content)
|
||||
{
|
||||
// Core title bar holder
|
||||
|
||||
GameObject titleBar = UIFactory.CreateHorizontalGroup(content);
|
||||
|
||||
HorizontalLayoutGroup titleGroup = titleBar.GetComponent<HorizontalLayoutGroup>();
|
||||
titleGroup.childControlHeight = true;
|
||||
titleGroup.childControlWidth = true;
|
||||
titleGroup.childForceExpandHeight = true;
|
||||
titleGroup.childForceExpandWidth = true;
|
||||
titleGroup.padding.left = 15;
|
||||
titleGroup.padding.right = 3;
|
||||
titleGroup.padding.top = 3;
|
||||
titleGroup.padding.bottom = 3;
|
||||
|
||||
LayoutElement titleLayout = titleBar.AddComponent<LayoutElement>();
|
||||
titleLayout.minHeight = 25;
|
||||
titleLayout.flexibleHeight = 0;
|
||||
|
||||
// Explorer label
|
||||
|
||||
GameObject textObj = UIFactory.CreateLabel(titleBar, TextAnchor.MiddleLeft);
|
||||
|
||||
Text text = textObj.GetComponent<Text>();
|
||||
text.text = $"<b>UnityExplorer</b> <i>v{ExplorerCore.VERSION}</i>";
|
||||
text.fontSize = 15;
|
||||
LayoutElement textLayout = textObj.AddComponent<LayoutElement>();
|
||||
textLayout.flexibleWidth = 5000;
|
||||
|
||||
// Add PanelDragger using the label object
|
||||
|
||||
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), MainPanel.GetComponent<RectTransform>());
|
||||
|
||||
// Hide button
|
||||
|
||||
GameObject hideBtnObj = UIFactory.CreateButton(titleBar);
|
||||
|
||||
Button hideBtn = hideBtnObj.GetComponent<Button>();
|
||||
hideBtn.onClick.AddListener(() => { ExplorerCore.ShowMenu = false; });
|
||||
ColorBlock colorBlock = hideBtn.colors;
|
||||
colorBlock.normalColor = new Color(65f / 255f, 23f / 255f, 23f / 255f);
|
||||
colorBlock.pressedColor = new Color(35f / 255f, 10f / 255f, 10f / 255f);
|
||||
colorBlock.highlightedColor = new Color(156f / 255f, 0f, 0f);
|
||||
hideBtn.colors = colorBlock;
|
||||
|
||||
LayoutElement btnLayout = hideBtnObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minWidth = 90;
|
||||
btnLayout.flexibleWidth = 2;
|
||||
|
||||
Text hideText = hideBtnObj.GetComponentInChildren<Text>();
|
||||
hideText.color = Color.white;
|
||||
hideText.resizeTextForBestFit = true;
|
||||
hideText.resizeTextMinSize = 8;
|
||||
hideText.resizeTextMaxSize = 14;
|
||||
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
|
||||
|
||||
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
|
||||
|
||||
void ModConfig_OnConfigChanged()
|
||||
{
|
||||
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
|
||||
}
|
||||
}
|
||||
|
||||
private void ConstructNavbar(GameObject content)
|
||||
{
|
||||
GameObject navbarObj = UIFactory.CreateHorizontalGroup(content);
|
||||
|
||||
HorizontalLayoutGroup navGroup = navbarObj.GetComponent<HorizontalLayoutGroup>();
|
||||
navGroup.spacing = 5;
|
||||
navGroup.childControlHeight = true;
|
||||
navGroup.childControlWidth = true;
|
||||
navGroup.childForceExpandHeight = true;
|
||||
navGroup.childForceExpandWidth = true;
|
||||
|
||||
LayoutElement navLayout = navbarObj.AddComponent<LayoutElement>();
|
||||
navLayout.minHeight = 25;
|
||||
navLayout.flexibleHeight = 0;
|
||||
|
||||
foreach (Page page in Pages)
|
||||
{
|
||||
GameObject btnObj = UIFactory.CreateButton(navbarObj);
|
||||
Button btn = btnObj.GetComponent<Button>();
|
||||
|
||||
page.RefNavbarButton = btn;
|
||||
|
||||
btn.onClick.AddListener(() => { SetPage(page); });
|
||||
|
||||
Text text = btnObj.GetComponentInChildren<Text>();
|
||||
text.text = page.Name;
|
||||
|
||||
// Set button colors
|
||||
ColorBlock colorBlock = btn.colors;
|
||||
colorBlock.normalColor = m_navButtonNormal;
|
||||
//try { colorBlock.selectedColor = colorBlock.normalColor; } catch { }
|
||||
colorBlock.highlightedColor = m_navButtonHighlight;
|
||||
colorBlock.pressedColor = m_navButtonSelected;
|
||||
btn.colors = colorBlock;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConstructMainViewport(GameObject content)
|
||||
{
|
||||
GameObject mainObj = UIFactory.CreateHorizontalGroup(content);
|
||||
HorizontalLayoutGroup mainGroup = mainObj.GetComponent<HorizontalLayoutGroup>();
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
PageViewport = mainObj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
120
src/UI/Modules/CSConsolePage.cs
Normal file
120
src/UI/Modules/CSConsolePage.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityExplorer.CSConsole;
|
||||
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class CSConsolePage : MainMenu.Page
|
||||
{
|
||||
public override string Name => "C# Console";
|
||||
|
||||
public static CSConsolePage Instance { get; private set; }
|
||||
|
||||
public CodeEditor m_codeEditor;
|
||||
public ScriptEvaluator m_evaluator;
|
||||
|
||||
public static List<string> UsingDirectives;
|
||||
|
||||
public static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection",
|
||||
"UnityEngine",
|
||||
#if CPP
|
||||
"UnhollowerBaseLib",
|
||||
"UnhollowerRuntimeLib",
|
||||
#endif
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
try
|
||||
{
|
||||
m_codeEditor = new CodeEditor();
|
||||
|
||||
AutoCompleter.Init();
|
||||
|
||||
ResetConsole();
|
||||
|
||||
// Make sure compiler is supported on this platform
|
||||
m_evaluator.Compile("");
|
||||
|
||||
foreach (string use in DefaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// TODO remove page button from menu?
|
||||
ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
m_codeEditor?.Update();
|
||||
|
||||
AutoCompleter.Update();
|
||||
}
|
||||
|
||||
public void AddUsing(string asm)
|
||||
{
|
||||
if (!UsingDirectives.Contains(asm))
|
||||
{
|
||||
Evaluate($"using {asm};", true);
|
||||
UsingDirectives.Add(asm);
|
||||
}
|
||||
}
|
||||
|
||||
public void Evaluate(string code, bool suppressWarning = false)
|
||||
{
|
||||
m_evaluator.Compile(code, out Mono.CSharp.CompiledMethod compiled);
|
||||
|
||||
if (compiled == null)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("Unable to compile the code!");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
object ret = VoidType.Value;
|
||||
compiled.Invoke(ref ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning($"Exception executing code: {e.GetType()}, {e.Message}\r\n{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetConsole()
|
||||
{
|
||||
if (m_evaluator != null)
|
||||
{
|
||||
m_evaluator.Dispose();
|
||||
}
|
||||
|
||||
m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) };
|
||||
|
||||
UsingDirectives = new List<string>();
|
||||
}
|
||||
|
||||
private class VoidType
|
||||
{
|
||||
public static readonly VoidType Value = new VoidType();
|
||||
private VoidType() { }
|
||||
}
|
||||
}
|
||||
}
|
301
src/UI/Modules/DebugConsole.cs
Normal file
301
src/UI/Modules/DebugConsole.cs
Normal file
@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityExplorer.Unstrip;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Helpers;
|
||||
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class DebugConsole
|
||||
{
|
||||
public static DebugConsole Instance { get; private set; }
|
||||
|
||||
public static bool LogUnity { get; set; } = ModConfig.Instance.Log_Unity_Debug;
|
||||
public static bool SaveToDisk { get; set; } = ModConfig.Instance.Save_Logs_To_Disk;
|
||||
|
||||
internal static StreamWriter s_streamWriter;
|
||||
|
||||
public static readonly List<string> AllMessages = new List<string>();
|
||||
public static readonly List<Text> MessageHolders = new List<Text>();
|
||||
|
||||
// logs that occured before the actual UI was ready.
|
||||
// these ones include the hex color codes.
|
||||
internal static readonly List<string> s_preInitMessages = new List<string>();
|
||||
|
||||
private InputField m_textInput;
|
||||
internal const int MAX_TEXT_LEN = 10000;
|
||||
|
||||
public DebugConsole(GameObject parent)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
ConstructUI(parent);
|
||||
|
||||
// append messages that logged before we were set up
|
||||
string preAppend = "";
|
||||
for (int i = s_preInitMessages.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var msg = s_preInitMessages[i];
|
||||
if (preAppend != "")
|
||||
preAppend += "\r\n";
|
||||
preAppend += msg;
|
||||
}
|
||||
m_textInput.text = preAppend;
|
||||
|
||||
// set up IO
|
||||
|
||||
if (!SaveToDisk)
|
||||
return;
|
||||
|
||||
var path = ExplorerCore.EXPLORER_FOLDER + @"\Logs";
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
// clean old log(s)
|
||||
var files = Directory.GetFiles(path);
|
||||
if (files.Length >= 10)
|
||||
{
|
||||
var sorted = files.ToList();
|
||||
// sort by 'datetime.ToString("u")' will put the oldest ones first
|
||||
sorted.Sort();
|
||||
for (int i = 0; i < files.Length - 9; i++)
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
|
||||
var fileName = "UnityExplorer " + DateTime.Now.ToString("u") + ".txt";
|
||||
fileName = ExplorerCore.RemoveInvalidFilenameChars(fileName);
|
||||
|
||||
var stream = File.Create(path + @"\" + fileName);
|
||||
s_streamWriter = new StreamWriter(stream)
|
||||
{
|
||||
AutoFlush = true
|
||||
};
|
||||
|
||||
foreach (var msg in AllMessages)
|
||||
s_streamWriter.WriteLine(msg);
|
||||
}
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
Log(message, null);
|
||||
}
|
||||
|
||||
public static void Log(string message, Color color)
|
||||
{
|
||||
Log(message, color.ToHex());
|
||||
}
|
||||
|
||||
public static void Log(string message, string hexColor)
|
||||
{
|
||||
message = $"{AllMessages.Count}: {message}";
|
||||
|
||||
AllMessages.Add(message);
|
||||
s_streamWriter?.WriteLine(message);
|
||||
|
||||
if (hexColor != null)
|
||||
message = $"<color=#{hexColor}>{message}</color>";
|
||||
|
||||
if (Instance?.m_textInput)
|
||||
{
|
||||
var input = Instance.m_textInput;
|
||||
var wanted = $"{message}\n{input.text}";
|
||||
|
||||
if (wanted.Length > MAX_TEXT_LEN)
|
||||
wanted = wanted.Substring(0, MAX_TEXT_LEN);
|
||||
|
||||
input.text = wanted;
|
||||
}
|
||||
else
|
||||
s_preInitMessages.Add(message);
|
||||
}
|
||||
|
||||
public void ConstructUI(GameObject parent)
|
||||
{
|
||||
var mainObj = UIFactory.CreateVerticalGroup(parent, new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
||||
|
||||
var mainGroup = mainObj.GetComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
var mainImage = mainObj.GetComponent<Image>();
|
||||
mainImage.maskable = true;
|
||||
|
||||
var mask = mainObj.AddComponent<Mask>();
|
||||
mask.showMaskGraphic = true;
|
||||
|
||||
var mainLayout = mainObj.AddComponent<LayoutElement>();
|
||||
mainLayout.minHeight = 190;
|
||||
mainLayout.flexibleHeight = 0;
|
||||
|
||||
#region LOG AREA
|
||||
var logAreaObj = UIFactory.CreateHorizontalGroup(mainObj);
|
||||
var logAreaGroup = logAreaObj.GetComponent<HorizontalLayoutGroup>();
|
||||
logAreaGroup.childControlHeight = true;
|
||||
logAreaGroup.childControlWidth = true;
|
||||
logAreaGroup.childForceExpandHeight = true;
|
||||
logAreaGroup.childForceExpandWidth = true;
|
||||
|
||||
var logAreaLayout = logAreaObj.AddComponent<LayoutElement>();
|
||||
logAreaLayout.preferredHeight = 190;
|
||||
logAreaLayout.flexibleHeight = 0;
|
||||
|
||||
var inputScrollerObj = UIFactory.CreateSrollInputField(logAreaObj, out InputFieldScroller inputScroll, 14, new Color(0.05f, 0.05f, 0.05f));
|
||||
|
||||
inputScroll.inputField.textComponent.font = UIManager.ConsoleFont;
|
||||
inputScroll.inputField.readOnly = true;
|
||||
|
||||
m_textInput = inputScroll.inputField;
|
||||
#endregion
|
||||
|
||||
#region BOTTOM BAR
|
||||
|
||||
var bottomBarObj = UIFactory.CreateHorizontalGroup(mainObj);
|
||||
LayoutElement topBarLayout = bottomBarObj.AddComponent<LayoutElement>();
|
||||
topBarLayout.minHeight = 30;
|
||||
topBarLayout.flexibleHeight = 0;
|
||||
|
||||
var bottomGroup = bottomBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||
bottomGroup.padding.left = 10;
|
||||
bottomGroup.padding.right = 10;
|
||||
bottomGroup.padding.top = 2;
|
||||
bottomGroup.padding.bottom = 2;
|
||||
bottomGroup.spacing = 10;
|
||||
bottomGroup.childForceExpandHeight = true;
|
||||
bottomGroup.childForceExpandWidth = false;
|
||||
bottomGroup.childControlWidth = true;
|
||||
bottomGroup.childControlHeight = true;
|
||||
bottomGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||
|
||||
// Debug Console label
|
||||
|
||||
var bottomLabel = UIFactory.CreateLabel(bottomBarObj, TextAnchor.MiddleLeft);
|
||||
var topBarLabelLayout = bottomLabel.AddComponent<LayoutElement>();
|
||||
topBarLabelLayout.minWidth = 100;
|
||||
topBarLabelLayout.flexibleWidth = 0;
|
||||
var topBarText = bottomLabel.GetComponent<Text>();
|
||||
topBarText.fontStyle = FontStyle.Bold;
|
||||
topBarText.text = "Debug Console";
|
||||
topBarText.fontSize = 14;
|
||||
|
||||
// Hide button
|
||||
|
||||
var hideButtonObj = UIFactory.CreateButton(bottomBarObj);
|
||||
|
||||
var hideBtnText = hideButtonObj.GetComponentInChildren<Text>();
|
||||
hideBtnText.text = "Hide";
|
||||
|
||||
var hideButton = hideButtonObj.GetComponent<Button>();
|
||||
|
||||
hideButton.onClick.AddListener(HideCallback);
|
||||
void HideCallback()
|
||||
{
|
||||
if (logAreaObj.activeSelf)
|
||||
{
|
||||
logAreaObj.SetActive(false);
|
||||
hideBtnText.text = "Show";
|
||||
mainLayout.minHeight = 30;
|
||||
}
|
||||
else
|
||||
{
|
||||
logAreaObj.SetActive(true);
|
||||
hideBtnText.text = "Hide";
|
||||
mainLayout.minHeight = 190;
|
||||
}
|
||||
}
|
||||
|
||||
var hideBtnColors = hideButton.colors;
|
||||
//hideBtnColors.normalColor = new Color(160f / 255f, 140f / 255f, 40f / 255f);
|
||||
hideButton.colors = hideBtnColors;
|
||||
|
||||
var hideBtnLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||
hideBtnLayout.minWidth = 80;
|
||||
hideBtnLayout.flexibleWidth = 0;
|
||||
|
||||
// Clear button
|
||||
|
||||
var clearButtonObj = UIFactory.CreateButton(bottomBarObj);
|
||||
|
||||
var clearBtnText = clearButtonObj.GetComponentInChildren<Text>();
|
||||
clearBtnText.text = "Clear";
|
||||
|
||||
var clearButton = clearButtonObj.GetComponent<Button>();
|
||||
|
||||
clearButton.onClick.AddListener(ClearCallback);
|
||||
void ClearCallback()
|
||||
{
|
||||
m_textInput.text = "";
|
||||
AllMessages.Clear();
|
||||
}
|
||||
|
||||
var clearBtnColors = clearButton.colors;
|
||||
//clearBtnColors.normalColor = new Color(160f/255f, 140f/255f, 40f/255f);
|
||||
clearButton.colors = clearBtnColors;
|
||||
|
||||
var clearBtnLayout = clearButtonObj.AddComponent<LayoutElement>();
|
||||
clearBtnLayout.minWidth = 80;
|
||||
clearBtnLayout.flexibleWidth = 0;
|
||||
|
||||
// Unity log toggle
|
||||
|
||||
var unityToggleObj = UIFactory.CreateToggle(bottomBarObj, out Toggle unityToggle, out Text unityToggleText);
|
||||
|
||||
unityToggle.onValueChanged.AddListener(ToggleLogUnity);
|
||||
|
||||
unityToggle.isOn = LogUnity;
|
||||
unityToggleText.text = "Print Unity Debug?";
|
||||
unityToggleText.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
void ToggleLogUnity(bool val)
|
||||
{
|
||||
LogUnity = val;
|
||||
ModConfig.Instance.Log_Unity_Debug = val;
|
||||
ModConfig.SaveSettings();
|
||||
}
|
||||
|
||||
var unityToggleLayout = unityToggleObj.AddComponent<LayoutElement>();
|
||||
unityToggleLayout.minWidth = 170;
|
||||
unityToggleLayout.flexibleWidth = 0;
|
||||
|
||||
var unityToggleRect = unityToggleObj.transform.Find("Background").GetComponent<RectTransform>();
|
||||
var pos = unityToggleRect.localPosition;
|
||||
pos.y = -4;
|
||||
unityToggleRect.localPosition = pos;
|
||||
|
||||
// // Save to disk button
|
||||
|
||||
// var saveToDiskObj = UIFactory.CreateToggle(bottomBarObj, out Toggle diskToggle, out Text diskToggleText);
|
||||
|
||||
// diskToggle.onValueChanged.AddListener(ToggleDisk);
|
||||
|
||||
// diskToggle.isOn = SaveToDisk;
|
||||
// diskToggleText.text = "Save logs to 'Mods\\UnityExplorer\\Logs'?";
|
||||
// diskToggleText.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
// void ToggleDisk(bool val)
|
||||
// {
|
||||
// SaveToDisk = val;
|
||||
// ModConfig.Instance.Save_Logs_To_Disk = val;
|
||||
// ModConfig.SaveSettings();
|
||||
// }
|
||||
|
||||
// var diskToggleLayout = saveToDiskObj.AddComponent<LayoutElement>();
|
||||
// diskToggleLayout.minWidth = 340;
|
||||
// diskToggleLayout.flexibleWidth = 0;
|
||||
|
||||
// var saveToDiskRect = saveToDiskObj.transform.Find("Background").GetComponent<RectTransform>();
|
||||
// pos = unityToggleRect.localPosition;
|
||||
// pos.y = -8;
|
||||
// saveToDiskRect.localPosition = pos;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
52
src/UI/Modules/HomePage.cs
Normal file
52
src/UI/Modules/HomePage.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors;
|
||||
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class HomePage : MainMenu.Page
|
||||
{
|
||||
public override string Name => "Home";
|
||||
|
||||
public static HomePage Instance { get; internal set; }
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
ConstructMenu();
|
||||
|
||||
new SceneExplorer();
|
||||
|
||||
new InspectorManager();
|
||||
|
||||
SceneExplorer.Instance.Init();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
SceneExplorer.Instance.Update();
|
||||
InspectorManager.Instance.Update();
|
||||
}
|
||||
|
||||
private void ConstructMenu()
|
||||
{
|
||||
GameObject parent = MainMenu.Instance.PageViewport;
|
||||
|
||||
Content = UIFactory.CreateHorizontalGroup(parent);
|
||||
var mainGroup = Content.GetComponent<HorizontalLayoutGroup>();
|
||||
mainGroup.padding.left = 1;
|
||||
mainGroup.padding.right = 1;
|
||||
mainGroup.padding.top = 1;
|
||||
mainGroup.padding.bottom = 1;
|
||||
mainGroup.spacing = 3;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user