mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-15 07:56:41 +08:00
Namespace cleanup, move some categories out of UI namespace
This commit is contained in:
340
src/Inspectors/GameObjectInspector.cs
Normal file
340
src/Inspectors/GameObjectInspector.cs
Normal file
@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectInspector : InspectorBase
|
||||
{
|
||||
public GameObject GOTarget => Target as GameObject;
|
||||
|
||||
public GameObject Content;
|
||||
|
||||
public GameObjectControls GOControls;
|
||||
|
||||
public TransformTree TransformTree;
|
||||
private ScrollPool<TransformCell> transformScroll;
|
||||
private readonly List<GameObject> cachedChildren = new List<GameObject>();
|
||||
|
||||
public ComponentList ComponentList;
|
||||
private ScrollPool<ComponentCell> componentScroll;
|
||||
|
||||
private InputFieldRef addChildInput;
|
||||
private InputFieldRef addCompInput;
|
||||
|
||||
public override void OnBorrowedFromPool(object target)
|
||||
{
|
||||
base.OnBorrowedFromPool(target);
|
||||
|
||||
Target = target as GameObject;
|
||||
|
||||
GOControls.UpdateGameObjectInfo(true, true);
|
||||
GOControls.UpdateTransformControlValues(true);
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(InitCoroutine());
|
||||
}
|
||||
|
||||
private IEnumerator InitCoroutine()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect);
|
||||
|
||||
TransformTree.Rebuild();
|
||||
|
||||
ComponentList.ScrollPool.Refresh(true, true);
|
||||
UpdateComponents();
|
||||
}
|
||||
|
||||
public override void OnReturnToPool()
|
||||
{
|
||||
base.OnReturnToPool();
|
||||
|
||||
addChildInput.Text = "";
|
||||
addCompInput.Text = "";
|
||||
|
||||
TransformTree.Clear();
|
||||
UpdateComponents();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (!this.IsActive)
|
||||
return;
|
||||
|
||||
if (Target.IsNullOrDestroyed(false))
|
||||
{
|
||||
InspectorManager.ReleaseInspector(this);
|
||||
return;
|
||||
}
|
||||
|
||||
GOControls.UpdateVectorSlider();
|
||||
GOControls.UpdateTransformControlValues(false);
|
||||
|
||||
// Slow update
|
||||
if (timeOfLastUpdate.OccuredEarlierThan(1))
|
||||
{
|
||||
timeOfLastUpdate = Time.realtimeSinceStartup;
|
||||
|
||||
GOControls.UpdateGameObjectInfo(false, false);
|
||||
|
||||
TransformTree.RefreshData(true, false);
|
||||
UpdateComponents();
|
||||
}
|
||||
}
|
||||
|
||||
// Child and Component Lists
|
||||
|
||||
private IEnumerable<GameObject> GetTransformEntries()
|
||||
{
|
||||
if (!GOTarget)
|
||||
return Enumerable.Empty<GameObject>();
|
||||
|
||||
cachedChildren.Clear();
|
||||
for (int i = 0; i < GOTarget.transform.childCount; i++)
|
||||
cachedChildren.Add(GOTarget.transform.GetChild(i).gameObject);
|
||||
return cachedChildren;
|
||||
}
|
||||
|
||||
private readonly List<Component> componentEntries = new List<Component>();
|
||||
private readonly HashSet<int> compInstanceIDs = new HashSet<int>();
|
||||
private readonly List<Behaviour> behaviourEntries = new List<Behaviour>();
|
||||
private readonly List<bool> behaviourEnabledStates = new List<bool>();
|
||||
|
||||
// ComponentList.GetRootEntriesMethod
|
||||
private List<Component> GetComponentEntries() => GOTarget ? componentEntries : Enumerable.Empty<Component>().ToList();
|
||||
|
||||
public void UpdateComponents()
|
||||
{
|
||||
if (!GOTarget)
|
||||
{
|
||||
componentEntries.Clear();
|
||||
compInstanceIDs.Clear();
|
||||
behaviourEntries.Clear();
|
||||
behaviourEnabledStates.Clear();
|
||||
ComponentList.RefreshData();
|
||||
ComponentList.ScrollPool.Refresh(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we actually need to refresh the component cells or not.
|
||||
var comps = GOTarget.GetComponents<Component>();
|
||||
var behaviours = GOTarget.GetComponents<Behaviour>();
|
||||
|
||||
bool needRefresh = false;
|
||||
|
||||
int count = 0;
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (!comp)
|
||||
continue;
|
||||
count++;
|
||||
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
|
||||
{
|
||||
needRefresh = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!needRefresh)
|
||||
{
|
||||
if (count != componentEntries.Count)
|
||||
needRefresh = true;
|
||||
else
|
||||
{
|
||||
count = 0;
|
||||
foreach (var behaviour in behaviours)
|
||||
{
|
||||
if (!behaviour)
|
||||
continue;
|
||||
if (count >= behaviourEnabledStates.Count || behaviour.enabled != behaviourEnabledStates[count])
|
||||
{
|
||||
needRefresh = true;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
if (!needRefresh && count != behaviourEntries.Count)
|
||||
needRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needRefresh)
|
||||
return;
|
||||
|
||||
componentEntries.Clear();
|
||||
compInstanceIDs.Clear();
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (!comp) continue;
|
||||
componentEntries.Add(comp);
|
||||
compInstanceIDs.Add(comp.GetInstanceID());
|
||||
}
|
||||
|
||||
behaviourEntries.Clear();
|
||||
behaviourEnabledStates.Clear();
|
||||
foreach (var behaviour in behaviours)
|
||||
{
|
||||
if (!behaviour) continue;
|
||||
behaviourEntries.Add(behaviour);
|
||||
behaviourEnabledStates.Add(behaviour.enabled);
|
||||
}
|
||||
|
||||
ComponentList.RefreshData();
|
||||
ComponentList.ScrollPool.Refresh(true);
|
||||
}
|
||||
|
||||
|
||||
private void OnAddChildClicked(string input)
|
||||
{
|
||||
var newObject = new GameObject(input);
|
||||
newObject.transform.parent = GOTarget.transform;
|
||||
|
||||
TransformTree.RefreshData(true, false);
|
||||
}
|
||||
|
||||
private void OnAddComponentClicked(string input)
|
||||
{
|
||||
if (ReflectionUtility.GetTypeByName(input) is Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeProvider.Instance.AddComponent<Component>(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}'!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region UI Construction
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "GameObjectInspector", true, false, true, true, 5,
|
||||
new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f));
|
||||
|
||||
var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar,
|
||||
new Color(0.065f, 0.065f, 0.065f));
|
||||
UIFactory.SetLayoutElement(scrollObj, minHeight: 250, preferredHeight: 300, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2);
|
||||
|
||||
// Construct GO Controls
|
||||
GOControls = new GameObjectControls(this);
|
||||
|
||||
ConstructLists();
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
// Child and Comp Lists
|
||||
|
||||
private void ConstructLists()
|
||||
{
|
||||
var listHolder = UIFactory.CreateUIObject("ListHolders", UIRoot);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(listHolder, false, true, true, true, 8, 2, 2, 2, 2);
|
||||
UIFactory.SetLayoutElement(listHolder, minHeight: 150, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
// Left group (Children)
|
||||
|
||||
var leftGroup = UIFactory.CreateUIObject("ChildrenGroup", listHolder);
|
||||
UIFactory.SetLayoutElement(leftGroup, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(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<HorizontalLayoutGroup>(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<TransformCell>(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("ComponentGroup", listHolder);
|
||||
UIFactory.SetLayoutElement(rightGroup, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(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<HorizontalLayoutGroup>(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<ComponentCell>(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
|
||||
}
|
||||
}
|
54
src/Inspectors/GameObjectWidgets/ComponentCell.cs
Normal file
54
src/Inspectors/GameObjectWidgets/ComponentCell.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ComponentCell : ButtonCell
|
||||
{
|
||||
public Toggle BehaviourToggle;
|
||||
public ButtonRef DestroyButton;
|
||||
|
||||
public Action<bool, int> OnBehaviourToggled;
|
||||
public Action<int> 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<Mask>().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;
|
||||
}
|
||||
}
|
||||
}
|
125
src/Inspectors/GameObjectWidgets/ComponentList.cs
Normal file
125
src/Inspectors/GameObjectWidgets/ComponentList.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ComponentList : ButtonListHandler<Component, ComponentCell>
|
||||
{
|
||||
public GameObjectInspector Parent;
|
||||
|
||||
public ComponentList(ScrollPool<ComponentCell> scrollPool, Func<List<Component>> getEntriesMethod)
|
||||
: base(scrollPool, getEntriesMethod, null, null, null)
|
||||
{
|
||||
base.SetICell = SetComponentCell;
|
||||
base.ShouldDisplay = CheckShouldDisplay;
|
||||
base.OnCellClicked = OnComponentClicked;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
RefreshData();
|
||||
ScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
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<Behaviour>() 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<string, string> compToStringCache = new Dictionary<string, string>();
|
||||
|
||||
// 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<Behaviour>().enabled, false);
|
||||
cell.BehaviourToggle.graphic.color = new Color(0.8f, 1, 0.8f, 0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.BehaviourToggle.interactable = false;
|
||||
cell.BehaviourToggle.Set(true, false);
|
||||
//RuntimeProvider.Instance.SetColorBlock(cell.BehaviourToggle,)
|
||||
cell.BehaviourToggle.graphic.color = new Color(0.2f, 0.2f, 0.2f);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
682
src/Inspectors/GameObjectWidgets/GameObjectControls.cs
Normal file
682
src/Inspectors/GameObjectWidgets/GameObjectControls.cs
Normal file
@ -0,0 +1,682 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectControls
|
||||
{
|
||||
public GameObjectInspector Parent;
|
||||
private GameObject GOTarget => Parent.GOTarget;
|
||||
|
||||
// Top info
|
||||
|
||||
private ButtonRef ViewParentButton;
|
||||
private InputFieldRef PathInput;
|
||||
|
||||
private InputFieldRef NameInput;
|
||||
private Toggle ActiveSelfToggle;
|
||||
private Text ActiveSelfText;
|
||||
private Toggle IsStaticToggle;
|
||||
|
||||
private InputFieldRef SceneInput;
|
||||
private InputFieldRef InstanceIDInput;
|
||||
private InputFieldRef TagInput;
|
||||
|
||||
private Dropdown LayerDropdown;
|
||||
private Dropdown FlagsDropdown;
|
||||
|
||||
// transform controls
|
||||
|
||||
private TransformControl PositionControl;
|
||||
private TransformControl LocalPositionControl;
|
||||
private TransformControl RotationControl;
|
||||
private TransformControl ScaleControl;
|
||||
|
||||
private VectorSlider currentSlidingVectorControl;
|
||||
private float currentVectorValue;
|
||||
|
||||
public GameObjectControls(GameObjectInspector parent)
|
||||
{
|
||||
this.Parent = parent;
|
||||
|
||||
ConstructTopInfo();
|
||||
ConstructTransformControls();
|
||||
}
|
||||
|
||||
#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<GameObject>();
|
||||
foreach (var obj in allObjects)
|
||||
if (obj.name == name) shortList.Add(obj.TryCast<GameObject>());
|
||||
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)
|
||||
{
|
||||
ExplorerCore.Log($"Setting target's transform parent to: {(transform == null ? "null" : $"'{transform.name}'")}");
|
||||
|
||||
if (GOTarget.GetComponent<RectTransform>())
|
||||
GOTarget.transform.SetParent(transform, false);
|
||||
else
|
||||
GOTarget.transform.parent = transform;
|
||||
|
||||
UpdateGameObjectInfo(false, false);
|
||||
UpdateTransformControlValues(false);
|
||||
}
|
||||
|
||||
private void OnNameEndEdit(string value)
|
||||
{
|
||||
GOTarget.name = value;
|
||||
UpdateGameObjectInfo(false, true);
|
||||
}
|
||||
|
||||
private void OnActiveSelfToggled(bool value)
|
||||
{
|
||||
GOTarget.SetActive(value);
|
||||
UpdateGameObjectInfo(false, true);
|
||||
}
|
||||
|
||||
private void OnTagEndEdit(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
GOTarget.tag = value;
|
||||
UpdateGameObjectInfo(false, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting tag! {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExploreButtonClicked()
|
||||
{
|
||||
var panel = UIManager.GetPanel<UI.Panels.ObjectExplorerPanel>(UIManager.Panels.ObjectExplorer);
|
||||
panel.SceneExplorer.JumpToTransform(this.Parent.GOTarget.transform);
|
||||
}
|
||||
|
||||
private void OnLayerDropdownChanged(int value)
|
||||
{
|
||||
GOTarget.layer = value;
|
||||
UpdateGameObjectInfo(false, true);
|
||||
}
|
||||
|
||||
private void OnFlagsDropdownChanged(int value)
|
||||
{
|
||||
try
|
||||
{
|
||||
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.Content, "TopInfoHolder", false, false, true, true, 3,
|
||||
new Vector4(3, 3, 3, 3), new Color(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(topInfoHolder, minHeight: 100, flexibleWidth: 9999);
|
||||
topInfoHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
// first row (parent, path)
|
||||
|
||||
var firstRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(firstRow, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(firstRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ViewParentButton = UIFactory.CreateButton(firstRow, "ViewParentButton", "◄ View Parent", new Color(0.2f, 0.2f, 0.2f));
|
||||
ViewParentButton.ButtonText.fontSize = 13;
|
||||
UIFactory.SetLayoutElement(ViewParentButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
ViewParentButton.OnClick += OnViewParentClicked;
|
||||
|
||||
this.PathInput = UIFactory.CreateInputField(firstRow, "PathInput", "...");
|
||||
PathInput.Component.textComponent.color = Color.grey;
|
||||
PathInput.Component.textComponent.fontSize = 14;
|
||||
UIFactory.SetLayoutElement(PathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
|
||||
PathInput.Component.lineType = InputField.LineType.MultiLineSubmit;
|
||||
|
||||
//var pathApplyBtn = UIFactory.CreateButton(firstRow, "PathButton", "Set Parent Path", new Color(0.2f, 0.2f, 0.2f));
|
||||
//UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120);
|
||||
//pathApplyBtn.OnClick += () => { OnPathEndEdit(PathInput.Text); };
|
||||
|
||||
PathInput.Component.GetOnEndEdit().AddListener((string val) => { OnPathEndEdit(val); });
|
||||
|
||||
// Title and update row
|
||||
|
||||
var titleRow = UIFactory.CreateUIObject("TitleRow", topInfoHolder);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(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.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
|
||||
|
||||
// second row (toggles, instanceID, tag, buttons)
|
||||
|
||||
var secondRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(secondRow, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(secondRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
// activeSelf
|
||||
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.GetOnEndEdit().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<HorizontalLayoutGroup>(thirdrow, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(thirdrow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
// Inspect in Explorer button
|
||||
var explorerBtn = UIFactory.CreateButton(thirdrow, "ExploreBtn", "Show in Explorer", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(explorerBtn.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
explorerBtn.ButtonText.fontSize = 12;
|
||||
explorerBtn.OnClick += OnExploreButtonClicked;
|
||||
|
||||
// Scene
|
||||
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: 110, 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<string> layerToNames;
|
||||
|
||||
private static void GetLayerNames()
|
||||
{
|
||||
layerToNames = new List<string>();
|
||||
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<string, HideFlags> hideFlagsValues;
|
||||
|
||||
private static void GetHideFlagNames()
|
||||
{
|
||||
hideFlagsValues = new Dictionary<string, HideFlags>();
|
||||
|
||||
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.CreateVerticalGroup(Parent.Content, "TransformControls", false, false, true, true, 2,
|
||||
new Vector4(2, 2, 0, 0), new Color(0.1f, 0.1f, 0.1f));
|
||||
UIFactory.SetLayoutElement(transformGroup, minHeight: 100, flexibleWidth: 9999);
|
||||
//transformGroup.SetActive(false);
|
||||
//var groupRect = transformGroup.GetComponent<RectTransform>();
|
||||
//groupRect.anchorMin = new Vector2(0, 1);
|
||||
//groupRect.anchorMax = new Vector2(1, 1);
|
||||
//groupRect.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, 100);
|
||||
|
||||
PositionControl = AddTransformRow(transformGroup, "Position:", TransformType.Position);
|
||||
LocalPositionControl = AddTransformRow(transformGroup, "Local Position:", TransformType.LocalPosition);
|
||||
RotationControl = AddTransformRow(transformGroup, "Rotation:", TransformType.Rotation);
|
||||
ScaleControl = AddTransformRow(transformGroup, "Scale:", TransformType.Scale);
|
||||
}
|
||||
|
||||
private TransformControl AddTransformRow(GameObject transformGroup, string title, TransformType type)
|
||||
{
|
||||
var rowObj = UIFactory.CreateUIObject("Row_" + title, transformGroup);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
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.GetOnEndEdit().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
|
||||
}
|
||||
}
|
391
src/Inspectors/InspectUnderMouse.cs
Normal file
391
src/Inspectors/InspectUnderMouse.cs
Normal file
@ -0,0 +1,391 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public enum MouseInspectMode
|
||||
{
|
||||
World,
|
||||
UI
|
||||
}
|
||||
|
||||
public class InspectUnderMouse : UIPanel
|
||||
{
|
||||
public static InspectUnderMouse Instance { get; private set; }
|
||||
|
||||
public InspectUnderMouse() { Instance = this; }
|
||||
|
||||
public static void OnDropdownSelect(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return;
|
||||
case 1: Instance.StartInspect(MouseInspectMode.World); break;
|
||||
case 2: Instance.StartInspect(MouseInspectMode.UI); break;
|
||||
}
|
||||
UIManager.MouseInspectDropdown.value = 0;
|
||||
}
|
||||
|
||||
// UIPanel
|
||||
public override string Name => "Inspect Under Mouse";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.MouseInspector;
|
||||
public override int MinWidth => -1;
|
||||
public override int MinHeight => -1;
|
||||
public override bool CanDragAndResize => false;
|
||||
public override bool NavButtonWanted => false;
|
||||
public override bool ShouldSaveActiveState => false;
|
||||
public override bool ShowByDefault => false;
|
||||
|
||||
internal static Text objNameLabel;
|
||||
internal static Text objPathLabel;
|
||||
internal static Text mousePosLabel;
|
||||
|
||||
// Mouse Inspector
|
||||
public static bool Inspecting { get; set; }
|
||||
public static MouseInspectMode Mode { get; set; }
|
||||
|
||||
private static GameObject lastHitObject;
|
||||
private static Vector3 lastMousePos;
|
||||
|
||||
private static readonly List<Graphic> wasDisabledGraphics = new List<Graphic>();
|
||||
private static readonly List<CanvasGroup> wasDisabledCanvasGroups = new List<CanvasGroup>();
|
||||
private static readonly List<GameObject> objectsAddedCastersTo = new List<GameObject>();
|
||||
|
||||
internal static Camera MainCamera;
|
||||
internal static GraphicRaycaster[] graphicRaycasters;
|
||||
|
||||
public void StartInspect(MouseInspectMode mode)
|
||||
{
|
||||
MainCamera = Camera.main;
|
||||
|
||||
if (!MainCamera && mode == MouseInspectMode.World)
|
||||
{
|
||||
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
|
||||
return;
|
||||
}
|
||||
|
||||
PanelDragger.ForceEnd();
|
||||
|
||||
Mode = mode;
|
||||
Inspecting = true;
|
||||
UIManager.NavBarRect.gameObject.SetActive(false);
|
||||
UIManager.PanelHolder.SetActive(false);
|
||||
|
||||
UIRoot.SetActive(true);
|
||||
|
||||
if (mode == MouseInspectMode.UI)
|
||||
SetupUIRaycast();
|
||||
}
|
||||
|
||||
internal void ClearHitData()
|
||||
{
|
||||
lastHitObject = null;
|
||||
objNameLabel.text = "No hits...";
|
||||
objPathLabel.text = "";
|
||||
}
|
||||
|
||||
public void StopInspect()
|
||||
{
|
||||
Inspecting = false;
|
||||
|
||||
UIManager.NavBarRect.gameObject.SetActive(true);
|
||||
UIManager.PanelHolder.SetActive(true);
|
||||
|
||||
var drop = UIManager.MouseInspectDropdown;
|
||||
if (drop.transform.Find("Dropdown List") is Transform list)
|
||||
drop.DestroyDropdownList(list.gameObject);
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
|
||||
if (Mode == MouseInspectMode.UI)
|
||||
StopUIInspect();
|
||||
|
||||
ClearHitData();
|
||||
}
|
||||
|
||||
private static float timeOfLastRaycast;
|
||||
|
||||
public void UpdateInspect()
|
||||
{
|
||||
if (InputManager.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
StopInspect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastHitObject && InputManager.GetMouseButtonDown(0))
|
||||
{
|
||||
var target = lastHitObject;
|
||||
StopInspect();
|
||||
InspectorManager.Inspect(target);
|
||||
return;
|
||||
}
|
||||
|
||||
var mousePos = InputManager.MousePosition;
|
||||
|
||||
if (mousePos != lastMousePos)
|
||||
UpdatePosition(mousePos);
|
||||
|
||||
if (!timeOfLastRaycast.OccuredEarlierThan(0.1f))
|
||||
return;
|
||||
|
||||
timeOfLastRaycast = Time.realtimeSinceStartup;
|
||||
|
||||
// actual inspect raycast
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case MouseInspectMode.UI:
|
||||
RaycastUI(mousePos); break;
|
||||
case MouseInspectMode.World:
|
||||
RaycastWorld(mousePos); break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdatePosition(Vector2 mousePos)
|
||||
{
|
||||
lastMousePos = mousePos;
|
||||
|
||||
// use the raw mouse pos for the label
|
||||
mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
|
||||
|
||||
// constrain the mouse pos we use within certain bounds
|
||||
if (mousePos.x < 350)
|
||||
mousePos.x = 350;
|
||||
if (mousePos.x > Screen.width - 350)
|
||||
mousePos.x = Screen.width - 350;
|
||||
if (mousePos.y < Rect.rect.height)
|
||||
mousePos.y += Rect.rect.height + 10;
|
||||
else
|
||||
mousePos.y -= 10;
|
||||
|
||||
// calculate and set our UI position
|
||||
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
|
||||
|
||||
UIRoot.transform.localPosition = new Vector3(inversePos.x, inversePos.y, 0);
|
||||
}
|
||||
|
||||
internal void OnHitGameObject(GameObject obj)
|
||||
{
|
||||
if (obj != lastHitObject)
|
||||
{
|
||||
lastHitObject = obj;
|
||||
objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
|
||||
objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
|
||||
}
|
||||
}
|
||||
|
||||
// Collider raycasting
|
||||
|
||||
internal void RaycastWorld(Vector2 mousePos)
|
||||
{
|
||||
var ray = MainCamera.ScreenPointToRay(mousePos);
|
||||
Physics.Raycast(ray, out RaycastHit hit, 1000f);
|
||||
|
||||
if (hit.transform)
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
OnHitGameObject(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastHitObject)
|
||||
ClearHitData();
|
||||
}
|
||||
}
|
||||
|
||||
// UI Graphic raycasting
|
||||
|
||||
private static void SetupUIRaycast()
|
||||
{
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas)))
|
||||
{
|
||||
var canvas = obj.TryCast<Canvas>();
|
||||
if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
if (!canvas.GetComponent<GraphicRaycaster>())
|
||||
{
|
||||
canvas.gameObject.AddComponent<GraphicRaycaster>();
|
||||
//ExplorerCore.Log("Added raycaster to " + canvas.name);
|
||||
objectsAddedCastersTo.Add(canvas.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// recache Graphic Raycasters each time we start
|
||||
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
|
||||
graphicRaycasters = new GraphicRaycaster[casters.Length];
|
||||
for (int i = 0; i < casters.Length; i++)
|
||||
{
|
||||
graphicRaycasters[i] = casters[i].TryCast<GraphicRaycaster>();
|
||||
}
|
||||
|
||||
// enable raycastTarget on Graphics
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic)))
|
||||
{
|
||||
var graphic = obj.TryCast<Graphic>();
|
||||
if (!graphic || !graphic.enabled || graphic.raycastTarget || !graphic.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
graphic.raycastTarget = true;
|
||||
//ExplorerCore.Log("Enabled raycastTarget on " + graphic.name);
|
||||
wasDisabledGraphics.Add(graphic);
|
||||
}
|
||||
|
||||
// enable blocksRaycasts on CanvasGroups
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(CanvasGroup)))
|
||||
{
|
||||
var canvas = obj.TryCast<CanvasGroup>();
|
||||
if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts)
|
||||
continue;
|
||||
canvas.blocksRaycasts = true;
|
||||
//ExplorerCore.Log("Enabled raycasts on " + canvas.name);
|
||||
wasDisabledCanvasGroups.Add(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RaycastUI(Vector2 mousePos)
|
||||
{
|
||||
var ped = new PointerEventData(null)
|
||||
{
|
||||
position = mousePos
|
||||
};
|
||||
|
||||
//ExplorerCore.Log("~~~~~~~~~ begin raycast ~~~~~~~~");
|
||||
GameObject hitObject = null;
|
||||
int highestLayer = int.MinValue;
|
||||
int highestOrder = int.MinValue;
|
||||
int highestDepth = int.MinValue;
|
||||
foreach (var gr in graphicRaycasters)
|
||||
{
|
||||
if (!gr || !gr.canvas)
|
||||
continue;
|
||||
|
||||
var list = new List<RaycastResult>();
|
||||
RuntimeProvider.Instance.GraphicRaycast(gr, ped, list);
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
foreach (var hit in list)
|
||||
{
|
||||
// Manualy trying to determine which object is "on top".
|
||||
// Could be improved, but seems to work pretty well and isn't as laggy as you would expect.
|
||||
|
||||
if (!hit.gameObject)
|
||||
continue;
|
||||
|
||||
if (hit.gameObject.GetComponent<CanvasGroup>() is CanvasGroup group && group.alpha == 0)
|
||||
continue;
|
||||
|
||||
if (hit.gameObject.GetComponent<Graphic>() is Graphic graphic && graphic.color.a == 0f)
|
||||
continue;
|
||||
|
||||
if (hit.sortingLayer < highestLayer)
|
||||
continue;
|
||||
|
||||
if (hit.sortingLayer > highestLayer)
|
||||
{
|
||||
highestLayer = hit.sortingLayer;
|
||||
highestDepth = int.MinValue;
|
||||
}
|
||||
|
||||
if (hit.depth < highestDepth)
|
||||
continue;
|
||||
|
||||
if (hit.depth > highestDepth)
|
||||
{
|
||||
highestDepth = hit.depth;
|
||||
highestOrder = int.MinValue;
|
||||
}
|
||||
|
||||
if (hit.sortingOrder <= highestOrder)
|
||||
continue;
|
||||
|
||||
highestOrder = hit.sortingOrder;
|
||||
hitObject = hit.gameObject;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastHitObject)
|
||||
ClearHitData();
|
||||
}
|
||||
}
|
||||
|
||||
if (hitObject)
|
||||
OnHitGameObject(hitObject);
|
||||
|
||||
//ExplorerCore.Log("~~~~~~~~~ end raycast ~~~~~~~~");
|
||||
}
|
||||
|
||||
private static void StopUIInspect()
|
||||
{
|
||||
foreach (var obj in objectsAddedCastersTo)
|
||||
{
|
||||
if (obj.GetComponent<GraphicRaycaster>() is GraphicRaycaster raycaster)
|
||||
GameObject.Destroy(raycaster);
|
||||
}
|
||||
|
||||
foreach (var graphic in wasDisabledGraphics)
|
||||
graphic.raycastTarget = false;
|
||||
|
||||
foreach (var canvas in wasDisabledCanvasGroups)
|
||||
canvas.blocksRaycasts = false;
|
||||
|
||||
objectsAddedCastersTo.Clear();
|
||||
wasDisabledCanvasGroups.Clear();
|
||||
wasDisabledGraphics.Clear();
|
||||
}
|
||||
|
||||
|
||||
// UI Construction
|
||||
|
||||
protected internal override void DoSetDefaultPosAndAnchors()
|
||||
{
|
||||
Rect.anchorMin = Vector2.zero;
|
||||
Rect.anchorMax = Vector2.zero;
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(700, 150);
|
||||
}
|
||||
|
||||
public override void ConstructPanelContent()
|
||||
{
|
||||
// hide title bar
|
||||
this.titleBar.SetActive(false);
|
||||
this.UIRoot.transform.SetParent(UIManager.CanvasRoot.transform, false);
|
||||
|
||||
var inspectContent = UIFactory.CreateVerticalGroup(this.content, "InspectContent", true, true, true, true, 3, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(inspectContent, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
// Title text
|
||||
|
||||
var title = UIFactory.CreateLabel(inspectContent, "InspectLabel", "<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(title.gameObject, flexibleWidth: 9999);
|
||||
|
||||
mousePosLabel = UIFactory.CreateLabel(inspectContent, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter);
|
||||
|
||||
objNameLabel = UIFactory.CreateLabel(inspectContent, "HitLabelObj", "No hits...", TextAnchor.MiddleLeft);
|
||||
objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
objPathLabel = UIFactory.CreateLabel(inspectContent, "PathLabel", "", TextAnchor.MiddleLeft);
|
||||
objPathLabel.fontStyle = FontStyle.Italic;
|
||||
objPathLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
|
||||
UIFactory.SetLayoutElement(objPathLabel.gameObject, minHeight: 75);
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public override void DoSaveToConfigElement() { }
|
||||
|
||||
public override string GetSaveDataFromConfigManager() => null;
|
||||
}
|
||||
}
|
69
src/Inspectors/InspectorBase.cs
Normal file
69
src/Inspectors/InspectorBase.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public abstract class InspectorBase : IPooledObject
|
||||
{
|
||||
public bool IsActive { get; internal set; }
|
||||
public object Target { get; set; }
|
||||
|
||||
public InspectorTab Tab { get; internal set; }
|
||||
|
||||
public GameObject UIRoot { get; set; }
|
||||
|
||||
public float DefaultHeight => -1f;
|
||||
public abstract GameObject CreateContent(GameObject parent);
|
||||
|
||||
public abstract void Update();
|
||||
|
||||
public abstract void CloseInspector();
|
||||
|
||||
public virtual void OnBorrowedFromPool(object target)
|
||||
{
|
||||
this.Target = target;
|
||||
Tab = Pool<InspectorTab>.Borrow();
|
||||
Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false);
|
||||
|
||||
Tab.TabButton.OnClick += OnTabButtonClicked;
|
||||
Tab.CloseButton.OnClick += CloseInspector;
|
||||
}
|
||||
|
||||
public virtual void OnReturnToPool()
|
||||
{
|
||||
Pool<InspectorTab>.Return(Tab);
|
||||
|
||||
this.Target = null;
|
||||
|
||||
Tab.TabButton.OnClick -= OnTabButtonClicked;
|
||||
Tab.CloseButton.OnClick -= CloseInspector;
|
||||
}
|
||||
|
||||
public virtual void OnSetActive()
|
||||
{
|
||||
Tab.SetTabColor(true);
|
||||
UIRoot.SetActive(true);
|
||||
IsActive = true;
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(UIRoot.GetComponent<RectTransform>());
|
||||
}
|
||||
|
||||
public virtual void OnSetInactive()
|
||||
{
|
||||
Tab.SetTabColor(false);
|
||||
UIRoot.SetActive(false);
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
private void OnTabButtonClicked()
|
||||
{
|
||||
InspectorManager.SetInspectorActive(this);
|
||||
}
|
||||
}
|
||||
}
|
166
src/Inspectors/InspectorManager.cs
Normal file
166
src/Inspectors/InspectorManager.cs
Normal file
@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class InspectorManager
|
||||
{
|
||||
public static readonly List<InspectorBase> Inspectors = new List<InspectorBase>();
|
||||
|
||||
public static InspectorBase ActiveInspector { get; private set; }
|
||||
private static InspectorBase lastActiveInspector;
|
||||
|
||||
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())
|
||||
return;
|
||||
|
||||
obj = obj.TryCast();
|
||||
|
||||
if (TryFocusActiveInspector(obj))
|
||||
return;
|
||||
|
||||
if (obj is GameObject)
|
||||
CreateInspector<GameObjectInspector>(obj);
|
||||
else
|
||||
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
|
||||
}
|
||||
|
||||
private static bool TryFocusActiveInspector(object target)
|
||||
{
|
||||
foreach (var inspector in Inspectors)
|
||||
{
|
||||
if (inspector.Target.ReferenceEqual(target))
|
||||
{
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
|
||||
SetInspectorActive(inspector);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
CreateInspector<ReflectionInspector>(type, true);
|
||||
}
|
||||
|
||||
public static void SetInspectorActive(InspectorBase inspector)
|
||||
{
|
||||
UnsetActiveInspector();
|
||||
|
||||
ActiveInspector = inspector;
|
||||
inspector.OnSetActive();
|
||||
}
|
||||
|
||||
public static void UnsetActiveInspector()
|
||||
{
|
||||
if (ActiveInspector != null)
|
||||
{
|
||||
lastActiveInspector = ActiveInspector;
|
||||
ActiveInspector.OnSetInactive();
|
||||
ActiveInspector = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateInspector<T>(object target, bool staticReflection = false,
|
||||
CacheObjectBase sourceCache = null) where T : InspectorBase
|
||||
{
|
||||
var inspector = Pool<T>.Borrow();
|
||||
Inspectors.Add(inspector);
|
||||
inspector.Target = target;
|
||||
|
||||
if (sourceCache != null && sourceCache.CanWrite)
|
||||
{
|
||||
// only set parent cache object if we are inspecting a struct, otherwise there is no point.
|
||||
if (target.GetType().IsValueType && inspector is ReflectionInspector ri)
|
||||
ri.ParentCacheObject = sourceCache;
|
||||
}
|
||||
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
|
||||
inspector.UIRoot.transform.SetParent(InspectorPanel.Instance.ContentHolder.transform, false);
|
||||
|
||||
if (inspector is ReflectionInspector reflectInspector)
|
||||
reflectInspector.StaticOnly = staticReflection;
|
||||
|
||||
inspector.OnBorrowedFromPool(target);
|
||||
SetInspectorActive(inspector);
|
||||
}
|
||||
|
||||
internal static void ReleaseInspector<T>(T inspector) where T : InspectorBase
|
||||
{
|
||||
if (lastActiveInspector == inspector)
|
||||
lastActiveInspector = null;
|
||||
|
||||
bool wasActive = ActiveInspector == inspector;
|
||||
int wasIdx = Inspectors.IndexOf(inspector);
|
||||
|
||||
Inspectors.Remove(inspector);
|
||||
inspector.OnReturnToPool();
|
||||
Pool<T>.Return(inspector);
|
||||
|
||||
if (wasActive)
|
||||
{
|
||||
ActiveInspector = null;
|
||||
// Try focus another inspector, or close the window.
|
||||
if (lastActiveInspector != null)
|
||||
{
|
||||
SetInspectorActive(lastActiveInspector);
|
||||
lastActiveInspector = null;
|
||||
}
|
||||
else if (Inspectors.Any())
|
||||
{
|
||||
int newIdx = Math.Min(Inspectors.Count - 1, Math.Max(0, wasIdx - 1));
|
||||
SetInspectorActive(Inspectors[newIdx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
for (int i = Inspectors.Count - 1; i >= 0; i--)
|
||||
Inspectors[i].Update();
|
||||
}
|
||||
|
||||
internal static void OnPanelResized(float width)
|
||||
{
|
||||
PanelWidth = width;
|
||||
|
||||
foreach (var obj in Inspectors)
|
||||
{
|
||||
if (obj is ReflectionInspector inspector)
|
||||
{
|
||||
inspector.SetLayouts();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
src/Inspectors/InspectorTab.cs
Normal file
60
src/Inspectors/InspectorTab.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class InspectorTab : IPooledObject
|
||||
{
|
||||
public GameObject UIRoot { get; set; }
|
||||
|
||||
public float DefaultHeight => 25f;
|
||||
|
||||
public ButtonRef TabButton;
|
||||
public Text TabText;
|
||||
|
||||
public ButtonRef CloseButton;
|
||||
|
||||
private static readonly Color _enabledTabColor = new Color(0.15f, 0.22f, 0.15f);
|
||||
private static readonly Color _disabledTabColor = new Color(0.13f, 0.13f, 0.13f);
|
||||
|
||||
public void SetTabColor(bool active)
|
||||
{
|
||||
if (active)
|
||||
RuntimeProvider.Instance.SetColorBlock(TabButton.Component, _enabledTabColor, _enabledTabColor * 1.2f);
|
||||
else
|
||||
RuntimeProvider.Instance.SetColorBlock(TabButton.Component, _disabledTabColor, _disabledTabColor * 1.2f);
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
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<Mask>();
|
||||
|
||||
TabButton = UIFactory.CreateButton(UIRoot, "TabButton", "");
|
||||
UIFactory.SetLayoutElement(TabButton.Component.gameObject, minWidth: 175, flexibleWidth: 0);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(TabButton.Component.gameObject, false, false, true, true, 0, 0, 0, 3);
|
||||
|
||||
TabText = TabButton.Component.GetComponentInChildren<Text>();
|
||||
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, minHeight: 25, minWidth: 25, flexibleWidth: 0);
|
||||
var closeBtnText = CloseButton.Component.GetComponentInChildren<Text>();
|
||||
closeBtnText.color = Color.red;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
}
|
||||
}
|
693
src/Inspectors/ReflectionInspector.cs
Normal file
693
src/Inspectors/ReflectionInspector.cs
Normal file
@ -0,0 +1,693 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
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;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ReflectionInspector : InspectorBase, ICellPoolDataSource<CacheMemberCell>, ICacheObjectController
|
||||
{
|
||||
public CacheObjectBase ParentCacheObject { get; set; }
|
||||
public Type TargetType { get; private set; }
|
||||
public bool StaticOnly { get; internal set; }
|
||||
public bool CanWrite => true;
|
||||
|
||||
private List<CacheMember> members = new List<CacheMember>();
|
||||
private readonly List<CacheMember> filteredMembers = new List<CacheMember>();
|
||||
|
||||
public bool AutoUpdateWanted => autoUpdateToggle.isOn;
|
||||
|
||||
private BindingFlags FlagsFilter;
|
||||
private string NameFilter;
|
||||
|
||||
private MemberFlags MemberFilter = MemberFlags.All;
|
||||
private enum MemberFlags
|
||||
{
|
||||
None = 0,
|
||||
Property = 1,
|
||||
Field = 2,
|
||||
Method = 4,
|
||||
All = 7
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
|
||||
|
||||
public Text NameText;
|
||||
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<BindingFlags, ButtonRef> scopeFilterButtons = new Dictionary<BindingFlags, ButtonRef>();
|
||||
private readonly List<Toggle> memberTypeToggles = new List<Toggle>();
|
||||
private InputFieldRef filterInputField;
|
||||
|
||||
// Setup / return
|
||||
|
||||
public override void OnBorrowedFromPool(object target)
|
||||
{
|
||||
base.OnBorrowedFromPool(target);
|
||||
CalculateLayouts();
|
||||
|
||||
SetTarget(target);
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(InitCoroutine());
|
||||
}
|
||||
|
||||
private IEnumerator InitCoroutine()
|
||||
{
|
||||
yield return null;
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect);
|
||||
}
|
||||
|
||||
public override void CloseInspector()
|
||||
{
|
||||
InspectorManager.ReleaseInspector(this);
|
||||
}
|
||||
|
||||
public override void OnReturnToPool()
|
||||
{
|
||||
foreach (var member in members)
|
||||
{
|
||||
member.UnlinkFromView();
|
||||
member.ReleasePooledObjects();
|
||||
}
|
||||
|
||||
members.Clear();
|
||||
filteredMembers.Clear();
|
||||
|
||||
autoUpdateToggle.isOn = false;
|
||||
|
||||
UnityObjectRef = null;
|
||||
ComponentRef = null;
|
||||
TextureRef = null;
|
||||
CleanupTextureViewer();
|
||||
|
||||
base.OnReturnToPool();
|
||||
}
|
||||
|
||||
// Setting target
|
||||
|
||||
private void SetTarget(object target)
|
||||
{
|
||||
string prefix;
|
||||
if (StaticOnly)
|
||||
{
|
||||
Target = null;
|
||||
TargetType = target as Type;
|
||||
prefix = "[S]";
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetType = target.GetActualType();
|
||||
prefix = "[R]";
|
||||
}
|
||||
|
||||
// Setup main labels and tab text
|
||||
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
|
||||
Tab.TabText.text = currentBaseTabText;
|
||||
NameText.text = SignatureHighlighter.Parse(TargetType, true);
|
||||
|
||||
string asmText;
|
||||
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
|
||||
asmText = $"{TargetType.Assembly.GetName().Name} <color=grey><i>(in memory)</i></color>";
|
||||
else
|
||||
asmText = Path.GetFileName(TargetType.Assembly.Location);
|
||||
AssemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
|
||||
|
||||
// unity helpers
|
||||
SetUnityTargets();
|
||||
|
||||
// Get cache members
|
||||
|
||||
this.members = CacheMember.GetCacheMembers(Target, TargetType, this);
|
||||
|
||||
// reset filters
|
||||
|
||||
this.filterInputField.Text = "";
|
||||
|
||||
SetFilter("", StaticOnly ? BindingFlags.Static : BindingFlags.Instance);
|
||||
scopeFilterButtons[BindingFlags.Default].Component.gameObject.SetActive(!StaticOnly);
|
||||
scopeFilterButtons[BindingFlags.Instance].Component.gameObject.SetActive(!StaticOnly);
|
||||
|
||||
foreach (var toggle in memberTypeToggles)
|
||||
toggle.isOn = true;
|
||||
|
||||
refreshWanted = true;
|
||||
}
|
||||
|
||||
// Updating
|
||||
|
||||
private bool refreshWanted;
|
||||
private string lastNameFilter;
|
||||
private BindingFlags lastFlagsFilter;
|
||||
private MemberFlags lastMemberFilter = MemberFlags.All;
|
||||
private float timeOfLastAutoUpdate;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!this.IsActive)
|
||||
return;
|
||||
|
||||
if (!StaticOnly && Target.IsNullOrDestroyed(false))
|
||||
{
|
||||
InspectorManager.ReleaseInspector(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// check filter changes or force-refresh
|
||||
if (refreshWanted || NameFilter != lastNameFilter || FlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter)
|
||||
{
|
||||
lastNameFilter = NameFilter;
|
||||
lastFlagsFilter = FlagsFilter;
|
||||
lastMemberFilter = MemberFilter;
|
||||
|
||||
FilterMembers();
|
||||
MemberScrollPool.Refresh(true, true);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateClicked()
|
||||
{
|
||||
UpdateDisplayedMembers();
|
||||
}
|
||||
|
||||
// Filtering
|
||||
|
||||
public void SetFilter(string filter) => SetFilter(filter, FlagsFilter);
|
||||
|
||||
public void SetFilter(BindingFlags flagsFilter) => SetFilter(NameFilter, flagsFilter);
|
||||
|
||||
public void SetFilter(string nameFilter, BindingFlags flagsFilter)
|
||||
{
|
||||
this.NameFilter = nameFilter;
|
||||
|
||||
if (flagsFilter != FlagsFilter)
|
||||
{
|
||||
var btn = scopeFilterButtons[FlagsFilter].Component;
|
||||
RuntimeProvider.Instance.SetColorBlock(btn, disabledButtonColor, disabledButtonColor * 1.3f);
|
||||
|
||||
this.FlagsFilter = flagsFilter;
|
||||
btn = scopeFilterButtons[FlagsFilter].Component;
|
||||
RuntimeProvider.Instance.SetColorBlock(btn, enabledButtonColor, enabledButtonColor * 1.3f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMemberTypeToggled(MemberFlags flag, bool val)
|
||||
{
|
||||
if (!val)
|
||||
MemberFilter &= ~flag;
|
||||
else
|
||||
MemberFilter |= flag;
|
||||
}
|
||||
|
||||
private void FilterMembers()
|
||||
{
|
||||
filteredMembers.Clear();
|
||||
|
||||
for (int i = 0; i < members.Count; i++)
|
||||
{
|
||||
var member = members[i];
|
||||
|
||||
if (FlagsFilter != BindingFlags.Default)
|
||||
{
|
||||
if (FlagsFilter == BindingFlags.Instance && member.IsStatic
|
||||
|| FlagsFilter == BindingFlags.Static && !member.IsStatic)
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFlags.Method))
|
||||
|| (member is CacheField && !MemberFilter.HasFlag(MemberFlags.Field))
|
||||
|| (member is CacheProperty && !MemberFilter.HasFlag(MemberFlags.Property)))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(NameFilter) && !member.NameForFiltering.ContainsIgnoreCase(NameFilter))
|
||||
continue;
|
||||
|
||||
filteredMembers.Add(member);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisplayedMembers()
|
||||
{
|
||||
bool shouldRefresh = false;
|
||||
foreach (var cell in MemberScrollPool.CellPool)
|
||||
{
|
||||
if (!cell.Enabled || cell.Occupant == null)
|
||||
continue;
|
||||
var member = cell.MemberOccupant;
|
||||
if (member.ShouldAutoEvaluate)
|
||||
{
|
||||
shouldRefresh = true;
|
||||
member.Evaluate();
|
||||
member.SetDataToCell(member.CellView);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRefresh)
|
||||
MemberScrollPool.Refresh(false);
|
||||
}
|
||||
|
||||
// Member cells
|
||||
|
||||
public int ItemCount => filteredMembers.Count;
|
||||
|
||||
public void OnCellBorrowed(CacheMemberCell cell) { } // not needed
|
||||
|
||||
public void SetCell(CacheMemberCell cell, int index)
|
||||
{
|
||||
CacheObjectControllerHelper.SetCell(cell, index, filteredMembers, SetCellLayout);
|
||||
}
|
||||
|
||||
// Cell layout (fake table alignment)
|
||||
|
||||
private static int LeftGroupWidth { get; set; }
|
||||
private static int RightGroupWidth { get; set; }
|
||||
|
||||
internal void SetLayouts()
|
||||
{
|
||||
CalculateLayouts();
|
||||
|
||||
foreach (var cell in MemberScrollPool.CellPool)
|
||||
SetCellLayout(cell);
|
||||
}
|
||||
|
||||
private void CalculateLayouts()
|
||||
{
|
||||
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);
|
||||
RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65);
|
||||
}
|
||||
|
||||
private void SetCellLayout(CacheObjectCell cell)
|
||||
{
|
||||
cell.NameLayout.minWidth = LeftGroupWidth;
|
||||
cell.RightGroupLayout.minWidth = RightGroupWidth;
|
||||
|
||||
if (cell.Occupant?.IValue != null)
|
||||
cell.Occupant.IValue.SetLayout();
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
private GameObject mainContentHolder;
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, true, true, true, 5,
|
||||
new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f));
|
||||
|
||||
// Class name, assembly
|
||||
|
||||
NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 17);
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ConstructUnityObjectRow();
|
||||
|
||||
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
|
||||
new Color(0.12f, 0.12f, 0.12f));
|
||||
UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
ConstructFirstRow(mainContentHolder);
|
||||
|
||||
ConstructSecondRow(mainContentHolder);
|
||||
|
||||
// Member scroll pool
|
||||
|
||||
var memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2, 2, 2, 2),
|
||||
bgColor: new Color(0.05f, 0.05f, 0.05f));
|
||||
UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
MemberScrollPool = UIFactory.CreateScrollPool<CacheMemberCell>(memberBorder, "MemberList", out GameObject scrollObj,
|
||||
out GameObject _, new Color(0.09f, 0.09f, 0.09f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
|
||||
MemberScrollPool.Initialize(this);
|
||||
|
||||
// For debugging scroll pool
|
||||
//InspectorPanel.Instance.UIRoot.GetComponent<Mask>().enabled = false;
|
||||
//MemberScrollPool.Viewport.GetComponent<Mask>().enabled = false;
|
||||
//MemberScrollPool.Viewport.GetComponent<Image>().color = new Color(0.12f, 0.12f, 0.12f);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
// First row
|
||||
|
||||
private void ConstructFirstRow(GameObject parent)
|
||||
{
|
||||
var rowObj = UIFactory.CreateUIObject("FirstRow", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, true, true, true, true, 5, 2, 2, 2, 2);
|
||||
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
var nameLabel = UIFactory.CreateLabel(rowObj, "NameFilterLabel", "Filter names:", TextAnchor.MiddleLeft, Color.grey);
|
||||
UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 90, flexibleWidth: 0);
|
||||
|
||||
filterInputField = UIFactory.CreateInputField(rowObj, "NameFilterInput", "...");
|
||||
UIFactory.SetLayoutElement(filterInputField.UIRoot, minHeight: 25, flexibleWidth: 300);
|
||||
filterInputField.OnValueChanged += (string val) => { SetFilter(val); };
|
||||
|
||||
var spacer = UIFactory.CreateUIObject("Spacer", rowObj);
|
||||
UIFactory.SetLayoutElement(spacer, minWidth: 25);
|
||||
|
||||
// Update button and toggle
|
||||
|
||||
var updateButton = UIFactory.CreateButton(rowObj, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f));
|
||||
UIFactory.SetLayoutElement(updateButton.Component.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0);
|
||||
updateButton.OnClick += UpdateClicked;
|
||||
|
||||
var toggleObj = UIFactory.CreateToggle(rowObj, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText);
|
||||
UIFactory.SetLayoutElement(toggleObj, minWidth: 125, minHeight: 25);
|
||||
autoUpdateToggle.isOn = false;
|
||||
toggleText.text = "Auto-update";
|
||||
}
|
||||
|
||||
// Second row
|
||||
|
||||
private void ConstructSecondRow(GameObject parent)
|
||||
{
|
||||
var rowObj = UIFactory.CreateUIObject("SecondRow", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 2, 2, 2, 2);
|
||||
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
// Scope buttons
|
||||
|
||||
var scopeLabel = UIFactory.CreateLabel(rowObj, "ScopeLabel", "Scope:", TextAnchor.MiddleLeft, Color.grey);
|
||||
UIFactory.SetLayoutElement(scopeLabel.gameObject, minHeight: 25, minWidth: 60, flexibleWidth: 0);
|
||||
AddScopeFilterButton(rowObj, BindingFlags.Default, true);
|
||||
AddScopeFilterButton(rowObj, BindingFlags.Instance);
|
||||
AddScopeFilterButton(rowObj, BindingFlags.Static);
|
||||
|
||||
var spacer = UIFactory.CreateUIObject("Spacer", rowObj);
|
||||
UIFactory.SetLayoutElement(spacer, minWidth: 15);
|
||||
|
||||
// Member type toggles
|
||||
|
||||
AddMemberTypeToggle(rowObj, MemberTypes.Property, 90);
|
||||
AddMemberTypeToggle(rowObj, MemberTypes.Field, 70);
|
||||
AddMemberTypeToggle(rowObj, MemberTypes.Method, 90);
|
||||
}
|
||||
|
||||
private void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
|
||||
{
|
||||
string lbl = flags == BindingFlags.Default ? "All" : flags.ToString();
|
||||
var color = setAsActive ? enabledButtonColor : disabledButtonColor;
|
||||
|
||||
var button = UIFactory.CreateButton(parent, "Filter_" + flags, lbl, color);
|
||||
UIFactory.SetLayoutElement(button.Component.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 70, flexibleWidth: 0);
|
||||
scopeFilterButtons.Add(flags, button);
|
||||
|
||||
button.OnClick += () => { SetFilter(flags); };
|
||||
}
|
||||
|
||||
private void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
|
||||
{
|
||||
var toggleObj = UIFactory.CreateToggle(parent, "Toggle_" + type, out Toggle toggle, out Text toggleText);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: width);
|
||||
var color = SignatureHighlighter.GetMemberInfoColor(type);
|
||||
toggleText.text = $"<color={color}>{type}</color>";
|
||||
|
||||
toggle.graphic.TryCast<Image>().color = color.ToColor() * 0.65f;
|
||||
|
||||
MemberFlags flag;
|
||||
switch (type)
|
||||
{
|
||||
case MemberTypes.Method: flag = MemberFlags.Method; break;
|
||||
case MemberTypes.Property: flag = MemberFlags.Property; break;
|
||||
case MemberTypes.Field: flag = MemberFlags.Field; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
toggle.onValueChanged.AddListener((bool val) => { OnMemberTypeToggled(flag, val); });
|
||||
|
||||
memberTypeToggles.Add(toggle);
|
||||
}
|
||||
|
||||
|
||||
// Todo should probably put this in a separate class or maybe as a widget
|
||||
|
||||
#region UNITY OBJECT SPECIFIC
|
||||
|
||||
// Unity object helpers
|
||||
|
||||
private UnityEngine.Object UnityObjectRef;
|
||||
private Component ComponentRef;
|
||||
private Texture2D TextureRef;
|
||||
private bool TextureViewerWanted;
|
||||
private GameObject unityObjectRow;
|
||||
private ButtonRef gameObjectButton;
|
||||
private InputFieldRef nameInput;
|
||||
private InputFieldRef instanceIdInput;
|
||||
private ButtonRef textureButton;
|
||||
private GameObject textureViewer;
|
||||
|
||||
private void SetUnityTargets()
|
||||
{
|
||||
if (StaticOnly || !typeof(UnityEngine.Object).IsAssignableFrom(TargetType))
|
||||
{
|
||||
unityObjectRow.SetActive(false);
|
||||
textureViewer.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
UnityObjectRef = (UnityEngine.Object)Target.TryCast(typeof(UnityEngine.Object));
|
||||
unityObjectRow.SetActive(true);
|
||||
|
||||
nameInput.Text = UnityObjectRef.name;
|
||||
instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString();
|
||||
|
||||
if (typeof(Component).IsAssignableFrom(TargetType))
|
||||
{
|
||||
ComponentRef = (Component)Target.TryCast(typeof(Component));
|
||||
gameObjectButton.Component.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
gameObjectButton.Component.gameObject.SetActive(false);
|
||||
|
||||
if (typeof(Texture2D).IsAssignableFrom(TargetType))
|
||||
{
|
||||
TextureRef = (Texture2D)Target.TryCast(typeof(Texture2D));
|
||||
textureButton.Component.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
textureButton.Component.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnGameObjectButtonClicked()
|
||||
{
|
||||
if (!ComponentRef)
|
||||
{
|
||||
ExplorerCore.LogWarning("Component reference is null or destroyed!");
|
||||
return;
|
||||
}
|
||||
|
||||
InspectorManager.Inspect(ComponentRef.gameObject);
|
||||
}
|
||||
|
||||
private void ToggleTextureViewer()
|
||||
{
|
||||
if (TextureViewerWanted)
|
||||
{
|
||||
// disable
|
||||
TextureViewerWanted = false;
|
||||
textureViewer.SetActive(false);
|
||||
mainContentHolder.SetActive(true);
|
||||
textureButton.ButtonText.text = "View Texture";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!textureImage.sprite)
|
||||
{
|
||||
// First show, need to create sprite for displaying texture
|
||||
SetTextureViewer();
|
||||
}
|
||||
|
||||
// enable
|
||||
TextureViewerWanted = true;
|
||||
textureViewer.SetActive(true);
|
||||
mainContentHolder.gameObject.SetActive(false);
|
||||
textureButton.ButtonText.text = "Hide Texture";
|
||||
}
|
||||
}
|
||||
|
||||
// UI construction
|
||||
|
||||
private void ConstructUnityObjectRow()
|
||||
{
|
||||
unityObjectRow = UIFactory.CreateUIObject("UnityObjectRow", UIRoot);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(unityObjectRow, false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(unityObjectRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
textureButton = UIFactory.CreateButton(unityObjectRow, "TextureButton", "View Texture", new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(textureButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
textureButton.OnClick += ToggleTextureViewer;
|
||||
|
||||
var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey);
|
||||
UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0);
|
||||
|
||||
nameInput = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled");
|
||||
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);
|
||||
|
||||
instanceIdInput = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR");
|
||||
UIFactory.SetLayoutElement(instanceIdInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 0);
|
||||
instanceIdInput.Component.readOnly = true;
|
||||
|
||||
unityObjectRow.SetActive(false);
|
||||
|
||||
ConstructTextureHelper();
|
||||
}
|
||||
|
||||
// Texture viewer helper
|
||||
|
||||
private InputFieldRef textureSavePathInput;
|
||||
private Image textureImage;
|
||||
private LayoutElement textureImageLayout;
|
||||
|
||||
private void CleanupTextureViewer()
|
||||
{
|
||||
if (textureImage.sprite)
|
||||
GameObject.Destroy(textureImage.sprite);
|
||||
|
||||
if (TextureViewerWanted)
|
||||
ToggleTextureViewer();
|
||||
}
|
||||
|
||||
private void ConstructTextureHelper()
|
||||
{
|
||||
textureViewer = UIFactory.CreateVerticalGroup(UIRoot, "TextureViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5),
|
||||
new Color(0.1f, 0.1f, 0.1f));
|
||||
UIFactory.SetLayoutElement(textureViewer, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
// Save helper
|
||||
|
||||
var saveRowObj = UIFactory.CreateHorizontalGroup(textureViewer, "SaveRow", false, false, true, true, 2, new Vector4(2, 2, 2, 2),
|
||||
new Color(0.1f, 0.1f, 0.1f));
|
||||
|
||||
var saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", new Color(0.2f, 0.25f, 0.2f));
|
||||
UIFactory.SetLayoutElement(saveBtn.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0);
|
||||
saveBtn.OnClick += OnSaveTextureClicked;
|
||||
|
||||
textureSavePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...");
|
||||
UIFactory.SetLayoutElement(textureSavePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999);
|
||||
|
||||
// Actual texture viewer
|
||||
|
||||
var imageViewport = UIFactory.CreateVerticalGroup(textureViewer, "Viewport", false, false, true, true);
|
||||
imageViewport.GetComponent<Image>().color = Color.white;
|
||||
imageViewport.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
var imageObj = UIFactory.CreateUIObject("Image", imageViewport);
|
||||
var fitter = imageObj.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
textureImage = imageObj.AddComponent<Image>();
|
||||
textureImageLayout = UIFactory.SetLayoutElement(imageObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
textureViewer.SetActive(false);
|
||||
}
|
||||
|
||||
private void SetTextureViewer()
|
||||
{
|
||||
if (!this.TextureRef)
|
||||
return;
|
||||
|
||||
var name = TextureRef.name;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = "untitled";
|
||||
|
||||
textureSavePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png");
|
||||
|
||||
var sprite = TextureUtilProvider.Instance.CreateSprite(TextureRef);
|
||||
textureImage.sprite = sprite;
|
||||
|
||||
textureImageLayout.preferredHeight = sprite.rect.height;
|
||||
textureImageLayout.preferredWidth = sprite.rect.width;
|
||||
}
|
||||
|
||||
private void OnSaveTextureClicked()
|
||||
{
|
||||
if (!TextureRef)
|
||||
{
|
||||
ExplorerCore.LogWarning("Ref Texture is null, maybe it was destroyed?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(textureSavePathInput.Text))
|
||||
{
|
||||
ExplorerCore.LogWarning("Save path cannot be empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
var path = textureSavePathInput.Text;
|
||||
if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ExplorerCore.LogWarning("Desired save path must end with '.png'!");
|
||||
return;
|
||||
}
|
||||
|
||||
path = IOUtility.EnsureValidDirectory(path);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
var tex = TextureRef;
|
||||
|
||||
if (!TextureUtilProvider.IsReadable(tex))
|
||||
tex = TextureUtilProvider.ForceReadTexture(tex);
|
||||
|
||||
byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex);
|
||||
|
||||
File.WriteAllBytes(path, data);
|
||||
|
||||
if (tex != TextureRef)
|
||||
{
|
||||
// cleanup temp texture if we had to force-read it.
|
||||
GameObject.Destroy(tex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user