Compare commits

..

55 Commits

Author SHA1 Message Date
1c5306b7c8 Fix the onValueChange throttling affecting internal features 2021-05-03 00:59:39 +10:00
ea1e183c4a Fix onValueChanged bursts 2021-05-01 16:32:11 +10:00
8080129d58 Merge branch 'master' of https://github.com/sinai-dev/Explorer 2021-04-30 00:12:48 +10:00
4b8298fd2e Update CursorUnlocker.cs 2021-04-30 00:12:41 +10:00
ad055b4383 Update README.md 2021-04-29 21:54:13 +10:00
23483a6108 Add aggressive mouse unlock option using WaitForEndOfFrame 2021-04-29 21:45:45 +10:00
a6c24f91e4 Add startup delay 2021-04-11 20:45:02 +10:00
9e4c335a05 Update MelonLoaderConfigHandler.cs 2021-04-10 23:17:49 +10:00
a1c2dfbe50 Add support for setting disabled color on ColoBlock 2021-04-10 20:15:03 +10:00
a5a07a0a23 Add RuntimeProvider method for setting Selectable.colors 2021-04-10 18:25:13 +10:00
e0fd682c81 Add MethodInfo helper 2021-04-10 18:24:16 +10:00
7426bd1dd6 Bump version 2021-04-10 17:44:15 +10:00
b39b044f79 Add advanced attributes to BepInEx config 2021-04-10 17:44:09 +10:00
7a2b4aa257 Fix setting color block when partially stripped 2021-04-10 17:43:56 +10:00
3762d14bdb Fix InputSystem for IL2CPP 2021-04-10 17:43:32 +10:00
3628f3db31 Fix event system control 2021-04-09 01:46:26 +10:00
d39fea69c3 Better InputSystem Key enum resolving 2021-04-07 20:54:08 +10:00
95e8b3aa58 fix string unbox 2021-04-07 17:31:06 +10:00
b68145385c Fix issue with float struct check 2021-04-07 17:20:54 +10:00
2310f2f7ce Add "Default Tab" config setting instead of "last active tab" 2021-04-07 17:20:42 +10:00
2cc403ad17 Cleanup runtime-specific 2021-04-07 17:20:09 +10:00
c2d9b9b59e a few small fixes
* Fix InteractiveFlags toggles not being properly updated
* Fix cases where games that don't have Reflection.Emit would still have the C# Console available. Also added a "(disabled)" message to the tab button.
2021-04-06 01:01:46 +10:00
c748be7bcc Rewrite InteractiveUnityStruct, now called InteractiveFloatStruct
InteractiveFloatStruct supports any struct where all the fields are floats.
2021-04-05 20:32:47 +10:00
09dae6f1d3 Add proper support for InputSystem 2021-04-05 16:28:30 +10:00
6ca117b070 Fix strings boxed as Il2CppSystem.Objects 2021-04-04 13:44:58 +10:00
113f2fd922 3.3.5 - fix Il2Cpp Hashtable, boxed strings 2021-04-04 03:41:36 +10:00
7443f6500e Update README.md 2021-04-03 17:24:28 +11:00
92566c2729 Update README.md 2021-04-02 19:56:18 +11:00
713f87f455 Update THIRDPARTY_LICENSES.md 2021-04-02 17:45:50 +11:00
4241e7e207 Include third-party licenses 2021-04-02 17:44:49 +11:00
6d479a6703 3.3.4
* Fixed Harmony patches not working properly for games which use older BepInEx releases (ie. Risk of Rain 2)
* Fixed a couple minor issues with the config settings
2021-04-02 17:06:49 +11:00
6539f818c3 Update README.md 2021-04-01 22:04:58 +11:00
bc5ffcab40 Update README.md 2021-04-01 17:15:50 +11:00
d070ded036 3.3.3
* Fix `Hide on Startup` not working
* Fix for cases when we try to `scene.GetRootGameObjects()` but the scene has not yet fully loaded.
* MelonLoader releases will no longer spam "Preferences Saved!" constantly in the Console log
* Fix mistake with UI Event System setting/releasing
* Fix some UI elements not having correct Color transition values
2021-04-01 17:13:31 +11:00
8f025622b4 3.3.2
* Added InteractiveColor UI editor to make changing a Color easier
* Added a "Scene Loader" helper which allows you to load any Scene that the game was built with. In some cases you may not find all the Scenes that the game uses, they may be loaded through AssetBundles or other means and won't show up here yet
* Adjusted the SceneExplorer UI, the "Hide" button is now always on the left of the window
*
* Handled some errors related to UI unstripping that could occur in rare cases
2021-03-31 22:58:17 +11:00
89f137680e Update DebugConsole.cs 2021-03-31 02:02:12 +11:00
f280b45ed3 3.3.1
* Added a 'Default' button for config values to revert to the default value
* Added an internal config entry to save the window position between sessions
* Reordered the config settings in the menu so the important ones are at the top
* Adjusted the UI for config entries, should be a bit easier to read now.
* Adjusted the UI for Dictionaries, the keys and values now alternate background colors.
* A few other minor UI fixes and tweaks for 3.3.0
2021-03-31 01:42:32 +11:00
456e15020f Update README.md 2021-03-31 00:00:39 +11:00
d33d46927d Update README.md 2021-03-30 23:59:08 +11:00
7a872cecf9 Update UIFactory.cs 2021-03-30 22:34:59 +11:00
b2cbdc1802 Update README.md 2021-03-30 21:31:12 +11:00
3501a28fd1 Restore UnlockMouse config, adjust config saving 2021-03-30 21:23:45 +11:00
40f698122d Revert colorblock changes 2021-03-30 19:55:18 +11:00
0555a644b7 3.3.0 rewrite
* Huge restructure/rewrite. No real changes to any functionality, just a cleaner and more manageable project.
2021-03-30 19:50:04 +11:00
f66a04c93f Update README.md 2021-03-26 23:34:06 +11:00
8f07255d1b Update README.md 2021-03-26 23:12:31 +11:00
46f35129c5 3.2.10
* The following preferences are now persistent between sessions: Active Menu Page, Scene Explorer Hide State, Debug Console Hide State
* The "Resize Cursor" is now just a `↔` Text label instead of a sprite.
* Added support for Unity 5.2+ games (previously was only supporting 5.6)
2021-03-26 19:49:53 +11:00
604c499822 Add Reset button to C# Console 2021-03-26 07:31:30 +11:00
8964c48ba0 Move melon attributes to ExplorerMelonMod.cs 2021-03-26 06:38:59 +11:00
e85a3e0f1e Merge bepinex/melonloader unhollowed libs 2021-03-26 06:04:44 +11:00
c658d393f5 Update README.md 2021-03-26 05:59:17 +11:00
9a45e29e02 Update README.md 2021-03-26 05:53:03 +11:00
418ece55e3 Improve UI inspect-under-mouse 2021-03-26 05:43:53 +11:00
bf455893e7 Include all references in lib folder 2021-03-26 05:40:12 +11:00
6d9cb8205a Update README.md 2021-03-25 18:48:40 +11:00
123 changed files with 7322 additions and 6909 deletions

169
README.md
View File

@ -5,31 +5,72 @@
<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, 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>
<img src="https://img.shields.io/github/downloads/sinai-dev/Explorer/total.svg" />
Supports most Unity games from versions 5.2 to 2020+.
</p>
- [Releases](#releases)
- [Features](#features)
- [How to install](#how-to-install)
- [Mod Config](#mod-config)
- [Building](#building)
- [Credits](#credits)
## Releases
## Releases [![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=release%20notes)](../../releases/latest) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../releases/latest)
| Mod Loader | IL2CPP | Mono |
| ----------- | ------ | ---- |
| [BepInEx](https://github.com/BepInEx/BepInEx) 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ❔* [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 5.X | n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
| Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) |
\* BepInEx 6.X Mono release may not work on all games yet.
## How to install
### BepInEx
1. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6).
2. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
3. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
4. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
### MelonLoader
1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3+ for your game. Version 0.3 is currently in pre-release, so you must "Enable ALPHA Releases" in your MelonLoader Installer settings to see the option for it.
2. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
3. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder.
### Standalone
The standalone release requires you to also load `0Harmony.dll` (HarmonyX, from the `lib\` folder) to function properly, and the IL2CPP also requires `UnhollowerBaseLib.dll` as well. The Mono release should be fairly easy to use with any loader, but the IL2CPP one may be tricky, I'd recommend just using BepInEx or MelonLoader for IL2CPP.
1. Load the UnityExplorer DLL from your mod or inject it, as well as `0Harmony.dll` and `UnhollowerBaseLib.dll` as required.
2. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
3. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish.
## Issues and contributions
Both issue reports and PR contributions are welcome in this repository.
### Issue reporting
To report an issue with UnityExplorer, please use the following template (as well as any other information / images you can provide).
Template:
```
* Game issue occurs on:
* UnityExplorer version: (eg BIE.IL2CPP v3.3.3, etc)
* Description of issue:
* Unity log link:
* Mod Loader log link:
```
Please upload your Unity log file to [Pastebin](https://pastebin.com/) (or equivalent service) and provide a link to the paste.
* The log should be found at `%userprofile%\AppData\LocalLow\[Company]\[Game]\` unless redirected by your Mod Loader.
* The file will be called either `output_log.txt` or `Player.log`
As well as the Unity log, please upload your Mod Loader's log:
* BepInEx: `BepInEx\LogOutput.log`
* MelonLoader: `MelonLoader\Latest.log`
## Features
@ -56,9 +97,9 @@ For example, you could run this code to define a temporary class (it will be vis
public class MyClass
{
public static void Method()
{
UnityExplorer.ExplorerCore.Log("hello");
}
{
UnityExplorer.ExplorerCore.Log("hello");
}
}
```
@ -72,91 +113,37 @@ However, you cannot define a class and run it both at the same time. You must ei
You can also make use of the helper methods in the console to simplify some tasks, which you can see listed when the console has nothing entered for input. These methods are **not** accessible within any temporary classes you define, they can only be used in the expression context.
## How to install
### BepInEx
Note: For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6).
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.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
3. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
### MelonLoader
Note: You must use version 0.3 of MelonLoader or greater. Version 0.3 is currently in pre-release, so you must opt-in from your MelonLoader installer (enable alpha releases).
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.ML.___.dll`
### Standalone
The standalone release is based on the BepInEx build, so it requires Harmony 2.0 (or HarmonyX) to function properly.
0. Load the DLL from your mod or inject it. You must also make sure that the required libraries (Harmony, Unhollower for Il2Cpp, etc) are loaded.
1. Create an instance of Unity Explorer with `ExplorerStandalone.CreateInstance();`
2. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish.
## Logging
### Logging
Explorer saves all logs to disk (only keeps the most recent 10 logs). They can be found in a "UnityExplorer" folder in the same place as where you put the DLL file.
These logs are also visible in the Debug Console part of the UI.
## Settings
### Settings
You can change the settings via the "Options" page of the main menu, or directly from the config file (generated after first launch). The config file will be found either inside a "UnityExplorer" folder in the same directory as where you put the DLL file, or for BepInEx it will be at `BepInEx\config\UnityExplorer\`.
You can change the settings via the "Options" page of the main menu, or directly from the config file.
`Main Menu Toggle` (KeyCode)
* Default: `F7`
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
`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 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.
`Default Output Path` (string)
* Default: `Mods\UnityExplorer`
* Where output is generated to, by default (for Texture PNG saving, etc).
`Log Unity Debug` (bool)
* Default: `false`
* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log.
`Hide on Startup` (bool)
* Default: `false`
* If true, UnityExplorer will be hidden when you start the game, you must open it via the keybind.
Depending on the release you are using, the config file will be found at:
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
* MelonLoader: `UserData\MelonPreferences.cfg`
* Standalone `{DLL_location}\UnityExplorer\config.ini`
## Building
If you'd like to build this yourself, all you need to do is download this repository and build from Visual Studio. If you want to build for BepInEx or MelonLoader IL2CPP then you will need to install the mod loader for a game and set the directory in the `csproj` file.
Building the project should be straight-forward, the references are all inside the `lib\` folder.
For IL2CPP:
1. Install BepInEx or MelonLoader for your game.
2. Open the `src\UnityExplorer.csproj` file in a text editor.
3. Set `BIECppGameFolder` (for BepInEx) and/or `MLCppGameFolder` (for MelonLoader) so the project can locate the necessary references.
4. For Standalone builds, you can either install BepInEx for the game to build, or just change the .csproj file and set the Unhollower reference manually.
For all builds:
1. Open the `src\UnityExplorer.sln` project.
2. 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.
1. Open the `src\UnityExplorer.sln` project in Visual Studio.
2. 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. Alternatively, use "Batch Build" and select all releases.
3. The DLLs are built to the `Release\` folder in the root of the repository.
4. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
4. If ILRepack complains about an error, just change the Active config to a different release and then back again. This sometimes happens for the first time you build the project.
## Credits
## Acknowledgments
Written by Sinai.
* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], snippets from the REPL Console were used for UnityExplorer's C# Console.
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
* [HerpDerpenstine](https://github.com/HerpDerpinstine) for [MelonCoroutines](https://github.com/LavaGang/MelonLoader/blob/6cc958ec23b5e2e8453a73bc2e0d5aa353d4f0d1/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs) \[[license](THIRDPARTY_LICENSES.md#melonloader-license)\], they were included for standalone IL2CPP coroutine support.
* [InGameCodeEditor](https://assetstore.unity.com/packages/tools/gui/ingame-code-editor-144254) \[[license](THIRDPARTY_LICENSES.md#ingamecodeeditor-license)\] was used as the base for the syntax highlighting for UnityExplorer's C# console (`UnityExplorer.UI.Main.CSConsole.Lexer`).
### Licensing
### Disclaimer
This project uses code from:
* (GPL) [ManlyMarco](https://github.com/ManlyMarco)'s [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for some aspects of the C# Console and Auto-Complete features. The snippets I used are indicated with a comment.
* (MIT) [denikson](https://github.com/denikson) (aka Horse)'s [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.
* (Apache) [InGameCodeEditor](https://assetstore.unity.com/packages/tools/gui/ingame-code-editor-144254) was used as the base for the syntax highlighting for UnityExplorer's C# console, although it has been heavily rewritten and optimized. Used classes are in the `UnityExplorer.UI.Main.CSConsole.Lexer` namespace.
UnityExplorer is in no way associated with Unity Technologies. "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere.

1067
THIRDPARTY_LICENSES.md Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
lib/UnhollowerBaseLib.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,14 +1,13 @@
using System;
using Mono.CSharp;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityExplorer.Core.Inspectors;
using UnityExplorer.UI.Main.CSConsole;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main.CSConsole;
using UnityExplorer.UI.Main.Home;
using UnityExplorer.UI.Inspectors;
namespace UnityExplorer.Core.CSharp
{
@ -21,7 +20,7 @@ namespace UnityExplorer.Core.CSharp
public static void StartCoroutine(IEnumerator ienumerator)
{
RuntimeProvider.Instance.StartConsoleCoroutine(ienumerator);
RuntimeProvider.Instance.StartCoroutine(ienumerator);
}
public static void AddUsing(string directive)

View File

@ -4,7 +4,6 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI.Main.CSConsole;
namespace UnityExplorer.Core.CSharp
@ -47,8 +46,8 @@ namespace UnityExplorer.Core.CSharp
// ~~~~ Static ~~~~
public static HashSet<string> Namespaces => m_namspaces ?? GetNamespaces();
private static HashSet<string> m_namspaces;
public static HashSet<string> Namespaces => m_namespaces ?? GetNamespaces();
private static HashSet<string> m_namespaces;
public static HashSet<string> Keywords => m_keywords ?? (m_keywords = new HashSet<string>(CSLexerHighlighter.validKeywordMatcher.Keywords));
private static HashSet<string> m_keywords;
@ -63,7 +62,7 @@ namespace UnityExplorer.Core.CSharp
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
.Select(x => x.Namespace));
return m_namspaces = set;
return m_namespaces = set;
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Config
{
public class ConfigElement<T> : IConfigElement
{
public string Name { get; }
public string Description { get; }
public bool IsInternal { get; }
public Type ElementType => typeof(T);
public Action<T> OnValueChanged;
public Action OnValueChangedNotify { get; set; }
public object DefaultValue { get; }
public T Value
{
get => m_value;
set => SetValue(value);
}
private T m_value;
object IConfigElement.BoxedValue
{
get => m_value;
set => SetValue((T)value);
}
public ConfigElement(string name, string description, T defaultValue, bool isInternal = false)
{
Name = name;
Description = description;
m_value = defaultValue;
DefaultValue = defaultValue;
IsInternal = isInternal;
ConfigManager.RegisterConfigElement(this);
}
private void SetValue(T value)
{
if ((m_value == null && value == null) || (m_value != null && m_value.Equals(value)))
return;
m_value = value;
ConfigManager.Handler.SetConfigValue(this, value);
OnValueChanged?.Invoke(value);
OnValueChangedNotify?.Invoke();
ConfigManager.Handler.OnAnyConfigChanged();
}
object IConfigElement.GetLoaderConfigValue() => GetLoaderConfigValue();
public T GetLoaderConfigValue()
{
return ConfigManager.Handler.GetConfigValue(this);
}
public void RevertToDefaultValue()
{
Value = (T)DefaultValue;
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Config
{
public abstract class ConfigHandler
{
public abstract void RegisterConfigElement<T>(ConfigElement<T> element);
public abstract void SetConfigValue<T>(ConfigElement<T> element, T value);
public abstract T GetConfigValue<T>(ConfigElement<T> element);
public abstract void Init();
public abstract void LoadConfig();
public abstract void SaveConfig();
public virtual void OnAnyConfigChanged() { }
}
}

View File

@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Main.Home;
namespace UnityExplorer.Core.Config
{
public static class ConfigManager
{
// Each Mod Loader has its own ConfigHandler.
// See the UnityExplorer.Loader namespace for the implementations.
public static ConfigHandler Handler { get; private set; }
public static ConfigElement<KeyCode> Main_Menu_Toggle;
public static ConfigElement<bool> Force_Unlock_Mouse;
public static ConfigElement<bool> Aggressive_Force_Unlock;
public static ConfigElement<MenuPages> Default_Tab;
public static ConfigElement<int> Default_Page_Limit;
public static ConfigElement<string> Default_Output_Path;
public static ConfigElement<bool> Log_Unity_Debug;
public static ConfigElement<bool> Hide_On_Startup;
public static ConfigElement<float> Startup_Delay_Time;
public static ConfigElement<string> Last_Window_Anchors;
public static ConfigElement<string> Last_Window_Position;
public static ConfigElement<bool> Last_DebugConsole_State;
public static ConfigElement<bool> Last_SceneExplorer_State;
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
public static void Init(ConfigHandler configHandler)
{
Handler = configHandler;
Handler.Init();
CreateConfigElements();
Handler.LoadConfig();
SceneExplorer.OnToggleShow += SceneExplorer_OnToggleShow;
PanelDragger.OnFinishResize += PanelDragger_OnFinishResize;
PanelDragger.OnFinishDrag += PanelDragger_OnFinishDrag;
DebugConsole.OnToggleShow += DebugConsole_OnToggleShow;
InitConsoleCallback();
}
internal static void RegisterConfigElement<T>(ConfigElement<T> configElement)
{
Handler.RegisterConfigElement(configElement);
ConfigElements.Add(configElement.Name, configElement);
}
private static void CreateConfigElements()
{
Main_Menu_Toggle = new ConfigElement<KeyCode>("Main Menu Toggle",
"The UnityEngine.KeyCode to toggle the UnityExplorer Menu.",
KeyCode.F7);
Hide_On_Startup = new ConfigElement<bool>("Hide On Startup",
"Should UnityExplorer be hidden on startup?",
false);
Default_Tab = new ConfigElement<MenuPages>("Default Tab",
"The default menu page when starting the game.",
MenuPages.Home);
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
false);
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
Aggressive_Force_Unlock = new ConfigElement<bool>("Aggressive Mouse Unlock",
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked (requires game restart).",
false);
Default_Page_Limit = new ConfigElement<int>("Default Page Limit",
"The default maximum number of elements per 'page' in UnityExplorer.",
25);
Default_Output_Path = new ConfigElement<string>("Default Output Path",
"The default output path when exporting things from UnityExplorer.",
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Output"));
Startup_Delay_Time = new ConfigElement<float>("Startup Delay Time",
"The delay on startup before the UI is created.",
1f);
// Internal configs
Last_Window_Anchors = new ConfigElement<string>("Last_Window_Anchors",
"For internal use, the last anchors of the UnityExplorer window.",
DEFAULT_WINDOW_ANCHORS,
true);
Last_Window_Position = new ConfigElement<string>("Last_Window_Position",
"For internal use, the last position of the UnityExplorer window.",
DEFAULT_WINDOW_POSITION,
true);
Last_DebugConsole_State = new ConfigElement<bool>("Last_DebugConsole_State",
"For internal use, the collapsed state of the Debug Console.",
true,
true);
Last_SceneExplorer_State = new ConfigElement<bool>("Last_SceneExplorer_State",
"For internal use, the collapsed state of the Scene Explorer.",
true,
true);
}
// Internal config callback listeners
private static void PanelDragger_OnFinishResize(RectTransform rect)
{
Last_Window_Anchors.Value = rect.RectAnchorsToString();
PanelDragger_OnFinishDrag(rect);
}
private static void PanelDragger_OnFinishDrag(RectTransform rect)
{
Last_Window_Position.Value = rect.RectPositionToString();
}
private static void DebugConsole_OnToggleShow(bool showing)
{
Last_DebugConsole_State.Value = showing;
}
private static void SceneExplorer_OnToggleShow(bool showing)
{
Last_SceneExplorer_State.Value = showing;
}
#region CONSOLE ONEXIT CALLBACK
internal static void InitConsoleCallback()
{
handler = new ConsoleEventDelegate(ConsoleEventCallback);
SetConsoleCtrlHandler(handler, true);
}
static bool ConsoleEventCallback(int eventType)
{
// 2 is Console Quit
if (eventType == 2)
Handler.SaveConfig();
return false;
}
static ConsoleEventDelegate handler;
private delegate bool ConsoleEventDelegate(int eventType);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
#endregion
#region WINDOW ANCHORS / POSITION HELPERS
// Window Anchors helpers
private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.10,0.78,0.95";
private const string DEFAULT_WINDOW_POSITION = "0,0";
internal static CultureInfo _enCulture = new CultureInfo("en-US");
internal static string RectAnchorsToString(this RectTransform rect)
{
try
{
return string.Format(_enCulture, "{0},{1},{2},{3}", new object[]
{
rect.anchorMin.x,
rect.anchorMin.y,
rect.anchorMax.x,
rect.anchorMax.y
});
}
catch
{
return DEFAULT_WINDOW_ANCHORS;
}
}
internal static void SetAnchorsFromString(this RectTransform panel, string stringAnchors)
{
Vector4 anchors;
try
{
var split = stringAnchors.Split(',');
if (split.Length != 4)
throw new Exception();
anchors.x = float.Parse(split[0], _enCulture);
anchors.y = float.Parse(split[1], _enCulture);
anchors.z = float.Parse(split[2], _enCulture);
anchors.w = float.Parse(split[3], _enCulture);
}
catch
{
anchors = new Vector4(0.25f, 0.1f, 0.78f, 0.95f);
}
panel.anchorMin = new Vector2(anchors.x, anchors.y);
panel.anchorMax = new Vector2(anchors.z, anchors.w);
}
internal static string RectPositionToString(this RectTransform rect)
{
return string.Format(_enCulture, "{0},{1}", new object[]
{
rect.localPosition.x, rect.localPosition.y
});
}
internal static void SetPositionFromString(this RectTransform rect, string stringPosition)
{
try
{
var split = stringPosition.Split(',');
if (split.Length != 2)
throw new Exception();
Vector3 vector = rect.localPosition;
vector.x = float.Parse(split[0], _enCulture);
vector.y = float.Parse(split[1], _enCulture);
rect.localPosition = vector;
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning("Exception setting window position: " + ex);
}
}
#endregion
}
}

View File

@ -1,171 +0,0 @@
using System;
using System.IO;
using UnityEngine;
using IniParser;
using IniParser.Parser;
using UnityExplorer.UI;
using System.Globalization;
namespace UnityExplorer.Core.Config
{
public class ExplorerConfig
{
public static ExplorerConfig Instance;
internal static readonly IniDataParser _parser = new IniDataParser();
internal static readonly string INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini");
internal static CultureInfo _enCulture = new CultureInfo("en-US");
static ExplorerConfig()
{
_parser.Configuration.CommentString = "#";
PanelDragger.OnFinishResize += PanelDragger_OnFinishResize;
}
// Actual configs
public KeyCode Main_Menu_Toggle = KeyCode.F7;
public bool Force_Unlock_Mouse = true;
public int Default_Page_Limit = 25;
public string Default_Output_Path = Path.Combine(ExplorerCore.EXPLORER_FOLDER, "Output");
public bool Log_Unity_Debug = false;
public bool Hide_On_Startup = false;
public string Window_Anchors = DEFAULT_WINDOW_ANCHORS;
private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.10,0.78,0.95";
public static event Action OnConfigChanged;
internal static void InvokeConfigChanged()
{
OnConfigChanged?.Invoke();
}
public static void OnLoad()
{
Instance = new ExplorerConfig();
if (LoadSettings())
return;
SaveSettings();
}
public static bool LoadSettings()
{
if (!File.Exists(INI_PATH))
return false;
string ini = File.ReadAllText(INI_PATH);
var data = _parser.Parse(ini);
foreach (var config in data.Sections["Config"])
{
switch (config.KeyName)
{
case nameof(Main_Menu_Toggle):
Instance.Main_Menu_Toggle = (KeyCode)Enum.Parse(typeof(KeyCode), config.Value);
break;
case nameof(Force_Unlock_Mouse):
Instance.Force_Unlock_Mouse = bool.Parse(config.Value);
break;
case nameof(Default_Page_Limit):
Instance.Default_Page_Limit = int.Parse(config.Value);
break;
case nameof(Log_Unity_Debug):
Instance.Log_Unity_Debug = bool.Parse(config.Value);
break;
case nameof(Default_Output_Path):
Instance.Default_Output_Path = config.Value;
break;
case nameof(Hide_On_Startup):
Instance.Hide_On_Startup = bool.Parse(config.Value);
break;
case nameof(Window_Anchors):
Instance.Window_Anchors = config.Value;
break;
}
}
return true;
}
public static void SaveSettings()
{
var data = new IniParser.Model.IniData();
data.Sections.AddSection("Config");
var sec = data.Sections["Config"];
sec.AddKey(nameof(Main_Menu_Toggle), Instance.Main_Menu_Toggle.ToString());
sec.AddKey(nameof(Force_Unlock_Mouse), Instance.Force_Unlock_Mouse.ToString());
sec.AddKey(nameof(Default_Page_Limit), Instance.Default_Page_Limit.ToString());
sec.AddKey(nameof(Log_Unity_Debug), Instance.Log_Unity_Debug.ToString());
sec.AddKey(nameof(Default_Output_Path), Instance.Default_Output_Path);
sec.AddKey(nameof(Hide_On_Startup), Instance.Hide_On_Startup.ToString());
sec.AddKey(nameof(Window_Anchors), GetWindowAnchorsString());
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
File.WriteAllText(INI_PATH, data.ToString());
}
// ============ Window Anchors specific stuff ============== //
private static void PanelDragger_OnFinishResize()
{
Instance.Window_Anchors = GetWindowAnchorsString();
SaveSettings();
}
internal Vector4 GetWindowAnchorsVector()
{
try
{
var split = Window_Anchors.Split(',');
if (split.Length != 4)
throw new Exception();
Vector4 ret = Vector4.zero;
ret.x = float.Parse(split[0], _enCulture);
ret.y = float.Parse(split[1], _enCulture);
ret.z = float.Parse(split[2], _enCulture);
ret.w = float.Parse(split[3], _enCulture);
return ret;
}
catch
{
return DefaultWindowAnchors();
}
}
internal static string GetWindowAnchorsString()
{
try
{
var rect = PanelDragger.Instance.Panel;
return string.Format(_enCulture, "{0},{1},{2},{3}", new object[]
{
rect.anchorMin.x,
rect.anchorMin.y,
rect.anchorMax.x,
rect.anchorMax.y
});
}
catch
{
return DEFAULT_WINDOW_ANCHORS;
}
}
internal static Vector4 DefaultWindowAnchors()
{
Instance.Window_Anchors = DEFAULT_WINDOW_ANCHORS;
return new Vector4(0.25f, 0.1f, 0.78f, 0.95f);
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace UnityExplorer.Core.Config
{
public interface IConfigElement
{
string Name { get; }
string Description { get; }
bool IsInternal { get; }
Type ElementType { get; }
object BoxedValue { get; set; }
object DefaultValue { get; }
object GetLoaderConfigValue();
void RevertToDefaultValue();
Action OnValueChangedNotify { get; set; }
}
}

View File

@ -6,30 +6,30 @@ using UnityExplorer.Core.Input;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnityExplorer.Core;
using UnityExplorer.UI;
using System.Collections;
#if ML
using Harmony;
#else
using HarmonyLib;
#endif
namespace UnityExplorer.UI.Utility
namespace UnityExplorer.Core.Input
{
public class CursorUnlocker
{
public static bool Unlock
{
get => m_forceUnlock;
set => SetForceUnlock(value);
set
{
m_forceUnlock = value;
UpdateCursorControl();
}
}
private static bool m_forceUnlock;
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
public static bool ShouldForceMouse => UIManager.ShowMenu && Unlock;
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
@ -43,83 +43,39 @@ namespace UnityExplorer.UI.Utility
public static void Init()
{
ExplorerConfig.OnConfigChanged += ModConfig_OnConfigChanged;
SetupPatches();
Unlock = true;
UpdateCursorControl();
Unlock = ConfigManager.Force_Unlock_Mouse.Value;
ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; };
if (ConfigManager.Aggressive_Force_Unlock.Value)
SetupAggressiveUnlock();
}
internal static void ModConfig_OnConfigChanged()
{
Unlock = ExplorerConfig.Instance.Force_Unlock_Mouse;
}
private static void SetupPatches()
public static void SetupAggressiveUnlock()
{
try
{
if (CursorType == null)
{
throw new Exception("Could not find Type 'UnityEngine.Cursor'!");
}
// Get current cursor state and enable cursor
try
{
//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(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_lockState))),
true);
TryPatch(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_visible))),
true);
TryPatch(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_EventSystem_set_current))),
true);
RuntimeProvider.Instance.StartCoroutine(AggressiveUnlockCoroutine());
}
catch (Exception e)
catch (Exception ex)
{
ExplorerCore.Log($"Exception on ForceUnlockCursor.Init! {e.GetType()}, {e.Message}");
ExplorerCore.LogWarning($"Exception setting up Aggressive Mouse Unlock: {ex}");
}
}
private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter)
private static readonly WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
private static IEnumerator AggressiveUnlockCoroutine()
{
try
while (true)
{
var harmony = ExplorerCore.Loader.HarmonyInstance;
yield return _waitForEndOfFrame;
System.Reflection.PropertyInfo prop = type.GetProperty(property);
if (setter) // setter is prefix
{
harmony.Patch(prop.GetSetMethod(), prefix: patch);
}
else // getter is postfix
{
harmony.Patch(prop.GetGetMethod(), postfix: patch);
}
}
catch (Exception e)
{
string suf = setter ? "set_" : "get_";
ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}");
if (UIManager.ShowMenu)
UpdateCursorControl();
}
}
@ -128,16 +84,24 @@ namespace UnityExplorer.UI.Utility
try
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
if (ShouldActuallyUnlock)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
if (UIManager.EventSys)
SetEventSystem();
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
if (UIManager.EventSys)
ReleaseEventSystem();
}
m_currentlySettingCursor = false;
}
catch (Exception e)
@ -154,25 +118,19 @@ namespace UnityExplorer.UI.Utility
public static void SetEventSystem()
{
// temp disabled for new InputSystem
if (InputManager.CurrentType == InputType.InputSystem)
return;
// Disable current event system object
if (m_lastEventSystem || EventSystem.current)
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
{
if (!m_lastEventSystem)
m_lastEventSystem = EventSystem.current;
//ExplorerCore.Log("Disabling current event system...");
m_lastEventSystem = EventSystem.current;
m_lastEventSystem.enabled = false;
//m_lastEventSystem.gameObject.SetActive(false);
}
// Set to our current system
m_settingEventSystem = true;
EventSystem.current = UIManager.EventSys;
UIManager.EventSys.enabled = true;
EventSystem.current = UIManager.EventSys;
InputManager.ActivateUIModule();
m_settingEventSystem = false;
}
@ -182,10 +140,9 @@ namespace UnityExplorer.UI.Utility
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (m_lastEventSystem)
if (m_lastEventSystem && m_lastEventSystem.gameObject.activeSelf)
{
m_lastEventSystem.enabled = true;
//m_lastEventSystem.gameObject.SetActive(true);
m_settingEventSystem = true;
EventSystem.current = m_lastEventSystem;
@ -194,17 +151,41 @@ namespace UnityExplorer.UI.Utility
}
}
[HarmonyPrefix]
// Patches
private static void SetupPatches()
{
try
{
if (CursorType == null)
throw new Exception("Could not load Type 'UnityEngine.Cursor'!");
// Get current cursor state and enable cursor
m_lastLockMode = (CursorLockMode?)CursorType.GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
?? CursorLockMode.None;
m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
?? false;
ExplorerCore.Loader.SetupPatches();
}
catch (Exception e)
{
ExplorerCore.Log($"Error on CursorUnlocker.Init! {e.GetType()}, {e.Message}");
}
}
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
if (!m_settingEventSystem)
if (!m_settingEventSystem && value != UIManager.EventSys)
{
m_lastEventSystem = value;
m_lastInputModule = value?.currentInputModule;
if (UIManager.ShowMenu)
if (ShouldActuallyUnlock)
{
value = UIManager.EventSys;
value.enabled = true;
}
}
}
@ -213,32 +194,26 @@ namespace UnityExplorer.UI.Utility
// 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.
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
{
m_lastLockMode = value;
if (ShouldForceMouse)
{
if (ShouldActuallyUnlock)
value = CursorLockMode.None;
}
}
}
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
if (ShouldActuallyUnlock)
value = true;
}
}
}
}
}
}

View File

@ -15,9 +15,7 @@ namespace UnityExplorer.Core.Input
BaseInputModule UIModule { get; }
PointerEventData InputPointerEvent { get; }
void AddUIInputModule();
void ActivateModule();
}
}
}

View File

@ -27,7 +27,6 @@ namespace UnityExplorer.Core.Input
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
public static BaseInputModule UIInput => m_inputModule.UIModule;
public static PointerEventData InputPointerEvent => m_inputModule.InputPointerEvent;
public static void ActivateUIModule() => m_inputModule.ActivateModule();
@ -52,10 +51,12 @@ namespace UnityExplorer.Core.Input
if (m_inputModule == null)
{
ExplorerCore.LogWarning("Could not find any Input module!");
ExplorerCore.LogWarning("Could not find any Input Module Type!");
m_inputModule = new NoInput();
CurrentType = InputType.None;
}
CursorUnlocker.Init();
}
}
}

View File

@ -5,6 +5,8 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
using System.Collections.Generic;
using UnityExplorer.UI.Inspectors;
using System.Linq;
namespace UnityExplorer.Core.Input
{
@ -83,16 +85,28 @@ namespace UnityExplorer.Core.Input
}
internal static Dictionary<KeyCode, object> ActualKeyDict = new Dictionary<KeyCode, object>();
internal static Dictionary<string, string> enumNameFixes = new Dictionary<string, string>
{
{ "Control", "Ctrl" },
{ "Return", "Enter" },
{ "Alpha", "Digit" },
{ "Keypad", "Numpad" },
{ "Numlock", "NumLock" },
{ "Print", "PrintScreen" },
{ "BackQuote", "Backquote" }
};
internal object GetActualKey(KeyCode key)
{
if (!ActualKeyDict.ContainsKey(key))
{
var s = key.ToString();
if (s.Contains("Control"))
s = s.Replace("Control", "Ctrl");
else if (s.Contains("Return"))
s = "Enter";
try
{
if (enumNameFixes.First(it => s.Contains(it.Key)) is KeyValuePair<string, string> entry)
s = s.Replace(entry.Key, entry.Value);
}
catch { }
var parsed = Enum.Parse(TKey, s);
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
@ -131,41 +145,74 @@ namespace UnityExplorer.Core.Input
// UI Input
//public Type TInputSystemUIInputModule
// => m_tUIInputModule
// ?? (m_tUIInputModule = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
//internal Type m_tUIInputModule;
public Type TInputSystemUIInputModule
=> m_tUIInputModule
?? (m_tUIInputModule = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
internal Type m_tUIInputModule;
public BaseInputModule UIModule => null; // m_newInputModule;
//internal BaseInputModule m_newInputModule;
public PointerEventData InputPointerEvent => null;
public BaseInputModule UIModule => m_newInputModule;
internal BaseInputModule m_newInputModule;
public void AddUIInputModule()
{
// if (TInputSystemUIInputModule != null)
// {
//#if CPP
// // m_newInputModule = UIManager.CanvasRoot.AddComponent(Il2CppType.From(TInputSystemUIInputModule)).TryCast<BaseInputModule>();
//#else
// m_newInputModule = (BaseInputModule)UIManager.CanvasRoot.AddComponent(TInputSystemUIInputModule);
//#endif
// }
// else
// {
// ExplorerCore.LogWarning("New input system: Could not find type by name 'UnityEngine.InputSystem.UI.InputSystemUIInputModule'");
// }
if (TInputSystemUIInputModule == null)
{
ExplorerCore.LogWarning("Unable to find UI Input Module Type, Input will not work!");
return;
}
var assetType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionAsset");
m_newInputModule = RuntimeProvider.Instance.AddComponent<BaseInputModule>(UIManager.CanvasRoot, TInputSystemUIInputModule);
var asset = RuntimeProvider.Instance.CreateScriptable(assetType)
.Cast(assetType);
inputExtensions = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionSetupExtensions");
var addMap = inputExtensions.GetMethod("AddActionMap", new Type[] { assetType, typeof(string) });
var map = addMap.Invoke(null, new object[] { asset, "UI" })
.Cast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
CreateAction(map, "point", new[] { "<Mouse>/position" }, "point");
CreateAction(map, "click", new[] { "<Mouse>/leftButton" }, "leftClick");
CreateAction(map, "rightClick", new[] { "<Mouse>/rightButton" }, "rightClick");
CreateAction(map, "scrollWheel", new[] { "<Mouse>/scroll" }, "scrollWheel");
UI_Enable = map.GetType().GetMethod("Enable");
UI_Enable.Invoke(map, new object[0]);
UI_ActionMap = map;
}
private Type inputExtensions;
private object UI_ActionMap;
private MethodInfo UI_Enable;
private void CreateAction(object map, string actionName, string[] bindings, string propertyName)
{
var inputActionType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputAction");
var addAction = inputExtensions.GetMethod("AddAction");
var action = addAction.Invoke(null, new object[] { map, actionName, default, null, null, null, null, null })
.Cast(inputActionType);
var addBinding = inputExtensions.GetMethod("AddBinding",
new Type[] { inputActionType, typeof(string), typeof(string), typeof(string), typeof(string) });
foreach (string binding in bindings)
addBinding.Invoke(null, new object[] { action.Cast(inputActionType), binding, null, null, null });
var refType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionReference");
var inputRef = refType.GetMethod("Create")
.Invoke(null, new object[] { action })
.Cast(refType);
TInputSystemUIInputModule
.GetProperty(propertyName)
.SetValue(m_newInputModule.Cast(TInputSystemUIInputModule), inputRef, null);
}
public void ActivateModule()
{
//#if CPP
// // m_newInputModule.ActivateModule();
//#else
// m_newInputModule.ActivateModule();
//#endif
m_newInputModule.ActivateModule();
UI_Enable.Invoke(UI_ActionMap, new object[0]);
}
}
}
}

View File

@ -44,13 +44,6 @@ namespace UnityExplorer.Core.Input
public BaseInputModule UIModule => m_inputModule;
internal StandaloneInputModule m_inputModule;
public PointerEventData InputPointerEvent =>
#if CPP
m_inputModule.m_InputPointerEvent;
#else
null;
#endif
public void AddUIInputModule()
{
m_inputModule = UIManager.CanvasRoot.gameObject.AddComponent<StandaloneInputModule>();
@ -61,4 +54,4 @@ namespace UnityExplorer.Core.Input
m_inputModule.ActivateModule();
}
}
}
}

View File

@ -16,8 +16,7 @@ namespace UnityExplorer.Core.Input
public bool GetMouseButtonDown(int btn) => false;
public BaseInputModule UIModule => null;
public PointerEventData InputPointerEvent => null;
public void ActivateModule() { }
public void AddUIInputModule() { }
}
}
}

View File

@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Inspectors.Reflection;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main.Home;
namespace UnityExplorer.Core.Inspectors
{
public class InspectorManager
{
public static InspectorManager Instance { get; private set; }
internal static InspectorManagerUI UI;
public InspectorManager()
{
Instance = this;
UI = new InspectorManagerUI();
UI.ConstructInspectorPane();
}
public InspectorBase m_activeInspector;
public readonly List<InspectorBase> m_currentInspectors = new List<InspectorBase>();
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, CacheObjectBase parentMember = null)
{
obj = ReflectionProvider.Instance.Cast(obj, ReflectionProvider.Instance.GetActualType(obj));
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);
if (inspector is ReflectionInspector ri)
ri.ParentMember = parentMember;
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();
UI.OnSetInspectorTab(inspector);
}
public void UnsetInspectorTab()
{
if (m_activeInspector == null)
return;
m_activeInspector.SetInactive();
UI.OnUnsetInspectorTab();
m_activeInspector = null;
}
}
}

View File

@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.UI;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI.Main.Home.Inspectors;
namespace UnityExplorer.Core.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;
public GameObjectInspectorUI UIModule;
// sub modules
internal static ChildList s_childList;
internal static ComponentList s_compList;
internal static GameObjectControls s_controls;
internal static bool m_UIConstructed;
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();
UIModule.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;
UIModule.RefreshTopInfo();
s_childList.RefreshChildObjectList();
s_compList.RefreshComponentList();
s_controls.RefreshControls();
if (GameObjectControls.s_sliderChangedWanted)
GameObjectControls.UpdateSliderControl();
}
public override void CreateUIModule()
{
base.BaseUI = UIModule = new GameObjectInspectorUI();
}
}
}

View File

@ -1,184 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Unity;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Main.Home.Inspectors;
namespace UnityExplorer.Core.Inspectors
{
public class InspectUnderMouse
{
public enum MouseInspectMode
{
World,
UI
}
public static bool Enabled { get; set; }
public static MouseInspectMode Mode { get; set; }
private static GameObject s_lastHit;
private static Vector3 s_lastMousePos;
internal static MouseInspectorUI UI;
static InspectUnderMouse()
{
UI = new MouseInspectorUI();
}
public static void StartInspect()
{
Enabled = true;
MainMenu.Instance.MainPanel.SetActive(false);
UI.s_UIContent.SetActive(true);
// recache Graphic Raycasters each time we start
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
m_gCasters = new GraphicRaycaster[casters.Length];
for (int i = 0; i < casters.Length; i++)
{
m_gCasters[i] = casters[i].Cast(typeof(GraphicRaycaster)) as GraphicRaycaster;
}
}
public static void StopInspect()
{
Enabled = false;
MainMenu.Instance.MainPanel.SetActive(true);
UI.s_UIContent.SetActive(false);
ClearHitData();
}
internal static GraphicRaycaster[] m_gCasters;
public static void UpdateInspect()
{
if (InputManager.GetKeyDown(KeyCode.Escape))
{
StopInspect();
return;
}
var mousePos = InputManager.MousePosition;
if (mousePos != s_lastMousePos)
UpdatePosition(mousePos);
if (!UnityHelper.MainCamera)
return;
// actual inspect raycast
switch (Mode)
{
case MouseInspectMode.UI:
RaycastUI(mousePos); break;
case MouseInspectMode.World:
RaycastWorld(mousePos); break;
}
}
internal static void OnHitGameObject(GameObject obj)
{
if (obj != s_lastHit)
{
s_lastHit = obj;
UI.s_objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
UI.s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
}
if (InputManager.GetMouseButtonDown(0))
{
StopInspect();
InspectorManager.Instance.Inspect(obj);
}
}
internal static void RaycastWorld(Vector2 mousePos)
{
var ray = UnityHelper.MainCamera.ScreenPointToRay(mousePos);
Physics.Raycast(ray, out RaycastHit hit, 1000f);
if (hit.transform)
{
var obj = hit.transform.gameObject;
OnHitGameObject(obj);
}
else
{
if (s_lastHit)
ClearHitData();
}
}
internal static void RaycastUI(Vector2 mousePos)
{
var ped = new PointerEventData(null)
{
position = mousePos
};
#if MONO
var list = new List<RaycastResult>();
#else
var list = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
#endif
foreach (var gr in m_gCasters)
{
gr.Raycast(ped, list);
if (list.Count > 0)
{
foreach (var hit in list)
{
if (hit.gameObject)
{
var obj = hit.gameObject;
OnHitGameObject(obj);
break;
}
}
}
else
{
if (s_lastHit)
ClearHitData();
}
}
}
internal static void UpdatePosition(Vector2 mousePos)
{
s_lastMousePos = mousePos;
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
UI.s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
float yFix = mousePos.y < 120 ? 80 : -80;
UI.s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
}
internal static void ClearHitData()
{
s_lastHit = null;
UI.s_objNameLabel.text = "No hits...";
UI.s_objPathLabel.text = "";
}
}
}

View File

@ -1,54 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UnityEngine.UI;
using System.IO;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main.Home.Inspectors;
namespace UnityExplorer.Core.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) { }
internal InstanceInspectorUI InstanceUI;
public void CreateInstanceUIModule()
{
InstanceUI = new InstanceInspectorUI(this);
}
internal 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);
base.ReflectionUI.m_sliderScroller.m_slider.value = 1f;
}
}
}

View File

@ -1,338 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
namespace UnityExplorer.Core.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)
{
DestroySubContent();
//// 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
}
}

View File

@ -7,18 +7,18 @@ using System.Reflection;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer.Core
namespace UnityExplorer
{
public static class ReflectionUtility
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
/// <summary>
/// Helper for IL2CPP to get the underlying true Type (Unhollowed) of the object.
/// </summary>
/// <param name="obj">The object to get the true Type for.</param>
/// <returns>The most accurate Type of the object which could be identified.</returns>
public static Type GetType(this object obj)
public static Type GetActualType(this object obj)
{
if (obj == null)
return null;
@ -32,7 +32,7 @@ namespace UnityExplorer.Core
/// <param name="obj">The object to cast</param>
/// <returns>The object, cast to the underlying Type if possible, otherwise the original object.</returns>
public static object Cast(this object obj)
=> Cast(obj, GetType(obj));
=> ReflectionProvider.Instance.Cast(obj, GetActualType(obj));
/// <summary>
/// Cast an object to a Type, if possible.
@ -43,6 +43,9 @@ namespace UnityExplorer.Core
public static object Cast(this object obj, Type castTo)
=> ReflectionProvider.Instance.Cast(obj, castTo);
public static T TryCast<T>(this object obj)
=> ReflectionProvider.Instance.TryCast<T>(obj);
/// <summary>
/// Check if the provided Type is assignable to IEnumerable.
/// </summary>
@ -59,7 +62,10 @@ namespace UnityExplorer.Core
public static bool IsDictionary(this Type t)
=> ReflectionProvider.Instance.IsAssignableFrom(typeof(IDictionary), t);
public static bool LoadModule(string module)
/// <summary>
/// [INTERNAL] Used to load Unhollowed DLLs in IL2CPP.
/// </summary>
internal static bool LoadModule(string module)
=> ReflectionProvider.Instance.LoadModule(module);
// cache for GetTypeByName
@ -102,7 +108,7 @@ namespace UnityExplorer.Core
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetType(obj));
public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj));
/// <summary>
/// Get all base types of the provided Type, including itself.
@ -160,6 +166,77 @@ namespace UnityExplorer.Core
}
}
internal static Dictionary<Type, Dictionary<string, FieldInfo>> s_cachedFieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
public static FieldInfo GetFieldInfo(Type type, string fieldName)
{
if (!s_cachedFieldInfos.ContainsKey(type))
s_cachedFieldInfos.Add(type, new Dictionary<string, FieldInfo>());
if (!s_cachedFieldInfos[type].ContainsKey(fieldName))
s_cachedFieldInfos[type].Add(fieldName, type.GetField(fieldName, AllFlags));
return s_cachedFieldInfos[type][fieldName];
}
internal static Dictionary<Type, Dictionary<string, PropertyInfo>> s_cachedPropInfos = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
if (!s_cachedPropInfos.ContainsKey(type))
s_cachedPropInfos.Add(type, new Dictionary<string, PropertyInfo>());
if (!s_cachedPropInfos[type].ContainsKey(propertyName))
s_cachedPropInfos[type].Add(propertyName, type.GetProperty(propertyName, AllFlags));
return s_cachedPropInfos[type][propertyName];
}
internal static Dictionary<Type, Dictionary<string, MethodInfo>> s_cachedMethodInfos = new Dictionary<Type, Dictionary<string, MethodInfo>>();
public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes)
{
if (!s_cachedMethodInfos.ContainsKey(type))
s_cachedMethodInfos.Add(type, new Dictionary<string, MethodInfo>());
var sig = methodName;
if (argumentTypes != null)
{
sig += "(";
for (int i = 0; i < argumentTypes.Length; i++)
{
if (i > 0)
sig += ",";
sig += argumentTypes[i].FullName;
}
sig += ")";
}
try
{
if (!s_cachedMethodInfos[type].ContainsKey(sig))
{
if (argumentTypes != null)
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags, null, argumentTypes, null));
else
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags));
}
return s_cachedMethodInfos[type][sig];
}
catch (AmbiguousMatchException)
{
ExplorerCore.LogWarning($"AmbiguousMatchException trying to get method '{sig}'");
return null;
}
catch (Exception e)
{
ExplorerCore.LogWarning($"{e.GetType()} trying to get method '{sig}': {e.Message}\r\n{e.StackTrace}");
return null;
}
}
/// <summary>
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
/// </summary>
@ -172,10 +249,9 @@ namespace UnityExplorer.Core
{
while (e.InnerException != null)
{
#if CPP
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break;
#endif
e = e.InnerException;
}
}

View File

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
namespace UnityExplorer.Core.Runtime.Il2Cpp
@ -9,6 +10,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
public static class ICallManager
{
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
/// <summary>
@ -26,9 +30,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
IntPtr ptr = il2cpp_resolve_icall(signature);
if (ptr == IntPtr.Zero)
{
throw new MissingMethodException($"Could not resolve internal call by name '{signature}'!");
}
throw new MissingMethodException($"Could not find any iCall with the signature '{signature}'!");
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
iCallCache.Add(signature, iCall);
@ -36,8 +38,35 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return (T)iCall;
}
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
private static readonly Dictionary<string, Delegate> s_unreliableCache = new Dictionary<string, Delegate>();
/// <summary>
/// Get an iCall which may be one of multiple different signatures (ie, it changed in different Unity versions).
/// Each possible signature must have the same Type pattern, it can only vary by name.
/// </summary>
public static T GetICallUnreliable<T>(IEnumerable<string> possibleSignatures) where T : Delegate
{
// use the first possible signature as the 'key'.
string key = possibleSignatures.First();
if (s_unreliableCache.ContainsKey(key))
return (T)s_unreliableCache[key];
T iCall;
IntPtr ptr;
foreach (var sig in possibleSignatures)
{
ptr = il2cpp_resolve_icall(sig);
if (ptr != IntPtr.Zero)
{
iCall = (T)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
s_unreliableCache.Add(key, iCall);
return iCall;
}
}
throw new MissingMethodException($"Could not find any iCall from list of provided signatures starting with '{key}'!");
}
}
}
#endif

View File

@ -11,6 +11,9 @@ using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
@ -33,7 +36,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
var addMethod = typeof(Application).GetMethod("add_logMessageReceived", BF.Static | BF.Public, null, new[] { logType }, null);
addMethod.Invoke(null, new[]
{
castMethod.Invoke(null, new[] { new Action<string, string, LogType>(ExplorerCore.Instance.OnUnityLog) })
castMethod.Invoke(null, new[] { new Action<string, string, LogType>(Application_logMessageReceived) })
});
}
catch
@ -42,11 +45,55 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
}
}
public override void StartConsoleCoroutine(IEnumerator routine)
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.Log(condition, type, true);
}
public override void StartCoroutine(IEnumerator routine)
{
Il2CppCoroutine.Start(routine);
}
public override void Update()
{
Il2CppCoroutine.Process();
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return obj.AddComponent(Il2CppType.From(type)).TryCast<T>();
}
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(Il2CppType.From(type));
}
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
raycaster.Raycast(data, il2cppList);
if (il2cppList.Count > 0)
list.AddRange(il2cppList.ToArray());
}
public override bool IsReferenceEqual(object a, object b)
{
if (a.TryCast<UnityEngine.Object>() is UnityEngine.Object ua)
{
var ub = b.TryCast<UnityEngine.Object>();
if (ub && ua.m_CachedPtr == ub.m_CachedPtr)
return true;
}
return base.IsReferenceEqual(a, b);
}
// LayerMask.LayerToName
internal delegate IntPtr d_LayerToName(int layer);
public override string LayerToName(int layer)
@ -55,27 +102,35 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return IL2CPP.Il2CppStringToManaged(iCall.Invoke(layer));
}
// Resources.FindObjectsOfTypeAll
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
{
var iCall = ICallManager.GetICall<d_FindObjectsOfTypeAll>("UnityEngine.Resources::FindObjectsOfTypeAll");
var cppType = Il2CppType.From(type);
var iCall = ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(new[]
{
"UnityEngine.Resources::FindObjectsOfTypeAll",
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
});
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(cppType.Pointer));
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(Il2CppType.From(type).Pointer));
}
public override int GetSceneHandle(Scene scene)
=> scene.handle;
//Scene.GetRootGameObjects();
// Scene.GetRootGameObjects();
internal delegate void d_GetRootGameObjects(int handle, IntPtr list);
public override GameObject[] GetRootGameObjects(Scene scene) => GetRootGameObjects(scene.handle);
public static GameObject[] GetRootGameObjects(int handle)
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
int handle = scene.handle;
if (handle == -1)
return new GameObject[0];
@ -93,7 +148,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return list.ToArray();
}
//Scene.rootCount;
// Scene.rootCount
internal delegate int d_GetRootCountInternal(int handle);
@ -104,10 +159,121 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return ICallManager.GetICall<d_GetRootCountInternal>("UnityEngine.SceneManagement.Scene::GetRootCountInternal")
.Invoke(handle);
}
internal static bool triedToGetColorBlockProps;
internal static PropertyInfo _normalColorProp;
internal static PropertyInfo _highlightColorProp;
internal static PropertyInfo _pressedColorProp;
internal static PropertyInfo _disabledColorProp;
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
colors.colorMultiplier = 1;
object boxed = (object)colors;
if (!triedToGetColorBlockProps)
{
triedToGetColorBlockProps = true;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "normalColor") is PropertyInfo norm && norm.CanWrite)
_normalColorProp = norm;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "highlightedColor") is PropertyInfo high && high.CanWrite)
_highlightColorProp = high;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "pressedColor") is PropertyInfo pres && pres.CanWrite)
_pressedColorProp = pres;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "disabledColor") is PropertyInfo disa && disa.CanWrite)
_disabledColorProp = disa;
}
try
{
if (normal != null)
{
if (_normalColorProp != null)
_normalColorProp.SetValue(boxed, (Color)normal);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_NormalColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)normal);
}
if (highlighted != null)
{
if (_highlightColorProp != null)
_highlightColorProp.SetValue(boxed, (Color)highlighted);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_HighlightedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)highlighted);
}
if (pressed != null)
{
if (_pressedColorProp != null)
_pressedColorProp.SetValue(boxed, (Color)pressed);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_PressedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)pressed);
}
if (disabled != null)
{
if (_disabledColorProp != null)
_disabledColorProp.SetValue(boxed, (Color)disabled);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_DisabledColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)disabled);
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
colors = (ColorBlock)boxed;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock _colorBlock)
{
try
{
selectable = selectable.TryCast<Selectable>();
ReflectionUtility.GetPropertyInfo(typeof(Selectable), "m_Colors")
.SetValue(selectable, _colorBlock, null);
ReflectionUtility.GetMethodInfo(typeof(Selectable), "OnSetProperty", new Type[0])
.Invoke(selectable, new object[0]);
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
public override void FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
{
PropertyInfo pi;
foreach (var name in possibleNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
base.FindSingleton(possibleNames, type, flags, instances);
}
}
}
public static class UnityEventExtensions
public static class Il2CppExtensions
{
public static void AddListener(this UnityEvent action, Action listener)
{
@ -118,6 +284,9 @@ public static class UnityEventExtensions
{
action.AddListener(listener);
}
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlHeight = value;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlWidth = value;
}
#endif

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityExplorer.Core.Unity;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
using System.Reflection;
@ -33,6 +32,43 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return Il2CppCast(obj, castTo);
}
public override T TryCast<T>(object obj)
{
try
{
return (T)Il2CppCast(obj, typeof(T));
}
catch
{
return default;
}
}
public override void BoxStringToType(ref object value, Type castTo)
{
if (castTo == typeof(Il2CppSystem.String))
value = (Il2CppSystem.String)(value as string);
else
value = (Il2CppSystem.Object)(value as string);
}
public override string UnboxString(object value)
{
if (value is string s)
return s;
s = null;
// strings boxed as Il2CppSystem.Objects can behave weirdly.
// GetActualType will find they are a string, but if its boxed
// then we need to unbox it like this...
if (value is Il2CppSystem.Object cppObject)
s = cppObject.ToString();
else if (value is Il2CppSystem.String cppString)
s = cppString;
return s;
}
public override string ProcessTypeNameInString(Type type, string theString, ref string typeName)
{
if (!Il2CppTypeNotNull(type))
@ -109,6 +145,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
Type ret = Type.GetType(name);
// Thanks to Slaynash for this deobfuscation snippet!
if (ret == null)
{
string baseName = cppType.FullName;
@ -154,21 +191,24 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
/// <returns>The object, as the type (or a normal C# object) if successful or the input value if not.</returns>
public static object Il2CppCast(object obj, Type castTo)
{
if (!(obj is Il2CppSystem.Object ilObj))
if (!(obj is Il2CppSystem.Object cppObj))
return obj;
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
return obj;
IntPtr castFromPtr = il2cpp_object_get_class(ilObj.Pointer);
IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
return obj;
return null;
if (RuntimeSpecificsStore.IsInjected(castToPtr))
return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
return Activator.CreateInstance(castTo, ilObj.Pointer);
if (castTo == typeof(string))
return cppObj.ToString();
return Activator.CreateInstance(castTo, cppObj.Pointer);
}
/// <summary>
@ -319,6 +359,112 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return false;
}
internal static readonly Dictionary<Type, MethodInfo> s_getEnumeratorMethods = new Dictionary<Type, MethodInfo>();
internal static readonly Dictionary<Type, EnumeratorInfo> s_enumeratorInfos = new Dictionary<Type, EnumeratorInfo>();
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
public override IEnumerable EnumerateEnumerable(object value)
{
if (value == null)
return null;
var cppEnumerable = (value as Il2CppSystem.Object)?.TryCast<Il2CppSystem.Collections.IEnumerable>();
if (cppEnumerable != null)
{
var type = value.GetType();
if (!s_getEnumeratorMethods.ContainsKey(type))
s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator"));
var enumerator = s_getEnumeratorMethods[type].Invoke(value, null);
var enumeratorType = enumerator.GetType();
if (!s_enumeratorInfos.ContainsKey(enumeratorType))
{
s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo
{
current = enumeratorType.GetProperty("Current"),
moveNext = enumeratorType.GetMethod("MoveNext"),
});
}
var info = s_enumeratorInfos[enumeratorType];
// iterate
var list = new List<object>();
while ((bool)info.moveNext.Invoke(enumerator, null))
list.Add(info.current.GetValue(enumerator));
return list;
}
return null;
}
public override IDictionary EnumerateDictionary(object value, Type typeOfKeys, Type typeOfValues)
{
var valueType = ReflectionUtility.GetActualType(value);
var keyList = new List<object>();
var valueList = new List<object>();
var hashtable = value.Cast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable;
if (hashtable != null)
{
EnumerateCppHashtable(hashtable, keyList, valueList);
}
else
{
var keys = valueType.GetProperty("Keys").GetValue(value, null);
var values = valueType.GetProperty("Values").GetValue(value, null);
EnumerateCppIDictionary(keys, keyList);
EnumerateCppIDictionary(values, valueList);
}
var dict = Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(typeOfKeys, typeOfValues))
as IDictionary;
for (int i = 0; i < keyList.Count; i++)
dict.Add(keyList[i], valueList[i]);
return dict;
}
private void EnumerateCppIDictionary(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 EnumerateCppHashtable(Il2CppSystem.Collections.Hashtable hashtable, List<object> keys, List<object> values)
{
for (int i = 0; i < hashtable.buckets.Count; i++)
{
var bucket = hashtable.buckets[i];
if (bucket == null || bucket.key == null)
continue;
keys.Add(bucket.key);
values.Add(bucket.val);
}
}
// ~~~~~~~~~~ not used ~~~~~~~~~~~~
// cached il2cpp unbox methods

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.Core.CSharp
namespace UnityExplorer.Core.Runtime.Mono
{
public class DummyBehaviour : MonoBehaviour
{

View File

@ -6,7 +6,10 @@ using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.CSharp;
@ -18,27 +21,52 @@ namespace UnityExplorer.Core.Runtime.Mono
{
Reflection = new MonoReflection();
TextureUtil = new MonoTextureUtil();
DummyBehaviour.Setup();
}
public override void SetupEvents()
{
Application.logMessageReceived += ExplorerCore.Instance.OnUnityLog;
//SceneManager.sceneLoaded += ExplorerCore.Instance.OnSceneLoaded1;
//SceneManager.activeSceneChanged += ExplorerCore.Instance.OnSceneLoaded2;
Application.logMessageReceived += Application_logMessageReceived;
}
public override void StartConsoleCoroutine(IEnumerator routine)
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.Log(condition, type, true);
}
public override void StartCoroutine(IEnumerator routine)
{
DummyBehaviour.Instance.StartCoroutine(routine);
}
public override void Update()
{
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return (T)obj.AddComponent(type);
}
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(type);
}
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
raycaster.Raycast(data, list);
}
public override string LayerToName(int layer)
=> LayerMask.LayerToName(layer);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
=> Resources.FindObjectsOfTypeAll(type);
private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionUtility.CommonFlags);
private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionUtility.AllFlags);
public override int GetSceneHandle(Scene scene)
{
@ -47,6 +75,9 @@ namespace UnityExplorer.Core.Runtime.Mono
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
return scene.GetRootGameObjects();
}
@ -54,15 +85,70 @@ namespace UnityExplorer.Core.Runtime.Mono
{
return scene.rootCount;
}
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
if (normal != null)
colors.normalColor = (Color)normal;
if (highlighted != null)
colors.highlightedColor = (Color)highlighted;
if (pressed != null)
colors.pressedColor = (Color)pressed;
if (disabled != null)
colors.disabledColor = (Color)disabled;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
{
selectable.colors = colors;
}
}
}
public static class MonoExtensions
{
public static void AddListener(this UnityEvent _event, Action listener)
{
_event.AddListener(new UnityAction(listener));
}
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
{
_event.AddListener(new UnityAction<T>(listener));
}
public static void Clear(this StringBuilder sb)
{
sb.Remove(0, sb.Length);
}
private static PropertyInfo pi_childControlHeight;
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlHeight == null)
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
pi_childControlHeight?.SetValue(group, value, null);
}
private static PropertyInfo pi_childControlWidth;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlWidth == null)
pi_childControlWidth = group.GetType().GetProperty("childControlWidth");
pi_childControlWidth?.SetValue(group, value, null);
}
}
#endif

View File

@ -12,6 +12,18 @@ namespace UnityExplorer.Core.Runtime.Mono
public override object Cast(object obj, Type castTo)
=> obj;
public override T TryCast<T>(object obj)
{
try
{
return (T)obj;
}
catch
{
return default;
}
}
// Vanilla GetType is fine for mono
public override Type GetActualType(object obj)
=> obj.GetType();
@ -27,6 +39,9 @@ namespace UnityExplorer.Core.Runtime.Mono
public override string ProcessTypeNameInString(Type type, string theString, ref string typeName)
=> theString;
// not necessary
public override void BoxStringToType(ref object _string, Type castTo) { }
}
}

View File

@ -52,9 +52,9 @@ namespace UnityExplorer.Core.Runtime.Mono
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.CommonFlags);
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.AllFlags);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.CommonFlags);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.AllFlags);
if (method != null)
return m_encodeToPNGMethod = method;

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -18,6 +19,8 @@ namespace UnityExplorer.Core.Runtime
public abstract object Cast(object obj, Type castTo);
public abstract T TryCast<T>(object obj);
public abstract bool IsAssignableFrom(Type toAssignTo, Type toAssignFrom);
public abstract bool IsReflectionSupported(Type type);
@ -25,5 +28,15 @@ namespace UnityExplorer.Core.Runtime
public abstract string ProcessTypeNameInString(Type type, string theString, ref string typeName);
public abstract bool LoadModule(string module);
public abstract void BoxStringToType(ref object _string, Type castTo);
public virtual string UnboxString(object value) => (string)value;
public virtual IDictionary EnumerateDictionary(object value, Type typeOfKeys, Type typeOfValues)
=> null;
public virtual IEnumerable EnumerateEnumerable(object value)
=> null;
}
}

View File

@ -2,15 +2,17 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer.Core.Runtime
// Intentionally project-wide namespace so that its always easily accessible.
namespace UnityExplorer
{
// Work in progress, this will be used to replace all the "if CPP / if MONO"
// pre-processor directives all over the codebase.
public abstract class RuntimeProvider
{
public static RuntimeProvider Instance;
@ -27,28 +29,61 @@ namespace UnityExplorer.Core.Runtime
public static void Init() =>
#if CPP
Instance = new Il2Cpp.Il2CppProvider();
Instance = new Core.Runtime.Il2Cpp.Il2CppProvider();
#else
Instance = new Mono.MonoProvider();
Instance = new Core.Runtime.Mono.MonoProvider();
#endif
public abstract void Initialize();
public abstract void SetupEvents();
public abstract void StartConsoleCoroutine(IEnumerator routine);
public abstract void StartCoroutine(IEnumerator routine);
public abstract void Update();
public virtual bool IsReferenceEqual(object a, object b) => ReferenceEquals(a, b);
// Unity API handlers
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
public abstract ScriptableObject CreateScriptable(Type type);
public abstract string LayerToName(int layer);
public abstract UnityEngine.Object[] FindObjectsOfTypeAll(Type type);
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
public abstract int GetSceneHandle(Scene scene);
public abstract GameObject[] GetRootGameObjects(Scene scene);
public abstract int GetRootCount(Scene scene);
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null);
public virtual void FindSingleton(string[] s_instanceNames, Type type, BindingFlags flags, List<object> instances)
{
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in s_instanceNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
}
}
}

View File

@ -1,204 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Reusable;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main.Home;
namespace UnityExplorer.Core.Inspectors
{
public class SceneExplorer
{
public static SceneExplorer Instance;
public static SceneExplorerUI UI;
internal static Action OnToggleShow;
public SceneExplorer()
{
Instance = this;
UI = new SceneExplorerUI();
UI.ConstructScenePane();
}
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];
internal GameObject[] m_allObjects = new GameObject[0];
internal GameObject m_selectedSceneObject;
internal int m_lastCount;
internal static GameObject DontDestroyObject
{
get
{
if (!s_dontDestroyObject)
{
s_dontDestroyObject = new GameObject("DontDestroyMe");
GameObject.DontDestroyOnLoad(s_dontDestroyObject);
}
return s_dontDestroyObject;
}
}
internal static GameObject s_dontDestroyObject;
public void Init()
{
RefreshSceneSelector();
}
public void Update()
{
if (SceneExplorerUI.Hiding || Time.realtimeSinceStartup - m_timeOfLastSceneUpdate < UPDATE_INTERVAL)
{
return;
}
RefreshSceneSelector();
if (!m_selectedSceneObject)
{
if (m_currentScene != default)
{
var rootObjects = RuntimeProvider.Instance.GetRootGameObjects(m_currentScene);
SetSceneObjectList(rootObjects);
}
}
else
{
RefreshSelectedSceneObject();
}
}
private void RefreshSceneSelector()
{
var newNames = new List<string>();
var newScenes = new List<Scene>();
if (m_currentScenes == null)
m_currentScenes = new Scene[0];
bool anyChange = SceneManager.sceneCount != m_currentScenes.Length - 1;
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene == default)
continue;
int handle = RuntimeProvider.Instance.GetSceneHandle(scene);
if (!anyChange && !m_currentScenes.Any(it => handle == RuntimeProvider.Instance.GetSceneHandle(it)))
anyChange = true;
newScenes.Add(scene);
newNames.Add(scene.name);
}
if (anyChange)
{
newNames.Add("DontDestroyOnLoad");
newScenes.Add(DontDestroyScene);
m_currentScenes = newScenes.ToArray();
UI.OnActiveScenesChanged(newNames);
SetTargetScene(newScenes[0]);
SearchPage.Instance.OnSceneChange();
}
}
public void SetTargetScene(int index)
=> SetTargetScene(m_currentScenes[index]);
public void SetTargetScene(Scene scene)
{
if (scene == default)
return;
m_currentScene = scene;
var rootObjs = RuntimeProvider.Instance.GetRootGameObjects(scene);
SetSceneObjectList(rootObjs);
m_selectedSceneObject = null;
UI.OnSceneSelected();
}
public void SetSceneObjectParent()
{
if (!m_selectedSceneObject || !m_selectedSceneObject.transform.parent?.gameObject)
{
m_selectedSceneObject = null;
SetTargetScene(m_currentScene);
}
else
{
SetTargetObject(m_selectedSceneObject.transform.parent.gameObject);
}
}
public void SetTargetObject(GameObject obj)
{
if (!obj)
return;
UI.OnGameObjectSelected(obj);
m_selectedSceneObject = obj;
RefreshSelectedSceneObject();
}
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();
}
internal void RefreshSceneObjectList()
{
m_timeOfLastSceneUpdate = Time.realtimeSinceStartup;
UI.RefreshSceneObjectList(m_allObjects, out int newCount);
m_lastCount = newCount;
}
internal static void InspectSelectedGameObject()
{
InspectorManager.Instance.Inspect(Instance.m_selectedSceneObject);
}
internal static void InvokeOnToggleShow()
{
OnToggleShow?.Invoke();
}
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Search
namespace UnityExplorer.Core.Search
{
internal enum ChildFilter
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Search
namespace UnityExplorer.Core.Search
{
internal enum SceneFilter
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Search
namespace UnityExplorer.Core.Search
{
internal enum SearchContext
{

View File

@ -4,11 +4,11 @@ using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Main.Search;
namespace UnityExplorer.Search
namespace UnityExplorer.Core.Search
{
public static class SearchProvider
{
@ -67,38 +67,8 @@ namespace UnityExplorer.Search
{
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ToLower().Contains(nameFilter))
continue;
#if CPP
// Only look for Properties in IL2CPP, not for Mono.
PropertyInfo pi;
foreach (var name in s_instanceNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
continue;
}
}
}
#endif
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in s_instanceNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
break;
}
}
}
RuntimeProvider.Instance.FindSingleton(s_instanceNames, type, flags, instances);
}
catch { }
}
@ -175,15 +145,9 @@ namespace UnityExplorer.Search
if (canGetGameObject)
{
#if MONO
var go = context == SearchContext.GameObject
? obj as GameObject
: (obj as Component).gameObject;
#else
var go = context == SearchContext.GameObject
? obj.TryCast<GameObject>()
: obj.TryCast<Component>().gameObject;
#endif
// scene check
if (sceneFilter != SceneFilter.Any)

36
src/Core/TestClass.cs Normal file
View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class TestClass
{
public static UI.Main.PanelDragger.ResizeTypes flags = UI.Main.PanelDragger.ResizeTypes.NONE;
#if CPP
public static string testStringOne = "Test";
public static Il2CppSystem.Object testStringTwo = "string boxed as cpp object";
public static Il2CppSystem.String testStringThree = "string boxed as cpp string";
public static string nullString = null;
public static Il2CppSystem.Collections.Hashtable testHashset;
public static Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object> testList;
static TestClass()
{
testHashset = new Il2CppSystem.Collections.Hashtable();
testHashset.Add("key1", "itemOne");
testHashset.Add("key2", "itemTwo");
testHashset.Add("key3", "itemThree");
testList = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object>(3);
testList.Add("One");
testList.Add("Two");
testList.Add("Three");
//testIList = list.TryCast<Il2CppSystem.Collections.IList>();
}
#endif
}
}

View File

@ -1,290 +0,0 @@
//using System.Collections;
//using System.Collections.Generic;
//using UnityExplorer.UI;
//using UnityEngine;
//using System;
//using System.Runtime.InteropServices;
//using System.Text;
//using UnityExplorer.Core.Runtime;
//namespace UnityExplorer.Core.Tests
//{
// internal enum TestByteEnum : byte
// {
// One,
// Two,
// Three,
// TwoFiftyFive = 255,
// }
// public static class StaticTestClass
// {
// public static int StaticProperty => 5;
// public static int StaticField = 69;
// public static List<string> StaticList = new List<string>
// {
// "one",
// "two",
// "three",
// };
// public static void StaticMethod() { }
// }
// public class TestClass
// {
// internal static TestByteEnum testingByte = TestByteEnum.One;
// public string AAALongString = @"1
//2
//3
//4
//5";
// 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 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;
// public static Sprite TestSprite;
//#if CPP
// public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest;
// public static Il2CppSystem.Collections.Generic.List<string> CppStringTest;
// public static Il2CppSystem.Collections.IList CppIList;
// //public static Il2CppSystem.Collections.Generic.Dictionary<string, string> CppDictTest;
// //public static Il2CppSystem.Collections.Generic.Dictionary<int, float> CppDictTest2;
//#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
// //TextureSpriteTest();
// 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");
// //CppDictTest = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
// //CppDictTest.Add("key1", "value1");
// //CppDictTest.Add("key2", "value2");
// //CppDictTest.Add("key3", "value3");
// //CppDictTest2 = new Il2CppSystem.Collections.Generic.Dictionary<int, float>();
// //CppDictTest2.Add(0, 0.5f);
// //CppDictTest2.Add(1, 0.5f);
// //CppDictTest2.Add(2, 0.5f);
//#endif
// }
// //private void TextureSpriteTest()
// //{
// // TestTexture = new Texture2D(32, 32, TextureFormat.ARGB32, false)
// // {
// // name = "TestTexture"
// // };
// // TestSprite = TextureUtilProvider.Instance.CreateSprite(TestTexture);
// // GameObject.DontDestroyOnLoad(TestTexture);
// // GameObject.DontDestroyOnLoad(TestSprite);
// // // test loading a tex from file
// // if (System.IO.File.Exists(@"D:\Downloads\test.png"))
// // {
// // var dataToLoad = System.IO.File.ReadAllBytes(@"D:\Downloads\test.png");
// // ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
// // }
// //}
// //public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
// //{
// // arg2 = "this is arg2";
// // return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'";
// //}
// // test a non-generic dictionary
// public Hashtable TestNonGenericDict()
// {
// return new Hashtable
// {
// { "One", 1 },
// { "Two", 2 },
// { "Three", 3 },
// };
// }
// // test HashSets
// public static HashSet<string> HashSetTest = new HashSet<string>
// {
// "One",
// "Two",
// "Three"
// };
// // Test indexed parameter
// public string this[int arg0, string arg1]
// {
// get
// {
// return $"arg0: {arg0}, arg1: {arg1}";
// }
// }
// // Test basic list
// public static List<string> TestList = new List<string>
// {
// "1",
// "2",
// "3",
// "etc..."
// };
// // Test a nested dictionary
// public static Dictionary<int, Dictionary<string, int>> NestedDictionary = new Dictionary<int, Dictionary<string, int>>
// {
// {
// 1,
// new Dictionary<string, int>
// {
// {
// "Sub 1", 123
// },
// {
// "Sub 2", 456
// },
// }
// },
// {
// 2,
// new Dictionary<string, int>
// {
// {
// "Sub 3", 789
// },
// {
// "Sub 4", 000
// },
// }
// },
// };
// // Test a basic method
// public static Color TestMethod(float r, float g, float b, float a)
// {
// return new Color(r, g, b, a);
// }
// // A method with default arguments
// public static Vector3 TestDefaultArgs(float arg0, float arg1, float arg2 = 5.0f)
// {
// return new Vector3(arg0, arg1, arg2);
// }
// }
//}

View File

@ -1,46 +0,0 @@
using System.Globalization;
using UnityEngine;
namespace UnityExplorer.Core.Unity
{
public static class ColorHelper
{
/// <summary>
/// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000
/// </summary>
public static string ToHex(this Color color)
{
byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255);
byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255);
byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255);
return $"{r:X2}{g:X2}{b:X2}";
}
/// <summary>
/// Assumes the string is a 6-digit RGB Hex color code, which it will parse into a UnityEngine.Color.
/// Eg, FF0000 -> RGBA(1,0,0,1)
/// </summary>
public static Color ToColor(this string _string)
{
_string = _string.Replace("#", "");
if (_string.Length != 6)
return Color.magenta;
var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber);
var color = new Color
{
r = (float)(r / (decimal)255),
g = (float)(g / (decimal)255),
b = (float)(b / (decimal)255),
a = 1
};
return color;
}
}
}

View File

@ -1,62 +0,0 @@
using UnityEngine;
namespace UnityExplorer.Core.Unity
{
public static class UnityHelper
{
private static Camera m_mainCamera;
public static Camera MainCamera
{
get
{
if (!m_mainCamera)
{
m_mainCamera = Camera.main;
}
return m_mainCamera;
}
}
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
{
var unityObj = obj as Object;
if (obj == null)
{
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;
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UnityExplorer.Core.Unity
{
public static class UnityHelpers
{
/// <summary>
/// Check if an object is null, and if it's a UnityEngine.Object then also check if it was destroyed.
/// </summary>
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
{
var unityObj = obj as Object;
if (obj == null)
{
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;
}
/// <summary>
/// Print a nice {X, Y, Z} output of the Vector3, formatted to 3 decimal places.
/// </summary>
public static string ToStringPretty(this Vector3 vec)
{
return $"{vec.x:F3}, {vec.y:F3}, {vec.z:F3}";
}
/// <summary>
/// Get the full Transform heirarchy path for this provided Transform.
/// </summary>
public static string GetTransformPath(this Transform transform, bool includeSelf = false)
{
string path = includeSelf
? transform.transform.name
: "";
while (transform.parent)
{
transform = transform.parent;
path = $"{transform.name}/{path}";
}
return path;
}
/// <summary>
/// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000
/// </summary>
public static string ToHex(this Color color)
{
byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255);
byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255);
byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255);
return $"{r:X2}{g:X2}{b:X2}";
}
/// <summary>
/// Assumes the string is a 6-digit RGB Hex color code, which it will parse into a UnityEngine.Color.
/// Eg, FF0000 -> RGBA(1,0,0,1)
/// </summary>
public static Color ToColor(this string _string)
{
_string = _string.Replace("#", "");
if (_string.Length != 6)
return Color.magenta;
var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber);
var color = new Color
{
r = (float)(r / (decimal)255),
g = (float)(g / (decimal)255),
b = (float)(b / (decimal)255),
a = 1
};
return color;
}
}
}

View File

@ -1,126 +1,113 @@
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Unity;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Inspectors;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Utility;
namespace UnityExplorer
{
public class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "3.2.8";
public const string VERSION = "3.3.15";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
public static ExplorerCore Instance { get; private set; }
public static IExplorerLoader Loader =>
#if ML
ExplorerMelonMod.Instance;
#elif BIE
ExplorerBepInPlugin.Instance;
#elif STANDALONE
ExplorerStandalone.Instance;
#endif
public static IExplorerLoader Loader { get; private set; }
public static string EXPLORER_FOLDER => Loader.ExplorerFolder;
// Prevent using ctor, must use Init method.
private ExplorerCore() { }
public ExplorerCore()
public static void Init(IExplorerLoader loader)
{
if (Instance != null)
{
Log("An instance of Explorer is already active!");
Log("An instance of UnityExplorer is already active!");
return;
}
Instance = this;
Loader = loader;
Instance = new ExplorerCore();
if (!Directory.Exists(EXPLORER_FOLDER))
Directory.CreateDirectory(EXPLORER_FOLDER);
if (!Directory.Exists(Loader.ExplorerFolder))
Directory.CreateDirectory(Loader.ExplorerFolder);
ExplorerConfig.OnLoad();
ConfigManager.Init(Loader.ConfigHandler);
RuntimeProvider.Init();
InputManager.Init();
CursorUnlocker.Init();
UIManager.ShowMenu = true;
Log($"{NAME} {VERSION} initialized.");
RuntimeProvider.Instance.StartCoroutine(SetupCoroutine());
}
// Do a delayed setup so that objects aren't destroyed instantly.
// This can happen for a multitude of reasons.
// Default delay is 1 second which is usually enough.
private static IEnumerator SetupCoroutine()
{
float f = Time.realtimeSinceStartup;
float delay = ConfigManager.Startup_Delay_Time.Value;
while (Time.realtimeSinceStartup - f < delay)
yield return null;
Log($"Creating UI, after delay of {delay} second(s).");
UIManager.Init();
//InspectorManager.Instance.Inspect(typeof(TestClass));
}
public static void Update()
{
UIManager.CheckUIInit();
RuntimeProvider.Instance.Update();
if (InspectUnderMouse.Enabled)
InspectUnderMouse.UpdateInspect();
else
UIManager.Update();
UIManager.Update();
}
public void OnUnityLog(string message, string stackTrace, LogType type)
public static void Log(object message)
=> Log(message, LogType.Log, false);
public static void LogWarning(object message)
=> Log(message, LogType.Warning, false);
public static void LogError(object message)
=> Log(message, LogType.Error, false);
internal static void Log(object message, LogType logType, bool isFromUnity = false)
{
if (!DebugConsole.LogUnity)
if (isFromUnity && !ConfigManager.Log_Unity_Debug.Value)
return;
message = $"[UNITY] {message}";
string log = message?.ToString() ?? "";
switch (type)
switch (logType)
{
case LogType.Assert:
case LogType.Log:
Log(message, true);
Loader.OnLogMessage(log);
DebugConsole.Log(log, Color.white);
break;
case LogType.Warning:
LogWarning(message, true);
Loader.OnLogWarning(log);
DebugConsole.Log(log, Color.yellow);
break;
case LogType.Exception:
case LogType.Error:
LogError(message, true);
case LogType.Exception:
Loader.OnLogError(log);
DebugConsole.Log(log, Color.red);
break;
}
}
public static void Log(object message, bool unity = false)
{
DebugConsole.Log(message?.ToString());
if (unity)
return;
Loader.OnLogMessage(message);
}
public static void LogWarning(object message, bool unity = false)
{
DebugConsole.Log(message?.ToString(), "FFFF00");
if (unity)
return;
Loader.OnLogWarning(message);
}
public static void LogError(object message, bool unity = false)
{
DebugConsole.Log(message?.ToString(), "FF0000");
if (unity)
return;
Loader.OnLogError(message);
}
}
}

View File

@ -1,22 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
<InputAssemblies Include="..\lib\mcs.dll" />
<InputAssemblies Include="..\lib\INIFileParser.dll" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
DebugInfo="false"
LibraryPath="..\lib\"
InputAssemblies="@(InputAssemblies)"
TargetKind="Dll"
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
<Target Name="ILRepacker" AfterTargets="Build">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
<InputAssemblies Include="..\lib\mcs.dll" />
<InputAssemblies Include="..\lib\INIFileParser.dll" Condition="'$(IsStandalone)' == 'true'"/>
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
DebugInfo="false"
LibraryPath="..\lib\"
InputAssemblies="@(InputAssemblies)"
TargetKind="Dll"
OutputFile="$(OutputPath)$(AssemblyName).dll" />
</Target>
</Project>

View File

@ -0,0 +1,73 @@
#if BIE
using BepInEx.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Config;
namespace UnityExplorer.Loader.BIE
{
public class BepInExConfigHandler : ConfigHandler
{
private ConfigFile Config => ExplorerBepInPlugin.Instance.Config;
private const string CTG_NAME = "UnityExplorer";
public override void Init()
{
// Not necessary
}
public override void RegisterConfigElement<T>(ConfigElement<T> config)
{
object[] tags = null;
if (config.IsInternal)
tags = new[] { "Advanced" };
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, new ConfigDescription(config.Description, null, tags));
entry.SettingChanged += (object o, EventArgs e) =>
{
config.Value = entry.Value;
};
}
public override T GetConfigValue<T>(ConfigElement<T> element)
{
if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry<T> configEntry))
return configEntry.Value;
else
throw new Exception("Could not get config entry '" + element.Name + "'");
}
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
{
if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry<T> configEntry))
configEntry.Value = value;
else
ExplorerCore.Log("Could not get config entry '" + element.Name + "'");
}
public override void LoadConfig()
{
foreach (var entry in ConfigManager.ConfigElements)
{
var key = entry.Key;
var def = new ConfigDefinition(CTG_NAME, key);
if (Config.ContainsKey(def) && Config[def] is ConfigEntryBase configEntry)
{
var config = entry.Value;
config.BoxedValue = configEntry.BoxedValue;
}
}
}
public override void SaveConfig()
{
// not required
}
}
}
#endif

View File

@ -0,0 +1,149 @@
#if BIE
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Config;
using UnityExplorer.Loader.BIE;
using UnityEngine;
using UnityExplorer.Core;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
#if CPP
using BepInEx.IL2CPP;
using UnhollowerRuntimeLib;
#endif
namespace UnityExplorer
{
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin :
#if MONO
BaseUnityPlugin
#else
BasePlugin
#endif
, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public ManualLogSource LogSource
#if MONO
=> Logger;
#else
=> Log;
#endif
public ConfigHandler ConfigHandler => _configHandler;
private BepInExConfigHandler _configHandler;
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => LogSource.LogMessage;
public Action<object> OnLogWarning => LogSource.LogWarning;
public Action<object> OnLogError => LogSource.LogError;
// Init common to Mono and Il2Cpp
internal void UniversalInit()
{
Instance = this;
_configHandler = new BepInExConfigHandler();
}
#if MONO // Mono-specific
internal void Awake()
{
UniversalInit();
ExplorerCore.Init(this);
}
internal void Update()
{
ExplorerCore.Update();
}
#else // Il2Cpp-specific
public override void Load()
{
UniversalInit();
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
var obj = new GameObject("ExplorerBehaviour");
obj.AddComponent<ExplorerBehaviour>();
obj.hideFlags = HideFlags.HideAndDontSave;
GameObject.DontDestroyOnLoad(obj);
ExplorerCore.Init(this);
}
// 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) { }
internal void Awake()
{
Instance.LogSource.LogMessage("ExplorerBehaviour.Awake");
}
internal void Update()
{
ExplorerCore.Update();
}
}
#endif
public void SetupPatches()
{
try
{
this.HarmonyInstance.PatchAll();
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
public class PATCH_EventSystem_current
{
[HarmonyPrefix]
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
public class PATCH_Cursor_lockState
{
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
CursorUnlocker.Prefix_set_lockState(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
public class PATCH_Cursor_visible
{
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
CursorUnlocker.Prefix_set_visible(ref value);
}
}
}
}
#endif

View File

@ -1,41 +0,0 @@
#if BIE5
using System;
using System.IO;
using System.Reflection;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
namespace UnityExplorer
{
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Logger;
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
internal void Awake()
{
Instance = this;
new ExplorerCore();
}
internal void Update()
{
ExplorerCore.Update();
}
}
}
#endif

View File

@ -1,104 +0,0 @@
#if BIE6
using System;
using System.IO;
using System.Reflection;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.UI.Main;
#if CPP
using UnhollowerRuntimeLib;
using BepInEx.IL2CPP;
#endif
namespace UnityExplorer
{
#if MONO
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Logger;
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
internal void Awake()
{
Instance = this;
new ExplorerCore();
}
internal void Update()
{
ExplorerCore.Update();
}
}
#endif
#if CPP
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BasePlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Log;
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
// Init
public override void Load()
{
Instance = this;
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
var obj = new GameObject(
"ExplorerBehaviour",
new Il2CppSystem.Type[] { Il2CppType.Of<ExplorerBehaviour>() }
);
obj.hideFlags = HideFlags.HideAndDontSave;
GameObject.DontDestroyOnLoad(obj);
new ExplorerCore();
}
// 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) { }
internal void Awake()
{
Logging.LogMessage("ExplorerBehaviour.Awake");
}
internal void Update()
{
ExplorerCore.Update();
}
}
}
#endif
}
#endif

View File

@ -1,39 +0,0 @@
#if ML
using System;
using System.IO;
using MelonLoader;
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod, IExplorerLoader
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string ConfigFolder => ExplorerFolder;
public Action<object> OnLogMessage => (object log) => { MelonLogger.Msg(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { MelonLogger.Warning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { MelonLogger.Error(log?.ToString() ?? ""); };
public Harmony.HarmonyInstance HarmonyInstance => Instance.Harmony;
public override void OnApplicationStart()
{
Instance = this;
new ExplorerCore();
}
public override void OnUpdate()
{
ExplorerCore.Update();
}
//public override void OnSceneWasLoaded(int buildIndex, string sceneName)
//{
// ExplorerCore.Instance.OnSceneLoaded();
//}
}
}
#endif

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Config;
namespace UnityExplorer
{
@ -10,15 +11,12 @@ namespace UnityExplorer
string ExplorerFolder { get; }
string ConfigFolder { get; }
ConfigHandler ConfigHandler { get; }
Action<object> OnLogMessage { get; }
Action<object> OnLogWarning { get; }
Action<object> OnLogError { get; }
#if ML
Harmony.HarmonyInstance HarmonyInstance { get; }
#else
HarmonyLib.Harmony HarmonyInstance { get; }
#endif
void SetupPatches();
}
}

View File

@ -0,0 +1,85 @@
#if ML
using System;
using System.IO;
using Harmony;
using MelonLoader;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.ML;
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
//[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod, IExplorerLoader
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string ConfigFolder => ExplorerFolder;
public ConfigHandler ConfigHandler => _configHandler;
public MelonLoaderConfigHandler _configHandler;
public Action<object> OnLogMessage => MelonLogger.Msg;
public Action<object> OnLogWarning => MelonLogger.Warning;
public Action<object> OnLogError => MelonLogger.Error;
public Harmony.HarmonyInstance HarmonyInstance => Instance.Harmony;
public override void OnApplicationStart()
{
Instance = this;
_configHandler = new MelonLoaderConfigHandler();
ExplorerCore.Init(this);
}
public override void OnUpdate()
{
ExplorerCore.Update();
}
public void SetupPatches()
{
try
{
PrefixProperty(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
PrefixProperty(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
PrefixProperty(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
private void PrefixProperty(Type type, string property, HarmonyMethod prefix)
{
try
{
var prop = type.GetProperty(property);
this.Harmony.Patch(prop.GetSetMethod(), prefix: prefix);
}
catch (Exception e)
{
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
}
}
}
}
#endif

View File

@ -0,0 +1,129 @@
#if ML
using MelonLoader;
using MelonLoader.Tomlyn.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
namespace UnityExplorer.Loader.ML
{
public class MelonLoaderConfigHandler : ConfigHandler
{
internal const string CTG_NAME = "UnityExplorer";
internal MelonPreferences_Category prefCategory;
public override void Init()
{
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings");
// temporary until melonloader 0.3.1 released
try { MelonPreferences.Mapper.RegisterMapper(KeycodeReader, KeycodeWriter); } catch { }
try { MelonPreferences.Mapper.RegisterMapper(MenuPagesReader, MenuPagesWriter); } catch { }
}
public override void LoadConfig()
{
foreach (var entry in ConfigManager.ConfigElements)
{
var key = entry.Key;
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
{
var config = entry.Value;
config.BoxedValue = config.GetLoaderConfigValue();
}
}
}
public override void RegisterConfigElement<T>(ConfigElement<T> config)
{
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.IsInternal) as MelonPreferences_Entry<T>;
entry.OnValueChangedUntyped += () =>
{
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
return;
config.Value = entry.Value;
};
}
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
{
entry.Value = value;
entry.Save();
}
}
public override T GetConfigValue<T>(ConfigElement<T> config)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
return entry.Value;
return default;
}
public override void OnAnyConfigChanged()
{
}
public override void SaveConfig()
{
MelonPreferences.Save();
}
// TEMPORARY - JUST REQUIRED UNTIL ML 0.3.1 RELEASED
public static KeyCode KeycodeReader(TomlObject value)
{
try
{
KeyCode kc = (KeyCode)Enum.Parse(typeof(KeyCode), (value as TomlString).Value);
if (kc == default)
throw new Exception();
return kc;
}
catch
{
return KeyCode.F7;
}
}
public static TomlObject KeycodeWriter(KeyCode value)
{
return MelonPreferences.Mapper.ToToml(value.ToString());
}
public static UI.Main.MenuPages MenuPagesReader(TomlObject value)
{
try
{
var kc = (UI.Main.MenuPages)Enum.Parse(typeof(UI.Main.MenuPages), (value as TomlString).Value);
if (kc == default)
throw new Exception();
return kc;
}
catch
{
return UI.Main.MenuPages.Home;
}
}
public static TomlObject MenuPagesWriter(UI.Main.MenuPages value)
{
return MelonPreferences.Mapper.ToToml(value.ToString());
}
}
}
#endif

View File

@ -4,6 +4,11 @@ using System;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityExplorer.Core.Config;
using UnityExplorer.Loader.STANDALONE;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
using UnityExplorer.Core;
#if CPP
using UnhollowerRuntimeLib;
#endif
@ -13,20 +18,27 @@ namespace UnityExplorer
public class ExplorerStandalone : IExplorerLoader
{
/// <summary>
/// Call this to initialize UnityExplorer. Optionally, also subscribe to the 'OnLog' event to handle logging.
/// Call this to initialize UnityExplorer without adding a log listener.
/// </summary>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance()
=> CreateInstance(null);
/// <summary>
/// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages.
/// </summary>
/// <param name="logListener">Your log listener to handle UnityExplorer logs.</param>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener)
{
if (Instance != null)
return Instance;
return new ExplorerStandalone();
}
OnLog += logListener;
private ExplorerStandalone()
{
Init();
var instance = new ExplorerStandalone();
instance.Init();
return instance;
}
public static ExplorerStandalone Instance { get; private set; }
@ -39,6 +51,9 @@ namespace UnityExplorer
public Harmony HarmonyInstance => s_harmony;
public static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public ConfigHandler ConfigHandler => _configHandler;
private StandaloneConfigHandler _configHandler;
public string ExplorerFolder
{
get
@ -70,24 +85,18 @@ namespace UnityExplorer
private void Init()
{
Instance = this;
_configHandler = new StandaloneConfigHandler();
#if CPP
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
var obj = new GameObject(
"ExplorerBehaviour",
new Il2CppSystem.Type[] { Il2CppType.Of<ExplorerBehaviour>() }
);
#else
var obj = new GameObject(
"ExplorerBehaviour",
new Type[] { typeof(ExplorerBehaviour) }
);
#endif
var obj = new GameObject("ExplorerBehaviour");
obj.AddComponent<ExplorerBehaviour>();
obj.hideFlags = HideFlags.HideAndDontSave;
GameObject.DontDestroyOnLoad(obj);
obj.hideFlags = HideFlags.HideAndDontSave;
new ExplorerCore();
ExplorerCore.Init(this);
}
public class ExplorerBehaviour : MonoBehaviour
@ -100,6 +109,48 @@ namespace UnityExplorer
ExplorerCore.Update();
}
}
public void SetupPatches()
{
try
{
this.HarmonyInstance.PatchAll();
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
public class PATCH_EventSystem_current
{
[HarmonyPrefix]
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
public class PATCH_Cursor_lockState
{
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
CursorUnlocker.Prefix_set_lockState(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
public class PATCH_Cursor_visible
{
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
CursorUnlocker.Prefix_set_visible(ref value);
}
}
}
}
#endif

View File

@ -0,0 +1,108 @@
#if STANDALONE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Config;
using IniParser.Parser;
using System.IO;
using UnityEngine;
namespace UnityExplorer.Loader.STANDALONE
{
public class StandaloneConfigHandler : ConfigHandler
{
internal static IniDataParser _parser;
internal static string INI_PATH;
public override void Init()
{
INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini");
_parser = new IniDataParser();
_parser.Configuration.CommentString = "#";
}
public override void LoadConfig()
{
if (!TryLoadConfig())
SaveConfig();
}
public override void RegisterConfigElement<T>(ConfigElement<T> element)
{
// Not necessary
}
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
{
// Not necessary, just save.
SaveConfig();
}
public override T GetConfigValue<T>(ConfigElement<T> element)
{
// Not necessary, just return the value.
return element.Value;
}
public bool TryLoadConfig()
{
try
{
if (!File.Exists(INI_PATH))
return false;
string ini = File.ReadAllText(INI_PATH);
var data = _parser.Parse(ini);
foreach (var config in data.Sections["Config"])
{
if (ConfigManager.ConfigElements.TryGetValue(config.KeyName, out IConfigElement configElement))
configElement.BoxedValue = StringToConfigValue(config.Value, configElement.ElementType);
}
return true;
}
catch
{
return false;
}
}
public object StringToConfigValue(string value, Type elementType)
{
if (elementType == typeof(KeyCode))
return (KeyCode)Enum.Parse(typeof(KeyCode), value);
else if (elementType == typeof(bool))
return bool.Parse(value);
else if (elementType == typeof(int))
return int.Parse(value);
else
return value;
}
public override void OnAnyConfigChanged()
{
SaveConfig();
}
public override void SaveConfig()
{
var data = new IniParser.Model.IniData();
data.Sections.AddSection("Config");
var sec = data.Sections["Config"];
foreach (var entry in ConfigManager.ConfigElements)
sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString());
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
File.WriteAllText(INI_PATH, data.ToString());
}
}
}
#endif

View File

@ -2,13 +2,6 @@
using System.Runtime.InteropServices;
using UnityExplorer;
#if ML
using MelonLoader;
[assembly: MelonInfo(typeof(ExplorerMelonMod), "UnityExplorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
#endif
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.

Binary file not shown.

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.InteractiveValues;
namespace UnityExplorer.UI.CacheObject
{
public class CacheConfigEntry : CacheObjectBase
{
public IConfigElement RefConfig { get; }
public override Type FallbackType => RefConfig.ElementType;
public override bool HasEvaluated => true;
public override bool HasParameters => false;
public override bool IsMember => false;
public override bool CanWrite => true;
public CacheConfigEntry(IConfigElement config, GameObject parent)
{
RefConfig = config;
m_parentContent = parent;
config.OnValueChangedNotify += () => { UpdateValue(); };
CreateIValue(config.BoxedValue, config.ElementType);
}
public override void CreateIValue(object value, Type fallbackType)
{
IValue = InteractiveValue.Create(value, fallbackType);
IValue.Owner = this;
IValue.m_mainContentParent = m_mainGroup;
IValue.m_subContentParent = this.m_subContent;
}
public override void UpdateValue()
{
IValue.Value = RefConfig.BoxedValue;
base.UpdateValue();
}
public override void SetValue()
{
RefConfig.BoxedValue = IValue.Value;
}
internal GameObject m_mainGroup;
internal override void ConstructUI()
{
base.ConstructUI();
m_mainGroup = UIFactory.CreateVerticalGroup(m_mainContent, "ConfigHolder", true, false, true, true, 5, new Vector4(2, 2, 2, 2));
var horiGroup = UIFactory.CreateHorizontalGroup(m_mainGroup, "ConfigEntryHolder", false, false, true, true, childAlignment: TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(horiGroup, minHeight: 30, flexibleHeight: 0);
// config entry label
var configLabel = UIFactory.CreateLabel(horiGroup, "ConfigLabel", this.RefConfig.Name, TextAnchor.MiddleLeft);
var leftRect = configLabel.GetComponent<RectTransform>();
leftRect.anchorMin = Vector2.zero;
leftRect.anchorMax = Vector2.one;
leftRect.offsetMin = Vector2.zero;
leftRect.offsetMax = Vector2.zero;
leftRect.sizeDelta = Vector2.zero;
UIFactory.SetLayoutElement(configLabel.gameObject, minWidth: 250, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
// Default button
var defaultButton = UIFactory.CreateButton(horiGroup,
"RevertDefaultButton",
"Default",
() => { RefConfig.RevertToDefaultValue(); },
new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(defaultButton.gameObject, minWidth: 80, minHeight: 22, flexibleWidth: 0);
// Description label
var desc = UIFactory.CreateLabel(m_mainGroup, "Description", $"<i>{RefConfig.Description}</i>", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(desc.gameObject, minWidth: 250, minHeight: 20, flexibleWidth: 9999, flexibleHeight: 0);
// IValue
if (IValue != null)
{
IValue.m_mainContentParent = m_mainGroup;
IValue.m_subContentParent = this.m_subContent;
}
// makes the subcontent look nicer
m_subContent.transform.SetParent(m_mainGroup.transform, false);
}
}
}

View File

@ -6,8 +6,9 @@ using System.Text;
using UnityExplorer.UI;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.InteractiveValues;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public class CacheEnumerated : CacheObjectBase
{
@ -44,18 +45,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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 rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, "CacheEnumeratedGroup", false, true, true, true, 0, new Vector4(0,0,5,2),
new Color(1, 1, 1, 0));
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 + ":";
var indexLabel = UIFactory.CreateLabel(rowObj, "IndexLabel", $"{this.Index}:", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(indexLabel.gameObject, minWidth: 20, flexibleWidth: 30, minHeight: 25);
IValue.m_mainContentParent = rowObj;
}

View File

@ -6,7 +6,7 @@ using System.Reflection;
using UnityExplorer.UI;
using UnityEngine;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public class CacheField : CacheMember
{

View File

@ -5,13 +5,14 @@ using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using UnityExplorer.Core.Unity;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Core;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.InteractiveValues;
using UnityExplorer.UI.Inspectors.Reflection;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public abstract class CacheMember : CacheObjectBase
{
@ -85,7 +86,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
try
{
Type baseType = ReflectionUtility.GetType(IValue.Value) ?? FallbackType;
Type baseType = ReflectionUtility.GetActualType(IValue.Value) ?? FallbackType;
if (!ReflectionProvider.Instance.IsReflectionSupported(baseType))
throw new Exception("Type not supported with reflection");
@ -93,7 +94,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
UpdateReflection();
if (IValue.Value != null)
IValue.Value = IValue.Value.Cast(ReflectionUtility.GetType(IValue.Value));
IValue.Value = IValue.Value.Cast(ReflectionUtility.GetActualType(IValue.Value));
}
catch (Exception e)
{
@ -214,75 +215,46 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
base.ConstructUI();
var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, "CacheMemberGroup", false, false, true, true, 10, new Vector4(0, 0, 3, 3),
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;
UIFactory.SetLayoutElement(topGroupObj, minHeight: 25, flexibleHeight: 0, minWidth: 300, flexibleWidth: 5000);
// 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;
m_leftGroup = UIFactory.CreateHorizontalGroup(topGroupObj, "LeftGroup", false, true, true, true, 4, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(m_leftGroup, minHeight: 25, flexibleHeight: 0, minWidth: 125, flexibleWidth: 200);
// member label
var labelObj = UIFactory.CreateLabel(m_leftGroup, TextAnchor.MiddleLeft);
var leftRect = labelObj.GetComponent<RectTransform>();
m_memLabelText = UIFactory.CreateLabel(m_leftGroup, "MemLabelText", RichTextName, TextAnchor.MiddleLeft);
m_memLabelText.horizontalOverflow = HorizontalWrapMode.Wrap;
var leftRect = m_memLabelText.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 = m_memLabelText.gameObject.AddComponent<LayoutElement>();
m_leftLayout.preferredWidth = 125;
m_leftLayout.minHeight = 25;
m_leftLayout.flexibleHeight = 100;
var labelFitter = labelObj.AddComponent<ContentSizeFitter>();
var labelFitter = m_memLabelText.gameObject.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_rightGroup = UIFactory.CreateVerticalGroup(topGroupObj, "RightGroup", false, true, true, true, 2, new Vector4(4,2,0,0),
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);
@ -297,27 +269,17 @@ namespace UnityExplorer.Core.Inspectors.Reflection
if (HasParameters)
{
argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, new Color(1, 1, 1, 0));
var argsGroup = argsHolder.GetComponent<VerticalLayoutGroup>();
argsGroup.spacing = 4;
argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, "ArgsHolder", true, false, true, true, 4, new Color(1, 1, 1, 0));
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>";
UIFactory.CreateLabel(argsHolder, "ArgumentsLabel", "Arguments:", TextAnchor.MiddleLeft);
for (int i = 0; i < m_arguments.Length; i++)
{
AddArgRow(i, argsHolder);
}
}
argsHolder.SetActive(false);
@ -328,30 +290,16 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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 rowObj = UIFactory.CreateHorizontalGroup(parent, "ArgRow", true, false, true, true, 4, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000);
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
var argLabelLayout = argLabelObj.AddComponent<LayoutElement>();
argLabelLayout.minHeight = 25;
var argText = argLabelObj.GetComponent<Text>();
var argTypeTxt = SignatureHighlighter.ParseFullSyntax(arg.ParameterType, false);
argText.text = $"{argTypeTxt} <color={SignatureHighlighter.LOCAL_ARG}>{arg.Name}</color>";
var argLabel = UIFactory.CreateLabel(rowObj, "ArgLabel", $"{argTypeTxt} <color={SignatureHighlighter.LOCAL_ARG}>{arg.Name}</color>",
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(argLabel.gameObject, minHeight: 25);
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 argInputObj = UIFactory.CreateInputField(rowObj, "ArgInput", "...", 14, (int)TextAnchor.MiddleLeft, 1);
UIFactory.SetLayoutElement(argInputObj, flexibleWidth: 1200, preferredWidth: 150, minWidth: 20, minHeight: 25, flexibleHeight: 0);
var argInput = argInputObj.GetComponent<InputField>();
argInput.onValueChanged.AddListener((string val) => { m_argumentInput[i] = val; });
@ -367,38 +315,26 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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 evalGroupObj = UIFactory.CreateHorizontalGroup(m_rightGroup, "EvalGroup", false, false, true, true, 5,
default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(evalGroupObj, minHeight: 25, flexibleHeight: 0, 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 = UIFactory.CreateButton(evalGroupObj,
"EvalButton",
$"Evaluate ({ParamCount})",
null);
var evalButton = evalButtonObj.GetComponent<Button>();
var colors = evalButton.colors;
colors.highlightedColor = new Color(0.4f, 0.7f, 0.4f);
evalButton.colors = colors;
RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f),
new Color(0.4f, 0.7f, 0.4f), new Color(0.3f, 0.3f, 0.3f));
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";
UIFactory.SetLayoutElement(evalButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0);
cancelButtonObj.SetActive(false);
var evalText = evalButton.GetComponentInChildren<Text>();
var cancelButton = UIFactory.CreateButton(evalGroupObj, "CancelButton", "Close", null, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(cancelButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0);
cancelButton.gameObject.SetActive(false);
evalButton.onClick.AddListener(() =>
{
@ -407,11 +343,9 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.3f, 0.6f, 0.3f));
cancelButtonObj.SetActive(true);
cancelButton.gameObject.SetActive(true);
}
else
{
@ -422,41 +356,25 @@ namespace UnityExplorer.Core.Inspectors.Reflection
}
});
var cancelButton = cancelButtonObj.GetComponent<Button>();
cancelButton.onClick.AddListener(() =>
{
cancelButtonObj.SetActive(false);
cancelButton.gameObject.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;
RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f));
});
}
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 = UIFactory.CreateButton(m_rightGroup, "EvalButton", "Evaluate", () => { (this as CacheMethod).Evaluate(); });
RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f),
new Color(0.4f, 0.7f, 0.4f), new Color(0.3f, 0.3f, 0.3f));
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();
}
UIFactory.SetLayoutElement(evalButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0);
}
}

View File

@ -9,7 +9,7 @@ using UnityExplorer.Core.Unity;
using UnityExplorer.Core;
using UnityExplorer.UI.Utility;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public class CacheMethod : CacheMember
{
@ -130,14 +130,10 @@ namespace UnityExplorer.Core.Inspectors.Reflection
internal void ConstructGenericArgInput(GameObject parent)
{
var titleObj = UIFactory.CreateLabel(parent, TextAnchor.MiddleLeft);
var titleText = titleObj.GetComponent<Text>();
titleText.text = "<b>Generic Arguments:</b>";
UIFactory.CreateLabel(parent, "GenericArgLabel", "Generic Arguments:", TextAnchor.MiddleLeft);
for (int i = 0; i < GenericArgs.Length; i++)
{
AddGenericArgRow(i, parent);
}
}
internal void AddGenericArgRow(int i, GameObject parent)
@ -158,33 +154,18 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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 rowObj = UIFactory.CreateHorizontalGroup(parent, "ArgRowObj", false, true, true, true, 4, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000);
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
//var argLayout = argLabelObj.AddComponent<LayoutElement>();
//argLayout.minWidth = 20;
var argText = argLabelObj.GetComponent<Text>();
argText.text = $"{constrainTxt} <color={SignatureHighlighter.CONST_VAR}>{arg.Name}</color>";
var argLabelObj = UIFactory.CreateLabel(rowObj, "ArgLabelObj", $"{constrainTxt} <color={SignatureHighlighter.CONST_VAR}>{arg.Name}</color>",
TextAnchor.MiddleLeft);
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
var argInputLayout = argInputObj.AddComponent<LayoutElement>();
argInputLayout.flexibleWidth = 1200;
var argInputObj = UIFactory.CreateInputField(rowObj, "ArgInput", "...", 14, (int)TextAnchor.MiddleLeft, 1);
UIFactory.SetLayoutElement(argInputObj, 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

View File

@ -4,12 +4,12 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using UnityExplorer.Core.Unity;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.UI.InteractiveValues;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public abstract class CacheObjectBase
{
@ -55,7 +55,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
// if the type has changed fundamentally, make a new interactivevalue for it
var type = value == null
? FallbackType
: ReflectionUtility.GetType(value);
: ReflectionUtility.GetActualType(value);
var ivalueType = InteractiveValue.GetIValueForType(type);
@ -86,33 +86,18 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
m_constructedUI = true;
m_mainContent = UIFactory.CreateVerticalGroup(m_parentContent, new Color(0.1f, 0.1f, 0.1f));
m_mainContent.name = "CacheObjectBase.MainContent";
m_mainContent = UIFactory.CreateVerticalGroup(m_parentContent, "CacheObjectBase.MainContent", true, true, true, true, 0, default,
new Color(0.1f, 0.1f, 0.1f));
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;
UIFactory.SetLayoutElement(m_mainContent, minHeight: 25, flexibleHeight: 9999, minWidth: 200, 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 = UIFactory.CreateVerticalGroup(m_mainContent, "CacheObjectBase.SubContent", true, false, true, true, 0, default,
new Color(0.085f, 0.085f, 0.085f));
UIFactory.SetLayoutElement(m_subContent, minHeight: 30, flexibleHeight: 9999, minWidth: 125, flexibleWidth: 9000);
m_subContent.SetActive(false);

View File

@ -6,9 +6,9 @@ using System.Text;
using UnityExplorer.UI;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.InteractiveValues;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public enum PairTypes
{
@ -50,18 +50,19 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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;
Color bgColor = this.PairType == PairTypes.Key
? new Color(0.07f, 0.07f, 0.07f)
: new Color(0.1f, 0.1f, 0.1f);
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}:";
var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, "PairedGroup", false, false, true, true, 0, new Vector4(0,0,5,2),
bgColor);
string lbl = $"{this.PairType}";
if (this.PairType == PairTypes.Key)
lbl = $"[{Index}] {lbl}";
var indexLabel = UIFactory.CreateLabel(rowObj, "IndexLabel", lbl, TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(indexLabel.gameObject, minWidth: 80, flexibleWidth: 30, minHeight: 25);
IValue.m_mainContentParent = rowObj;
}

View File

@ -7,7 +7,7 @@ using UnityExplorer.UI;
using UnityExplorer.Core.Unity;
using UnityEngine;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.CacheObject
{
public class CacheProperty : CacheMember
{

View File

@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Inspectors;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Utility;
namespace UnityExplorer.UI.Main.Home.Inspectors
namespace UnityExplorer.UI.Inspectors.GameObjects
{
public class ChildList
{
@ -139,29 +137,15 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
internal void ConstructChildList(GameObject parent)
{
var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
var vertGroup = vertGroupObj.GetComponent<VerticalLayoutGroup>();
vertGroup.childForceExpandHeight = true;
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 vertGroupObj = UIFactory.CreateVerticalGroup(parent, "ChildListGroup", false, true, true, true, 5, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(vertGroupObj, minWidth: 120, flexibleWidth: 25000, minHeight: 200, 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 childTitle = UIFactory.CreateLabel(vertGroupObj, "ChildListTitle", "Children:", TextAnchor.MiddleLeft, Color.grey, true, 14);
UIFactory.SetLayoutElement(childTitle.gameObject, 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;
var childrenScrollObj = UIFactory.CreateScrollView(vertGroupObj, "ChildListScrollView", out s_childListContent,
out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
UIFactory.SetLayoutElement(childrenScrollObj, minHeight: 50);
s_childListPageHandler = new PageHandler(scroller);
s_childListPageHandler.ConstructUI(vertGroupObj);
@ -172,46 +156,35 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
{
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;
var btnGroupObj = UIFactory.CreateHorizontalGroup(s_childListContent, "ChildButtonGroup", true, false, true, true,
0, default, new Color(0.07f, 0.07f, 0.07f));
UIFactory.SetLayoutElement(btnGroupObj, flexibleWidth: 320, minHeight: 25, 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;
var toggleObj = UIFactory.CreateToggle(btnGroupObj, "Toggle", out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, 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); });
var mainBtn = UIFactory.CreateButton(btnGroupObj,
"MainButton",
"",
() => { OnChildListObjectClicked(thisIndex); });
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
RuntimeProvider.Instance.SetColorBlock(mainBtn, new Color(0.07f, 0.07f, 0.07f),
new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.05f, 0.05f, 0.05f));
UIFactory.SetLayoutElement(mainBtn.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 9999);
Text mainText = mainBtn.GetComponentInChildren<Text>();
mainText.alignment = TextAnchor.MiddleLeft;
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
mainText.resizeTextForBestFit = true;
mainText.resizeTextMaxSize = 14;
mainText.resizeTextMinSize = 10;
s_childListTexts.Add(mainText);
}

View File

@ -1,16 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityExplorer.Core;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Utility;
using UnityExplorer.Core.Inspectors;
namespace UnityExplorer.UI.Main.Home.Inspectors
namespace UnityExplorer.UI.Inspectors.GameObjects
{
public class ComponentList
{
@ -77,14 +75,10 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
var text = s_compListTexts[i];
text.text = SignatureHighlighter.ParseFullSyntax(ReflectionUtility.GetType(comp), true);
text.text = SignatureHighlighter.ParseFullSyntax(ReflectionUtility.GetActualType(comp), true);
var toggle = s_compToggles[i];
#if CPP
if (comp.TryCast<Behaviour>() is Behaviour behaviour)
#else
if (comp is Behaviour behaviour)
#endif
{
if (!toggle.gameObject.activeSelf)
toggle.gameObject.SetActive(true);
@ -111,19 +105,13 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
internal static void OnCompToggleClicked(int index, bool value)
{
var comp = s_compShortlist[index];
#if CPP
comp.TryCast<Behaviour>().enabled = value;
#else
(comp as Behaviour).enabled = value;
#endif
}
internal static void OnCompListObjectClicked(int index)
{
if (index >= s_compShortlist.Count || !s_compShortlist[index])
{
return;
}
InspectorManager.Instance.Inspect(s_compShortlist[index]);
}
@ -137,34 +125,20 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
}
#region UI CONSTRUCTION
#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 = true;
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 vertGroupObj = UIFactory.CreateVerticalGroup(parent, "ComponentList", false, true, true, true, 5, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(vertGroupObj, minWidth: 120, flexibleWidth: 25000, minHeight: 200, 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 compTitle = UIFactory.CreateLabel(vertGroupObj, "ComponentsTitle", "Components:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(compTitle.gameObject, 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;
contentLayout.flexibleHeight = 5000;
var compScrollObj = UIFactory.CreateScrollView(vertGroupObj, "ComponentListScrollView", out s_compListContent,
out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
UIFactory.SetLayoutElement(compScrollObj, minHeight: 50, flexibleHeight: 5000);
s_compListPageHandler = new PageHandler(scroller);
s_compListPageHandler.ConstructUI(vertGroupObj);
@ -175,26 +149,15 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
{
int thisIndex = s_compListTexts.Count;
GameObject groupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
HorizontalLayoutGroup group = groupObj.GetComponent<HorizontalLayoutGroup>();
group.childForceExpandWidth = true;
group.childControlWidth = true;
group.childForceExpandHeight = false;
group.childControlHeight = true;
group.childAlignment = TextAnchor.MiddleLeft;
LayoutElement groupLayout = groupObj.AddComponent<LayoutElement>();
groupLayout.minWidth = 25;
groupLayout.flexibleWidth = 999;
groupLayout.minHeight = 25;
groupLayout.flexibleHeight = 0;
GameObject groupObj = UIFactory.CreateHorizontalGroup(s_compListContent, "CompListButton", true, false, true, true, 0, default,
new Color(0.07f, 0.07f, 0.07f), TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(groupObj, minWidth: 25, flexibleWidth: 999, minHeight: 25, flexibleHeight: 0);
groupObj.AddComponent<Mask>();
// Behaviour enabled toggle
var toggleObj = UIFactory.CreateToggle(groupObj, 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;
var toggleObj = UIFactory.CreateToggle(groupObj, "EnabledToggle", out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(toggleObj, minWidth: 25, minHeight: 25);
toggleText.text = "";
toggle.isOn = true;
s_compToggles.Add(toggle);
@ -202,35 +165,28 @@ namespace UnityExplorer.UI.Main.Home.Inspectors
// Main component button
GameObject mainButtonObj = UIFactory.CreateButton(groupObj);
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); });
var mainBtn = UIFactory.CreateButton(groupObj,
"MainButton",
"",
() => { OnCompListObjectClicked(thisIndex); });
RuntimeProvider.Instance.SetColorBlock(mainBtn, new Color(0.07f, 0.07f, 0.07f),
new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.05f, 0.05f, 0.05f));
UIFactory.SetLayoutElement(mainBtn.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 999);
// Component button text
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
Text mainText = mainBtn.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
#endregion
}
}

View File

@ -0,0 +1,468 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Core.Unity;
namespace UnityExplorer.UI.Inspectors.GameObjects
{
public class GameObjectControls
{
internal static GameObjectControls Instance;
public GameObjectControls()
{
Instance = this;
}
internal static bool Showing;
internal static void ToggleVisibility() => SetVisibility(!Showing);
internal static void SetVisibility(bool show)
{
if (show == Showing)
return;
Showing = show;
m_hideShowLabel.text = show ? "Hide" : "Show";
m_contentObj.SetActive(show);
}
internal static GameObject m_contentObj;
internal static Text m_hideShowLabel;
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.ToStringPretty();
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.ToStringPretty();
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.ToStringPretty();
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.ToStringPretty();
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 mainGroup = UIFactory.CreateVerticalGroup(parent, "ControlsGroup", false, false, true, true, 5, new Vector4(4,4,4,4),
new Color(0.07f, 0.07f, 0.07f));
// ~~~~~~ Top row ~~~~~~
var topRow = UIFactory.CreateHorizontalGroup(mainGroup, "TopRow", false, false, true, true, 5, default, new Color(1, 1, 1, 0));
var hideButton = UIFactory.CreateButton(topRow, "ToggleShowButton", "Show", ToggleVisibility, new Color(0.16f, 0.16f, 0.16f));
UIFactory.SetLayoutElement(hideButton.gameObject, minWidth: 40, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
m_hideShowLabel = hideButton.GetComponentInChildren<Text>();
var topTitle = UIFactory.CreateLabel(topRow, "ControlsLabel", "Controls", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(topTitle.gameObject, minWidth: 100, flexibleWidth: 9500, minHeight: 25);
//// ~~~~~~~~ Content ~~~~~~~~ //
m_contentObj = UIFactory.CreateVerticalGroup(mainGroup, "ContentGroup", true, false, true, true, 5, default, new Color(1, 1, 1, 0));
// transform controls
ConstructVector3Editor(m_contentObj, "Position", ControlType.position, out s_positionControl);
ConstructVector3Editor(m_contentObj, "Local Position", ControlType.localPosition, out s_localPosControl);
ConstructVector3Editor(m_contentObj, "Rotation", ControlType.eulerAngles, out s_rotationControl);
ConstructVector3Editor(m_contentObj, "Scale", ControlType.localScale, out s_scaleControl);
// set parent
ConstructSetParent(m_contentObj);
// bottom row buttons
ConstructBottomButtons(m_contentObj);
// set controls content inactive now that content is made (otherwise TMP font size goes way too big?)
m_contentObj.SetActive(false);
}
internal void ConstructSetParent(GameObject contentObj)
{
var setParentGroupObj = UIFactory.CreateHorizontalGroup(contentObj, "SetParentRow", false, false, true, true, 5, default,
new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(setParentGroupObj, minHeight: 25, flexibleHeight: 0);
var title = UIFactory.CreateLabel(setParentGroupObj, "SetParentLabel", "Set Parent:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(title.gameObject, minWidth: 110, minHeight: 25, flexibleHeight: 0);
var inputFieldObj = UIFactory.CreateInputField(setParentGroupObj, "SetParentInputField", "Enter a GameObject name or path...");
s_setParentInput = inputFieldObj.GetComponent<InputField>();
UIFactory.SetLayoutElement(inputFieldObj, minHeight: 25, preferredWidth: 400, flexibleWidth: 9999);
var applyButton = UIFactory.CreateButton(setParentGroupObj, "SetParentButton", "Apply", OnSetParentClicked);
UIFactory.SetLayoutElement(applyButton.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
}
internal void ConstructVector3Editor(GameObject parent, string titleText, ControlType type, out ControlEditor editor)
{
editor = new ControlEditor();
var topBarObj = UIFactory.CreateHorizontalGroup(parent, "Vector3Editor", false, false, true, true, 5, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(topBarObj, minHeight: 25, flexibleHeight: 0);
var title = UIFactory.CreateLabel(topBarObj, "Title", titleText, TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(title.gameObject, minWidth: 110, flexibleWidth: 0, minHeight: 25);
// expand button
var expandButton = UIFactory.CreateButton(topBarObj, "ExpandArrow", "▼");
var expandText = expandButton.GetComponentInChildren<Text>();
expandText.fontSize = 12;
UIFactory.SetLayoutElement(expandButton.gameObject, minWidth: 35, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
// readonly value input
var valueInputObj = UIFactory.CreateInputField(topBarObj, "ValueInput", "...");
var valueInput = valueInputObj.GetComponent<InputField>();
valueInput.readOnly = true;
UIFactory.SetLayoutElement(valueInputObj, minHeight: 25, flexibleHeight: 0, preferredWidth: 400, 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, "EditorRow", false, false, true, true, 5, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(rowObject, minHeight: 25, flexibleHeight: 0, minWidth: 100);
// Value labels
var valueTitle = UIFactory.CreateLabel(rowObject, "ValueTitle", $"{vectorValue.ToString().ToUpper()}:", TextAnchor.MiddleLeft, Color.cyan);
UIFactory.SetLayoutElement(valueTitle.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 0);
// actual value label
var valueLabel = UIFactory.CreateLabel(rowObject, "ValueLabel", "", TextAnchor.MiddleLeft);
editor.values[(int)vectorValue] = valueLabel;
UIFactory.SetLayoutElement(valueLabel.gameObject, minWidth: 85, flexibleWidth: 0, minHeight: 25);
// input field
var inputHolder = UIFactory.CreateVerticalGroup(rowObject, "InputFieldGroup", false, false, true, true, 0, default, new Color(1, 1, 1, 0));
var inputObj = UIFactory.CreateInputField(inputHolder, "InputField", "...");
var input = inputObj.GetComponent<InputField>();
//input.characterValidation = InputField.CharacterValidation.Decimal;
UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 0, minWidth: 90, flexibleWidth: 50);
editor.inputs[(int)vectorValue] = input;
// apply button
var applyBtn = UIFactory.CreateButton(rowObject, "ApplyButton", "Apply", () => { OnVectorControlInputApplied(type, vectorValue); });
UIFactory.SetLayoutElement(applyBtn.gameObject, minWidth: 60, minHeight: 25);
// Slider
var sliderObj = UIFactory.CreateSlider(rowObject, "VectorSlider", out Slider slider);
UIFactory.SetLayoutElement(sliderObj, minHeight: 20, flexibleHeight: 0, minWidth: 200, flexibleWidth: 9000);
sliderObj.transform.Find("Fill Area").gameObject.SetActive(false);
RuntimeProvider.Instance.SetColorBlock(slider, new Color(0.65f, 0.65f, 0.65f));
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, "BottomButtons", true, true, false, false, 4, default, new Color(1, 1, 1, 0));
var instantiateBtn = UIFactory.CreateButton(bottomRow, "InstantiateBtn", "Instantiate", InstantiateBtn, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(instantiateBtn.gameObject, minWidth: 150);
void InstantiateBtn()
{
var go = GameObjectInspector.ActiveInstance.TargetGO;
if (!go)
return;
var clone = GameObject.Instantiate(go);
InspectorManager.Instance.Inspect(clone);
}
var dontDestroyBtn = UIFactory.CreateButton(bottomRow, "DontDestroyButton", "Set DontDestroyOnLoad", DontDestroyOnLoadBtn,
new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(dontDestroyBtn.gameObject, flexibleWidth: 5000);
void DontDestroyOnLoadBtn()
{
var go = GameObjectInspector.ActiveInstance.TargetGO;
if (!go)
return;
GameObject.DontDestroyOnLoad(go);
}
var destroyBtn = UIFactory.CreateButton(bottomRow, "DestroyButton", "Destroy", DestroyBtn, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(destroyBtn.gameObject, minWidth: 150);
var destroyText = destroyBtn.GetComponentInChildren<Text>();
destroyText.color = Color.red;
void DestroyBtn()
{
var go = GameObjectInspector.ActiveInstance.TargetGO;
if (!go)
return;
GameObject.Destroy(go);
}
}
#endregion
}
}

View File

@ -0,0 +1,350 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Core.Unity;
namespace UnityExplorer.UI.Inspectors.GameObjects
{
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
internal static ChildList s_childList;
internal static ComponentList s_compList;
internal static GameObjectControls s_controls;
internal static bool m_UIConstructed;
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 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;
internal void RefreshTopInfo()
{
var target = TargetGO;
string name = target.name;
if (m_lastName != name)
{
m_lastName = name;
m_nameInput.text = m_lastName;
}
if (target.transform.parent)
{
if (!m_pathGroupObj.activeSelf)
m_pathGroupObj.SetActive(true);
var path = target.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 != target.activeSelf)
{
m_lastEnabledState = target.activeSelf;
m_enabledToggle.isOn = target.activeSelf;
m_enabledText.text = target.activeSelf ? "Enabled" : "Disabled";
m_enabledText.color = target.activeSelf ? Color.green : Color.red;
}
if (m_lastLayer != target.layer)
{
m_lastLayer = target.layer;
m_layerDropdown.value = target.layer;
}
if (string.IsNullOrEmpty(m_lastScene) || m_lastScene != target.scene.name)
{
m_lastScene = target.scene.name;
if (!string.IsNullOrEmpty(target.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
internal void ConstructUI()
{
var parent = InspectorManager.m_inspectorContent;
s_content = UIFactory.CreateScrollView(parent, "GameObjectInspector_Content", out GameObject scrollContent, out _,
new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(scrollContent.transform.parent.gameObject, true, true, true, true);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(scrollContent, true, true, true, true, 5);
var contentFitter = scrollContent.GetComponent<ContentSizeFitter>();
contentFitter.verticalFit = ContentSizeFitter.FitMode.Unconstrained;
ConstructTopArea(scrollContent);
s_controls.ConstructControls(scrollContent);
var midGroupObj = ConstructMidGroup(scrollContent);
s_childList.ConstructChildList(midGroupObj);
s_compList.ConstructCompList(midGroupObj);
LayoutRebuilder.ForceRebuildLayoutImmediate(s_content.GetComponent<RectTransform>());
Canvas.ForceUpdateCanvases();
}
private void ConstructTopArea(GameObject scrollContent)
{
// path row
m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, "TopArea", false, false, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f));
var pathRect = m_pathGroupObj.GetComponent<RectTransform>();
pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20);
UIFactory.SetLayoutElement(m_pathGroupObj, minHeight: 20, flexibleHeight: 75);
// Back button
var backButton = UIFactory.CreateButton(m_pathGroupObj, "BackButton", "◄", OnBackButtonClicked, new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(backButton.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
m_hiddenPathText = UIFactory.CreateLabel(m_pathGroupObj, "HiddenPathText", "", TextAnchor.MiddleLeft);
m_hiddenPathText.color = Color.clear;
m_hiddenPathText.fontSize = 14;
m_hiddenPathText.raycastTarget = false;
var hiddenFitter = m_hiddenPathText.gameObject.AddComponent<ContentSizeFitter>();
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
UIFactory.SetLayoutElement(m_hiddenPathText.gameObject, minHeight: 25, flexibleHeight: 125, minWidth: 250, flexibleWidth: 9000);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(m_hiddenPathText.gameObject, true, true, true, true);
// Path input
var pathInputObj = UIFactory.CreateInputField(m_hiddenPathText.gameObject, "PathInputField", "...");
UIFactory.SetLayoutElement(pathInputObj, minHeight: 25, flexibleHeight: 75, preferredWidth: 400, flexibleWidth: 9999);
var pathInputRect = pathInputObj.GetComponent<RectTransform>();
pathInputRect.sizeDelta = new Vector2(pathInputRect.sizeDelta.x, 25);
m_pathInput = pathInputObj.GetComponent<InputField>();
m_pathInput.text = ActiveInstance.TargetGO.transform.GetTransformPath();
m_pathInput.readOnly = true;
m_pathInput.lineType = InputField.LineType.MultiLineNewline;
m_pathInput.textComponent.color = new Color(0.75f, 0.75f, 0.75f);
var textRect = m_pathInput.textComponent.GetComponent<RectTransform>();
textRect.offsetMin = new Vector2(3, 3);
textRect.offsetMax = new Vector2(3, 3);
m_pathInputRect = m_pathInput.GetComponent<RectTransform>();
m_hiddenPathRect = m_hiddenPathText.GetComponent<RectTransform>();
// name and enabled row
var nameRowObj = UIFactory.CreateHorizontalGroup(scrollContent, "NameGroup", false, false, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(nameRowObj, minHeight: 25, flexibleHeight: 0);
var nameRect = nameRowObj.GetComponent<RectTransform>();
nameRect.sizeDelta = new Vector2(nameRect.sizeDelta.x, 25);
var nameLabel = UIFactory.CreateLabel(nameRowObj, "NameLabel", "Name:", TextAnchor.MiddleCenter, Color.grey, true, 14);
UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 55, flexibleWidth: 0);
var nameInputObj = UIFactory.CreateInputField(nameRowObj, "NameInput", "...");
var nameInputRect = nameInputObj.GetComponent<RectTransform>();
nameInputRect.sizeDelta = new Vector2(nameInputRect.sizeDelta.x, 25);
m_nameInput = nameInputObj.GetComponent<InputField>();
m_nameInput.text = ActiveInstance.TargetGO.name;
var applyNameBtn = UIFactory.CreateButton(nameRowObj, "ApplyNameButton", "Apply", OnApplyNameClicked);
UIFactory.SetLayoutElement(applyNameBtn.gameObject, minWidth: 65, minHeight: 25, flexibleHeight: 0);
var applyNameRect = applyNameBtn.GetComponent<RectTransform>();
applyNameRect.sizeDelta = new Vector2(applyNameRect.sizeDelta.x, 25);
var activeLabel = UIFactory.CreateLabel(nameRowObj, "ActiveLabel", "Active:", TextAnchor.MiddleCenter, Color.grey, true, 14);
UIFactory.SetLayoutElement(activeLabel.gameObject, minWidth: 55, minHeight: 25);
var enabledToggleObj = UIFactory.CreateToggle(nameRowObj, "EnabledToggle", out m_enabledToggle, out m_enabledText);
UIFactory.SetLayoutElement(enabledToggleObj, minHeight: 25, minWidth: 100, 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, "SceneLayerRow", false, true, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f));
// layer
var layerLabel = UIFactory.CreateLabel(sceneLayerRow, "LayerLabel", "Layer:", TextAnchor.MiddleCenter, Color.grey, true, 14);
UIFactory.SetLayoutElement(layerLabel.gameObject, minWidth: 55, flexibleWidth: 0);
var layerDropdownObj = UIFactory.CreateDropdown(sceneLayerRow, out m_layerDropdown, "", 14, OnLayerSelected);
m_layerDropdown.options.Clear();
for (int i = 0; i < 32; i++)
{
var layer = RuntimeProvider.Instance.LayerToName(i);
m_layerDropdown.options.Add(new Dropdown.OptionData { text = $"{i}: {layer}" });
}
UIFactory.SetLayoutElement(layerDropdownObj, minWidth: 120, flexibleWidth: 2000, minHeight: 25);
// scene
var sceneLabel = UIFactory.CreateLabel(sceneLayerRow, "SceneLabel", "Scene:", TextAnchor.MiddleCenter, Color.grey, true, 14);
UIFactory.SetLayoutElement(sceneLabel.gameObject, minWidth: 55, flexibleWidth: 0);
m_sceneText = UIFactory.CreateLabel(sceneLayerRow, "SceneText", "", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(m_sceneText.gameObject, minWidth: 120, flexibleWidth: 2000);
}
private GameObject ConstructMidGroup(GameObject parent)
{
var midGroupObj = UIFactory.CreateHorizontalGroup(parent, "MidGroup", true, true, true, true, 5, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(midGroupObj, minHeight: 300, flexibleHeight: 3000);
return midGroupObj;
}
#endregion
}
}

View File

@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Unity;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Inspectors;
namespace UnityExplorer.UI.Main.Home
{
public class InspectUnderMouse
{
public enum MouseInspectMode
{
World,
UI
}
public static bool Inspecting { get; set; }
public static MouseInspectMode Mode { get; set; }
private static GameObject s_lastHit;
private static Vector3 s_lastMousePos;
private static readonly List<Graphic> _wasDisabledGraphics = new List<Graphic>();
private static readonly List<CanvasGroup> _wasDisabledCanvasGroups = new List<CanvasGroup>();
private static readonly List<GameObject> _objectsAddedCastersTo = new List<GameObject>();
internal static Camera MainCamera;
internal static GraphicRaycaster[] graphicRaycasters;
public static void Init()
{
ConstructUI();
}
public static void StartInspect(MouseInspectMode mode)
{
MainCamera = Camera.main;
if (!MainCamera)
return;
Mode = mode;
Inspecting = true;
MainMenu.Instance.MainPanel.SetActive(false);
s_UIContent.SetActive(true);
if (mode == MouseInspectMode.UI)
SetupUIRaycast();
}
internal static void ClearHitData()
{
s_lastHit = null;
s_objNameLabel.text = "No hits...";
s_objPathLabel.text = "";
}
public static void StopInspect()
{
Inspecting = false;
MainMenu.Instance.MainPanel.SetActive(true);
s_UIContent.SetActive(false);
if (Mode == MouseInspectMode.UI)
StopUIInspect();
ClearHitData();
}
public static void UpdateInspect()
{
if (InputManager.GetKeyDown(KeyCode.Escape))
{
StopInspect();
return;
}
var mousePos = InputManager.MousePosition;
if (mousePos != s_lastMousePos)
UpdatePosition(mousePos);
// actual inspect raycast
switch (Mode)
{
case MouseInspectMode.UI:
RaycastUI(mousePos); break;
case MouseInspectMode.World:
RaycastWorld(mousePos); break;
}
}
internal static void UpdatePosition(Vector2 mousePos)
{
s_lastMousePos = mousePos;
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
float yFix = mousePos.y < 120 ? 80 : -80;
s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
}
internal static void OnHitGameObject(GameObject obj)
{
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);
}
}
// Collider raycasting
internal static void RaycastWorld(Vector2 mousePos)
{
var ray = MainCamera.ScreenPointToRay(mousePos);
Physics.Raycast(ray, out RaycastHit hit, 1000f);
if (hit.transform)
{
var obj = hit.transform.gameObject;
OnHitGameObject(obj);
}
else
{
if (s_lastHit)
ClearHitData();
}
}
// UI Graphic raycasting
private static void SetupUIRaycast()
{
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas)))
{
var canvas = obj.Cast(typeof(Canvas)) as Canvas;
if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy)
continue;
if (!canvas.GetComponent<GraphicRaycaster>())
{
canvas.gameObject.AddComponent<GraphicRaycaster>();
//ExplorerCore.Log("Added raycaster to " + canvas.name);
_objectsAddedCastersTo.Add(canvas.gameObject);
}
}
// recache Graphic Raycasters each time we start
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
graphicRaycasters = new GraphicRaycaster[casters.Length];
for (int i = 0; i < casters.Length; i++)
{
graphicRaycasters[i] = casters[i].Cast(typeof(GraphicRaycaster)) as GraphicRaycaster;
}
// enable raycastTarget on Graphics
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic)))
{
var graphic = obj.Cast(typeof(Graphic)) as Graphic;
if (!graphic || !graphic.enabled || graphic.raycastTarget || !graphic.gameObject.activeInHierarchy)
continue;
graphic.raycastTarget = true;
//ExplorerCore.Log("Enabled raycastTarget on " + graphic.name);
_wasDisabledGraphics.Add(graphic);
}
// enable blocksRaycasts on CanvasGroups
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(CanvasGroup)))
{
var canvas = obj.Cast(typeof(CanvasGroup)) as CanvasGroup;
if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts)
continue;
canvas.blocksRaycasts = true;
//ExplorerCore.Log("Enabled raycasts on " + canvas.name);
_wasDisabledCanvasGroups.Add(canvas);
}
}
internal static void RaycastUI(Vector2 mousePos)
{
var ped = new PointerEventData(null)
{
position = mousePos
};
//ExplorerCore.Log("~~~~~~~~~ begin raycast ~~~~~~~~");
GameObject hitObject = null;
int highestLayer = int.MinValue;
int highestOrder = int.MinValue;
int highestDepth = int.MinValue;
foreach (var gr in graphicRaycasters)
{
var list = new List<RaycastResult>();
RuntimeProvider.Instance.GraphicRaycast(gr, ped, list);
//gr.Raycast(ped, list);
if (list.Count > 0)
{
foreach (var hit in list)
{
// Manual trying to determine which object is "on top".
// Not perfect, but not terrible.
if (!hit.gameObject)
continue;
if (hit.gameObject.GetComponent<CanvasGroup>() is CanvasGroup group && group.alpha == 0)
continue;
if (hit.gameObject.GetComponent<Graphic>() is Graphic graphic && graphic.color.a == 0f)
continue;
if (hit.sortingLayer < highestLayer)
continue;
if (hit.sortingLayer > highestLayer)
{
highestLayer = hit.sortingLayer;
highestDepth = int.MinValue;
}
if (hit.depth < highestDepth)
continue;
if (hit.depth > highestDepth)
{
highestDepth = hit.depth;
highestOrder = int.MinValue;
}
if (hit.sortingOrder <= highestOrder)
continue;
highestOrder = hit.sortingOrder;
hitObject = hit.gameObject;
}
}
else
{
if (s_lastHit)
ClearHitData();
}
}
if (hitObject)
OnHitGameObject(hitObject);
//ExplorerCore.Log("~~~~~~~~~ end raycast ~~~~~~~~");
}
private static void StopUIInspect()
{
foreach (var obj in _objectsAddedCastersTo)
{
if (obj.GetComponent<GraphicRaycaster>() is GraphicRaycaster raycaster)
GameObject.Destroy(raycaster);
}
foreach (var graphic in _wasDisabledGraphics)
graphic.raycastTarget = false;
foreach (var canvas in _wasDisabledCanvasGroups)
canvas.blocksRaycasts = false;
_objectsAddedCastersTo.Clear();
_wasDisabledCanvasGroups.Clear();
_wasDisabledGraphics.Clear();
}
internal static Text s_objNameLabel;
internal static Text s_objPathLabel;
internal static Text s_mousePosLabel;
internal static GameObject s_UIContent;
internal static void ConstructUI()
{
s_UIContent = UIFactory.CreatePanel("InspectUnderMouse_UI", out GameObject content);
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, 150);
var group = content.GetComponent<VerticalLayoutGroup>();
group.childForceExpandHeight = true;
// Title text
UIFactory.CreateLabel(content, "InspectLabel", "<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)", TextAnchor.MiddleCenter);
s_mousePosLabel = UIFactory.CreateLabel(content, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter);
s_objNameLabel = UIFactory.CreateLabel(content, "HitLabelObj", "No hits...", TextAnchor.MiddleLeft);
s_objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
s_objPathLabel = UIFactory.CreateLabel(content, "PathLabel", "", TextAnchor.MiddleLeft);
s_objPathLabel.fontStyle = FontStyle.Italic;
s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
UIFactory.SetLayoutElement(s_objPathLabel.gameObject, minHeight: 75);
s_UIContent.SetActive(false);
}
}
}

View File

@ -2,21 +2,16 @@
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Main.Home.Inspectors;
namespace UnityExplorer.Core.Inspectors
namespace UnityExplorer.UI.Inspectors
{
public abstract class InspectorBase
{
public object Target;
public InspectorBaseUI BaseUI;
public bool IsActive { get; private set; }
public abstract string TabLabel { get; }
public bool IsActive { get; private set; }
internal bool m_pendingDestroy;
public InspectorBase(object target)
@ -29,23 +24,19 @@ namespace UnityExplorer.Core.Inspectors
return;
}
CreateUIModule();
BaseUI.AddInspectorTab(this);
AddInspectorTab(this);
}
public abstract void CreateUIModule();
public virtual void SetActive()
{
this.IsActive = true;
BaseUI.Content?.SetActive(true);
Content?.SetActive(true);
}
public virtual void SetInactive()
{
this.IsActive = false;
BaseUI.Content?.SetActive(false);
Content?.SetActive(false);
}
public virtual void Update()
@ -56,19 +47,17 @@ namespace UnityExplorer.Core.Inspectors
return;
}
BaseUI.tabText.text = TabLabel;
m_tabText.text = TabLabel;
}
public virtual void Destroy()
{
m_pendingDestroy = true;
GameObject tabGroup = BaseUI.tabButton?.transform.parent.gameObject;
GameObject tabGroup = m_tabButton?.transform.parent.gameObject;
if (tabGroup)
{
GameObject.Destroy(tabGroup);
}
int thisIndex = -1;
if (InspectorManager.Instance.m_currentInspectors.Contains(this))
@ -88,5 +77,44 @@ namespace UnityExplorer.Core.Inspectors
}
}
}
#region UI
public abstract GameObject Content { get; set; }
public Button m_tabButton;
public Text m_tabText;
public void AddInspectorTab(InspectorBase parent)
{
var tabContent = InspectorManager.m_tabBarContent;
var tabGroupObj = UIFactory.CreateHorizontalGroup(tabContent, "TabObject", true, true, true, true);
UIFactory.SetLayoutElement(tabGroupObj, minWidth: 185, flexibleWidth: 0);
tabGroupObj.AddComponent<Mask>();
m_tabButton = UIFactory.CreateButton(tabGroupObj,
"TabButton",
"",
() => { InspectorManager.Instance.SetInspectorTab(parent); });
UIFactory.SetLayoutElement(m_tabButton.gameObject, minWidth: 165, flexibleWidth: 0);
m_tabText = m_tabButton.GetComponentInChildren<Text>();
m_tabText.horizontalOverflow = HorizontalWrapMode.Overflow;
m_tabText.alignment = TextAnchor.MiddleLeft;
var closeBtn = UIFactory.CreateButton(tabGroupObj,
"CloseButton",
"X",
parent.Destroy,
new Color(0.2f, 0.2f, 0.2f, 1));
UIFactory.SetLayoutElement(closeBtn.gameObject, minWidth: 20, flexibleWidth: 0);
var closeBtnText = closeBtn.GetComponentInChildren<Text>();
closeBtnText.color = new Color(1, 0, 0, 1);
}
#endregion
}
}

View File

@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.Main.Home;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.Inspectors.GameObjects;
using UnityExplorer.UI.Inspectors.Reflection;
namespace UnityExplorer.UI.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 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, CacheObjectBase parentMember = null)
{
obj = ReflectionProvider.Instance.Cast(obj, ReflectionProvider.Instance.GetActualType(obj));
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 (RuntimeProvider.Instance.IsReferenceEqual(obj, tab.Target))
{
SetInspectorTab(tab);
return;
}
}
InspectorBase inspector;
if (obj is GameObject go)
inspector = new GameObjectInspector(go);
else
inspector = new InstanceInspector(obj);
if (inspector is ReflectionInspector ri)
ri.ParentMember = parentMember;
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();
OnSetInspectorTab(inspector);
}
public void UnsetInspectorTab()
{
if (m_activeInspector == null)
return;
m_activeInspector.SetInactive();
OnUnsetInspectorTab();
m_activeInspector = null;
}
public static GameObject m_tabBarContent;
public static GameObject m_inspectorContent;
public void OnSetInspectorTab(InspectorBase inspector)
{
Color activeColor = new Color(0, 0.25f, 0, 1);
RuntimeProvider.Instance.SetColorBlock(inspector.m_tabButton, activeColor, activeColor);
}
public void OnUnsetInspectorTab()
{
RuntimeProvider.Instance.SetColorBlock(m_activeInspector.m_tabButton,
new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.1f, 0.3f, 0.1f, 1));
}
public void ConstructInspectorPane()
{
var mainObj = UIFactory.CreateVerticalGroup(HomePage.Instance.Content,
"InspectorManager_Root",
true, true, true, true,
4,
new Vector4(4,4,4,4));
UIFactory.SetLayoutElement(mainObj, preferredHeight: 400, flexibleHeight: 9000, preferredWidth: 620, flexibleWidth: 9000);
var topRowObj = UIFactory.CreateHorizontalGroup(mainObj, "TopRow", false, true, true, true, 15);
var inspectorTitle = UIFactory.CreateLabel(topRowObj, "Title", "Inspector", TextAnchor.MiddleLeft, default, true, 25);
UIFactory.SetLayoutElement(inspectorTitle.gameObject, minHeight: 30, flexibleHeight: 0, minWidth: 90, flexibleWidth: 20000);
ConstructToolbar(topRowObj);
// inspector tab bar
m_tabBarContent = UIFactory.CreateGridGroup(mainObj, "TabHolder", 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, "InspectorContent",
true, true, true, true,
0,
new Vector4(2,2,2,2),
new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(m_inspectorContent, preferredHeight: 900, flexibleHeight: 10000, preferredWidth: 600, flexibleWidth: 10000);
}
private static void ConstructToolbar(GameObject topRowObj)
{
// invisible group
UIFactory.CreateHorizontalGroup(topRowObj, "Toolbar", false, false, true, true, 10, new Vector4(2, 2, 2, 2), new Color(1,1,1,0));
// inspect under mouse button
AddMouseInspectButton(topRowObj, "UI", InspectUnderMouse.MouseInspectMode.UI);
AddMouseInspectButton(topRowObj, "3D", InspectUnderMouse.MouseInspectMode.World);
}
private static void AddMouseInspectButton(GameObject topRowObj, string suffix, InspectUnderMouse.MouseInspectMode mode)
{
string lbl = $"Mouse Inspect ({suffix})";
var inspectObj = UIFactory.CreateButton(topRowObj,
lbl,
lbl,
() => { InspectUnderMouse.StartInspect(mode); },
new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(inspectObj.gameObject, minWidth: 150, flexibleWidth: 0);
}
}
}

View File

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer.UI.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) { }
internal void OnScopeFilterClicked(MemberScopes type, Button button)
{
if (m_lastActiveScopeButton)
RuntimeProvider.Instance.SetColorBlock(m_lastActiveScopeButton, new Color(0.2f, 0.2f, 0.2f));
m_scopeFilter = type;
m_lastActiveScopeButton = button;
RuntimeProvider.Instance.SetColorBlock(m_lastActiveScopeButton, new Color(0.2f, 0.6f, 0.2f));
FilterMembers(null, true);
m_sliderScroller.m_slider.value = 1f;
}
public void ConstructUnityInstanceHelpers()
{
if (!typeof(UnityEngine.Object).IsAssignableFrom(m_targetType))
return;
var rowObj = UIFactory.CreateHorizontalGroup(Content, "InstanceHelperRow", true, true, true, true, 5, new Vector4(2,2,2,2),
new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000);
if (typeof(Component).IsAssignableFrom(m_targetType))
ConstructCompHelper(rowObj);
ConstructUnityObjHelper(rowObj);
if (m_targetType == typeof(Texture2D))
ConstructTextureHelper();
}
internal void ConstructCompHelper(GameObject rowObj)
{
var gameObjectLabel = UIFactory.CreateLabel(rowObj, "GameObjectLabel", "GameObject:", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(gameObjectLabel.gameObject, minWidth: 90, minHeight: 25, flexibleWidth: 0);
var comp = Target.Cast(typeof(Component)) as Component;
var btn = UIFactory.CreateButton(rowObj,
"GameObjectButton",
comp.name,
() => { InspectorManager.Instance.Inspect(comp.gameObject); },
new Color(0.2f, 0.5f, 0.2f));
UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 200, flexibleWidth: 0);
}
internal void ConstructUnityObjHelper(GameObject rowObj)
{
var label = UIFactory.CreateLabel(rowObj, "NameLabel", "Name:", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 60, minHeight: 25, flexibleWidth: 0);
var uObj = Target.Cast(typeof(UnityEngine.Object)) as UnityEngine.Object;
var inputObj = UIFactory.CreateInputField(rowObj, "NameInput", "...", 14, 3, 1);
UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleWidth: 2000);
var inputField = inputObj.GetComponent<InputField>();
inputField.readOnly = true;
inputField.text = uObj.name;
}
internal bool showingTextureHelper;
internal bool constructedTextureViewer;
internal GameObject m_textureViewerObj;
internal void ConstructTextureHelper()
{
var rowObj = UIFactory.CreateHorizontalGroup(Content, "TextureHelper", true, false, true, true, 5, new Vector4(3,3,3,3),
new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleHeight: 0);
var showBtn = UIFactory.CreateButton(rowObj, "ShowButton", "Show", null, new Color(0.2f, 0.6f, 0.2f));
UIFactory.SetLayoutElement(showBtn.gameObject, minWidth: 50, flexibleWidth: 0);
UIFactory.CreateLabel(rowObj, "TextureViewerLabel", "Texture Viewer", TextAnchor.MiddleLeft);
var textureViewerObj = UIFactory.CreateScrollView(Content, "TextureViewerContent", out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(textureViewerObj, false, false, true, true);
UIFactory.SetLayoutElement(textureViewerObj, minHeight: 100, flexibleHeight: 9999, flexibleWidth: 9999);
textureViewerObj.SetActive(false);
m_textureViewerObj = textureViewerObj;
var showText = showBtn.GetComponentInChildren<Text>();
showBtn.onClick.AddListener(() =>
{
showingTextureHelper = !showingTextureHelper;
if (showingTextureHelper)
{
if (!constructedTextureViewer)
ConstructTextureViewerArea(scrollContent);
showText.text = "Hide";
ToggleTextureViewer(true);
}
else
{
showText.text = "Show";
ToggleTextureViewer(false);
}
});
}
internal void ConstructTextureViewerArea(GameObject parent)
{
constructedTextureViewer = true;
var tex = Target.Cast(typeof(Texture2D)) as Texture2D;
if (!tex)
{
ExplorerCore.LogWarning("Could not cast the target instance to Texture2D! Maybe its null or destroyed?");
return;
}
// Save helper
var saveRowObj = UIFactory.CreateHorizontalGroup(parent, "SaveRow", true, true, true, true, 2, new Vector4(2,2,2,2),
new Color(0.1f, 0.1f, 0.1f));
var saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", null, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(saveBtn.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0);
var inputObj = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...");
UIFactory.SetLayoutElement(inputObj, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
var inputField = inputObj.GetComponent<InputField>();
var name = tex.name;
if (string.IsNullOrEmpty(name))
name = "untitled";
inputField.text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png");
saveBtn.onClick.AddListener(() =>
{
if (tex && !string.IsNullOrEmpty(inputField.text))
{
var path = inputField.text;
if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
{
ExplorerCore.LogWarning("Desired save path must end with '.png'!");
return;
}
var dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
if (File.Exists(path))
File.Delete(path);
if (!TextureUtilProvider.IsReadable(tex))
tex = TextureUtilProvider.ForceReadTexture(tex);
byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex);
File.WriteAllBytes(path, data);
}
});
// Actual texture viewer
var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent);
var image = imageObj.AddComponent<Image>();
var sprite = TextureUtilProvider.Instance.CreateSprite(tex);
image.sprite = sprite;
var fitter = imageObj.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var imageLayout = imageObj.AddComponent<LayoutElement>();
imageLayout.preferredHeight = sprite.rect.height;
imageLayout.preferredWidth = sprite.rect.width;
}
internal void ToggleTextureViewer(bool enabled)
{
m_textureViewerObj.SetActive(enabled);
m_filterAreaObj.SetActive(!enabled);
m_memberListObj.SetActive(!enabled);
m_updateRowObj.SetActive(!enabled);
}
public void ConstructInstanceFilters(GameObject parent)
{
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(parent, "InstanceFilterRow", false, false, true, true, 5, default,
new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(memberFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000);
var memLabel = UIFactory.CreateLabel(memberFilterRowObj, "MemberLabel", "Filter scope:", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(memLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0);
AddFilterButton(memberFilterRowObj, MemberScopes.All, true);
AddFilterButton(memberFilterRowObj, MemberScopes.Instance);
AddFilterButton(memberFilterRowObj, MemberScopes.Static);
}
private void AddFilterButton(GameObject parent, MemberScopes type, bool setEnabled = false)
{
var btn = UIFactory.CreateButton(parent,
"ScopeFilterButton_" + type,
type.ToString(),
null,
new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 70);
btn.onClick.AddListener(() => { OnScopeFilterClicked(type, btn); });
RuntimeProvider.Instance.SetColorBlock(btn, highlighted: new Color(0.3f, 0.7f, 0.3f));
if (setEnabled)
{
RuntimeProvider.Instance.SetColorBlock(btn, new Color(0.2f, 0.6f, 0.2f));
m_scopeFilter = type;
m_lastActiveScopeButton = btn;
}
}
}
}

View File

@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Unity;
using UnityEngine;
using UnityExplorer.Core.Inspectors.Reflection;
using UnityExplorer.UI.Reusable;
using System.Reflection;
using UnityExplorer.UI;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.Main;
using UnityExplorer.UI.Main.Home;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Main.Home.Inspectors;
namespace UnityExplorer.Core.Inspectors
namespace UnityExplorer.UI.Inspectors.Reflection
{
public class ReflectionInspector : InspectorBase
{
@ -24,16 +23,16 @@ namespace UnityExplorer.Core.Inspectors
static ReflectionInspector()
{
PanelDragger.OnFinishResize += OnContainerResized;
PanelDragger.OnFinishResize += (RectTransform _) => OnContainerResized();
SceneExplorer.OnToggleShow += OnContainerResized;
}
private static void OnContainerResized()
private static void OnContainerResized(bool _ = false)
{
if (ActiveInstance == null)
return;
ActiveInstance.ReflectionUI.m_widthUpdateWanted = true;
ActiveInstance.m_widthUpdateWanted = true;
}
// Blacklists
@ -64,8 +63,6 @@ namespace UnityExplorer.Core.Inspectors
internal CacheObjectBase ParentMember { get; set; }
internal ReflectionInspectorUI ReflectionUI;
internal readonly Type m_targetType;
internal readonly string m_targetTypeShortName;
@ -74,7 +71,7 @@ namespace UnityExplorer.Core.Inspectors
// 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[ExplorerConfig.Instance.Default_Page_Limit];
internal readonly CacheMember[] m_displayedMembers = new CacheMember[ConfigManager.Default_Page_Limit.Value];
internal bool m_autoUpdate;
@ -83,22 +80,17 @@ namespace UnityExplorer.Core.Inspectors
if (this is StaticInspector)
m_targetType = target as Type;
else
m_targetType = ReflectionUtility.GetType(target);
m_targetType = ReflectionUtility.GetActualType(target);
m_targetTypeShortName = SignatureHighlighter.ParseFullSyntax(m_targetType, false);
ReflectionUI.ConstructUI();
ConstructUI();
CacheMembers(m_targetType);
FilterMembers();
}
public override void CreateUIModule()
{
BaseUI = ReflectionUI = new ReflectionInspectorUI(this);
}
public override void SetActive()
{
base.SetActive();
@ -115,8 +107,8 @@ namespace UnityExplorer.Core.Inspectors
{
base.Destroy();
if (this.BaseUI.Content)
GameObject.Destroy(this.BaseUI.Content);
if (this.Content)
GameObject.Destroy(this.Content);
}
internal void OnPageTurned()
@ -188,11 +180,11 @@ namespace UnityExplorer.Core.Inspectors
cachedSigs.Add(sig);
if (mi != null)
list.Add(new CacheMethod(mi, target, ReflectionUI.m_scrollContent));
list.Add(new CacheMethod(mi, target, m_scrollContent));
else if (pi != null)
list.Add(new CacheProperty(pi, target, ReflectionUI.m_scrollContent));
list.Add(new CacheProperty(pi, target, m_scrollContent));
else
list.Add(new CacheField(fi, target, ReflectionUI.m_scrollContent));
list.Add(new CacheField(fi, target, m_scrollContent));
list.Last().ParentInspector = this;
}
@ -233,37 +225,31 @@ namespace UnityExplorer.Core.Inspectors
}
}
if (ReflectionUI.m_widthUpdateWanted)
if (m_widthUpdateWanted)
{
if (!ReflectionUI.m_widthUpdateWaiting)
ReflectionUI.m_widthUpdateWaiting = true;
if (!m_widthUpdateWaiting)
m_widthUpdateWaiting = true;
else
{
UpdateWidths();
ReflectionUI.m_widthUpdateWaiting = false;
ReflectionUI.m_widthUpdateWanted = false;
m_widthUpdateWaiting = false;
m_widthUpdateWanted = false;
}
}
}
internal void OnMemberFilterClicked(MemberTypes type, Button button)
{
if (ReflectionUI.m_lastActiveMemButton)
{
var lastColors = ReflectionUI.m_lastActiveMemButton.colors;
lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
ReflectionUI.m_lastActiveMemButton.colors = lastColors;
}
if (m_lastActiveMemButton)
RuntimeProvider.Instance.SetColorBlock(m_lastActiveMemButton, new Color(0.2f, 0.2f, 0.2f));
ReflectionUI.m_memberFilter = type;
ReflectionUI.m_lastActiveMemButton = button;
m_memberFilter = type;
m_lastActiveMemButton = button;
var colors = ReflectionUI.m_lastActiveMemButton.colors;
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
ReflectionUI.m_lastActiveMemButton.colors = colors;
RuntimeProvider.Instance.SetColorBlock(m_lastActiveMemButton, new Color(0.2f, 0.6f, 0.2f));
FilterMembers(null, true);
ReflectionUI.m_sliderScroller.m_slider.value = 1f;
m_sliderScroller.m_slider.value = 1f;
}
public void FilterMembers(string nameFilter = null, bool force = false)
@ -271,12 +257,12 @@ namespace UnityExplorer.Core.Inspectors
int lastCount = m_membersFiltered.Count;
m_membersFiltered.Clear();
nameFilter = nameFilter?.ToLower() ?? ReflectionUI.m_nameFilterText.text.ToLower();
nameFilter = nameFilter?.ToLower() ?? m_nameFilterText.text.ToLower();
foreach (var mem in m_allMembers)
{
// membertype filter
if (ReflectionUI.m_memberFilter != MemberTypes.All && mem.MemInfo.MemberType != ReflectionUI.m_memberFilter)
if (m_memberFilter != MemberTypes.All && mem.MemInfo.MemberType != m_memberFilter)
continue;
if (this is InstanceInspector ii && ii.m_scopeFilter != MemberScopes.All)
@ -301,7 +287,7 @@ namespace UnityExplorer.Core.Inspectors
public void RefreshDisplay()
{
var members = m_membersFiltered;
ReflectionUI.m_pageHandler.ListCount = members.Count;
m_pageHandler.ListCount = members.Count;
// disable current members
for (int i = 0; i < m_displayedMembers.Length; i++)
@ -316,17 +302,17 @@ namespace UnityExplorer.Core.Inspectors
if (members.Count < 1)
return;
foreach (var itemIndex in ReflectionUI.m_pageHandler)
foreach (var itemIndex in m_pageHandler)
{
if (itemIndex >= members.Count)
break;
CacheMember member = members[itemIndex];
m_displayedMembers[itemIndex - ReflectionUI.m_pageHandler.StartIndex] = member;
m_displayedMembers[itemIndex - m_pageHandler.StartIndex] = member;
member.Enable();
}
ReflectionUI.m_widthUpdateWanted = true;
m_widthUpdateWanted = true;
}
internal void UpdateWidths()
@ -338,13 +324,13 @@ namespace UnityExplorer.Core.Inspectors
if (cache == null)
break;
var width = cache.GetMemberLabelWidth(ReflectionUI.m_scrollContentRect);
var width = cache.GetMemberLabelWidth(m_scrollContentRect);
if (width > labelWidth)
labelWidth = width;
}
float valueWidth = ReflectionUI.m_scrollContentRect.rect.width - labelWidth - 20;
float valueWidth = m_scrollContentRect.rect.width - labelWidth - 20;
foreach (var cache in m_displayedMembers)
{
@ -356,5 +342,177 @@ namespace UnityExplorer.Core.Inspectors
#endregion // end instance
#region UI
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;
internal GameObject m_filterAreaObj;
internal GameObject m_updateRowObj;
internal GameObject m_memberListObj;
internal void ConstructUI()
{
var parent = InspectorManager.m_inspectorContent;
this.Content = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, false, true, true, 5, new Vector4(4,4,4,4),
new Color(0.15f, 0.15f, 0.15f));
ConstructTopArea();
ConstructMemberList();
}
internal void ConstructTopArea()
{
var nameRowObj = UIFactory.CreateHorizontalGroup(Content, "NameRowObj", true, true, true, true, 2, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(nameRowObj, minHeight: 25, flexibleHeight: 0, minWidth: 200, flexibleWidth: 5000);
var typeLabelText = UIFactory.CreateLabel(nameRowObj, "TypeLabel", "Type:", TextAnchor.MiddleLeft);
typeLabelText.horizontalOverflow = HorizontalWrapMode.Overflow;
UIFactory.SetLayoutElement(typeLabelText.gameObject, minWidth: 40, flexibleWidth: 0, minHeight: 25);
var typeDisplay = UIFactory.CreateLabel(nameRowObj, "TypeDisplayText", SignatureHighlighter.ParseFullSyntax(m_targetType, true),
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(typeDisplay.gameObject, minHeight: 25, flexibleWidth: 5000);
// instance helper tools
if (this is InstanceInspector instanceInspector)
{
instanceInspector.ConstructUnityInstanceHelpers();
}
ConstructFilterArea();
ConstructUpdateRow();
}
internal void ConstructFilterArea()
{
// Filters
m_filterAreaObj = UIFactory.CreateVerticalGroup(Content, "FilterGroup", true, true, true, true, 4, new Vector4(4,4,4,4),
new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(m_filterAreaObj, minHeight: 60);
// name filter
var nameFilterRowObj = UIFactory.CreateHorizontalGroup(m_filterAreaObj, "NameFilterRow", false, false, true, true, 5, default,
new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(nameFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000);
var nameLabel = UIFactory.CreateLabel(nameFilterRowObj, "NameLabel", "Filter names:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(nameLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0);
var nameInputObj = UIFactory.CreateInputField(nameFilterRowObj, "NameInput", "...", 14, (int)TextAnchor.MiddleLeft, (int)HorizontalWrapMode.Overflow);
UIFactory.SetLayoutElement(nameInputObj, flexibleWidth: 5000, minWidth: 100, minHeight: 25);
var nameInput = nameInputObj.GetComponent<InputField>();
nameInput.onValueChanged.AddListener((string val) => { FilterMembers(val); });
m_nameFilterText = nameInput.textComponent;
// membertype filter
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(m_filterAreaObj, "MemberFilter", false, false, true, true, 5, default,
new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(memberFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000);
var memLabel = UIFactory.CreateLabel(memberFilterRowObj, "MemberFilterLabel", "Filter members:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(memLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0);
AddFilterButton(memberFilterRowObj, MemberTypes.All);
AddFilterButton(memberFilterRowObj, MemberTypes.Method);
AddFilterButton(memberFilterRowObj, MemberTypes.Property, true);
AddFilterButton(memberFilterRowObj, MemberTypes.Field);
// Instance filters
if (this is InstanceInspector instanceInspector)
{
instanceInspector.ConstructInstanceFilters(m_filterAreaObj);
}
}
private void AddFilterButton(GameObject parent, MemberTypes type, bool setEnabled = false)
{
var btn = UIFactory.CreateButton(parent,
"FilterButton_" + type,
type.ToString(),
null,
new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 70);
btn.onClick.AddListener(() => { OnMemberFilterClicked(type, btn); });
RuntimeProvider.Instance.SetColorBlock(btn, highlighted: new Color(0.3f, 0.7f, 0.3f));
if (setEnabled)
{
RuntimeProvider.Instance.SetColorBlock(btn, new Color(0.2f, 0.6f, 0.2f));
m_memberFilter = type;
m_lastActiveMemButton = btn;
}
}
internal void ConstructUpdateRow()
{
m_updateRowObj = UIFactory.CreateHorizontalGroup(Content, "UpdateRow", false, true, true, true, 10, default, new Color(1, 1, 1, 0));
UIFactory.SetLayoutElement(m_updateRowObj, minHeight: 25);
// update button
var updateBtn = UIFactory.CreateButton(m_updateRowObj, "UpdateButton", "Update Values", null, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(updateBtn.gameObject, minWidth: 110, flexibleWidth: 0);
updateBtn.onClick.AddListener(() =>
{
bool orig = m_autoUpdate;
m_autoUpdate = true;
Update();
if (!orig)
m_autoUpdate = orig;
});
// auto update
var autoUpdateObj = UIFactory.CreateToggle(m_updateRowObj, "UpdateToggle", 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()
{
m_memberListObj = UIFactory.CreateScrollView(Content, "MemberList", out m_scrollContent, out m_sliderScroller, new Color(0.05f, 0.05f, 0.05f));
m_scrollContentRect = m_scrollContent.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(m_scrollContent, forceHeight: true, spacing: 3, padLeft: 0, padRight: 0);
m_pageHandler = new PageHandler(m_sliderScroller);
m_pageHandler.ConstructUI(Content);
m_pageHandler.OnPageChanged += OnPageTurned;
}
}
}
#endregion
}

View File

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.Inspectors.Reflection
{
public class StaticInspector : ReflectionInspector
{

View File

@ -6,8 +6,9 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.CacheObject;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveBool : InteractiveValue
{
@ -87,27 +88,22 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
var toggleObj = UIFactory.CreateToggle(m_mainContent, "InteractiveBoolToggle", out m_toggle, out _, new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(toggleObj, 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(); });
m_applyBtn = UIFactory.CreateButton(m_mainContent,
"ApplyButton",
"Apply",
() => { Owner.SetValue(); },
new Color(0.2f, 0.2f, 0.2f));
var applyText = applyBtnObj.GetComponentInChildren<Text>();
applyText.text = "Apply";
UIFactory.SetLayoutElement(m_applyBtn.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0);
toggleObj.SetActive(false);
applyBtnObj.SetActive(false);
m_applyBtn.gameObject.SetActive(false);
}
}
}

View File

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveColor : InteractiveValue
{
//~~~~~~~~~ Instance ~~~~~~~~~~
public InteractiveColor(object value, Type valueType) : base(value, valueType) { }
public override bool HasSubContent => true;
public override bool SubContentWanted => true;
public override bool WantInspectBtn => true;
public override void RefreshUIForValue()
{
base.RefreshUIForValue();
if (m_subContentConstructed)
RefreshUI();
}
private void RefreshUI()
{
var color = (Color)this.Value;
m_inputs[0].text = color.r.ToString();
m_inputs[1].text = color.g.ToString();
m_inputs[2].text = color.b.ToString();
m_inputs[3].text = color.a.ToString();
if (m_colorImage)
m_colorImage.color = color;
}
internal override void OnToggleSubcontent(bool toggle)
{
base.OnToggleSubcontent(toggle);
RefreshUI();
}
#region UI CONSTRUCTION
private Image m_colorImage;
private readonly InputField[] m_inputs = new InputField[4];
private readonly Slider[] m_sliders = new Slider[4];
public override void ConstructUI(GameObject parent, GameObject subGroup)
{
base.ConstructUI(parent, subGroup);
//// Limit the label width for colors, they're always about the same so make use of that space.
//UIFactory.SetLayoutElement(this.m_baseLabel.gameObject, flexibleWidth: 0, minWidth: 250);
}
public override void ConstructSubcontent()
{
base.ConstructSubcontent();
var horiGroup = UIFactory.CreateHorizontalGroup(m_subContentParent, "ColorEditor", false, false, true, true, 5,
default, default, TextAnchor.MiddleLeft);
var editorContainer = UIFactory.CreateVerticalGroup(horiGroup, "EditorContent", false, true, true, true, 2, new Vector4(4, 4, 4, 4),
new Color(0.08f, 0.08f, 0.08f));
UIFactory.SetLayoutElement(editorContainer, minWidth: 300, flexibleWidth: 0);
for (int i = 0; i < 4; i++)
AddEditorRow(i, editorContainer);
if (Owner.CanWrite)
{
var applyBtn = UIFactory.CreateButton(editorContainer, "ApplyButton", "Apply", OnSetValue, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(applyBtn.gameObject, minWidth: 175, minHeight: 25, flexibleWidth: 0);
void OnSetValue()
{
Owner.SetValue();
RefreshUIForValue();
}
}
var imgHolder = UIFactory.CreateVerticalGroup(horiGroup, "ImgHolder", true, true, true, true, 0, new Vector4(1, 1, 1, 1),
new Color(0.08f, 0.08f, 0.08f));
UIFactory.SetLayoutElement(imgHolder, minWidth: 128, minHeight: 128, flexibleWidth: 0, flexibleHeight: 0);
var imgObj = UIFactory.CreateUIObject("ColorImageHelper", imgHolder, new Vector2(100, 25));
m_colorImage = imgObj.AddComponent<Image>();
m_colorImage.color = (Color)this.Value;
}
private static readonly string[] s_fieldNames = new[] { "R", "G", "B", "A" };
internal void AddEditorRow(int index, GameObject groupObj)
{
var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow_" + s_fieldNames[index],
false, true, true, true, 5, default, new Color(1, 1, 1, 0));
var label = UIFactory.CreateLabel(row, "RowLabel", $"{s_fieldNames[index]}:", TextAnchor.MiddleRight, Color.cyan);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 50, flexibleWidth: 0, minHeight: 25);
var inputFieldObj = UIFactory.CreateInputField(row, "InputField", "...", 14, 3, 1);
UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0);
var inputField = inputFieldObj.GetComponent<InputField>();
m_inputs[index] = inputField;
inputField.characterValidation = InputField.CharacterValidation.Decimal;
inputField.onValueChanged.AddListener((string value) =>
{
float val = float.Parse(value);
SetValueToColor(val);
m_sliders[index].value = val;
});
var sliderObj = UIFactory.CreateSlider(row, "Slider", out Slider slider);
m_sliders[index] = slider;
UIFactory.SetLayoutElement(sliderObj, minWidth: 200, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
slider.minValue = 0;
slider.maxValue = 1;
slider.value = GetValueFromColor();
slider.onValueChanged.AddListener((float value) =>
{
inputField.text = value.ToString();
SetValueToColor(value);
m_inputs[index].text = value.ToString();
});
// methods for writing to the color for this field
void SetValueToColor(float floatValue)
{
Color _color = (Color)Value;
switch (index)
{
case 0: _color.r = floatValue; break;
case 1: _color.g = floatValue; break;
case 2: _color.b = floatValue; break;
case 3: _color.a = floatValue; break;
}
Value = _color;
m_colorImage.color = _color;
}
float GetValueFromColor()
{
Color _color = (Color)Value;
switch (index)
{
case 0: return _color.r;
case 1: return _color.g;
case 2: return _color.b;
case 3: return _color.a;
default: throw new NotImplementedException();
}
}
}
#endregion
}
}

View File

@ -8,13 +8,17 @@ using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using System.Reflection;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.Core;
using UnityExplorer.UI.Utility;
#if CPP
using CppDictionary = Il2CppSystem.Collections.IDictionary;
using AltIDictionary = Il2CppSystem.Collections.IDictionary;
#else
using AltIDictionary = System.Collections.IDictionary;
#endif
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveDictionary : InteractiveValue
{
@ -35,7 +39,6 @@ namespace UnityExplorer.Core.Inspectors.Reflection
public override bool WantInspectBtn => false;
public override bool HasSubContent => true;
// todo fix for il2cpp
public override bool SubContentWanted
{
get
@ -47,11 +50,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
}
internal IDictionary RefIDictionary;
#if CPP
internal CppDictionary RefCppDictionary;
#else
internal IDictionary RefCppDictionary = null;
#endif
internal AltIDictionary RefAltIDictionary;
internal Type m_typeOfKeys;
internal Type m_typeofValues;
@ -59,7 +58,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
= new List<KeyValuePair<CachePaired, CachePaired>>();
internal readonly KeyValuePair<CachePaired, CachePaired>[] m_displayedEntries
= new KeyValuePair<CachePaired, CachePaired>[ExplorerConfig.Instance.Default_Page_Limit];
= new KeyValuePair<CachePaired, CachePaired>[ConfigManager.Default_Page_Limit.Value];
internal bool m_recacheWanted = true;
@ -72,10 +71,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
RefIDictionary = Value as IDictionary;
#if CPP
try { RefCppDictionary = (Value as Il2CppSystem.Object).TryCast<CppDictionary>(); }
catch { }
#endif
if (RefIDictionary == null)
{
try { RefAltIDictionary = Value.TryCast<AltIDictionary>(); }
catch { }
}
if (m_subContentParent.activeSelf)
{
@ -128,10 +128,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
m_entries.Clear();
}
#if CPP
if (RefIDictionary == null && Value != null)
RefIDictionary = EnumerateWithReflection();
#endif
RefIDictionary = RuntimeProvider.Instance.Reflection.EnumerateDictionary(Value, m_typeOfKeys, m_typeofValues);
if (RefIDictionary != null)
{
@ -211,65 +209,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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
EnumerateCollection(keys, keyList);
EnumerateCollection(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 EnumerateCollection(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);
@ -283,38 +227,21 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
m_listContent = UIFactory.CreateVerticalGroup(m_subContentParent, "DictionaryContent", true, true, true, true, 2, new Vector4(5,5,5,5),
new Color(0.08f, 0.08f, 0.08f));
var scrollRect = scrollObj.GetComponent<RectTransform>();
var scrollRect = m_listContent.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>();
var contentFitter = m_listContent.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
}
}

View File

@ -7,7 +7,7 @@ using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveEnum : InteractiveValue
{
@ -94,7 +94,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
base.RefreshUIForValue();
if (m_subContentConstructed)
if (m_subContentConstructed && !(this is InteractiveFlags))
{
m_dropdownText.text = Value?.ToString() ?? "<no value set>";
}
@ -136,32 +136,18 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
var groupObj = UIFactory.CreateHorizontalGroup(m_subContentParent, "InteractiveEnumGroup", false, true, true, true, 5,
new Vector4(3,3,3,3),new Color(1, 1, 1, 0));
// 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);
var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromDropdown, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(apply.gameObject, minHeight: 25, minWidth: 50);
// dropdown
var dropdownObj = UIFactory.CreateDropdown(groupObj, out m_dropdown);
var dropLayout = dropdownObj.AddComponent<LayoutElement>();
dropLayout.minWidth = 150;
dropLayout.minHeight = 25;
dropLayout.flexibleWidth = 120;
var dropdownObj = UIFactory.CreateDropdown(groupObj, out m_dropdown, "", 14, null);
UIFactory.SetLayoutElement(dropdownObj, minWidth: 150, minHeight: 25, flexibleWidth: 120);
foreach (var kvp in m_values)
{

View File

@ -6,12 +6,14 @@ using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Reusable;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.Utility;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveEnumerable : InteractiveValue
{
@ -37,16 +39,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
internal IEnumerable RefIEnumerable;
internal IList RefIList;
#if CPP
internal Il2CppSystem.Collections.ICollection CppICollection;
#else
internal ICollection CppICollection = null;
#endif
internal readonly Type m_baseEntryType;
internal readonly List<CacheEnumerated> m_entries = new List<CacheEnumerated>();
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ExplorerConfig.Instance.Default_Page_Limit];
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ConfigManager.Default_Page_Limit.Value];
internal bool m_recacheWanted = true;
public override void OnValueUpdated()
@ -54,14 +51,6 @@ namespace UnityExplorer.Core.Inspectors.Reflection
RefIEnumerable = Value as IEnumerable;
RefIList = Value as IList;
#if CPP
if (Value != null && RefIList == null)
{
try { CppICollection = (Value as Il2CppSystem.Object).TryCast<Il2CppSystem.Collections.ICollection>(); }
catch { }
}
#endif
if (m_subContentParent.activeSelf)
{
GetCacheEntries();
@ -90,8 +79,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
if (Value != null)
{
string count = "?";
if (m_recacheWanted && (RefIList != null || CppICollection != null))
count = RefIList?.Count.ToString() ?? CppICollection.Count.ToString();
if (m_recacheWanted && RefIList != null)
count = RefIList.Count.ToString();
else if (!m_recacheWanted)
count = m_entries.Count.ToString();
@ -115,10 +104,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
m_entries.Clear();
}
#if CPP
if (RefIEnumerable == null && Value != null)
RefIEnumerable = EnumerateWithReflection();
#endif
RefIEnumerable = RuntimeProvider.Instance.Reflection.EnumerateEnumerable(Value);
if (RefIEnumerable != null)
{
@ -182,62 +169,6 @@ namespace UnityExplorer.Core.Inspectors.Reflection
RefreshDisplay();
}
#region CPP Helpers
#if CPP
// some temp fixes for Il2Cpp IEnumerables until interfaces are fixed
internal static readonly Dictionary<Type, MethodInfo> s_getEnumeratorMethods = new Dictionary<Type, MethodInfo>();
internal static readonly Dictionary<Type, EnumeratorInfo> s_enumeratorInfos = new Dictionary<Type, EnumeratorInfo>();
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
private IEnumerable EnumerateWithReflection()
{
if (Value == null)
return null;
// new test
var CppEnumerable = (Value as Il2CppSystem.Object)?.TryCast<Il2CppSystem.Collections.IEnumerable>();
if (CppEnumerable != null)
{
var type = Value.GetType();
if (!s_getEnumeratorMethods.ContainsKey(type))
s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator"));
var enumerator = s_getEnumeratorMethods[type].Invoke(Value, null);
var enumeratorType = enumerator.GetType();
if (!s_enumeratorInfos.ContainsKey(enumeratorType))
{
s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo
{
current = enumeratorType.GetProperty("Current"),
moveNext = enumeratorType.GetMethod("MoveNext"),
});
}
var info = s_enumeratorInfos[enumeratorType];
// iterate
var list = new List<object>();
while ((bool)info.moveNext.Invoke(enumerator, null))
list.Add(info.current.GetValue(enumerator));
return list;
}
return null;
}
#endif
#endregion
#region UI CONSTRUCTION
internal GameObject m_listContent;
internal LayoutElement m_listLayout;
@ -257,10 +188,10 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
m_listContent = UIFactory.CreateVerticalGroup(this.m_subContentParent, "EnumerableContent", true, true, true, true, 2, new Vector4(5,5,5,5),
new Color(0.08f, 0.08f, 0.08f));
var scrollRect = scrollObj.GetComponent<RectTransform>();
var scrollRect = m_listContent.GetComponent<RectTransform>();
scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
m_listLayout = Owner.m_mainContent.GetComponent<LayoutElement>();
@ -268,20 +199,9 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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>();
var contentFitter = m_listContent.AddComponent<ContentSizeFitter>();
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
#endregion
}
}

View File

@ -7,7 +7,7 @@ using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveFlags : InteractiveEnum
{
@ -26,8 +26,6 @@ namespace UnityExplorer.Core.Inspectors.Reflection
public override void OnValueUpdated()
{
base.OnValueUpdated();
if (Owner.CanWrite)
{
var enabledNames = new List<string>();
@ -37,10 +35,10 @@ namespace UnityExplorer.Core.Inspectors.Reflection
enabledNames.AddRange(enabled);
for (int i = 0; i < m_values.Length; i++)
{
m_enabledFlags[i] = enabledNames.Contains(m_values[i].Value);
}
}
base.OnValueUpdated();
}
public override void RefreshUIForValue()
@ -48,6 +46,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
GetDefaultLabel();
m_baseLabel.text = DefaultLabel;
base.RefreshUIForValue();
if (m_subContentConstructed)
{
for (int i = 0; i < m_values.Length; i++)
@ -94,35 +94,18 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, "InteractiveFlagsContent", false, true, true, true, 5,
new Vector4(3,3,3,3), new Color(1, 1, 1, 0));
// 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);
var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromToggles, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(apply.gameObject, minWidth: 50, minHeight: 25);
// toggles
for (int i = 0; i < m_values.Length; i++)
{
AddToggle(i, groupObj);
}
}
}
@ -130,11 +113,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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;
var toggleObj = UIFactory.CreateToggle(groupObj, "FlagToggle", out Toggle toggle, out Text text, new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(toggleObj, minWidth: 100, flexibleWidth: 2000, minHeight: 25);
m_toggles[index] = toggle;

View File

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
namespace UnityExplorer.UI.InteractiveValues
{
// Class for supporting any "float struct" (ie Vector, Rect, etc).
// Supports any struct where all the public instance fields are floats (or types assignable to float)
public class StructInfo
{
public string[] FieldNames { get; }
private readonly FieldInfo[] m_fields;
public StructInfo(Type type)
{
m_fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Where(it => !it.IsLiteral)
.ToArray();
FieldNames = m_fields.Select(it => it.Name)
.ToArray();
}
public object SetValue(ref object instance, int fieldIndex, float val)
{
m_fields[fieldIndex].SetValue(instance, val);
return instance;
}
public float GetValue(object instance, int fieldIndex)
=> (float)m_fields[fieldIndex].GetValue(instance);
public void RefreshUI(InputField[] inputs, object instance)
{
try
{
for (int i = 0; i < m_fields.Length; i++)
{
var field = m_fields[i];
float val = (float)field.GetValue(instance);
inputs[i].text = val.ToString();
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
}
public class InteractiveFloatStruct : InteractiveValue
{
private static readonly Dictionary<string, bool> _typeSupportCache = new Dictionary<string, bool>();
public static bool IsTypeSupported(Type type)
{
if (!type.IsValueType)
return false;
if (string.IsNullOrEmpty(type.AssemblyQualifiedName))
return false;
if (_typeSupportCache.TryGetValue(type.AssemblyQualifiedName, out bool ret))
return ret;
ret = true;
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var field in fields)
{
if (field.IsLiteral)
continue;
if (!typeof(float).IsAssignableFrom(field.FieldType))
{
ret = false;
break;
}
}
_typeSupportCache.Add(type.AssemblyQualifiedName, ret);
return ret;
}
//~~~~~~~~~ Instance ~~~~~~~~~~
public InteractiveFloatStruct(object value, Type valueType) : base(value, valueType) { }
public override bool HasSubContent => true;
public override bool SubContentWanted => true;
public StructInfo 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 && m_subContentConstructed)
DestroySubContent();
m_lastStructType = type;
StructInfo = new StructInfo(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, "EditorContent", false, true, true, true, 2, new Vector4(4, 4, 4, 4),
new Color(0.08f, 0.08f, 0.08f));
m_inputs = new InputField[StructInfo.FieldNames.Length];
for (int i = 0; i < StructInfo.FieldNames.Length; i++)
AddEditorRow(i, editorContainer);
RefreshUIForValue();
}
internal void AddEditorRow(int index, GameObject groupObj)
{
try
{
var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow", false, true, true, true, 5, default, new Color(1, 1, 1, 0));
string name = StructInfo.FieldNames[index];
var label = UIFactory.CreateLabel(row, "RowLabel", $"{name}:", TextAnchor.MiddleRight, Color.cyan);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25);
var inputFieldObj = UIFactory.CreateInputField(row, "InputField", "...", 14, 3, 1);
UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0);
var inputField = inputFieldObj.GetComponent<InputField>();
m_inputs[index] = inputField;
inputField.onValueChanged.AddListener((string val) =>
{
try
{
float f = float.Parse(val);
Value = StructInfo.SetValue(ref this.Value, index, f);
Owner.SetValue();
}
catch { }
});
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
#endregion
}
}

View File

@ -9,8 +9,9 @@ using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.Core;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.CacheObject;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveNumber : InteractiveValue
{
@ -101,27 +102,16 @@ namespace UnityExplorer.Core.Inspectors.Reflection
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;
var inputObj = UIFactory.CreateInputField(m_mainContent, "InteractiveNumberInput", "...");
UIFactory.SetLayoutElement(inputObj, minWidth: 120, minHeight: 25, 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";
m_applyBtn = UIFactory.CreateButton(m_mainContent, "ApplyButton", "Apply", OnApplyClicked, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(m_applyBtn.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0);
}
}
}

View File

@ -8,8 +8,10 @@ using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveString : InteractiveValue
{
@ -22,6 +24,9 @@ namespace UnityExplorer.Core.Inspectors.Reflection
public override void OnValueUpdated()
{
if (!(Value is string) && Value != null)
Value = RuntimeProvider.Instance.Reflection.UnboxString(Value);
base.OnValueUpdated();
}
@ -87,10 +92,18 @@ namespace UnityExplorer.Core.Inspectors.Reflection
m_labelLayout.flexibleWidth = 0;
}
internal void OnApplyClicked()
internal void SetValueFromInput()
{
Value = m_valueInput.text;
if (!typeof(string).IsAssignableFrom(Owner.FallbackType))
ReflectionProvider.Instance.BoxStringToType(ref Value, Owner.FallbackType);
Owner.SetValue();
// revert back to string now
OnValueUpdated();
RefreshUIForValue();
}
@ -114,32 +127,23 @@ namespace UnityExplorer.Core.Inspectors.Reflection
m_labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
var readonlyInputObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
m_readonlyInput = readonlyInputObj.GetComponent<Text>();
m_readonlyInput = UIFactory.CreateLabel(m_mainContent, "ReadonlyLabel", "", TextAnchor.MiddleLeft);
m_readonlyInput.horizontalOverflow = HorizontalWrapMode.Overflow;
var testFitter = readonlyInputObj.AddComponent<ContentSizeFitter>();
var testFitter = m_readonlyInput.gameObject.AddComponent<ContentSizeFitter>();
testFitter.verticalFit = ContentSizeFitter.FitMode.MinSize;
var labelLayout = readonlyInputObj.AddComponent<LayoutElement>();
labelLayout.minHeight = 25;
labelLayout.preferredHeight = 25;
labelLayout.flexibleHeight = 0;
UIFactory.SetLayoutElement(m_readonlyInput.gameObject, minHeight: 25, preferredHeight: 25, flexibleHeight: 0);
}
public override void ConstructSubcontent()
{
base.ConstructSubcontent();
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(1, 1, 1, 0));
var group = groupObj.GetComponent<VerticalLayoutGroup>();
group.spacing = 4;
group.padding.top = 3;
group.padding.left = 3;
group.padding.right = 3;
group.padding.bottom = 3;
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, "SubContent", false, false, true, true, 4, new Vector4(3,3,3,3),
new Color(1, 1, 1, 0));
m_hiddenObj = UIFactory.CreateLabel(groupObj, TextAnchor.MiddleLeft);
m_hiddenObj = UIFactory.CreateLabel(groupObj, "HiddenLabel", "", TextAnchor.MiddleLeft).gameObject;
m_hiddenObj.SetActive(false);
var hiddenText = m_hiddenObj.GetComponent<Text>();
hiddenText.color = Color.clear;
@ -148,23 +152,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
hiddenText.supportRichText = 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;
UIFactory.SetLayoutElement(m_hiddenObj, minHeight: 25, flexibleHeight: 500, minWidth: 250, flexibleWidth: 9000);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(m_hiddenObj, true, true, true, 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;
var inputObj = UIFactory.CreateInputField(m_hiddenObj, "StringInputField", "...", 14, 3);
UIFactory.SetLayoutElement(inputObj, minWidth: 120, minHeight: 25, flexibleWidth: 5000, flexibleHeight: 5000);
m_valueInput = inputObj.GetComponent<InputField>();
m_valueInput.lineType = InputField.LineType.MultiLineNewline;
@ -182,17 +174,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
if (Owner.CanWrite)
{
var applyBtnObj = UIFactory.CreateButton(groupObj, 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";
var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromInput, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(apply.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0);
}
else
{

View File

@ -10,8 +10,11 @@ using UnityExplorer.Core.Unity;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.Main.Home;
using UnityExplorer.UI.Inspectors;
namespace UnityExplorer.Core.Inspectors.Reflection
namespace UnityExplorer.UI.InteractiveValues
{
public class InteractiveValue
{
@ -28,8 +31,8 @@ namespace UnityExplorer.Core.Inspectors.Reflection
// arbitrarily check some types, fastest methods first.
if (type == typeof(bool))
return typeof(InteractiveBool);
// if type is primitive then it must be a number if its not a bool
else if (type.IsPrimitive)
// if type is primitive then it must be a number if its not a bool. Also check for decimal.
else if (type.IsPrimitive || type == typeof(decimal))
return typeof(InteractiveNumber);
// check for strings
else if (type == typeof(string))
@ -44,8 +47,10 @@ namespace UnityExplorer.Core.Inspectors.Reflection
return typeof(InteractiveEnum);
}
// check for unity struct types
else if (InteractiveUnityStruct.SupportsType(type))
return typeof(InteractiveUnityStruct);
else if (typeof(Color).IsAssignableFrom(type))
return typeof(InteractiveColor);
else if (InteractiveFloatStruct.IsTypeSupported(type))
return typeof(InteractiveFloatStruct);
// check Transform, force InteractiveValue so they dont become InteractiveEnumerables.
else if (typeof(Transform).IsAssignableFrom(type))
return typeof(InteractiveValue);
@ -62,7 +67,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
public static InteractiveValue Create(object value, Type fallbackType)
{
var type = ReflectionUtility.GetType(value) ?? fallbackType;
var type = ReflectionUtility.GetActualType(value) ?? fallbackType;
var iType = GetIValueForType(type);
return (InteractiveValue)Activator.CreateInstance(iType, new object[] { value, type });
@ -93,11 +98,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection
public virtual void OnDestroy()
{
if (this.m_valueContent)
if (this.m_mainContent)
{
m_valueContent.transform.SetParent(null, false);
m_valueContent.SetActive(false);
GameObject.Destroy(this.m_valueContent.gameObject);
m_mainContent.transform.SetParent(null, false);
m_mainContent.SetActive(false);
GameObject.Destroy(this.m_mainContent.gameObject);
}
DestroySubContent();
@ -291,7 +296,7 @@ namespace UnityExplorer.Core.Inspectors.Reflection
internal GameObject m_mainContentParent;
internal GameObject m_subContentParent;
internal GameObject m_valueContent;
internal GameObject m_mainContent;
internal GameObject m_inspectButton;
internal Text m_baseLabel;
@ -302,69 +307,42 @@ namespace UnityExplorer.Core.Inspectors.Reflection
{
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;
m_mainContent = UIFactory.CreateHorizontalGroup(parent, $"InteractiveValue_{this.GetType().Name}", false, false, true, true, 4, default,
new Color(1, 1, 1, 0), TextAnchor.UpperLeft);
// subcontent expand button TODO
var mainRect = m_mainContent.GetComponent<RectTransform>();
mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
UIFactory.SetLayoutElement(m_mainContent, flexibleWidth: 9000, minWidth: 175, minHeight: 25, flexibleHeight: 0);
// subcontent expand button
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();
});
m_subExpandBtn = UIFactory.CreateButton(m_mainContent, "ExpandSubcontentButton", "▲", ToggleSubcontent, new Color(0.3f, 0.3f, 0.3f));
UIFactory.SetLayoutElement(m_subExpandBtn.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0, flexibleHeight: 0);
}
// 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>();
var inspectBtn = UIFactory.CreateButton(m_mainContent,
"InspectButton",
"Inspect",
() =>
{
if (!Value.IsNullOrDestroyed(false))
InspectorManager.Instance.Inspect(this.Value, this.Owner);
},
new Color(0.3f, 0.3f, 0.3f, 0.2f));
inspectBtn.onClick.AddListener(OnInspectClicked);
void OnInspectClicked()
{
if (!Value.IsNullOrDestroyed(false))
InspectorManager.Instance.Inspect(this.Value, this.Owner);
}
m_inspectButton = inspectBtn.gameObject;
UIFactory.SetLayoutElement(m_inspectButton, minWidth: 60, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
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_baseLabel = UIFactory.CreateLabel(m_mainContent, "ValueLabel", "", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(m_baseLabel.gameObject, flexibleWidth: 9000, minHeight: 25);
m_subContentParent = subGroup;
}

View File

@ -7,9 +7,19 @@ using UnityEngine.UI;
namespace UnityExplorer.UI.Main
{
public enum MenuPages
{
Home,
Search,
CSConsole,
Options
}
public abstract class BaseMenuPage
{
public abstract string Name { get; }
public abstract MenuPages Type { get; }
public bool WasDisabled { get; internal set; }
public GameObject Content;
public Button RefNavbarButton { get; set; }
@ -20,7 +30,6 @@ namespace UnityExplorer.UI.Main
set => Content?.SetActive(true);
}
public abstract bool Init();
public abstract void Update();
}

View File

@ -5,6 +5,8 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core.CSharp;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
using UnityExplorer.UI.Main;
@ -19,7 +21,6 @@ namespace UnityExplorer.UI.Main.CSConsole
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>();
@ -42,16 +43,12 @@ namespace UnityExplorer.UI.Main.CSConsole
public static void Update()
{
if (!m_mainObj)
{
return;
}
if (!CSharpConsole.EnableAutocompletes)
{
if (m_mainObj.activeSelf)
{
m_mainObj.SetActive(false);
}
return;
}
@ -137,7 +134,7 @@ namespace UnityExplorer.UI.Main.CSConsole
if (!editor.InputField.isFocused)
return;
var textGen = editor.InputText.cachedTextGenerator;
int caretPos = editor.m_lastCaretPos;
@ -256,7 +253,7 @@ namespace UnityExplorer.UI.Main.CSConsole
{
var parent = UIManager.CanvasRoot;
var obj = UIFactory.CreateScrollView(parent, out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
var obj = UIFactory.CreateScrollView(parent, "AutoCompleterScrollView", out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
m_mainObj = obj;
@ -269,43 +266,33 @@ namespace UnityExplorer.UI.Main.CSConsole
mainRect.offsetMax = Vector2.zero;
var mainGroup = content.GetComponent<VerticalLayoutGroup>();
mainGroup.childControlHeight = false;
mainGroup.childControlWidth = true;
mainGroup.SetChildControlHeight(false);
mainGroup.SetChildControlWidth(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 btn = UIFactory.CreateButton(content, "AutoCompleteButton", "", null);
RuntimeProvider.Instance.SetColorBlock(btn, new Color(0, 0, 0, 0), highlighted: new Color(0.2f, 0.2f, 0.2f, 1.0f));
var nav = btn.navigation;
nav.mode = Navigation.Mode.Vertical;
btn.navigation = nav;
var btnLayout = buttonObj.AddComponent<LayoutElement>();
btnLayout.minHeight = 20;
UIFactory.SetLayoutElement(btn.gameObject, minHeight: 20);
var text = btn.GetComponentInChildren<Text>();
text.alignment = TextAnchor.MiddleLeft;
text.color = Color.white;
var hiddenChild = UIFactory.CreateUIObject("HiddenText", buttonObj);
var hiddenChild = UIFactory.CreateUIObject("HiddenText", btn.gameObject);
hiddenChild.SetActive(false);
var hiddenText = hiddenChild.AddComponent<Text>();
m_hiddenSuggestionTexts.Add(hiddenText);
btn.onClick.AddListener(UseAutocompleteButton);
btn.onClick.AddListener(() => { CSharpConsole.Instance.UseAutocomplete(hiddenText.text); });
void UseAutocompleteButton()
{
CSharpConsole.Instance.UseAutocomplete(hiddenText.text);
}
m_suggestionButtons.Add(buttonObj);
m_suggestionButtons.Add(btn.gameObject);
m_suggestionTexts.Add(text);
}
}

View File

@ -40,24 +40,12 @@ namespace UnityExplorer.UI.Main.CSConsole
{
'[', ']', '(', ')', '{', '}', ';', ':', ',', '.'
};
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",
"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"}
};
public static KeywordMatch validKeywordMatcher = new KeywordMatch();
// ~~~~~~~ ctor ~~~~~~~

View File

@ -9,18 +9,17 @@ using UnityExplorer.Core.Input;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.UI.Reusable;
using UnityExplorer.UI.Main.CSConsole;
using UnityExplorer.Core;
#if CPP
using UnityExplorer.Core.Runtime.Il2Cpp;
#endif
using UnityExplorer.Core.Unity;
using UnityExplorer.UI.Utility;
namespace UnityExplorer.UI.Main.CSConsole
{
public class CSharpConsole : BaseMenuPage
{
public override string Name => "C# Console";
public override MenuPages Type => MenuPages.CSConsole;
public static CSharpConsole Instance { get; private set; }
@ -52,16 +51,10 @@ namespace UnityExplorer.UI.Main.CSConsole
InitConsole();
AutoCompleter.Init();
#if MONO
DummyBehaviour.Setup();
#endif
ResetConsole();
ResetConsole(false);
// Make sure compiler is supported on this platform
Evaluator.Compile("");
foreach (string use in DefaultUsing)
AddUsing(use);
Evaluator.Compile("new object();");
return true;
}
@ -75,11 +68,13 @@ namespace UnityExplorer.UI.Main.CSConsole
ExplorerCore.LogWarning(info);
this.RefNavbarButton.GetComponentInChildren<Text>().text += " (disabled)";
return false;
}
}
public void ResetConsole()
public void ResetConsole(bool log = true)
{
if (Evaluator != null)
Evaluator.Dispose();
@ -89,6 +84,12 @@ namespace UnityExplorer.UI.Main.CSConsole
Evaluator = new ScriptEvaluator(new StringWriter(m_evalLogBuilder)) { InteractiveBaseClass = typeof(ScriptInteraction) };
UsingDirectives = new List<string>();
foreach (string use in DefaultUsing)
AddUsing(use);
if (log)
ExplorerCore.Log($"C# Console reset. Using directives:\r\n{Evaluator.GetUsing()}");
}
public override void Update()
@ -96,10 +97,6 @@ namespace UnityExplorer.UI.Main.CSConsole
UpdateConsole();
AutoCompleter.Update();
#if CPP
Il2CppCoroutine.Process();
#endif
}
public void AddUsing(string asm)
@ -202,23 +199,10 @@ The following helper methods are available:
InputField.onValueChanged.AddListener((string s) => { OnInputChanged(s); });
}
internal static bool IsUserCopyPasting()
{
return (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
&& InputManager.GetKeyDown(KeyCode.V);
}
public void UpdateConsole()
{
if (s_copyPasteBuffer != null)
{
if (!IsUserCopyPasting())
{
OnInputChanged(s_copyPasteBuffer);
s_copyPasteBuffer = null;
}
}
if (Time.time > s_timeOfLastInternalSet)
Writing = false;
if (EnableCtrlRShortcut)
{
@ -277,6 +261,8 @@ The following helper methods are available:
public void UseAutocomplete(string suggestion)
{
Writing = true;
string input = InputField.text;
input = input.Insert(m_lastCaretPos, suggestion);
InputField.text = input;
@ -291,20 +277,32 @@ The following helper methods are available:
AutoCompleter.ClearAutocompletes();
}
internal static string s_copyPasteBuffer;
private static float s_timeOfLastUpdate;
private static bool Writing
{
get => s_writing;
set
{
if (value)
s_timeOfLastInternalSet = Time.time;
s_writing = value;
}
}
private static bool s_writing;
private static float s_timeOfLastInternalSet;
public void OnInputChanged(string newText, bool forceUpdate = false)
{
if (IsUserCopyPasting())
{
//Console.WriteLine("Copy+Paste detected!");
s_copyPasteBuffer = newText;
if (!Writing && Time.time <= s_timeOfLastUpdate)
return;
}
s_timeOfLastUpdate = Time.time;
if (EnableAutoIndent)
UpdateIndent(newText);
Writing = true;
if (!forceUpdate && string.IsNullOrEmpty(newText))
inputHighlightText.text = string.Empty;
else
@ -358,7 +356,7 @@ The following helper methods are available:
string ret = "";
foreach (LexerMatchInfo match in highlightLexer.GetMatches(inputText))
foreach (var match in highlightLexer.GetMatches(inputText))
{
for (int i = offset; i < match.startIndex; i++)
ret += inputText[i];
@ -381,6 +379,8 @@ The following helper methods are available:
private void AutoIndentCaret()
{
Writing = true;
if (CurrentIndent > 0)
{
string indent = GetAutoIndentTab(CurrentIndent);
@ -453,101 +453,62 @@ The following helper methods are available:
public void ConstructUI()
{
Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport);
Content = UIFactory.CreateVerticalGroup(MainMenu.Instance.PageViewport, "CSharpConsole", true, true, true, true);
UIFactory.SetLayoutElement(Content, preferredHeight: 500, flexibleHeight: 9000);
var mainLayout = Content.AddComponent<LayoutElement>();
mainLayout.preferredHeight = 500;
mainLayout.flexibleHeight = 9000;
var mainGroup = Content.AddComponent<VerticalLayoutGroup>();
mainGroup.childControlHeight = true;
mainGroup.childControlWidth = true;
mainGroup.childForceExpandHeight = true;
mainGroup.childForceExpandWidth = true;
#region TOP BAR
#region TOP BAR
// Main group object
var topBarObj = UIFactory.CreateHorizontalGroup(Content);
LayoutElement topBarLayout = topBarObj.AddComponent<LayoutElement>();
topBarLayout.minHeight = 50;
topBarLayout.flexibleHeight = 0;
var topBarObj = UIFactory.CreateHorizontalGroup(Content, "TopBar", true, true, true, true, 10, new Vector4(8, 8, 30, 30),
default, TextAnchor.LowerCenter);
UIFactory.SetLayoutElement(topBarObj, minHeight: 50, 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;
// Top label
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;
var topBarLabel = UIFactory.CreateLabel(topBarObj, "TopLabel", "C# Console", TextAnchor.MiddleLeft, default, true, 25);
UIFactory.SetLayoutElement(topBarLabel.gameObject, preferredWidth: 150, flexibleWidth: 5000);
// 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;
}
var ctrlRToggleObj = UIFactory.CreateToggle(topBarObj, "CtrlRToggle", out Toggle ctrlRToggle, out Text ctrlRToggleText);
ctrlRToggle.onValueChanged.AddListener((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;
UIFactory.SetLayoutElement(ctrlRToggleObj, minWidth: 140, flexibleWidth: 0, minHeight: 25);
// Enable Suggestions toggle
var suggestToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle suggestToggle, out Text suggestToggleText);
suggestToggle.onValueChanged.AddListener(SuggestToggleCallback);
void SuggestToggleCallback(bool val)
var suggestToggleObj = UIFactory.CreateToggle(topBarObj, "SuggestionToggle", out Toggle suggestToggle, out Text suggestToggleText);
suggestToggle.onValueChanged.AddListener((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;
UIFactory.SetLayoutElement(suggestToggleObj, minWidth: 120, flexibleWidth: 0, 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;
var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, "IndentToggle", out Toggle autoIndentToggle, out Text autoIndentToggleText);
autoIndentToggle.onValueChanged.AddListener((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;
UIFactory.SetLayoutElement(autoIndentToggleObj, minWidth: 180, flexibleWidth: 0, minHeight: 25);
#endregion
#endregion
#region CONSOLE INPUT
#region CONSOLE INPUT
int fontSize = 16;
var inputObj = UIFactory.CreateSrollInputField(Content, out InputFieldScroller consoleScroll, fontSize);
var inputObj = UIFactory.CreateSrollInputField(Content, "ConsoleInput", STARTUP_TEXT, out InputFieldScroller consoleScroll, fontSize);
var inputField = consoleScroll.inputField;
@ -557,7 +518,6 @@ The following helper methods are available:
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);
@ -572,36 +532,33 @@ The following helper methods are available:
highlightTextInput.supportRichText = true;
highlightTextInput.fontSize = fontSize;
#endregion
#endregion
#region COMPILE BUTTON
#region COMPILE BUTTON BAR
var compileBtnObj = UIFactory.CreateButton(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";
var horozGroupObj = UIFactory.CreateHorizontalGroup(Content, "BigButtons", true, true, true, true, 0, new Vector4(2,2,2,2),
new Color(1, 1, 1, 0));
var resetButton = UIFactory.CreateButton(horozGroupObj, "ResetButton", "Reset", () => ResetConsole(), "666666".ToColor());
var resetBtnText = resetButton.GetComponentInChildren<Text>();
resetBtnText.fontSize = 18;
UIFactory.SetLayoutElement(resetButton.gameObject, preferredWidth: 80, flexibleWidth: 0, minHeight: 45, flexibleHeight: 0);
var compileButton = UIFactory.CreateButton(horozGroupObj, "CompileButton", "Compile", CompileCallback,
new Color(14f / 255f, 80f / 255f, 14f / 255f));
var btnText = compileButton.GetComponentInChildren<Text>();
btnText.fontSize = 18;
btnText.color = Color.white;
UIFactory.SetLayoutElement(compileButton.gameObject, preferredWidth: 80, flexibleWidth: 0, minHeight: 45, flexibleHeight: 0);
// Set compile button callback now that we have the Input Field reference
compileButton.onClick.AddListener(CompileCallback);
void CompileCallback()
{
if (!string.IsNullOrEmpty(inputField.text))
{
Evaluate(inputField.text.Trim());
}
else
ExplorerCore.Log("Cannot evaluate empty input!");
}
#endregion
#endregion
//mainTextInput.supportRichText = false;

Some files were not shown because too many files have changed in this diff Show More