From bc113e909319c33067bea6ef75d1ef79ace94616 Mon Sep 17 00:00:00 2001 From: sinaioutlander <49360850+sinaioutlander@users.noreply.github.com> Date: Fri, 13 Nov 2020 18:46:36 +1100 Subject: [PATCH] A few important fixes * Reflection on Il2CppSystem-namespace instances has been fixed * Type/Value Syntax highlighting generalized and improved globally * Scene changes now refresh the scene-picker dropdown * probably other minor stuff too --- src/Console/CodeEditor.cs | 9 +- src/Console/Lexer/KeywordMatch.cs | 4 +- src/ExplorerBepInPlugin.cs | 34 +--- src/ExplorerCore.cs | 84 ++++----- src/ExplorerMelonMod.cs | 14 +- src/Helpers/ReflectionHelpers.cs | 170 +++++++++--------- src/Inspectors/GameObjects/ComponentList.cs | 2 +- .../{ => GameObjects}/GameObjectInspector.cs | 0 src/Inspectors/InspectorBase.cs | 13 +- src/Inspectors/InspectorManager.cs | 16 +- .../Reflection/CacheObject/CacheMember.cs | 17 +- .../InteractiveValue/InteractiveValue.cs | 111 ++++++++---- .../{ => Reflection}/ReflectionInspector.cs | 32 ++-- src/Inspectors/SceneExplorer.cs | 18 +- src/UI/MainMenu.cs | 48 +++-- src/UI/Modules/DebugConsole.cs | 30 ++-- src/UI/PanelDragger.cs | 55 ++---- src/UI/Shared/PageHandler.cs | 12 +- src/UI/Shared/UISyntaxHighlight.cs | 169 ++++++++++------- src/UI/UIManager.cs | 3 +- src/UnityExplorer.csproj | 4 +- 21 files changed, 450 insertions(+), 395 deletions(-) rename src/Inspectors/{ => GameObjects}/GameObjectInspector.cs (100%) rename src/Inspectors/{ => Reflection}/ReflectionInspector.cs (94%) diff --git a/src/Console/CodeEditor.cs b/src/Console/CodeEditor.cs index 920504a..b297ded 100644 --- a/src/Console/CodeEditor.cs +++ b/src/Console/CodeEditor.cs @@ -181,14 +181,17 @@ The following helper methods are available: CurrentIndent = 0; + bool stringState = false; + for (int i = 0; i < caret && i < newText.Length; i++) { char character = newText[i]; - if (character == CSharpLexer.indentOpen) + if (character == '"') + stringState = !stringState; + else if (!stringState && character == CSharpLexer.indentOpen) CurrentIndent++; - - if (character == CSharpLexer.indentClose) + else if (!stringState && character == CSharpLexer.indentClose) CurrentIndent--; } diff --git a/src/Console/Lexer/KeywordMatch.cs b/src/Console/Lexer/KeywordMatch.cs index b944526..abf247f 100644 --- a/src/Console/Lexer/KeywordMatch.cs +++ b/src/Console/Lexer/KeywordMatch.cs @@ -17,8 +17,8 @@ namespace UnityExplorer.Console.Lexer public override bool IsImplicitMatch(CSharpLexer lexer) { - if (!char.IsWhiteSpace(lexer.Previous) && - !lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End)) + if (!char.IsWhiteSpace(lexer.Previous) || + lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End)) { return false; } diff --git a/src/ExplorerBepInPlugin.cs b/src/ExplorerBepInPlugin.cs index 5ee763c..8478a6e 100644 --- a/src/ExplorerBepInPlugin.cs +++ b/src/ExplorerBepInPlugin.cs @@ -30,27 +30,15 @@ namespace UnityExplorer { Instance = this; - SceneManager.activeSceneChanged += DoSceneChange; - new ExplorerCore(); // HarmonyInstance.PatchAll(); } - internal static void DoSceneChange(Scene arg0, Scene arg1) - { - ExplorerCore.OnSceneChange(); - } - internal void Update() { ExplorerCore.Update(); } - - internal void OnApplicationQuit() - { - DebugConsole.OnQuit(); - } } #endif @@ -64,9 +52,6 @@ namespace UnityExplorer public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID); - // temporary for Il2Cpp until scene change delegate works - private static string lastSceneName; - // Init public override void Load() { @@ -85,12 +70,7 @@ namespace UnityExplorer new ExplorerCore(); - HarmonyInstance.PatchAll(); - } - - internal static void DoSceneChange(Scene arg0, Scene arg1) - { - ExplorerCore.OnSceneChange(); + // HarmonyInstance.PatchAll(); } // BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy. @@ -106,18 +86,6 @@ namespace UnityExplorer internal void Update() { ExplorerCore.Update(); - - var scene = SceneManager.GetActiveScene(); - if (scene.name != lastSceneName) - { - lastSceneName = scene.name; - DoSceneChange(scene, scene); - } - } - - internal void OnApplicationQuit() - { - DebugConsole.OnQuit(); } } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 66c6144..241f56b 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -7,29 +7,16 @@ using UnityEngine; using UnityExplorer.Inspectors; using System.IO; using UnityExplorer.Unstrip; +using UnityEngine.SceneManagement; namespace UnityExplorer { public class ExplorerCore { - public const string NAME = "UnityExplorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")"; + public const string NAME = "UnityExplorer"; public const string VERSION = "3.0.0"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.unityexplorer"; - - public const string PLATFORM = -#if CPP - "Il2Cpp"; -#else - "Mono"; -#endif - public const string MODLOADER = -#if ML - "MelonLoader"; -#else - "BepInEx"; -#endif - public const string EXPLORER_FOLDER = @"Mods\UnityExplorer"; public static ExplorerCore Instance { get; private set; } @@ -42,7 +29,7 @@ namespace UnityExplorer public static bool m_showMenu; private static bool s_doneUIInit; - private static float m_timeSinceStartup; + private static float s_timeSinceStartup; public ExplorerCore() { @@ -62,32 +49,11 @@ namespace UnityExplorer InputManager.Init(); ForceUnlockCursor.Init(); -#if CPP - Application.add_logMessageReceived(new Action(LogCallback)); -#else - Application.logMessageReceived += LogCallback; -#endif + SetupEvents(); ShowMenu = true; - Log($"{NAME} initialized."); - } - - private static void SetShowMenu(bool show) - { - m_showMenu = show; - - if (UIManager.CanvasRoot) - { - UIManager.CanvasRoot.SetActive(show); - - if (show) - ForceUnlockCursor.SetEventSystem(); - else - ForceUnlockCursor.ReleaseEventSystem(); - } - - ForceUnlockCursor.UpdateCursorControl(); + Log($"{NAME} {VERSION} initialized."); } public static void Update() @@ -109,9 +75,9 @@ namespace UnityExplorer private static void CheckUIInit() { - m_timeSinceStartup += Time.deltaTime; + s_timeSinceStartup += Time.deltaTime; - if (m_timeSinceStartup > 0.1f) + if (s_timeSinceStartup > 0.1f) { s_doneUIInit = true; try @@ -129,11 +95,45 @@ namespace UnityExplorer } } - public static void OnSceneChange() + private void SetupEvents() + { +#if CPP + try + { + Application.add_logMessageReceived(new Action(LogCallback)); + SceneManager.add_sceneLoaded(new Action((Scene a, LoadSceneMode b) => { OnSceneLoaded(); })); + SceneManager.add_activeSceneChanged(new Action((Scene a, Scene b) => { OnSceneLoaded(); })); + } + catch { } +#else + Application.logMessageReceived += LogCallback; + SceneManager.sceneLoaded += (Scene a, LoadSceneMode b) => { OnSceneLoaded(); }; + SceneManager.activeSceneChanged += (Scene a, Scene b) => { OnSceneLoaded(); }; +#endif + } + + internal void OnSceneLoaded() { UIManager.OnSceneChange(); } + private static void SetShowMenu(bool show) + { + m_showMenu = show; + + if (UIManager.CanvasRoot) + { + UIManager.CanvasRoot.SetActive(show); + + if (show) + ForceUnlockCursor.SetEventSystem(); + else + ForceUnlockCursor.ReleaseEventSystem(); + } + + ForceUnlockCursor.UpdateCursorControl(); + } + private void LogCallback(string message, string stackTrace, LogType type) { if (!DebugConsole.LogUnity) diff --git a/src/ExplorerMelonMod.cs b/src/ExplorerMelonMod.cs index 030ff3e..5cb48ad 100644 --- a/src/ExplorerMelonMod.cs +++ b/src/ExplorerMelonMod.cs @@ -15,25 +15,15 @@ namespace UnityExplorer new ExplorerCore(); } - public override void OnLevelWasLoaded(int level) - { - ExplorerCore.OnSceneChange(); - } - public override void OnUpdate() { ExplorerCore.Update(); } - public override void OnApplicationQuit() + public override void OnLevelWasLoaded(int level) { - DebugConsole.OnQuit(); + ExplorerCore.Instance.OnSceneLoaded(); } - - //public override void OnGUI() - //{ - // ExplorerCore.OnGUI(); - //} } } #endif \ No newline at end of file diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index 7d91ccd..bf38c66 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -35,15 +35,66 @@ namespace UnityExplorer.Helpers public static Type BehaviourType => typeof(Behaviour); #endif + public static Type GetTypeByName(string fullName) + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in asm.TryGetTypes()) + { + if (type.FullName == fullName) + { + return type; + } + } + } + + return null; + } + + public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj)); + + public static Type[] GetAllBaseTypes(Type type) + { + List list = new List(); + + while (type != null) + { + list.Add(type); + type = type.BaseType; + } + + return list.ToArray(); + } + + public static Type GetActualType(object obj) + { + if (obj == null) + return null; + + var type = obj.GetType(); +#if CPP + if (obj is Il2CppSystem.Object ilObject) + { + if (obj is ILType) + return typeof(ILType); + + // Il2CppSystem-namespace objects should just return GetType, + // because using GetIl2CppType returns the System namespace type instead. + if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem.")) + return ilObject.GetType(); + + var getType = Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName); + + if (getType != null) + return getType; + } +#endif + return type; + } + #if CPP private static readonly Dictionary ClassPointers = new Dictionary(); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_object_get_class(IntPtr obj); - public static object Il2CppCast(this object obj, Type castTo) { if (!(obj is Il2CppSystem.Object ilObj)) @@ -51,30 +102,8 @@ namespace UnityExplorer.Helpers return obj; } - if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) - { + if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr)) return obj; - } - - IntPtr castToPtr; - if (!ClassPointers.ContainsKey(castTo)) - { - castToPtr = (IntPtr)typeof(Il2CppClassPointerStore<>) - .MakeGenericType(new Type[] { castTo }) - .GetField("NativeClassPtr", BF.Public | BF.Static) - .GetValue(null); - - ClassPointers.Add(castTo, castToPtr); - } - else - { - castToPtr = ClassPointers[castTo]; - } - - if (castToPtr == IntPtr.Zero) - { - return obj; - } IntPtr classPtr = il2cpp_object_get_class(ilObj.Pointer); @@ -86,6 +115,35 @@ namespace UnityExplorer.Helpers return Activator.CreateInstance(castTo, ilObj.Pointer); } + + public static bool Il2CppTypeNotNull(Type type) + { + return Il2CppTypeNotNull(type, out _); + } + + public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr) + { + if (!ClassPointers.ContainsKey(type)) + { + il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>) + .MakeGenericType(new Type[] { type }) + .GetField("NativeClassPtr", BF.Public | BF.Static) + .GetValue(null); + + ClassPointers.Add(type, il2cppPtr); + } + else + il2cppPtr = ClassPointers[type]; + + return il2cppPtr != IntPtr.Zero; + } + + [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass); + + [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr il2cpp_object_get_class(IntPtr obj); + #endif public static IEnumerable TryGetTypes(this Assembly asm) @@ -111,60 +169,6 @@ namespace UnityExplorer.Helpers } } - public static Type GetTypeByName(string fullName) - { - foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) - { - foreach (var type in asm.TryGetTypes()) - { - if (type.FullName == fullName) - { - return type; - } - } - } - - return null; - } - - public static Type GetActualType(object obj) - { - if (obj == null) - return null; - -#if CPP - // Need to use GetIl2CppType for Il2CppSystem Objects - if (obj is Il2CppSystem.Object ilObject) - { - // Prevent weird behaviour when inspecting an Il2CppSystem.Type object. - if (ilObject is ILType) - { - return typeof(ILType); - } - - return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType(); - } -#endif - - // It's a normal object, this is fine - return obj.GetType(); - } - - public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj)); - - public static Type[] GetAllBaseTypes(Type type) - { - List list = new List(); - - while (type != null) - { - list.Add(type); - type = type.BaseType; - } - - return list.ToArray(); - } - public static bool LoadModule(string module) { #if CPP diff --git a/src/Inspectors/GameObjects/ComponentList.cs b/src/Inspectors/GameObjects/ComponentList.cs index 9e4453e..0887545 100644 --- a/src/Inspectors/GameObjects/ComponentList.cs +++ b/src/Inspectors/GameObjects/ComponentList.cs @@ -77,7 +77,7 @@ namespace UnityExplorer.Inspectors.GameObjects var text = s_compListTexts[i]; - text.text = UISyntaxHighlight.GetHighlight(ReflectionHelpers.GetActualType(comp), true); + text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true); var toggle = s_compToggles[i]; if (comp is Behaviour behaviour) diff --git a/src/Inspectors/GameObjectInspector.cs b/src/Inspectors/GameObjects/GameObjectInspector.cs similarity index 100% rename from src/Inspectors/GameObjectInspector.cs rename to src/Inspectors/GameObjects/GameObjectInspector.cs diff --git a/src/Inspectors/InspectorBase.cs b/src/Inspectors/InspectorBase.cs index 567ce1a..27f1340 100644 --- a/src/Inspectors/InspectorBase.cs +++ b/src/Inspectors/InspectorBase.cs @@ -8,7 +8,6 @@ namespace UnityExplorer.Inspectors public abstract class InspectorBase { public object Target; - public UnityEngine.Object UnityTarget; public abstract string TabLabel { get; } @@ -22,9 +21,8 @@ namespace UnityExplorer.Inspectors public InspectorBase(object target) { Target = target; - UnityTarget = target as UnityEngine.Object; - if (ObjectNullOrDestroyed(Target, UnityTarget)) + if (IsNullOrDestroyed(Target)) { Destroy(); return; @@ -47,7 +45,7 @@ namespace UnityExplorer.Inspectors public virtual void Update() { - if (ObjectNullOrDestroyed(Target, UnityTarget)) + if (IsNullOrDestroyed(Target)) { Destroy(); return; @@ -86,14 +84,13 @@ namespace UnityExplorer.Inspectors } } - public static bool ObjectNullOrDestroyed(object obj, UnityEngine.Object unityObj, bool suppressWarning = false) + public static bool IsNullOrDestroyed(object obj, bool suppressWarning = false) { + var unityObj = obj as UnityEngine.Object; if (obj == null) { if (!suppressWarning) - { ExplorerCore.LogWarning("The target instance is null!"); - } return true; } @@ -102,9 +99,7 @@ namespace UnityExplorer.Inspectors if (!unityObj) { if (!suppressWarning) - { ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!"); - } return true; } diff --git a/src/Inspectors/InspectorManager.cs b/src/Inspectors/InspectorManager.cs index 95be444..2b86ce2 100644 --- a/src/Inspectors/InspectorManager.cs +++ b/src/Inspectors/InspectorManager.cs @@ -45,13 +45,11 @@ namespace UnityExplorer.Inspectors #endif UnityEngine.Object unityObj = obj as UnityEngine.Object; - if (InspectorBase.ObjectNullOrDestroyed(obj, unityObj)) + if (InspectorBase.IsNullOrDestroyed(obj)) { return; } - MainMenu.Instance.SetPage(HomePage.Instance); - // check if currently inspecting this object foreach (InspectorBase tab in m_currentInspectors) { @@ -84,7 +82,13 @@ namespace UnityExplorer.Inspectors public void Inspect(Type type) { - foreach (var tab in m_currentInspectors) + if (type == null) + { + ExplorerCore.LogWarning("The provided type was null!"); + return; + } + + foreach (var tab in m_currentInspectors.Where(x => x is StaticInspector)) { if (ReferenceEquals(tab.Target as Type, type)) { @@ -101,8 +105,12 @@ namespace UnityExplorer.Inspectors public void SetInspectorTab(InspectorBase inspector) { + if (m_activeInspector == inspector) + return; + UnsetInspectorTab(); + MainMenu.Instance.SetPage(HomePage.Instance); m_activeInspector = inspector; inspector.SetActive(); diff --git a/src/Inspectors/Reflection/CacheObject/CacheMember.cs b/src/Inspectors/Reflection/CacheObject/CacheMember.cs index 94779f8..fbb9543 100644 --- a/src/Inspectors/Reflection/CacheObject/CacheMember.cs +++ b/src/Inspectors/Reflection/CacheObject/CacheMember.cs @@ -152,7 +152,7 @@ namespace UnityExplorer.Inspectors.Reflection private string GetRichTextName() { - return m_richTextName = UISyntaxHighlight.GetHighlight(MemInfo.DeclaringType, false, MemInfo); + return m_richTextName = UISyntaxHighlight.ParseFullSyntax(MemInfo.DeclaringType, false, MemInfo); } #if CPP @@ -179,12 +179,7 @@ namespace UnityExplorer.Inspectors.Reflection if (!typeof(Il2CppSystem.Object).IsAssignableFrom(type)) return true; - var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>) - .MakeGenericType(type) - .GetField("NativeClassPtr") - .GetValue(null); - - if (ptr == IntPtr.Zero) + if (!ReflectionHelpers.Il2CppTypeNotNull(type, out IntPtr ptr)) return false; return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type; @@ -207,7 +202,7 @@ namespace UnityExplorer.Inspectors.Reflection var textGen = m_memLabelText.cachedTextGeneratorForLayout; float preferredWidth = textGen.GetPreferredWidth(RichTextName, textGenSettings); - float max = scrollRect.rect.width * 0.5f; + float max = scrollRect.rect.width * 0.4f; if (preferredWidth > max) preferredWidth = max; @@ -245,6 +240,10 @@ namespace UnityExplorer.Inspectors.Reflection topGroup.childControlHeight = true; topGroup.childControlWidth = true; topGroup.spacing = 10; + topGroup.padding.left = 3; + topGroup.padding.right = 3; + topGroup.padding.top = 0; + topGroup.padding.bottom = 0; // left group @@ -295,6 +294,8 @@ namespace UnityExplorer.Inspectors.Reflection rightGroup.childControlHeight = true; rightGroup.childControlWidth = true; rightGroup.spacing = 4; + rightGroup.padding.top = 2; + rightGroup.padding.bottom = 2; // evaluate button if (this is CacheMethod || HasParameters) diff --git a/src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs b/src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs index 604df8e..a39270c 100644 --- a/src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs +++ b/src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs @@ -22,7 +22,7 @@ namespace UnityExplorer.Inspectors.Reflection // might not need public virtual bool HasSubContent => false; - public string RichTextValue => m_richValue ?? GetRichTextValue(); + public string RichTextValue => m_richValue ?? GetLabelForValue(); internal string m_richValue; internal string m_richValueType; @@ -45,39 +45,20 @@ namespace UnityExplorer.Inspectors.Reflection return; } - GetRichTextValue(); - + GetLabelForValue(); m_text.text = RichTextValue; - //if (Value == null) - // m_text.text = $"null {m_richValueType}"; - //else - // m_text.text = RichTextValue; + bool shouldShowInspect = !InspectorBase.IsNullOrDestroyed(this.Value, true); + if (m_inspectButton.activeSelf != shouldShowInspect) + m_inspectButton.SetActive(shouldShowInspect); } - private MethodInfo GetToStringMethod() - { - try - { - m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0]) - ?? typeof(object).GetMethod("ToString", new Type[0]); - - // test invoke - m_toStringMethod.Invoke(Value, null); - } - catch - { - m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]); - } - return m_toStringMethod; - } - - public string GetRichTextValue() + public string GetLabelForValue() { if (Value != null) ValueType = Value.GetType(); - m_richValueType = UISyntaxHighlight.GetHighlight(ValueType, true); + m_richValueType = UISyntaxHighlight.ParseFullSyntax(ValueType, true); if (OwnerCacheObject is CacheMember cm && !cm.HasEvaluated) return $"Not yet evaluated ({m_richValueType})"; @@ -103,7 +84,11 @@ namespace UnityExplorer.Inspectors.Reflection { var toString = (string)ToStringMethod.Invoke(Value, null); - var temp = toString.Replace(ValueType.FullName, "").Trim(); + var fullnametemp = ValueType.ToString(); + if (fullnametemp.StartsWith("Il2CppSystem")) + fullnametemp = fullnametemp.Substring(6, fullnametemp.Length - 6); + + var temp = toString.Replace(fullnametemp, "").Trim(); if (string.IsNullOrEmpty(temp)) { @@ -127,20 +112,78 @@ namespace UnityExplorer.Inspectors.Reflection return m_richValue = label; } -#region UI CONSTRUCTION + private MethodInfo GetToStringMethod() + { + try + { + m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0]) + ?? typeof(object).GetMethod("ToString", new Type[0]); - internal GameObject m_UIContent; + // test invoke + m_toStringMethod.Invoke(Value, null); + } + catch + { + m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]); + } + return m_toStringMethod; + } + + #region UI CONSTRUCTION + + internal GameObject m_mainContent; + internal GameObject m_inspectButton; internal Text m_text; internal GameObject m_subContentParent; public virtual void ConstructUI(GameObject parent, GameObject subGroup) { - m_UIContent = UIFactory.CreateLabel(parent, TextAnchor.MiddleLeft); - var mainLayout = m_UIContent.AddComponent(); - mainLayout.minWidth = 200; - mainLayout.flexibleWidth = 5000; + m_mainContent = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0)); + var mainGroup = m_mainContent.GetComponent(); + + mainGroup.childForceExpandWidth = true; + mainGroup.childControlWidth = true; + mainGroup.childForceExpandHeight = false; + mainGroup.childControlHeight = true; + mainGroup.spacing = 4; + mainGroup.childAlignment = TextAnchor.UpperLeft; + var mainLayout = m_mainContent.AddComponent(); + mainLayout.flexibleWidth = 9000; + mainLayout.minWidth = 175; mainLayout.minHeight = 25; - m_text = m_UIContent.GetComponent(); + mainLayout.flexibleHeight = 0; + + // inspect button + + m_inspectButton = UIFactory.CreateButton(m_mainContent, new Color(0.3f, 0.3f, 0.3f, 0.2f)); + var inspectLayout = m_inspectButton.AddComponent(); + inspectLayout.minWidth = 60; + inspectLayout.minHeight = 25; + inspectLayout.flexibleHeight = 0; + inspectLayout.flexibleWidth = 0; + var inspectText = m_inspectButton.GetComponentInChildren(); + inspectText.text = "Inspect"; + var inspectBtn = m_inspectButton.GetComponent