diff --git a/src/Inspectors/InspectUnderMouse.cs b/src/Inspectors/InspectUnderMouse.cs index 6ee8d3a..921d3a8 100644 --- a/src/Inspectors/InspectUnderMouse.cs +++ b/src/Inspectors/InspectUnderMouse.cs @@ -8,6 +8,7 @@ using UnityEngine.UI; using UnityExplorer.Core; using UnityExplorer.Core.Input; using UnityExplorer.Core.Runtime; +using UnityExplorer.Inspectors.MouseInspectors; using UnityExplorer.UI; using UnityExplorer.UI.Panels; @@ -23,17 +24,27 @@ namespace UnityExplorer.Inspectors { public static InspectUnderMouse Instance { get; private set; } - public InspectUnderMouse() { Instance = this; } + private readonly WorldInspector worldInspector; + private readonly UiInspector uiInspector; - public static void OnDropdownSelect(int index) + public static bool Inspecting { get; set; } + public static MouseInspectMode Mode { get; set; } + + private static Vector3 lastMousePos; + + public MouseInspectorBase CurrentInspector { - switch (index) + get { - case 0: return; - case 1: Instance.StartInspect(MouseInspectMode.World); break; - case 2: Instance.StartInspect(MouseInspectMode.UI); break; + switch (Mode) + { + case MouseInspectMode.UI: + return uiInspector; + case MouseInspectMode.World: + return worldInspector; + } + return null; } - UIManager.MouseInspectDropdown.value = 0; } // UIPanel @@ -46,56 +57,54 @@ namespace UnityExplorer.Inspectors public override bool ShouldSaveActiveState => false; public override bool ShowByDefault => false; - private static Text objNameLabel; - private static Text objPathLabel; - private static Text mousePosLabel; + internal Text objNameLabel; + internal Text objPathLabel; + internal Text mousePosLabel; - // Mouse Inspector - public static bool Inspecting { get; set; } - public static MouseInspectMode Mode { get; set; } + public InspectUnderMouse() + { + Instance = this; + worldInspector = new WorldInspector(); + uiInspector = new UiInspector(); + } - private static Camera MainCamera; - private static GraphicRaycaster[] graphicRaycasters; - - private static GameObject lastHitObject; - private static Vector3 lastMousePos; - - private static readonly List wasDisabledGraphics = new List(); - private static readonly List wasDisabledCanvasGroups = new List(); - private static readonly List objectsAddedCastersTo = new List(); + 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; + } 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; + + CurrentInspector.OnBeginMouseInspect(); + + PanelDragger.ForceEnd(); UIManager.NavBarRect.gameObject.SetActive(false); UIManager.PanelHolder.SetActive(false); UIRoot.SetActive(true); - - if (mode == MouseInspectMode.UI) - SetupUIRaycast(); } internal void ClearHitData() { - lastHitObject = null; + CurrentInspector.ClearHitData(); + objNameLabel.text = "No hits..."; objPathLabel.text = ""; } public void StopInspect() { + CurrentInspector.OnEndInspect(); + ClearHitData(); Inspecting = false; UIManager.NavBarRect.gameObject.SetActive(true); @@ -106,11 +115,6 @@ namespace UnityExplorer.Inspectors drop.DestroyDropdownList(list.gameObject); UIRoot.SetActive(false); - - if (Mode == MouseInspectMode.UI) - StopUIInspect(); - - ClearHitData(); } private static float timeOfLastRaycast; @@ -123,33 +127,22 @@ namespace UnityExplorer.Inspectors return; } - if (lastHitObject && InputManager.GetMouseButtonDown(0)) + if (InputManager.GetMouseButtonDown(0)) { - var target = lastHitObject; + CurrentInspector.OnSelectMouseInspect(); 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; - } + CurrentInspector.UpdateMouseInspect(mousePos); } internal void UpdatePosition(Vector2 mousePos) @@ -171,181 +164,9 @@ namespace UnityExplorer.Inspectors // 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 = $"Click to Inspect: {obj.name}"; - 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(); - if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy) - continue; - if (!canvas.GetComponent()) - { - canvas.gameObject.AddComponent(); - //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(); - } - - // enable raycastTarget on Graphics - foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic))) - { - var graphic = obj.TryCast(); - 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(); - 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(); - 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() is CanvasGroup group && group.alpha == 0) - continue; - - if (hit.gameObject.GetComponent() 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() 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() @@ -367,7 +188,10 @@ namespace UnityExplorer.Inspectors // Title text - var title = UIFactory.CreateLabel(inspectContent, "InspectLabel", "Mouse Inspector (press ESC to cancel)", TextAnchor.MiddleCenter); + var title = UIFactory.CreateLabel(inspectContent, + "InspectLabel", + "Mouse Inspector (press ESC to cancel)", + TextAnchor.MiddleCenter); UIFactory.SetLayoutElement(title.gameObject, flexibleWidth: 9999); mousePosLabel = UIFactory.CreateLabel(inspectContent, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter); diff --git a/src/Inspectors/MouseInspectors/MouseInspectorBase.cs b/src/Inspectors/MouseInspectors/MouseInspectorBase.cs new file mode 100644 index 0000000..0314d79 --- /dev/null +++ b/src/Inspectors/MouseInspectors/MouseInspectorBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace UnityExplorer.Inspectors.MouseInspectors +{ + public abstract class MouseInspectorBase + { + public abstract void OnBeginMouseInspect(); + + public abstract void UpdateMouseInspect(Vector2 mousePos); + + public abstract void OnSelectMouseInspect(); + + public abstract void ClearHitData(); + + public abstract void OnEndInspect(); + } +} diff --git a/src/Inspectors/MouseInspectors/UiInspector.cs b/src/Inspectors/MouseInspectors/UiInspector.cs new file mode 100644 index 0000000..7b865f8 --- /dev/null +++ b/src/Inspectors/MouseInspectors/UiInspector.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.UI.Panels; + +namespace UnityExplorer.Inspectors.MouseInspectors +{ + public class UiInspector : MouseInspectorBase + { + public static readonly List LastHitObjects = new List(); + + private static GraphicRaycaster[] graphicRaycasters; + + private static readonly List currentHitObjects = new List(); + + private static readonly List wasDisabledGraphics = new List(); + private static readonly List wasDisabledCanvasGroups = new List(); + private static readonly List objectsAddedCastersTo = new List(); + + public override void OnBeginMouseInspect() + { + SetupUIRaycast(); + InspectUnderMouse.Instance.objPathLabel.text = ""; + } + + public override void ClearHitData() + { + currentHitObjects.Clear(); + } + + public override void OnSelectMouseInspect() + { + LastHitObjects.Clear(); + LastHitObjects.AddRange(currentHitObjects); + var panel = UIManager.GetPanel(UIManager.Panels.UIInspectorResults); + panel.SetActive(true); + panel.ShowResults(); + } + + public override void UpdateMouseInspect(Vector2 mousePos) + { + currentHitObjects.Clear(); + + var ped = new PointerEventData(null) + { + position = mousePos + }; + + foreach (var gr in graphicRaycasters) + { + if (!gr || !gr.canvas) + continue; + + var list = new List(); + RuntimeProvider.Instance.GraphicRaycast(gr, ped, list); + if (list.Count > 0) + { + foreach (var hit in list) + { + if (hit.gameObject) + currentHitObjects.Add(hit.gameObject); + } + } + } + + if (currentHitObjects.Any()) + InspectUnderMouse.Instance.objNameLabel.text = $"Click to view UI Objects under mouse: {currentHitObjects.Count}"; + else + InspectUnderMouse.Instance.objNameLabel.text = $"No UI objects under mouse."; + } + + private static void SetupUIRaycast() + { + foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas))) + { + var canvas = obj.TryCast(); + if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy) + continue; + if (!canvas.GetComponent()) + { + canvas.gameObject.AddComponent(); + //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(); + } + + // enable raycastTarget on Graphics + foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic))) + { + var graphic = obj.TryCast(); + 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(); + if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts) + continue; + canvas.blocksRaycasts = true; + //ExplorerCore.Log("Enabled raycasts on " + canvas.name); + wasDisabledCanvasGroups.Add(canvas); + } + } + + public override void OnEndInspect() + { + foreach (var obj in objectsAddedCastersTo) + { + if (obj.GetComponent() 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(); + } + } +} diff --git a/src/Inspectors/MouseInspectors/WorldInspector.cs b/src/Inspectors/MouseInspectors/WorldInspector.cs new file mode 100644 index 0000000..ad41684 --- /dev/null +++ b/src/Inspectors/MouseInspectors/WorldInspector.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace UnityExplorer.Inspectors.MouseInspectors +{ + public class WorldInspector : MouseInspectorBase + { + private static Camera MainCamera; + private static GameObject lastHitObject; + + public override void OnBeginMouseInspect() + { + MainCamera = Camera.main; + + if (!MainCamera) + { + ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!"); + return; + } + } + + public override void ClearHitData() + { + lastHitObject = null; + } + + public override void OnSelectMouseInspect() + { + InspectorManager.Inspect(lastHitObject); + } + + public override void UpdateMouseInspect(Vector2 mousePos) + { + var ray = MainCamera.ScreenPointToRay(mousePos); + Physics.Raycast(ray, out RaycastHit hit, 1000f); + + if (hit.transform) + OnHitGameObject(hit.transform.gameObject); + else if (lastHitObject) + InspectUnderMouse.Instance.ClearHitData(); + } + + internal void OnHitGameObject(GameObject obj) + { + if (obj != lastHitObject) + { + lastHitObject = obj; + InspectUnderMouse.Instance.objNameLabel.text = $"Click to Inspect: {obj.name}"; + InspectUnderMouse.Instance.objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}"; + } + } + + public override void OnEndInspect() + { + // not needed + } + } +} diff --git a/src/UI/Panels/UiInspectorResultsPanel.cs b/src/UI/Panels/UiInspectorResultsPanel.cs new file mode 100644 index 0000000..b4c38cd --- /dev/null +++ b/src/UI/Panels/UiInspectorResultsPanel.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityExplorer.Inspectors.MouseInspectors; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Panels +{ + public class UiInspectorResultsPanel : UIPanel + { + public override UIManager.Panels PanelType => UIManager.Panels.UIInspectorResults; + + public override string Name => "UI Inspector Results"; + + public override int MinWidth => 500; + public override int MinHeight => 500; + public override bool CanDragAndResize => true; + public override bool NavButtonWanted => false; + public override bool ShouldSaveActiveState => false; + public override bool ShowByDefault => false; + + private ButtonListHandler dataHandler; + private ScrollPool buttonScrollPool; + + public override void ConstructPanelContent() + { + dataHandler = new ButtonListHandler(buttonScrollPool, GetEntries, SetCell, ShouldDisplayCell, OnCellClicked); + + buttonScrollPool = UIFactory.CreateScrollPool(this.content, "ResultsList", out GameObject scrollObj, + out GameObject scrollContent); + + buttonScrollPool.Initialize(dataHandler); + UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); + } + + public void ShowResults() + { + dataHandler.RefreshData(); + buttonScrollPool.Refresh(true, true); + } + + private List GetEntries() => UiInspector.LastHitObjects; + + private bool ShouldDisplayCell(object cell, string filter) => true; + + private void OnCellClicked(int index) + { + if (index >= UiInspector.LastHitObjects.Count) + return; + + InspectorManager.Inspect(UiInspector.LastHitObjects[index]); + } + + private void SetCell(ButtonCell cell, int index) + { + if (index >= UiInspector.LastHitObjects.Count) + return; + + var obj = UiInspector.LastHitObjects[index]; + cell.Button.ButtonText.text = $"{obj.name} ({obj.transform.GetTransformPath(true)})"; + } + + protected internal override void DoSetDefaultPosAndAnchors() + { + this.Rect.anchorMin = new Vector2(0.5f, 0.5f); + this.Rect.anchorMax = new Vector2(0.5f, 0.5f); + this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 500f); + this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 500f); + } + + public override void DoSaveToConfigElement() { } + public override string GetSaveDataFromConfigManager() => null; + } +} diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index 16fcc9e..6cd9a86 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -27,7 +27,8 @@ namespace UnityExplorer.UI Options, ConsoleLog, AutoCompleter, - MouseInspector + MouseInspector, + UIInspectorResults, } public enum VerticalAnchor @@ -108,6 +109,7 @@ namespace UnityExplorer.UI UIPanels.Add(Panels.CSConsole, new CSConsolePanel()); UIPanels.Add(Panels.ConsoleLog, new LogPanel()); UIPanels.Add(Panels.Options, new OptionsPanel()); + UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel()); UIPanels.Add(Panels.MouseInspector, new InspectUnderMouse()); foreach (var panel in UIPanels.Values) diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index be675ce..3731919 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -263,9 +263,13 @@ + + + +