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:
252
src/ObjectExplorer/ObjectSearch.cs
Normal file
252
src/ObjectExplorer/ObjectSearch.cs
Normal file
@ -0,0 +1,252 @@
|
||||
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;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
public class ObjectSearch : UIModel
|
||||
{
|
||||
public ObjectExplorerPanel Parent { get; }
|
||||
|
||||
public ObjectSearch(ObjectExplorerPanel parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
private SearchContext m_context = SearchContext.UnityObject;
|
||||
private SceneFilter m_sceneFilter = SceneFilter.Any;
|
||||
private ChildFilter m_childFilter = ChildFilter.Any;
|
||||
private string desiredTypeInput;
|
||||
private string lastCheckedTypeInput;
|
||||
private bool lastTypeCanHaveGO;
|
||||
|
||||
public ButtonListHandler<object, ButtonCell> dataHandler;
|
||||
|
||||
private ScrollPool<ButtonCell> resultsScrollPool;
|
||||
private List<object> currentResults = new List<object>();
|
||||
|
||||
public TypeCompleter typeAutocompleter;
|
||||
|
||||
public override GameObject UIRoot => uiRoot;
|
||||
private GameObject uiRoot;
|
||||
|
||||
private GameObject sceneFilterRow;
|
||||
private GameObject childFilterRow;
|
||||
private GameObject unityObjectClassRow;
|
||||
private InputFieldRef nameInputField;
|
||||
|
||||
private Text resultsLabel;
|
||||
|
||||
public List<object> GetEntries() => currentResults;
|
||||
|
||||
public void DoSearch()
|
||||
{
|
||||
cachedCellTexts.Clear();
|
||||
|
||||
if (m_context == SearchContext.Singleton)
|
||||
currentResults = SearchProvider.SingletonSearch(nameInputField.Text);
|
||||
else if (m_context == SearchContext.StaticClass)
|
||||
currentResults = SearchProvider.StaticClassSearch(nameInputField.Text);
|
||||
else
|
||||
{
|
||||
string compType = "";
|
||||
if (m_context == SearchContext.UnityObject)
|
||||
compType = this.desiredTypeInput;
|
||||
|
||||
currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, compType, m_context, m_childFilter, m_sceneFilter);
|
||||
}
|
||||
|
||||
dataHandler.RefreshData();
|
||||
resultsScrollPool.Refresh(true);
|
||||
|
||||
resultsLabel.text = $"{currentResults.Count} results";
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (m_context == SearchContext.UnityObject && lastCheckedTypeInput != desiredTypeInput)
|
||||
{
|
||||
lastCheckedTypeInput = desiredTypeInput;
|
||||
|
||||
//var type = ReflectionUtility.GetTypeByName(desiredTypeInput);
|
||||
if (ReflectionUtility.GetTypeByName(desiredTypeInput) is Type cachedType)
|
||||
{
|
||||
var type = cachedType;
|
||||
lastTypeCanHaveGO = typeof(Component).IsAssignableFrom(type) || type == typeof(GameObject);
|
||||
sceneFilterRow.SetActive(lastTypeCanHaveGO);
|
||||
childFilterRow.SetActive(lastTypeCanHaveGO);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneFilterRow.SetActive(false);
|
||||
childFilterRow.SetActive(false);
|
||||
lastTypeCanHaveGO = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI Callbacks
|
||||
|
||||
private void OnContextDropdownChanged(int value)
|
||||
{
|
||||
m_context = (SearchContext)value;
|
||||
|
||||
lastCheckedTypeInput = null;
|
||||
sceneFilterRow.SetActive(false);
|
||||
childFilterRow.SetActive(false);
|
||||
|
||||
unityObjectClassRow.SetActive(m_context == SearchContext.UnityObject);
|
||||
}
|
||||
|
||||
private void OnSceneFilterDropChanged(int value) => m_sceneFilter = (SceneFilter)value;
|
||||
|
||||
private void OnChildFilterDropChanged(int value) => m_childFilter = (ChildFilter)value;
|
||||
|
||||
private void OnTypeInputChanged(string val)
|
||||
{
|
||||
desiredTypeInput = val;
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
sceneFilterRow.SetActive(false);
|
||||
childFilterRow.SetActive(false);
|
||||
lastCheckedTypeInput = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the syntax-highlighted text for each search result to reduce allocs.
|
||||
private static readonly Dictionary<int, string> cachedCellTexts = new Dictionary<int, string>();
|
||||
|
||||
public void SetCell(ButtonCell cell, int index)
|
||||
{
|
||||
if (!cachedCellTexts.ContainsKey(index))
|
||||
{
|
||||
string text;
|
||||
if (m_context == SearchContext.StaticClass)
|
||||
text = SignatureHighlighter.Parse(currentResults[index] as Type, true);
|
||||
else
|
||||
text = ToStringUtility.ToStringWithType(currentResults[index], currentResults[index]?.GetActualType());
|
||||
|
||||
cachedCellTexts.Add(index, text);
|
||||
}
|
||||
|
||||
cell.Button.ButtonText.text = cachedCellTexts[index];
|
||||
}
|
||||
|
||||
private void OnCellClicked(int dataIndex)
|
||||
{
|
||||
if (m_context == SearchContext.StaticClass)
|
||||
InspectorManager.Inspect(currentResults[dataIndex] as Type);
|
||||
else
|
||||
InspectorManager.Inspect(currentResults[dataIndex]);
|
||||
}
|
||||
|
||||
private bool ShouldDisplayCell(object arg1, string arg2) => true;
|
||||
|
||||
public override void ConstructUI(GameObject parent)
|
||||
{
|
||||
uiRoot = UIFactory.CreateVerticalGroup(parent, "ObjectSearch", true, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(uiRoot, flexibleHeight: 9999);
|
||||
|
||||
// Search context row
|
||||
|
||||
var contextGroup = UIFactory.CreateHorizontalGroup(uiRoot, "SearchContextRow", false, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(contextGroup, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var contextLbl = UIFactory.CreateLabel(contextGroup, "SearchContextLabel", "Searching for:", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(contextLbl.gameObject, minWidth: 110, flexibleWidth: 0);
|
||||
|
||||
var contextDropObj = UIFactory.CreateDropdown(contextGroup, out Dropdown contextDrop, null, 14, OnContextDropdownChanged);
|
||||
foreach (var name in Enum.GetNames(typeof(SearchContext)))
|
||||
contextDrop.options.Add(new Dropdown.OptionData(name));
|
||||
UIFactory.SetLayoutElement(contextDropObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
// Unity class input
|
||||
|
||||
unityObjectClassRow = UIFactory.CreateHorizontalGroup(uiRoot, "UnityClassRow", false, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(unityObjectClassRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var unityClassLbl = UIFactory.CreateLabel(unityObjectClassRow, "UnityClassLabel", "Class filter:", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(unityClassLbl.gameObject, minWidth: 110, flexibleWidth: 0);
|
||||
|
||||
var classInputField = UIFactory.CreateInputField(unityObjectClassRow, "ClassInput", "...");
|
||||
UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField);
|
||||
classInputField.OnValueChanged += OnTypeInputChanged;
|
||||
|
||||
//unityObjectClassRow.SetActive(false);
|
||||
|
||||
// Child filter row
|
||||
|
||||
childFilterRow = UIFactory.CreateHorizontalGroup(uiRoot, "ChildFilterRow", false, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(childFilterRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var childLbl = UIFactory.CreateLabel(childFilterRow, "ChildLabel", "Child filter:", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(childLbl.gameObject, minWidth: 110, flexibleWidth: 0);
|
||||
|
||||
var childDropObj = UIFactory.CreateDropdown(childFilterRow, out Dropdown childDrop, null, 14, OnChildFilterDropChanged);
|
||||
foreach (var name in Enum.GetNames(typeof(ChildFilter)))
|
||||
childDrop.options.Add(new Dropdown.OptionData(name));
|
||||
UIFactory.SetLayoutElement(childDropObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
childFilterRow.SetActive(false);
|
||||
|
||||
// Scene filter row
|
||||
|
||||
sceneFilterRow = UIFactory.CreateHorizontalGroup(uiRoot, "SceneFilterRow", false, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(sceneFilterRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var sceneLbl = UIFactory.CreateLabel(sceneFilterRow, "SceneLabel", "Scene filter:", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(sceneLbl.gameObject, minWidth: 110, flexibleWidth: 0);
|
||||
|
||||
var sceneDropObj = UIFactory.CreateDropdown(sceneFilterRow, out Dropdown sceneDrop, null, 14, OnSceneFilterDropChanged);
|
||||
foreach (var name in Enum.GetNames(typeof(SceneFilter)))
|
||||
sceneDrop.options.Add(new Dropdown.OptionData(name));
|
||||
UIFactory.SetLayoutElement(sceneDropObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
sceneFilterRow.SetActive(false);
|
||||
|
||||
// Name filter input
|
||||
|
||||
var nameRow = UIFactory.CreateHorizontalGroup(uiRoot, "NameRow", true, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(nameRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var nameLbl = UIFactory.CreateLabel(nameRow, "NameFilterLabel", "Name contains:", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(nameLbl.gameObject, minWidth: 110, flexibleWidth: 0);
|
||||
|
||||
nameInputField = UIFactory.CreateInputField(nameRow, "NameFilterInput", "...");
|
||||
UIFactory.SetLayoutElement(nameInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
// Search button
|
||||
|
||||
var searchButton = UIFactory.CreateButton(uiRoot, "SearchButton", "Search");
|
||||
UIFactory.SetLayoutElement(searchButton.Component.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
searchButton.OnClick += DoSearch;
|
||||
|
||||
// Results count label
|
||||
|
||||
var resultsCountRow = UIFactory.CreateHorizontalGroup(uiRoot, "ResultsCountRow", true, true, true, true);
|
||||
UIFactory.SetLayoutElement(resultsCountRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
resultsLabel = UIFactory.CreateLabel(resultsCountRow, "ResultsLabel", "0 results", TextAnchor.MiddleCenter);
|
||||
|
||||
// RESULTS SCROLL POOL
|
||||
|
||||
dataHandler = new ButtonListHandler<object, ButtonCell>(resultsScrollPool, GetEntries, SetCell, ShouldDisplayCell, OnCellClicked);
|
||||
resultsScrollPool = UIFactory.CreateScrollPool<ButtonCell>(uiRoot, "ResultsList", out GameObject scrollObj,
|
||||
out GameObject scrollContent);
|
||||
|
||||
resultsScrollPool.Initialize(dataHandler);
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
|
||||
}
|
||||
}
|
||||
}
|
320
src/ObjectExplorer/SceneExplorer.cs
Normal file
320
src/ObjectExplorer/SceneExplorer.cs
Normal file
@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
public class SceneExplorer : UIModel
|
||||
{
|
||||
public ObjectExplorerPanel Parent { get; }
|
||||
|
||||
public SceneExplorer(ObjectExplorerPanel parent)
|
||||
{
|
||||
Parent = parent;
|
||||
|
||||
SceneHandler.OnInspectedSceneChanged += SceneHandler_OnInspectedSceneChanged;
|
||||
SceneHandler.OnLoadedScenesChanged += SceneHandler_OnLoadedScenesChanged;
|
||||
}
|
||||
|
||||
public override GameObject UIRoot => m_uiRoot;
|
||||
private GameObject m_uiRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to automatically update per auto-update interval or not.
|
||||
/// </summary>
|
||||
public bool AutoUpdate = false;
|
||||
|
||||
public TransformTree Tree;
|
||||
private float timeOfLastUpdate = -1f;
|
||||
|
||||
private GameObject refreshRow;
|
||||
private Dropdown sceneDropdown;
|
||||
private readonly Dictionary<Scene, Dropdown.OptionData> sceneToDropdownOption = new Dictionary<Scene, Dropdown.OptionData>();
|
||||
|
||||
private IEnumerable<GameObject> GetRootEntries() => SceneHandler.CurrentRootObjects;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if ((AutoUpdate || !SceneHandler.InspectingAssetScene) && timeOfLastUpdate.OccuredEarlierThan(1))
|
||||
{
|
||||
timeOfLastUpdate = Time.realtimeSinceStartup;
|
||||
UpdateTree();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTree()
|
||||
{
|
||||
SceneHandler.Update();
|
||||
Tree.RefreshData(true);
|
||||
}
|
||||
|
||||
public void JumpToTransform(Transform transform)
|
||||
{
|
||||
if (!transform)
|
||||
return;
|
||||
|
||||
UIManager.SetPanelActive(this.Parent, true);
|
||||
this.Parent.SetTab(0);
|
||||
|
||||
// select the transform's scene
|
||||
var go = transform.gameObject;
|
||||
if (SceneHandler.SelectedScene != go.scene)
|
||||
{
|
||||
int idx = sceneDropdown.options.IndexOf(sceneToDropdownOption[go.scene]);
|
||||
sceneDropdown.value = idx;
|
||||
}
|
||||
|
||||
// Let the TransformTree handle the rest
|
||||
Tree.JumpAndExpandToTransform(transform);
|
||||
}
|
||||
|
||||
private void OnDropdownChanged(int value)
|
||||
{
|
||||
if (value < 0 || SceneHandler.LoadedScenes.Count <= value)
|
||||
return;
|
||||
|
||||
SceneHandler.SelectedScene = SceneHandler.LoadedScenes[value];
|
||||
SceneHandler.Update();
|
||||
Tree.RefreshData(true);
|
||||
OnSelectedSceneChanged(SceneHandler.SelectedScene.Value);
|
||||
}
|
||||
|
||||
private void SceneHandler_OnInspectedSceneChanged(Scene scene)
|
||||
{
|
||||
if (!sceneToDropdownOption.ContainsKey(scene))
|
||||
PopulateSceneDropdown();
|
||||
|
||||
if (sceneToDropdownOption.ContainsKey(scene))
|
||||
{
|
||||
var opt = sceneToDropdownOption[scene];
|
||||
int idx = sceneDropdown.options.IndexOf(opt);
|
||||
if (sceneDropdown.value != idx)
|
||||
sceneDropdown.value = idx;
|
||||
else
|
||||
sceneDropdown.captionText.text = opt.text;
|
||||
}
|
||||
|
||||
OnSelectedSceneChanged(scene);
|
||||
}
|
||||
|
||||
private void OnSelectedSceneChanged(Scene scene)
|
||||
{
|
||||
if (refreshRow)
|
||||
refreshRow.SetActive(!scene.IsValid());
|
||||
}
|
||||
|
||||
private void SceneHandler_OnLoadedScenesChanged(ReadOnlyCollection<Scene> loadedScenes)
|
||||
{
|
||||
PopulateSceneDropdown();
|
||||
}
|
||||
|
||||
private void PopulateSceneDropdown()
|
||||
{
|
||||
sceneToDropdownOption.Clear();
|
||||
sceneDropdown.options.Clear();
|
||||
|
||||
foreach (var scene in SceneHandler.LoadedScenes)
|
||||
{
|
||||
string name = scene.name?.Trim();
|
||||
|
||||
if (!scene.IsValid())
|
||||
name = "HideAndDontSave";
|
||||
else if (string.IsNullOrEmpty(name))
|
||||
name = "<untitled>";
|
||||
|
||||
var option = new Dropdown.OptionData(name);
|
||||
sceneDropdown.options.Add(option);
|
||||
sceneToDropdownOption.Add(scene, option);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilterInput(string input)
|
||||
{
|
||||
if ((!string.IsNullOrEmpty(input) && !Tree.Filtering) || (string.IsNullOrEmpty(input) && Tree.Filtering))
|
||||
{
|
||||
Tree.cachedTransforms.Clear();
|
||||
}
|
||||
|
||||
Tree.CurrentFilter = input;
|
||||
Tree.RefreshData(true, true);
|
||||
}
|
||||
|
||||
private void TryLoadScene(LoadSceneMode mode, Dropdown allSceneDrop)
|
||||
{
|
||||
var text = allSceneDrop.options[allSceneDrop.value].text;
|
||||
|
||||
if (text == DEFAULT_LOAD_TEXT)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
SceneManager.LoadScene(text, mode);
|
||||
allSceneDrop.value = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Unable to load the Scene! {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ConstructUI(GameObject content)
|
||||
{
|
||||
m_uiRoot = UIFactory.CreateUIObject("SceneExplorer", content);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(m_uiRoot, true, true, true, true, 0, 2, 2, 2, 2);
|
||||
UIFactory.SetLayoutElement(m_uiRoot, flexibleHeight: 9999);
|
||||
|
||||
// Tool bar (top area)
|
||||
|
||||
var toolbar = UIFactory.CreateVerticalGroup(m_uiRoot, "Toolbar", true, true, true, true, 2, new Vector4(2, 2, 2, 2),
|
||||
new Color(0.15f, 0.15f, 0.15f));
|
||||
|
||||
// Scene selector dropdown
|
||||
|
||||
var dropRow = UIFactory.CreateHorizontalGroup(toolbar, "DropdownRow", true, true, true, true, 5, default, new Color(1, 1, 1, 0));
|
||||
UIFactory.SetLayoutElement(dropRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
var dropLabel = UIFactory.CreateLabel(dropRow, "SelectorLabel", "Scene:", TextAnchor.MiddleLeft, Color.cyan, false, 15);
|
||||
UIFactory.SetLayoutElement(dropLabel.gameObject, minHeight: 25, minWidth: 60, flexibleWidth: 0);
|
||||
|
||||
var dropdownObj = UIFactory.CreateDropdown(dropRow, out sceneDropdown, "<notset>", 13, OnDropdownChanged);
|
||||
UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
SceneHandler.Update();
|
||||
PopulateSceneDropdown();
|
||||
sceneDropdown.captionText.text = sceneToDropdownOption.First().Value.text;
|
||||
|
||||
// Filter row
|
||||
|
||||
var filterRow = UIFactory.CreateHorizontalGroup(toolbar, "FilterGroup", true, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(filterRow, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
//Filter input field
|
||||
var inputField = UIFactory.CreateInputField(filterRow, "FilterInput", "Search...");
|
||||
inputField.Component.targetGraphic.color = new Color(0.2f, 0.2f, 0.2f);
|
||||
RuntimeProvider.Instance.SetColorBlock(inputField.Component, new Color(0.4f, 0.4f, 0.4f), new Color(0.2f, 0.2f, 0.2f),
|
||||
new Color(0.08f, 0.08f, 0.08f));
|
||||
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25);
|
||||
inputField.OnValueChanged += OnFilterInput;
|
||||
|
||||
// refresh row
|
||||
|
||||
refreshRow = UIFactory.CreateHorizontalGroup(toolbar, "RefreshGroup", true, true, true, true, 2, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(refreshRow, minHeight: 30, flexibleHeight: 0);
|
||||
|
||||
var refreshButton = UIFactory.CreateButton(refreshRow, "RefreshButton", "Update");
|
||||
UIFactory.SetLayoutElement(refreshButton.Component.gameObject, minWidth: 65, flexibleWidth: 0);
|
||||
refreshButton.OnClick += UpdateTree;
|
||||
|
||||
var refreshToggle = UIFactory.CreateToggle(refreshRow, "RefreshToggle", out Toggle toggle, out Text text);
|
||||
UIFactory.SetLayoutElement(refreshToggle, flexibleWidth: 9999);
|
||||
text.text = "Auto-update (1 second)";
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
text.color = Color.white;
|
||||
text.fontSize = 12;
|
||||
toggle.isOn = false;
|
||||
toggle.onValueChanged.AddListener((bool val) => AutoUpdate = val);
|
||||
|
||||
refreshRow.SetActive(false);
|
||||
|
||||
// Transform Tree
|
||||
|
||||
var scrollPool = UIFactory.CreateScrollPool<TransformCell>(m_uiRoot, "TransformTree", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.11f, 0.11f, 0.11f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
|
||||
UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999);
|
||||
|
||||
Tree = new TransformTree(scrollPool, GetRootEntries);
|
||||
Tree.Init();
|
||||
Tree.RefreshData(true, true);
|
||||
//scrollPool.Viewport.GetComponent<Mask>().enabled = false;
|
||||
//UIRoot.GetComponent<Mask>().enabled = false;
|
||||
|
||||
// Scene Loader
|
||||
|
||||
ConstructSceneLoader();
|
||||
}
|
||||
|
||||
private const string DEFAULT_LOAD_TEXT = "[Select a scene]";
|
||||
|
||||
private void ConstructSceneLoader()
|
||||
{
|
||||
// Scene Loader
|
||||
try
|
||||
{
|
||||
if (SceneHandler.WasAbleToGetScenesInBuild)
|
||||
{
|
||||
var sceneLoaderObj = UIFactory.CreateVerticalGroup(m_uiRoot, "SceneLoader", true, true, true, true);
|
||||
UIFactory.SetLayoutElement(sceneLoaderObj, minHeight: 25);
|
||||
//sceneLoaderObj.SetActive(false);
|
||||
|
||||
var loaderTitle = UIFactory.CreateLabel(sceneLoaderObj, "SceneLoaderLabel", "Scene Loader", TextAnchor.MiddleLeft, Color.white, true, 14);
|
||||
UIFactory.SetLayoutElement(loaderTitle.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var allSceneDropObj = UIFactory.CreateDropdown(sceneLoaderObj, out Dropdown allSceneDrop, "", 14, null);
|
||||
UIFactory.SetLayoutElement(allSceneDropObj, minHeight: 25, minWidth: 150, flexibleWidth: 0, flexibleHeight: 0);
|
||||
|
||||
allSceneDrop.options.Add(new Dropdown.OptionData(DEFAULT_LOAD_TEXT));
|
||||
|
||||
foreach (var scene in SceneHandler.AllSceneNames)
|
||||
allSceneDrop.options.Add(new Dropdown.OptionData(Path.GetFileNameWithoutExtension(scene)));
|
||||
|
||||
allSceneDrop.value = 1;
|
||||
allSceneDrop.value = 0;
|
||||
|
||||
var buttonRow = UIFactory.CreateHorizontalGroup(sceneLoaderObj, "LoadButtons", true, true, true, true, 4);
|
||||
|
||||
var loadButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Single)", new Color(0.1f, 0.3f, 0.3f));
|
||||
UIFactory.SetLayoutElement(loadButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
loadButton.OnClick += () =>
|
||||
{
|
||||
TryLoadScene(LoadSceneMode.Single, allSceneDrop);
|
||||
};
|
||||
|
||||
var loadAdditiveButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Additive)", new Color(0.1f, 0.3f, 0.3f));
|
||||
UIFactory.SetLayoutElement(loadAdditiveButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
loadAdditiveButton.OnClick += () =>
|
||||
{
|
||||
TryLoadScene(LoadSceneMode.Additive, allSceneDrop);
|
||||
};
|
||||
|
||||
var disabledColor = new Color(0.24f, 0.24f, 0.24f);
|
||||
RuntimeProvider.Instance.SetColorBlock(loadButton.Component, disabled: disabledColor);
|
||||
RuntimeProvider.Instance.SetColorBlock(loadAdditiveButton.Component, disabled: disabledColor);
|
||||
|
||||
loadButton.Component.interactable = false;
|
||||
loadAdditiveButton.Component.interactable = false;
|
||||
|
||||
allSceneDrop.onValueChanged.AddListener((int val) =>
|
||||
{
|
||||
var text = allSceneDrop.options[val].text;
|
||||
if (text == DEFAULT_LOAD_TEXT)
|
||||
{
|
||||
loadButton.Component.interactable = false;
|
||||
loadAdditiveButton.Component.interactable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadButton.Component.interactable = true;
|
||||
loadAdditiveButton.Component.interactable = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not create the Scene Loader helper! {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
172
src/ObjectExplorer/SceneHandler.cs
Normal file
172
src/ObjectExplorer/SceneHandler.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
public static class SceneHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently inspected Scene.
|
||||
/// </summary>
|
||||
public static Scene? SelectedScene
|
||||
{
|
||||
get => selectedScene;
|
||||
internal set
|
||||
{
|
||||
if (selectedScene != null && selectedScene == value)
|
||||
return;
|
||||
selectedScene = value;
|
||||
OnInspectedSceneChanged?.Invoke((Scene)selectedScene);
|
||||
}
|
||||
}
|
||||
private static Scene? selectedScene;
|
||||
|
||||
/// <summary>
|
||||
/// The GameObjects in the currently inspected scene.
|
||||
/// </summary>
|
||||
public static ReadOnlyCollection<GameObject> CurrentRootObjects => new ReadOnlyCollection<GameObject>(rootObjects);
|
||||
private static GameObject[] rootObjects = new GameObject[0];
|
||||
|
||||
/// <summary>
|
||||
/// All currently loaded Scenes.
|
||||
/// </summary>
|
||||
public static ReadOnlyCollection<Scene> LoadedScenes => new ReadOnlyCollection<Scene>(allLoadedScenes);
|
||||
private static readonly List<Scene> allLoadedScenes = new List<Scene>();
|
||||
private static HashSet<Scene> previousLoadedScenes;
|
||||
|
||||
/// <summary>
|
||||
/// The names of all scenes in the build settings, if they could be retrieved.
|
||||
/// </summary>
|
||||
public static ReadOnlyCollection<string> AllSceneNames => new ReadOnlyCollection<string>(allScenesInBuild);
|
||||
private static readonly List<string> allScenesInBuild = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not we successfuly retrieved the names of the scenes in the build settings.
|
||||
/// </summary>
|
||||
public static bool WasAbleToGetScenesInBuild => gotAllScenesInBuild;
|
||||
private static bool gotAllScenesInBuild = true;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the currently inspected Scene changes. The argument is the new scene.
|
||||
/// </summary>
|
||||
public static event Action<Scene> OnInspectedSceneChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked whenever the list of currently loaded Scenes changes. The argument contains all loaded scenes after the change.
|
||||
/// </summary>
|
||||
public static event Action<ReadOnlyCollection<Scene>> OnLoadedScenesChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to <see cref="SceneManager.sceneCount"/> + 2, to include 'DontDestroyOnLoad' and the 'None' scene.
|
||||
/// </summary>
|
||||
public static int LoadedSceneCount => SceneManager.sceneCount + 2;
|
||||
|
||||
internal static Scene DontDestroyScene => DontDestroyMe.scene;
|
||||
internal static int DontDestroyHandle => DontDestroyScene.handle;
|
||||
|
||||
internal static GameObject DontDestroyMe
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!dontDestroyObject)
|
||||
{
|
||||
dontDestroyObject = new GameObject("DontDestroyMe");
|
||||
GameObject.DontDestroyOnLoad(dontDestroyObject);
|
||||
}
|
||||
return dontDestroyObject;
|
||||
}
|
||||
}
|
||||
private static GameObject dontDestroyObject;
|
||||
|
||||
public static bool InspectingAssetScene => SelectedScene.HasValue && SelectedScene.Value == default;
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
// Try to get all scenes in the build settings. This may not work.
|
||||
try
|
||||
{
|
||||
Type sceneUtil = ReflectionUtility.GetTypeByName("UnityEngine.SceneManagement.SceneUtility");
|
||||
if (sceneUtil == null)
|
||||
throw new Exception("This version of Unity does not ship with the 'SceneUtility' class, or it was not unstripped.");
|
||||
|
||||
var method = sceneUtil.GetMethod("GetScenePathByBuildIndex", ReflectionUtility.FLAGS);
|
||||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
var scenePath = (string)method.Invoke(null, new object[] { i });
|
||||
allScenesInBuild.Add(scenePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
gotAllScenesInBuild = false;
|
||||
ExplorerCore.LogWarning($"Unable to generate list of all Scenes in the build: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
// check if the loaded scenes changed. always confirm DontDestroy / HideAndDontSave
|
||||
int confirmedCount = 2;
|
||||
bool inspectedExists = SelectedScene == DontDestroyScene || (SelectedScene.HasValue && SelectedScene.Value == default);
|
||||
|
||||
allLoadedScenes.Clear();
|
||||
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
Scene scene = SceneManager.GetSceneAt(i);
|
||||
if (scene == default || !scene.isLoaded)
|
||||
continue;
|
||||
|
||||
// If no changes yet, ensure the previous list contained the scene
|
||||
if (previousLoadedScenes != null && previousLoadedScenes.Contains(scene))
|
||||
confirmedCount++;
|
||||
|
||||
// If we have not yet confirmed inspectedExists, check if this scene is our currently inspected one.
|
||||
if (!inspectedExists && scene == SelectedScene)
|
||||
inspectedExists = true;
|
||||
|
||||
allLoadedScenes.Add(scene);
|
||||
}
|
||||
|
||||
bool anyChange = confirmedCount != allLoadedScenes.Count;
|
||||
|
||||
allLoadedScenes.Add(DontDestroyScene);
|
||||
allLoadedScenes.Add(default);
|
||||
previousLoadedScenes = new HashSet<Scene>(allLoadedScenes);
|
||||
|
||||
// Default to first scene if none selected or previous selection no longer exists.
|
||||
if (!inspectedExists)
|
||||
{
|
||||
SelectedScene = allLoadedScenes.First();
|
||||
}
|
||||
|
||||
// Notify on the list changing at all
|
||||
if (anyChange)
|
||||
{
|
||||
OnLoadedScenesChanged?.Invoke(LoadedScenes);
|
||||
}
|
||||
|
||||
// Finally, update the root objects list.
|
||||
if (SelectedScene != null && ((Scene)SelectedScene).IsValid())
|
||||
rootObjects = RuntimeProvider.Instance.GetRootGameObjects((Scene)SelectedScene);
|
||||
else
|
||||
{
|
||||
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject));
|
||||
var objects = new List<GameObject>();
|
||||
foreach (var obj in allObjects)
|
||||
{
|
||||
var go = obj.TryCast<GameObject>();
|
||||
if (go.transform.parent == null && !go.scene.IsValid())
|
||||
objects.Add(go);
|
||||
}
|
||||
rootObjects = objects.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
203
src/ObjectExplorer/SearchProvider.cs
Normal file
203
src/ObjectExplorer/SearchProvider.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
public enum SearchContext
|
||||
{
|
||||
UnityObject,
|
||||
// GameObject,
|
||||
Singleton,
|
||||
StaticClass
|
||||
}
|
||||
|
||||
public enum ChildFilter
|
||||
{
|
||||
Any,
|
||||
RootObject,
|
||||
HasParent
|
||||
}
|
||||
|
||||
public enum SceneFilter
|
||||
{
|
||||
Any,
|
||||
ActivelyLoaded,
|
||||
DontDestroyOnLoad,
|
||||
HideAndDontSave,
|
||||
}
|
||||
|
||||
public static class SearchProvider
|
||||
{
|
||||
|
||||
private static bool Filter(Scene scene, SceneFilter filter)
|
||||
{
|
||||
switch (filter)
|
||||
{
|
||||
case SceneFilter.Any:
|
||||
return true;
|
||||
case SceneFilter.DontDestroyOnLoad:
|
||||
return scene == SceneHandler.DontDestroyScene;
|
||||
case SceneFilter.HideAndDontSave:
|
||||
return scene == default;
|
||||
case SceneFilter.ActivelyLoaded:
|
||||
return scene != SceneHandler.DontDestroyScene && scene != default;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<object> UnityObjectSearch(string input, string customTypeInput, SearchContext context,
|
||||
ChildFilter childFilter, SceneFilter sceneFilter)
|
||||
{
|
||||
var results = new List<object>();
|
||||
|
||||
Type searchType = null;
|
||||
if (!string.IsNullOrEmpty(customTypeInput))
|
||||
{
|
||||
if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType)
|
||||
{
|
||||
if (typeof(UnityEngine.Object).IsAssignableFrom(customType))
|
||||
searchType = customType;
|
||||
else
|
||||
ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!");
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning($"Could not find any type by name '{customTypeInput}'!");
|
||||
}
|
||||
|
||||
if (searchType == null)
|
||||
searchType = typeof(UnityEngine.Object);
|
||||
|
||||
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType);
|
||||
|
||||
// perform filter comparers
|
||||
|
||||
string nameFilter = null;
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
nameFilter = input;
|
||||
|
||||
bool shouldFilterGOs = searchType == typeof(GameObject) || typeof(Component).IsAssignableFrom(searchType);
|
||||
|
||||
foreach (var obj in allObjects)
|
||||
{
|
||||
// name check
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter))
|
||||
continue;
|
||||
|
||||
GameObject go = null;
|
||||
var type = obj.GetActualType();
|
||||
|
||||
if (type == typeof(GameObject))
|
||||
go = obj.TryCast<GameObject>();
|
||||
else if (typeof(Component).IsAssignableFrom(type))
|
||||
go = obj.TryCast<Component>()?.gameObject;
|
||||
|
||||
if (go)
|
||||
{
|
||||
// hide unityexplorer objects
|
||||
if (go.transform.root.name == "ExplorerCanvas")
|
||||
continue;
|
||||
|
||||
if (shouldFilterGOs)
|
||||
{
|
||||
// scene check
|
||||
if (sceneFilter != SceneFilter.Any)
|
||||
{
|
||||
if (!Filter(go.scene, sceneFilter))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (childFilter != ChildFilter.Any)
|
||||
{
|
||||
if (!go)
|
||||
continue;
|
||||
|
||||
// root object check (no parent)
|
||||
if (childFilter == ChildFilter.HasParent && !go.transform.parent)
|
||||
continue;
|
||||
else if (childFilter == ChildFilter.RootObject && go.transform.parent)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(obj);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
internal static List<object> StaticClassSearch(string input)
|
||||
{
|
||||
var list = new List<object>();
|
||||
|
||||
var nameFilter = "";
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
nameFilter = input;
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes().Where(it => it.IsSealed && it.IsAbstract))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
|
||||
continue;
|
||||
|
||||
list.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
internal static string[] instanceNames = new string[]
|
||||
{
|
||||
"m_instance",
|
||||
"m_Instance",
|
||||
"s_instance",
|
||||
"s_Instance",
|
||||
"_instance",
|
||||
"_Instance",
|
||||
"instance",
|
||||
"Instance",
|
||||
"<Instance>k__BackingField",
|
||||
"<instance>k__BackingField",
|
||||
};
|
||||
|
||||
internal static List<object> SingletonSearch(string input)
|
||||
{
|
||||
var instances = new List<object>();
|
||||
|
||||
var nameFilter = "";
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
nameFilter = input;
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
// Search all non-static, non-enum classes.
|
||||
foreach (var type in asm.TryGetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
|
||||
continue;
|
||||
|
||||
ReflectionUtility.FindSingleton(instanceNames, type, flags, instances);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user