Compare commits

...

70 Commits
4.7.5 ... 4.8.0

Author SHA1 Message Date
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
58 changed files with 2778 additions and 1666 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) ⚡ 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 ## BepInEx
| Release | IL2CPP | Mono | | Release | IL2CPP | Mono |
@ -32,10 +38,10 @@
## MelonLoader ## MelonLoader
| Release | IL2CPP | Mono | | 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.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) | ✖️ | | 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 1. Unzip the release file into a folder
2. Copy the DLL inside the `Mods` folder into your MelonLoader `Mods` folder 2. Copy the DLL inside the `Mods` folder into your MelonLoader `Mods` folder
@ -104,7 +110,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. * 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 * 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 * 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 ### C# Console
@ -115,7 +122,7 @@ The inspector is used to see detailed information on objects of any type and man
### Hook Manager ### Hook Manager
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes. * 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. * 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 ### Mouse-Inspect
@ -124,6 +131,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>World</b>: uses Physics.Raycast to look for Colliders
* <b>UI</b>: uses GraphicRaycasters to find UI objects * <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 ### Clipboard
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`) * 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", "name": "com.sinai-dev.unityexplorer",
"version": "4.7.3", "version": "4.7.12",
"displayName": "UnityExplorer", "displayName": "UnityExplorer",
"description": "An in-game UI for exploring, debugging and modifying Unity games.", "description": "An in-game UI for exploring, debugging and modifying Unity games.",
"unity": "2017.1", "unity": "2017.1",

View File

@ -1,97 +1,125 @@
# MelonLoader IL2CPP (net6) # ----------- MelonLoader IL2CPP (net6) -----------
dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net6 dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net6
# (cleanup and move files)
$Path = "Release\UnityExplorer.MelonLoader.IL2CPP.net6preview" $Path = "Release\UnityExplorer.MelonLoader.IL2CPP.net6preview"
# 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\UnityExplorer.ML.IL2CPP.net6preview.deps.json
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 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 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) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.net6preview.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.net6preview.zip -Force
# MelonLoader IL2CPP (net472) # ----------- MelonLoader IL2CPP (net472) -----------
dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net472 dotnet build src\UnityExplorer.sln -c Release_ML_Cpp_net472
# (cleanup and move files)
$Path = "Release\UnityExplorer.MelonLoader.IL2CPP" $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)
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 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 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) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.IL2CPP.zip -Force
# MelonLoader Mono # ----------- MelonLoader Mono -----------
dotnet build src\UnityExplorer.sln -c Release_ML_Mono dotnet build src\UnityExplorer.sln -c Release_ML_Mono
# (cleanup and move files)
$Path = "Release\UnityExplorer.MelonLoader.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)
Remove-Item $Path\Tomlet.dll Remove-Item $Path\Tomlet.dll
Remove-Item $Path\mcs.dll
New-Item -Path "$Path" -Name "Mods" -ItemType "directory" -Force 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 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) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.Mono.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.MelonLoader.Mono.zip -Force
# BepInEx IL2CPP # ----------- BepInEx IL2CPP -----------
dotnet build src\UnityExplorer.sln -c Release_BIE_Cpp dotnet build src\UnityExplorer.sln -c Release_BIE_Cpp
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx.IL2CPP" $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)
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" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -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\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 Move-Item -Path $Path\UniverseLib.IL2CPP.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
# (create zip archive) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx.IL2CPP.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx.IL2CPP.zip -Force
# BepInEx 5 Mono # ----------- BepInEx 5 Mono -----------
dotnet build src\UnityExplorer.sln -c Release_BIE5_Mono dotnet build src\UnityExplorer.sln -c Release_BIE5_Mono
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx5.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)
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" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -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\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 Move-Item -Path $Path\UniverseLib.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
# (create zip archive) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx5.Mono.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx5.Mono.zip -Force
# BepInEx 6 Mono # ----------- BepInEx 6 Mono -----------
dotnet build src\UnityExplorer.sln -c Release_BIE6_Mono dotnet build src\UnityExplorer.sln -c Release_BIE6_Mono
# (cleanup and move files)
$Path = "Release\UnityExplorer.BepInEx6.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)
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" -ItemType "directory" -Force
New-Item -Path "$Path" -Name "plugins\sinai-dev-UnityExplorer" -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\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 Move-Item -Path $Path\UniverseLib.Mono.dll -Destination $Path\plugins\sinai-dev-UnityExplorer -Force
# (create zip archive) # (create zip archive)
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx6.Mono.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.BepInEx6.Mono.zip -Force
# Standalone Mono # ----------- Standalone Mono -----------
dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Mono dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Mono
$Path = "Release\UnityExplorer.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
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.Mono.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.Mono.zip -Force
# Standalone IL2CPP # ----------- Standalone IL2CPP -----------
dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Cpp dotnet build src\UnityExplorer.sln -c Release_STANDALONE_Cpp
$Path = "Release\UnityExplorer.Standalone.IL2CPP" $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
Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.IL2CPP.zip -Force Compress-Archive -Path $Path\* -CompressionLevel Fastest -DestinationPath $Path\..\UnityExplorer.Standalone.IL2CPP.zip -Force
# Editor (mono) # ----------- Editor (mono) -----------
$Path1 = "Release\UnityExplorer.Standalone.Mono" $Path1 = "Release\UnityExplorer.Standalone.Mono"
$Path2 = "UnityEditorPackage\Runtime" $Path2 = "UnityEditorPackage\Runtime"
Copy-Item $Path1\UnityExplorer.STANDALONE.Mono.dll -Destination $Path2 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 Copy-Item $Path1\UniverseLib.Mono.dll -Destination $Path2
Compress-Archive -Path UnityEditorPackage\* -CompressionLevel Fastest -DestinationPath Release\UnityExplorer.Editor.zip -Force Compress-Archive -Path UnityEditorPackage\* -CompressionLevel Fastest -DestinationPath Release\UnityExplorer.Editor.zip -Force

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,28 +23,33 @@ namespace UnityExplorer.CSConsole
{ {
public static class ConsoleController public static class ConsoleController
{ {
public static ScriptEvaluator Evaluator; public static ScriptEvaluator Evaluator { get; private set; }
public static LexerBuilder Lexer; public static LexerBuilder Lexer { get; private set; }
public static CSAutoCompleter Completer; public static CSAutoCompleter Completer { get; private set; }
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 bool SRENotSupported { get; private set; }
public static int LastCaretPosition { 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 EnableCtrlRShortcut { get; private set; } = true;
public static bool EnableAutoIndent { get; private set; } = true; public static bool EnableAutoIndent { get; private set; } = true;
public static bool EnableSuggestions { get; private set; } = true; public static bool EnableSuggestions { get; private set; } = true;
internal static string ScriptsFolder => Path.Combine(ExplorerCore.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",
"System.Linq", "System.Linq",
@ -59,9 +64,10 @@ namespace UnityExplorer.CSConsole
#endif #endif
}; };
const int CSCONSOLE_LINEHEIGHT = 18;
public static void Init() public static void Init()
{ {
// Make sure console is supported on this platform
try try
{ {
ResetConsole(false); ResetConsole(false);
@ -111,31 +117,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 #region Evaluating
private static void GenerateTextWriter() static void GenerateTextWriter()
{ {
evaluatorOutput = new StringBuilder(); evaluatorOutput = new StringBuilder();
evaluatorStringWriter = new StringWriter(evaluatorOutput); evaluatorStringWriter = new StringWriter(evaluatorOutput);
@ -247,16 +232,48 @@ namespace UnityExplorer.CSConsole
#endregion #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 if (!settingCaretCoroutine && EnableSuggestions)
private static void OnInputChanged(string value) {
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) if (SRENotSupported)
return; return;
@ -283,7 +300,7 @@ namespace UnityExplorer.CSConsole
DoAutoIndent(); DoAutoIndent();
} }
bool inStringOrComment = HighlightVisibleInput(); HighlightVisibleInput(out bool inStringOrComment);
if (!settingCaretCoroutine) if (!settingCaretCoroutine)
{ {
@ -299,40 +316,27 @@ namespace UnityExplorer.CSConsole
UpdateCaret(out _); UpdateCaret(out _);
} }
private static float timeOfLastCtrlR; static void OnToggleAutoIndent(bool value)
public static void Update()
{ {
if (SRENotSupported) EnableAutoIndent = value;
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);
}
} }
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; int prevCaret = LastCaretPosition;
caretMoved = false; caretMoved = false;
@ -377,52 +381,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; settingCaretCoroutine = true;
Input.Component.readOnly = 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 color = Input.Component.selectionColor;
color.a = 0f; color.a = 0f;
Input.Component.selectionColor = color; Input.Component.selectionColor = color;
EventSystemHelper.SetSelectedGameObject(null); EventSystemHelper.SetSelectionGuard(false);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
EventSystemHelper.SetSelectedGameObject(null);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
Input.Component.Select(); Input.Component.Select();
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
Input.Component.caretPosition = caretPosition; Input.Component.caretPosition = caretPosition;
Input.Component.selectionFocusPosition = caretPosition; Input.Component.selectionFocusPosition = caretPosition;
LastCaretPosition = Input.Component.caretPosition; LastCaretPosition = Input.Component.caretPosition;
color.a = defaultInputFieldAlpha; color.a = DefaultInputFieldAlpha;
Input.Component.selectionColor = color; Input.Component.selectionColor = color;
Input.Component.readOnly = false; Input.Component.readOnly = false;
settingCaretCoroutine = 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 #region Lexer Highlighting
/// <summary> private static void HighlightVisibleInput(out bool inStringOrComment)
/// Returns true if caret is inside string or comment, false otherwise
/// </summary>
private static bool HighlightVisibleInput()
{ {
inStringOrComment = false;
if (string.IsNullOrEmpty(Input.Text)) if (string.IsNullOrEmpty(Input.Text))
{ {
Panel.HighlightText.text = ""; Panel.HighlightText.text = "";
Panel.LineNumberText.text = "1"; Panel.LineNumberText.text = "1";
return false; return;
} }
// Calculate the visible lines // Calculate the visible lines
@ -457,7 +504,7 @@ namespace UnityExplorer.CSConsole
// Highlight the visible text with the LexerBuilder // Highlight the visible text with the LexerBuilder
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret); Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out inStringOrComment);
// Set the line numbers // Set the line numbers
@ -495,7 +542,7 @@ namespace UnityExplorer.CSConsole
Panel.LineNumberText.text = sb.ToString(); Panel.LineNumberText.text = sb.ToString();
return ret; return;
} }
#endregion #endregion
@ -535,13 +582,11 @@ namespace UnityExplorer.CSConsole
#region Auto indenting #region Auto indenting
private static int prevContentLen = 0;
private static void DoAutoIndent() 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) if (inc == 1)
{ {
@ -560,7 +605,7 @@ namespace UnityExplorer.CSConsole
} }
} }
prevContentLen = Input.Text.Length; previousContentLength = Input.Text.Length;
} }
#endregion #endregion
@ -568,8 +613,6 @@ namespace UnityExplorer.CSConsole
#region "Help" interaction #region "Help" interaction
private static bool SRENotSupported;
private static void DisableConsole(Exception ex) private static void DisableConsole(Exception ex)
{ {
SRENotSupported = true; SRENotSupported = true;

View File

@ -17,8 +17,6 @@ namespace UnityExplorer.CSConsole
public class LexerBuilder public class LexerBuilder
{ {
#region Core and initialization
public const char WHITESPACE = ' '; public const char WHITESPACE = ' ';
public readonly HashSet<char> IndentOpenChars = new() { '{', '(' }; public readonly HashSet<char> IndentOpenChars = new() { '{', '(' };
public readonly HashSet<char> IndentCloseChars = 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> /// <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; } public int CommittedIndex { get; private set; }
/// <summary>The index of the character we are currently parsing, at minimum it will be CommittedIndex + 1.</summary> /// <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)) if (IsSymbol(lexer.Current))
{ {
do lexer.Commit();
{
lexer.Commit();
lexer.PeekNext();
}
while (IsSymbol(lexer.Current));
return true; return true;
} }

View File

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

View File

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

View File

@ -16,17 +16,17 @@ namespace UnityExplorer.Config
// Actual UE Settings // Actual UE Settings
public static ConfigElement<KeyCode> Master_Toggle; 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<bool> Hide_On_Startup;
public static ConfigElement<float> Startup_Delay_Time; public static ConfigElement<float> Startup_Delay_Time;
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<string> Reflection_Signature_Blacklist; public static ConfigElement<string> Reflection_Signature_Blacklist;
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
public static ConfigElement<KeyCode> World_MouseInspect_Keybind; public static ConfigElement<KeyCode> World_MouseInspect_Keybind;
public static ConfigElement<KeyCode> UI_MouseInspect_Keybind; public static ConfigElement<KeyCode> UI_MouseInspect_Keybind;
@ -54,6 +54,10 @@ namespace UnityExplorer.Config
Handler.LoadConfig(); Handler.LoadConfig();
InternalHandler.LoadConfig(); InternalHandler.LoadConfig();
#if STANDALONE
Loader.Standalone.ExplorerEditorBehaviour.Instance?.LoadConfigs();
#endif
//InitConsoleCallback(); //InitConsoleCallback();
} }
@ -81,23 +85,15 @@ namespace UnityExplorer.Config
"Should UnityExplorer be hidden on startup?", "Should UnityExplorer be hidden on startup?",
false); false);
Startup_Delay_Time = new ConfigElement<float>("Startup Delay Time",
"The delay on startup before the UI is created.",
1f);
Target_Display = new ConfigElement<int>("Target Display", Target_Display = new ConfigElement<int>("Target Display",
"The monitor index for UnityExplorer to use, if you have multiple. 0 is the default display, 1 is secondary, etc. " + "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.", "Restart recommended when changing this setting. Make sure your extra monitors are the same resolution as your primary monitor.",
0); 0);
Main_Navbar_Anchor = new ConfigElement<UIManager.VerticalAnchor>("Main Navbar Anchor",
"The vertical anchor of the main UnityExplorer Navbar, in case you want to move it.",
UIManager.VerticalAnchor.Top);
World_MouseInspect_Keybind = new("World Mouse-Inspect Keybind",
"Optional keybind to being a World-mode Mouse Inspect.",
KeyCode.None);
UI_MouseInspect_Keybind = new("UI Mouse-Inspect Keybind",
"Optional keybind to begin a UI-mode Mouse Inspect.",
KeyCode.None);
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse", Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.", "Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true); true);
@ -112,17 +108,29 @@ namespace UnityExplorer.Config
false); false);
Disable_EventSystem_Override.OnValueChanged += (bool value) => UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value; 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", Default_Output_Path = new ConfigElement<string>("Default Output Path",
"The default output path when exporting things from UnityExplorer.", "The default output path when exporting things from UnityExplorer.",
Path.Combine(ExplorerCore.ExplorerFolder, "Output")); Path.Combine(ExplorerCore.ExplorerFolder, "Output"));
Startup_Delay_Time = new ConfigElement<float>("Startup Delay Time", DnSpy_Path = new ConfigElement<string>("dnSpy Path",
"The delay on startup before the UI is created.", "The full path to dnSpy.exe (64-bit).",
1f); @"C:/Program Files/dnspy/dnSpy.exe");
Main_Navbar_Anchor = new ConfigElement<UIManager.VerticalAnchor>("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 ConfigElement<bool>("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);
UI_MouseInspect_Keybind = new("UI Mouse-Inspect Keybind",
"Optional keybind to begin a UI-mode Mouse Inspect.",
KeyCode.None);
Reflection_Signature_Blacklist = new ConfigElement<string>("Member Signature Blacklist", Reflection_Signature_Blacklist = new ConfigElement<string>("Member Signature Blacklist",
"Use this to blacklist certain member signatures if they are known to cause a crash or other issues.\r\n" + "Use this to blacklist certain member signatures if they are known to cause a crash or other issues.\r\n" +

View File

@ -1,4 +1,8 @@
using UnityEngine; using System;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UniverseLib;
#if CPP #if CPP
using UnhollowerRuntimeLib; using UnhollowerRuntimeLib;
#endif #endif
@ -29,5 +33,39 @@ namespace UnityExplorer
{ {
ExplorerCore.Update(); 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 static class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; public const string NAME = "UnityExplorer";
public const string VERSION = "4.7.5"; public const string VERSION = "4.8.0";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";

View File

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

View File

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

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

@ -0,0 +1,339 @@
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);
}
}
// 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 HarmonyLib;
using Mono.CSharp; using Mono.CSharp;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -14,12 +15,13 @@ namespace UnityExplorer.Hooks
{ {
// Static // Static
private static readonly StringBuilder evalOutput = new(); static readonly StringBuilder evaluatorOutput;
private static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evalOutput)); static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder()));
static HookInstance() static HookInstance()
{ {
scriptEvaluator.Run("using System;"); scriptEvaluator.Run("using System;");
scriptEvaluator.Run("using System.Text;");
scriptEvaluator.Run("using System.Reflection;"); scriptEvaluator.Run("using System.Reflection;");
scriptEvaluator.Run("using System.Collections;"); scriptEvaluator.Run("using System.Collections;");
scriptEvaluator.Run("using System.Collections.Generic;"); scriptEvaluator.Run("using System.Collections.Generic;");
@ -28,21 +30,22 @@ namespace UnityExplorer.Hooks
// Instance // Instance
public bool Enabled; public bool Enabled;
public MethodInfo TargetMethod; public MethodInfo TargetMethod;
public string PatchSourceCode; public string PatchSourceCode;
private readonly string shortSignature; readonly string signature;
private PatchProcessor patchProcessor; PatchProcessor patchProcessor;
private MethodInfo postfix; MethodInfo postfix;
private MethodInfo prefix; MethodInfo prefix;
private MethodInfo finalizer; MethodInfo finalizer;
private MethodInfo transpiler; MethodInfo transpiler;
public HookInstance(MethodInfo targetMethod) public HookInstance(MethodInfo targetMethod)
{ {
this.TargetMethod = targetMethod; this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}"; this.signature = TargetMethod.FullDescription();
GenerateDefaultPatchSourceCode(targetMethod); GenerateDefaultPatchSourceCode(targetMethod);
@ -59,15 +62,15 @@ namespace UnityExplorer.Hooks
{ {
Unpatch(); Unpatch();
StringBuilder codeBuilder = new();
try try
{ {
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod); patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method // Dynamically compile the patch method
StringBuilder codeBuilder = new(); codeBuilder.AppendLine($"static class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{"); codeBuilder.AppendLine("{");
codeBuilder.AppendLine(patchSource); codeBuilder.AppendLine(patchSource);
codeBuilder.AppendLine("}"); codeBuilder.AppendLine("}");
@ -107,85 +110,108 @@ namespace UnityExplorer.Hooks
} }
catch (Exception ex) 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; 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) private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{ {
StringBuilder codeBuilder = new(); StringBuilder codeBuilder = new();
// Arguments
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod"); codeBuilder.Append("static void Postfix(");
if (!targetMethod.IsStatic) bool isStatic = targetMethod.IsStatic;
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
List<string> arguments = new();
if (!isStatic)
arguments.Add($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance");
if (targetMethod.ReturnType != typeof(void)) if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result"); arguments.Add($"{FullDescriptionClean(targetMethod.ReturnType)} __result");
ParameterInfo[] parameters = targetMethod.GetParameters(); ParameterInfo[] parameters = targetMethod.GetParameters();
int paramIdx = 0; int paramIdx = 0;
foreach (ParameterInfo param in parameters) foreach (ParameterInfo param in parameters)
{ {
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}"); arguments.Add($"{FullDescriptionClean(param.ParameterType)} __{paramIdx}");
paramIdx++; paramIdx++;
} }
codeBuilder.Append(string.Join(", ", arguments.ToArray()));
codeBuilder.Append(")\n"); codeBuilder.Append(")\n");
// Patch body // Patch body
codeBuilder.AppendLine("{"); codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {"); codeBuilder.AppendLine(" try {");
codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();");
// Log message codeBuilder.AppendLine($" sb.AppendLine(\"--------------------\");");
codeBuilder.AppendLine($" sb.AppendLine(\"{signature}\");");
StringBuilder logMessage = new();
logMessage.Append($"Patch called: {shortSignature}\\n");
if (!targetMethod.IsStatic) if (!targetMethod.IsStatic)
logMessage.Append("__instance: {__instance.ToString()}\\n"); codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());");
paramIdx = 0; paramIdx = 0;
foreach (ParameterInfo param in parameters) foreach (ParameterInfo param in parameters)
{ {
logMessage.Append($"Parameter {paramIdx} {param.Name}: "); codeBuilder.Append($" sb.Append(\"- Parameter {paramIdx} '{param.Name}': \")");
Type pType = param.ParameterType; Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType(); if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType) if (pType.IsValueType)
logMessage.Append($"{{__{paramIdx}.ToString()}}"); codeBuilder.AppendLine($".AppendLine(__{paramIdx}.ToString());");
else else
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}"); codeBuilder.AppendLine($".AppendLine(__{paramIdx}?.ToString() ?? \"null\");");
logMessage.Append("\\n");
paramIdx++; paramIdx++;
} }
if (targetMethod.ReturnType != typeof(void)) if (targetMethod.ReturnType != typeof(void))
{ {
logMessage.Append("Return value: "); codeBuilder.Append(" sb.Append(\"- Return value: \")");
if (targetMethod.ReturnType.IsValueType) if (targetMethod.ReturnType.IsValueType)
logMessage.Append("{__result.ToString()}"); codeBuilder.AppendLine(".AppendLine(__result.ToString());");
else else
logMessage.Append("{__result?.ToString() ?? \"null\"}"); codeBuilder.AppendLine(".AppendLine(__result?.ToString() ?? \"null\");");
logMessage.Append("\\n");
} }
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");"); codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log(sb.ToString());");
codeBuilder.AppendLine(" }"); codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {"); 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(" }"); codeBuilder.AppendLine(" }");
// End patch body
codeBuilder.AppendLine("}"); codeBuilder.AppendLine("}");
//ExplorerCore.Log(codeBuilder.ToString());
return PatchSourceCode = 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 class GameObjectInspector : InspectorBase
{ {
public GameObject GOTarget => Target as GameObject; public new GameObject Target => base.Target as GameObject;
public GameObject Content; public GameObject Content;
public GameObjectControls GOControls; public GameObjectControls Controls;
public TransformTree TransformTree; public TransformTree TransformTree;
private ScrollPool<TransformCell> transformScroll; private ScrollPool<TransformCell> transformScroll;
@ -38,10 +38,10 @@ namespace UnityExplorer.Inspectors
{ {
base.OnBorrowedFromPool(target); base.OnBorrowedFromPool(target);
Target = target as GameObject; base.Target = target as GameObject;
GOControls.UpdateGameObjectInfo(true, true); Controls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true); Controls.TransformControl.UpdateTransformControlValues(true);
RuntimeHelper.StartCoroutine(InitCoroutine()); RuntimeHelper.StartCoroutine(InitCoroutine());
} }
@ -76,9 +76,9 @@ namespace UnityExplorer.Inspectors
public void OnTransformCellClicked(GameObject newTarget) public void OnTransformCellClicked(GameObject newTarget)
{ {
this.Target = newTarget; base.Target = newTarget;
GOControls.UpdateGameObjectInfo(true, true); Controls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true); Controls.TransformControl.UpdateTransformControlValues(true);
TransformTree.RefreshData(true, false, true, false); TransformTree.RefreshData(true, false, true, false);
UpdateComponents(); UpdateComponents();
} }
@ -90,21 +90,21 @@ namespace UnityExplorer.Inspectors
if (!this.IsActive) if (!this.IsActive)
return; return;
if (Target.IsNullOrDestroyed(false)) if (base.Target.IsNullOrDestroyed(false))
{ {
InspectorManager.ReleaseInspector(this); InspectorManager.ReleaseInspector(this);
return; return;
} }
GOControls.UpdateVectorSlider(); Controls.UpdateVectorSlider();
GOControls.UpdateTransformControlValues(false); Controls.TransformControl.UpdateTransformControlValues(false);
// Slow update // Slow update
if (timeOfLastUpdate.OccuredEarlierThan(1)) if (timeOfLastUpdate.OccuredEarlierThan(1))
{ {
timeOfLastUpdate = Time.realtimeSinceStartup; timeOfLastUpdate = Time.realtimeSinceStartup;
GOControls.UpdateGameObjectInfo(false, false); Controls.UpdateGameObjectInfo(false, false);
TransformTree.RefreshData(true, false, false, false); TransformTree.RefreshData(true, false, false, false);
UpdateComponents(); UpdateComponents();
@ -115,12 +115,12 @@ namespace UnityExplorer.Inspectors
private IEnumerable<GameObject> GetTransformEntries() private IEnumerable<GameObject> GetTransformEntries()
{ {
if (!GOTarget) if (!Target)
return Enumerable.Empty<GameObject>(); return Enumerable.Empty<GameObject>();
cachedChildren.Clear(); cachedChildren.Clear();
for (int i = 0; i < GOTarget.transform.childCount; i++) for (int i = 0; i < Target.transform.childCount; i++)
cachedChildren.Add(GOTarget.transform.GetChild(i).gameObject); cachedChildren.Add(Target.transform.GetChild(i).gameObject);
return cachedChildren; return cachedChildren;
} }
@ -130,11 +130,11 @@ namespace UnityExplorer.Inspectors
private readonly List<bool> behaviourEnabledStates = new(); private readonly List<bool> behaviourEnabledStates = new();
// ComponentList.GetRootEntriesMethod // 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() public void UpdateComponents()
{ {
if (!GOTarget) if (!Target)
{ {
componentEntries.Clear(); componentEntries.Clear();
compInstanceIDs.Clear(); compInstanceIDs.Clear();
@ -146,8 +146,8 @@ namespace UnityExplorer.Inspectors
} }
// Check if we actually need to refresh the component cells or not. // Check if we actually need to refresh the component cells or not.
IEnumerable<Component> comps = GOTarget.GetComponents<Component>(); IEnumerable<Component> comps = Target.GetComponents<Component>();
IEnumerable<Behaviour> behaviours = GOTarget.GetComponents<Behaviour>(); IEnumerable<Behaviour> behaviours = Target.GetComponents<Behaviour>();
bool needRefresh = false; bool needRefresh = false;
@ -231,7 +231,7 @@ namespace UnityExplorer.Inspectors
private void OnAddChildClicked(string input) private void OnAddChildClicked(string input)
{ {
GameObject newObject = new(input); GameObject newObject = new(input);
newObject.transform.parent = GOTarget.transform; newObject.transform.parent = Target.transform;
TransformTree.RefreshData(true, false, true, false); TransformTree.RefreshData(true, false, true, false);
} }
@ -242,7 +242,7 @@ namespace UnityExplorer.Inspectors
{ {
try try
{ {
RuntimeHelper.AddComponent<Component>(GOTarget, type); RuntimeHelper.AddComponent<Component>(Target, type);
UpdateComponents(); UpdateComponents();
} }
catch (Exception ex) catch (Exception ex)
@ -270,7 +270,7 @@ namespace UnityExplorer.Inspectors
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2); UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2);
// Construct GO Controls // Construct GO Controls
GOControls = new GameObjectControls(this); Controls = new GameObjectControls(this);
ConstructLists(); ConstructLists();
@ -333,7 +333,7 @@ namespace UnityExplorer.Inspectors
addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); }; addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); };
// comp autocompleter // comp autocompleter
new TypeCompleter(typeof(Component), addCompInput); new TypeCompleter(typeof(Component), addCompInput, false, false, false);
// Component List // 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 UnityEngine.UI;
using UnityExplorer.UI.Panels; using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.ObjectPool; using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.Inspectors namespace UnityExplorer.Inspectors
@ -9,6 +11,7 @@ namespace UnityExplorer.Inspectors
{ {
public bool IsActive { get; internal set; } public bool IsActive { get; internal set; }
public object Target { get; set; } public object Target { get; set; }
public Type TargetType { get; protected set; }
public InspectorTab Tab { get; internal set; } public InspectorTab Tab { get; internal set; }
@ -24,6 +27,8 @@ namespace UnityExplorer.Inspectors
public virtual void OnBorrowedFromPool(object target) public virtual void OnBorrowedFromPool(object target)
{ {
this.Target = target; this.Target = target;
this.TargetType = target is Type type ? type : target.GetActualType();
Tab = Pool<InspectorTab>.Borrow(); Tab = Pool<InspectorTab>.Borrow();
Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false); Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false);

View File

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

View File

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

View File

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

View File

@ -6,27 +6,53 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
using UnityExplorer.Config;
using UnityExplorer.UI;
using UniverseLib;
namespace UnityExplorer.Loader.Standalone namespace UnityExplorer.Loader.Standalone
{ {
public class ExplorerEditorBehaviour : MonoBehaviour 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() internal void Awake()
{ {
Instance = this;
ExplorerEditorLoader.Initialize(); ExplorerEditorLoader.Initialize();
DontDestroyOnLoad(this); DontDestroyOnLoad(this);
this.gameObject.hideFlags = HideFlags.HideAndDontSave; this.gameObject.hideFlags = HideFlags.HideAndDontSave;
} }
internal void OnDestroy()
{
OnApplicationQuit();
}
internal void OnApplicationQuit() internal void OnApplicationQuit()
{ {
if (UI.UIManager.UIRoot) Destroy(this.gameObject);
Destroy(UI.UIManager.UIRoot.transform.root.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() protected override void CheckExplorerFolder()
{ {
if (explorerFolderDest == null) 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 ScrollPool<ButtonCell> resultsScrollPool;
private List<object> currentResults = new(); private List<object> currentResults = new();
//public TypeCompleter typeAutocompleter;
public TypeCompleter unityObjectTypeCompleter;
public TypeCompleter allTypesCompleter;
public override GameObject UIRoot => uiRoot; public override GameObject UIRoot => uiRoot;
private GameObject uiRoot; private GameObject uiRoot;
private GameObject sceneFilterRow; private GameObject sceneFilterRow;
private GameObject childFilterRow; private GameObject childFilterRow;
private GameObject classInputRow; private GameObject classInputRow;
public TypeCompleter typeAutocompleter;
private GameObject nameInputRow; private GameObject nameInputRow;
private InputFieldRef nameInputField; private InputFieldRef nameInputField;
private Text resultsLabel; private Text resultsLabel;
@ -98,14 +101,18 @@ namespace UnityExplorer.ObjectExplorer
nameInputRow.SetActive(context == SearchContext.UnityObject); nameInputRow.SetActive(context == SearchContext.UnityObject);
if (context == SearchContext.Class) switch (context)
typeAutocompleter.AllTypes = true;
else
{ {
typeAutocompleter.BaseType = context == SearchContext.UnityObject ? typeof(UnityEngine.Object) : typeof(object); case SearchContext.UnityObject:
typeAutocompleter.AllTypes = false; 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; private void OnSceneFilterDropChanged(int value) => sceneFilter = (SceneFilter)value;
@ -185,7 +192,9 @@ namespace UnityExplorer.ObjectExplorer
InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "..."); InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "...");
UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); 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; classInputField.OnValueChanged += OnTypeInputChanged;
//unityObjectClassRow.SetActive(false); //unityObjectClassRow.SetActive(false);

View File

@ -135,7 +135,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) 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)) if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
continue; continue;
@ -173,7 +173,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{ {
// Search all non-static, non-enum classes. // 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 try
{ {

View File

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

View File

@ -69,15 +69,22 @@ namespace UnityExplorer.UI.Panels
if (CurrentHandler == provider) if (CurrentHandler == provider)
{ {
Suggestions.Clear();
CurrentHandler = null; CurrentHandler = null;
UIRoot.SetActive(false); 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(); Suggestions = suggestions;
SelectedIndex = 0;
if (jumpToTop)
{
SelectedIndex = 0;
if (scrollPool.DataSource.ItemCount > 0)
scrollPool.JumpToIndex(0, null);
}
if (!Suggestions.Any()) if (!Suggestions.Any())
base.UIRoot.SetActive(false); base.UIRoot.SetActive(false);
@ -86,7 +93,7 @@ namespace UnityExplorer.UI.Panels
base.UIRoot.SetActive(true); base.UIRoot.SetActive(true);
base.UIRoot.transform.SetAsLastSibling(); base.UIRoot.transform.SetAsLastSibling();
buttonListDataHandler.RefreshData(); 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) private void SetCell(ButtonCell cell, int index)
{ {
if (CurrentHandler == null)
{
UIRoot.SetActive(false);
return;
}
if (index < 0 || index >= Suggestions.Count) if (index < 0 || index >= Suggestions.Count)
{ {
cell.Disable(); cell.Disable();
@ -225,13 +238,18 @@ namespace UnityExplorer.UI.Panels
InputFieldRef input = CurrentHandler.InputField; InputFieldRef input = CurrentHandler.InputField;
if (!input.Component.isFocused || input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition) //if (!input.Component.isFocused
return; // || (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition))
lastInputPosition = input.UIRoot.transform.position; // return;
lastCaretPosition = input.Component.caretPosition;
if (input.Component.caretPosition == lastCaretPosition && input.UIRoot.transform.position == lastInputPosition)
return;
if (CurrentHandler.AnchorToCaretPosition) if (CurrentHandler.AnchorToCaretPosition)
{ {
if (!input.Component.isFocused)
return;
TextGenerator textGen = input.Component.cachedInputTextGenerator; TextGenerator textGen = input.Component.cachedInputTextGenerator;
int caretIdx = Math.Max(0, Math.Min(textGen.characterCount - 1, input.Component.caretPosition)); 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); 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(); this.Dragger.OnEndResize();
} }

View File

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

View File

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

View File

@ -1,7 +1,10 @@
using UnityEngine; using System;
using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityExplorer.Hooks; using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete; using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI; using UniverseLib.UI;
using UniverseLib.UI.Models; using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets; using UniverseLib.UI.Widgets;
@ -11,76 +14,58 @@ namespace UnityExplorer.UI.Panels
{ {
public class HookManagerPanel : UEPanel public class HookManagerPanel : UEPanel
{ {
public static HookManagerPanel Instance { get; private set; }
public enum Pages public enum Pages
{ {
CurrentHooks,
ClassMethodSelector, 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 string Name => "Hooks";
public override bool ShowByDefault => false; public override bool ShowByDefault => false;
public override int MinWidth => 400;
public override int MinWidth => 500; public override int MinHeight => 400;
public override int MinHeight => 600;
public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f); public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f);
public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f); public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f);
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks; public Pages CurrentPage { get; private set; } = Pages.ClassMethodSelector;
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 HookManagerPanel(UIBase owner) : base(owner) 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) public void SetPage(Pages page)
{ {
switch (page) switch (page)
{ {
case Pages.CurrentHooks:
currentHooksPanel.SetActive(true);
addHooksPanel.SetActive(false);
editorPanel.SetActive(false);
break;
case Pages.ClassMethodSelector: case Pages.ClassMethodSelector:
currentHooksPanel.SetActive(false); HookCreator.AddHooksRoot.SetActive(true);
addHooksPanel.SetActive(true); HookCreator.EditorRoot.SetActive(false);
editorPanel.SetActive(false); genericArgsHandler.UIRoot.SetActive(false);
break; break;
case Pages.HookSourceEditor: case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false); HookCreator.AddHooksRoot.SetActive(false);
addHooksPanel.SetActive(false); HookCreator.EditorRoot.SetActive(true);
editorPanel.SetActive(true); genericArgsHandler.UIRoot.SetActive(false);
break;
case Pages.GenericArgsSelector:
HookCreator.AddHooksRoot.SetActive(false);
HookCreator.EditorRoot.SetActive(false);
genericArgsHandler.UIRoot.SetActive(true);
break; break;
} }
} }
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void SetDefaultSizeAndPosition() public override void SetDefaultSizeAndPosition()
{ {
base.SetDefaultSizeAndPosition(); base.SetDefaultSizeAndPosition();
@ -91,115 +76,35 @@ namespace UnityExplorer.UI.Panels
protected override void ConstructPanelContent() protected override void ConstructPanelContent()
{ {
// ~~~~~~~~~ Active hooks scroll pool Instance = this;
hookList = new();
hookCreator = new();
genericArgsHandler = new();
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.ContentRoot); UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ContentRoot, true, false);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
GameObject addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4, // GameObject baseHoriGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "HoriGroup", true, true, true, true);
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f)); // UIFactory.SetLayoutElement(baseHoriGroup, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to..."); // // Left Group
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks"); //GameObject leftGroup = UIFactory.CreateVerticalGroup(ContentRoot, "LeftGroup", true, true, true, true);
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25); UIFactory.SetLayoutElement(ContentRoot.gameObject, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
addButton.OnClick += OnClassInputAddClicked;
Text hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter); hookList.ConstructUI(ContentRoot);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool", // // Right Group
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// ~~~~~~~~~ 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); hookCreator.ConstructAddHooksView(ContentRoot);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter); hookCreator.ConstructEditor(ContentRoot);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999); HookCreator.EditorRoot.SetActive(false);
GameObject buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5); genericArgsHandler.ConstructUI(ContentRoot);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999); genericArgsHandler.UIRoot.SetActive(false);
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);
} }
} }
} }

View File

@ -14,8 +14,6 @@ using UniverseLib.Utility;
namespace UnityExplorer.UI.Panels 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 class LogPanel : UEPanel, ICellPoolDataSource<ConsoleLogCell>
{ {
public struct LogInfo public struct LogInfo

View File

@ -119,6 +119,8 @@ namespace UnityExplorer.UI.Panels
{ {
Rect.SetAnchorsFromString(split[1]); Rect.SetAnchorsFromString(split[1]);
Rect.SetPositionFromString(split[2]); Rect.SetPositionFromString(split[2]);
this.EnsureValidSize();
this.EnsureValidPosition();
this.SetActive(bool.Parse(split[0])); this.SetActive(bool.Parse(split[0]));
} }
catch catch

View File

@ -26,7 +26,6 @@ namespace UnityExplorer.UI
Options, Options,
ConsoleLog, ConsoleLog,
AutoCompleter, AutoCompleter,
//MouseInspector,
UIInspectorResults, UIInspectorResults,
HookManager, HookManager,
Clipboard, Clipboard,
@ -113,14 +112,14 @@ namespace UnityExplorer.UI
Notification.Init(); Notification.Init();
ConsoleController.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. // Failsafe fix, in some games all dropdowns displayed values are blank on startup for some reason.
foreach (Dropdown dropdown in UIRoot.GetComponentsInChildren<Dropdown>(true)) foreach (Dropdown dropdown in UIRoot.GetComponentsInChildren<Dropdown>(true))
dropdown.RefreshShownValue(); dropdown.RefreshShownValue();
Initializing = false; Initializing = false;
if (ConfigManager.Hide_On_Startup.Value)
ShowMenu = false;
} }
// Main UI Update loop // Main UI Update loop

View File

@ -1,6 +1,9 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using UnityEngine;
using UnityExplorer.UI.Panels; using UnityExplorer.UI.Panels;
using UniverseLib; using UniverseLib;
using UniverseLib.UI.Models; using UniverseLib.UI.Models;
@ -12,133 +15,217 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
{ {
public bool Enabled public bool Enabled
{ {
get => _enabled; get => enabled;
set set
{ {
_enabled = value; enabled = value;
if (!_enabled) if (!enabled)
{
AutoCompleteModal.Instance.ReleaseOwnership(this); AutoCompleteModal.Instance.ReleaseOwnership(this);
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
}
} }
} }
private bool _enabled = true; bool enabled = true;
public event Action<Suggestion> SuggestionClicked; 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 InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false; public bool AnchorToCaretPosition => false;
private readonly List<Suggestion> suggestions = new(); readonly bool allowAbstract;
private readonly HashSet<string> suggestedNames = new(); 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; 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; BaseType = baseType;
InputField = inputField; InputField = inputField;
this.allowAbstract = allowAbstract; this.allowAbstract = allowAbstract;
this.allowEnum = allowEnum; this.allowEnum = allowEnum;
this.allowGeneric = allowGeneric;
inputField.OnValueChanged += OnInputFieldChanged; inputField.OnValueChanged += OnInputFieldChanged;
if (BaseType != null) CacheTypes();
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);
}
}
} }
public void OnSuggestionClicked(Suggestion suggestion) public void OnSuggestionClicked(Suggestion suggestion)
{ {
chosenSuggestion = suggestion.UnderlyingValue;
InputField.Text = suggestion.UnderlyingValue; InputField.Text = suggestion.UnderlyingValue;
SuggestionClicked?.Invoke(suggestion); SuggestionClicked?.Invoke(suggestion);
suggestions.Clear(); suggestions.Clear();
AutoCompleteModal.Instance.SetSuggestions(suggestions); //AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
chosenSuggestion = suggestion.UnderlyingValue; 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) if (!Enabled)
return; return;
if (string.IsNullOrEmpty(value) || value == chosenSuggestion) if (input != chosenSuggestion)
{
chosenSuggestion = null; chosenSuggestion = null;
if (string.IsNullOrEmpty(input) || input == chosenSuggestion)
{
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
AutoCompleteModal.Instance.ReleaseOwnership(this); AutoCompleteModal.Instance.ReleaseOwnership(this);
} }
else else
{ {
GetSuggestions(value); GetSuggestions(input);
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
} }
} }
private void GetSuggestions(string value) void GetSuggestions(string input)
{ {
suggestions.Clear(); if (allowedTypes == null)
suggestedNames.Clear();
if (BaseType == null)
{ {
ExplorerCore.LogWarning("Autocompleter Base type is null!"); if (pendingInput != null)
{
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true);
}
pendingInput = input;
return; 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 // 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); 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) 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); 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(); internal static readonly Dictionary<string, string> sharedTypeToLabel = new();
void AddSuggestion(Type type) void AddSuggestion(Type type)
{ {
if (suggestedNames.Contains(type.FullName)) if (suggestedTypes.Contains(type.FullName))
return; return;
suggestedNames.Add(type.FullName); suggestedTypes.Add(type.FullName);
if (!sharedTypeToLabel.ContainsKey(type.FullName)) if (!sharedTypeToLabel.ContainsKey(type.FullName))
sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true)); sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true));

View File

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

View File

@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets
GenericArgumentHandler holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow(); GenericArgumentHandler holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.genericArgumentsHolder.transform, false); 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(); ParameterHandler holder = paramHandlers[i] = Pool<ParameterHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.parametersHolder.transform, false); 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 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.genericArgument = genericArgument;
this.genericType = genericConstraint;
typeCompleter.Enabled = true; typeCompleter.Enabled = true;
typeCompleter.BaseType = genericType; typeCompleter.BaseType = this.genericArgument;
typeCompleter.CacheTypes(); typeCompleter.CacheTypes();
Type[] constraints = genericType.GetGenericParameterConstraints(); Type[] constraints = this.genericArgument.GetGenericParameterConstraints();
typeCompleter.GenericConstraints = constraints;
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(' '); 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(')'); sb.Append(')');
} }
@ -39,8 +37,7 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned() public void OnReturned()
{ {
this.evaluator = null; this.genericArgument = null;
this.genericType = null;
this.typeCompleter.Enabled = false; 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 Text basicLabel;
private ButtonRef pasteButton; private ButtonRef pasteButton;
public void OnBorrowed(EvaluateWidget evaluator, ParameterInfo paramInfo) public void OnBorrowed(ParameterInfo paramInfo)
{ {
this.evaluator = evaluator;
this.paramInfo = paramInfo; this.paramInfo = paramInfo;
this.paramType = paramInfo.ParameterType; this.paramType = paramInfo.ParameterType;
@ -85,7 +84,6 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned() public void OnReturned()
{ {
this.evaluator = null;
this.paramInfo = null; this.paramInfo = null;
this.enumCompleter.Enabled = false; 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.UI.Widgets.ButtonList;
using UniverseLib; using UniverseLib;
namespace UnityExplorer.Inspectors namespace UnityExplorer.UI.Widgets
{ {
public class ComponentCell : ButtonCell public class ComponentCell : ButtonCell
{ {

View File

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

@ -22,18 +22,18 @@ namespace UnityExplorer.UI.Widgets
static Coroutine CurrentlyPlayingCoroutine; static Coroutine CurrentlyPlayingCoroutine;
static readonly string zeroLengthString = GetLengthString(0f); static readonly string zeroLengthString = GetLengthString(0f);
public AudioClip RefAudioClip; public AudioClip audioClip;
private string fullLengthText; string fullLengthText;
private ButtonRef toggleButton; ButtonRef toggleButton;
private bool audioPlayerWanted; bool audioPlayerWanted;
private GameObject audioPlayerRoot; GameObject audioPlayerRoot;
private ButtonRef playStopButton; ButtonRef playStopButton;
private Text progressLabel; Text progressLabel;
private GameObject saveObjectRow; GameObject saveObjectRow;
private InputFieldRef savePathInput; InputFieldRef savePathInput;
private GameObject cantSaveRow; GameObject cantSaveRow;
public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) 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.SetParent(inspector.UIRoot.transform);
this.audioPlayerRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2); this.audioPlayerRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2);
RefAudioClip = target.TryCast<AudioClip>(); audioClip = target.TryCast<AudioClip>();
this.fullLengthText = GetLengthString(RefAudioClip.length); this.fullLengthText = GetLengthString(audioClip.length);
if (RefAudioClip.loadType == AudioClipLoadType.DecompressOnLoad) if (audioClip.loadType == AudioClipLoadType.DecompressOnLoad)
{ {
cantSaveRow.SetActive(false); cantSaveRow.SetActive(false);
saveObjectRow.SetActive(true); saveObjectRow.SetActive(true);
@ -62,7 +62,7 @@ namespace UnityExplorer.UI.Widgets
public override void OnReturnToPool() public override void OnReturnToPool()
{ {
RefAudioClip = null; audioClip = null;
if (audioPlayerWanted) if (audioPlayerWanted)
ToggleAudioWidget(); ToggleAudioWidget();
@ -95,7 +95,7 @@ namespace UnityExplorer.UI.Widgets
void SetDefaultSavePath() void SetDefaultSavePath()
{ {
string name = RefAudioClip.name; string name = audioClip.name;
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
name = "untitled"; name = "untitled";
savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.wav"); savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.wav");
@ -163,7 +163,7 @@ namespace UnityExplorer.UI.Widgets
{ {
playStopButton.ButtonText.text = "Stop Clip"; playStopButton.ButtonText.text = "Stop Clip";
CurrentlyPlaying = this; CurrentlyPlaying = this;
Source.clip = this.RefAudioClip; Source.clip = this.audioClip;
Source.Play(); Source.Play();
while (Source.isPlaying) while (Source.isPlaying)
@ -191,7 +191,7 @@ namespace UnityExplorer.UI.Widgets
public void OnSaveClipClicked() public void OnSaveClipClicked()
{ {
if (!RefAudioClip) if (!audioClip)
{ {
ExplorerCore.LogWarning("AudioClip is null, maybe it was destroyed?"); ExplorerCore.LogWarning("AudioClip is null, maybe it was destroyed?");
return; return;
@ -212,7 +212,7 @@ namespace UnityExplorer.UI.Widgets
if (File.Exists(path)) if (File.Exists(path))
File.Delete(path); File.Delete(path);
SavWav.Save(RefAudioClip, path); SavWav.Save(audioClip, path);
} }
public override GameObject CreateContent(GameObject uiRoot) 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 public class Texture2DWidget : UnityObjectWidget
{ {
private Texture2D TextureRef; Texture2D texture;
private float realWidth; bool shouldDestroyTexture;
private float realHeight;
private bool textureViewerWanted; bool textureViewerWanted;
private ButtonRef toggleButton; ButtonRef toggleButton;
private GameObject textureViewerRoot; GameObject textureViewerRoot;
private InputFieldRef savePathInput; InputFieldRef savePathInput;
private Image image; Image image;
private LayoutElement imageLayout; LayoutElement imageLayout;
public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector)
{ {
base.OnBorrowed(target, targetType, 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; if (textureViewerRoot)
realHeight = TextureRef.height; textureViewerRoot.transform.SetParent(inspector.UIRoot.transform);
if (this.textureViewerRoot)
this.textureViewerRoot.transform.SetParent(inspector.UIRoot.transform);
InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize; InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize;
} }
@ -48,21 +70,25 @@ namespace UnityExplorer.UI.Widgets
{ {
InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize; InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize;
TextureRef = null; if (shouldDestroyTexture)
UnityEngine.Object.Destroy(texture);
texture = null;
shouldDestroyTexture = false;
if (image.sprite) if (image.sprite)
GameObject.Destroy(image.sprite); UnityEngine.Object.Destroy(image.sprite);
if (textureViewerWanted) if (textureViewerWanted)
ToggleTextureViewer(); ToggleTextureViewer();
if (this.textureViewerRoot) if (textureViewerRoot)
this.textureViewerRoot.transform.SetParent(Pool<Texture2DWidget>.Instance.InactiveHolder.transform); textureViewerRoot.transform.SetParent(Pool<Texture2DWidget>.Instance.InactiveHolder.transform);
base.OnReturnToPool(); base.OnReturnToPool();
} }
private void ToggleTextureViewer() void ToggleTextureViewer()
{ {
if (textureViewerWanted) if (textureViewerWanted)
{ {
@ -71,7 +97,7 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(false); textureViewerRoot.SetActive(false);
toggleButton.ButtonText.text = "View Texture"; toggleButton.ButtonText.text = "View Texture";
ParentInspector.mainContentHolder.SetActive(true); owner.ContentRoot.SetActive(true);
} }
else else
{ {
@ -85,30 +111,30 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(true); textureViewerRoot.SetActive(true);
toggleButton.ButtonText.text = "Hide Texture"; 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; return;
string name = TextureRef.name; string name = texture.name;
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
name = "untitled"; name = "untitled";
savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png");
Sprite sprite = TextureHelper.CreateSprite(TextureRef); Sprite sprite = TextureHelper.CreateSprite(texture);
image.sprite = sprite; image.sprite = sprite;
} }
private void OnInspectorFinishResize() void OnInspectorFinishResize()
{ {
SetImageSize(); SetImageSize();
} }
private void SetImageSize() void SetImageSize()
{ {
if (!imageLayout) if (!imageLayout)
return; return;
@ -127,34 +153,34 @@ namespace UnityExplorer.UI.Widgets
float rectHeight = imageRect.rect.height - 196; float rectHeight = imageRect.rect.height - 196;
// If our image is smaller than the viewport, just use 100% scaling // 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.minWidth = texture.width;
imageLayout.minHeight = realHeight; imageLayout.minHeight = texture.height;
} }
else // we will need to scale down the image to fit else // we will need to scale down the image to fit
{ {
// get the ratio of our viewport dimensions to width and height // get the ratio of our viewport dimensions to width and height
float viewWidthRatio = (float)((decimal)rectWidth / (decimal)realWidth); float viewWidthRatio = (float)((decimal)rectWidth / (decimal)texture.width);
float viewHeightRatio = (float)((decimal)rectHeight / (decimal)realHeight); float viewHeightRatio = (float)((decimal)rectHeight / (decimal)texture.height);
// if width needs to be scaled more than height // if width needs to be scaled more than height
if (viewWidthRatio < viewHeightRatio) if (viewWidthRatio < viewHeightRatio)
{ {
imageLayout.minWidth = realWidth * viewWidthRatio; imageLayout.minWidth = texture.width * viewWidthRatio;
imageLayout.minHeight = realHeight * viewWidthRatio; imageLayout.minHeight = texture.height * viewWidthRatio;
} }
else // if height needs to be scaled more than width else // if height needs to be scaled more than width
{ {
imageLayout.minWidth = realWidth * viewHeightRatio; imageLayout.minWidth = texture.width * viewHeightRatio;
imageLayout.minHeight = realHeight * viewHeightRatio; imageLayout.minHeight = texture.height * viewHeightRatio;
} }
} }
} }
private void OnSaveTextureClicked() void OnSaveTextureClicked()
{ {
if (!TextureRef) if (!texture)
{ {
ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?"); ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?");
return; return;
@ -175,18 +201,7 @@ namespace UnityExplorer.UI.Widgets
if (File.Exists(path)) if (File.Exists(path))
File.Delete(path); File.Delete(path);
Texture2D tex = TextureRef; TextureHelper.SaveTextureAsPNG(texture, path);
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);
}
} }
public override GameObject CreateContent(GameObject uiRoot) public override GameObject CreateContent(GameObject uiRoot)

View File

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

View File

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