Compare commits

...

24 Commits

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
31 changed files with 1497 additions and 945 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

View File

@ -1,6 +1,6 @@
{ {
"name": "com.sinai-dev.unityexplorer", "name": "com.sinai-dev.unityexplorer",
"version": "4.7.9", "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

@ -239,10 +239,13 @@ namespace UnityExplorer.CSConsole
if (SRENotSupported) if (SRENotSupported)
return; return;
if (InputManager.GetKeyDown(KeyCode.Home)) if (!InputManager.GetKey(KeyCode.LeftControl) && !InputManager.GetKey(KeyCode.RightControl))
JumpToStartOrEndOfLine(true); {
else if (InputManager.GetKeyDown(KeyCode.End)) if (InputManager.GetKeyDown(KeyCode.Home))
JumpToStartOrEndOfLine(false); JumpToStartOrEndOfLine(true);
else if (InputManager.GetKeyDown(KeyCode.End))
JumpToStartOrEndOfLine(false);
}
UpdateCaret(out bool caretMoved); UpdateCaret(out bool caretMoved);
@ -459,9 +462,6 @@ namespace UnityExplorer.CSConsole
#region Lexer Highlighting #region Lexer Highlighting
/// <summary>
/// Returns true if caret is inside string or comment, false otherwise
/// </summary>
private static void HighlightVisibleInput(out bool inStringOrComment) private static void HighlightVisibleInput(out bool inStringOrComment)
{ {
inStringOrComment = false; inStringOrComment = false;

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;
@ -85,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);
@ -116,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

@ -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.10"; 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

@ -158,11 +158,8 @@ namespace UnityExplorer.Hooks
} }
HookInstance hook = new(method); HookInstance hook = new(method);
if (hook.Enabled) HookList.hookedSignatures.Add(sig);
{ HookList.currentHooks.Add(sig, hook);
HookList.hookedSignatures.Add(sig);
HookList.currentHooks.Add(sig, hook);
}
AddHooksScrollPool.Refresh(true, false); AddHooksScrollPool.Refresh(true, false);
HookList.HooksScrollPool.Refresh(true, false); HookList.HooksScrollPool.Refresh(true, false);

View File

@ -15,7 +15,6 @@ namespace UnityExplorer.Hooks
{ {
// Static // Static
//static readonly StringBuilder evalOutput = new();
static readonly StringBuilder evaluatorOutput; static readonly StringBuilder evaluatorOutput;
static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder())); static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder()));
@ -31,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.FullDescription(); this.signature = TargetMethod.FullDescription();
GenerateDefaultPatchSourceCode(targetMethod); GenerateDefaultPatchSourceCode(targetMethod);
@ -144,28 +144,29 @@ namespace UnityExplorer.Hooks
{ {
StringBuilder codeBuilder = new(); StringBuilder codeBuilder = new();
codeBuilder.Append("static void Postfix("); // System.Reflection.MethodBase __originalMethod codeBuilder.Append("static void Postfix(");
bool isStatic = targetMethod.IsStatic; bool isStatic = targetMethod.IsStatic;
List<string> arguments = new();
if (!isStatic) if (!isStatic)
codeBuilder.Append($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance"); arguments.Add($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance");
if (targetMethod.ReturnType != typeof(void)) if (targetMethod.ReturnType != typeof(void))
{ arguments.Add($"{FullDescriptionClean(targetMethod.ReturnType)} __result");
if (!isStatic)
codeBuilder.Append(", ");
codeBuilder.Append($"{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($", {FullDescriptionClean(param.ParameterType)} __{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
@ -173,8 +174,8 @@ namespace UnityExplorer.Hooks
codeBuilder.AppendLine("{"); codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {"); codeBuilder.AppendLine(" try {");
codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();"); codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();");
codeBuilder.AppendLine($" sb.AppendLine(\"---- Patched called ----\");"); codeBuilder.AppendLine($" sb.AppendLine(\"--------------------\");");
codeBuilder.AppendLine($" sb.AppendLine(\"{shortSignature}\");"); codeBuilder.AppendLine($" sb.AppendLine(\"{signature}\");");
if (!targetMethod.IsStatic) if (!targetMethod.IsStatic)
codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());"); codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());");
@ -206,15 +207,11 @@ namespace UnityExplorer.Hooks
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log(sb.ToString());"); 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();
} }

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();

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,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,7 +36,6 @@ 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;
@ -73,6 +75,8 @@ namespace UnityExplorer.Inspectors
Text assemblyText; Text assemblyText;
Toggle autoUpdateToggle; Toggle autoUpdateToggle;
ButtonRef dnSpyButton;
ButtonRef makeGenericButton; ButtonRef makeGenericButton;
GenericConstructorWidget genericConstructor; GenericConstructorWidget genericConstructor;
@ -155,9 +159,15 @@ namespace UnityExplorer.Inspectors
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);
dnSpyButton.GameObject.SetActive(true);
}
assemblyText.text = $"<color=grey>Assembly:</color> {asmText}"; assemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
// Unity object helper widget // Unity object helper widget
@ -350,6 +360,25 @@ namespace UnityExplorer.Inspectors
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() void OnMakeGenericClicked()
{ {
ContentRoot.SetActive(false); ContentRoot.SetActive(false);
@ -425,10 +454,21 @@ namespace UnityExplorer.Inspectors
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
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); UIFactory.SetLayoutElement(assemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
ContentRoot = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2), 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(ContentRoot, flexibleWidth: 9999, flexibleHeight: 9999); UIFactory.SetLayoutElement(ContentRoot, flexibleWidth: 9999, flexibleHeight: 9999);

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

@ -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

@ -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.ContentRoot.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.ContentRoot.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.TabButtonText} \"{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.11" /> <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.11" /> <PackageReference Include="UniverseLib.Mono" Version="1.4.1" />
</ItemGroup> </ItemGroup>
<!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ --> <!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ -->