Compare commits

...

80 Commits
4.7.5 ... 4.8.1

Author SHA1 Message Date
d1fbbfa62d Bump version 2022-05-15 19:12:00 +10:00
56a3cef245 Add C# Console Assembly blacklist 2022-05-15 18:16:04 +10:00
61e7915a55 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2022-05-14 01:55:13 +10:00
88e63c8d6a Fix logic after changes in UniverseLib 2022-05-14 01:55:10 +10:00
048e5980a0 Update README.md 2022-05-13 20:44:51 +10:00
3b851b6e08 Use 7zip instead of Compress-Archive 2022-05-13 17:11:25 +10:00
4c029dad90 Update TimeScaleWidget.cs 2022-05-11 22:49:28 +10:00
3d61011e59 Move Time Scale Widget into separate class
And change "pause" to "lock" behaviour. Added patch to implement locking feature.
2022-05-10 01:44:08 +10:00
5285239bc5 Update hooks list after edit save 2022-05-10 01:43:41 +10:00
57d3a3f52e Fix UI Mouse Inspect hotkey not being implemented 2022-05-10 01:02:37 +10:00
c88182c831 Bump version 2022-05-08 18:06:56 +10:00
02e0102041 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2022-05-07 05:19:46 +10:00
6adecef785 Fix generated patch code for static void methods
And always show the patch even if it failed to apply
2022-05-07 05:19:43 +10:00
ff6c03e1f3 Update README.md 2022-05-06 15:46:49 +10:00
1aedc505b2 Bump UniverseLib 2022-05-05 23:13:51 +10:00
dbe993a7c7 Fix some casts for IL2CPP 2022-05-05 22:18:19 +10:00
5a1676fb84 Update README.md 2022-05-05 19:56:29 +10:00
a7a663aefa Prevent update overwriting input field changes 2022-05-05 19:53:02 +10:00
76c77fb082 Add button to inspect scene of a GameObject 2022-05-05 19:52:25 +10:00
a25017df69 Add wider Texture2DWidget support 2022-05-05 19:52:13 +10:00
a1fab0c4a7 Add support for opening inspected Type in dnSpy 2022-05-05 19:50:52 +10:00
4599747bfe Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2022-05-05 02:45:43 +10:00
3856e84c08 Bump version 2022-05-05 02:45:41 +10:00
104288a912 Add support for Cubemaps in Texture2DWidget 2022-05-05 02:45:24 +10:00
41e8a5ae33 Update README.md 2022-05-03 06:10:22 +10:00
ef4bc75d5e Bump version 2022-04-30 20:53:31 +10:00
68b81c6b53 Bump UniverseLib 2022-04-30 20:47:53 +10:00
bd86f09313 Cleanup and refactor GameObjectControls
Split into proper classes, cleanup ugly code
2022-04-30 20:32:31 +10:00
b35d6f5787 Cleanup 2022-04-30 20:31:33 +10:00
b97eada516 Update README.md 2022-04-29 16:56:29 +10:00
0a05848bef Implement control modifier to Home/End jump 2022-04-28 04:10:36 +10:00
6e5610129a Add ILRepack back to build process 2022-04-27 20:13:17 +10:00
acd30880a8 Bump UniverseLib 2022-04-27 03:04:28 +10:00
f54ff89290 Bump UniverseLib 2022-04-25 22:52:24 +10:00
62d565777d Bump UniverseLib and version 2022-04-25 19:39:44 +10:00
870f82ab26 Fix Home jump when line is entirely whitespace 2022-04-25 05:48:13 +10:00
9370c5e0e6 Fix jumpToTop and panel position behaviour 2022-04-25 05:47:57 +10:00
b8cf96438c Correct a comment position 2022-04-24 19:38:17 +10:00
7be7daf4d7 Bump version 2022-04-24 19:24:43 +10:00
8f54415ae0 Remove small logic error with SymbolLexer 2022-04-24 19:23:05 +10:00
aef4e11c01 C# Console: Implement Home and End keys 2022-04-24 19:13:43 +10:00
af7e32ec49 Add a null check 2022-04-24 18:16:13 +10:00
6f44a3376b Bump UniverseLib 2022-04-24 05:58:36 +10:00
3a6b573ac3 Bump version 2022-04-24 05:57:29 +10:00
cbe17927fb Ensure valid panel size after loading save data 2022-04-24 05:57:22 +10:00
15f3f37948 Clean up all objects in editor 2022-04-24 05:56:47 +10:00
de6760e427 Add more convenient editor config serialization 2022-04-24 05:56:36 +10:00
83edd1b9bb Bump editor package 2022-04-24 02:12:57 +10:00
613be34e95 Bump UniverseLib 2022-04-24 02:04:08 +10:00
58c65b9b8b Make TypeCompleter asynchronous 2022-04-24 02:02:34 +10:00
6fcf6a521c Bump version 2022-04-24 02:02:29 +10:00
81a174f865 Remove call to obsolete methods 2022-04-24 01:59:53 +10:00
b5e3cc2ea5 Use separate TypeCompleters for different contexts 2022-04-24 01:59:03 +10:00
14f46ade6a Cancel pending generic when edit button pressed 2022-04-24 01:58:27 +10:00
e92556805b Update BepInExConfigHandler.cs 2022-04-23 01:05:51 +10:00
8662742461 Bump version 2022-04-23 00:19:31 +10:00
63393a9d66 Update README.md 2022-04-22 22:31:36 +10:00
13986f95c1 Update UnityExplorer.STANDALONE.Mono.dll 2022-04-22 22:27:48 +10:00
b47bfa1e83 Remove Mono restriction on generic type eligibility 2022-04-22 22:27:43 +10:00
12c51248fe Update Editor package 2022-04-22 22:20:01 +10:00
9b9cb54a79 Fix a typo 2022-04-22 22:19:43 +10:00
6a28a93e3a Update for obsolete method 2022-04-22 22:11:24 +10:00
32e718faeb Bump UniverseLib 2022-04-22 21:31:30 +10:00
4d46b74d54 Fix references for rename 2022-04-22 21:04:50 +10:00
7e5246cead Recache types when borrowing 2022-04-22 21:04:23 +10:00
abf5267364 Fix HookCreator method filtering 2022-04-22 21:03:51 +10:00
3afee7254c Fix results TypeCompleter issues 2022-04-22 21:03:33 +10:00
1643d4b7dd Allow generic class construction for unbound types 2022-04-22 21:03:11 +10:00
cef8c12d20 Fix TryFocusActiveInspector for classes 2022-04-22 21:02:45 +10:00
5e07847356 Make current hooks view smaller in height 2022-04-22 21:02:08 +10:00
2dc6e386df Fix generated patch code for ref and subclasses 2022-04-22 21:01:47 +10:00
ff882296fd Fix for GenericConstructorWidget, adjust UI 2022-04-22 21:01:09 +10:00
ecc33927ee Make GenericConstructorWidget reusable 2022-04-22 21:00:18 +10:00
6e91f2a792 Hooks: Add support for generic classes and methods 2022-04-22 09:15:51 +10:00
97cb14d6fc HookInstance: Clean up generated patch code 2022-04-22 09:12:38 +10:00
8b861f7c77 Log Panel: Remove a todo 2022-04-22 09:12:22 +10:00
9379e0f813 Fix constraints on AddComponent TypeCompleter 2022-04-22 09:08:49 +10:00
bdda12a040 Remove redundant reference to EvaluateWidget 2022-04-22 09:08:17 +10:00
75bd654a94 TypeCompleter: Allow generics, support shorthand names 2022-04-22 09:07:51 +10:00
f174c7543a C# Console: Fix autocomplete caret deselection 2022-04-22 09:06:52 +10:00
61 changed files with 3021 additions and 1803 deletions

View File

@ -18,6 +18,12 @@
⚡ Thunderstore releases: [BepInEx Mono](https://thunderstore.io/package/sinai-dev/UnityExplorer) | [BepInEx IL2CPP](https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP) | [MelonLoader IL2CPP](https://boneworks.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP_ML)
## Release schedule
Releases will be posted at most once per week, generally on weekends.
Nightly builds can be found [here](https://github.com/sinai-dev/UnityExplorer/actions).
## BepInEx
| Release | IL2CPP | Mono |
@ -32,10 +38,10 @@
## MelonLoader
| Release | IL2CPP | Mono |
| ---------- | ------ | ---- |
| ML 0.4/0.5 | ✅ [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) |
| ML 0.6 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.IL2CPP.net6preview.zip) | ✖️ |
| Release | IL2CPP | Mono |
| ------- | ------ | ---- |
| ML 0.5 | ✅ [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) |
| ML 0.6 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.IL2CPP.net6preview.zip) | ✖️ |
1. Unzip the release file into a folder
2. Copy the DLL inside the `Mods` folder into your MelonLoader `Mods` folder
@ -84,6 +90,20 @@ If these fixes do not work, please create an issue in this repo and I'll do my b
</a>
</p>
### Inspector API
If you want to inspect an object or Type from outside the C# console, use the `InspectorManager` class:
**To inspect an object:**
```csharp
UnityExplorer.InspectorManager.Inspect(theObject);
```
**To inspect a Type:**
```cs
UnityExplorer.InspectorManager.Inspect(typeof(SomeClass));
```
### Object Explorer
* Use the <b>Scene Explorer</b> tab to traverse the active scenes, as well as the DontDestroyOnLoad and HideAndDontSave objects.
@ -104,7 +124,8 @@ The inspector is used to see detailed information on objects of any type and man
* Automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
* Press the `▼` button to expand certain values such as strings, enums, lists, dictionaries, some structs, etc
* Use the filters at the top to quickly find the members you are looking for
* For `Texture2D` objects, there is a `View Texture` button at the top of the inspector which lets you view it and save it as a PNG file. Currently there are no other similar helpers yet, but I may add more at some point for Mesh, Sprite, Material, etc
* For `Texture2D`, `Image`, `Sprite` and `Material` objects, there is a `View Texture` button at the top of the inspector which lets you view the Texture(s) and save them as a PNG file.
* For `AudioClip` objects there is a `Show Player` button which opens an audio player widget. For clips which are loaded as `DecompressOnLoad`, there is also a button to save them to a `.wav` file.
### C# Console
@ -115,7 +136,7 @@ The inspector is used to see detailed information on objects of any type and man
### Hook Manager
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
* Simply enter any class and hook the methods you want from the menu.
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
### Mouse-Inspect
@ -124,6 +145,13 @@ The inspector is used to see detailed information on objects of any type and man
* <b>World</b>: uses Physics.Raycast to look for Colliders
* <b>UI</b>: uses GraphicRaycasters to find UI objects
### Freecam
* UnityExplorer provides a basic Free Camera which you can control with your keyboard and mouse.
* Unlike all other features of UnityExplorer, you can still use Freecam while UnityExplorer's menu is hidden.
* Supports using the game's main Camera or a separate custom Camera.
* See the Freecam panel for further instructions and details.
### Clipboard
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`)

View File

@ -1,6 +1,6 @@
{
"name": "com.sinai-dev.unityexplorer",
"version": "4.7.3",
"version": "4.7.12",
"displayName": "UnityExplorer",
"description": "An in-game UI for exploring, debugging and modifying Unity games.",
"unity": "2017.1",

173
build.ps1
View File

@ -1,97 +1,134 @@
# MelonLoader IL2CPP (net6)
dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net6
# (cleanup and move files)
# ----------- MelonLoader IL2CPP (net6) -----------
dotnet build src/UnityExplorer.sln -c Release_ML_Cpp_net6
$Path = "Release\UnityExplorer.MelonLoader.IL2CPP.net6preview"
Remove-Item $Path\UnityExplorer.ML.IL2CPP.net6preview.deps.json
Remove-Item $Path\Tomlet.dll
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net6 /lib:lib/unhollowed /lib:$Path /internalize /out:$Path/UnityExplorer.ML.IL2CPP.net6preview.dll $Path/UnityExplorer.ML.IL2CPP.net6preview.dll $Path/mcs.dll
# (cleanup and move files)
Remove-Item $Path/UnityExplorer.ML.IL2CPP.net6preview.deps.json
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
Remove-Item $Path/Iced.dll
Remove-Item $Path/UnhollowerBaseLib.dll
New-Item -Path "$Path" -Name "Mods" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.ML.IL2CPP.net6preview.dll -Destination $Path\Mods -Force
Move-Item -Path $Path/UnityExplorer.ML.IL2CPP.net6preview.dll -Destination $Path/Mods -Force
New-Item -Path "$Path" -Name "UserLibs" -ItemType "directory" -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path\UniverseLib.IL2CPP.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path/UniverseLib.IL2CPP.dll -Destination $Path/UserLibs -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.net6preview.zip -Force
Remove-Item $Path/../UnityExplorer.MelonLoader.IL2CPP.net6preview.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.MelonLoader.IL2CPP.net6preview.zip .\$Path\*
# MelonLoader IL2CPP (net472)
dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net472
# ----------- MelonLoader IL2CPP (net472) -----------
dotnet build src/UnityExplorer.sln -c Release_ML_Cpp_net472
$Path = "Release/UnityExplorer.MelonLoader.IL2CPP"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net472 /lib:lib/net35 /lib:lib/unhollowed /lib:$Path /internalize /out:$Path/UnityExplorer.ML.IL2CPP.dll $Path/UnityExplorer.ML.IL2CPP.dll $Path/mcs.dll
# (cleanup and move files)
$Path = "Release\UnityExplorer.MelonLoader.IL2CPP"
Remove-Item $Path\Tomlet.dll
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
Remove-Item $Path/Iced.dll
Remove-Item $Path/UnhollowerBaseLib.dll
New-Item -Path "$Path" -Name "Mods" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.ML.IL2CPP.dll -Destination $Path\Mods -Force
Move-Item -Path $Path/UnityExplorer.ML.IL2CPP.dll -Destination $Path/Mods -Force
New-Item -Path "$Path" -Name "UserLibs" -ItemType "directory" -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path\UniverseLib.IL2CPP.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path/UniverseLib.IL2CPP.dll -Destination $Path/UserLibs -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.zip -Force
Remove-Item $Path/../UnityExplorer.MelonLoader.IL2CPP.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.MelonLoader.IL2CPP.zip .\$Path\*
# MelonLoader Mono
dotnet build src\UnityExplorer.sln -c Release_ML_Mono
# ----------- MelonLoader Mono -----------
dotnet build src/UnityExplorer.sln -c Release_ML_Mono
$Path = "Release/UnityExplorer.MelonLoader.Mono"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net35 /lib:$Path /internalize /out:$Path/UnityExplorer.ML.Mono.dll $Path/UnityExplorer.ML.Mono.dll $Path/mcs.dll
# (cleanup and move files)
$Path = "Release\UnityExplorer.MelonLoader.Mono"
Remove-Item $Path\Tomlet.dll
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
New-Item -Path "$Path" -Name "Mods" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.ML.Mono.dll -Destination $Path\Mods -Force
Move-Item -Path $Path/UnityExplorer.ML.Mono.dll -Destination $Path/Mods -Force
New-Item -Path "$Path" -Name "UserLibs" -ItemType "directory" -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path\UniverseLib.Mono.dll -Destination $Path\UserLibs -Force
Move-Item -Path $Path/UniverseLib.Mono.dll -Destination $Path/UserLibs -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.Mono.zip -Force
Remove-Item $Path/../UnityExplorer.MelonLoader.Mono.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.MelonLoader.Mono.zip .\$Path\*
# BepInEx IL2CPP
dotnet build src\UnityExplorer.sln -c Release_BIE_Cpp
# ----------- BepInEx IL2CPP -----------
dotnet build src/UnityExplorer.sln -c Release_BIE_Cpp
$Path = "Release/UnityExplorer.BepInEx.IL2CPP"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net472 /lib:lib/unhollowed /lib:$Path /internalize /out:$Path/UnityExplorer.BIE.IL2CPP.dll $Path/UnityExplorer.BIE.IL2CPP.dll $Path/mcs.dll $Path/Tomlet.dll
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx.IL2CPP"
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
Remove-Item $Path/Iced.dll
Remove-Item $Path/UnhollowerBaseLib.dll
New-Item -Path "$Path" -Name "plugins" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.BIE.IL2CPP.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\Tomlet.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\UniverseLib.IL2CPP.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
New-Item -Path "$Path" -Name "plugins/sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path/UnityExplorer.BIE.IL2CPP.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
Move-Item -Path $Path/UniverseLib.IL2CPP.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx.IL2CPP.zip -Force
Remove-Item $Path/../UnityExplorer.BepInEx.IL2CPP.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.BepInEx.IL2CPP.zip .\$Path\*
# BepInEx 5 Mono
dotnet build src\UnityExplorer.sln -c Release_BIE5_Mono
# ----------- BepInEx 5 Mono -----------
dotnet build src/UnityExplorer.sln -c Release_BIE5_Mono
$Path = "Release/UnityExplorer.BepInEx5.Mono"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net35 /lib:$Path /internalize /out:$Path/UnityExplorer.BIE5.Mono.dll $Path/UnityExplorer.BIE5.Mono.dll $Path/mcs.dll $Path/Tomlet.dll
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx5.Mono"
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
New-Item -Path "$Path" -Name "plugins" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.BIE5.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\Tomlet.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\UniverseLib.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
New-Item -Path "$Path" -Name "plugins/sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path/UnityExplorer.BIE5.Mono.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
Move-Item -Path $Path/UniverseLib.Mono.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx5.Mono.zip -Force
Remove-Item $Path/../UnityExplorer.BepInEx5.Mono.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.BepInEx5.Mono.zip .\$Path\*
# BepInEx 6 Mono
dotnet build src\UnityExplorer.sln -c Release_BIE6_Mono
# ----------- BepInEx 6 Mono -----------
dotnet build src/UnityExplorer.sln -c Release_BIE6_Mono
$Path = "Release/UnityExplorer.BepInEx6.Mono"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net35 /lib:$Path /internalize /out:$Path/UnityExplorer.BIE6.Mono.dll $Path/UnityExplorer.BIE6.Mono.dll $Path/mcs.dll $Path/Tomlet.dll
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx6.Mono"
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
New-Item -Path "$Path" -Name "plugins" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path\UnityExplorer.BIE6.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\mcs.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\Tomlet.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
Move-Item -Path $Path\UniverseLib.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
New-Item -Path "$Path" -Name "plugins/sinai-dev-UnityExplorer" -ItemType "directory" -Force
Move-Item -Path $Path/UnityExplorer.BIE6.Mono.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
Move-Item -Path $Path/UniverseLib.Mono.dll -Destination $Path/plugins/sinai-dev-UnityExplorer -Force
# (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx6.Mono.zip -Force
Remove-Item $Path/../UnityExplorer.BepInEx6.Mono.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.BepInEx6.Mono.zip .\$Path\*
# Standalone Mono
dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Mono
$Path = "Release\UnityExplorer.Standalone.Mono"
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.Mono.zip -Force
# ----------- Standalone Mono -----------
dotnet build src/UnityExplorer.sln -c Release_STANDALONE_Mono
$Path = "Release/UnityExplorer.Standalone.Mono"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net35 /lib:$Path /internalize /out:$Path/UnityExplorer.Standalone.Mono.dll $Path/UnityExplorer.Standalone.Mono.dll $Path/mcs.dll $Path/Tomlet.dll
# (cleanup and move files)
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
Remove-Item $Path/../UnityExplorer.Standalone.Mono.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.Standalone.Mono.zip .\$Path\*
# Standalone IL2CPP
dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Cpp
$Path = "Release\UnityExplorer.Standalone.IL2CPP"
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.IL2CPP.zip -Force
# ----------- Standalone IL2CPP -----------
dotnet build src/UnityExplorer.sln -c Release_STANDALONE_Cpp
$Path = "Release/UnityExplorer.Standalone.IL2CPP"
# ILRepack
lib/ILRepack.exe /target:library /lib:lib/net472 /lib:lib/unhollowed /lib:$Path /internalize /out:$Path/UnityExplorer.Standalone.IL2CPP.dll $Path/UnityExplorer.Standalone.IL2CPP.dll $Path/mcs.dll $Path/Tomlet.dll
# (cleanup and move files)
Remove-Item $Path/Tomlet.dll
Remove-Item $Path/mcs.dll
Remove-Item $Path/Iced.dll
Remove-Item $Path/UnhollowerBaseLib.dll
Remove-Item $Path/../UnityExplorer.Standalone.IL2CPP.zip -ErrorAction SilentlyContinue
7z a $Path/../UnityExplorer.Standalone.IL2CPP.zip .\$Path\*
# Editor (mono)
$Path1 = "Release\UnityExplorer.Standalone.Mono"
$Path2 = "UnityEditorPackage\Runtime"
Copy-Item $Path1\UnityExplorer.STANDALONE.Mono.dll -Destination $Path2
Copy-Item $Path1\mcs.dll -Destination $Path2
Copy-Item $Path1\Tomlet.dll -Destination $Path2
Copy-Item $Path1\UniverseLib.Mono.dll -Destination $Path2
Compress-Archive -Path UnityEditorPackage\* -CompressionLevel Fastest -DestinationPath Release\UnityExplorer.Editor.zip -Force
# ----------- Editor (mono) -----------
$Path1 = "Release/UnityExplorer.Standalone.Mono"
$Path2 = "UnityEditorPackage/Runtime"
Copy-Item $Path1/UnityExplorer.STANDALONE.Mono.dll -Destination $Path2
Copy-Item $Path1/UniverseLib.Mono.dll -Destination $Path2
Remove-Item Release/UnityExplorer.Editor.zip -ErrorAction SilentlyContinue
7z a Release/UnityExplorer.Editor.zip .\UnityEditorPackage\*

BIN
lib/ILRepack.exe Normal file

Binary file not shown.

BIN
lib/net6/System.Runtime.dll Normal file

Binary file not shown.

View File

@ -23,34 +23,40 @@ namespace UnityExplorer.CSConsole
{
public static class ConsoleController
{
public static ScriptEvaluator Evaluator;
public static LexerBuilder Lexer;
public static CSAutoCompleter Completer;
private static HashSet<string> usingDirectives;
private static StringBuilder evaluatorOutput;
private static StringWriter evaluatorStringWriter;
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
public static InputFieldRef Input => Panel.Input;
public static ScriptEvaluator Evaluator { get; private set; }
public static LexerBuilder Lexer { get; private set; }
public static CSAutoCompleter Completer { get; private set; }
public static bool SRENotSupported { get; private set; }
public static int LastCaretPosition { get; private set; }
internal static float defaultInputFieldAlpha;
public static float DefaultInputFieldAlpha { get; set; }
// Todo save as config?
public static bool EnableCtrlRShortcut { get; private set; } = true;
public static bool EnableAutoIndent { get; private set; } = true;
public static bool EnableSuggestions { get; private set; } = true;
internal static string ScriptsFolder => Path.Combine(ExplorerCore.ExplorerFolder, "Scripts");
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
public static InputFieldRef Input => Panel.Input;
internal static readonly string[] DefaultUsing = new string[]
public static string ScriptsFolder => Path.Combine(ExplorerCore.ExplorerFolder, "Scripts");
static HashSet<string> usingDirectives;
static StringBuilder evaluatorOutput;
static StringWriter evaluatorStringWriter;
static float timeOfLastCtrlR;
static bool settingCaretCoroutine;
static string previousInput;
static int previousContentLength = 0;
static readonly string[] DefaultUsing = new string[]
{
"System",
"System.Linq",
"System.Text",
"System.Collections",
"System.Collections.Generic",
"System.Reflection",
"UnityEngine",
"UniverseLib",
#if CPP
@ -59,9 +65,10 @@ namespace UnityExplorer.CSConsole
#endif
};
const int CSCONSOLE_LINEHEIGHT = 18;
public static void Init()
{
// Make sure console is supported on this platform
try
{
ResetConsole(false);
@ -111,31 +118,10 @@ namespace UnityExplorer.CSConsole
}
}
#region UI Listeners and options
// TODO save
private static void OnToggleAutoIndent(bool value)
{
EnableAutoIndent = value;
}
private static void OnToggleCtrlRShortcut(bool value)
{
EnableCtrlRShortcut = value;
}
private static void OnToggleSuggestions(bool value)
{
EnableSuggestions = value;
}
#endregion
#region Evaluating
private static void GenerateTextWriter()
static void GenerateTextWriter()
{
evaluatorOutput = new StringBuilder();
evaluatorStringWriter = new StringWriter(evaluatorOutput);
@ -247,16 +233,48 @@ namespace UnityExplorer.CSConsole
#endregion
// Updating and event listeners
#region Update loop and event listeners
private static bool settingCaretCoroutine;
public static void Update()
{
if (SRENotSupported)
return;
private static void OnInputScrolled() => HighlightVisibleInput();
if (!InputManager.GetKey(KeyCode.LeftControl) && !InputManager.GetKey(KeyCode.RightControl))
{
if (InputManager.GetKeyDown(KeyCode.Home))
JumpToStartOrEndOfLine(true);
else if (InputManager.GetKeyDown(KeyCode.End))
JumpToStartOrEndOfLine(false);
}
private static string previousInput;
UpdateCaret(out bool caretMoved);
// Invoked at most once per frame
private static void OnInputChanged(string value)
if (!settingCaretCoroutine && EnableSuggestions)
{
if (AutoCompleteModal.CheckEscape(Completer))
{
OnAutocompleteEscaped();
return;
}
if (caretMoved)
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
}
if (EnableCtrlRShortcut
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
&& InputManager.GetKeyDown(KeyCode.R)
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
{
timeOfLastCtrlR = Time.realtimeSinceStartup;
Evaluate(Panel.Input.Text);
}
}
static void OnInputScrolled() => HighlightVisibleInput(out _);
static void OnInputChanged(string value)
{
if (SRENotSupported)
return;
@ -283,7 +301,7 @@ namespace UnityExplorer.CSConsole
DoAutoIndent();
}
bool inStringOrComment = HighlightVisibleInput();
HighlightVisibleInput(out bool inStringOrComment);
if (!settingCaretCoroutine)
{
@ -299,40 +317,27 @@ namespace UnityExplorer.CSConsole
UpdateCaret(out _);
}
private static float timeOfLastCtrlR;
public static void Update()
static void OnToggleAutoIndent(bool value)
{
if (SRENotSupported)
return;
UpdateCaret(out bool caretMoved);
if (!settingCaretCoroutine && EnableSuggestions)
{
if (AutoCompleteModal.CheckEscape(Completer))
{
OnAutocompleteEscaped();
return;
}
if (caretMoved)
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
}
if (EnableCtrlRShortcut
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
&& InputManager.GetKeyDown(KeyCode.R)
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
{
timeOfLastCtrlR = Time.realtimeSinceStartup;
Evaluate(Panel.Input.Text);
}
EnableAutoIndent = value;
}
private const int CSCONSOLE_LINEHEIGHT = 18;
static void OnToggleCtrlRShortcut(bool value)
{
EnableCtrlRShortcut = value;
}
private static void UpdateCaret(out bool caretMoved)
static void OnToggleSuggestions(bool value)
{
EnableSuggestions = value;
}
#endregion
#region Caret position
static void UpdateCaret(out bool caretMoved)
{
int prevCaret = LastCaretPosition;
caretMoved = false;
@ -377,52 +382,95 @@ namespace UnityExplorer.CSConsole
}
}
private static void SetCaretPosition(int caretPosition)
public static void SetCaretPosition(int caretPosition)
{
Input.Component.caretPosition = caretPosition;
// Fix to make sure we always really set the caret position.
// Yields a frame and fixes text-selection issues.
settingCaretCoroutine = true;
Input.Component.readOnly = true;
RuntimeHelper.StartCoroutine(SetCaretCoroutine(caretPosition));
RuntimeHelper.StartCoroutine(DoSetCaretCoroutine(caretPosition));
}
private static IEnumerator SetCaretCoroutine(int caretPosition)
static IEnumerator DoSetCaretCoroutine(int caretPosition)
{
Color color = Input.Component.selectionColor;
color.a = 0f;
Input.Component.selectionColor = color;
EventSystemHelper.SetSelectedGameObject(null);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
EventSystemHelper.SetSelectedGameObject(null);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
EventSystemHelper.SetSelectionGuard(false);
Input.Component.Select();
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
Input.Component.caretPosition = caretPosition;
Input.Component.selectionFocusPosition = caretPosition;
LastCaretPosition = Input.Component.caretPosition;
color.a = defaultInputFieldAlpha;
color.a = DefaultInputFieldAlpha;
Input.Component.selectionColor = color;
Input.Component.readOnly = false;
settingCaretCoroutine = false;
}
// For Home and End keys
static void JumpToStartOrEndOfLine(bool toStart)
{
// Determine the current and next line
UILineInfo thisline = default;
UILineInfo? nextLine = null;
for (int i = 0; i < Input.Component.cachedInputTextGenerator.lineCount; i++)
{
UILineInfo line = Input.Component.cachedInputTextGenerator.lines[i];
if (line.startCharIdx > LastCaretPosition)
{
nextLine = line;
break;
}
thisline = line;
}
if (toStart)
{
// Determine where the indented text begins
int endOfLine = nextLine == null ? Input.Text.Length : nextLine.Value.startCharIdx;
int indentedStart = thisline.startCharIdx;
while (indentedStart < endOfLine - 1 && char.IsWhiteSpace(Input.Text[indentedStart]))
indentedStart++;
// Jump to either the true start or the non-whitespace position,
// depending on which one we are not at.
if (LastCaretPosition == indentedStart)
SetCaretPosition(thisline.startCharIdx);
else
SetCaretPosition(indentedStart);
}
else
{
// If there is no next line, jump to the end of this line (+1, to the invisible next character position)
if (nextLine == null)
SetCaretPosition(Input.Text.Length);
else // jump to the next line start index - 1, ie. end of this line
SetCaretPosition(nextLine.Value.startCharIdx - 1);
}
}
#endregion
#region Lexer Highlighting
/// <summary>
/// Returns true if caret is inside string or comment, false otherwise
/// </summary>
private static bool HighlightVisibleInput()
private static void HighlightVisibleInput(out bool inStringOrComment)
{
inStringOrComment = false;
if (string.IsNullOrEmpty(Input.Text))
{
Panel.HighlightText.text = "";
Panel.LineNumberText.text = "1";
return false;
return;
}
// Calculate the visible lines
@ -457,7 +505,7 @@ namespace UnityExplorer.CSConsole
// Highlight the visible text with the LexerBuilder
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret);
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out inStringOrComment);
// Set the line numbers
@ -495,7 +543,7 @@ namespace UnityExplorer.CSConsole
Panel.LineNumberText.text = sb.ToString();
return ret;
return;
}
#endregion
@ -535,13 +583,11 @@ namespace UnityExplorer.CSConsole
#region Auto indenting
private static int prevContentLen = 0;
private static void DoAutoIndent()
{
if (Input.Text.Length > prevContentLen)
if (Input.Text.Length > previousContentLength)
{
int inc = Input.Text.Length - prevContentLen;
int inc = Input.Text.Length - previousContentLength;
if (inc == 1)
{
@ -560,7 +606,7 @@ namespace UnityExplorer.CSConsole
}
}
prevContentLen = Input.Text.Length;
previousContentLength = Input.Text.Length;
}
#endregion
@ -568,8 +614,6 @@ namespace UnityExplorer.CSConsole
#region "Help" interaction
private static bool SRENotSupported;
private static void DisableConsole(Exception ex)
{
SRENotSupported = true;

View File

@ -17,8 +17,6 @@ namespace UnityExplorer.CSConsole
public class LexerBuilder
{
#region Core and initialization
public const char WHITESPACE = ' ';
public readonly HashSet<char> IndentOpenChars = new() { '{', '(' };
public readonly HashSet<char> IndentCloseChars = new() { '}', ')' };
@ -50,8 +48,6 @@ namespace UnityExplorer.CSConsole
}
}
#endregion
/// <summary>The last committed index for a match or no-match. Starts at -1 for a new parse.</summary>
public int CommittedIndex { get; private set; }
/// <summary>The index of the character we are currently parsing, at minimum it will be CommittedIndex + 1.</summary>

View File

@ -32,13 +32,7 @@ namespace UnityExplorer.CSConsole.Lexers
if (IsSymbol(lexer.Current))
{
do
{
lexer.Commit();
lexer.PeekNext();
}
while (IsSymbol(lexer.Current));
lexer.Commit();
return true;
}

View File

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityExplorer.Config;
// Thanks to ManlyMarco for this
@ -10,6 +11,9 @@ namespace UnityExplorer.CSConsole
{
public class ScriptEvaluator : Evaluator, IDisposable
{
internal TextWriter _textWriter;
internal static StreamReportPrinter _reportPrinter;
private static readonly HashSet<string> StdLib = new(StringComparer.InvariantCultureIgnoreCase)
{
"mscorlib",
@ -18,9 +22,6 @@ namespace UnityExplorer.CSConsole
"System.Xml"
};
internal TextWriter _textWriter;
internal static StreamReportPrinter _reportPrinter;
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
{
_textWriter = tw;
@ -48,8 +49,19 @@ namespace UnityExplorer.CSConsole
private void Reference(Assembly asm)
{
string name = asm.GetName().Name;
if (name == "completions")
if (name == "completions") // ignore assemblies generated by mcs' autocomplete.
return;
foreach (string blacklisted in ConfigManager.CSConsole_Assembly_Blacklist.Value.Split(';'))
{
string bl = blacklisted;
if (bl.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
bl = blacklisted.Substring(0, bl.Length - 4);
if (string.Equals(bl, name, StringComparison.OrdinalIgnoreCase))
return;
}
ReferenceAssembly(asm);
}

View File

@ -29,8 +29,8 @@ namespace UnityExplorer.CacheObject
this.Owner = inspector;
this.NameLabelText = this switch
{
CacheMethod => SignatureHighlighter.HighlightMethod(member as MethodInfo),
CacheConstructor => SignatureHighlighter.HighlightConstructor(member as ConstructorInfo),
CacheMethod => SignatureHighlighter.ParseMethod(member as MethodInfo),
CacheConstructor => SignatureHighlighter.ParseConstructor(member as ConstructorInfo),
_ => SignatureHighlighter.Parse(member.DeclaringType, false, member),
};

View File

@ -28,28 +28,31 @@ namespace UnityExplorer.CacheObject
public abstract class CacheObjectBase
{
public ICacheObjectController Owner { get; set; }
public CacheObjectCell CellView { get; internal set; }
public object Value { get; protected set; }
public Type FallbackType { get; protected set; }
public bool LastValueWasNull { get; private set; }
public ValueState State = ValueState.NotEvaluated;
public Type LastValueType;
public ValueState State { get; set; }
public Exception LastException { get; protected set; }
bool valueIsNull;
Type currentValueType;
// InteractiveValues
public InteractiveValue IValue { get; private set; }
public Type CurrentIValueType { get; private set; }
public bool SubContentShowWanted { get; private set; }
// UI
public string NameLabelText { get; protected set; }
public string NameLabelTextRaw { get; protected set; }
public string ValueLabelText { get; protected set; }
// Abstract
public abstract bool ShouldAutoEvaluate { get; }
public abstract bool HasArguments { get; }
public abstract bool CanWrite { get; }
public Exception LastException { get; protected set; }
protected const string NOT_YET_EVAL = "<color=grey>Not yet evaluated</color>";
public virtual void SetFallbackType(Type fallbackType)
{
@ -57,17 +60,6 @@ namespace UnityExplorer.CacheObject
this.ValueLabelText = GetValueLabel();
}
protected const string NOT_YET_EVAL = "<color=grey>Not yet evaluated</color>";
public virtual void ReleasePooledObjects()
{
if (this.IValue != null)
ReleaseIValue();
if (this.CellView != null)
UnlinkFromView();
}
public virtual void SetView(CacheObjectCell cellView)
{
this.CellView = cellView;
@ -86,6 +78,15 @@ namespace UnityExplorer.CacheObject
this.IValue.UIRoot.transform.SetParent(InactiveIValueHolder.transform, false);
}
public virtual void ReleasePooledObjects()
{
if (this.IValue != null)
ReleaseIValue();
if (this.CellView != null)
UnlinkFromView();
}
// Updating and applying values
// The only method which sets the CacheObjectBase.Value
@ -130,18 +131,18 @@ namespace UnityExplorer.CacheObject
if (LastException != null)
{
LastValueWasNull = true;
LastValueType = FallbackType;
valueIsNull = true;
currentValueType = FallbackType;
State = ValueState.Exception;
}
else if (Value.IsNullOrDestroyed())
{
LastValueWasNull = true;
valueIsNull = true;
State = GetStateForType(FallbackType);
}
else
{
LastValueWasNull = false;
valueIsNull = false;
State = GetStateForType(Value.GetActualType());
}
@ -163,10 +164,10 @@ namespace UnityExplorer.CacheObject
public ValueState GetStateForType(Type type)
{
if (LastValueType == type && (State != ValueState.Exception || LastException != null))
if (currentValueType == type && (State != ValueState.Exception || LastException != null))
return State;
LastValueType = type;
currentValueType = type;
if (type == typeof(bool))
return ValueState.Boolean;
else if (type.IsPrimitive || type == typeof(decimal))
@ -189,7 +190,7 @@ namespace UnityExplorer.CacheObject
protected string GetValueLabel()
{
string label = "";
string label = string.Empty;
switch (State)
{
@ -206,19 +207,19 @@ namespace UnityExplorer.CacheObject
// and valuestruct also doesnt want it if we can parse it
case ValueState.ValueStruct:
if (ParseUtility.CanParse(LastValueType))
if (ParseUtility.CanParse(currentValueType))
return null;
break;
// string wants it trimmed to max 200 chars
case ValueState.String:
if (!LastValueWasNull)
if (!valueIsNull)
return $"\"{ToStringUtility.PruneString(Value as string, 200, 5)}\"";
break;
// try to prefix the count of the collection for lists and dicts
case ValueState.Collection:
if (!LastValueWasNull)
if (!valueIsNull)
{
if (Value is IList iList)
label = $"[{iList.Count}] ";
@ -230,7 +231,7 @@ namespace UnityExplorer.CacheObject
break;
case ValueState.Dictionary:
if (!LastValueWasNull)
if (!valueIsNull)
{
if (Value is IDictionary iDict)
label = $"[{iDict.Count}] ";
@ -291,7 +292,7 @@ namespace UnityExplorer.CacheObject
SetValueState(cell, new(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite));
break;
case ValueState.String:
if (LastValueWasNull)
if (valueIsNull)
SetValueState(cell, new(true, subContentButtonActive: true));
else
SetValueState(cell, new(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true));
@ -301,17 +302,17 @@ namespace UnityExplorer.CacheObject
break;
case ValueState.Color:
case ValueState.ValueStruct:
if (ParseUtility.CanParse(LastValueType))
if (ParseUtility.CanParse(currentValueType))
SetValueState(cell, new(false, false, null, true, false, true, CanWrite, true, true));
else
SetValueState(cell, new(true, inspectActive: true, subContentButtonActive: true));
break;
case ValueState.Collection:
case ValueState.Dictionary:
SetValueState(cell, new(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
SetValueState(cell, new(true, inspectActive: !valueIsNull, subContentButtonActive: !valueIsNull));
break;
case ValueState.Unsupported:
SetValueState(cell, new(true, inspectActive: !LastValueWasNull));
SetValueState(cell, new(true, inspectActive: !valueIsNull));
break;
}
@ -333,7 +334,7 @@ namespace UnityExplorer.CacheObject
// Type label (for primitives)
cell.TypeLabel.gameObject.SetActive(args.typeLabelActive);
if (args.typeLabelActive)
cell.TypeLabel.text = SignatureHighlighter.Parse(LastValueType, false);
cell.TypeLabel.text = SignatureHighlighter.Parse(currentValueType, false);
// toggle for bools
cell.Toggle.gameObject.SetActive(args.toggleActive);
@ -348,7 +349,7 @@ namespace UnityExplorer.CacheObject
cell.InputField.UIRoot.SetActive(args.inputActive);
if (args.inputActive)
{
cell.InputField.Text = ParseUtility.ToStringForInput(Value, LastValueType);
cell.InputField.Text = ParseUtility.ToStringForInput(Value, currentValueType);
cell.InputField.Component.readOnly = !CanWrite;
}
@ -357,12 +358,12 @@ namespace UnityExplorer.CacheObject
// Inspect button only if last value not null.
if (cell.InspectButton != null)
cell.InspectButton.Component.gameObject.SetActive(args.inspectActive && !LastValueWasNull);
cell.InspectButton.Component.gameObject.SetActive(args.inspectActive && !valueIsNull);
// set subcontent button if needed, and for null strings and exceptions
cell.SubContentButton.Component.gameObject.SetActive(
args.subContentButtonActive
&& (!LastValueWasNull || State == ValueState.String || State == ValueState.Exception));
&& (!valueIsNull || State == ValueState.String || State == ValueState.Exception));
}
// CacheObjectCell Apply
@ -373,7 +374,7 @@ namespace UnityExplorer.CacheObject
SetUserValue(this.CellView.Toggle.isOn);
else
{
if (ParseUtility.TryParse(CellView.InputField.Text, LastValueType, out object value, out Exception ex))
if (ParseUtility.TryParse(CellView.InputField.Text, currentValueType, out object value, out Exception ex))
{
SetUserValue(value);
}

View File

@ -16,19 +16,20 @@ namespace UnityExplorer.Config
// Actual UE Settings
public static ConfigElement<KeyCode> Master_Toggle;
public static ConfigElement<int> Target_Display;
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
public static ConfigElement<bool> Force_Unlock_Mouse;
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
public static ConfigElement<bool> Aggressive_Mouse_Unlock;
public static ConfigElement<bool> Disable_EventSystem_Override;
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> Reflection_Signature_Blacklist;
public static ConfigElement<bool> Disable_EventSystem_Override;
public static ConfigElement<int> Target_Display;
public static ConfigElement<bool> Force_Unlock_Mouse;
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
public static ConfigElement<string> Default_Output_Path;
public static ConfigElement<string> DnSpy_Path;
public static ConfigElement<bool> Log_Unity_Debug;
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
public static ConfigElement<KeyCode> World_MouseInspect_Keybind;
public static ConfigElement<KeyCode> UI_MouseInspect_Keybind;
public static ConfigElement<string> CSConsole_Assembly_Blacklist;
public static ConfigElement<string> Reflection_Signature_Blacklist;
// internal configs
internal static InternalConfigHandler InternalHandler { get; private set; }
@ -54,7 +55,9 @@ namespace UnityExplorer.Config
Handler.LoadConfig();
InternalHandler.LoadConfig();
//InitConsoleCallback();
#if STANDALONE
Loader.Standalone.ExplorerEditorBehaviour.Instance?.LoadConfigs();
#endif
}
internal static void RegisterConfigElement<T>(ConfigElement<T> configElement)
@ -73,23 +76,53 @@ namespace UnityExplorer.Config
private static void CreateConfigElements()
{
Master_Toggle = new ConfigElement<KeyCode>("UnityExplorer Toggle",
Master_Toggle = new("UnityExplorer Toggle",
"The key to enable or disable UnityExplorer's menu and features.",
KeyCode.F7);
Hide_On_Startup = new ConfigElement<bool>("Hide On Startup",
Hide_On_Startup = new("Hide On Startup",
"Should UnityExplorer be hidden on startup?",
false);
Target_Display = new ConfigElement<int>("Target Display",
Startup_Delay_Time = new("Startup Delay Time",
"The delay on startup before the UI is created.",
1f);
Target_Display = new("Target Display",
"The monitor index for UnityExplorer to use, if you have multiple. 0 is the default display, 1 is secondary, etc. " +
"Restart recommended when changing this setting. Make sure your extra monitors are the same resolution as your primary monitor.",
0);
Main_Navbar_Anchor = new ConfigElement<UIManager.VerticalAnchor>("Main Navbar Anchor",
Force_Unlock_Mouse = new("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
Force_Unlock_Mouse.OnValueChanged += (bool value) => UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = value;
Force_Unlock_Toggle = new("Force Unlock Toggle Key",
"The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.",
KeyCode.None);
Disable_EventSystem_Override = new("Disable EventSystem override",
"If enabled, UnityExplorer will not override the EventSystem from the game.\n<b>May require restart to take effect.</b>",
false);
Disable_EventSystem_Override.OnValueChanged += (bool value) => UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value;
Default_Output_Path = new("Default Output Path",
"The default output path when exporting things from UnityExplorer.",
Path.Combine(ExplorerCore.ExplorerFolder, "Output"));
DnSpy_Path = new("dnSpy Path",
"The full path to dnSpy.exe (64-bit).",
@"C:/Program Files/dnspy/dnSpy.exe");
Main_Navbar_Anchor = new("Main Navbar Anchor",
"The vertical anchor of the main UnityExplorer Navbar, in case you want to move it.",
UIManager.VerticalAnchor.Top);
Log_Unity_Debug = new("Log Unity Debug",
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
false);
World_MouseInspect_Keybind = new("World Mouse-Inspect Keybind",
"Optional keybind to being a World-mode Mouse Inspect.",
KeyCode.None);
@ -98,33 +131,13 @@ namespace UnityExplorer.Config
"Optional keybind to begin a UI-mode Mouse Inspect.",
KeyCode.None);
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
Force_Unlock_Mouse.OnValueChanged += (bool value) => UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = value;
CSConsole_Assembly_Blacklist = new("CSharp Console Assembly Blacklist",
"Use this to blacklist Assembly names from being referenced by the C# Console. Requires a Reset of the C# Console.\n" +
"Separate each Assembly with a semicolon ';'." +
"For example, to blacklist Assembly-CSharp, you would add 'Assembly-CSharp;'",
"");
Force_Unlock_Toggle = new ConfigElement<KeyCode>("Force Unlock Toggle Key",
"The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.",
KeyCode.None);
Disable_EventSystem_Override = new ConfigElement<bool>("Disable EventSystem override",
"If enabled, UnityExplorer will not override the EventSystem from the game.\n<b>May require restart to take effect.</b>",
false);
Disable_EventSystem_Override.OnValueChanged += (bool value) => UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value;
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
false);
Default_Output_Path = new ConfigElement<string>("Default Output Path",
"The default output path when exporting things from UnityExplorer.",
Path.Combine(ExplorerCore.ExplorerFolder, "Output"));
Startup_Delay_Time = new ConfigElement<float>("Startup Delay Time",
"The delay on startup before the UI is created.",
1f);
Reflection_Signature_Blacklist = new ConfigElement<string>("Member Signature Blacklist",
Reflection_Signature_Blacklist = new("Member Signature Blacklist",
"Use this to blacklist certain member signatures if they are known to cause a crash or other issues.\r\n" +
"Seperate signatures with a semicolon ';'.\r\n" +
"For example, to blacklist Camera.main, you would add 'UnityEngine.Camera.main;'",

View File

@ -1,4 +1,8 @@
using UnityEngine;
using System;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UniverseLib;
#if CPP
using UnhollowerRuntimeLib;
#endif
@ -29,5 +33,39 @@ namespace UnityExplorer
{
ExplorerCore.Update();
}
// For editor, to clean up objects
internal void OnDestroy()
{
OnApplicationQuit();
}
internal bool quitting;
internal void OnApplicationQuit()
{
if (quitting) return;
quitting = true;
TryDestroy(UIManager.UIRoot?.transform.root.gameObject);
TryDestroy((typeof(Universe).Assembly.GetType("UniverseLib.UniversalBehaviour")
.GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic)
.GetValue(null, null)
as Component).gameObject);
TryDestroy(this.gameObject);
}
internal void TryDestroy(GameObject obj)
{
try
{
if (obj)
Destroy(obj);
}
catch { }
}
}
}

View File

@ -14,7 +14,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.7.5";
public const string VERSION = "4.8.1";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";

View File

@ -16,14 +16,13 @@ namespace UnityExplorer.Hooks
public float DefaultHeight => 30;
public Text MethodNameLabel;
public Text HookedLabel;
public ButtonRef HookButton;
public int CurrentDisplayedIndex;
private void OnHookClicked()
{
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
HookCreator.AddHookClicked(CurrentDisplayedIndex);
}
public void Enable()
@ -44,9 +43,6 @@ namespace UnityExplorer.Hooks
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
HookButton.OnClick += OnHookClicked;

View File

@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
@ -24,17 +25,18 @@ namespace UnityExplorer.Hooks
private void OnToggleActiveClicked()
{
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
HookList.EnableOrDisableHookClicked(CurrentDisplayedIndex);
}
private void OnDeleteClicked()
{
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
HookList.DeleteHookClicked(CurrentDisplayedIndex);
HookCreator.AddHooksScrollPool.Refresh(true, false);
}
private void OnEditPatchClicked()
{
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
HookList.EditPatchClicked(CurrentDisplayedIndex);
}
public GameObject CreateContent(GameObject parent)
@ -48,18 +50,18 @@ namespace UnityExplorer.Hooks
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "On", new Color(0.15f, 0.2f, 0.15f));
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 35);
ToggleActiveButton.OnClick += OnToggleActiveClicked;
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
DeleteButton.OnClick += OnDeleteClicked;
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 35);
EditPatchButton.OnClick += OnEditPatchClicked;
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "X", new Color(0.2f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 35);
DeleteButton.OnClick += OnDeleteClicked;
return UIRoot;
}

341
src/Hooks/HookCreator.cs Normal file
View File

@ -0,0 +1,341 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CSConsole;
using UnityExplorer.Runtime;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookCreator : ICellPoolDataSource<AddHookCell>
{
public int ItemCount => filteredEligibleMethods.Count;
static readonly List<MethodInfo> currentAddEligibleMethods = new();
static readonly List<MethodInfo> filteredEligibleMethods = new();
static readonly List<string> currentEligibleNamesForFiltering = new();
// hook editor
static readonly LexerBuilder Lexer = new();
internal static HookInstance CurrentEditedHook;
// Add Hooks UI
internal static GameObject AddHooksRoot;
internal static ScrollPool<AddHookCell> AddHooksScrollPool;
internal static Text AddHooksLabel;
internal static InputFieldRef AddHooksMethodFilterInput;
internal static InputFieldRef ClassSelectorInputField;
internal static Type pendingGenericDefinition;
internal static MethodInfo pendingGenericMethod;
public static bool PendingGeneric => pendingGenericDefinition != null || pendingGenericMethod != null;
// Hook Source Editor UI
public static GameObject EditorRoot { get; private set; }
public static Text EditingHookLabel { get; private set; }
public static InputFieldScroller EditorInputScroller { get; private set; }
public static InputFieldRef EditorInput => EditorInputScroller.InputField;
public static Text EditorInputText { get; private set; }
public static Text EditorHighlightText { get; private set; }
// ~~~~~~ New hook method selector ~~~~~~~
public void OnClassSelectedForHooks(string typeFullName)
{
Type type = ReflectionUtility.GetTypeByName(typeFullName);
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
if (type.IsGenericType)
{
pendingGenericDefinition = type;
HookManagerPanel.genericArgsHandler.Show(OnGenericClassChosen, OnGenericClassCancel, type);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
return;
}
ShowMethodsForType(type);
}
void ShowMethodsForType(Type type)
{
SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
AddHooksMethodFilterInput.Text = string.Empty;
filteredEligibleMethods.Clear();
currentAddEligibleMethods.Clear();
currentEligibleNamesForFiltering.Clear();
foreach (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (UERuntimeHelper.IsBlacklisted(method))
continue;
currentAddEligibleMethods.Add(method);
currentEligibleNamesForFiltering.Add(SignatureHighlighter.RemoveHighlighting(SignatureHighlighter.ParseMethod(method)));
filteredEligibleMethods.Add(method);
}
AddHooksScrollPool.Refresh(true, true);
}
void OnGenericClassChosen(Type[] genericArgs)
{
Type generic = pendingGenericDefinition.MakeGenericType(genericArgs);
ShowMethodsForType(generic);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
void OnGenericClassCancel()
{
pendingGenericDefinition = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
public void SetAddHooksLabelType(string typeText)
{
AddHooksLabel.text = $"Adding hooks to: {typeText}";
AddHooksMethodFilterInput.GameObject.SetActive(true);
AddHooksScrollPool.UIRoot.SetActive(true);
}
public static void AddHookClicked(int index)
{
if (index >= filteredEligibleMethods.Count)
return;
MethodInfo method = filteredEligibleMethods[index];
if (!method.IsGenericMethod && HookList.hookedSignatures.Contains(method.FullDescription()))
{
ExplorerCore.Log($"Non-generic methods can only be hooked once.");
return;
}
else if (method.IsGenericMethod)
{
pendingGenericMethod = method;
HookManagerPanel.genericArgsHandler.Show(OnGenericMethodChosen, OnGenericMethodCancel, method);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
return;
}
AddHook(filteredEligibleMethods[index]);
}
static void OnGenericMethodChosen(Type[] arguments)
{
MethodInfo generic = pendingGenericMethod.MakeGenericMethod(arguments);
AddHook(generic);
}
static void OnGenericMethodCancel()
{
pendingGenericMethod = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
public static void AddHook(MethodInfo method)
{
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
string sig = method.FullDescription();
if (HookList.hookedSignatures.Contains(sig))
{
ExplorerCore.LogWarning($"Method is already hooked!");
return;
}
HookInstance hook = new(method);
HookList.hookedSignatures.Add(sig);
HookList.currentHooks.Add(sig, hook);
AddHooksScrollPool.Refresh(true, false);
HookList.HooksScrollPool.Refresh(true, false);
}
public void OnAddHookFilterInputChanged(string input)
{
filteredEligibleMethods.Clear();
if (string.IsNullOrEmpty(input))
filteredEligibleMethods.AddRange(currentAddEligibleMethods);
else
{
for (int i = 0; i < currentAddEligibleMethods.Count; i++)
{
MethodInfo eligible = currentAddEligibleMethods[i];
string sig = currentEligibleNamesForFiltering[i];
if (sig.ContainsIgnoreCase(input))
filteredEligibleMethods.Add(eligible);
}
}
AddHooksScrollPool.Refresh(true, true);
}
// Set eligible method cell
public void OnCellBorrowed(AddHookCell cell) { }
public void SetCell(AddHookCell cell, int index)
{
if (index >= filteredEligibleMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
MethodInfo method = filteredEligibleMethods[index];
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(method);
}
// ~~~~~~~~ Hook source editor ~~~~~~~~
internal static void SetEditedHook(HookInstance hook)
{
CurrentEditedHook = hook;
EditingHookLabel.text = $"Editing: {SignatureHighlighter.Parse(hook.TargetMethod.DeclaringType, false, hook.TargetMethod)}";
EditorInput.Text = hook.PatchSourceCode;
}
internal static void OnEditorInputChanged(string value)
{
EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0, EditorInput.Component.caretPosition, out _);
}
internal static void EditorInputCancel()
{
CurrentEditedHook = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
internal static void EditorInputSave()
{
string input = EditorInput.Text;
bool wasEnabled = CurrentEditedHook.Enabled;
if (CurrentEditedHook.CompileAndGenerateProcessor(input))
{
if (wasEnabled)
CurrentEditedHook.Patch();
CurrentEditedHook.PatchSourceCode = input;
CurrentEditedHook = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
HookList.HooksScrollPool.Refresh(true, false);
}
// UI Construction
internal void ConstructAddHooksView(GameObject rightGroup)
{
AddHooksRoot = UIFactory.CreateUIObject("AddHooksPanel", rightGroup);
UIFactory.SetLayoutElement(AddHooksRoot, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(AddHooksRoot, false, false, true, true);
GameObject addRow = UIFactory.CreateHorizontalGroup(AddHooksRoot, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
ClassSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(ClassSelectorInputField.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
TypeCompleter completer = new(typeof(object), ClassSelectorInputField, true, false, true);
//completer.AllTypes = true;
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "View Methods");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 110, minHeight: 25);
addButton.OnClick += () => { OnClassSelectedForHooks(ClassSelectorInputField.Text); };
AddHooksLabel = UIFactory.CreateLabel(AddHooksRoot, "AddLabel", "Choose a class to begin...", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(AddHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
AddHooksMethodFilterInput = UIFactory.CreateInputField(AddHooksRoot, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
AddHooksMethodFilterInput.OnValueChanged += OnAddHookFilterInputChanged;
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(AddHooksRoot, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
AddHooksScrollPool.Initialize(this);
AddHooksMethodFilterInput.GameObject.SetActive(false);
AddHooksScrollPool.UIRoot.SetActive(false);
}
public void ConstructEditor(GameObject parent)
{
EditorRoot = UIFactory.CreateUIObject("HookSourceEditor", parent);
UIFactory.SetLayoutElement(EditorRoot, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(EditorRoot, true, true, true, true, 2, 3, 3, 3, 3);
EditingHookLabel = UIFactory.CreateLabel(EditorRoot, "EditingHookLabel", "NOT SET", TextAnchor.MiddleCenter);
EditingHookLabel.fontStyle = FontStyle.Bold;
UIFactory.SetLayoutElement(EditingHookLabel.gameObject, flexibleWidth: 9999, minHeight: 25);
Text editorLabel = UIFactory.CreateLabel(EditorRoot,
"EditorLabel",
"* Accepted method names are <b>Prefix</b>, <b>Postfix</b>, <b>Finalizer</b> and <b>Transpiler</b> (can define multiple).\n" +
"* Your patch methods must be static.\n" +
"* Hooks are temporary! Copy the source into your IDE to avoid losing work if you wish to keep it!",
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(EditorRoot, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorSaveButton.OnClick += EditorInputSave;
ButtonRef editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorDoneButton.OnClick += EditorInputCancel;
int fontSize = 16;
GameObject inputObj = UIFactory.CreateScrollInputField(EditorRoot, "EditorInput", "", out InputFieldScroller inputScroller, fontSize);
EditorInputScroller = inputScroller;
EditorInput.OnValueChanged += OnEditorInputChanged;
EditorInputText = EditorInput.Component.textComponent;
EditorInputText.supportRichText = false;
EditorInputText.color = Color.clear;
EditorInput.Component.customCaretColor = true;
EditorInput.Component.caretColor = Color.white;
EditorInput.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
RectTransform highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
highlightTextRect.pivot = new Vector2(0, 1);
highlightTextRect.anchorMin = Vector2.zero;
highlightTextRect.anchorMax = Vector2.one;
highlightTextRect.offsetMin = Vector2.zero;
highlightTextRect.offsetMax = Vector2.zero;
EditorHighlightText = highlightTextObj.AddComponent<Text>();
EditorHighlightText.color = Color.white;
EditorHighlightText.supportRichText = true;
EditorHighlightText.fontSize = fontSize;
// Set fonts
EditorInputText.font = UniversalUI.ConsoleFont;
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
EditorHighlightText.font = UniversalUI.ConsoleFont;
}
}
}

View File

@ -1,6 +1,7 @@
using HarmonyLib;
using Mono.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
@ -14,12 +15,13 @@ namespace UnityExplorer.Hooks
{
// Static
private static readonly StringBuilder evalOutput = new();
private static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evalOutput));
static readonly StringBuilder evaluatorOutput;
static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder()));
static HookInstance()
{
scriptEvaluator.Run("using System;");
scriptEvaluator.Run("using System.Text;");
scriptEvaluator.Run("using System.Reflection;");
scriptEvaluator.Run("using System.Collections;");
scriptEvaluator.Run("using System.Collections.Generic;");
@ -28,21 +30,22 @@ namespace UnityExplorer.Hooks
// Instance
public bool Enabled;
public MethodInfo TargetMethod;
public string PatchSourceCode;
private readonly string shortSignature;
private PatchProcessor patchProcessor;
readonly string signature;
PatchProcessor patchProcessor;
private MethodInfo postfix;
private MethodInfo prefix;
private MethodInfo finalizer;
private MethodInfo transpiler;
MethodInfo postfix;
MethodInfo prefix;
MethodInfo finalizer;
MethodInfo transpiler;
public HookInstance(MethodInfo targetMethod)
{
this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
this.signature = TargetMethod.FullDescription();
GenerateDefaultPatchSourceCode(targetMethod);
@ -59,15 +62,15 @@ namespace UnityExplorer.Hooks
{
Unpatch();
StringBuilder codeBuilder = new();
try
{
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method
StringBuilder codeBuilder = new();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine($"static class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(patchSource);
codeBuilder.AppendLine("}");
@ -107,85 +110,108 @@ namespace UnityExplorer.Hooks
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
if (ex is FormatException)
{
string output = scriptEvaluator._textWriter.ToString();
string[] outputSplit = output.Split('\n');
if (outputSplit.Length >= 2)
output = outputSplit[outputSplit.Length - 2];
evaluatorOutput.Clear();
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
ExplorerCore.LogWarning($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
else
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
}
else
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
// ExplorerCore.Log(codeBuilder.ToString());
return false;
}
}
static string FullDescriptionClean(Type type)
{
string description = type.FullDescription().Replace("+", ".");
if (description.EndsWith("&"))
description = $"ref {description.Substring(0, description.Length - 1)}";
return description;
}
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{
StringBuilder codeBuilder = new();
// Arguments
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
codeBuilder.Append("static void Postfix(");
if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
bool isStatic = targetMethod.IsStatic;
List<string> arguments = new();
if (!isStatic)
arguments.Add($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance");
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
arguments.Add($"{FullDescriptionClean(targetMethod.ReturnType)} __result");
ParameterInfo[] parameters = targetMethod.GetParameters();
int paramIdx = 0;
foreach (ParameterInfo param in parameters)
{
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
arguments.Add($"{FullDescriptionClean(param.ParameterType)} __{paramIdx}");
paramIdx++;
}
codeBuilder.Append(string.Join(", ", arguments.ToArray()));
codeBuilder.Append(")\n");
// Patch body
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {");
// Log message
StringBuilder logMessage = new();
logMessage.Append($"Patch called: {shortSignature}\\n");
codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();");
codeBuilder.AppendLine($" sb.AppendLine(\"--------------------\");");
codeBuilder.AppendLine($" sb.AppendLine(\"{signature}\");");
if (!targetMethod.IsStatic)
logMessage.Append("__instance: {__instance.ToString()}\\n");
codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());");
paramIdx = 0;
foreach (ParameterInfo param in parameters)
{
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
codeBuilder.Append($" sb.Append(\"- Parameter {paramIdx} '{param.Name}': \")");
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType)
logMessage.Append($"{{__{paramIdx}.ToString()}}");
codeBuilder.AppendLine($".AppendLine(__{paramIdx}.ToString());");
else
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
logMessage.Append("\\n");
codeBuilder.AppendLine($".AppendLine(__{paramIdx}?.ToString() ?? \"null\");");
paramIdx++;
}
if (targetMethod.ReturnType != typeof(void))
{
logMessage.Append("Return value: ");
codeBuilder.Append(" sb.Append(\"- Return value: \")");
if (targetMethod.ReturnType.IsValueType)
logMessage.Append("{__result.ToString()}");
codeBuilder.AppendLine(".AppendLine(__result.ToString());");
else
logMessage.Append("{__result?.ToString() ?? \"null\"}");
logMessage.Append("\\n");
codeBuilder.AppendLine(".AppendLine(__result?.ToString() ?? \"null\");");
}
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log(sb.ToString());");
codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {signature}:\\n{{ex}}\");");
codeBuilder.AppendLine(" }");
// End patch body
codeBuilder.AppendLine("}");
//ExplorerCore.Log(codeBuilder.ToString());
return PatchSourceCode = codeBuilder.ToString();
}

97
src/Hooks/HookList.cs Normal file
View File

@ -0,0 +1,97 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookList : ICellPoolDataSource<HookCell>
{
public int ItemCount => currentHooks.Count;
internal static readonly HashSet<string> hookedSignatures = new();
internal static readonly OrderedDictionary currentHooks = new();
internal static GameObject UIRoot;
internal static ScrollPool<HookCell> HooksScrollPool;
public static void EnableOrDisableHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
HooksScrollPool.Refresh(true, false);
}
public static void DeleteHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
if (HookCreator.CurrentEditedHook == hook)
HookCreator.EditorInputCancel();
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
HooksScrollPool.Refresh(true, false);
}
public static void EditPatchClicked(int index)
{
if (HookCreator.PendingGeneric)
HookManagerPanel.genericArgsHandler.Cancel();
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor);
HookInstance hook = (HookInstance)currentHooks[index];
HookCreator.SetEditedHook(hook);
}
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
HookInstance hook = (HookInstance)currentHooks[index];
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "On" : "Off";
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// UI
internal void ConstructUI(GameObject leftGroup)
{
UIRoot = UIFactory.CreateUIObject("CurrentHooksPanel", leftGroup);
UIFactory.SetLayoutElement(UIRoot, preferredHeight: 150, flexibleHeight: 0, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, true, true, true, true);
Text hooksLabel = UIFactory.CreateLabel(UIRoot, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(UIRoot, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(this);
}
}
}

View File

@ -1,227 +0,0 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using UnityEngine;
using UnityExplorer.CSConsole;
using UnityExplorer.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
{
private static HookManager s_instance;
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
// This class acts as the data source for both current hooks and eligable methods when adding hooks.
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
// correct pool cells.
private bool isAddingMethods;
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
// current hooks
private readonly HashSet<string> hookedSignatures = new();
private readonly OrderedDictionary currentHooks = new();
// adding hooks
private readonly List<MethodInfo> currentAddEligableMethods = new();
private readonly List<MethodInfo> filteredEligableMethods = new();
// hook editor
private readonly LexerBuilder Lexer = new();
private HookInstance currentEditedHook;
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
public void EnableOrDisableHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
Panel.HooksScrollPool.Refresh(true, false);
}
public void DeleteHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
Panel.HooksScrollPool.Refresh(true, false);
}
public void EditPatchClicked(int index)
{
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
HookInstance hook = (HookInstance)currentHooks[index];
currentEditedHook = hook;
Panel.EditorInput.Text = hook.PatchSourceCode;
}
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
HookInstance hook = (HookInstance)this.currentHooks[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
public void OnClassSelectedForHooks(string typeFullName)
{
Type type = ReflectionUtility.GetTypeByName(typeFullName);
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
Panel.ResetMethodFilter();
filteredEligableMethods.Clear();
currentAddEligableMethods.Clear();
foreach (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod || UERuntimeHelper.IsBlacklisted(method))
continue;
currentAddEligableMethods.Add(method);
filteredEligableMethods.Add(method);
}
isAddingMethods = true;
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
Panel.AddHooksScrollPool.Refresh(true, true);
}
public void DoneAddingHooks()
{
isAddingMethods = false;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
Panel.HooksScrollPool.Refresh(true, false);
}
public void AddHookClicked(int index)
{
if (index >= this.filteredEligableMethods.Count)
return;
AddHook(filteredEligableMethods[index]);
Panel.AddHooksScrollPool.Refresh(true, false);
}
public void AddHook(MethodInfo method)
{
string sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
return;
HookInstance hook = new(method);
if (hook.Enabled)
{
hookedSignatures.Add(sig);
currentHooks.Add(sig, hook);
}
}
public void OnAddHookFilterInputChanged(string input)
{
filteredEligableMethods.Clear();
if (string.IsNullOrEmpty(input))
filteredEligableMethods.AddRange(currentAddEligableMethods);
else
{
foreach (MethodInfo method in currentAddEligableMethods)
{
if (method.Name.ContainsIgnoreCase(input))
filteredEligableMethods.Add(method);
}
}
Panel.AddHooksScrollPool.Refresh(true, true);
}
// Set eligable method cell
public void OnCellBorrowed(AddHookCell cell) { }
public void SetCell(AddHookCell cell, int index)
{
if (index >= this.filteredEligableMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
MethodInfo method = this.filteredEligableMethods[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(method);
string sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
{
cell.HookButton.Component.gameObject.SetActive(false);
cell.HookedLabel.gameObject.SetActive(true);
}
else
{
cell.HookButton.Component.gameObject.SetActive(true);
cell.HookedLabel.gameObject.SetActive(false);
}
}
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
public void OnEditorInputChanged(string value)
{
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
Panel.EditorInput.Component.caretPosition, out _);
}
public void EditorInputCancel()
{
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
public void EditorInputSave()
{
string input = Panel.EditorInput.Text;
bool wasEnabled = currentEditedHook.Enabled;
if (currentEditedHook.CompileAndGenerateProcessor(input))
{
if (wasEnabled)
currentEditedHook.Patch();
currentEditedHook.PatchSourceCode = input;
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
}
}
}

View File

@ -18,11 +18,11 @@ namespace UnityExplorer.Inspectors
{
public class GameObjectInspector : InspectorBase
{
public GameObject GOTarget => Target as GameObject;
public new GameObject Target => base.Target as GameObject;
public GameObject Content;
public GameObjectControls GOControls;
public GameObjectControls Controls;
public TransformTree TransformTree;
private ScrollPool<TransformCell> transformScroll;
@ -38,10 +38,10 @@ namespace UnityExplorer.Inspectors
{
base.OnBorrowedFromPool(target);
Target = target as GameObject;
base.Target = target as GameObject;
GOControls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true);
Controls.UpdateGameObjectInfo(true, true);
Controls.TransformControl.UpdateTransformControlValues(true);
RuntimeHelper.StartCoroutine(InitCoroutine());
}
@ -76,9 +76,9 @@ namespace UnityExplorer.Inspectors
public void OnTransformCellClicked(GameObject newTarget)
{
this.Target = newTarget;
GOControls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true);
base.Target = newTarget;
Controls.UpdateGameObjectInfo(true, true);
Controls.TransformControl.UpdateTransformControlValues(true);
TransformTree.RefreshData(true, false, true, false);
UpdateComponents();
}
@ -90,21 +90,21 @@ namespace UnityExplorer.Inspectors
if (!this.IsActive)
return;
if (Target.IsNullOrDestroyed(false))
if (base.Target.IsNullOrDestroyed(false))
{
InspectorManager.ReleaseInspector(this);
return;
}
GOControls.UpdateVectorSlider();
GOControls.UpdateTransformControlValues(false);
Controls.UpdateVectorSlider();
Controls.TransformControl.UpdateTransformControlValues(false);
// Slow update
if (timeOfLastUpdate.OccuredEarlierThan(1))
{
timeOfLastUpdate = Time.realtimeSinceStartup;
GOControls.UpdateGameObjectInfo(false, false);
Controls.UpdateGameObjectInfo(false, false);
TransformTree.RefreshData(true, false, false, false);
UpdateComponents();
@ -115,12 +115,12 @@ namespace UnityExplorer.Inspectors
private IEnumerable<GameObject> GetTransformEntries()
{
if (!GOTarget)
if (!Target)
return Enumerable.Empty<GameObject>();
cachedChildren.Clear();
for (int i = 0; i < GOTarget.transform.childCount; i++)
cachedChildren.Add(GOTarget.transform.GetChild(i).gameObject);
for (int i = 0; i < Target.transform.childCount; i++)
cachedChildren.Add(Target.transform.GetChild(i).gameObject);
return cachedChildren;
}
@ -130,11 +130,11 @@ namespace UnityExplorer.Inspectors
private readonly List<bool> behaviourEnabledStates = new();
// ComponentList.GetRootEntriesMethod
private List<Component> GetComponentEntries() => GOTarget ? componentEntries : Enumerable.Empty<Component>().ToList();
private List<Component> GetComponentEntries() => Target ? componentEntries : Enumerable.Empty<Component>().ToList();
public void UpdateComponents()
{
if (!GOTarget)
if (!Target)
{
componentEntries.Clear();
compInstanceIDs.Clear();
@ -146,8 +146,8 @@ namespace UnityExplorer.Inspectors
}
// Check if we actually need to refresh the component cells or not.
IEnumerable<Component> comps = GOTarget.GetComponents<Component>();
IEnumerable<Behaviour> behaviours = GOTarget.GetComponents<Behaviour>();
IEnumerable<Component> comps = Target.GetComponents<Component>();
IEnumerable<Behaviour> behaviours = Target.GetComponents<Behaviour>();
bool needRefresh = false;
@ -231,7 +231,7 @@ namespace UnityExplorer.Inspectors
private void OnAddChildClicked(string input)
{
GameObject newObject = new(input);
newObject.transform.parent = GOTarget.transform;
newObject.transform.parent = Target.transform;
TransformTree.RefreshData(true, false, true, false);
}
@ -242,7 +242,7 @@ namespace UnityExplorer.Inspectors
{
try
{
RuntimeHelper.AddComponent<Component>(GOTarget, type);
RuntimeHelper.AddComponent<Component>(Target, type);
UpdateComponents();
}
catch (Exception ex)
@ -270,7 +270,7 @@ namespace UnityExplorer.Inspectors
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2);
// Construct GO Controls
GOControls = new GameObjectControls(this);
Controls = new GameObjectControls(this);
ConstructLists();
@ -333,7 +333,7 @@ namespace UnityExplorer.Inspectors
addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); };
// comp autocompleter
new TypeCompleter(typeof(Component), addCompInput);
new TypeCompleter(typeof(Component), addCompInput, false, false, false);
// Component List

View File

@ -1,696 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.Input;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.Inspectors
{
public class GameObjectControls
{
public GameObjectInspector Parent;
private GameObject GOTarget => Parent.GOTarget;
// Top info
private ButtonRef ViewParentButton;
private InputFieldRef PathInput;
private InputFieldRef NameInput;
private Toggle ActiveSelfToggle;
private Text ActiveSelfText;
private Toggle IsStaticToggle;
private InputFieldRef SceneInput;
private InputFieldRef InstanceIDInput;
private InputFieldRef TagInput;
private Dropdown LayerDropdown;
private Dropdown FlagsDropdown;
// transform controls
private TransformControl PositionControl;
private TransformControl LocalPositionControl;
private TransformControl RotationControl;
private TransformControl ScaleControl;
private VectorSlider currentSlidingVectorControl;
private float currentVectorValue;
public GameObjectControls(GameObjectInspector parent)
{
this.Parent = parent;
ConstructTopInfo();
ConstructTransformControls();
}
private void OnCopyClicked()
{
ClipboardPanel.Copy(this.GOTarget);
}
#region GO Controls
private string lastGoName;
private string lastPath;
private bool lastParentState;
private int lastSceneHandle;
private string lastTag;
private int lastLayer;
private int lastFlags;
public void UpdateGameObjectInfo(bool firstUpdate, bool force)
{
if (firstUpdate)
{
InstanceIDInput.Text = GOTarget.GetInstanceID().ToString();
}
if (force || (!NameInput.Component.isFocused && GOTarget.name != lastGoName))
{
lastGoName = GOTarget.name;
Parent.Tab.TabText.text = $"[G] {GOTarget.name}";
NameInput.Text = GOTarget.name;
}
if (force || !PathInput.Component.isFocused)
{
string path = GOTarget.transform.GetTransformPath();
if (path != lastPath)
{
lastPath = path;
PathInput.Text = path;
}
}
if (force || GOTarget.transform.parent != lastParentState)
{
lastParentState = GOTarget.transform.parent;
ViewParentButton.Component.interactable = lastParentState;
if (lastParentState)
{
ViewParentButton.ButtonText.color = Color.white;
ViewParentButton.ButtonText.text = "◄ View Parent";
}
else
{
ViewParentButton.ButtonText.color = Color.grey;
ViewParentButton.ButtonText.text = "No parent";
}
}
if (force || GOTarget.activeSelf != ActiveSelfToggle.isOn)
{
ActiveSelfToggle.Set(GOTarget.activeSelf, false);
ActiveSelfText.color = ActiveSelfToggle.isOn ? Color.green : Color.red;
}
if (force || GOTarget.isStatic != IsStaticToggle.isOn)
{
IsStaticToggle.Set(GOTarget.isStatic, false);
}
if (force || GOTarget.scene.handle != lastSceneHandle)
{
lastSceneHandle = GOTarget.scene.handle;
SceneInput.Text = GOTarget.scene.IsValid() ? GOTarget.scene.name : "None (Asset/Resource)";
}
if (force || (!TagInput.Component.isFocused && GOTarget.tag != lastTag))
{
lastTag = GOTarget.tag;
TagInput.Text = lastTag;
}
if (force || (GOTarget.layer != lastLayer))
{
lastLayer = GOTarget.layer;
LayerDropdown.value = GOTarget.layer;
}
if (force || ((int)GOTarget.hideFlags != lastFlags))
{
lastFlags = (int)GOTarget.hideFlags;
FlagsDropdown.captionText.text = GOTarget.hideFlags.ToString();
}
}
private void OnViewParentClicked()
{
if (this.GOTarget && this.GOTarget.transform.parent)
{
Parent.OnTransformCellClicked(this.GOTarget.transform.parent.gameObject);
}
}
private void OnPathEndEdit(string input)
{
lastPath = input;
if (string.IsNullOrEmpty(input))
{
DoSetParent(null);
}
else
{
Transform parentToSet = null;
if (input.EndsWith("/"))
input = input.Remove(input.Length - 1);
// try the easy way
if (GameObject.Find(input) is GameObject found)
{
parentToSet = found.transform;
}
else
{
// look for inactive objects
string name = input.Split('/').Last();
UnityEngine.Object[] allObjects = RuntimeHelper.FindObjectsOfTypeAll(typeof(GameObject));
List<GameObject> shortList = new();
foreach (UnityEngine.Object obj in allObjects)
if (obj.name == name) shortList.Add(obj.TryCast<GameObject>());
foreach (GameObject go in shortList)
{
string path = go.transform.GetTransformPath(true);
if (path.EndsWith("/"))
path = path.Remove(path.Length - 1);
if (path == input)
{
parentToSet = go.transform;
break;
}
}
}
if (parentToSet)
DoSetParent(parentToSet);
else
{
ExplorerCore.LogWarning($"Could not find any GameObject name or path '{input}'!");
UpdateGameObjectInfo(false, true);
}
}
}
private void DoSetParent(Transform transform)
{
ExplorerCore.Log($"Setting target's transform parent to: {(transform == null ? "null" : $"'{transform.name}'")}");
if (GOTarget.GetComponent<RectTransform>())
GOTarget.transform.SetParent(transform, false);
else
GOTarget.transform.parent = transform;
UpdateGameObjectInfo(false, false);
UpdateTransformControlValues(false);
}
private void OnNameEndEdit(string value)
{
GOTarget.name = value;
UpdateGameObjectInfo(false, true);
}
private void OnActiveSelfToggled(bool value)
{
GOTarget.SetActive(value);
UpdateGameObjectInfo(false, true);
}
private void OnTagEndEdit(string value)
{
try
{
GOTarget.tag = value;
UpdateGameObjectInfo(false, true);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting tag! {ex.ReflectionExToString()}");
}
}
private void OnExploreButtonClicked()
{
ObjectExplorerPanel panel = UIManager.GetPanel<UI.Panels.ObjectExplorerPanel>(UIManager.Panels.ObjectExplorer);
panel.SceneExplorer.JumpToTransform(this.Parent.GOTarget.transform);
}
private void OnLayerDropdownChanged(int value)
{
GOTarget.layer = value;
UpdateGameObjectInfo(false, true);
}
private void OnFlagsDropdownChanged(int value)
{
try
{
HideFlags enumVal = hideFlagsValues[FlagsDropdown.options[value].text];
GOTarget.hideFlags = enumVal;
UpdateGameObjectInfo(false, true);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting hideFlags: {ex}");
}
}
private void OnDestroyClicked()
{
GameObject.Destroy(this.GOTarget);
InspectorManager.ReleaseInspector(Parent);
}
private void OnInstantiateClicked()
{
GameObject clone = GameObject.Instantiate(this.GOTarget);
InspectorManager.Inspect(clone);
}
#endregion
#region Transform Controls
private enum TransformType { Position, LocalPosition, Rotation, Scale }
private class TransformControl
{
public TransformType Type;
public InputFieldRef Input;
public TransformControl(TransformType type, InputFieldRef input)
{
this.Type = type;
this.Input = input;
}
}
private class VectorSlider
{
public int axis;
public Slider slider;
public TransformControl parentControl;
public VectorSlider(int axis, Slider slider, TransformControl parentControl)
{
this.axis = axis;
this.slider = slider;
this.parentControl = parentControl;
}
}
private Vector3 lastPosValue;
private Vector3 lastLocalValue;
private Quaternion lastRotValue;
private Vector3 lastScaleValue;
public void UpdateTransformControlValues(bool force)
{
Transform transform = GOTarget.transform;
if (force || (!PositionControl.Input.Component.isFocused && lastPosValue != transform.position))
{
PositionControl.Input.Text = ParseUtility.ToStringForInput(transform.position, typeof(Vector3));
lastPosValue = transform.position;
}
if (force || (!LocalPositionControl.Input.Component.isFocused && lastLocalValue != transform.localPosition))
{
LocalPositionControl.Input.Text = ParseUtility.ToStringForInput(transform.localPosition, typeof(Vector3));
lastLocalValue = transform.localPosition;
}
if (force || (!RotationControl.Input.Component.isFocused && lastRotValue != transform.localRotation))
{
RotationControl.Input.Text = ParseUtility.ToStringForInput(transform.localRotation, typeof(Quaternion));
lastRotValue = transform.localRotation;
}
if (force || (!ScaleControl.Input.Component.isFocused && lastScaleValue != transform.localScale))
{
ScaleControl.Input.Text = ParseUtility.ToStringForInput(transform.localScale, typeof(Vector3));
lastScaleValue = transform.localScale;
}
}
private void OnTransformInputEndEdit(TransformType type, string input)
{
switch (type)
{
case TransformType.Position:
{
if (ParseUtility.TryParse(input, typeof(Vector3), out object boxed, out _))
GOTarget.transform.position = (Vector3)boxed;
}
break;
case TransformType.LocalPosition:
{
if (ParseUtility.TryParse(input, typeof(Vector3), out object boxed, out _))
GOTarget.transform.localPosition = (Vector3)boxed;
}
break;
case TransformType.Rotation:
{
if (ParseUtility.TryParse(input, typeof(Quaternion), out object boxed, out _))
GOTarget.transform.localRotation = (Quaternion)boxed;
}
break;
case TransformType.Scale:
{
if (ParseUtility.TryParse(input, typeof(Vector3), out object boxed, out _))
GOTarget.transform.localScale = (Vector3)boxed;
}
break;
}
UpdateTransformControlValues(true);
}
private void OnVectorSliderChanged(VectorSlider slider, float value)
{
if (value == 0f)
{
currentSlidingVectorControl = null;
}
else
{
currentSlidingVectorControl = slider;
currentVectorValue = value;
}
}
public void UpdateVectorSlider()
{
if (currentSlidingVectorControl == null)
return;
if (!InputManager.GetMouseButton(0))
{
currentSlidingVectorControl.slider.value = 0f;
currentSlidingVectorControl = null;
currentVectorValue = 0f;
return;
}
Transform transform = GOTarget.transform;
Vector3 vector = Vector2.zero;
switch (currentSlidingVectorControl.parentControl.Type)
{
case TransformType.Position:
vector = transform.position; break;
case TransformType.LocalPosition:
vector = transform.localPosition; break;
case TransformType.Rotation:
vector = transform.eulerAngles; break;
case TransformType.Scale:
vector = transform.localScale; break;
}
// apply vector value change
switch (currentSlidingVectorControl.axis)
{
case 0:
vector.x += currentVectorValue; break;
case 1:
vector.y += currentVectorValue; break;
case 2:
vector.z += currentVectorValue; break;
}
// set vector back to transform
switch (currentSlidingVectorControl.parentControl.Type)
{
case TransformType.Position:
transform.position = vector; break;
case TransformType.LocalPosition:
transform.localPosition = vector; break;
case TransformType.Rotation:
transform.eulerAngles = vector; break;
case TransformType.Scale:
transform.localScale = vector; break;
}
UpdateTransformControlValues(false);
}
#endregion
#region GO Controls UI Construction
private void ConstructTopInfo()
{
GameObject topInfoHolder = UIFactory.CreateVerticalGroup(Parent.Content, "TopInfoHolder", false, false, true, true, 3,
new Vector4(3, 3, 3, 3), new Color(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(topInfoHolder, minHeight: 100, flexibleWidth: 9999);
topInfoHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// first row (parent, path)
GameObject firstRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(firstRow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(firstRow, minHeight: 25, flexibleWidth: 9999);
ViewParentButton = UIFactory.CreateButton(firstRow, "ViewParentButton", "◄ View Parent", new Color(0.2f, 0.2f, 0.2f));
ViewParentButton.ButtonText.fontSize = 13;
UIFactory.SetLayoutElement(ViewParentButton.Component.gameObject, minHeight: 25, minWidth: 100);
ViewParentButton.OnClick += OnViewParentClicked;
this.PathInput = UIFactory.CreateInputField(firstRow, "PathInput", "...");
PathInput.Component.textComponent.color = Color.grey;
PathInput.Component.textComponent.fontSize = 14;
UIFactory.SetLayoutElement(PathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
PathInput.Component.lineType = InputField.LineType.MultiLineSubmit;
ButtonRef copyButton = UIFactory.CreateButton(firstRow, "CopyButton", "Copy to Clipboard", new Color(0.2f, 0.2f, 0.2f, 1));
copyButton.ButtonText.color = Color.yellow;
UIFactory.SetLayoutElement(copyButton.Component.gameObject, minHeight: 25, minWidth: 120);
copyButton.OnClick += OnCopyClicked;
//var pathApplyBtn = UIFactory.CreateButton(firstRow, "PathButton", "Set Parent Path", new Color(0.2f, 0.2f, 0.2f));
//UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120);
//pathApplyBtn.OnClick += () => { OnPathEndEdit(PathInput.Text); };
PathInput.Component.GetOnEndEdit().AddListener((string val) => { OnPathEndEdit(val); });
// Title and update row
GameObject titleRow = UIFactory.CreateUIObject("TitleRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(titleRow, false, false, true, true, 5);
Text titleLabel = UIFactory.CreateLabel(titleRow, "Title", SignatureHighlighter.Parse(typeof(GameObject), false),
TextAnchor.MiddleLeft, fontSize: 17);
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 30, minWidth: 100);
// name
NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled");
UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
NameInput.Component.textComponent.fontSize = 15;
NameInput.Component.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
// second row (toggles, instanceID, tag, buttons)
GameObject secondRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(secondRow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(secondRow, minHeight: 25, flexibleWidth: 9999);
// activeSelf
GameObject activeToggleObj = UIFactory.CreateToggle(secondRow, "ActiveSelf", out ActiveSelfToggle, out ActiveSelfText);
UIFactory.SetLayoutElement(activeToggleObj, minHeight: 25, minWidth: 100);
ActiveSelfText.text = "ActiveSelf";
ActiveSelfToggle.onValueChanged.AddListener(OnActiveSelfToggled);
// isStatic
GameObject isStaticObj = UIFactory.CreateToggle(secondRow, "IsStatic", out IsStaticToggle, out Text staticText);
UIFactory.SetLayoutElement(isStaticObj, minHeight: 25, minWidth: 80);
staticText.text = "IsStatic";
staticText.color = Color.grey;
IsStaticToggle.interactable = false;
// InstanceID
Text instanceIdLabel = UIFactory.CreateLabel(secondRow, "InstanceIDLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(instanceIdLabel.gameObject, minHeight: 25, minWidth: 90);
InstanceIDInput = UIFactory.CreateInputField(secondRow, "InstanceIDInput", "error");
UIFactory.SetLayoutElement(InstanceIDInput.Component.gameObject, minHeight: 25, minWidth: 110);
InstanceIDInput.Component.textComponent.color = Color.grey;
InstanceIDInput.Component.readOnly = true;
//Tag
Text tagLabel = UIFactory.CreateLabel(secondRow, "TagLabel", "Tag:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(tagLabel.gameObject, minHeight: 25, minWidth: 40);
TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none");
UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
TagInput.Component.textComponent.color = Color.white;
TagInput.Component.GetOnEndEdit().AddListener((string val) => { OnTagEndEdit(val); });
// Instantiate
ButtonRef instantiateBtn = UIFactory.CreateButton(secondRow, "InstantiateBtn", "Instantiate", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(instantiateBtn.Component.gameObject, minHeight: 25, minWidth: 120);
instantiateBtn.OnClick += OnInstantiateClicked;
// Destroy
ButtonRef destroyBtn = UIFactory.CreateButton(secondRow, "DestroyBtn", "Destroy", new Color(0.3f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(destroyBtn.Component.gameObject, minHeight: 25, minWidth: 80);
destroyBtn.OnClick += OnDestroyClicked;
// third row (scene, layer, flags)
GameObject thirdrow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(thirdrow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(thirdrow, minHeight: 25, flexibleWidth: 9999);
// Inspect in Explorer button
ButtonRef explorerBtn = UIFactory.CreateButton(thirdrow, "ExploreBtn", "Show in Explorer", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(explorerBtn.Component.gameObject, minHeight: 25, minWidth: 100);
explorerBtn.ButtonText.fontSize = 12;
explorerBtn.OnClick += OnExploreButtonClicked;
// Scene
Text sceneLabel = UIFactory.CreateLabel(thirdrow, "SceneLabel", "Scene:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(sceneLabel.gameObject, minHeight: 25, minWidth: 50);
SceneInput = UIFactory.CreateInputField(thirdrow, "SceneInput", "untitled");
UIFactory.SetLayoutElement(SceneInput.Component.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 999);
SceneInput.Component.readOnly = true;
SceneInput.Component.textComponent.color = new Color(0.7f, 0.7f, 0.7f);
// Layer
Text layerLabel = UIFactory.CreateLabel(thirdrow, "LayerLabel", "Layer:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(layerLabel.gameObject, minHeight: 25, minWidth: 50);
GameObject layerDrop = UIFactory.CreateDropdown(thirdrow, "LayerDropdown", out LayerDropdown, "0", 14, OnLayerDropdownChanged);
UIFactory.SetLayoutElement(layerDrop, minHeight: 25, minWidth: 110, flexibleWidth: 999);
LayerDropdown.captionText.color = SignatureHighlighter.EnumGreen;
if (layerToNames == null)
GetLayerNames();
foreach (string name in layerToNames)
LayerDropdown.options.Add(new Dropdown.OptionData(name));
LayerDropdown.value = 0;
LayerDropdown.RefreshShownValue();
// Flags
Text flagsLabel = UIFactory.CreateLabel(thirdrow, "FlagsLabel", "Flags:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(flagsLabel.gameObject, minHeight: 25, minWidth: 50);
GameObject flagsDrop = UIFactory.CreateDropdown(thirdrow, "FlagsDropdown", out FlagsDropdown, "None", 14, OnFlagsDropdownChanged);
FlagsDropdown.captionText.color = SignatureHighlighter.EnumGreen;
UIFactory.SetLayoutElement(flagsDrop, minHeight: 25, minWidth: 135, flexibleWidth: 999);
if (hideFlagsValues == null)
GetHideFlagNames();
foreach (string name in hideFlagsValues.Keys)
FlagsDropdown.options.Add(new Dropdown.OptionData(name));
FlagsDropdown.value = 0;
FlagsDropdown.RefreshShownValue();
}
private static List<string> layerToNames;
private static void GetLayerNames()
{
layerToNames = new List<string>();
for (int i = 0; i < 32; i++)
{
string name = RuntimeHelper.LayerToName(i);
if (string.IsNullOrEmpty(name))
name = i.ToString();
layerToNames.Add(name);
}
}
private static Dictionary<string, HideFlags> hideFlagsValues;
private static void GetHideFlagNames()
{
hideFlagsValues = new Dictionary<string, HideFlags>();
Array names = Enum.GetValues(typeof(HideFlags));
foreach (HideFlags value in names)
{
hideFlagsValues.Add(value.ToString(), value);
}
}
#endregion
#region Transform Controls UI Construction
private void ConstructTransformControls()
{
GameObject transformGroup = UIFactory.CreateVerticalGroup(Parent.Content, "TransformControls", false, false, true, true, 2,
new Vector4(2, 2, 0, 0), new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(transformGroup, minHeight: 100, flexibleWidth: 9999);
//transformGroup.SetActive(false);
//var groupRect = transformGroup.GetComponent<RectTransform>();
//groupRect.anchorMin = new Vector2(0, 1);
//groupRect.anchorMax = new Vector2(1, 1);
//groupRect.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, 100);
PositionControl = AddTransformRow(transformGroup, "Position:", TransformType.Position);
LocalPositionControl = AddTransformRow(transformGroup, "Local Position:", TransformType.LocalPosition);
RotationControl = AddTransformRow(transformGroup, "Rotation:", TransformType.Rotation);
ScaleControl = AddTransformRow(transformGroup, "Scale:", TransformType.Scale);
}
private TransformControl AddTransformRow(GameObject transformGroup, string title, TransformType type)
{
GameObject rowObj = UIFactory.CreateUIObject("Row_" + title, transformGroup);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999);
Text titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110);
InputFieldRef inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
inputField.Component.GetOnEndEdit().AddListener((string value) => { OnTransformInputEndEdit(type, value); });
TransformControl control = new(type, inputField);
AddVectorAxisSlider(rowObj, "X", 0, control);
AddVectorAxisSlider(rowObj, "Y", 1, control);
AddVectorAxisSlider(rowObj, "Z", 2, control);
return control;
}
private VectorSlider AddVectorAxisSlider(GameObject parent, string title, int axis, TransformControl control)
{
Text label = UIFactory.CreateLabel(parent, "Label_" + title, title + ":", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30);
GameObject sliderObj = UIFactory.CreateSlider(parent, "Slider_" + title, out Slider slider);
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 120, flexibleWidth: 0);
slider.m_FillImage.color = Color.clear;
slider.minValue = -1;
slider.maxValue = 1;
VectorSlider sliderControl = new(axis, slider, control);
slider.onValueChanged.AddListener((float val) =>
{
OnVectorSliderChanged(sliderControl, val);
});
return sliderControl;
}
#endregion
}
}

View File

@ -1,6 +1,8 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.Inspectors
@ -9,6 +11,7 @@ namespace UnityExplorer.Inspectors
{
public bool IsActive { get; internal set; }
public object Target { get; set; }
public Type TargetType { get; protected set; }
public InspectorTab Tab { get; internal set; }
@ -24,6 +27,8 @@ namespace UnityExplorer.Inspectors
public virtual void OnBorrowedFromPool(object target)
{
this.Target = target;
this.TargetType = target is Type type ? type : target.GetActualType();
Tab = Pool<InspectorTab>.Borrow();
Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false);

View File

@ -23,7 +23,7 @@ namespace UnityExplorer
public static event Action OnInspectedTabsChanged;
public static void Inspect(object obj, CacheObjectBase sourceCache = null)
public static void Inspect(object obj, CacheObjectBase parent = null)
{
if (obj.IsNullOrDestroyed())
return;
@ -36,19 +36,34 @@ namespace UnityExplorer
if (obj is GameObject)
CreateInspector<GameObjectInspector>(obj);
else
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
CreateInspector<ReflectionInspector>(obj, false, parent);
}
public static void Inspect(Type type)
{
if (TryFocusActiveInspector(type))
return;
CreateInspector<ReflectionInspector>(type, true);
}
private static bool TryFocusActiveInspector(object target)
static bool TryFocusActiveInspector(object target)
{
foreach (InspectorBase inspector in Inspectors)
{
if (inspector.Target.ReferenceEqual(target))
bool shouldFocus = false;
if (target is Type targetAsType)
{
if (inspector.TargetType.FullName == targetAsType.FullName)
shouldFocus = true;
}
else if(inspector.Target.ReferenceEqual(target))
{
shouldFocus = true;
}
if (shouldFocus)
{
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
SetInspectorActive(inspector);
@ -76,7 +91,7 @@ namespace UnityExplorer
}
}
internal static void CloseAllTabs()
public static void CloseAllTabs()
{
if (Inspectors.Any())
{
@ -89,18 +104,17 @@ namespace UnityExplorer
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
}
private static void CreateInspector<T>(object target, bool staticReflection = false,
CacheObjectBase parentObject = null) where T : InspectorBase
static void CreateInspector<T>(object target, bool staticReflection = false, CacheObjectBase parent = null) where T : InspectorBase
{
T inspector = Pool<T>.Borrow();
Inspectors.Add(inspector);
inspector.Target = target;
if (parentObject != null && parentObject.CanWrite)
if (parent != null && parent.CanWrite)
{
// only set parent cache object if we are inspecting a struct, otherwise there is no point.
if (target.GetType().IsValueType && inspector is ReflectionInspector ri)
ri.ParentCacheObject = parentObject;
ri.ParentCacheObject = parent;
}
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
@ -115,7 +129,7 @@ namespace UnityExplorer
OnInspectedTabsChanged?.Invoke();
}
internal static void ReleaseInspector<T>(T inspector) where T : InspectorBase
public static void ReleaseInspector<T>(T inspector) where T : InspectorBase
{
if (lastActiveInspector == inspector)
lastActiveInspector = null;

View File

@ -113,17 +113,11 @@ namespace UnityExplorer.Inspectors
public bool TryUpdate()
{
if (ConfigManager.World_MouseInspect_Keybind.Value != KeyCode.None)
{
if (InputManager.GetKeyDown(ConfigManager.World_MouseInspect_Keybind.Value))
Instance.StartInspect(MouseInspectMode.World);
}
if (InputManager.GetKeyDown(ConfigManager.World_MouseInspect_Keybind.Value))
Instance.StartInspect(MouseInspectMode.World);
if (ConfigManager.World_MouseInspect_Keybind.Value != KeyCode.None)
{
if (InputManager.GetKeyDown(ConfigManager.World_MouseInspect_Keybind.Value))
Instance.StartInspect(MouseInspectMode.World);
}
if (InputManager.GetKeyDown(ConfigManager.UI_MouseInspect_Keybind.Value))
Instance.StartInspect(MouseInspectMode.UI);
if (Inspecting)
UpdateInspect();

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
@ -8,6 +9,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.Config;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib;
@ -33,54 +36,53 @@ namespace UnityExplorer.Inspectors
public class ReflectionInspector : InspectorBase, ICellPoolDataSource<CacheMemberCell>, ICacheObjectController
{
public CacheObjectBase ParentCacheObject { get; set; }
public Type TargetType { get; private set; }
public bool StaticOnly { get; internal set; }
public bool CanWrite => true;
public bool AutoUpdateWanted => autoUpdateToggle.isOn;
private List<CacheMember> members = new();
private readonly List<CacheMember> filteredMembers = new();
List<CacheMember> members = new();
readonly List<CacheMember> filteredMembers = new();
private BindingFlags scopeFlagsFilter;
private string nameFilter;
private MemberFilter MemberFilter = MemberFilter.All;
string nameFilter;
BindingFlags scopeFlagsFilter;
MemberFilter memberFilter = MemberFilter.All;
// Updating
private bool refreshWanted;
private string lastNameFilter;
private BindingFlags lastFlagsFilter;
private MemberFilter lastMemberFilter = MemberFilter.All;
private float timeOfLastAutoUpdate;
bool refreshWanted;
string lastNameFilter;
BindingFlags lastFlagsFilter;
MemberFilter lastMemberFilter = MemberFilter.All;
float timeOfLastAutoUpdate;
// UI
internal GameObject mainContentHolder;
private static int LeftGroupWidth { get; set; }
private static int RightGroupWidth { get; set; }
static int LeftGroupWidth { get; set; }
static int RightGroupWidth { get; set; }
static readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
static readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
public GameObject ContentRoot { get; private set; }
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
public int ItemCount => filteredMembers.Count;
public UnityObjectWidget UnityWidget { get; private set; }
public string TabButtonText { get; set; }
public UnityObjectWidget UnityWidget;
InputFieldRef hiddenNameText;
Text nameText;
Text assemblyText;
Toggle autoUpdateToggle;
public InputFieldRef HiddenNameText;
public Text NameText;
public Text AssemblyText;
private Toggle autoUpdateToggle;
ButtonRef dnSpyButton;
internal string currentBaseTabText;
ButtonRef makeGenericButton;
GenericConstructorWidget genericConstructor;
private readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
private readonly List<Toggle> memberTypeToggles = new();
private InputFieldRef filterInputField;
// const
private readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
private readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
InputFieldRef filterInputField;
readonly List<Toggle> memberTypeToggles = new();
readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
// Setup
@ -125,6 +127,8 @@ namespace UnityExplorer.Inspectors
this.UnityWidget = null;
}
genericConstructor?.Cancel();
base.OnReturnToPool();
}
@ -138,6 +142,8 @@ namespace UnityExplorer.Inspectors
Target = null;
TargetType = target as Type;
prefix = "[S]";
makeGenericButton.GameObject.SetActive(TargetType.IsGenericTypeDefinition);
}
else
{
@ -146,17 +152,23 @@ namespace UnityExplorer.Inspectors
}
// Setup main labels and tab text
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
Tab.TabText.text = currentBaseTabText;
NameText.text = SignatureHighlighter.Parse(TargetType, true);
HiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(NameText.text);
TabButtonText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
Tab.TabText.text = TabButtonText;
nameText.text = SignatureHighlighter.Parse(TargetType, true);
hiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(nameText.text);
string asmText;
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
{
asmText = $"{TargetType.Assembly.GetName().Name} <color=grey><i>(in memory)</i></color>";
dnSpyButton.GameObject.SetActive(false);
}
else
{
asmText = Path.GetFileName(TargetType.Assembly.Location);
AssemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
dnSpyButton.GameObject.SetActive(true);
}
assemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
// Unity object helper widget
@ -195,11 +207,11 @@ namespace UnityExplorer.Inspectors
}
// check filter changes or force-refresh
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter)
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != memberFilter)
{
lastNameFilter = nameFilter;
lastFlagsFilter = scopeFlagsFilter;
lastMemberFilter = MemberFilter;
lastMemberFilter = memberFilter;
FilterMembers();
MemberScrollPool.Refresh(true, true);
@ -219,17 +231,8 @@ namespace UnityExplorer.Inspectors
}
}
public void UpdateClicked()
{
UpdateDisplayedMembers();
}
// Filtering
public void SetFilter(string name) => SetFilter(name, scopeFlagsFilter);
public void SetFilter(BindingFlags flags) => SetFilter(nameFilter, flags);
public void SetFilter(string name, BindingFlags flags)
{
this.nameFilter = name;
@ -245,15 +248,7 @@ namespace UnityExplorer.Inspectors
}
}
private void OnMemberTypeToggled(MemberFilter flag, bool val)
{
if (!val)
MemberFilter &= ~flag;
else
MemberFilter |= flag;
}
private void FilterMembers()
void FilterMembers()
{
filteredMembers.Clear();
@ -268,10 +263,10 @@ namespace UnityExplorer.Inspectors
continue;
}
if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFilter.Method))
|| (member is CacheField && !MemberFilter.HasFlag(MemberFilter.Field))
|| (member is CacheProperty && !MemberFilter.HasFlag(MemberFilter.Property))
|| (member is CacheConstructor && !MemberFilter.HasFlag(MemberFilter.Constructor)))
if ((member is CacheMethod && !memberFilter.HasFlag(MemberFilter.Method))
|| (member is CacheField && !memberFilter.HasFlag(MemberFilter.Field))
|| (member is CacheProperty && !memberFilter.HasFlag(MemberFilter.Property))
|| (member is CacheConstructor && !memberFilter.HasFlag(MemberFilter.Constructor)))
continue;
if (!string.IsNullOrEmpty(nameFilter) && !member.NameForFiltering.ContainsIgnoreCase(nameFilter))
@ -281,7 +276,7 @@ namespace UnityExplorer.Inspectors
}
}
private void UpdateDisplayedMembers()
void UpdateDisplayedMembers()
{
bool shouldRefresh = false;
foreach (CacheMemberCell cell in MemberScrollPool.CellPool)
@ -320,13 +315,13 @@ namespace UnityExplorer.Inspectors
SetCellLayout(cell);
}
private void CalculateLayouts()
void CalculateLayouts()
{
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);
RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65);
}
private void SetCellLayout(CacheObjectCell cell)
void SetCellLayout(CacheObjectCell cell)
{
cell.NameLayout.minWidth = LeftGroupWidth;
cell.RightGroupLayout.minWidth = RightGroupWidth;
@ -335,11 +330,85 @@ namespace UnityExplorer.Inspectors
cell.Occupant.IValue.SetLayout();
}
private void OnCopyClicked()
// UI listeners
void OnUpdateClicked()
{
UpdateDisplayedMembers();
}
public void OnSetNameFilter(string name)
{
SetFilter(name, scopeFlagsFilter);
}
public void OnSetFlags(BindingFlags flags)
{
SetFilter(nameFilter, flags);
}
void OnMemberTypeToggled(MemberFilter flag, bool val)
{
if (!val)
memberFilter &= ~flag;
else
memberFilter |= flag;
}
void OnCopyClicked()
{
ClipboardPanel.Copy(this.Target ?? this.TargetType);
}
void OnDnSpyButtonClicked()
{
string path = ConfigManager.DnSpy_Path.Value;
if (File.Exists(path) && path.EndsWith("dnspy.exe", StringComparison.OrdinalIgnoreCase))
{
Type type = TargetType;
// if constructed generic type, use the generic type definition
if (type.IsGenericType && !type.IsGenericTypeDefinition)
type = type.GetGenericTypeDefinition();
string args = $"\"{type.Assembly.Location}\" --select T:{type.FullName}";
Process.Start(path, args);
}
else
{
Notification.ShowMessage($"Please set a valid dnSpy path in UnityExplorer Settings.");
}
}
void OnMakeGenericClicked()
{
ContentRoot.SetActive(false);
if (genericConstructor == null)
{
genericConstructor = new();
genericConstructor.ConstructUI(UIRoot);
}
genericConstructor.UIRoot.SetActive(true);
genericConstructor.Show(OnGenericSubmit, OnGenericCancel, TargetType);
}
void OnGenericSubmit(Type[] args)
{
ContentRoot.SetActive(true);
genericConstructor.UIRoot.SetActive(false);
Type newType = TargetType.MakeGenericType(args);
InspectorManager.Inspect(newType);
//InspectorManager.ReleaseInspector(this);
}
void OnGenericCancel()
{
ContentRoot.SetActive(true);
genericConstructor.UIRoot.SetActive(false);
}
// UI Construction
public override GameObject CreateContent(GameObject parent)
@ -349,51 +418,68 @@ namespace UnityExplorer.Inspectors
// Class name, assembly
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default, new(1, 1, 1, 0), TextAnchor.MiddleLeft);
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default,
new(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(topRow, minHeight: 25, flexibleWidth: 9999);
GameObject titleHolder = UIFactory.CreateUIObject("TitleHolder", topRow);
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
RectTransform namerect = NameText.GetComponent<RectTransform>();
nameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
RectTransform namerect = nameText.GetComponent<RectTransform>();
namerect.anchorMin = new Vector2(0, 0);
namerect.anchorMax = new Vector2(1, 1);
NameText.fontSize = 17;
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
nameText.fontSize = 17;
UIFactory.SetLayoutElement(nameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
RectTransform hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
hiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
RectTransform hiddenrect = hiddenNameText.Component.gameObject.GetComponent<RectTransform>();
hiddenrect.anchorMin = new Vector2(0, 0);
hiddenrect.anchorMax = new Vector2(1, 1);
HiddenNameText.Component.readOnly = true;
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
HiddenNameText.Component.textComponent.fontSize = 17;
HiddenNameText.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
hiddenNameText.Component.readOnly = true;
hiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
hiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
hiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
hiddenNameText.Component.textComponent.fontSize = 17;
hiddenNameText.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(hiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
makeGenericButton = UIFactory.CreateButton(topRow, "MakeGenericButton", "Construct Generic", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(makeGenericButton.GameObject, minWidth: 140, minHeight: 25);
makeGenericButton.OnClick += OnMakeGenericClicked;
makeGenericButton.GameObject.SetActive(false);
ButtonRef copyButton = UIFactory.CreateButton(topRow, "CopyButton", "Copy to Clipboard", new Color(0.2f, 0.2f, 0.2f, 1));
copyButton.ButtonText.color = Color.yellow;
UIFactory.SetLayoutElement(copyButton.Component.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 0);
copyButton.OnClick += OnCopyClicked;
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
// Assembly row
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
GameObject asmRow = UIFactory.CreateHorizontalGroup(UIRoot, "AssemblyRow", false, false, true, true, 5, default, new(1, 1, 1, 0));
UIFactory.SetLayoutElement(asmRow, flexibleWidth: 9999, minHeight: 25);
assemblyText = UIFactory.CreateLabel(asmRow, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(assemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
dnSpyButton = UIFactory.CreateButton(asmRow, "DnSpyButton", "View in dnSpy");
UIFactory.SetLayoutElement(dnSpyButton.GameObject, minWidth: 120, minHeight: 25);
dnSpyButton.OnClick += OnDnSpyButtonClicked;
// Content
ContentRoot = UIFactory.CreateVerticalGroup(UIRoot, "ContentRoot", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
new Color(0.12f, 0.12f, 0.12f));
UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutElement(ContentRoot, flexibleWidth: 9999, flexibleHeight: 9999);
ConstructFirstRow(mainContentHolder);
ConstructFirstRow(ContentRoot);
ConstructSecondRow(mainContentHolder);
ConstructSecondRow(ContentRoot);
// Member scroll pool
GameObject memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2, 2, 2, 2),
bgColor: new Color(0.05f, 0.05f, 0.05f));
GameObject memberBorder = UIFactory.CreateVerticalGroup(ContentRoot, "ScrollPoolHolder", false, false, true, true,
padding: new Vector4(2, 2, 2, 2), bgColor: new Color(0.05f, 0.05f, 0.05f));
UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999);
MemberScrollPool = UIFactory.CreateScrollPool<CacheMemberCell>(memberBorder, "MemberList", out GameObject scrollObj,
@ -411,7 +497,7 @@ namespace UnityExplorer.Inspectors
// First row
private void ConstructFirstRow(GameObject parent)
void ConstructFirstRow(GameObject parent)
{
GameObject rowObj = UIFactory.CreateUIObject("FirstRow", parent);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, true, true, true, true, 5, 2, 2, 2, 2);
@ -422,7 +508,7 @@ namespace UnityExplorer.Inspectors
filterInputField = UIFactory.CreateInputField(rowObj, "NameFilterInput", "...");
UIFactory.SetLayoutElement(filterInputField.UIRoot, minHeight: 25, flexibleWidth: 300);
filterInputField.OnValueChanged += (string val) => { SetFilter(val); };
filterInputField.OnValueChanged += (string val) => { OnSetNameFilter(val); };
GameObject spacer = UIFactory.CreateUIObject("Spacer", rowObj);
UIFactory.SetLayoutElement(spacer, minWidth: 25);
@ -431,7 +517,7 @@ namespace UnityExplorer.Inspectors
ButtonRef updateButton = UIFactory.CreateButton(rowObj, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f));
UIFactory.SetLayoutElement(updateButton.Component.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0);
updateButton.OnClick += UpdateClicked;
updateButton.OnClick += OnUpdateClicked;
GameObject toggleObj = UIFactory.CreateToggle(rowObj, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText);
UIFactory.SetLayoutElement(toggleObj, minWidth: 125, minHeight: 25);
@ -441,7 +527,7 @@ namespace UnityExplorer.Inspectors
// Second row
private void ConstructSecondRow(GameObject parent)
void ConstructSecondRow(GameObject parent)
{
GameObject rowObj = UIFactory.CreateUIObject("SecondRow", parent);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 2, 2, 2, 2);
@ -466,7 +552,7 @@ namespace UnityExplorer.Inspectors
AddMemberTypeToggle(rowObj, MemberTypes.Constructor, 110);
}
private void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
{
string lbl = flags == BindingFlags.Default ? "All" : flags.ToString();
Color color = setAsActive ? enabledButtonColor : disabledButtonColor;
@ -475,10 +561,10 @@ namespace UnityExplorer.Inspectors
UIFactory.SetLayoutElement(button.Component.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 70, flexibleWidth: 0);
scopeFilterButtons.Add(flags, button);
button.OnClick += () => { SetFilter(flags); };
button.OnClick += () => { OnSetFlags(flags); };
}
private void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
{
GameObject toggleObj = UIFactory.CreateToggle(parent, "Toggle_" + type, out Toggle toggle, out Text toggleText);
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: width);

View File

@ -59,7 +59,7 @@ namespace UnityExplorer.Loader.BIE
public override void SaveConfig()
{
// not required
Config.Save();
}
}
}

View File

@ -6,27 +6,53 @@ using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Config;
using UnityExplorer.UI;
using UniverseLib;
namespace UnityExplorer.Loader.Standalone
{
public class ExplorerEditorBehaviour : MonoBehaviour
{
internal static ExplorerEditorBehaviour Instance { get; private set; }
public bool Hide_On_Startup = true;
public KeyCode Master_Toggle_Key = KeyCode.F7;
public UIManager.VerticalAnchor Main_Navbar_Anchor = UIManager.VerticalAnchor.Top;
public bool Log_Unity_Debug = false;
public float Startup_Delay_Time = 1f;
public KeyCode World_MouseInspect_Keybind;
public KeyCode UI_MouseInspect_Keybind;
public bool Force_Unlock_Mouse = true;
public KeyCode Force_Unlock_Toggle;
public bool Disable_EventSystem_Override;
internal void Awake()
{
Instance = this;
ExplorerEditorLoader.Initialize();
DontDestroyOnLoad(this);
this.gameObject.hideFlags = HideFlags.HideAndDontSave;
}
internal void OnDestroy()
{
OnApplicationQuit();
}
internal void OnApplicationQuit()
{
if (UI.UIManager.UIRoot)
Destroy(UI.UIManager.UIRoot.transform.root.gameObject);
Destroy(this.gameObject);
}
internal void LoadConfigs()
{
ConfigManager.Hide_On_Startup.Value = this.Hide_On_Startup;
ConfigManager.Master_Toggle.Value = this.Master_Toggle_Key;
ConfigManager.Main_Navbar_Anchor.Value = this.Main_Navbar_Anchor;
ConfigManager.Log_Unity_Debug.Value = this.Log_Unity_Debug;
ConfigManager.Startup_Delay_Time.Value = this.Startup_Delay_Time;
ConfigManager.World_MouseInspect_Keybind.Value = this.World_MouseInspect_Keybind;
ConfigManager.UI_MouseInspect_Keybind.Value = this.UI_MouseInspect_Keybind;
ConfigManager.Force_Unlock_Mouse.Value = this.Force_Unlock_Mouse;
ConfigManager.Force_Unlock_Toggle.Value = this.Force_Unlock_Toggle;
ConfigManager.Disable_EventSystem_Override.Value = this.Disable_EventSystem_Override;
}
}
}

View File

@ -36,7 +36,7 @@ namespace UnityExplorer.Loader.Standalone
protected override void CheckExplorerFolder()
{
if (explorerFolderDest == null)
explorerFolderDest = Application.dataPath;
explorerFolderDest = Path.GetDirectoryName(Application.dataPath);
}
}
}

View File

@ -34,12 +34,15 @@ namespace UnityExplorer.ObjectExplorer
private ScrollPool<ButtonCell> resultsScrollPool;
private List<object> currentResults = new();
//public TypeCompleter typeAutocompleter;
public TypeCompleter unityObjectTypeCompleter;
public TypeCompleter allTypesCompleter;
public override GameObject UIRoot => uiRoot;
private GameObject uiRoot;
private GameObject sceneFilterRow;
private GameObject childFilterRow;
private GameObject classInputRow;
public TypeCompleter typeAutocompleter;
private GameObject nameInputRow;
private InputFieldRef nameInputField;
private Text resultsLabel;
@ -98,14 +101,18 @@ namespace UnityExplorer.ObjectExplorer
nameInputRow.SetActive(context == SearchContext.UnityObject);
if (context == SearchContext.Class)
typeAutocompleter.AllTypes = true;
else
switch (context)
{
typeAutocompleter.BaseType = context == SearchContext.UnityObject ? typeof(UnityEngine.Object) : typeof(object);
typeAutocompleter.AllTypes = false;
case SearchContext.UnityObject:
unityObjectTypeCompleter.Enabled = true;
allTypesCompleter.Enabled = false;
break;
case SearchContext.Singleton:
case SearchContext.Class:
allTypesCompleter.Enabled = true;
unityObjectTypeCompleter.Enabled = false;
break;
}
typeAutocompleter.CacheTypes();
}
private void OnSceneFilterDropChanged(int value) => sceneFilter = (SceneFilter)value;
@ -185,7 +192,9 @@ namespace UnityExplorer.ObjectExplorer
InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "...");
UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField);
unityObjectTypeCompleter = new(typeof(UnityEngine.Object), classInputField, true, false, true);
allTypesCompleter = new(null, classInputField, true, false, true);
allTypesCompleter.Enabled = false;
classInputField.OnValueChanged += OnTypeInputChanged;
//unityObjectClassRow.SetActive(false);

View File

@ -135,7 +135,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type type in asm.TryGetTypes())
foreach (Type type in asm.GetTypes())
{
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
continue;
@ -173,7 +173,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
// Search all non-static, non-enum classes.
foreach (Type type in asm.TryGetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
foreach (Type type in asm.GetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
{
try
{

View File

@ -39,7 +39,6 @@ namespace UnityExplorer.UI
private static void ConstructUI()
{
popupLabel = UIFactory.CreateLabel(UIManager.UIRoot, "ClipboardNotification", "", TextAnchor.MiddleCenter);
popupLabel.rectTransform.sizeDelta = new(500, 100);
popupLabel.gameObject.AddComponent<Outline>();

View File

@ -69,15 +69,22 @@ namespace UnityExplorer.UI.Panels
if (CurrentHandler == provider)
{
Suggestions.Clear();
CurrentHandler = null;
UIRoot.SetActive(false);
}
}
public void SetSuggestions(IEnumerable<Suggestion> suggestions)
public void SetSuggestions(List<Suggestion> suggestions, bool jumpToTop = true)
{
Suggestions = suggestions as List<Suggestion> ?? suggestions.ToList();
SelectedIndex = 0;
Suggestions = suggestions;
if (jumpToTop)
{
SelectedIndex = 0;
if (scrollPool.DataSource.ItemCount > 0)
scrollPool.JumpToIndex(0, null);
}
if (!Suggestions.Any())
base.UIRoot.SetActive(false);
@ -86,7 +93,7 @@ namespace UnityExplorer.UI.Panels
base.UIRoot.SetActive(true);
base.UIRoot.transform.SetAsLastSibling();
buttonListDataHandler.RefreshData();
scrollPool.Refresh(true, true);
scrollPool.Refresh(true, jumpToTop);
}
}
@ -194,6 +201,12 @@ namespace UnityExplorer.UI.Panels
private void SetCell(ButtonCell cell, int index)
{
if (CurrentHandler == null)
{
UIRoot.SetActive(false);
return;
}
if (index < 0 || index >= Suggestions.Count)
{
cell.Disable();
@ -225,13 +238,18 @@ namespace UnityExplorer.UI.Panels
InputFieldRef input = CurrentHandler.InputField;
if (!input.Component.isFocused || input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition)
return;
lastInputPosition = input.UIRoot.transform.position;
lastCaretPosition = input.Component.caretPosition;
//if (!input.Component.isFocused
// || (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition))
// return;
if (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition)
return;
if (CurrentHandler.AnchorToCaretPosition)
{
if (!input.Component.isFocused)
return;
TextGenerator textGen = input.Component.cachedInputTextGenerator;
int caretIdx = Math.Max(0, Math.Min(textGen.characterCount - 1, input.Component.caretPosition));
@ -248,6 +266,9 @@ namespace UnityExplorer.UI.Panels
uiRoot.transform.position = input.Transform.position + new Vector3(-(input.Transform.rect.width / 2) + 10, -20, 0);
}
lastInputPosition = input.UIRoot.transform.position;
lastCaretPosition = input.Component.caretPosition;
this.Dragger.OnEndResize();
}

View File

@ -145,7 +145,7 @@ namespace UnityExplorer.UI.Panels
GameObject inputObj = UIFactory.CreateScrollInputField(inputArea, "ConsoleInput", ConsoleController.STARTUP_TEXT,
out InputFieldScroller inputScroller, fontSize);
InputScroller = inputScroller;
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
ConsoleController.DefaultInputFieldAlpha = Input.Component.selectionColor.a;
Input.OnValueChanged += InvokeOnValueChanged;
// move line number text with input field

View File

@ -171,6 +171,9 @@ namespace UnityExplorer.UI.Panels
if (!ourCamera)
return;
if (positionInput.Component.isFocused)
return;
lastSetCameraPosition = ourCamera.transform.position;
positionInput.Text = ParseUtility.ToStringForInput<Vector3>(lastSetCameraPosition);
}

View File

@ -1,7 +1,10 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
@ -11,76 +14,58 @@ namespace UnityExplorer.UI.Panels
{
public class HookManagerPanel : UEPanel
{
public static HookManagerPanel Instance { get; private set; }
public enum Pages
{
CurrentHooks,
ClassMethodSelector,
HookSourceEditor
HookSourceEditor,
GenericArgsSelector,
}
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public static HookCreator hookCreator;
public static HookList hookList;
public static GenericConstructorWidget genericArgsHandler;
// Panel
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks";
public override bool ShowByDefault => false;
public override int MinWidth => 500;
public override int MinHeight => 600;
public override int MinWidth => 400;
public override int MinHeight => 400;
public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f);
public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f);
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
private GameObject currentHooksPanel;
public ScrollPool<HookCell> HooksScrollPool;
private InputFieldRef classSelectorInputField;
private GameObject addHooksPanel;
public ScrollPool<AddHookCell> AddHooksScrollPool;
private Text addHooksLabel;
private InputFieldRef AddHooksMethodFilterInput;
private GameObject editorPanel;
public InputFieldScroller EditorInputScroller { get; private set; }
public InputFieldRef EditorInput => EditorInputScroller.InputField;
public Text EditorInputText { get; private set; }
public Text EditorHighlightText { get; private set; }
public Pages CurrentPage { get; private set; } = Pages.ClassMethodSelector;
public HookManagerPanel(UIBase owner) : base(owner)
{
}
private void OnClassInputAddClicked()
{
HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
}
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
public void SetPage(Pages page)
{
switch (page)
{
case Pages.CurrentHooks:
currentHooksPanel.SetActive(true);
addHooksPanel.SetActive(false);
editorPanel.SetActive(false);
break;
case Pages.ClassMethodSelector:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(true);
editorPanel.SetActive(false);
HookCreator.AddHooksRoot.SetActive(true);
HookCreator.EditorRoot.SetActive(false);
genericArgsHandler.UIRoot.SetActive(false);
break;
case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(false);
editorPanel.SetActive(true);
HookCreator.AddHooksRoot.SetActive(false);
HookCreator.EditorRoot.SetActive(true);
genericArgsHandler.UIRoot.SetActive(false);
break;
case Pages.GenericArgsSelector:
HookCreator.AddHooksRoot.SetActive(false);
HookCreator.EditorRoot.SetActive(false);
genericArgsHandler.UIRoot.SetActive(true);
break;
}
}
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void SetDefaultSizeAndPosition()
{
base.SetDefaultSizeAndPosition();
@ -91,115 +76,35 @@ namespace UnityExplorer.UI.Panels
protected override void ConstructPanelContent()
{
// ~~~~~~~~~ Active hooks scroll pool
Instance = this;
hookList = new();
hookCreator = new();
genericArgsHandler = new();
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.ContentRoot);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ContentRoot, true, false);
GameObject addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
// GameObject baseHoriGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "HoriGroup", true, true, true, true);
// UIFactory.SetLayoutElement(baseHoriGroup, flexibleWidth: 9999, flexibleHeight: 9999);
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
// // Left Group
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
addButton.OnClick += OnClassInputAddClicked;
//GameObject leftGroup = UIFactory.CreateVerticalGroup(ContentRoot, "LeftGroup", true, true, true, true);
UIFactory.SetLayoutElement(ContentRoot.gameObject, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
Text hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
hookList.ConstructUI(ContentRoot);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// // Right Group
// ~~~~~~~~~ Add hooks panel
//GameObject rightGroup = UIFactory.CreateVerticalGroup(ContentRoot, "RightGroup", true, true, true, true);
UIFactory.SetLayoutElement(ContentRoot, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.ContentRoot);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
hookCreator.ConstructAddHooksView(ContentRoot);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
hookCreator.ConstructEditor(ContentRoot);
HookCreator.EditorRoot.SetActive(false);
GameObject buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
doneButton.OnClick += HookManager.Instance.DoneAddingHooks;
AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
AddHooksScrollPool.Initialize(HookManager.Instance);
addHooksPanel.gameObject.SetActive(false);
// ~~~~~~~~~ Hook source editor panel
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.ContentRoot);
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
Text editorLabel = UIFactory.CreateLabel(editorPanel,
"EditorLabel",
"Edit Harmony patch source as desired. Accepted method names are Prefix, Postfix, Finalizer and Transpiler (can define multiple).\n\n" +
"Hooks are temporary! Please copy the source into your IDE to avoid losing work if you wish to keep it!",
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorSaveButton.OnClick += HookManager.Instance.EditorInputSave;
ButtonRef editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorDoneButton.OnClick += HookManager.Instance.EditorInputCancel;
int fontSize = 16;
GameObject inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out InputFieldScroller inputScroller, fontSize);
EditorInputScroller = inputScroller;
EditorInput.OnValueChanged += HookManager.Instance.OnEditorInputChanged;
EditorInputText = EditorInput.Component.textComponent;
EditorInputText.supportRichText = false;
EditorInputText.color = Color.clear;
EditorInput.Component.customCaretColor = true;
EditorInput.Component.caretColor = Color.white;
EditorInput.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
RectTransform highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
highlightTextRect.pivot = new Vector2(0, 1);
highlightTextRect.anchorMin = Vector2.zero;
highlightTextRect.anchorMax = Vector2.one;
highlightTextRect.offsetMin = Vector2.zero;
highlightTextRect.offsetMax = Vector2.zero;
EditorHighlightText = highlightTextObj.AddComponent<Text>();
EditorHighlightText.color = Color.white;
EditorHighlightText.supportRichText = true;
EditorHighlightText.fontSize = fontSize;
// Set fonts
EditorInputText.font = UniversalUI.ConsoleFont;
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
EditorHighlightText.font = UniversalUI.ConsoleFont;
editorPanel.SetActive(false);
genericArgsHandler.ConstructUI(ContentRoot);
genericArgsHandler.UIRoot.SetActive(false);
}
}
}

View File

@ -14,8 +14,6 @@ using UniverseLib.Utility;
namespace UnityExplorer.UI.Panels
{
// TODO move the logic out of this class into a LogUtil class (also move ExplorerCore.Log into that)
public class LogPanel : UEPanel, ICellPoolDataSource<ConsoleLogCell>
{
public struct LogInfo

View File

@ -71,6 +71,17 @@ namespace UnityExplorer.UI.Panels
// Save Data
bool setDefault = false;
public override void SetDefaultSizeAndPosition()
{
if (setDefault)
return;
setDefault = true;
base.SetDefaultSizeAndPosition();
}
public bool ApplyingSaveData { get; set; }
public void SaveInternalData()
@ -119,6 +130,8 @@ namespace UnityExplorer.UI.Panels
{
Rect.SetAnchorsFromString(split[1]);
Rect.SetPositionFromString(split[2]);
this.EnsureValidSize();
this.EnsureValidPosition();
this.SetActive(bool.Parse(split[0]));
}
catch

View File

@ -5,6 +5,7 @@ using UnityExplorer.Config;
using UnityExplorer.CSConsole;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.Input;
@ -26,7 +27,6 @@ namespace UnityExplorer.UI
Options,
ConsoleLog,
AutoCompleter,
//MouseInspector,
UIInspectorResults,
HookManager,
Clipboard,
@ -55,10 +55,7 @@ namespace UnityExplorer.UI
private static readonly Vector2 NAVBAR_DIMENSIONS = new(1020f, 35f);
private static ButtonRef closeBtn;
private static ButtonRef pauseBtn;
private static InputFieldRef timeInput;
private static bool pauseButtonPausing;
private static float lastTimeScale;
private static TimeScaleWidget timeScaleWidget;
private static int lastScreenWidth;
private static int lastScreenHeight;
@ -113,14 +110,14 @@ namespace UnityExplorer.UI
Notification.Init();
ConsoleController.Init();
// Set default menu visibility
ShowMenu = !ConfigManager.Hide_On_Startup.Value;
// Failsafe fix, in some games all dropdowns displayed values are blank on startup for some reason.
foreach (Dropdown dropdown in UIRoot.GetComponentsInChildren<Dropdown>(true))
dropdown.RefreshShownValue();
Initializing = false;
if (ConfigManager.Hide_On_Startup.Value)
ShowMenu = false;
}
// Main UI Update loop
@ -142,20 +139,7 @@ namespace UnityExplorer.UI
UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = !UniverseLib.Config.ConfigManager.Force_Unlock_Mouse;
// update the timescale value
if (!timeInput.Component.isFocused && lastTimeScale != Time.timeScale)
{
if (pauseButtonPausing && Time.timeScale != 0.0f)
{
pauseButtonPausing = false;
OnPauseButtonToggled();
}
if (!pauseButtonPausing)
{
timeInput.Text = Time.timeScale.ToString("F2");
lastTimeScale = Time.timeScale;
}
}
timeScaleWidget.Update();
// check screen dimension change
Display display = DisplayManager.ActiveDisplay;
@ -233,41 +217,7 @@ namespace UnityExplorer.UI
closeBtn.ButtonText.text = val.ToString();
}
// Time controls
private static void OnTimeInputEndEdit(string val)
{
if (pauseButtonPausing)
return;
if (float.TryParse(val, out float f))
{
Time.timeScale = f;
lastTimeScale = f;
}
timeInput.Text = Time.timeScale.ToString("F2");
}
private static void OnPauseButtonClicked()
{
pauseButtonPausing = !pauseButtonPausing;
Time.timeScale = pauseButtonPausing ? 0f : lastTimeScale;
OnPauseButtonToggled();
}
private static void OnPauseButtonToggled()
{
timeInput.Component.text = Time.timeScale.ToString("F2");
timeInput.Component.readOnly = pauseButtonPausing;
timeInput.Component.textComponent.color = pauseButtonPausing ? Color.grey : Color.white;
Color color = pauseButtonPausing ? new Color(0.3f, 0.3f, 0.2f) : new Color(0.2f, 0.2f, 0.2f);
RuntimeHelper.SetColorBlock(pauseBtn.Component, color, color * 1.2f, color * 0.7f);
pauseBtn.ButtonText.text = pauseButtonPausing ? "►" : "||";
}
// UI Construction
@ -299,26 +249,17 @@ namespace UnityExplorer.UI
UIFactory.SetLayoutElement(NavbarTabButtonHolder, minHeight: 25, flexibleHeight: 999, flexibleWidth: 999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(NavbarTabButtonHolder, false, true, true, true, 4, 2, 2, 2, 2);
// Time controls
// Time scale widget
timeScaleWidget = new(navbarPanel);
Text timeLabel = UIFactory.CreateLabel(navbarPanel, "TimeLabel", "Time:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(timeLabel.gameObject, minHeight: 25, minWidth: 50);
timeInput = UIFactory.CreateInputField(navbarPanel, "TimeInput", "timeScale");
UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40);
timeInput.Component.GetOnEndEdit().AddListener(OnTimeInputEndEdit);
timeInput.Text = string.Empty;
timeInput.Text = Time.timeScale.ToString();
pauseBtn = UIFactory.CreateButton(navbarPanel, "PauseButton", "||", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(pauseBtn.Component.gameObject, minHeight: 25, minWidth: 25);
pauseBtn.OnClick += OnPauseButtonClicked;
//spacer
GameObject spacer = UIFactory.CreateUIObject("Spacer", navbarPanel);
UIFactory.SetLayoutElement(spacer, minWidth: 15);
// Hide menu button
closeBtn = UIFactory.CreateButton(navbarPanel, "CloseButton", ConfigManager.Master_Toggle.Value.ToString());
UIFactory.SetLayoutElement(closeBtn.Component.gameObject, minHeight: 25, minWidth: 80, flexibleWidth: 0);
UIFactory.SetLayoutElement(closeBtn.Component.gameObject, minHeight: 25, minWidth: 60, flexibleWidth: 0);
RuntimeHelper.SetColorBlock(closeBtn.Component, new Color(0.63f, 0.32f, 0.31f),
new Color(0.81f, 0.25f, 0.2f), new Color(0.6f, 0.18f, 0.16f));

View File

@ -1,6 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.Models;
@ -12,133 +15,217 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
{
public bool Enabled
{
get => _enabled;
get => enabled;
set
{
_enabled = value;
if (!_enabled)
enabled = value;
if (!enabled)
{
AutoCompleteModal.Instance.ReleaseOwnership(this);
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
}
}
}
private bool _enabled = true;
bool enabled = true;
public event Action<Suggestion> SuggestionClicked;
public Type BaseType { get; set; }
public Type[] GenericConstraints { get; set; }
public bool AllTypes { get; set; }
private readonly bool allowAbstract;
private readonly bool allowEnum;
public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false;
private readonly List<Suggestion> suggestions = new();
private readonly HashSet<string> suggestedNames = new();
readonly bool allowAbstract;
readonly bool allowEnum;
readonly bool allowGeneric;
private HashSet<Type> allowedTypes;
public Type BaseType { get; set; }
HashSet<Type> allowedTypes;
string pendingInput;
Coroutine getSuggestionsCoroutine;
readonly Stopwatch cacheTypesStopwatch = new();
private string chosenSuggestion;
readonly List<Suggestion> suggestions = new();
readonly HashSet<string> suggestedTypes = new();
string chosenSuggestion;
readonly List<Suggestion> loadingSuggestions = new()
{
new("<color=grey>Loading...</color>", "")
};
bool ISuggestionProvider.AllowNavigation => false;
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true) { }
static readonly Dictionary<string, Type> shorthandToType = new()
{
{ "object", typeof(object) },
{ "string", typeof(string) },
{ "bool", typeof(bool) },
{ "byte", typeof(byte) },
{ "sbyte", typeof(sbyte) },
{ "char", typeof(char) },
{ "decimal", typeof(decimal) },
{ "double", typeof(double) },
{ "float", typeof(float) },
{ "int", typeof(int) },
{ "uint", typeof(uint) },
{ "long", typeof(long) },
{ "ulong", typeof(ulong) },
{ "short", typeof(short) },
{ "ushort", typeof(ushort) },
{ "void", typeof(void) },
};
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum)
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true, true) { }
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum, bool allowGeneric)
{
BaseType = baseType;
InputField = inputField;
this.allowAbstract = allowAbstract;
this.allowEnum = allowEnum;
this.allowGeneric = allowGeneric;
inputField.OnValueChanged += OnInputFieldChanged;
if (BaseType != null)
CacheTypes();
}
public void CacheTypes()
{
if (!AllTypes)
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowEnum, false);
else
{
allowedTypes = new();
foreach (KeyValuePair<string, Type> entry in ReflectionUtility.AllTypes)
{
// skip <PrivateImplementationDetails> and <AnonymousClass> classes
Type type = entry.Value;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
{
continue;
}
allowedTypes.Add(type);
}
}
CacheTypes();
}
public void OnSuggestionClicked(Suggestion suggestion)
{
chosenSuggestion = suggestion.UnderlyingValue;
InputField.Text = suggestion.UnderlyingValue;
SuggestionClicked?.Invoke(suggestion);
suggestions.Clear();
AutoCompleteModal.Instance.SetSuggestions(suggestions);
chosenSuggestion = suggestion.UnderlyingValue;
//AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
private void OnInputFieldChanged(string value)
public void CacheTypes()
{
allowedTypes = null;
cacheTypesStopwatch.Reset();
cacheTypesStopwatch.Start();
ReflectionUtility.GetImplementationsOf(BaseType, OnTypesCached, allowAbstract, allowGeneric, allowEnum);
}
void OnTypesCached(HashSet<Type> set)
{
allowedTypes = set;
// ExplorerCore.Log($"Cached {allowedTypes.Count} TypeCompleter types in {cacheTypesStopwatch.ElapsedMilliseconds * 0.001f} seconds.");
if (pendingInput != null)
{
GetSuggestions(pendingInput);
pendingInput = null;
}
}
void OnInputFieldChanged(string input)
{
if (!Enabled)
return;
if (string.IsNullOrEmpty(value) || value == chosenSuggestion)
{
if (input != chosenSuggestion)
chosenSuggestion = null;
if (string.IsNullOrEmpty(input) || input == chosenSuggestion)
{
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
else
{
GetSuggestions(value);
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
GetSuggestions(input);
}
}
private void GetSuggestions(string value)
void GetSuggestions(string input)
{
suggestions.Clear();
suggestedNames.Clear();
if (BaseType == null)
if (allowedTypes == null)
{
ExplorerCore.LogWarning("Autocompleter Base type is null!");
if (pendingInput != null)
{
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true);
}
pendingInput = input;
return;
}
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
getSuggestionsCoroutine = RuntimeHelper.StartCoroutine(GetSuggestionsAsync(input));
}
IEnumerator GetSuggestionsAsync(string input)
{
suggestions.Clear();
suggestedTypes.Clear();
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
// shorthand types all inherit from System.Object
if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand))
AddSuggestion(shorthand);
foreach (KeyValuePair<string, Type> entry in shorthandToType)
{
if (allowedTypes.Contains(entry.Value) && entry.Key.StartsWith(input, StringComparison.InvariantCultureIgnoreCase))
AddSuggestion(entry.Value);
}
// Check for exact match first
if (ReflectionUtility.GetTypeByName(value) is Type t && allowedTypes.Contains(t))
if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(t))
AddSuggestion(t);
if (!suggestions.Any())
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, false);
else
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
Stopwatch sw = new();
sw.Start();
// ExplorerCore.Log($"Checking {allowedTypes.Count} types...");
foreach (Type entry in allowedTypes)
{
if (entry.FullName.ContainsIgnoreCase(value))
if (AutoCompleteModal.CurrentHandler == null)
yield break;
if (sw.ElapsedMilliseconds > 10)
{
yield return null;
if (suggestions.Any())
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
sw.Reset();
sw.Start();
}
if (entry.FullName.ContainsIgnoreCase(input))
AddSuggestion(entry);
}
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
// ExplorerCore.Log($"Fetched {suggestions.Count} TypeCompleter suggestions in {sw.ElapsedMilliseconds * 0.001f} seconds.");
}
internal static readonly Dictionary<string, string> sharedTypeToLabel = new();
void AddSuggestion(Type type)
{
if (suggestedNames.Contains(type.FullName))
if (suggestedTypes.Contains(type.FullName))
return;
suggestedNames.Add(type.FullName);
suggestedTypes.Add(type.FullName);
if (!sharedTypeToLabel.ContainsKey(type.FullName))
sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true));

View File

@ -9,8 +9,6 @@ namespace UnityExplorer.UI.Widgets
{
public abstract class BaseArgumentHandler : IPooledObject
{
protected EvaluateWidget evaluator;
internal Text argNameLabel;
internal InputFieldRef inputField;
internal TypeCompleter typeCompleter;

View File

@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets
GenericArgumentHandler holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.genericArgumentsHolder.transform, false);
holder.OnBorrowed(this, type);
holder.OnBorrowed(type);
}
}
@ -122,7 +122,7 @@ namespace UnityExplorer.UI.Widgets
ParameterHandler holder = paramHandlers[i] = Pool<ParameterHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.parametersHolder.transform, false);
holder.OnBorrowed(this, param);
holder.OnBorrowed(param);
}
}

View File

@ -7,30 +7,28 @@ namespace UnityExplorer.UI.Widgets
{
public class GenericArgumentHandler : BaseArgumentHandler
{
private Type genericType;
private Type genericArgument;
public void OnBorrowed(EvaluateWidget evaluator, Type genericConstraint)
public void OnBorrowed(Type genericArgument)
{
this.evaluator = evaluator;
this.genericType = genericConstraint;
this.genericArgument = genericArgument;
typeCompleter.Enabled = true;
typeCompleter.BaseType = genericType;
typeCompleter.BaseType = this.genericArgument;
typeCompleter.CacheTypes();
Type[] constraints = genericType.GetGenericParameterConstraints();
typeCompleter.GenericConstraints = constraints;
Type[] constraints = this.genericArgument.GetGenericParameterConstraints();
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{genericType.Name}</color>");
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{this.genericArgument.Name}</color>");
for (int j = 0; j < constraints.Length; j++)
for (int i = 0; i < constraints.Length; i++)
{
if (j == 0) sb.Append(' ').Append('(');
if (i == 0) sb.Append(' ').Append('(');
else sb.Append(',').Append(' ');
sb.Append(SignatureHighlighter.Parse(constraints[j], false));
sb.Append(SignatureHighlighter.Parse(constraints[i], false));
if (j + 1 == constraints.Length)
if (i + 1 == constraints.Length)
sb.Append(')');
}
@ -39,8 +37,7 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned()
{
this.evaluator = null;
this.genericType = null;
this.genericArgument = null;
this.typeCompleter.Enabled = false;

View File

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.ObjectPool;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
public class GenericConstructorWidget
{
GenericArgumentHandler[] handlers;
Type[] currentGenericParameters;
Action<Type[]> currentOnSubmit;
Action currentOnCancel;
public GameObject UIRoot;
Text Title;
GameObject ArgsHolder;
public void Show(Action<Type[]> onSubmit, Action onCancel, Type genericTypeDefinition)
{
Title.text = $"Setting generic arguments for {SignatureHighlighter.Parse(genericTypeDefinition, false)}...";
OnShow(onSubmit, onCancel, genericTypeDefinition.GetGenericArguments());
}
public void Show(Action<Type[]> onSubmit, Action onCancel, MethodInfo genericMethodDefinition)
{
Title.text = $"Setting generic arguments for {SignatureHighlighter.ParseMethod(genericMethodDefinition)}...";
OnShow(onSubmit, onCancel, genericMethodDefinition.GetGenericArguments());
}
void OnShow(Action<Type[]> onSubmit, Action onCancel, Type[] genericParameters)
{
currentOnSubmit = onSubmit;
currentOnCancel = onCancel;
SetGenericParameters(genericParameters);
}
void SetGenericParameters(Type[] genericParameters)
{
currentGenericParameters = genericParameters;
handlers = new GenericArgumentHandler[genericParameters.Length];
for (int i = 0; i < genericParameters.Length; i++)
{
Type type = genericParameters[i];
GenericArgumentHandler holder = handlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(ArgsHolder.transform, false);
holder.OnBorrowed(type);
}
}
public void TrySubmit()
{
Type[] args = new Type[currentGenericParameters.Length];
for (int i = 0; i < args.Length; i++)
{
GenericArgumentHandler handler = handlers[i];
Type arg;
try
{
arg = handler.Evaluate();
if (arg == null) throw new Exception();
}
catch
{
ExplorerCore.LogWarning($"Generic argument '{handler.inputField.Text}' is not a valid type.");
return;
}
args[i] = arg;
}
OnClose();
currentOnSubmit(args);
}
public void Cancel()
{
OnClose();
currentOnCancel?.Invoke();
}
void OnClose()
{
if (handlers != null)
{
foreach (GenericArgumentHandler widget in handlers)
{
widget.OnReturned();
Pool<GenericArgumentHandler>.Return(widget);
}
handlers = null;
}
}
// UI Construction
internal void ConstructUI(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "GenericArgsHandler", false, false, true, true, 5, new Vector4(5, 5, 5, 5),
childAlignment: TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, flexibleHeight: 9999);
ButtonRef submitButton = UIFactory.CreateButton(UIRoot, "SubmitButton", "Submit", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(submitButton.GameObject, minHeight: 25, minWidth: 200);
submitButton.OnClick += TrySubmit;
ButtonRef cancelButton = UIFactory.CreateButton(UIRoot, "CancelButton", "Cancel", new Color(0.3f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(cancelButton.GameObject, minHeight: 25, minWidth: 200);
cancelButton.OnClick += Cancel;
Title = UIFactory.CreateLabel(UIRoot, "Title", "Generic Arguments", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(Title.gameObject, minHeight: 25, flexibleWidth: 9999);
GameObject scrollview = UIFactory.CreateScrollView(UIRoot, "GenericArgsScrollView", out ArgsHolder, out _, new(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(scrollview, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ArgsHolder, padTop: 5, padLeft: 5, padBottom: 5, padRight: 5);
}
}
}

View File

@ -26,9 +26,8 @@ namespace UnityExplorer.UI.Widgets
private Text basicLabel;
private ButtonRef pasteButton;
public void OnBorrowed(EvaluateWidget evaluator, ParameterInfo paramInfo)
public void OnBorrowed(ParameterInfo paramInfo)
{
this.evaluator = evaluator;
this.paramInfo = paramInfo;
this.paramType = paramInfo.ParameterType;
@ -85,7 +84,6 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned()
{
this.evaluator = null;
this.paramInfo = null;
this.enumCompleter.Enabled = false;

View File

@ -0,0 +1,68 @@
using UnityEngine;
using UnityEngine.UI;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
// Handles the slider and +/- buttons for a specific axis of a transform property.
public class AxisControl
{
public readonly Vector3Control parent;
public readonly int axis;
public readonly Slider slider;
public AxisControl(int axis, Slider slider, Vector3Control parentControl)
{
this.parent = parentControl;
this.axis = axis;
this.slider = slider;
}
void OnVectorSliderChanged(float value)
{
parent.Owner.CurrentSlidingAxisControl = value == 0f ? null : this;
}
void OnVectorMinusClicked()
{
parent.Owner.AxisControlOperation(-this.parent.Increment, this.parent, this.axis);
}
void OnVectorPlusClicked()
{
parent.Owner.AxisControlOperation(this.parent.Increment, this.parent, this.axis);
}
public static AxisControl Create(GameObject parent, string title, int axis, Vector3Control owner)
{
Text label = UIFactory.CreateLabel(parent, $"Label_{title}", $"{title}:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30);
GameObject sliderObj = UIFactory.CreateSlider(parent, $"Slider_{title}", out Slider slider);
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 75, flexibleWidth: 0);
slider.m_FillImage.color = Color.clear;
slider.minValue = -0.1f;
slider.maxValue = 0.1f;
AxisControl sliderControl = new(axis, slider, owner);
slider.onValueChanged.AddListener(sliderControl.OnVectorSliderChanged);
ButtonRef minusButton = UIFactory.CreateButton(parent, "MinusIncrementButton", "-");
UIFactory.SetLayoutElement(minusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
minusButton.OnClick += sliderControl.OnVectorMinusClicked;
ButtonRef plusButton = UIFactory.CreateButton(parent, "PlusIncrementButton", "+");
UIFactory.SetLayoutElement(plusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
plusButton.OnClick += sliderControl.OnVectorPlusClicked;
return sliderControl;
}
}
}

View File

@ -6,7 +6,7 @@ using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ButtonList;
using UniverseLib;
namespace UnityExplorer.Inspectors
namespace UnityExplorer.UI.Widgets
{
public class ComponentCell : ButtonCell
{

View File

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityExplorer.Inspectors;
using UniverseLib;
using UniverseLib.UI.Widgets.ButtonList;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Inspectors
namespace UnityExplorer.UI.Widgets
{
public class ComponentList : ButtonListHandler<Component, ComponentCell>
{

View File

@ -0,0 +1,35 @@
using UnityEngine;
using UnityExplorer.Inspectors;
namespace UnityExplorer.UI.Widgets
{
// The base wrapper to hold a reference to the parent Inspector and the GameObjectInfo and TransformControls widgets.
public class GameObjectControls
{
public GameObjectInspector Parent { get; }
public GameObject Target => Parent.Target;
public GameObjectInfoPanel GameObjectInfo { get; }
public TransformControls TransformControl { get; }
public GameObjectControls(GameObjectInspector parent)
{
this.Parent = parent;
this.GameObjectInfo = new(this);
this.TransformControl = new(this);
}
public void UpdateGameObjectInfo(bool firstUpdate, bool force)
{
GameObjectInfo.UpdateGameObjectInfo(firstUpdate, force);
}
public void UpdateVectorSlider()
{
TransformControl.UpdateVectorSlider();
}
}
}

View File

@ -0,0 +1,455 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
public class GameObjectInfoPanel
{
public GameObjectControls Owner { get; }
GameObject Target => Owner.Target;
string lastGoName;
string lastPath;
bool lastParentState;
int lastSceneHandle;
string lastTag;
int lastLayer;
int lastFlags;
ButtonRef ViewParentButton;
InputFieldRef PathInput;
InputFieldRef NameInput;
Toggle ActiveSelfToggle;
Text ActiveSelfText;
Toggle IsStaticToggle;
ButtonRef SceneButton;
InputFieldRef InstanceIDInput;
InputFieldRef TagInput;
Dropdown LayerDropdown;
Dropdown FlagsDropdown;
public GameObjectInfoPanel(GameObjectControls owner)
{
this.Owner = owner;
Create();
}
public void UpdateGameObjectInfo(bool firstUpdate, bool force)
{
if (firstUpdate)
{
InstanceIDInput.Text = Target.GetInstanceID().ToString();
}
if (force || (!NameInput.Component.isFocused && Target.name != lastGoName))
{
lastGoName = Target.name;
Owner.Parent.Tab.TabText.text = $"[G] {Target.name}";
NameInput.Text = Target.name;
}
if (force || !PathInput.Component.isFocused)
{
string path = Target.transform.GetTransformPath();
if (path != lastPath)
{
lastPath = path;
PathInput.Text = path;
}
}
if (force || Target.transform.parent != lastParentState)
{
lastParentState = Target.transform.parent;
ViewParentButton.Component.interactable = lastParentState;
if (lastParentState)
{
ViewParentButton.ButtonText.color = Color.white;
ViewParentButton.ButtonText.text = "◄ View Parent";
}
else
{
ViewParentButton.ButtonText.color = Color.grey;
ViewParentButton.ButtonText.text = "No parent";
}
}
if (force || Target.activeSelf != ActiveSelfToggle.isOn)
{
ActiveSelfToggle.Set(Target.activeSelf, false);
ActiveSelfText.color = ActiveSelfToggle.isOn ? Color.green : Color.red;
}
if (force || Target.isStatic != IsStaticToggle.isOn)
{
IsStaticToggle.Set(Target.isStatic, false);
}
if (force || Target.scene.handle != lastSceneHandle)
{
lastSceneHandle = Target.scene.handle;
SceneButton.ButtonText.text = Target.scene.IsValid() ? Target.scene.name : "None (Asset/Resource)";
}
if (force || (!TagInput.Component.isFocused && Target.tag != lastTag))
{
lastTag = Target.tag;
TagInput.Text = lastTag;
}
if (force || (Target.layer != lastLayer))
{
lastLayer = Target.layer;
LayerDropdown.value = Target.layer;
}
if (force || ((int)Target.hideFlags != lastFlags))
{
lastFlags = (int)Target.hideFlags;
FlagsDropdown.captionText.text = Target.hideFlags.ToString();
}
}
void DoSetParent(Transform transform)
{
ExplorerCore.Log($"Setting target's transform parent to: {(transform == null ? "null" : $"'{transform.name}'")}");
if (Target.GetComponent<RectTransform>())
Target.transform.SetParent(transform, false);
else
Target.transform.parent = transform;
UpdateGameObjectInfo(false, false);
Owner.TransformControl.UpdateTransformControlValues(false);
}
#region UI event listeners
void OnViewParentClicked()
{
if (this.Target && this.Target.transform.parent)
{
Owner.Parent.OnTransformCellClicked(this.Target.transform.parent.gameObject);
}
}
void OnPathEndEdit(string input)
{
lastPath = input;
if (string.IsNullOrEmpty(input))
{
DoSetParent(null);
}
else
{
Transform parentToSet = null;
if (input.EndsWith("/"))
input = input.Remove(input.Length - 1);
// try the easy way
if (GameObject.Find(input) is GameObject found)
{
parentToSet = found.transform;
}
else
{
// look for inactive objects
string name = input.Split('/').Last();
UnityEngine.Object[] allObjects = RuntimeHelper.FindObjectsOfTypeAll(typeof(GameObject));
List<GameObject> shortList = new();
foreach (UnityEngine.Object obj in allObjects)
if (obj.name == name) shortList.Add(obj.TryCast<GameObject>());
foreach (GameObject go in shortList)
{
string path = go.transform.GetTransformPath(true);
if (path.EndsWith("/"))
path = path.Remove(path.Length - 1);
if (path == input)
{
parentToSet = go.transform;
break;
}
}
}
if (parentToSet)
DoSetParent(parentToSet);
else
{
ExplorerCore.LogWarning($"Could not find any GameObject name or path '{input}'!");
UpdateGameObjectInfo(false, true);
}
}
}
void OnNameEndEdit(string value)
{
Target.name = value;
UpdateGameObjectInfo(false, true);
}
void OnCopyClicked()
{
ClipboardPanel.Copy(this.Target);
}
void OnActiveSelfToggled(bool value)
{
Target.SetActive(value);
UpdateGameObjectInfo(false, true);
}
void OnTagEndEdit(string value)
{
try
{
Target.tag = value;
UpdateGameObjectInfo(false, true);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting tag! {ex.ReflectionExToString()}");
}
}
void OnSceneButtonClicked()
{
InspectorManager.Inspect(Target.scene);
}
void OnExploreButtonClicked()
{
ObjectExplorerPanel panel = UIManager.GetPanel<ObjectExplorerPanel>(UIManager.Panels.ObjectExplorer);
panel.SceneExplorer.JumpToTransform(this.Owner.Parent.Target.transform);
}
void OnLayerDropdownChanged(int value)
{
Target.layer = value;
UpdateGameObjectInfo(false, true);
}
void OnFlagsDropdownChanged(int value)
{
try
{
HideFlags enumVal = hideFlagsValues[FlagsDropdown.options[value].text];
Target.hideFlags = enumVal;
UpdateGameObjectInfo(false, true);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting hideFlags: {ex}");
}
}
void OnDestroyClicked()
{
GameObject.Destroy(this.Target);
InspectorManager.ReleaseInspector(Owner.Parent);
}
void OnInstantiateClicked()
{
GameObject clone = GameObject.Instantiate(this.Target);
InspectorManager.Inspect(clone);
}
#endregion
#region UI Construction
public void Create()
{
GameObject topInfoHolder = UIFactory.CreateVerticalGroup(Owner.Parent.Content, "TopInfoHolder", false, false, true, true, 3,
new Vector4(3, 3, 3, 3), new Color(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(topInfoHolder, minHeight: 100, flexibleWidth: 9999);
topInfoHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// first row (parent, path)
GameObject firstRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(firstRow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(firstRow, minHeight: 25, flexibleWidth: 9999);
ViewParentButton = UIFactory.CreateButton(firstRow, "ViewParentButton", "◄ View Parent", new Color(0.2f, 0.2f, 0.2f));
ViewParentButton.ButtonText.fontSize = 13;
UIFactory.SetLayoutElement(ViewParentButton.Component.gameObject, minHeight: 25, minWidth: 100);
ViewParentButton.OnClick += OnViewParentClicked;
this.PathInput = UIFactory.CreateInputField(firstRow, "PathInput", "...");
PathInput.Component.textComponent.color = Color.grey;
PathInput.Component.textComponent.fontSize = 14;
UIFactory.SetLayoutElement(PathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
PathInput.Component.lineType = InputField.LineType.MultiLineSubmit;
ButtonRef copyButton = UIFactory.CreateButton(firstRow, "CopyButton", "Copy to Clipboard", new Color(0.2f, 0.2f, 0.2f, 1));
copyButton.ButtonText.color = Color.yellow;
UIFactory.SetLayoutElement(copyButton.Component.gameObject, minHeight: 25, minWidth: 120);
copyButton.OnClick += OnCopyClicked;
PathInput.Component.GetOnEndEdit().AddListener((string val) => { OnPathEndEdit(val); });
// Title and update row
GameObject titleRow = UIFactory.CreateUIObject("TitleRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(titleRow, false, false, true, true, 5);
Text titleLabel = UIFactory.CreateLabel(titleRow, "Title", SignatureHighlighter.Parse(typeof(GameObject), false),
TextAnchor.MiddleLeft, fontSize: 17);
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 30, minWidth: 100);
// name
NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled");
UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
NameInput.Component.textComponent.fontSize = 15;
NameInput.Component.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
// second row (toggles, instanceID, tag, buttons)
GameObject secondRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(secondRow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(secondRow, minHeight: 25, flexibleWidth: 9999);
// activeSelf
GameObject activeToggleObj = UIFactory.CreateToggle(secondRow, "ActiveSelf", out ActiveSelfToggle, out ActiveSelfText);
UIFactory.SetLayoutElement(activeToggleObj, minHeight: 25, minWidth: 100);
ActiveSelfText.text = "ActiveSelf";
ActiveSelfToggle.onValueChanged.AddListener(OnActiveSelfToggled);
// isStatic
GameObject isStaticObj = UIFactory.CreateToggle(secondRow, "IsStatic", out IsStaticToggle, out Text staticText);
UIFactory.SetLayoutElement(isStaticObj, minHeight: 25, minWidth: 80);
staticText.text = "IsStatic";
staticText.color = Color.grey;
IsStaticToggle.interactable = false;
// InstanceID
Text instanceIdLabel = UIFactory.CreateLabel(secondRow, "InstanceIDLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(instanceIdLabel.gameObject, minHeight: 25, minWidth: 90);
InstanceIDInput = UIFactory.CreateInputField(secondRow, "InstanceIDInput", "error");
UIFactory.SetLayoutElement(InstanceIDInput.Component.gameObject, minHeight: 25, minWidth: 110);
InstanceIDInput.Component.textComponent.color = Color.grey;
InstanceIDInput.Component.readOnly = true;
//Tag
Text tagLabel = UIFactory.CreateLabel(secondRow, "TagLabel", "Tag:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(tagLabel.gameObject, minHeight: 25, minWidth: 40);
TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none");
UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
TagInput.Component.textComponent.color = Color.white;
TagInput.Component.GetOnEndEdit().AddListener((string val) => { OnTagEndEdit(val); });
// Instantiate
ButtonRef instantiateBtn = UIFactory.CreateButton(secondRow, "InstantiateBtn", "Instantiate", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(instantiateBtn.Component.gameObject, minHeight: 25, minWidth: 120);
instantiateBtn.OnClick += OnInstantiateClicked;
// Destroy
ButtonRef destroyBtn = UIFactory.CreateButton(secondRow, "DestroyBtn", "Destroy", new Color(0.3f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(destroyBtn.Component.gameObject, minHeight: 25, minWidth: 80);
destroyBtn.OnClick += OnDestroyClicked;
// third row (scene, layer, flags)
GameObject thirdrow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(thirdrow, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(thirdrow, minHeight: 25, flexibleWidth: 9999);
// Inspect in Explorer button
ButtonRef explorerBtn = UIFactory.CreateButton(thirdrow, "ExploreBtn", "Show in Explorer", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(explorerBtn.Component.gameObject, minHeight: 25, minWidth: 100);
explorerBtn.ButtonText.fontSize = 12;
explorerBtn.OnClick += OnExploreButtonClicked;
// Scene
Text sceneLabel = UIFactory.CreateLabel(thirdrow, "SceneLabel", "Scene:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(sceneLabel.gameObject, minHeight: 25, minWidth: 50);
SceneButton = UIFactory.CreateButton(thirdrow, "SceneButton", "untitled");
UIFactory.SetLayoutElement(SceneButton.Component.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 999);
SceneButton.OnClick += OnSceneButtonClicked;
// Layer
Text layerLabel = UIFactory.CreateLabel(thirdrow, "LayerLabel", "Layer:", TextAnchor.MiddleLeft, Color.grey);
UIFactory.SetLayoutElement(layerLabel.gameObject, minHeight: 25, minWidth: 50);
GameObject layerDrop = UIFactory.CreateDropdown(thirdrow, "LayerDropdown", out LayerDropdown, "0", 14, OnLayerDropdownChanged);
UIFactory.SetLayoutElement(layerDrop, minHeight: 25, minWidth: 110, flexibleWidth: 999);
LayerDropdown.captionText.color = SignatureHighlighter.EnumGreen;
if (layerToNames == null)
GetLayerNames();
foreach (string name in layerToNames)
LayerDropdown.options.Add(new Dropdown.OptionData(name));
LayerDropdown.value = 0;
LayerDropdown.RefreshShownValue();
// Flags
Text flagsLabel = UIFactory.CreateLabel(thirdrow, "FlagsLabel", "Flags:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(flagsLabel.gameObject, minHeight: 25, minWidth: 50);
GameObject flagsDrop = UIFactory.CreateDropdown(thirdrow, "FlagsDropdown", out FlagsDropdown, "None", 14, OnFlagsDropdownChanged);
FlagsDropdown.captionText.color = SignatureHighlighter.EnumGreen;
UIFactory.SetLayoutElement(flagsDrop, minHeight: 25, minWidth: 135, flexibleWidth: 999);
if (hideFlagsValues == null)
GetHideFlagNames();
foreach (string name in hideFlagsValues.Keys)
FlagsDropdown.options.Add(new Dropdown.OptionData(name));
FlagsDropdown.value = 0;
FlagsDropdown.RefreshShownValue();
}
private static List<string> layerToNames;
private static void GetLayerNames()
{
layerToNames = new List<string>();
for (int i = 0; i < 32; i++)
{
string name = RuntimeHelper.LayerToName(i);
if (string.IsNullOrEmpty(name))
name = i.ToString();
layerToNames.Add(name);
}
}
private static Dictionary<string, HideFlags> hideFlagsValues;
private static void GetHideFlagNames()
{
hideFlagsValues = new Dictionary<string, HideFlags>();
Array names = Enum.GetValues(typeof(HideFlags));
foreach (HideFlags value in names)
{
hideFlagsValues.Add(value.ToString(), value);
}
}
#endregion
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UniverseLib.Input;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
// Handles axis change operations and holds references to the Vector3Controls for each transform property
public class TransformControls
{
public GameObjectControls Owner { get; }
GameObject Target => Owner.Target;
public AxisControl CurrentSlidingAxisControl { get; set; }
Vector3Control PositionControl;
Vector3Control LocalPositionControl;
Vector3Control RotationControl;
Vector3Control ScaleControl;
public TransformControls(GameObjectControls owner)
{
this.Owner = owner;
Create();
}
public void UpdateTransformControlValues(bool force)
{
PositionControl.Update(force);
LocalPositionControl.Update(force);
RotationControl.Update(force);
ScaleControl.Update(force);
}
public void UpdateVectorSlider()
{
AxisControl control = CurrentSlidingAxisControl;
if (control == null)
return;
if (!InputManager.GetMouseButton(0))
{
control.slider.value = 0f;
control = null;
return;
}
AxisControlOperation(control.slider.value, control.parent, control.axis);
}
public void AxisControlOperation(float value, Vector3Control parent, int axis)
{
Transform transform = Target.transform;
Vector3 vector = parent.Type switch
{
TransformType.Position => transform.position,
TransformType.LocalPosition => transform.localPosition,
TransformType.Rotation => transform.localEulerAngles,
TransformType.Scale => transform.localScale,
_ => throw new NotImplementedException()
};
// apply vector value change
switch (axis)
{
case 0:
vector.x += value; break;
case 1:
vector.y += value; break;
case 2:
vector.z += value; break;
}
// set vector back to transform
switch (parent.Type)
{
case TransformType.Position:
transform.position = vector; break;
case TransformType.LocalPosition:
transform.localPosition = vector; break;
case TransformType.Rotation:
transform.localEulerAngles = vector; break;
case TransformType.Scale:
transform.localScale = vector; break;
}
UpdateTransformControlValues(false);
}
public void Create()
{
GameObject transformGroup = UIFactory.CreateVerticalGroup(Owner.Parent.Content, "TransformControls", false, false, true, true, 2,
new Vector4(2, 2, 0, 0), new Color(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(transformGroup, minHeight: 100, flexibleWidth: 9999);
PositionControl = Vector3Control.Create(this, transformGroup, "Position:", TransformType.Position);
LocalPositionControl = Vector3Control.Create(this, transformGroup, "Local Position:", TransformType.LocalPosition);
RotationControl = Vector3Control.Create(this, transformGroup, "Rotation:", TransformType.Rotation);
ScaleControl = Vector3Control.Create(this, transformGroup, "Scale:", TransformType.Scale);
}
}
}

View File

@ -0,0 +1,4 @@
namespace UnityExplorer.UI.Widgets
{
public enum TransformType { Position, LocalPosition, Rotation, Scale }
}

View File

@ -0,0 +1,131 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
// Controls a Vector3 property of a Transform, and holds references to each AxisControl for X/Y/Z.
public class Vector3Control
{
public TransformControls Owner { get; }
public GameObject Target => Owner.Owner.Target;
public Transform Transform => Target.transform;
public TransformType Type { get; }
public InputFieldRef MainInput { get; }
public AxisControl[] AxisControls { get; } = new AxisControl[3];
public InputFieldRef IncrementInput { get; set; }
public float Increment { get; set; } = 0.1f;
Vector3 lastValue;
Vector3 CurrentValue => Type switch
{
TransformType.Position => Transform.position,
TransformType.LocalPosition => Transform.localPosition,
TransformType.Rotation => Transform.localEulerAngles,
TransformType.Scale => Transform.localScale,
_ => throw new NotImplementedException()
};
public Vector3Control(TransformControls owner, TransformType type, InputFieldRef input)
{
this.Owner = owner;
this.Type = type;
this.MainInput = input;
}
public void Update(bool force)
{
Vector3 currValue = CurrentValue;
if (force || (!MainInput.Component.isFocused && !lastValue.Equals(currValue)))
{
MainInput.Text = ParseUtility.ToStringForInput<Vector3>(currValue);
lastValue = currValue;
}
}
void OnTransformInputEndEdit(TransformType type, string input)
{
switch (type)
{
case TransformType.Position:
{
if (ParseUtility.TryParse(input, out Vector3 val, out _))
Target.transform.position = val;
}
break;
case TransformType.LocalPosition:
{
if (ParseUtility.TryParse(input, out Vector3 val, out _))
Target.transform.localPosition = val;
}
break;
case TransformType.Rotation:
{
if (ParseUtility.TryParse(input, out Vector3 val, out _))
Target.transform.localEulerAngles = val;
}
break;
case TransformType.Scale:
{
if (ParseUtility.TryParse(input, out Vector3 val, out _))
Target.transform.localScale = val;
}
break;
}
Owner.UpdateTransformControlValues(true);
}
void IncrementInput_OnEndEdit(string value)
{
if (!ParseUtility.TryParse(value, out float increment, out _))
IncrementInput.Text = ParseUtility.ToStringForInput<float>(Increment);
else
{
Increment = increment;
foreach (AxisControl slider in AxisControls)
{
slider.slider.minValue = -increment;
slider.slider.maxValue = increment;
}
}
}
public static Vector3Control Create(TransformControls owner, GameObject transformGroup, string title, TransformType type)
{
GameObject rowObj = UIFactory.CreateUIObject($"Row_{title}", transformGroup);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999);
Text titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110);
InputFieldRef inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
Vector3Control control = new(owner, type, inputField);
inputField.Component.GetOnEndEdit().AddListener((string value) => { control.OnTransformInputEndEdit(type, value); });
control.AxisControls[0] = AxisControl.Create(rowObj, "X", 0, control);
control.AxisControls[1] = AxisControl.Create(rowObj, "Y", 1, control);
control.AxisControls[2] = AxisControl.Create(rowObj, "Z", 2, control);
control.IncrementInput = UIFactory.CreateInputField(rowObj, "IncrementInput", "...");
control.IncrementInput.Text = "0.1";
UIFactory.SetLayoutElement(control.IncrementInput.GameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25);
control.IncrementInput.Component.GetOnEndEdit().AddListener(control.IncrementInput_OnEndEdit);
return control;
}
}
}

View File

@ -0,0 +1,116 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
internal class TimeScaleWidget
{
public TimeScaleWidget(GameObject parent)
{
Instance = this;
ConstructUI(parent);
InitPatch();
}
static TimeScaleWidget Instance;
ButtonRef lockBtn;
bool locked;
InputFieldRef timeInput;
float desiredTime;
bool settingTimeScale;
public void Update()
{
// Fallback in case Time.timeScale patch failed for whatever reason
if (locked)
SetTimeScale(desiredTime);
if (!timeInput.Component.isFocused)
timeInput.Text = Time.timeScale.ToString("F2");
}
void SetTimeScale(float time)
{
settingTimeScale = true;
Time.timeScale = time;
settingTimeScale = false;
}
// UI event listeners
void OnTimeInputEndEdit(string val)
{
if (float.TryParse(val, out float f))
{
SetTimeScale(f);
desiredTime = f;
}
}
void OnPauseButtonClicked()
{
OnTimeInputEndEdit(timeInput.Text);
locked = !locked;
Color color = locked ? new Color(0.3f, 0.3f, 0.2f) : new Color(0.2f, 0.2f, 0.2f);
RuntimeHelper.SetColorBlock(lockBtn.Component, color, color * 1.2f, color * 0.7f);
lockBtn.ButtonText.text = locked ? "Unlock" : "Lock";
}
// UI Construction
void ConstructUI(GameObject parent)
{
Text timeLabel = UIFactory.CreateLabel(parent, "TimeLabel", "Time:", TextAnchor.MiddleRight, Color.grey);
UIFactory.SetLayoutElement(timeLabel.gameObject, minHeight: 25, minWidth: 35);
timeInput = UIFactory.CreateInputField(parent, "TimeInput", "timeScale");
UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40);
timeInput.Component.GetOnEndEdit().AddListener(OnTimeInputEndEdit);
timeInput.Text = string.Empty;
timeInput.Text = Time.timeScale.ToString();
lockBtn = UIFactory.CreateButton(parent, "PauseButton", "Lock", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(lockBtn.Component.gameObject, minHeight: 25, minWidth: 50);
lockBtn.OnClick += OnPauseButtonClicked;
}
// Only allow Time.timeScale to be set if the user hasn't "locked" it or if we are setting the value internally.
static void InitPatch()
{
try
{
MethodInfo target = AccessTools.Method(typeof(Time), nameof(Time.timeScale));
#if CPP
if (UnhollowerBaseLib.UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(target) == null)
return;
#endif
ExplorerCore.Harmony.Patch(target,
prefix: new(AccessTools.Method(typeof(TimeScaleWidget), nameof(Prefix_Time_set_timeScale))));
}
catch { }
}
static bool Prefix_Time_set_timeScale()
{
return !Instance.locked || Instance.settingTimeScale;
}
}
}

View File

@ -22,18 +22,18 @@ namespace UnityExplorer.UI.Widgets
static Coroutine CurrentlyPlayingCoroutine;
static readonly string zeroLengthString = GetLengthString(0f);
public AudioClip RefAudioClip;
private string fullLengthText;
public AudioClip audioClip;
string fullLengthText;
private ButtonRef toggleButton;
private bool audioPlayerWanted;
ButtonRef toggleButton;
bool audioPlayerWanted;
private GameObject audioPlayerRoot;
private ButtonRef playStopButton;
private Text progressLabel;
private GameObject saveObjectRow;
private InputFieldRef savePathInput;
private GameObject cantSaveRow;
GameObject audioPlayerRoot;
ButtonRef playStopButton;
Text progressLabel;
GameObject saveObjectRow;
InputFieldRef savePathInput;
GameObject cantSaveRow;
public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector)
{
@ -42,10 +42,10 @@ namespace UnityExplorer.UI.Widgets
this.audioPlayerRoot.transform.SetParent(inspector.UIRoot.transform);
this.audioPlayerRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2);
RefAudioClip = target.TryCast<AudioClip>();
this.fullLengthText = GetLengthString(RefAudioClip.length);
audioClip = target.TryCast<AudioClip>();
this.fullLengthText = GetLengthString(audioClip.length);
if (RefAudioClip.loadType == AudioClipLoadType.DecompressOnLoad)
if (audioClip.loadType == AudioClipLoadType.DecompressOnLoad)
{
cantSaveRow.SetActive(false);
saveObjectRow.SetActive(true);
@ -62,7 +62,7 @@ namespace UnityExplorer.UI.Widgets
public override void OnReturnToPool()
{
RefAudioClip = null;
audioClip = null;
if (audioPlayerWanted)
ToggleAudioWidget();
@ -95,7 +95,7 @@ namespace UnityExplorer.UI.Widgets
void SetDefaultSavePath()
{
string name = RefAudioClip.name;
string name = audioClip.name;
if (string.IsNullOrEmpty(name))
name = "untitled";
savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.wav");
@ -163,7 +163,7 @@ namespace UnityExplorer.UI.Widgets
{
playStopButton.ButtonText.text = "Stop Clip";
CurrentlyPlaying = this;
Source.clip = this.RefAudioClip;
Source.clip = this.audioClip;
Source.Play();
while (Source.isPlaying)
@ -191,7 +191,7 @@ namespace UnityExplorer.UI.Widgets
public void OnSaveClipClicked()
{
if (!RefAudioClip)
if (!audioClip)
{
ExplorerCore.LogWarning("AudioClip is null, maybe it was destroyed?");
return;
@ -212,7 +212,7 @@ namespace UnityExplorer.UI.Widgets
if (File.Exists(path))
File.Delete(path);
SavWav.Save(RefAudioClip, path);
SavWav.Save(audioClip, path);
}
public override GameObject CreateContent(GameObject uiRoot)

View File

@ -0,0 +1,344 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Config;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.Runtime;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.ObjectPool;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
public class MaterialWidget : UnityObjectWidget
{
static MaterialWidget()
{
mi_GetTexturePropertyNames = typeof(Material).GetMethod("GetTexturePropertyNames", ArgumentUtility.EmptyTypes);
MaterialWidgetSupported = mi_GetTexturePropertyNames != null;
}
internal static bool MaterialWidgetSupported { get; }
static readonly MethodInfo mi_GetTexturePropertyNames;
Material material;
Texture2D activeTexture;
readonly Dictionary<string, Texture> textures = new();
readonly HashSet<Texture2D> texturesToDestroy = new();
bool textureViewerWanted;
ButtonRef toggleButton;
GameObject textureViewerRoot;
Dropdown textureDropdown;
InputFieldRef savePathInput;
Image image;
LayoutElement imageLayout;
public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector)
{
base.OnBorrowed(target, targetType, inspector);
material = target.TryCast<Material>();
if (material.mainTexture)
SetActiveTexture(material.mainTexture);
if (mi_GetTexturePropertyNames.Invoke(material, ArgumentUtility.EmptyArgs) is IEnumerable<string> propNames)
{
foreach (string property in propNames)
{
if (material.GetTexture(property) is Texture texture)
{
if (texture.TryCast<Texture2D>() is null && texture.TryCast<Cubemap>() is null)
continue;
textures.Add(property, texture);
if (!activeTexture)
SetActiveTexture(texture);
}
}
}
if (textureViewerRoot)
{
textureViewerRoot.transform.SetParent(inspector.UIRoot.transform);
RefreshTextureDropdown();
}
InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize;
}
void SetActiveTexture(Texture texture)
{
if (texture.TryCast<Texture2D>() is Texture2D tex2D)
activeTexture = tex2D;
else if (texture.TryCast<Cubemap>() is Cubemap cubemap)
{
activeTexture = TextureHelper.UnwrapCubemap(cubemap);
texturesToDestroy.Add(activeTexture);
}
}
public override void OnReturnToPool()
{
InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize;
if (texturesToDestroy.Any())
{
foreach (Texture2D tex in texturesToDestroy)
UnityEngine.Object.Destroy(tex);
texturesToDestroy.Clear();
}
material = null;
activeTexture = null;
textures.Clear();
if (image.sprite)
UnityEngine.Object.Destroy(image.sprite);
if (textureViewerWanted)
ToggleTextureViewer();
if (textureViewerRoot)
textureViewerRoot.transform.SetParent(Pool<Texture2DWidget>.Instance.InactiveHolder.transform);
base.OnReturnToPool();
}
void ToggleTextureViewer()
{
if (textureViewerWanted)
{
// disable
textureViewerWanted = false;
textureViewerRoot.SetActive(false);
toggleButton.ButtonText.text = "View Material";
owner.ContentRoot.SetActive(true);
}
else
{
// enable
if (!image.sprite)
{
RefreshTextureViewer();
RefreshTextureDropdown();
}
SetImageSize();
textureViewerWanted = true;
textureViewerRoot.SetActive(true);
toggleButton.ButtonText.text = "Hide Material";
owner.ContentRoot.gameObject.SetActive(false);
}
}
void RefreshTextureViewer()
{
if (!this.activeTexture)
{
ExplorerCore.LogWarning($"Material has no active textures!");
savePathInput.Text = string.Empty;
return;
}
if (image.sprite)
UnityEngine.Object.Destroy(image.sprite);
string name = activeTexture.name;
if (string.IsNullOrEmpty(name))
name = "untitled";
savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png");
Sprite sprite = TextureHelper.CreateSprite(activeTexture);
image.sprite = sprite;
}
void RefreshTextureDropdown()
{
if (!textureDropdown)
return;
textureDropdown.options.Clear();
foreach (string key in textures.Keys)
textureDropdown.options.Add(new(key));
int i = 0;
foreach (Texture value in textures.Values)
{
if (activeTexture.ReferenceEqual(value))
{
textureDropdown.value = i;
break;
}
i++;
}
textureDropdown.RefreshShownValue();
}
void OnTextureDropdownChanged(int value)
{
Texture tex = textures.ElementAt(value).Value;
if (activeTexture.ReferenceEqual(tex))
return;
SetActiveTexture(tex);
RefreshTextureViewer();
}
void OnInspectorFinishResize()
{
SetImageSize();
}
void SetImageSize()
{
if (!imageLayout)
return;
RuntimeHelper.StartCoroutine(SetImageSizeCoro());
}
IEnumerator SetImageSizeCoro()
{
if (!activeTexture)
yield break;
// let unity rebuild layout etc
yield return null;
RectTransform imageRect = InspectorPanel.Instance.Rect;
float rectWidth = imageRect.rect.width - 25;
float rectHeight = imageRect.rect.height - 196;
// If our image is smaller than the viewport, just use 100% scaling
if (activeTexture.width < rectWidth && activeTexture.height < rectHeight)
{
imageLayout.minWidth = activeTexture.width;
imageLayout.minHeight = activeTexture.height;
}
else // we will need to scale down the image to fit
{
// get the ratio of our viewport dimensions to width and height
float viewWidthRatio = (float)((decimal)rectWidth / (decimal)activeTexture.width);
float viewHeightRatio = (float)((decimal)rectHeight / (decimal)activeTexture.height);
// if width needs to be scaled more than height
if (viewWidthRatio < viewHeightRatio)
{
imageLayout.minWidth = activeTexture.width * viewWidthRatio;
imageLayout.minHeight = activeTexture.height * viewWidthRatio;
}
else // if height needs to be scaled more than width
{
imageLayout.minWidth = activeTexture.width * viewHeightRatio;
imageLayout.minHeight = activeTexture.height * viewHeightRatio;
}
}
}
void OnSaveTextureClicked()
{
if (!activeTexture)
{
ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?");
return;
}
if (string.IsNullOrEmpty(savePathInput.Text))
{
ExplorerCore.LogWarning("Save path cannot be empty!");
return;
}
string path = savePathInput.Text;
if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
path += ".png";
path = IOUtility.EnsureValidFilePath(path);
if (File.Exists(path))
File.Delete(path);
TextureHelper.SaveTextureAsPNG(activeTexture, path);
}
public override GameObject CreateContent(GameObject uiRoot)
{
GameObject ret = base.CreateContent(uiRoot);
// Button
toggleButton = UIFactory.CreateButton(UIRoot, "MaterialButton", "View Material", new Color(0.2f, 0.3f, 0.2f));
toggleButton.Transform.SetSiblingIndex(0);
UIFactory.SetLayoutElement(toggleButton.Component.gameObject, minHeight: 25, minWidth: 150);
toggleButton.OnClick += ToggleTextureViewer;
// Texture viewer
textureViewerRoot = UIFactory.CreateVerticalGroup(uiRoot, "MaterialViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5),
new Color(0.1f, 0.1f, 0.1f), childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(textureViewerRoot, flexibleWidth: 9999, flexibleHeight: 9999);
// Buttons holder
GameObject dropdownRow = UIFactory.CreateHorizontalGroup(textureViewerRoot, "DropdownRow", false, true, true, true, 5, new(3, 3, 3, 3));
UIFactory.SetLayoutElement(dropdownRow, minHeight: 30, flexibleWidth: 9999);
Text dropdownLabel = UIFactory.CreateLabel(dropdownRow, "DropdownLabel", "Texture:");
UIFactory.SetLayoutElement(dropdownLabel.gameObject, minWidth: 75, minHeight: 25);
GameObject dropdownObj = UIFactory.CreateDropdown(dropdownRow, "TextureDropdown", out textureDropdown, "NOT SET", 13, OnTextureDropdownChanged);
UIFactory.SetLayoutElement(dropdownObj, minWidth: 350, minHeight: 25);
// Save helper
GameObject saveRowObj = UIFactory.CreateHorizontalGroup(textureViewerRoot, "SaveRow", false, false, true, true, 2, new Vector4(2, 2, 2, 2),
new Color(0.1f, 0.1f, 0.1f));
ButtonRef saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", new Color(0.2f, 0.25f, 0.2f));
UIFactory.SetLayoutElement(saveBtn.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0);
saveBtn.OnClick += OnSaveTextureClicked;
savePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...");
UIFactory.SetLayoutElement(savePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
// Actual texture viewer
GameObject imageViewport = UIFactory.CreateVerticalGroup(textureViewerRoot, "ImageViewport", false, false, true, true,
bgColor: new(1, 1, 1, 0), childAlignment: TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(imageViewport, flexibleWidth: 9999, flexibleHeight: 9999);
GameObject imageHolder = UIFactory.CreateUIObject("ImageHolder", imageViewport);
imageLayout = UIFactory.SetLayoutElement(imageHolder, 1, 1, 0, 0);
GameObject actualImageObj = UIFactory.CreateUIObject("ActualImage", imageHolder);
RectTransform actualRect = actualImageObj.GetComponent<RectTransform>();
actualRect.anchorMin = new(0, 0);
actualRect.anchorMax = new(1, 1);
image = actualImageObj.AddComponent<Image>();
textureViewerRoot.SetActive(false);
return ret;
}
}
}

View File

@ -17,29 +17,51 @@ namespace UnityExplorer.UI.Widgets
{
public class Texture2DWidget : UnityObjectWidget
{
private Texture2D TextureRef;
private float realWidth;
private float realHeight;
Texture2D texture;
bool shouldDestroyTexture;
private bool textureViewerWanted;
private ButtonRef toggleButton;
bool textureViewerWanted;
ButtonRef toggleButton;
private GameObject textureViewerRoot;
private InputFieldRef savePathInput;
private Image image;
private LayoutElement imageLayout;
GameObject textureViewerRoot;
InputFieldRef savePathInput;
Image image;
LayoutElement imageLayout;
public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector)
{
base.OnBorrowed(target, targetType, inspector);
TextureRef = target.TryCast<Texture2D>();
if (target.TryCast<Cubemap>() is Cubemap cubemap)
{
texture = TextureHelper.UnwrapCubemap(cubemap);
shouldDestroyTexture = true;
}
else if (target.TryCast<Sprite>() is Sprite sprite)
{
if (sprite.packingMode == SpritePackingMode.Tight)
texture = sprite.texture;
else
{
texture = TextureHelper.CopyTexture(sprite.texture, sprite.textureRect);
shouldDestroyTexture = true;
}
}
else if (target.TryCast<Image>() is Image image)
{
if (image.sprite.packingMode == SpritePackingMode.Tight)
texture = image.sprite.texture;
else
{
texture = TextureHelper.CopyTexture(image.sprite.texture, image.sprite.textureRect);
shouldDestroyTexture = true;
}
}
else
texture = target.TryCast<Texture2D>();
realWidth = TextureRef.width;
realHeight = TextureRef.height;
if (this.textureViewerRoot)
this.textureViewerRoot.transform.SetParent(inspector.UIRoot.transform);
if (textureViewerRoot)
textureViewerRoot.transform.SetParent(inspector.UIRoot.transform);
InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize;
}
@ -48,21 +70,25 @@ namespace UnityExplorer.UI.Widgets
{
InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize;
TextureRef = null;
if (shouldDestroyTexture)
UnityEngine.Object.Destroy(texture);
texture = null;
shouldDestroyTexture = false;
if (image.sprite)
GameObject.Destroy(image.sprite);
UnityEngine.Object.Destroy(image.sprite);
if (textureViewerWanted)
ToggleTextureViewer();
if (this.textureViewerRoot)
this.textureViewerRoot.transform.SetParent(Pool<Texture2DWidget>.Instance.InactiveHolder.transform);
if (textureViewerRoot)
textureViewerRoot.transform.SetParent(Pool<Texture2DWidget>.Instance.InactiveHolder.transform);
base.OnReturnToPool();
}
private void ToggleTextureViewer()
void ToggleTextureViewer()
{
if (textureViewerWanted)
{
@ -71,7 +97,7 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(false);
toggleButton.ButtonText.text = "View Texture";
ParentInspector.mainContentHolder.SetActive(true);
owner.ContentRoot.SetActive(true);
}
else
{
@ -85,30 +111,30 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(true);
toggleButton.ButtonText.text = "Hide Texture";
ParentInspector.mainContentHolder.gameObject.SetActive(false);
owner.ContentRoot.gameObject.SetActive(false);
}
}
private void SetupTextureViewer()
void SetupTextureViewer()
{
if (!this.TextureRef)
if (!this.texture)
return;
string name = TextureRef.name;
string name = texture.name;
if (string.IsNullOrEmpty(name))
name = "untitled";
savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png");
Sprite sprite = TextureHelper.CreateSprite(TextureRef);
Sprite sprite = TextureHelper.CreateSprite(texture);
image.sprite = sprite;
}
private void OnInspectorFinishResize()
void OnInspectorFinishResize()
{
SetImageSize();
}
private void SetImageSize()
void SetImageSize()
{
if (!imageLayout)
return;
@ -127,34 +153,34 @@ namespace UnityExplorer.UI.Widgets
float rectHeight = imageRect.rect.height - 196;
// If our image is smaller than the viewport, just use 100% scaling
if (realWidth < rectWidth && realHeight < rectHeight)
if (texture.width < rectWidth && texture.height < rectHeight)
{
imageLayout.minWidth = realWidth;
imageLayout.minHeight = realHeight;
imageLayout.minWidth = texture.width;
imageLayout.minHeight = texture.height;
}
else // we will need to scale down the image to fit
{
// get the ratio of our viewport dimensions to width and height
float viewWidthRatio = (float)((decimal)rectWidth / (decimal)realWidth);
float viewHeightRatio = (float)((decimal)rectHeight / (decimal)realHeight);
float viewWidthRatio = (float)((decimal)rectWidth / (decimal)texture.width);
float viewHeightRatio = (float)((decimal)rectHeight / (decimal)texture.height);
// if width needs to be scaled more than height
if (viewWidthRatio < viewHeightRatio)
{
imageLayout.minWidth = realWidth * viewWidthRatio;
imageLayout.minHeight = realHeight * viewWidthRatio;
imageLayout.minWidth = texture.width * viewWidthRatio;
imageLayout.minHeight = texture.height * viewWidthRatio;
}
else // if height needs to be scaled more than width
{
imageLayout.minWidth = realWidth * viewHeightRatio;
imageLayout.minHeight = realHeight * viewHeightRatio;
imageLayout.minWidth = texture.width * viewHeightRatio;
imageLayout.minHeight = texture.height * viewHeightRatio;
}
}
}
private void OnSaveTextureClicked()
void OnSaveTextureClicked()
{
if (!TextureRef)
if (!texture)
{
ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?");
return;
@ -175,18 +201,7 @@ namespace UnityExplorer.UI.Widgets
if (File.Exists(path))
File.Delete(path);
Texture2D tex = TextureRef;
if (!TextureHelper.IsReadable(tex))
tex = TextureHelper.ForceReadTexture(tex);
byte[] data = TextureHelper.EncodeToPNG(tex);
File.WriteAllBytes(path, data);
if (tex != TextureRef)
{
// cleanup temp texture if we had to force-read it.
GameObject.Destroy(tex);
}
TextureHelper.SaveTextureAsPNG(texture, path);
}
public override GameObject CreateContent(GameObject uiRoot)

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Inspectors;
@ -11,9 +12,9 @@ namespace UnityExplorer.UI.Widgets
{
public class UnityObjectWidget : IPooledObject
{
public UnityEngine.Object UnityObjectRef;
public Component ComponentRef;
public ReflectionInspector ParentInspector;
public UnityEngine.Object unityObject;
public Component component;
public ReflectionInspector owner;
protected ButtonRef gameObjectButton;
protected InputFieldRef nameInput;
@ -30,8 +31,14 @@ namespace UnityExplorer.UI.Widgets
UnityObjectWidget widget = target switch
{
Texture2D => Pool<Texture2DWidget>.Borrow(),
Texture2D or Cubemap => Pool<Texture2DWidget>.Borrow(),
Sprite s when s.texture => Pool<Texture2DWidget>.Borrow(),
Image i when i.sprite?.texture => Pool<Texture2DWidget>.Borrow(),
Material when MaterialWidget.MaterialWidgetSupported => Pool<MaterialWidget>.Borrow(),
AudioClip => Pool<AudioClipWidget>.Borrow(),
_ => Pool<UnityObjectWidget>.Borrow()
};
@ -42,7 +49,7 @@ namespace UnityExplorer.UI.Widgets
public virtual void OnBorrowed(object target, Type targetType, ReflectionInspector inspector)
{
this.ParentInspector = inspector ?? throw new ArgumentNullException(nameof(inspector));
this.owner = inspector;
if (!this.UIRoot)
CreateContent(inspector.UIRoot);
@ -51,15 +58,15 @@ namespace UnityExplorer.UI.Widgets
this.UIRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2);
UnityObjectRef = target.TryCast<UnityEngine.Object>();
unityObject = target.TryCast<UnityEngine.Object>();
UIRoot.SetActive(true);
nameInput.Text = UnityObjectRef.name;
instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString();
nameInput.Text = unityObject.name;
instanceIdInput.Text = unityObject.GetInstanceID().ToString();
if (typeof(Component).IsAssignableFrom(targetType))
{
ComponentRef = (Component)target.TryCast(typeof(Component));
component = (Component)target.TryCast(typeof(Component));
gameObjectButton.Component.gameObject.SetActive(true);
}
else
@ -68,19 +75,20 @@ namespace UnityExplorer.UI.Widgets
public virtual void OnReturnToPool()
{
UnityObjectRef = null;
ComponentRef = null;
ParentInspector = null;
unityObject = null;
component = null;
owner = null;
}
// Update
public virtual void Update()
{
if (this.UnityObjectRef)
if (this.unityObject)
{
nameInput.Text = UnityObjectRef.name;
ParentInspector.Tab.TabText.text = $"{ParentInspector.currentBaseTabText} \"{UnityObjectRef.name}\"";
nameInput.Text = unityObject.name;
owner.Tab.TabText.text = $"{owner.TabButtonText} \"{unityObject.name}\"";
}
}
@ -88,13 +96,13 @@ namespace UnityExplorer.UI.Widgets
private void OnGameObjectButtonClicked()
{
if (!ComponentRef)
if (!component)
{
ExplorerCore.LogWarning("Component reference is null or destroyed!");
return;
}
InspectorManager.Inspect(ComponentRef.gameObject);
InspectorManager.Inspect(component.gameObject);
}
// UI construction

View File

@ -78,12 +78,12 @@
</ItemGroup>
<!-- il2cpp nuget -->
<ItemGroup Condition="'$(Configuration)'=='ML_Cpp_net6' or '$(Configuration)'=='ML_Cpp_net472' or '$(Configuration)'=='STANDALONE_Cpp' or '$(Configuration)'=='BIE_Cpp'">
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" IncludeAssets="compile" />
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.7" />
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" />
<PackageReference Include="UniverseLib.IL2CPP" Version="1.4.2" />
</ItemGroup>
<!-- mono nuget -->
<ItemGroup Condition="'$(Configuration)'=='BIE6_Mono' or '$(Configuration)'=='BIE5_Mono' or '$(Configuration)'=='ML_Mono' or '$(Configuration)'=='STANDALONE_Mono'">
<PackageReference Include="UniverseLib.Mono" Version="1.3.7" />
<PackageReference Include="UniverseLib.Mono" Version="1.4.2" />
</ItemGroup>
<!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ -->