diff --git a/src/UI/Inspectors/GameObjectInspector.cs b/src/UI/Inspectors/GameObjectInspector.cs index 8c3078c..2b22695 100644 --- a/src/UI/Inspectors/GameObjectInspector.cs +++ b/src/UI/Inspectors/GameObjectInspector.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.Core.Input; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; +using UnityExplorer.UI.Widgets.AutoComplete; namespace UnityExplorer.UI.Inspectors { @@ -16,27 +18,26 @@ namespace UnityExplorer.UI.Inspectors { public GameObject GOTarget => Target as GameObject; - // Top info - - private InputFieldRef NameInput; - private GameObject PathRow; - private InputFieldRef PathInput; - - // Child and comp lists + public GameObjectControls GOControls; public TransformTree TransformTree; private ScrollPool transformScroll; private readonly List cachedChildren = new List(); - public ButtonListSource ComponentList; - private ScrollPool componentScroll; + public ComponentList ComponentList; + private ScrollPool componentScroll; + + private InputFieldRef addChildInput; + private InputFieldRef addCompInput; public override void OnBorrowedFromPool(object target) { base.OnBorrowedFromPool(target); Target = target as GameObject; - Tab.TabText.text = $"[G] {GOTarget.name}"; + + GOControls.UpdateGameObjectInfo(true, true); + GOControls.UpdateTransformControlValues(true); RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); } @@ -56,12 +57,25 @@ namespace UnityExplorer.UI.Inspectors public override void OnReturnToPool() { base.OnReturnToPool(); + + addChildInput.Text = ""; + addCompInput.Text = ""; } - protected override void OnCloseClicked() + + public override void CloseInspector() { InspectorManager.ReleaseInspector(this); } + public void ChangeTarget(GameObject newTarget) + { + this.Target = newTarget; + GOControls.UpdateGameObjectInfo(true, true); + GOControls.UpdateTransformControlValues(true); + TransformTree.RefreshData(true, false); + UpdateComponents(); + } + private float timeOfLastUpdate; public override void Update() @@ -75,26 +89,22 @@ namespace UnityExplorer.UI.Inspectors return; } + GOControls.UpdateVectorSlider(); + GOControls.UpdateTransformControlValues(false); + + // Slow update if (timeOfLastUpdate.OccuredEarlierThan(1)) { timeOfLastUpdate = Time.realtimeSinceStartup; - // Refresh children and components + GOControls.UpdateGameObjectInfo(false, false); + TransformTree.RefreshData(true, false); - UpdateComponents(); - - Tab.TabText.text = $"[G] {GOTarget.name}"; } } - private void RefreshTopInfo() - { - - } - - - #region Transform and Component Lists + // Child and Component Lists private IEnumerable GetTransformEntries() { @@ -104,157 +114,194 @@ namespace UnityExplorer.UI.Inspectors return cachedChildren; } - private readonly List _componentEntries = new List(); - private readonly HashSet _compInstanceIDs = new HashSet(); + private readonly List componentEntries = new List(); + private readonly HashSet compInstanceIDs = new HashSet(); + private readonly List behaviourEntries = new List(); + private readonly List behaviourEnabledStates = new List(); - private List GetComponentEntries() - { - return _componentEntries; - } + // ComponentList.GetRootEntriesMethod + private List GetComponentEntries() => componentEntries; - private void UpdateComponents() + public void UpdateComponents() { // Check if we actually need to refresh the component cells or not. - // Doing this check is far more efficient than blindly setting cells. - var comps = GOTarget.GetComponents(); + var behaviours = GOTarget.GetComponents(); bool needRefresh = false; - if (comps.Length != _componentEntries.Count) + if (comps.Length != componentEntries.Count || behaviours.Length != behaviourEntries.Count) + { needRefresh = true; + } else { foreach (var comp in comps) { - if (!_compInstanceIDs.Contains(comp.GetInstanceID())) + if (!compInstanceIDs.Contains(comp.GetInstanceID())) { needRefresh = true; break; } } + + if (!needRefresh) + { + for (int i = 0; i < behaviours.Length; i++) + { + var behaviour = behaviours[i]; + if (behaviour.enabled != behaviourEnabledStates[i]) + { + needRefresh = true; + break; + } + } + } } if (!needRefresh) return; - _componentEntries.Clear(); - _compInstanceIDs.Clear(); + componentEntries.Clear(); + compInstanceIDs.Clear(); foreach (var comp in comps) { - _componentEntries.Add(comp); - _compInstanceIDs.Add(comp.GetInstanceID()); + componentEntries.Add(comp); + compInstanceIDs.Add(comp.GetInstanceID()); + } + + behaviourEntries.Clear(); + behaviourEnabledStates.Clear(); + foreach (var behaviour in behaviours) + { + behaviourEntries.Add(behaviour); + behaviourEnabledStates.Add(behaviour.enabled); } ComponentList.RefreshData(); ComponentList.ScrollPool.Refresh(true); } - private static readonly Dictionary compToStringCache = new Dictionary(); - private void SetComponentCell(ButtonCell cell, int index) + private void OnAddChildClicked(string input) { - if (index < 0 || index >= _componentEntries.Count) - { - cell.Disable(); - return; - } + var newObject = new GameObject(input); + newObject.transform.parent = GOTarget.transform; - cell.Enable(); - - var comp = _componentEntries[index]; - var type = comp.GetActualType(); - - if (!compToStringCache.ContainsKey(type.AssemblyQualifiedName)) - { - compToStringCache.Add(type.AssemblyQualifiedName, SignatureHighlighter.Parse(type, true)); - } - - cell.Button.ButtonText.text = compToStringCache[type.AssemblyQualifiedName]; + TransformTree.RefreshData(true, false); } - - private bool ShouldDisplay(Component comp, string filter) => true; - - private void OnComponentClicked(int index) + + private void OnAddComponentClicked(string input) { - if (index < 0 || index >= _componentEntries.Count) - return; - - var comp = _componentEntries[index]; - if (comp) - InspectorManager.Inspect(comp); + if (ReflectionUtility.AllTypes.TryGetValue(input, out Type type)) + { + try + { + RuntimeProvider.Instance.AddComponent(GOTarget, type); + UpdateComponents(); + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception adding component: {ex.ReflectionExToString()}"); + } + } + else + { + ExplorerCore.LogWarning($"Could not find any Type by the name '{input}'!"); + } } - #endregion - #region UI Construction public override GameObject CreateContent(GameObject parent) { UIRoot = UIFactory.CreateVerticalGroup(Pool.Instance.InactiveHolder, - "GameObjectInspector", true, true, true, true, 5, new Vector4(4, 4, 4, 4), new Color(0.12f, 0.12f, 0.12f)); + "GameObjectInspector", true, true, true, true, 5, new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f)); - // Title row + // Construct GO Controls + GOControls = new GameObjectControls(this); - var titleRow = UIFactory.CreateUIObject("TitleRow", UIRoot); - UIFactory.SetLayoutGroup(titleRow, false, false, true, true, 5); - - var titleLabel = UIFactory.CreateLabel(titleRow, "Title", SignatureHighlighter.Parse(typeof(GameObject), true), - TextAnchor.MiddleLeft, fontSize: 17); - UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 30, flexibleHeight: 0, flexibleWidth: 9999); - - // Update button - var updateBtn = UIFactory.CreateButton(titleRow, "UpdateButton", "Update Info", new Color(0.2f, 0.3f, 0.2f)); - UIFactory.SetLayoutElement(updateBtn.Component.gameObject, minHeight: 25, minWidth: 200); - updateBtn.OnClick += () => { ExplorerCore.Log("TODO!"); }; - - // ~~~~~~ Top info ~~~~~~ - - // parent / path row - - this.PathRow = UIFactory.CreateUIObject("ParentRow", UIRoot); - UIFactory.SetLayoutGroup(this.PathRow, false, false, true, true, 5, 2, 2, 2, 2); - UIFactory.SetLayoutElement(this.PathRow, minHeight: 25, flexibleHeight: 100, flexibleWidth: 9999); - - var viewParentButton = UIFactory.CreateButton(PathRow, "ViewParentButton", "View Parent", new Color(0.3f, 0.3f, 0.3f)); - UIFactory.SetLayoutElement(viewParentButton.Component.gameObject, minHeight: 25, minWidth: 80); - viewParentButton.OnClick += () => { ExplorerCore.LogWarning("TODO!"); }; - - this.PathInput = UIFactory.CreateInputField(PathRow, "PathInput", "No parent (root object)"); - UIFactory.SetLayoutElement(PathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); - - - // ~~~~~~ Child and comp lists ~~~~~~ - - var listTitles = UIFactory.CreateUIObject("ListTitles", UIRoot); - UIFactory.SetLayoutGroup(listTitles, true, true, true, true, 5, 2, 2, 2, 2); - UIFactory.SetLayoutElement(listTitles, flexibleWidth: 9999, flexibleHeight: 30); - UIFactory.CreateLabel(listTitles, "ChildListTitle", "Children", TextAnchor.MiddleCenter, default, false, 15); - UIFactory.CreateLabel(listTitles, "CompListTitle", "Components", TextAnchor.MiddleCenter, default, false, 15); - - var listHolder = UIFactory.CreateHorizontalGroup(UIRoot, "ListHolder", true, true, true, true, 5, new Vector4(2, 2, 2, 2)); - UIFactory.SetLayoutElement(listHolder, flexibleWidth: 9999, flexibleHeight: 9999); - - transformScroll = UIFactory.CreateScrollPool(listHolder, "TransformTree", out GameObject transformObj, - out GameObject transformContent, new Color(0.11f, 0.11f, 0.11f)); - UIFactory.SetLayoutElement(transformObj, flexibleHeight: 9999); - UIFactory.SetLayoutElement(transformContent, flexibleHeight: 9999); - - componentScroll = UIFactory.CreateScrollPool(listHolder, "ComponentList", out GameObject compObj, - out GameObject compContent, new Color(0.11f, 0.11f, 0.11f)); - UIFactory.SetLayoutElement(compObj, flexibleHeight: 9999); - UIFactory.SetLayoutElement(compContent, flexibleHeight: 9999); - - TransformTree = new TransformTree(transformScroll) { GetRootEntriesMethod = GetTransformEntries }; - TransformTree.Init(); - - ComponentList = new ButtonListSource(componentScroll, GetComponentEntries, SetComponentCell, ShouldDisplay, OnComponentClicked); - componentScroll.Initialize(ComponentList); + ConstructLists(); return UIRoot; } + // Child and Comp Lists + + private void ConstructLists() + { + var listHolder = UIFactory.CreateUIObject("ListTitles", UIRoot); + UIFactory.SetLayoutGroup(listHolder, false, true, true, true, 8, 2, 2, 2, 2); + UIFactory.SetLayoutElement(listHolder, flexibleWidth: 9999, flexibleHeight: 9999); + + // Left group (Children) + + var leftGroup = UIFactory.CreateUIObject("ChildrenGroup", listHolder); + UIFactory.SetLayoutElement(leftGroup, flexibleWidth: 9999, flexibleHeight: 9999); + UIFactory.SetLayoutGroup(leftGroup, false, false, true, true, 2); + + var childrenLabel = UIFactory.CreateLabel(leftGroup, "ChildListTitle", "Children", TextAnchor.MiddleCenter, default, false, 16); + UIFactory.SetLayoutElement(childrenLabel.gameObject, flexibleWidth: 9999); + + // Add Child + var addChildRow = UIFactory.CreateUIObject("AddChildRow", leftGroup); + UIFactory.SetLayoutGroup(addChildRow, false, false, true, true, 2); + + addChildInput = UIFactory.CreateInputField(addChildRow, "AddChildInput", "Enter a name..."); + UIFactory.SetLayoutElement(addChildInput.Component.gameObject, minHeight: 25, preferredWidth: 9999); + + var addChildButton = UIFactory.CreateButton(addChildRow, "AddChildButton", "Add Child"); + UIFactory.SetLayoutElement(addChildButton.Component.gameObject, minHeight: 25, minWidth: 80); + addChildButton.OnClick += () => { OnAddChildClicked(addChildInput.Text); }; + + // TransformTree + + transformScroll = UIFactory.CreateScrollPool(leftGroup, "TransformTree", out GameObject transformObj, + out GameObject transformContent, new Color(0.11f, 0.11f, 0.11f)); + UIFactory.SetLayoutElement(transformObj, flexibleHeight: 9999); + UIFactory.SetLayoutElement(transformContent, flexibleHeight: 9999); + + TransformTree = new TransformTree(transformScroll, GetTransformEntries); + TransformTree.Init(); + TransformTree.OnClickOverrideHandler = ChangeTarget; + + // Right group (Components) + + var rightGroup = UIFactory.CreateUIObject("ChildrenGroup", listHolder); + UIFactory.SetLayoutElement(rightGroup, flexibleWidth: 9999, flexibleHeight: 9999); + UIFactory.SetLayoutGroup(rightGroup, false, false, true, true, 2); + + var compLabel = UIFactory.CreateLabel(rightGroup, "CompListTitle", "Components", TextAnchor.MiddleCenter, default, false, 16); + UIFactory.SetLayoutElement(compLabel.gameObject, flexibleWidth: 9999); + + // Add Comp + var addCompRow = UIFactory.CreateUIObject("AddCompRow", rightGroup); + UIFactory.SetLayoutGroup(addCompRow, false, false, true, true, 2); + + addCompInput = UIFactory.CreateInputField(addCompRow, "AddCompInput", "Enter a Component type..."); + UIFactory.SetLayoutElement(addCompInput.Component.gameObject, minHeight: 25, preferredWidth: 9999); + + var addCompButton = UIFactory.CreateButton(addCompRow, "AddCompButton", "Add Comp"); + UIFactory.SetLayoutElement(addCompButton.Component.gameObject, minHeight: 25, minWidth: 80); + addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); }; + + // comp autocompleter + new TypeCompleter(typeof(Component), addCompInput); + + // Component List + + componentScroll = UIFactory.CreateScrollPool(rightGroup, "ComponentList", out GameObject compObj, + out GameObject compContent, new Color(0.11f, 0.11f, 0.11f)); + UIFactory.SetLayoutElement(compObj, flexibleHeight: 9999); + UIFactory.SetLayoutElement(compContent, flexibleHeight: 9999); + + ComponentList = new ComponentList(componentScroll, GetComponentEntries); + ComponentList.Parent = this; + componentScroll.Initialize(ComponentList); + } + + #endregion } } diff --git a/src/UI/Inspectors/GameObjectWidgets/ComponentCell.cs b/src/UI/Inspectors/GameObjectWidgets/ComponentCell.cs new file mode 100644 index 0000000..59ef68d --- /dev/null +++ b/src/UI/Inspectors/GameObjectWidgets/ComponentCell.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors +{ + public class ComponentCell : ButtonCell + { + public Toggle BehaviourToggle; + public ButtonRef DestroyButton; + + public Action OnBehaviourToggled; + public Action OnDestroyClicked; + + private void BehaviourToggled(bool val) + { + OnBehaviourToggled?.Invoke(val, CurrentDataIndex); + } + + private void DestroyClicked() + { + OnDestroyClicked?.Invoke(CurrentDataIndex); + } + + public override GameObject CreateContent(GameObject parent) + { + var root = base.CreateContent(parent); + + // Add mask to button so text doesnt overlap on Close button + this.Button.Component.gameObject.AddComponent().showMaskGraphic = true; + + // Behaviour toggle + + var toggleObj = UIFactory.CreateToggle(UIRoot, "BehaviourToggle", out BehaviourToggle, out var behavText); + UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: 25); + BehaviourToggle.onValueChanged.AddListener(BehaviourToggled); + // put at first object + toggleObj.transform.SetSiblingIndex(0); + + // Destroy button + + DestroyButton = UIFactory.CreateButton(UIRoot, "DestroyButton", "X", new Color(0.3f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(DestroyButton.Component.gameObject, minHeight: 21, minWidth: 25); + DestroyButton.OnClick += DestroyClicked; + + return root; + } + } +} diff --git a/src/UI/Inspectors/GameObjectWidgets/ComponentList.cs b/src/UI/Inspectors/GameObjectWidgets/ComponentList.cs new file mode 100644 index 0000000..05b3106 --- /dev/null +++ b/src/UI/Inspectors/GameObjectWidgets/ComponentList.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors +{ + public class ComponentList : ButtonListHandler + { + public GameObjectInspector Parent; + + public ComponentList(ScrollPool scrollPool, Func> getEntriesMethod) + : base(scrollPool, getEntriesMethod, null, null, null) + { + base.SetICell = SetComponentCell; + base.ShouldDisplay = CheckShouldDisplay; + base.OnCellClicked = OnComponentClicked; + } + + private bool CheckShouldDisplay(Component _, string __) => true; + + public override void OnCellBorrowed(ComponentCell cell) + { + base.OnCellBorrowed(cell); + + cell.OnBehaviourToggled += OnBehaviourToggled; + cell.OnDestroyClicked += OnDestroyClicked; + } + + public override void SetCell(ComponentCell cell, int index) + { + base.SetCell(cell, index); + } + + private void OnComponentClicked(int index) + { + var entries = GetEntries(); + + if (index < 0 || index >= entries.Count) + return; + + var comp = entries[index]; + if (comp) + InspectorManager.Inspect(comp); + } + + private void OnBehaviourToggled(bool value, int index) + { + try + { + var entries = GetEntries(); + var comp = entries[index]; + + if (comp.TryCast() is Behaviour behaviour) + behaviour.enabled = value; + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception toggling Behaviour.enabled: {ex.ReflectionExToString()}"); + } + } + + private void OnDestroyClicked(int index) + { + try + { + var entries = GetEntries(); + var comp = entries[index]; + + GameObject.DestroyImmediate(comp); + + Parent.UpdateComponents(); + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception destroying Component: {ex.ReflectionExToString()}"); + } + } + + private static readonly Dictionary compToStringCache = new Dictionary(); + + // Called from ButtonListHandler.SetCell, will be valid + private void SetComponentCell(ComponentCell cell, int index) + { + var entries = GetEntries(); + cell.Enable(); + + var comp = entries[index]; + var type = comp.GetActualType(); + + if (!compToStringCache.ContainsKey(type.AssemblyQualifiedName)) + compToStringCache.Add(type.AssemblyQualifiedName, SignatureHighlighter.Parse(type, true)); + + cell.Button.ButtonText.text = compToStringCache[type.AssemblyQualifiedName]; + + if (typeof(Behaviour).IsAssignableFrom(type)) + { + cell.BehaviourToggle.interactable = true; + cell.BehaviourToggle.Set(comp.TryCast().enabled, false); + } + else + { + cell.BehaviourToggle.interactable = false; + cell.BehaviourToggle.Set(false, false); + } + + // if component is the first index it must be the transform, dont show Destroy button for it. + if (index == 0 && cell.DestroyButton.Component.gameObject.activeSelf) + cell.DestroyButton.Component.gameObject.SetActive(false); + else if (index > 0 && !cell.DestroyButton.Component.gameObject.activeSelf) + cell.DestroyButton.Component.gameObject.SetActive(true); + } + } +} diff --git a/src/UI/Inspectors/GameObjectWidgets/GameObjectControls.cs b/src/UI/Inspectors/GameObjectWidgets/GameObjectControls.cs new file mode 100644 index 0000000..4a5854b --- /dev/null +++ b/src/UI/Inspectors/GameObjectWidgets/GameObjectControls.cs @@ -0,0 +1,658 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Input; + +namespace UnityExplorer.UI.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(); + } + + #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.ChangeTarget(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 + var name = input.Split('/').Last(); + var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject)); + var shortList = new List(); + foreach (var obj in allObjects) + if (obj.name == name) shortList.Add(obj.TryCast()); + foreach (var go in shortList) + { + var 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) + { + if (GOTarget.GetComponent()) + 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 OnLayerDropdownChanged(int value) + { + GOTarget.layer = value; + UpdateGameObjectInfo(false, true); + } + + private void OnFlagsDropdownChanged(int value) + { + try + { + var 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() + { + var 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) + { + var 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; + } + + var 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() + { + var topInfoHolder = UIFactory.CreateVerticalGroup(Parent.UIRoot, "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: 25, flexibleWidth: 9999); + + // first row (parent, path) + + var firstRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder); + UIFactory.SetLayoutGroup(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", "Enter a GameObject name or path..."); + PathInput.Component.textComponent.color = Color.grey; + UIFactory.SetLayoutElement(PathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + PathInput.Component.lineType = InputField.LineType.MultiLineSubmit; + + PathInput.Component.onEndEdit.AddListener((string val) => { OnPathEndEdit(val); }); + + // Title and update row + + var titleRow = UIFactory.CreateUIObject("TitleRow", topInfoHolder); + UIFactory.SetLayoutGroup(titleRow, false, false, true, true, 5); + + var 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.onEndEdit.AddListener((string val) => { OnNameEndEdit(val); }); + + // second row (toggles, instanceID, tag, buttons) + + var secondRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder); + UIFactory.SetLayoutGroup(secondRow, false, false, true, true, 5, 0, 0, 0, 0, default); + UIFactory.SetLayoutElement(secondRow, minHeight: 25, flexibleWidth: 9999); + + // activeSelf + var activeToggleObj = UIFactory.CreateToggle(secondRow, "ActiveSelf", out ActiveSelfToggle, out ActiveSelfText); + UIFactory.SetLayoutElement(activeToggleObj, minHeight: 25, minWidth: 100); + ActiveSelfText.text = "ActiveSelf"; + ActiveSelfToggle.onValueChanged.AddListener(OnActiveSelfToggled); + + // isStatic + var 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 + var 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 + var 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.onEndEdit.AddListener((string val) => { OnTagEndEdit(val); }); + + // Instantiate + var 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 + var 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) + + var thirdrow = UIFactory.CreateUIObject("ParentRow", topInfoHolder); + UIFactory.SetLayoutGroup(thirdrow, false, false, true, true, 5, 0, 0, 0, 0, default); + UIFactory.SetLayoutElement(thirdrow, minHeight: 25, flexibleWidth: 9999); + + // Scene + var 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 + var layerLabel = UIFactory.CreateLabel(thirdrow, "LayerLabel", "Layer:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(layerLabel.gameObject, minHeight: 25, minWidth: 50); + + var layerDrop = UIFactory.CreateDropdown(thirdrow, out LayerDropdown, "0", 14, OnLayerDropdownChanged); + UIFactory.SetLayoutElement(layerDrop, minHeight: 25, minWidth: 120, flexibleWidth: 999); + LayerDropdown.captionText.color = SignatureHighlighter.EnumGreen; + if (layerToNames == null) + GetLayerNames(); + foreach (var name in layerToNames) + LayerDropdown.options.Add(new Dropdown.OptionData(name)); + LayerDropdown.value = 0; + LayerDropdown.RefreshShownValue(); + + // Flags + var flagsLabel = UIFactory.CreateLabel(thirdrow, "FlagsLabel", "Flags:", TextAnchor.MiddleRight, Color.grey); + UIFactory.SetLayoutElement(flagsLabel.gameObject, minHeight: 25, minWidth: 50); + + var flagsDrop = UIFactory.CreateDropdown(thirdrow, out FlagsDropdown, "None", 14, OnFlagsDropdownChanged); + FlagsDropdown.captionText.color = SignatureHighlighter.EnumGreen; + UIFactory.SetLayoutElement(flagsDrop, minHeight: 25, minWidth: 135, flexibleWidth: 999); + if (hideFlagsValues == null) + GetHideFlagNames(); + foreach (var name in hideFlagsValues.Keys) + FlagsDropdown.options.Add(new Dropdown.OptionData(name)); + FlagsDropdown.value = 0; + FlagsDropdown.RefreshShownValue(); + } + + private static List layerToNames; + + private static void GetLayerNames() + { + layerToNames = new List(); + for (int i = 0; i < 32; i++) + { + var name = RuntimeProvider.Instance.LayerToName(i); + if (string.IsNullOrEmpty(name)) + name = i.ToString(); + layerToNames.Add(name); + } + } + + private static Dictionary hideFlagsValues; + + private static void GetHideFlagNames() + { + hideFlagsValues = new Dictionary(); + + var names = Enum.GetValues(typeof(HideFlags)); + foreach (HideFlags value in names) + { + hideFlagsValues.Add(value.ToString(), value); + } + } + + #endregion + + + #region Transform Controls UI Construction + + private void ConstructTransformControls() + { + //var transformGroup = UIFactory.CreateUIObject("TransformGroup", UIRoot); + //UIFactory.SetLayoutGroup(transformGroup, false, false, true, true, 2, 2, 2); + var transformGroup = UIFactory.CreateVerticalGroup(Parent.UIRoot, "TransformControls", false, false, true, true, 2, + new Vector4(2, 2, 0, 0), new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(transformGroup, minHeight: 25, flexibleWidth: 9999); + + 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) + { + var rowObj = UIFactory.CreateUIObject("Row_" + title, transformGroup); + UIFactory.SetLayoutGroup(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999); + + var titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey); + UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110); + + var inputField = UIFactory.CreateInputField(rowObj, "InputField", "..."); + UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999); + + inputField.Component.onEndEdit.AddListener((string value) => { OnTransformInputEndEdit(type, value); }); + + var control = new TransformControl(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) + { + var label = UIFactory.CreateLabel(parent, "Label_" + title, title + ":", TextAnchor.MiddleRight, Color.grey); + UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30); + + var sliderObj = UIFactory.CreateSlider(parent, "Slider_" + title, out var slider); + UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 120, flexibleWidth: 0); + slider.m_FillImage.color = Color.clear; + + slider.minValue = -1; + slider.maxValue = 1; + var sliderControl = new VectorSlider(axis, slider, control); + + slider.onValueChanged.AddListener((float val) => + { + OnVectorSliderChanged(sliderControl, val); + }); + + return sliderControl; + } + + #endregion + } +} diff --git a/src/UI/Inspectors/InspectorBase.cs b/src/UI/Inspectors/InspectorBase.cs index 6a8d6a1..0634bac 100644 --- a/src/UI/Inspectors/InspectorBase.cs +++ b/src/UI/Inspectors/InspectorBase.cs @@ -23,6 +23,8 @@ namespace UnityExplorer.UI.Inspectors public abstract void Update(); + public abstract void CloseInspector(); + public virtual void OnBorrowedFromPool(object target) { this.Target = target; @@ -30,7 +32,7 @@ namespace UnityExplorer.UI.Inspectors Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false); Tab.TabButton.OnClick += OnTabButtonClicked; - Tab.CloseButton.OnClick += OnCloseClicked; + Tab.CloseButton.OnClick += CloseInspector; } public virtual void OnReturnToPool() @@ -40,7 +42,7 @@ namespace UnityExplorer.UI.Inspectors this.Target = null; Tab.TabButton.OnClick -= OnTabButtonClicked; - Tab.CloseButton.OnClick -= OnCloseClicked; + Tab.CloseButton.OnClick -= CloseInspector; } public virtual void OnSetActive() @@ -62,7 +64,5 @@ namespace UnityExplorer.UI.Inspectors { InspectorManager.SetInspectorActive(this); } - - protected abstract void OnCloseClicked(); } } diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs index 87a37b3..c1dffe0 100644 --- a/src/UI/Inspectors/InspectorManager.cs +++ b/src/UI/Inspectors/InspectorManager.cs @@ -21,6 +21,19 @@ namespace UnityExplorer public static float PanelWidth; + internal static void CloseAllTabs() + { + if (Inspectors.Any()) + { + for (int i = Inspectors.Count - 1; i >= 0; i--) + Inspectors[i].CloseInspector(); + + Inspectors.Clear(); + } + + UIManager.SetPanelActive(UIManager.Panels.Inspector, false); + } + public static void Inspect(object obj, CacheObjectBase sourceCache = null) { if (obj.IsNullOrDestroyed()) @@ -73,7 +86,9 @@ namespace UnityExplorer ActiveInspector = null; } } - private static void CreateInspector(object target, bool staticReflection = false, CacheObjectBase sourceCache = null) where T : InspectorBase + + private static void CreateInspector(object target, bool staticReflection = false, + CacheObjectBase sourceCache = null) where T : InspectorBase { var inspector = Pool.Borrow(); Inspectors.Add(inspector); diff --git a/src/UI/Inspectors/InspectorTab.cs b/src/UI/Inspectors/InspectorTab.cs index af2dbde..ae8f22a 100644 --- a/src/UI/Inspectors/InspectorTab.cs +++ b/src/UI/Inspectors/InspectorTab.cs @@ -33,21 +33,23 @@ namespace UnityExplorer.UI.Inspectors public GameObject CreateContent(GameObject parent) { - UIRoot = UIFactory.CreateHorizontalGroup(parent, "TabObject", true, true, true, true, 0, - new Vector4(0, 0, 3, 0), new Color(0.13f, 0.13f, 0.13f)); - UIFactory.SetLayoutElement(UIRoot, minWidth: 185, flexibleWidth: 0); + UIRoot = UIFactory.CreateHorizontalGroup(parent, "TabObject", false, true, true, true, 0, + default, new Color(0.13f, 0.13f, 0.13f), childAlignment: TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(UIRoot, minWidth: 200, flexibleWidth: 0); UIRoot.AddComponent(); TabButton = UIFactory.CreateButton(UIRoot, "TabButton", ""); - - UIFactory.SetLayoutElement(TabButton.Component.gameObject, minWidth: 165, flexibleWidth: 0); + UIFactory.SetLayoutElement(TabButton.Component.gameObject, minWidth: 175, flexibleWidth: 0); + UIFactory.SetLayoutGroup(TabButton.Component.gameObject, false, false, true, true, 0, 0, 0, 3); TabText = TabButton.Component.GetComponentInChildren(); - TabText.horizontalOverflow = HorizontalWrapMode.Overflow; + UIFactory.SetLayoutElement(TabText.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0); TabText.alignment = TextAnchor.MiddleLeft; + TabText.fontSize = 12; + TabText.horizontalOverflow = HorizontalWrapMode.Overflow; CloseButton = UIFactory.CreateButton(UIRoot, "CloseButton", "X", new Color(0.2f, 0.2f, 0.2f, 1)); - UIFactory.SetLayoutElement(CloseButton.Component.gameObject, minWidth: 20, flexibleWidth: 0); + UIFactory.SetLayoutElement(CloseButton.Component.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0); var closeBtnText = CloseButton.Component.GetComponentInChildren(); closeBtnText.color = Color.red; diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index c8fb9d5..9caee23 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Emit; using System.Text; using UnityEngine; using UnityEngine.UI; @@ -28,7 +29,7 @@ namespace UnityExplorer.UI.Inspectors private List members = new List(); private readonly List filteredMembers = new List(); - public bool AutoUpdateWanted { get; set; } + public bool AutoUpdateWanted => autoUpdateToggle.isOn; private BindingFlags FlagsFilter; private string NameFilter; @@ -51,6 +52,8 @@ namespace UnityExplorer.UI.Inspectors public Text AssemblyText; private Toggle autoUpdateToggle; + private string currentBaseTabText; + private readonly Color disabledButtonColor = new Color(0.24f, 0.24f, 0.24f); private readonly Color enabledButtonColor = new Color(0.2f, 0.27f, 0.2f); private readonly Dictionary scopeFilterButtons = new Dictionary(); @@ -76,7 +79,7 @@ namespace UnityExplorer.UI.Inspectors LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect); } - protected override void OnCloseClicked() + public override void CloseInspector() { InspectorManager.ReleaseInspector(this); } @@ -93,7 +96,6 @@ namespace UnityExplorer.UI.Inspectors filteredMembers.Clear(); autoUpdateToggle.isOn = false; - AutoUpdateWanted = false; UnityObjectRef = null; ComponentRef = null; @@ -121,27 +123,26 @@ namespace UnityExplorer.UI.Inspectors } // Setup main labels and tab text - Tab.TabText.text = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}"; + currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}"; + Tab.TabText.text = currentBaseTabText; NameText.text = SignatureHighlighter.Parse(TargetType, true); string asmText; - - try - { - asmText = Path.GetFileName(TargetType.Assembly.Location); - } - catch - { + if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location)) asmText = $"{TargetType.Assembly.GetName().Name} (in memory)"; - } - + else + asmText = Path.GetFileName(TargetType.Assembly.Location); AssemblyText.text = $"Assembly: {asmText}"; // unity helpers SetUnityTargets(); - // Get cache members, and set filter to default + // Get cache members + this.members = CacheMember.GetCacheMembers(Target, TargetType, this); + + // reset filters + this.filterInputField.Text = ""; SetFilter("", StaticOnly ? BindingFlags.Static : BindingFlags.Instance); @@ -173,6 +174,7 @@ namespace UnityExplorer.UI.Inspectors return; } + // check filter changes or force-refresh if (refreshWanted || NameFilter != lastNameFilter || FlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter) { lastNameFilter = NameFilter; @@ -184,10 +186,17 @@ namespace UnityExplorer.UI.Inspectors refreshWanted = false; } + // once-per-second updates if (timeOfLastAutoUpdate.OccuredEarlierThan(1)) { timeOfLastAutoUpdate = Time.realtimeSinceStartup; + if (this.UnityObjectRef) + { + nameInput.Text = UnityObjectRef.name; + this.Tab.TabText.text = $"{currentBaseTabText} \"{UnityObjectRef.name}\""; + } + if (AutoUpdateWanted) UpdateDisplayedMembers(); } @@ -196,12 +205,6 @@ namespace UnityExplorer.UI.Inspectors public void UpdateClicked() { UpdateDisplayedMembers(); - - if (this.UnityObjectRef) - { - nameInput.Text = UnityObjectRef.name; - instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString(); - } } // Filtering @@ -360,6 +363,7 @@ namespace UnityExplorer.UI.Inspectors UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); MemberScrollPool.Initialize(this); + // For debugging scroll pool //InspectorPanel.Instance.UIRoot.GetComponent().enabled = false; //MemberScrollPool.Viewport.GetComponent().enabled = false; //MemberScrollPool.Viewport.GetComponent().color = new Color(0.12f, 0.12f, 0.12f); @@ -392,10 +396,8 @@ namespace UnityExplorer.UI.Inspectors updateButton.OnClick += UpdateClicked; var toggleObj = UIFactory.CreateToggle(rowObj, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText); - //GameObject.DestroyImmediate(toggleText); UIFactory.SetLayoutElement(toggleObj, minWidth: 125, minHeight: 25); autoUpdateToggle.isOn = false; - autoUpdateToggle.onValueChanged.AddListener((bool val) => { AutoUpdateWanted = val; }); toggleText.text = "Auto-update"; } @@ -420,8 +422,6 @@ namespace UnityExplorer.UI.Inspectors // Member type toggles - //var typeLabel = UIFactory.CreateLabel(rowObj, "MemberTypeLabel", "Show:", TextAnchor.MiddleLeft, Color.grey); - //UIFactory.SetLayoutElement(typeLabel.gameObject, minHeight: 25, minWidth: 40); AddMemberTypeToggle(rowObj, MemberTypes.Property, 90); AddMemberTypeToggle(rowObj, MemberTypes.Field, 70); AddMemberTypeToggle(rowObj, MemberTypes.Method, 90); @@ -462,6 +462,9 @@ namespace UnityExplorer.UI.Inspectors memberTypeToggles.Add(toggle); } + + // Todo should probably put this in a separate class or maybe as a widget + #region UNITY OBJECT SPECIFIC // Unity object helpers @@ -558,10 +561,6 @@ namespace UnityExplorer.UI.Inspectors UIFactory.SetLayoutElement(textureButton.Component.gameObject, minHeight: 25, minWidth: 150); textureButton.OnClick += ToggleTextureViewer; - gameObjectButton = UIFactory.CreateButton(unityObjectRow, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); - UIFactory.SetLayoutElement(gameObjectButton.Component.gameObject, minHeight: 25, minWidth: 170); - gameObjectButton.OnClick += OnGameObjectButtonClicked; - var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); @@ -569,6 +568,10 @@ namespace UnityExplorer.UI.Inspectors UIFactory.SetLayoutElement(nameInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 1000); nameInput.Component.readOnly = true; + gameObjectButton = UIFactory.CreateButton(unityObjectRow, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(gameObjectButton.Component.gameObject, minHeight: 25, minWidth: 160); + gameObjectButton.OnClick += OnGameObjectButtonClicked; + var instanceLabel = UIFactory.CreateLabel(unityObjectRow, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); diff --git a/src/UI/Widgets/TransformTree/CachedTransform.cs b/src/UI/Widgets/TransformTree/CachedTransform.cs index 01ca85c..d88189b 100644 --- a/src/UI/Widgets/TransformTree/CachedTransform.cs +++ b/src/UI/Widgets/TransformTree/CachedTransform.cs @@ -16,6 +16,7 @@ namespace UnityExplorer.UI.Widgets public int Depth { get; internal set; } public int ChildCount { get; internal set; } public string Name { get; internal set; } + public bool Enabled { get; internal set; } public bool Expanded => Tree.IsCellExpanded(InstanceID); @@ -32,12 +33,17 @@ namespace UnityExplorer.UI.Widgets { bool ret = false; - if (Value != transform || depth != Depth || ChildCount != transform.childCount || Name != transform.name) + if (Value != transform + || depth != Depth + || ChildCount != transform.childCount + || Name != transform.name + || Enabled != transform.gameObject.activeSelf) { Value = transform; Depth = depth; ChildCount = transform.childCount; Name = transform.name; + Enabled = transform.gameObject.activeSelf; ret = true; } return ret; diff --git a/src/UI/Widgets/TransformTree/TransformCell.cs b/src/UI/Widgets/TransformTree/TransformCell.cs index 72644ab..94d8e6d 100644 --- a/src/UI/Widgets/TransformTree/TransformCell.cs +++ b/src/UI/Widgets/TransformTree/TransformCell.cs @@ -17,6 +17,7 @@ namespace UnityExplorer.UI.Widgets private bool m_enabled; public Action OnExpandToggled; + public Action OnGameObjectClicked; public CachedTransform cachedTransform; public int _cellIndex; @@ -29,6 +30,14 @@ namespace UnityExplorer.UI.Widgets public LayoutElement spacer; + public void OnMainButtonClicked() + { + if (cachedTransform.Value) + OnGameObjectClicked?.Invoke(cachedTransform.Value.gameObject); + else + ExplorerCore.LogWarning("The object was destroyed!"); + } + public void ConfigureCell(CachedTransform cached, int cellIndex) { if (cached == null) @@ -90,14 +99,6 @@ namespace UnityExplorer.UI.Widgets OnExpandToggled?.Invoke(cachedTransform); } - public void OnMainButtonClicked() - { - if (cachedTransform.Value) - InspectorManager.Inspect(cachedTransform.Value.gameObject); - else - ExplorerCore.LogWarning("The object was destroyed!"); - } - public GameObject CreateContent(GameObject parent) { UIRoot = UIFactory.CreateUIObject("TransformCell", parent); diff --git a/src/UI/Widgets/TransformTree/TransformTree.cs b/src/UI/Widgets/TransformTree/TransformTree.cs index fea5677..d2c13af 100644 --- a/src/UI/Widgets/TransformTree/TransformTree.cs +++ b/src/UI/Widgets/TransformTree/TransformTree.cs @@ -14,15 +14,15 @@ namespace UnityExplorer.UI.Widgets public class TransformTree : ICellPoolDataSource { public Func> GetRootEntriesMethod; + public Action OnClickOverrideHandler; - internal ScrollPool ScrollPool; + public ScrollPool ScrollPool; - // Using an OrderedDictionary because we need constant-time lookup of both key and index. /// /// Key: UnityEngine.Transform instance ID
/// Value: CachedTransform ///
- private readonly OrderedDictionary displayedObjects = new OrderedDictionary(); + internal readonly OrderedDictionary displayedObjects = new OrderedDictionary(); // for keeping track of which actual transforms are expanded or not, outside of the cache data. private readonly HashSet expandedInstanceIDs = new HashSet(); @@ -50,9 +50,22 @@ namespace UnityExplorer.UI.Widgets } private string currentFilter; - public TransformTree(ScrollPool scrollPool) + public void OnGameObjectClicked(GameObject obj) + { + if (OnClickOverrideHandler != null) + { + OnClickOverrideHandler.Invoke(obj); + } + else + { + InspectorManager.Inspect(obj); + } + } + + public TransformTree(ScrollPool scrollPool, Func> getRootEntriesMethod) { ScrollPool = scrollPool; + GetRootEntriesMethod = getRootEntriesMethod; } public void Init() @@ -60,8 +73,6 @@ namespace UnityExplorer.UI.Widgets ScrollPool.Initialize(this); } - //public void DisableCell(TransformCell cell, int index) => cell.Disable(); - public bool IsCellExpanded(int instanceID) { @@ -124,16 +135,19 @@ namespace UnityExplorer.UI.Widgets if (visited.Contains(instanceID)) return; - visited.Add(instanceID); if (Filtering) { if (!FilterHierarchy(transform)) return; + visited.Add(instanceID); + if (!autoExpandedIDs.Contains(instanceID)) autoExpandedIDs.Add(instanceID); } + else + visited.Add(instanceID); CachedTransform cached; if (displayedObjects.Contains(instanceID)) @@ -179,7 +193,16 @@ namespace UnityExplorer.UI.Widgets public void SetCell(TransformCell cell, int index) { if (index < displayedObjects.Count) + { cell.ConfigureCell((CachedTransform)displayedObjects[index], index); + if (Filtering) + { + if (cell.cachedTransform.Name.ContainsIgnoreCase(currentFilter)) + { + cell.NameButton.ButtonText.color = Color.green; + } + } + } else cell.Disable(); } @@ -198,11 +221,12 @@ namespace UnityExplorer.UI.Widgets public void OnCellBorrowed(TransformCell cell) { cell.OnExpandToggled += ToggleExpandCell; + cell.OnGameObjectClicked += OnGameObjectClicked; } - public void ReleaseCell(TransformCell cell) - { - cell.OnExpandToggled -= ToggleExpandCell; - } + //public void ReleaseCell(TransformCell cell) + //{ + // cell.OnExpandToggled -= ToggleExpandCell; + //} } } diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 3a83969..a8ba38a 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -234,6 +234,9 @@ + + + @@ -326,7 +329,7 @@ - +