using System; using System.Collections.Generic; using System.Linq; using UnityExplorer.Helpers; using UnityExplorer.UI; using UnityExplorer.UI.Shared; using UnityExplorer.Unstrip.ColorUtility; using UnityExplorer.Unstrip.LayerMasks; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityExplorer.Input; namespace UnityExplorer.Inspectors { public class GameObjectInspector : InspectorBase { public override string TabLabel => $" [G] {TargetGO?.name}"; public static GameObjectInspector ActiveInstance { get; private set; } // just to help with casting in il2cpp public GameObject TargetGO; // static UI elements (only constructed once) private static bool m_UIConstructed; private static GameObject s_content; public override GameObject Content { get => s_content; set => s_content = value; } // cached ui elements private static ScrollRect s_contentScroll; private static float s_lastScrollPos; public static TMP_InputField m_nameInput; private static string m_lastName; public static TMP_InputField m_pathInput; private static string m_lastPath; private static GameObject m_pathGroupObj; private static Text m_hiddenPathText; private static Toggle m_enabledToggle; private static Text m_enabledText; private static bool? m_lastEnabledState; private static Dropdown m_layerDropdown; private static int m_lastLayer = -1; private static Text m_sceneText; private static string m_lastScene; // children list public static PageHandler s_childListPageHandler; private static GameObject[] s_allChildren = new GameObject[0]; private static readonly List s_childrenShortlist = new List(); private static GameObject s_childListContent; private static readonly List s_childListTexts = new List(); private static int s_lastChildCount; // comp list public static PageHandler s_compListPageHandler; private static Component[] s_allComps = new Component[0]; private static readonly List s_compShortlist = new List(); private static GameObject s_compListContent; private static readonly List s_compListTexts = new List(); private static int s_lastCompCount; public static readonly List s_compToggles = new List(); // gameobject controls inputs private static TMP_InputField s_setParentInput; private static ControlEditor s_positionControl; private static ControlEditor s_localPosControl; private static ControlEditor s_rotationControl; private static ControlEditor s_scaleControl; internal struct ControlEditor { public TMP_InputField fullValue; public Slider[] sliders; public TMP_InputField[] inputs; public Text[] values; } public GameObjectInspector(GameObject target) : base(target) { ActiveInstance = this; TargetGO = target; if (!TargetGO) { ExplorerCore.LogWarning("GameObjectInspector cctor: Target GameObject is null!"); return; } // one UI is used for all gameobject inspectors. no point recreating it. if (!m_UIConstructed) { ConstructUI(); m_UIConstructed = true; } } public override void SetContentActive() { base.SetContentActive(); ActiveInstance = this; } public override void SetContentInactive() { base.SetContentInactive(); ActiveInstance = null; } public override void Update() { base.Update(); if (m_pendingDestroy || ActiveInstance != this) return; if (!s_sliderChangedWanted) s_lastScrollPos = s_contentScroll.verticalScrollbar.value; RefreshTopInfo(); RefreshChildObjectList(); RefreshComponentList(); RefreshControls(); if (s_sliderChangedWanted) { UpdateSliderControl(); } } private void ChangeInspectorTarget(GameObject newTarget) { if (!newTarget) return; this.Target = this.TargetGO = newTarget; } #region TOP INFO private void RefreshTopInfo() { if (m_lastName != TargetGO.name) { m_lastName = TargetGO.name; m_nameInput.text = m_lastName; } if (TargetGO.transform.parent) { if (!m_pathGroupObj.activeSelf) m_pathGroupObj.SetActive(true); var path = TargetGO.transform.GetTransformPath(true); if (m_lastPath != path) { m_lastPath = path; m_pathInput.text = path; m_hiddenPathText.text = path; } } else if (m_pathGroupObj.activeSelf) m_pathGroupObj.SetActive(false); if (m_lastEnabledState != TargetGO.activeSelf) { m_lastEnabledState = TargetGO.activeSelf; m_enabledToggle.isOn = TargetGO.activeSelf; m_enabledText.text = TargetGO.activeSelf ? "Enabled" : "Disabled"; m_enabledText.color = TargetGO.activeSelf ? Color.green : Color.red; } if (m_lastLayer != TargetGO.layer) { m_lastLayer = TargetGO.layer; m_layerDropdown.value = TargetGO.layer; } if (m_lastScene != TargetGO.scene.name) { m_lastScene = TargetGO.scene.name; if (!string.IsNullOrEmpty(TargetGO.scene.name)) m_sceneText.text = m_lastScene; else m_sceneText.text = "None (Asset/Resource)"; } } private static void OnApplyNameClicked() { if (ActiveInstance == null) return; ActiveInstance.TargetGO.name = m_nameInput.text; } private static void OnEnableToggled(bool enabled) { if (ActiveInstance == null) return; ActiveInstance.TargetGO.SetActive(enabled); } private static void OnLayerSelected(int layer) { if (ActiveInstance == null) return; ActiveInstance.TargetGO.layer = layer; } private static void OnCompToggleClicked(int index, bool value) { var comp = s_compShortlist[index]; (comp as Behaviour).enabled = value; } #endregion #region CHILD LIST private void RefreshChildObjectList() { s_allChildren = new GameObject[TargetGO.transform.childCount]; for (int i = 0; i < TargetGO.transform.childCount; i++) { var child = TargetGO.transform.GetChild(i); s_allChildren[i] = child.gameObject; } var objects = s_allChildren; s_childListPageHandler.ListCount = objects.Length; //int startIndex = m_sceneListPageHandler.StartIndex; int newCount = 0; foreach (var itemIndex in s_childListPageHandler) { newCount++; // normalized index starting from 0 var i = itemIndex - s_childListPageHandler.StartIndex; if (itemIndex >= objects.Length) { if (i > s_lastChildCount || i >= s_childListTexts.Count) break; GameObject label = s_childListTexts[i].transform.parent.parent.gameObject; if (label.activeSelf) label.SetActive(false); } else { GameObject obj = objects[itemIndex]; if (!obj) continue; if (i >= s_childrenShortlist.Count) { s_childrenShortlist.Add(obj); AddChildListButton(); } else { s_childrenShortlist[i] = obj; } var text = s_childListTexts[i]; var name = obj.name; if (obj.transform.childCount > 0) name = $"[{obj.transform.childCount}] {name}"; text.text = name; text.color = obj.activeSelf ? Color.green : Color.red; var label = text.transform.parent.parent.gameObject; if (!label.activeSelf) { label.SetActive(true); } } } s_lastChildCount = newCount; } private static void OnChildListObjectClicked(int index) { if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index]) { return; } instance.ChangeInspectorTarget(s_childrenShortlist[index]); instance.Update(); } private static void OnBackButtonClicked() { if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; instance.ChangeInspectorTarget(instance.TargetGO.transform.parent.gameObject); } private static void OnChildListPageTurn() { if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; instance.RefreshChildObjectList(); } #endregion #region COMPONENT LIST private void RefreshComponentList() { s_allComps = TargetGO.GetComponents().ToArray(); var components = s_allComps; s_compListPageHandler.ListCount = components.Length; //int startIndex = m_sceneListPageHandler.StartIndex; int newCount = 0; foreach (var itemIndex in s_compListPageHandler) { newCount++; // normalized index starting from 0 var i = itemIndex - s_compListPageHandler.StartIndex; if (itemIndex >= components.Length) { if (i > s_lastCompCount || i >= s_compListTexts.Count) break; GameObject label = s_compListTexts[i].transform.parent.parent.gameObject; if (label.activeSelf) label.SetActive(false); } else { Component comp = components[itemIndex]; if (!comp) continue; if (i >= s_compShortlist.Count) { s_compShortlist.Add(comp); AddCompListButton(); } else { s_compShortlist[i] = comp; } var text = s_compListTexts[i]; text.text = ReflectionHelpers.GetActualType(comp).FullName; var toggle = s_compToggles[i]; if (comp is Behaviour behaviour) { if (!toggle.gameObject.activeSelf) toggle.gameObject.SetActive(true); toggle.isOn = behaviour.enabled; } else { if (toggle.gameObject.activeSelf) toggle.gameObject.SetActive(false); } var label = text.transform.parent.parent.gameObject; if (!label.activeSelf) { label.SetActive(true); } } } s_lastCompCount = newCount; } private static void OnCompListObjectClicked(int index) { if (index >= s_compShortlist.Count || !s_compShortlist[index]) { return; } InspectorManager.Instance.Inspect(s_compShortlist[index]); } private static void OnCompListPageTurn() { if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; instance.RefreshComponentList(); } #endregion #region TRANSFORM CONTROLS private void RefreshControls() { s_positionControl.fullValue.text = TargetGO.transform.position.ToStringLong(); s_positionControl.values[0].text = TargetGO.transform.position.x.ToString("F3"); s_positionControl.values[1].text = TargetGO.transform.position.y.ToString("F3"); s_positionControl.values[2].text = TargetGO.transform.position.z.ToString("F3"); s_localPosControl.fullValue.text = TargetGO.transform.localPosition.ToStringLong(); s_localPosControl.values[0].text = TargetGO.transform.position.x.ToString("F3"); s_localPosControl.values[1].text = TargetGO.transform.position.y.ToString("F3"); s_localPosControl.values[2].text = TargetGO.transform.position.z.ToString("F3"); s_rotationControl.fullValue.text = TargetGO.transform.eulerAngles.ToStringLong(); s_rotationControl.values[0].text = TargetGO.transform.position.x.ToString("F3"); s_rotationControl.values[1].text = TargetGO.transform.position.y.ToString("F3"); s_rotationControl.values[2].text = TargetGO.transform.position.z.ToString("F3"); s_scaleControl.fullValue.text = TargetGO.transform.localScale.ToStringLong(); s_scaleControl.values[0].text = TargetGO.transform.position.x.ToString("F3"); s_scaleControl.values[1].text = TargetGO.transform.position.y.ToString("F3"); s_scaleControl.values[2].text = TargetGO.transform.position.z.ToString("F3"); } private static void OnSetParentClicked() { var input = s_setParentInput.text; ExplorerCore.Log("Todo set parent to '" + input + "'"); } internal enum ControlType { position, localPosition, eulerAngles, localScale } internal enum VectorValue { x, y, z }; private static bool s_sliderChangedWanted; private static Slider s_currentSlider; private static ControlType s_currentSliderType; private static VectorValue s_currentSliderValueType; private static float s_currentSliderValue; private static void OnSliderControlChanged(float value, Slider slider, ControlType controlType, VectorValue vectorValue) { if (value == 0) s_sliderChangedWanted = false; else { if (!s_sliderChangedWanted) { s_sliderChangedWanted = true; s_currentSlider = slider; s_currentSliderType = controlType; s_currentSliderValueType = vectorValue; //ExplorerCore.LogWarning("Starting slide with scroll value of " + s_lastScrollPos); } s_currentSliderValue = value; } } private static void UpdateSliderControl() { //s_contentScroll.verticalNormalizedPosition = s_lastScrollPos; ////s_contentScroll.verticalScrollbar.value = s_lastScrollPos; //ExplorerCore.Log("Set scroll pos to " + s_lastScrollPos); if (!InputManager.GetMouseButton(0)) { s_sliderChangedWanted = false; s_currentSlider.value = 0; return; } if (ActiveInstance == null) return; var transform = ActiveInstance.TargetGO.transform; // get the current vector for the control type Vector3 vector = Vector2.zero; switch (s_currentSliderType) { case ControlType.position: vector = transform.position; break; case ControlType.localPosition: vector = transform.localPosition; break; case ControlType.eulerAngles: vector = transform.eulerAngles; break; case ControlType.localScale: vector = transform.localScale; break; } // apply vector value change switch (s_currentSliderValueType) { case VectorValue.x: vector.x += s_currentSliderValue; break; case VectorValue.y: vector.y += s_currentSliderValue; break; case VectorValue.z: vector.z += s_currentSliderValue; break; } // set vector to transform member switch (s_currentSliderType) { case ControlType.position: transform.position = vector; break; case ControlType.localPosition: transform.localPosition = vector; break; case ControlType.eulerAngles: transform.eulerAngles = vector; break; case ControlType.localScale: transform.localScale = vector; break; } } private static void OnVectorControlInputApplied(ControlType controlType, VectorValue vectorValue) { if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; // get relevant input for controltype + value TMP_InputField[] inputs = null; switch (controlType) { case ControlType.position: inputs = s_positionControl.inputs; break; case ControlType.localPosition: inputs = s_localPosControl.inputs; break; case ControlType.eulerAngles: inputs = s_rotationControl.inputs; break; case ControlType.localScale: inputs = s_scaleControl.inputs; break; } TMP_InputField input = inputs[(int)vectorValue]; float val = float.Parse(input.text); // apply transform value Vector3 vector = Vector3.zero; var transform = instance.TargetGO.transform; switch (controlType) { case ControlType.position: vector = transform.position; break; case ControlType.localPosition: vector = transform.localPosition; break; case ControlType.eulerAngles: vector = transform.eulerAngles; break; case ControlType.localScale: vector = transform.localScale; break; } switch (vectorValue) { case VectorValue.x: vector.x = val; break; case VectorValue.y: vector.y = val; break; case VectorValue.z: vector.z = val; break; } // set back to transform switch (controlType) { case ControlType.position: transform.position = vector; break; case ControlType.localPosition: transform.localPosition = vector; break; case ControlType.eulerAngles: transform.eulerAngles = vector; break; case ControlType.localScale: transform.localScale = vector; break; } } #endregion #region UI CONSTRUCTION private void ConstructUI() { var parent = InspectorManager.Instance.m_inspectorContent; s_content = UIFactory.CreateScrollView(parent, out GameObject scrollContent, new Color(0.1f, 0.1f, 0.1f)); s_contentScroll = s_content.GetComponent(); //s_contentScroll.onValueChanged.AddListener((Vector2 val) => //{ // if (s_sliderChangedWanted) // { // s_contentScroll.verticalNormalizedPosition = s_lastScrollPos; // } //}); var scrollGroup = scrollContent.GetComponent(); scrollGroup.childForceExpandHeight = false; scrollGroup.childControlHeight = true; scrollGroup.spacing = 5; ConstructTopArea(scrollContent); ConstructControls(scrollContent); // not sure if i'll use this mid group... //var midGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(1,1,1,0)); //var midGroup = midGroupObj.GetComponent(); //midGroup.spacing = 5; //midGroup.childForceExpandWidth = true; //midGroup.childControlWidth = true; //midGroup.childForceExpandHeight = true; //midGroup.childControlHeight = false; //var midlayout = midGroupObj.AddComponent(); //midlayout.minHeight = 40; //midlayout.flexibleHeight = 10000; //midlayout.flexibleWidth = 25000; //midlayout.minWidth = 200; ConstructChildList(scrollContent); ConstructCompList(scrollContent); } private void ConstructTopArea(GameObject scrollContent) { // path row m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f)); var pathGroup = m_pathGroupObj.GetComponent(); pathGroup.childForceExpandHeight = false; pathGroup.childForceExpandWidth = false; pathGroup.childControlHeight = false; pathGroup.childControlWidth = true; pathGroup.spacing = 5; var pathRect = m_pathGroupObj.GetComponent(); pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20); var pathLayout = m_pathGroupObj.AddComponent(); pathLayout.minHeight = 20; pathLayout.flexibleHeight = 75; var backButtonObj = UIFactory.CreateButton(m_pathGroupObj); var backButton = backButtonObj.GetComponent