diff --git a/src/Core/ReflectionUtility.cs b/src/Core/ReflectionUtility.cs index 70266cc..fe1bf43 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/ReflectionUtility.cs @@ -20,9 +20,9 @@ namespace UnityExplorer public static bool ReferenceEqual(this object objA, object objB) { - if (objA.TryCast() is UnityEngine.Object unityA) + if (objA is UnityEngine.Object unityA) { - var unityB = objB.TryCast(); + var unityB = objB as UnityEngine.Object; if (unityB && unityA.m_CachedPtr == unityB.m_CachedPtr) return true; } diff --git a/src/UI/InspectorManager.cs b/src/UI/InspectorManager.cs deleted file mode 100644 index 9672332..0000000 --- a/src/UI/InspectorManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace UnityExplorer.UI -{ - public static class InspectorManager - { - // internal static readonly List InspectedObjects; - - public static void Inspect(this object obj) - => throw new NotImplementedException("TODO"); - - - } -} diff --git a/src/UI/Inspectors/GameObjects/ChildList.cs b/src/UI/Inspectors/GameObjects/ChildList.cs new file mode 100644 index 0000000..606ea67 --- /dev/null +++ b/src/UI/Inspectors/GameObjects/ChildList.cs @@ -0,0 +1,193 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.Core.Runtime; +//using UnityExplorer.UI.Utility; + +//namespace UnityExplorer.UI.Inspectors.GameObjects +//{ +// public class ChildList +// { +// internal static ChildList Instance; + +// public ChildList() +// { +// Instance = this; +// } + +// public static PageHandler s_childListPageHandler; +// private static GameObject s_childListContent; + +// private static GameObject[] s_allChildren = new GameObject[0]; +// private static readonly List s_childrenShortlist = new List(); +// private static int s_lastChildCount; + +// private static readonly List s_childListTexts = new List(); +// private static readonly List s_childListToggles = new List(); + +// internal void RefreshChildObjectList() +// { +// var go = GameObjectInspector.ActiveInstance.TargetGO; + +// s_allChildren = new GameObject[go.transform.childCount]; +// for (int i = 0; i < go.transform.childCount; i++) +// { +// var child = go.transform.GetChild(i); +// s_allChildren[i] = child.gameObject; +// } + +// var objects = s_allChildren; +// s_childListPageHandler.ListCount = objects.Length; + +// int newCount = 0; + +// foreach (var itemIndex in s_childListPageHandler) +// { +// newCount++; + +// // normalized index starting from 0 +// var i = itemIndex - s_childListPageHandler.StartIndex; + +// if (itemIndex >= objects.Length) +// { +// if (i > s_lastChildCount || i >= s_childListTexts.Count) +// break; + +// GameObject label = s_childListTexts[i].transform.parent.parent.gameObject; +// if (label.activeSelf) +// label.SetActive(false); +// } +// else +// { +// GameObject obj = objects[itemIndex]; + +// if (!obj) +// continue; + +// if (i >= s_childrenShortlist.Count) +// { +// s_childrenShortlist.Add(obj); +// AddChildListButton(); +// } +// else +// { +// s_childrenShortlist[i] = obj; +// } + +// var text = s_childListTexts[i]; + +// var name = obj.name; + +// if (obj.transform.childCount > 0) +// name = $"[{obj.transform.childCount}] {name}"; + +// text.text = name; +// text.color = obj.activeSelf ? Color.green : Color.red; + +// var tog = s_childListToggles[i]; +// tog.isOn = obj.activeSelf; + +// var label = text.transform.parent.parent.gameObject; +// if (!label.activeSelf) +// { +// label.SetActive(true); +// } +// } +// } + +// s_lastChildCount = newCount; +// } + +// internal static void OnChildListObjectClicked(int index) +// { +// if (GameObjectInspector.ActiveInstance == null) +// return; + +// if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index]) +// return; + +// GameObjectInspector.ActiveInstance.ChangeInspectorTarget(s_childrenShortlist[index]); +// GameObjectInspector.ActiveInstance.Update(); +// } + +// internal static void OnChildListPageTurn() +// { +// if (Instance == null) +// return; + +// Instance.RefreshChildObjectList(); +// } + +// internal static void OnToggleClicked(int index, bool newVal) +// { +// if (GameObjectInspector.ActiveInstance == null) +// return; + +// if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index]) +// return; + +// var obj = s_childrenShortlist[index]; +// obj.SetActive(newVal); +// } + +// #region UI CONSTRUCTION + +// internal void ConstructChildList(GameObject parent) +// { +// var vertGroupObj = UIFactory.CreateVerticalGroup(parent, "ChildListGroup", false, true, true, true, 5, default, new Color(1, 1, 1, 0)); +// UIFactory.SetLayoutElement(vertGroupObj, minWidth: 120, flexibleWidth: 25000, minHeight: 200, flexibleHeight: 5000); + +// var childTitle = UIFactory.CreateLabel(vertGroupObj, "ChildListTitle", "Children:", TextAnchor.MiddleLeft, Color.grey, true, 14); +// UIFactory.SetLayoutElement(childTitle.gameObject, minHeight: 30); + +// var childrenScrollObj = UIFactory.CreateScrollView(vertGroupObj, "ChildListScrollView", out s_childListContent, +// out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f)); +// UIFactory.SetLayoutElement(childrenScrollObj, minHeight: 50); + +// s_childListPageHandler = new PageHandler(scroller); +// s_childListPageHandler.ConstructUI(vertGroupObj); +// s_childListPageHandler.OnPageChanged += OnChildListPageTurn; +// } + +// internal void AddChildListButton() +// { +// int thisIndex = s_childListTexts.Count; + +// var btnGroupObj = UIFactory.CreateHorizontalGroup(s_childListContent, "ChildButtonGroup", true, false, true, true, +// 0, default, new Color(0.07f, 0.07f, 0.07f)); +// UIFactory.SetLayoutElement(btnGroupObj, flexibleWidth: 320, minHeight: 25, flexibleHeight: 0); +// btnGroupObj.AddComponent(); + +// var toggleObj = UIFactory.CreateToggle(btnGroupObj, "Toggle", out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f)); +// UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: 25); +// toggleText.text = ""; +// toggle.isOn = false; +// s_childListToggles.Add(toggle); +// toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); }); + +// var mainBtn = UIFactory.CreateButton(btnGroupObj, +// "MainButton", +// "", +// () => { OnChildListObjectClicked(thisIndex); }); + +// RuntimeProvider.Instance.SetColorBlock(mainBtn, new Color(0.07f, 0.07f, 0.07f), +// new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.05f, 0.05f, 0.05f)); + +// UIFactory.SetLayoutElement(mainBtn.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 9999); + +// Text mainText = mainBtn.GetComponentInChildren(); +// mainText.alignment = TextAnchor.MiddleLeft; +// mainText.horizontalOverflow = HorizontalWrapMode.Overflow; +// mainText.resizeTextForBestFit = true; +// mainText.resizeTextMaxSize = 14; +// mainText.resizeTextMinSize = 10; + +// s_childListTexts.Add(mainText); +// } + +// #endregion +// } +//} diff --git a/src/UI/Inspectors/GameObjects/ComponentList.cs b/src/UI/Inspectors/GameObjects/ComponentList.cs new file mode 100644 index 0000000..d7bb9ad --- /dev/null +++ b/src/UI/Inspectors/GameObjects/ComponentList.cs @@ -0,0 +1,192 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.Core; +//using UnityExplorer.Core.Runtime; +//using UnityExplorer.UI.Utility; + +//namespace UnityExplorer.UI.Inspectors.GameObjects +//{ +// public class ComponentList +// { +// internal static ComponentList Instance; + +// public ComponentList() +// { +// Instance = this; +// } + +// public static PageHandler s_compListPageHandler; +// private static Component[] s_allComps = new Component[0]; +// private static readonly List s_compShortlist = new List(); +// private static GameObject s_compListContent; +// private static readonly List s_compListTexts = new List(); +// private static int s_lastCompCount; +// public static readonly List s_compToggles = new List(); + +// internal void RefreshComponentList() +// { +// var go = GameObjectInspector.ActiveInstance.TargetGO; + +// s_allComps = go.GetComponents().ToArray(); + +// var components = s_allComps; +// s_compListPageHandler.ListCount = components.Length; + +// //int startIndex = m_sceneListPageHandler.StartIndex; + +// int newCount = 0; + +// foreach (var itemIndex in s_compListPageHandler) +// { +// newCount++; + +// // normalized index starting from 0 +// var i = itemIndex - s_compListPageHandler.StartIndex; + +// if (itemIndex >= components.Length) +// { +// if (i > s_lastCompCount || i >= s_compListTexts.Count) +// break; + +// GameObject label = s_compListTexts[i].transform.parent.parent.gameObject; +// if (label.activeSelf) +// label.SetActive(false); +// } +// else +// { +// Component comp = components[itemIndex]; + +// if (!comp) +// continue; + +// if (i >= s_compShortlist.Count) +// { +// s_compShortlist.Add(comp); +// AddCompListButton(); +// } +// else +// { +// s_compShortlist[i] = comp; +// } + +// var text = s_compListTexts[i]; + +// text.text = SignatureHighlighter.ParseFullSyntax(ReflectionUtility.GetActualType(comp), true); + +// var toggle = s_compToggles[i]; +// if (comp.TryCast() is Behaviour behaviour) +// { +// if (!toggle.gameObject.activeSelf) +// toggle.gameObject.SetActive(true); + +// toggle.isOn = behaviour.enabled; +// } +// else +// { +// if (toggle.gameObject.activeSelf) +// toggle.gameObject.SetActive(false); +// } + +// var label = text.transform.parent.parent.gameObject; +// if (!label.activeSelf) +// { +// label.SetActive(true); +// } +// } +// } + +// s_lastCompCount = newCount; +// } + +// internal static void OnCompToggleClicked(int index, bool value) +// { +// var comp = s_compShortlist[index]; +// comp.TryCast().enabled = value; +// } + +// internal static void OnCompListObjectClicked(int index) +// { +// if (index >= s_compShortlist.Count || !s_compShortlist[index]) +// return; + +// InspectorManager.Instance.Inspect(s_compShortlist[index]); +// } + +// internal static void OnCompListPageTurn() +// { +// if (Instance == null) +// return; + +// Instance.RefreshComponentList(); +// } + + +// #region UI CONSTRUCTION + +// internal void ConstructCompList(GameObject parent) +// { +// var vertGroupObj = UIFactory.CreateVerticalGroup(parent, "ComponentList", false, true, true, true, 5, default, new Color(1, 1, 1, 0)); +// UIFactory.SetLayoutElement(vertGroupObj, minWidth: 120, flexibleWidth: 25000, minHeight: 200, flexibleHeight: 5000); + +// var compTitle = UIFactory.CreateLabel(vertGroupObj, "ComponentsTitle", "Components:", TextAnchor.MiddleLeft, Color.grey); +// UIFactory.SetLayoutElement(compTitle.gameObject, minHeight: 30); + +// var compScrollObj = UIFactory.CreateScrollView(vertGroupObj, "ComponentListScrollView", out s_compListContent, +// out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f)); + +// UIFactory.SetLayoutElement(compScrollObj, minHeight: 50, flexibleHeight: 5000); + +// s_compListPageHandler = new PageHandler(scroller); +// s_compListPageHandler.ConstructUI(vertGroupObj); +// s_compListPageHandler.OnPageChanged += OnCompListPageTurn; +// } + +// internal void AddCompListButton() +// { +// int thisIndex = s_compListTexts.Count; + +// GameObject groupObj = UIFactory.CreateHorizontalGroup(s_compListContent, "CompListButton", true, false, true, true, 0, default, +// new Color(0.07f, 0.07f, 0.07f), TextAnchor.MiddleLeft); +// UIFactory.SetLayoutElement(groupObj, minWidth: 25, flexibleWidth: 999, minHeight: 25, flexibleHeight: 0); +// groupObj.AddComponent(); + +// // Behaviour enabled toggle + +// var toggleObj = UIFactory.CreateToggle(groupObj, "EnabledToggle", out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f)); +// UIFactory.SetLayoutElement(toggleObj, minWidth: 25, minHeight: 25); +// toggleText.text = ""; +// toggle.isOn = true; +// s_compToggles.Add(toggle); +// toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); }); + +// // Main component button + +// var mainBtn = UIFactory.CreateButton(groupObj, +// "MainButton", +// "", +// () => { OnCompListObjectClicked(thisIndex); }); + +// RuntimeProvider.Instance.SetColorBlock(mainBtn, new Color(0.07f, 0.07f, 0.07f), +// new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.05f, 0.05f, 0.05f)); + +// UIFactory.SetLayoutElement(mainBtn.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 999); + +// // Component button text + +// Text mainText = mainBtn.GetComponentInChildren(); +// mainText.alignment = TextAnchor.MiddleLeft; +// mainText.horizontalOverflow = HorizontalWrapMode.Overflow; +// mainText.resizeTextForBestFit = true; +// mainText.resizeTextMaxSize = 14; +// mainText.resizeTextMinSize = 8; + +// s_compListTexts.Add(mainText); +// } + +// #endregion +// } +//} diff --git a/src/UI/Inspectors/GameObjects/GameObjectControls.cs b/src/UI/Inspectors/GameObjects/GameObjectControls.cs new file mode 100644 index 0000000..b8d1342 --- /dev/null +++ b/src/UI/Inspectors/GameObjects/GameObjectControls.cs @@ -0,0 +1,467 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.Core.Input; +//using UnityExplorer.Core.Runtime; + +//namespace UnityExplorer.UI.Inspectors.GameObjects +//{ +// public class GameObjectControls +// { +// internal static GameObjectControls Instance; + +// public GameObjectControls() +// { +// Instance = this; +// } + +// internal static bool Showing; + +// internal static void ToggleVisibility() => SetVisibility(!Showing); + +// internal static void SetVisibility(bool show) +// { +// if (show == Showing) +// return; + +// Showing = show; + +// m_hideShowLabel.text = show ? "Hide" : "Show"; +// m_contentObj.SetActive(show); +// } + +// internal static GameObject m_contentObj; +// internal static Text m_hideShowLabel; + +// private static InputField s_setParentInput; + +// private static ControlEditor s_positionControl; +// private static ControlEditor s_localPosControl; +// private static ControlEditor s_rotationControl; +// private static ControlEditor s_scaleControl; + +// // Transform Vector editors + +// internal struct ControlEditor +// { +// public InputField fullValue; +// public Slider[] sliders; +// public InputField[] inputs; +// public Text[] values; +// } + +// internal static bool s_sliderChangedWanted; +// private static Slider s_currentSlider; +// private static ControlType s_currentSliderType; +// private static VectorValue s_currentSliderValueType; +// private static float s_currentSliderValue; + +// internal enum ControlType +// { +// position, +// localPosition, +// eulerAngles, +// localScale +// } + +// internal enum VectorValue +// { +// x, y, z +// }; + +// internal void RefreshControls() +// { +// var go = GameObjectInspector.ActiveInstance.TargetGO; + +// s_positionControl.fullValue.text = go.transform.position.ToStringPretty(); +// s_positionControl.values[0].text = go.transform.position.x.ToString("F3"); +// s_positionControl.values[1].text = go.transform.position.y.ToString("F3"); +// s_positionControl.values[2].text = go.transform.position.z.ToString("F3"); + +// s_localPosControl.fullValue.text = go.transform.localPosition.ToStringPretty(); +// s_localPosControl.values[0].text = go.transform.localPosition.x.ToString("F3"); +// s_localPosControl.values[1].text = go.transform.localPosition.y.ToString("F3"); +// s_localPosControl.values[2].text = go.transform.localPosition.z.ToString("F3"); + +// s_rotationControl.fullValue.text = go.transform.eulerAngles.ToStringPretty(); +// s_rotationControl.values[0].text = go.transform.eulerAngles.x.ToString("F3"); +// s_rotationControl.values[1].text = go.transform.eulerAngles.y.ToString("F3"); +// s_rotationControl.values[2].text = go.transform.eulerAngles.z.ToString("F3"); + +// s_scaleControl.fullValue.text = go.transform.localScale.ToStringPretty(); +// s_scaleControl.values[0].text = go.transform.localScale.x.ToString("F3"); +// s_scaleControl.values[1].text = go.transform.localScale.y.ToString("F3"); +// s_scaleControl.values[2].text = go.transform.localScale.z.ToString("F3"); + +// } + +// internal static void OnSetParentClicked() +// { +// var go = GameObjectInspector.ActiveInstance.TargetGO; + +// if (!go) +// return; + +// var input = s_setParentInput.text; + +// if (string.IsNullOrEmpty(input)) +// { +// go.transform.parent = null; +// } +// else +// { +// if (GameObject.Find(input) is GameObject newParent) +// { +// go.transform.parent = newParent.transform; +// } +// else +// { +// ExplorerCore.Log($"Could not find any GameObject from name or path '{input}'! Note: The target must be enabled."); +// } +// } +// } + +// internal static void OnSliderControlChanged(float value, Slider slider, ControlType controlType, VectorValue vectorValue) +// { +// if (value == 0) +// s_sliderChangedWanted = false; +// else +// { +// if (!s_sliderChangedWanted) +// { +// s_sliderChangedWanted = true; +// s_currentSlider = slider; +// s_currentSliderType = controlType; +// s_currentSliderValueType = vectorValue; +// } + +// s_currentSliderValue = value; +// } +// } + +// internal static void UpdateSliderControl() +// { +// if (!InputManager.GetMouseButton(0)) +// { +// s_sliderChangedWanted = false; +// s_currentSlider.value = 0; + +// return; +// } + +// if (GameObjectInspector.ActiveInstance == null) return; + +// var transform = GameObjectInspector.ActiveInstance.TargetGO.transform; + +// // get the current vector for the control type +// Vector3 vector = Vector2.zero; +// switch (s_currentSliderType) +// { +// case ControlType.position: +// vector = transform.position; break; +// case ControlType.localPosition: +// vector = transform.localPosition; break; +// case ControlType.eulerAngles: +// vector = transform.eulerAngles; break; +// case ControlType.localScale: +// vector = transform.localScale; break; +// } + +// // apply vector value change +// switch (s_currentSliderValueType) +// { +// case VectorValue.x: +// vector.x += s_currentSliderValue; break; +// case VectorValue.y: +// vector.y += s_currentSliderValue; break; +// case VectorValue.z: +// vector.z += s_currentSliderValue; break; +// } + +// // set vector to transform member +// switch (s_currentSliderType) +// { +// case ControlType.position: +// transform.position = vector; break; +// case ControlType.localPosition: +// transform.localPosition = vector; break; +// case ControlType.eulerAngles: +// transform.eulerAngles = vector; break; +// case ControlType.localScale: +// transform.localScale = vector; break; +// } +// } + +// internal static void OnVectorControlInputApplied(ControlType controlType, VectorValue vectorValue) +// { +// if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return; + +// // get relevant input for controltype + value + +// InputField[] inputs = null; +// switch (controlType) +// { +// case ControlType.position: +// inputs = s_positionControl.inputs; break; +// case ControlType.localPosition: +// inputs = s_localPosControl.inputs; break; +// case ControlType.eulerAngles: +// inputs = s_rotationControl.inputs; break; +// case ControlType.localScale: +// inputs = s_scaleControl.inputs; break; +// } +// InputField input = inputs[(int)vectorValue]; + +// float val = float.Parse(input.text); + +// // apply transform value + +// Vector3 vector = Vector3.zero; +// var transform = instance.TargetGO.transform; +// switch (controlType) +// { +// case ControlType.position: +// vector = transform.position; break; +// case ControlType.localPosition: +// vector = transform.localPosition; break; +// case ControlType.eulerAngles: +// vector = transform.eulerAngles; break; +// case ControlType.localScale: +// vector = transform.localScale; break; +// } + +// switch (vectorValue) +// { +// case VectorValue.x: +// vector.x = val; break; +// case VectorValue.y: +// vector.y = val; break; +// case VectorValue.z: +// vector.z = val; break; +// } + +// // set back to transform +// switch (controlType) +// { +// case ControlType.position: +// transform.position = vector; break; +// case ControlType.localPosition: +// transform.localPosition = vector; break; +// case ControlType.eulerAngles: +// transform.eulerAngles = vector; break; +// case ControlType.localScale: +// transform.localScale = vector; break; +// } +// } + +// //#region UI CONSTRUCTION + +// //internal void ConstructControls(GameObject parent) +// //{ +// // var mainGroup = UIFactory.CreateVerticalGroup(parent, "ControlsGroup", false, false, true, true, 5, new Vector4(4,4,4,4), +// // new Color(0.07f, 0.07f, 0.07f)); + +// // // ~~~~~~ Top row ~~~~~~ + +// // var topRow = UIFactory.CreateHorizontalGroup(mainGroup, "TopRow", false, false, true, true, 5, default, new Color(1, 1, 1, 0)); + +// // var hideButton = UIFactory.CreateButton(topRow, "ToggleShowButton", "Show", ToggleVisibility, new Color(0.16f, 0.16f, 0.16f)); +// // UIFactory.SetLayoutElement(hideButton.gameObject, minWidth: 40, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); +// // m_hideShowLabel = hideButton.GetComponentInChildren(); + +// // var topTitle = UIFactory.CreateLabel(topRow, "ControlsLabel", "Controls", TextAnchor.MiddleLeft); +// // UIFactory.SetLayoutElement(topTitle.gameObject, minWidth: 100, flexibleWidth: 9500, minHeight: 25); + +// // //// ~~~~~~~~ Content ~~~~~~~~ // + +// // m_contentObj = UIFactory.CreateVerticalGroup(mainGroup, "ContentGroup", true, false, true, true, 5, default, new Color(1, 1, 1, 0)); + +// // // transform controls +// // ConstructVector3Editor(m_contentObj, "Position", ControlType.position, out s_positionControl); +// // ConstructVector3Editor(m_contentObj, "Local Position", ControlType.localPosition, out s_localPosControl); +// // ConstructVector3Editor(m_contentObj, "Rotation", ControlType.eulerAngles, out s_rotationControl); +// // ConstructVector3Editor(m_contentObj, "Scale", ControlType.localScale, out s_scaleControl); + +// // // set parent +// // ConstructSetParent(m_contentObj); + +// // // bottom row buttons +// // ConstructBottomButtons(m_contentObj); + +// // // set controls content inactive now that content is made (otherwise TMP font size goes way too big?) +// // m_contentObj.SetActive(false); +// //} + +// //internal void ConstructSetParent(GameObject contentObj) +// //{ +// // var setParentGroupObj = UIFactory.CreateHorizontalGroup(contentObj, "SetParentRow", false, false, true, true, 5, default, +// // new Color(1, 1, 1, 0)); +// // UIFactory.SetLayoutElement(setParentGroupObj, minHeight: 25, flexibleHeight: 0); + +// // var title = UIFactory.CreateLabel(setParentGroupObj, "SetParentLabel", "Set Parent:", TextAnchor.MiddleLeft, Color.grey); +// // UIFactory.SetLayoutElement(title.gameObject, minWidth: 110, minHeight: 25, flexibleHeight: 0); + +// // var inputFieldObj = UIFactory.CreateInputField(setParentGroupObj, "SetParentInputField", "Enter a GameObject name or path..."); +// // s_setParentInput = inputFieldObj.GetComponent(); +// // UIFactory.SetLayoutElement(inputFieldObj, minHeight: 25, preferredWidth: 400, flexibleWidth: 9999); + +// // var applyButton = UIFactory.CreateButton(setParentGroupObj, "SetParentButton", "Apply", OnSetParentClicked); +// // UIFactory.SetLayoutElement(applyButton.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); +// //} + +// //internal void ConstructVector3Editor(GameObject parent, string titleText, ControlType type, out ControlEditor editor) +// //{ +// // editor = new ControlEditor(); + +// // var topBarObj = UIFactory.CreateHorizontalGroup(parent, "Vector3Editor", false, false, true, true, 5, default, new Color(1, 1, 1, 0)); +// // UIFactory.SetLayoutElement(topBarObj, minHeight: 25, flexibleHeight: 0); + +// // var title = UIFactory.CreateLabel(topBarObj, "Title", titleText, TextAnchor.MiddleLeft, Color.grey); +// // UIFactory.SetLayoutElement(title.gameObject, minWidth: 110, flexibleWidth: 0, minHeight: 25); + +// // // expand button +// // var expandButton = UIFactory.CreateButton(topBarObj, "ExpandArrow", "▼"); +// // var expandText = expandButton.GetComponentInChildren(); +// // expandText.fontSize = 12; +// // UIFactory.SetLayoutElement(expandButton.gameObject, minWidth: 35, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + +// // // readonly value input + +// // var valueInputObj = UIFactory.CreateInputField(topBarObj, "ValueInput", "..."); +// // var valueInput = valueInputObj.GetComponent(); +// // valueInput.readOnly = true; +// // UIFactory.SetLayoutElement(valueInputObj, minHeight: 25, flexibleHeight: 0, preferredWidth: 400, flexibleWidth: 9999); + +// // editor.fullValue = valueInput; + +// // editor.sliders = new Slider[3]; +// // editor.inputs = new InputField[3]; +// // editor.values = new Text[3]; + +// // var xRow = ConstructEditorRow(parent, editor, type, VectorValue.x); +// // xRow.SetActive(false); +// // var yRow = ConstructEditorRow(parent, editor, type, VectorValue.y); +// // yRow.SetActive(false); +// // var zRow = ConstructEditorRow(parent, editor, type, VectorValue.z); +// // zRow.SetActive(false); + +// // // add expand callback now that we have group reference +// // expandButton.onClick.AddListener(ToggleExpand); +// // void ToggleExpand() +// // { +// // if (xRow.activeSelf) +// // { +// // xRow.SetActive(false); +// // yRow.SetActive(false); +// // zRow.SetActive(false); +// // expandText.text = "▼"; +// // } +// // else +// // { +// // xRow.SetActive(true); +// // yRow.SetActive(true); +// // zRow.SetActive(true); +// // expandText.text = "▲"; +// // } +// // } +// //} + +// //internal GameObject ConstructEditorRow(GameObject parent, ControlEditor editor, ControlType type, VectorValue vectorValue) +// //{ +// // var rowObject = UIFactory.CreateHorizontalGroup(parent, "EditorRow", false, false, true, true, 5, default, new Color(1, 1, 1, 0)); +// // UIFactory.SetLayoutElement(rowObject, minHeight: 25, flexibleHeight: 0, minWidth: 100); + +// // // Value labels + +// // var valueTitle = UIFactory.CreateLabel(rowObject, "ValueTitle", $"{vectorValue.ToString().ToUpper()}:", TextAnchor.MiddleLeft, Color.cyan); +// // UIFactory.SetLayoutElement(valueTitle.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 25, flexibleWidth: 0); + +// // // actual value label +// // var valueLabel = UIFactory.CreateLabel(rowObject, "ValueLabel", "", TextAnchor.MiddleLeft); +// // editor.values[(int)vectorValue] = valueLabel; +// // UIFactory.SetLayoutElement(valueLabel.gameObject, minWidth: 85, flexibleWidth: 0, minHeight: 25); + +// // // input field + +// // var inputHolder = UIFactory.CreateVerticalGroup(rowObject, "InputFieldGroup", false, false, true, true, 0, default, new Color(1, 1, 1, 0)); + +// // var inputObj = UIFactory.CreateInputField(inputHolder, "InputField", "..."); +// // var input = inputObj.GetComponent(); +// // //input.characterValidation = InputField.CharacterValidation.Decimal; + +// // UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 0, minWidth: 90, flexibleWidth: 50); + +// // editor.inputs[(int)vectorValue] = input; + +// // // apply button + +// // var applyBtn = UIFactory.CreateButton(rowObject, "ApplyButton", "Apply", () => { OnVectorControlInputApplied(type, vectorValue); }); +// // UIFactory.SetLayoutElement(applyBtn.gameObject, minWidth: 60, minHeight: 25); + + +// // // Slider + +// // var sliderObj = UIFactory.CreateSlider(rowObject, "VectorSlider", out Slider slider); +// // UIFactory.SetLayoutElement(sliderObj, minHeight: 20, flexibleHeight: 0, minWidth: 200, flexibleWidth: 9000); +// // sliderObj.transform.Find("Fill Area").gameObject.SetActive(false); +// // RuntimeProvider.Instance.SetColorBlock(slider, new Color(0.65f, 0.65f, 0.65f)); +// // slider.minValue = -2; +// // slider.maxValue = 2; +// // slider.value = 0; +// // slider.onValueChanged.AddListener((float val) => { OnSliderControlChanged(val, slider, type, vectorValue); }); +// // editor.sliders[(int)vectorValue] = slider; + +// // return rowObject; +// //} + +// //internal void ConstructBottomButtons(GameObject contentObj) +// //{ +// // var bottomRow = UIFactory.CreateHorizontalGroup(contentObj, "BottomButtons", true, true, false, false, 4, default, new Color(1, 1, 1, 0)); + +// // var instantiateBtn = UIFactory.CreateButton(bottomRow, "InstantiateBtn", "Instantiate", InstantiateBtn, new Color(0.2f, 0.2f, 0.2f)); +// // UIFactory.SetLayoutElement(instantiateBtn.gameObject, minWidth: 150); + +// // void InstantiateBtn() +// // { +// // var go = GameObjectInspector.ActiveInstance.TargetGO; +// // if (!go) +// // return; + +// // var clone = GameObject.Instantiate(go); +// // InspectorManager.Instance.Inspect(clone); +// // } + +// // var dontDestroyBtn = UIFactory.CreateButton(bottomRow, "DontDestroyButton", "Set DontDestroyOnLoad", DontDestroyOnLoadBtn, +// // new Color(0.2f, 0.2f, 0.2f)); +// // UIFactory.SetLayoutElement(dontDestroyBtn.gameObject, flexibleWidth: 5000); + +// // void DontDestroyOnLoadBtn() +// // { +// // var go = GameObjectInspector.ActiveInstance.TargetGO; +// // if (!go) +// // return; + +// // GameObject.DontDestroyOnLoad(go); +// // } + +// // var destroyBtn = UIFactory.CreateButton(bottomRow, "DestroyButton", "Destroy", DestroyBtn, new Color(0.2f, 0.2f, 0.2f)); +// // UIFactory.SetLayoutElement(destroyBtn.gameObject, minWidth: 150); +// // var destroyText = destroyBtn.GetComponentInChildren(); +// // destroyText.color = Color.red; + +// // void DestroyBtn() +// // { +// // var go = GameObjectInspector.ActiveInstance.TargetGO; +// // if (!go) +// // return; + +// // GameObject.Destroy(go); +// // } +// //} + +// //#endregion +// } +//} diff --git a/src/UI/Inspectors/GameObjects/GameObjectInspector.cs b/src/UI/Inspectors/GameObjects/GameObjectInspector.cs new file mode 100644 index 0000000..95c0cd0 --- /dev/null +++ b/src/UI/Inspectors/GameObjects/GameObjectInspector.cs @@ -0,0 +1,349 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.Core.Runtime; + +//namespace UnityExplorer.UI.Inspectors.GameObjects +//{ +// public class GameObjectInspector : InspectorBase +// { +// public override string TabLabel => $" [G] {TargetGO?.name}"; + +// public static GameObjectInspector ActiveInstance { get; private set; } + +// public GameObject TargetGO; + +// //// sub modules +// //internal static ChildList s_childList; +// //internal static ComponentList s_compList; +// //internal static GameObjectControls s_controls; + +// internal static bool m_UIConstructed; + +// public GameObjectInspector(GameObject target) : base(target) +// { +// ActiveInstance = this; + +// TargetGO = target; + +// if (!TargetGO) +// { +// ExplorerCore.LogWarning("Target GameObject is null!"); +// return; +// } + +// // one UI is used for all gameobject inspectors. no point recreating it. +// if (!m_UIConstructed) +// { +// m_UIConstructed = true; + +// //s_childList = new ChildList(); +// //s_compList = new ComponentList(); +// //s_controls = new GameObjectControls(); + +// //ConstructUI(); +// } +// } + +// public override void SetActive() +// { +// base.SetActive(); +// ActiveInstance = this; +// } + +// public override void SetInactive() +// { +// base.SetInactive(); +// ActiveInstance = null; +// } + +// internal void ChangeInspectorTarget(GameObject newTarget) +// { +// if (!newTarget) +// return; + +// this.Target = this.TargetGO = newTarget; +// } + +// // Update + +// //public override void Update() +// //{ +// // base.Update(); + +// // if (m_pendingDestroy || !this.IsActive) +// // return; + +// // RefreshTopInfo(); + +// // s_childList.RefreshChildObjectList(); + +// // s_compList.RefreshComponentList(); + +// // s_controls.RefreshControls(); + +// // if (GameObjectControls.s_sliderChangedWanted) +// // GameObjectControls.UpdateSliderControl(); +// //} + +// private static GameObject s_content; +// public override GameObject Content +// { +// get => s_content; +// set => s_content = value; +// } + +// private static string m_lastName; +// public static InputField m_nameInput; + +// private static string m_lastPath; +// public static InputField m_pathInput; +// private static RectTransform m_pathInputRect; +// private static GameObject m_pathGroupObj; +// private static Text m_hiddenPathText; +// private static RectTransform m_hiddenPathRect; + +// private static Toggle m_enabledToggle; +// private static Text m_enabledText; +// private static bool? m_lastEnabledState; + +// private static Dropdown m_layerDropdown; +// private static int m_lastLayer = -1; + +// private static Text m_sceneText; +// private static string m_lastScene; + +// internal void RefreshTopInfo() +// { +// var target = TargetGO; +// string name = target.name; + +// if (m_lastName != name) +// { +// m_lastName = name; +// m_nameInput.text = m_lastName; +// } + +// if (target.transform.parent) +// { +// if (!m_pathGroupObj.activeSelf) +// m_pathGroupObj.SetActive(true); + +// var path = target.transform.GetTransformPath(true); +// if (m_lastPath != path) +// { +// m_lastPath = path; + +// m_pathInput.text = path; +// m_hiddenPathText.text = path; + +// LayoutRebuilder.ForceRebuildLayoutImmediate(m_pathInputRect); +// LayoutRebuilder.ForceRebuildLayoutImmediate(m_hiddenPathRect); +// } +// } +// else if (m_pathGroupObj.activeSelf) +// m_pathGroupObj.SetActive(false); + +// if (m_lastEnabledState != target.activeSelf) +// { +// m_lastEnabledState = target.activeSelf; + +// m_enabledToggle.isOn = target.activeSelf; +// m_enabledText.text = target.activeSelf ? "Enabled" : "Disabled"; +// m_enabledText.color = target.activeSelf ? Color.green : Color.red; +// } + +// if (m_lastLayer != target.layer) +// { +// m_lastLayer = target.layer; +// m_layerDropdown.value = target.layer; +// } + +// if (string.IsNullOrEmpty(m_lastScene) || m_lastScene != target.scene.name) +// { +// m_lastScene = target.scene.name; + +// if (!string.IsNullOrEmpty(target.scene.name)) +// m_sceneText.text = m_lastScene; +// else +// m_sceneText.text = "None (Asset/Resource)"; +// } +// } + +// // UI Callbacks + +// private static void OnApplyNameClicked() +// { +// if (ActiveInstance == null) +// return; + +// ActiveInstance.TargetGO.name = m_nameInput.text; +// } + +// private static void OnEnableToggled(bool enabled) +// { +// if (ActiveInstance == null) +// return; + +// ActiveInstance.TargetGO.SetActive(enabled); +// } + +// private static void OnLayerSelected(int layer) +// { +// if (ActiveInstance == null) +// return; + +// ActiveInstance.TargetGO.layer = layer; +// } + +// internal static void OnBackButtonClicked() +// { +// if (ActiveInstance == null) +// return; + +// ActiveInstance.ChangeInspectorTarget(ActiveInstance.TargetGO.transform.parent.gameObject); +// } + +// //#region UI CONSTRUCTION + +// //internal void ConstructUI() +// //{ +// // var parent = InspectorManager.m_inspectorContent; + +// // s_content = UIFactory.CreateScrollView(parent, "GameObjectInspector_Content", out GameObject scrollContent, out _, +// // new Color(0.1f, 0.1f, 0.1f)); + +// // UIFactory.SetLayoutGroup(scrollContent.transform.parent.gameObject, true, true, true, true); + +// // UIFactory.SetLayoutGroup(scrollContent, true, true, true, true, 5); +// // var contentFitter = scrollContent.GetComponent(); +// // contentFitter.verticalFit = ContentSizeFitter.FitMode.Unconstrained; + +// // ConstructTopArea(scrollContent); + +// // s_controls.ConstructControls(scrollContent); + +// // var midGroupObj = ConstructMidGroup(scrollContent); + +// // s_childList.ConstructChildList(midGroupObj); +// // s_compList.ConstructCompList(midGroupObj); + +// // LayoutRebuilder.ForceRebuildLayoutImmediate(s_content.GetComponent()); +// // Canvas.ForceUpdateCanvases(); +// //} + +// //private void ConstructTopArea(GameObject scrollContent) +// //{ +// // // path row + +// // m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, "TopArea", false, false, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f)); + +// // var pathRect = m_pathGroupObj.GetComponent(); +// // pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20); +// // UIFactory.SetLayoutElement(m_pathGroupObj, minHeight: 20, flexibleHeight: 75); + +// // // Back button + +// // var backButton = UIFactory.CreateButton(m_pathGroupObj, "BackButton", "◄", OnBackButtonClicked, new Color(0.15f, 0.15f, 0.15f)); +// // UIFactory.SetLayoutElement(backButton.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + +// // m_hiddenPathText = UIFactory.CreateLabel(m_pathGroupObj, "HiddenPathText", "", TextAnchor.MiddleLeft); +// // m_hiddenPathText.color = Color.clear; +// // m_hiddenPathText.fontSize = 14; +// // m_hiddenPathText.raycastTarget = false; + +// // var hiddenFitter = m_hiddenPathText.gameObject.AddComponent(); +// // hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + +// // UIFactory.SetLayoutElement(m_hiddenPathText.gameObject, minHeight: 25, flexibleHeight: 125, minWidth: 250, flexibleWidth: 9000); +// // UIFactory.SetLayoutGroup(m_hiddenPathText.gameObject, true, true, true, true); + +// // // Path input + +// // var pathInputObj = UIFactory.CreateInputField(m_hiddenPathText.gameObject, "PathInputField", "..."); +// // UIFactory.SetLayoutElement(pathInputObj, minHeight: 25, flexibleHeight: 75, preferredWidth: 400, flexibleWidth: 9999); +// // var pathInputRect = pathInputObj.GetComponent(); +// // pathInputRect.sizeDelta = new Vector2(pathInputRect.sizeDelta.x, 25); + +// // m_pathInput = pathInputObj.GetComponent(); +// // m_pathInput.text = ActiveInstance.TargetGO.transform.GetTransformPath(); +// // m_pathInput.readOnly = true; +// // m_pathInput.lineType = InputField.LineType.MultiLineNewline; +// // m_pathInput.textComponent.color = new Color(0.75f, 0.75f, 0.75f); + +// // var textRect = m_pathInput.textComponent.GetComponent(); +// // textRect.offsetMin = new Vector2(3, 3); +// // textRect.offsetMax = new Vector2(3, 3); + +// // m_pathInputRect = m_pathInput.GetComponent(); +// // m_hiddenPathRect = m_hiddenPathText.GetComponent(); + +// // // name and enabled row + +// // var nameRowObj = UIFactory.CreateHorizontalGroup(scrollContent, "NameGroup", false, false, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f)); +// // UIFactory.SetLayoutElement(nameRowObj, minHeight: 25, flexibleHeight: 0); +// // var nameRect = nameRowObj.GetComponent(); +// // nameRect.sizeDelta = new Vector2(nameRect.sizeDelta.x, 25); + +// // var nameLabel = UIFactory.CreateLabel(nameRowObj, "NameLabel", "Name:", TextAnchor.MiddleCenter, Color.grey, true, 14); +// // UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 55, flexibleWidth: 0); + +// // var nameInputObj = UIFactory.CreateInputField(nameRowObj, "NameInput", "..."); +// // var nameInputRect = nameInputObj.GetComponent(); +// // nameInputRect.sizeDelta = new Vector2(nameInputRect.sizeDelta.x, 25); +// // m_nameInput = nameInputObj.GetComponent(); +// // m_nameInput.text = ActiveInstance.TargetGO.name; + +// // var applyNameBtn = UIFactory.CreateButton(nameRowObj, "ApplyNameButton", "Apply", OnApplyNameClicked); +// // UIFactory.SetLayoutElement(applyNameBtn.gameObject, minWidth: 65, minHeight: 25, flexibleHeight: 0); +// // var applyNameRect = applyNameBtn.GetComponent(); +// // applyNameRect.sizeDelta = new Vector2(applyNameRect.sizeDelta.x, 25); + +// // var activeLabel = UIFactory.CreateLabel(nameRowObj, "ActiveLabel", "Active:", TextAnchor.MiddleCenter, Color.grey, true, 14); +// // UIFactory.SetLayoutElement(activeLabel.gameObject, minWidth: 55, minHeight: 25); + +// // var enabledToggleObj = UIFactory.CreateToggle(nameRowObj, "EnabledToggle", out m_enabledToggle, out m_enabledText); +// // UIFactory.SetLayoutElement(enabledToggleObj, minHeight: 25, minWidth: 100, flexibleWidth: 0); +// // m_enabledText.text = "Enabled"; +// // m_enabledText.color = Color.green; +// // m_enabledToggle.onValueChanged.AddListener(OnEnableToggled); + +// // // layer and scene row + +// // var sceneLayerRow = UIFactory.CreateHorizontalGroup(scrollContent, "SceneLayerRow", false, true, true, true, 5, default, new Color(0.1f, 0.1f, 0.1f)); + +// // // layer + +// // var layerLabel = UIFactory.CreateLabel(sceneLayerRow, "LayerLabel", "Layer:", TextAnchor.MiddleCenter, Color.grey, true, 14); +// // UIFactory.SetLayoutElement(layerLabel.gameObject, minWidth: 55, flexibleWidth: 0); + +// // var layerDropdownObj = UIFactory.CreateDropdown(sceneLayerRow, out m_layerDropdown, "", 14, OnLayerSelected); +// // m_layerDropdown.options.Clear(); +// // for (int i = 0; i < 32; i++) +// // { +// // var layer = RuntimeProvider.Instance.LayerToName(i); +// // m_layerDropdown.options.Add(new Dropdown.OptionData { text = $"{i}: {layer}" }); +// // } +// // UIFactory.SetLayoutElement(layerDropdownObj, minWidth: 120, flexibleWidth: 2000, minHeight: 25); + +// // // scene + +// // var sceneLabel = UIFactory.CreateLabel(sceneLayerRow, "SceneLabel", "Scene:", TextAnchor.MiddleCenter, Color.grey, true, 14); +// // UIFactory.SetLayoutElement(sceneLabel.gameObject, minWidth: 55, flexibleWidth: 0); + +// // m_sceneText = UIFactory.CreateLabel(sceneLayerRow, "SceneText", "", TextAnchor.MiddleLeft); +// // UIFactory.SetLayoutElement(m_sceneText.gameObject, minWidth: 120, flexibleWidth: 2000); +// //} + +// //private GameObject ConstructMidGroup(GameObject parent) +// //{ +// // var midGroupObj = UIFactory.CreateHorizontalGroup(parent, "MidGroup", true, true, true, true, 5, default, new Color(1, 1, 1, 0)); +// // UIFactory.SetLayoutElement(midGroupObj, minHeight: 300, flexibleHeight: 3000); +// // return midGroupObj; +// //} +// //#endregion +// } +//} diff --git a/src/UI/Inspectors/InspectUnderMouse.cs b/src/UI/Inspectors/InspectUnderMouse.cs new file mode 100644 index 0000000..fb62bb1 --- /dev/null +++ b/src/UI/Inspectors/InspectUnderMouse.cs @@ -0,0 +1,327 @@ +//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.Main; +//using UnityExplorer.UI.Inspectors; + +//namespace UnityExplorer.UI.Main.Home +//{ +// public class InspectUnderMouse +// { +// public enum MouseInspectMode +// { +// World, +// UI +// } + +// public static bool Inspecting { get; set; } + +// public static MouseInspectMode Mode { get; set; } + +// private static GameObject s_lastHit; +// private static Vector3 s_lastMousePos; + +// private static readonly List _wasDisabledGraphics = new List(); +// private static readonly List _wasDisabledCanvasGroups = new List(); +// private static readonly List _objectsAddedCastersTo = new List(); + +// internal static Camera MainCamera; +// internal static GraphicRaycaster[] graphicRaycasters; + +// public static void Init() +// { +// ConstructUI(); +// } + +// public static void StartInspect(MouseInspectMode mode) +// { +// MainCamera = Camera.main; +// if (!MainCamera) +// return; + +// Mode = mode; +// Inspecting = true; +// MainMenu.Instance.MainPanel.SetActive(false); + +// s_UIContent.SetActive(true); + +// if (mode == MouseInspectMode.UI) +// SetupUIRaycast(); +// } + +// internal static void ClearHitData() +// { +// s_lastHit = null; +// s_objNameLabel.text = "No hits..."; +// s_objPathLabel.text = ""; +// } + +// public static void StopInspect() +// { +// Inspecting = false; +// MainMenu.Instance.MainPanel.SetActive(true); +// s_UIContent.SetActive(false); + +// if (Mode == MouseInspectMode.UI) +// StopUIInspect(); + +// ClearHitData(); +// } + +// public static void UpdateInspect() +// { +// if (InputManager.GetKeyDown(KeyCode.Escape)) +// { +// StopInspect(); +// return; +// } + +// var mousePos = InputManager.MousePosition; + +// if (mousePos != s_lastMousePos) +// UpdatePosition(mousePos); + +// // actual inspect raycast + +// switch (Mode) +// { +// case MouseInspectMode.UI: +// RaycastUI(mousePos); break; +// case MouseInspectMode.World: +// RaycastWorld(mousePos); break; +// } +// } + +// internal static void UpdatePosition(Vector2 mousePos) +// { +// s_lastMousePos = mousePos; + +// var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos); + +// s_mousePosLabel.text = $"Mouse Position: {mousePos.ToString()}"; + +// float yFix = mousePos.y < 120 ? 80 : -80; +// s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0); +// } + +// internal static void OnHitGameObject(GameObject obj) +// { +// if (obj != s_lastHit) +// { +// s_lastHit = obj; +// s_objNameLabel.text = $"Click to Inspect: {obj.name}"; +// s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}"; +// } + +// if (InputManager.GetMouseButtonDown(0)) +// { +// StopInspect(); +// InspectorManager.Instance.Inspect(obj); +// } +// } + +// // Collider raycasting + +// internal static 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 (s_lastHit) +// ClearHitData(); +// } +// } + +// // UI Graphic raycasting + +// private static void SetupUIRaycast() +// { +// foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas))) +// { +// var canvas = obj.Cast(typeof(Canvas)) as Canvas; +// 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].Cast(typeof(GraphicRaycaster)) as GraphicRaycaster; +// } + +// // enable raycastTarget on Graphics +// foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic))) +// { +// var graphic = obj.Cast(typeof(Graphic)) as 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.Cast(typeof(CanvasGroup)) as CanvasGroup; +// if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts) +// continue; +// canvas.blocksRaycasts = true; +// //ExplorerCore.Log("Enabled raycasts on " + canvas.name); +// _wasDisabledCanvasGroups.Add(canvas); +// } +// } + +// internal static 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) +// { +// var list = new List(); +// RuntimeProvider.Instance.GraphicRaycast(gr, ped, list); + +// //gr.Raycast(ped, list); + +// if (list.Count > 0) +// { +// foreach (var hit in list) +// { +// // Manual trying to determine which object is "on top". +// // Not perfect, but not terrible. + +// 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 (s_lastHit) +// 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(); +// } + +// internal static Text s_objNameLabel; +// internal static Text s_objPathLabel; +// internal static Text s_mousePosLabel; +// internal static GameObject s_UIContent; + +// internal static void ConstructUI() +// { +// s_UIContent = UIFactory.CreatePanel("InspectUnderMouse_UI", out GameObject content); + +// var baseRect = s_UIContent.GetComponent(); +// var half = new Vector2(0.5f, 0.5f); +// baseRect.anchorMin = half; +// baseRect.anchorMax = half; +// baseRect.pivot = half; +// baseRect.sizeDelta = new Vector2(700, 150); + +// var group = content.GetComponent(); +// group.childForceExpandHeight = true; + +// // Title text + +// UIFactory.CreateLabel(content, "InspectLabel", "Mouse Inspector (press ESC to cancel)", TextAnchor.MiddleCenter); + +// s_mousePosLabel = UIFactory.CreateLabel(content, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter); + +// s_objNameLabel = UIFactory.CreateLabel(content, "HitLabelObj", "No hits...", TextAnchor.MiddleLeft); +// s_objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow; + +// s_objPathLabel = UIFactory.CreateLabel(content, "PathLabel", "", TextAnchor.MiddleLeft); +// s_objPathLabel.fontStyle = FontStyle.Italic; +// s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + +// UIFactory.SetLayoutElement(s_objPathLabel.gameObject, minHeight: 75); + +// s_UIContent.SetActive(false); +// } +// } +//} diff --git a/src/UI/Inspectors/InspectorBase.cs b/src/UI/Inspectors/InspectorBase.cs new file mode 100644 index 0000000..04a03bc --- /dev/null +++ b/src/UI/Inspectors/InspectorBase.cs @@ -0,0 +1,122 @@ +using System; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Panels; + +namespace UnityExplorer.UI.Inspectors +{ + public abstract class InspectorBase + { + public object Target; + public bool IsActive { get; private set; } + + public abstract string TabLabel { get; } + + internal bool m_pendingDestroy; + + public InspectorBase(object target) + { + Target = target; + + if (Target.IsNullOrDestroyed(false)) + { + Destroy(); + return; + } + + AddInspectorTab(this); + } + + public virtual void SetActive() + { + this.IsActive = true; + Content?.SetActive(true); + } + + public virtual void SetInactive() + { + this.IsActive = false; + Content?.SetActive(false); + } + + public virtual void Update() + { + if (Target.IsNullOrDestroyed(false)) + { + Destroy(); + return; + } + + m_tabText.text = TabLabel; + } + + public virtual void Destroy() + { + m_pendingDestroy = true; + + GameObject tabGroup = m_tabButton?.transform.parent.gameObject; + + if (tabGroup) + GameObject.Destroy(tabGroup); + + int thisIndex = -1; + if (InspectorManager.ActiveInspectors.Contains(this)) + { + thisIndex = InspectorManager.ActiveInspectors.IndexOf(this); + InspectorManager.ActiveInspectors.Remove(this); + } + + if (ReferenceEquals(InspectorManager.m_activeInspector, this)) + { + InspectorManager.UnsetInspectorTab(); + + if (InspectorManager.ActiveInspectors.Count > 0) + { + var prevTab = InspectorManager.ActiveInspectors[thisIndex > 0 ? thisIndex - 1 : 0]; + InspectorManager.SetInspectorTab(prevTab); + } + } + } + + #region UI + + public abstract GameObject Content { get; set; } + public Button m_tabButton; + public Text m_tabText; + + public void AddInspectorTab(InspectorBase parent) + { + var tabContent = InspectorPanel.Instance.NavbarHolder; + + var tabGroupObj = UIFactory.CreateHorizontalGroup(tabContent, "TabObject", true, true, true, true); + UIFactory.SetLayoutElement(tabGroupObj, minWidth: 185, flexibleWidth: 0); + tabGroupObj.AddComponent(); + + m_tabButton = UIFactory.CreateButton(tabGroupObj, + "TabButton", + "", + () => { InspectorManager.SetInspectorTab(parent); }); + + UIFactory.SetLayoutElement(m_tabButton.gameObject, minWidth: 165, flexibleWidth: 0); + + m_tabText = m_tabButton.GetComponentInChildren(); + m_tabText.horizontalOverflow = HorizontalWrapMode.Overflow; + m_tabText.alignment = TextAnchor.MiddleLeft; + + var closeBtn = UIFactory.CreateButton(tabGroupObj, + "CloseButton", + "X", + parent.Destroy, + new Color(0.2f, 0.2f, 0.2f, 1)); + + UIFactory.SetLayoutElement(closeBtn.gameObject, minWidth: 20, flexibleWidth: 0); + + var closeBtnText = closeBtn.GetComponentInChildren(); + closeBtnText.color = new Color(1, 0, 0, 1); + } + + public virtual void OnPanelResized() { } + + #endregion + } +} diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs new file mode 100644 index 0000000..c1e9744 --- /dev/null +++ b/src/UI/Inspectors/InspectorManager.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityExplorer.UI; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; +using UnityExplorer.Core.Runtime; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.Inspectors.Reflection; +using UnityExplorer.UI.Panels; + +namespace UnityExplorer.UI.Inspectors +{ + public static class InspectorManager + { + public static InspectorBase m_activeInspector; + public static readonly List ActiveInspectors = new List(); + + public static void Update() + { + try + { + for (int i = 0; i < ActiveInspectors.Count; i++) + ActiveInspectors[i].Update(); + } + catch (Exception ex) + { + ExplorerCore.LogWarning(ex); + } + } + + public static void Inspect(object obj, CacheObjectBase parentMember = null) + { + var type = ReflectionProvider.Instance.GetActualType(obj); + + // only need to set parent member for structs + if (!type.IsValueType) + parentMember = null; + + obj = ReflectionProvider.Instance.Cast(obj, type); + + if (obj.IsNullOrDestroyed(false)) + return; + + // check if currently inspecting this object + foreach (InspectorBase tab in ActiveInspectors) + { + if (obj.ReferenceEqual(tab.Target)) + { + SetInspectorTab(tab); + return; + } + } + + InspectorBase inspector; + if (obj is GameObject go) + { + ExplorerCore.Log("TODO"); + return; + // inspector = new GameObjectInspector(go); + } + else + inspector = new InstanceInspector(obj); + + if (inspector is ReflectionInspector ri) + ri.ParentMember = parentMember; + + ActiveInspectors.Add(inspector); + SetInspectorTab(inspector); + } + + public static void InspectType(Type type) + { + if (type == null) + { + ExplorerCore.LogWarning("The provided type was null!"); + return; + } + + foreach (var tab in ActiveInspectors.Where(x => x is StaticInspector)) + { + if (ReferenceEquals(tab.Target as Type, type)) + { + SetInspectorTab(tab); + return; + } + } + + var inspector = new StaticInspector(type); + + ActiveInspectors.Add(inspector); + SetInspectorTab(inspector); + } + + public static void SetInspectorTab(InspectorBase inspector) + { + UIManager.SetPanelActive(UIManager.Panels.Inspector, true); + + if (m_activeInspector == inspector) + return; + + UnsetInspectorTab(); + + m_activeInspector = inspector; + inspector.SetActive(); + + OnSetInspectorTab(inspector); + } + + public static void UnsetInspectorTab() + { + if (m_activeInspector == null) + return; + + m_activeInspector.SetInactive(); + + OnUnsetInspectorTab(); + + m_activeInspector = null; + } + + public static void OnSetInspectorTab(InspectorBase inspector) + { + Color activeColor = new Color(0, 0.25f, 0, 1); + RuntimeProvider.Instance.SetColorBlock(inspector.m_tabButton, activeColor, activeColor); + } + + public static void OnUnsetInspectorTab() + { + RuntimeProvider.Instance.SetColorBlock(m_activeInspector.m_tabButton, + new Color(0.2f, 0.2f, 0.2f, 1), new Color(0.1f, 0.3f, 0.1f, 1)); + } + + internal static void OnPanelResized() + { + foreach (var instance in ActiveInspectors) + { + instance.OnPanelResized(); + } + } + } +} diff --git a/src/UI/Inspectors/Reflection/CacheMemberList.cs b/src/UI/Inspectors/Reflection/CacheMemberList.cs new file mode 100644 index 0000000..740d733 --- /dev/null +++ b/src/UI/Inspectors/Reflection/CacheMemberList.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors.Reflection +{ + public class CacheMemberList : IPoolDataSource + { + public ScrollPool ScrollPool { get; } + public ReflectionInspector Inspector { get; } + + public CacheMemberList(ScrollPool scrollPool, ReflectionInspector inspector) + { + this.ScrollPool = scrollPool; + this.Inspector = inspector; + } + + public int ItemCount => Inspector.filteredMembers.Count; + + public int GetRealIndexOfTempIndex(int index) + { + if (index < 0 || index >= Inspector.filteredToRealIndices.Count) + return -1; + return Inspector.filteredToRealIndices[index]; + } + + public ICell CreateCell(RectTransform cellTransform) => new CellViewHolder(cellTransform.gameObject); + + public void DisableCell(ICell cell, int index) + { + var root = (cell as CellViewHolder).UIRoot; + DisableContent(root); + cell.Disable(); + } + + public void SetCell(ICell icell, int index) + { + var root = (icell as CellViewHolder).UIRoot; + + if (index < 0 || index >= ItemCount) + { + DisableContent(root); + icell.Disable(); + return; + } + + float start = Time.realtimeSinceStartup; + index = GetRealIndexOfTempIndex(index); + + var cache = Inspector.allMembers[index]; + cache.Enable(); + + var content = cache.UIRoot; + + if (content.transform.parent.ReferenceEqual(root.transform)) + return; + + var orig = content.transform.parent; + + DisableContent(root); + + content.transform.SetParent(root.transform, false); + //ExplorerCore.Log("Set cell " + index + ", took " + (Time.realtimeSinceStartup - start) + " secs"); + //ExplorerCore.Log("orig parent was " + (orig?.name ?? " ")); + } + + private void DisableContent(GameObject cellRoot) + { + if (cellRoot.transform.childCount > 0 && cellRoot.transform.GetChild(0) is Transform existing) + existing.transform.SetParent(Inspector.InactiveHolder.transform, false); + } + } +} diff --git a/src/UI/Inspectors/Reflection/InstanceInspector.cs b/src/UI/Inspectors/Reflection/InstanceInspector.cs new file mode 100644 index 0000000..7a1dc0d --- /dev/null +++ b/src/UI/Inspectors/Reflection/InstanceInspector.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core; +using UnityExplorer.Core.Config; +using UnityExplorer.Core.Runtime; + +namespace UnityExplorer.UI.Inspectors.Reflection +{ + public enum MemberScopes + { + All, + Instance, + Static + } + + public class InstanceInspector : ReflectionInspector + { + public override string TabLabel => $" [R] {base.TabLabel}"; + + internal MemberScopes m_scopeFilter; + internal Button m_lastActiveScopeButton; + + public InstanceInspector(object target) : base(target) { } + + internal void OnScopeFilterClicked(MemberScopes type, Button button) + { + if (m_lastActiveScopeButton) + RuntimeProvider.Instance.SetColorBlock(m_lastActiveScopeButton, new Color(0.2f, 0.2f, 0.2f)); + + m_scopeFilter = type; + m_lastActiveScopeButton = button; + + RuntimeProvider.Instance.SetColorBlock(m_lastActiveScopeButton, new Color(0.2f, 0.6f, 0.2f)); + + FilterMembers(null, true); + + ScrollPool.EnableTempCache(); + ScrollPool.RefreshAndJumpToTop(); + //RefreshDisplay(); + //m_sliderScroller.m_slider.value = 1f; + } + + public void ConstructInstanceScopeFilters(GameObject parent) + { + var memberFilterRowObj = UIFactory.CreateHorizontalGroup(parent, "InstanceFilterRow", false, false, true, true, 5, default, + new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(memberFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000); + + var memLabel = UIFactory.CreateLabel(memberFilterRowObj, "MemberLabel", "Filter scope:", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(memLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0); + + AddFilterButton(memberFilterRowObj, MemberScopes.All, true); + AddFilterButton(memberFilterRowObj, MemberScopes.Instance); + AddFilterButton(memberFilterRowObj, MemberScopes.Static); + } + + private void AddFilterButton(GameObject parent, MemberScopes type, bool setEnabled = false) + { + var btn = UIFactory.CreateButton(parent, + "ScopeFilterButton_" + type, + type.ToString(), + null, + new Color(0.2f, 0.2f, 0.2f)); + + UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 70); + + btn.onClick.AddListener(() => { OnScopeFilterClicked(type, btn); }); + + RuntimeProvider.Instance.SetColorBlock(btn, highlighted: new Color(0.3f, 0.7f, 0.3f)); + + if (setEnabled) + { + RuntimeProvider.Instance.SetColorBlock(btn, new Color(0.2f, 0.6f, 0.2f)); + m_scopeFilter = type; + m_lastActiveScopeButton = btn; + } + } + + public void ConstructUnityInstanceHelpers() + { + if (!typeof(UnityEngine.Object).IsAssignableFrom(m_targetType)) + return; + + var rowObj = UIFactory.CreateHorizontalGroup(Content, "InstanceHelperRow", true, true, true, true, 5, new Vector4(2, 2, 2, 2), + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000); + + if (typeof(Component).IsAssignableFrom(m_targetType)) + ConstructCompHelper(rowObj); + + ConstructUnityObjHelper(rowObj); + + if (m_targetType == typeof(Texture2D)) + ConstructTextureHelper(); + } + + internal void ConstructCompHelper(GameObject rowObj) + { + //var gameObjectLabel = UIFactory.CreateLabel(rowObj, "GameObjectLabel", "GameObject:", TextAnchor.MiddleLeft); + //UIFactory.SetLayoutElement(gameObjectLabel.gameObject, minWidth: 90, minHeight: 25, flexibleWidth: 0); + + var comp = Target.TryCast(typeof(Component)) as Component; + + var btn = UIFactory.CreateButton(rowObj, + "GameObjectButton", + "View GameObject", + () => { InspectorManager.Inspect(comp.gameObject); }, + new Color(0.2f, 0.5f, 0.2f)); + UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 0); + } + + internal void ConstructUnityObjHelper(GameObject rowObj) + { + var label = UIFactory.CreateLabel(rowObj, "NameLabel", "Name:", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(label.gameObject, minWidth: 60, minHeight: 25, flexibleWidth: 0); + + var uObj = Target.TryCast(typeof(UnityEngine.Object)) as UnityEngine.Object; + + var inputObj = UIFactory.CreateInputField(rowObj, "NameInput", "...", out InputField inputField, 14, 3, 1); + UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleWidth: 2000); + inputField.readOnly = true; + inputField.text = uObj.name; + } + + internal bool showingTextureHelper; + internal bool constructedTextureViewer; + + internal GameObject m_textureViewerObj; + + internal void ConstructTextureHelper() + { + var rowObj = UIFactory.CreateHorizontalGroup(Content, "TextureHelper", true, false, true, true, 5, new Vector4(3, 3, 3, 3), + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleHeight: 0); + + var showBtn = UIFactory.CreateButton(rowObj, "ShowButton", "Show", null, new Color(0.2f, 0.6f, 0.2f)); + UIFactory.SetLayoutElement(showBtn.gameObject, minWidth: 50, flexibleWidth: 0); + + UIFactory.CreateLabel(rowObj, "TextureViewerLabel", "Texture Viewer", TextAnchor.MiddleLeft); + + var textureViewerObj = UIFactory.CreateScrollView(Content, "TextureViewerContent", out GameObject scrollContent, out _, + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutGroup(textureViewerObj, false, false, true, true); + UIFactory.SetLayoutElement(textureViewerObj, minHeight: 100, flexibleHeight: 9999, flexibleWidth: 9999); + + textureViewerObj.SetActive(false); + + m_textureViewerObj = textureViewerObj; + + var showText = showBtn.GetComponentInChildren(); + showBtn.onClick.AddListener(() => + { + showingTextureHelper = !showingTextureHelper; + + if (showingTextureHelper) + { + if (!constructedTextureViewer) + ConstructTextureViewerArea(scrollContent); + + showText.text = "Hide"; + ToggleTextureViewer(true); + } + else + { + showText.text = "Show"; + ToggleTextureViewer(false); + } + }); + } + + internal void ConstructTextureViewerArea(GameObject parent) + { + constructedTextureViewer = true; + + var tex = Target.TryCast(typeof(Texture2D)) as Texture2D; + + if (!tex) + { + ExplorerCore.LogWarning("Could not cast the target instance to Texture2D! Maybe its null or destroyed?"); + return; + } + + // Save helper + + var saveRowObj = UIFactory.CreateHorizontalGroup(parent, "SaveRow", true, true, 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", null, new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(saveBtn.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + + var inputObj = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...", out InputField inputField); + UIFactory.SetLayoutElement(inputObj, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + + var name = tex.name; + if (string.IsNullOrEmpty(name)) + name = "untitled"; + + inputField.text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); + + saveBtn.onClick.AddListener(() => + { + if (tex && !string.IsNullOrEmpty(inputField.text)) + { + var path = inputField.text; + if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) + { + ExplorerCore.LogWarning("Desired save path must end with '.png'!"); + return; + } + + var dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + if (File.Exists(path)) + File.Delete(path); + + if (!TextureUtilProvider.IsReadable(tex)) + tex = TextureUtilProvider.ForceReadTexture(tex); + + byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex); + + File.WriteAllBytes(path, data); + } + }); + + // Actual texture viewer + + var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent); + var image = imageObj.AddComponent(); + var sprite = TextureUtilProvider.Instance.CreateSprite(tex); + image.sprite = sprite; + + var fitter = imageObj.AddComponent(); + fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + var imageLayout = imageObj.AddComponent(); + imageLayout.preferredHeight = sprite.rect.height; + imageLayout.preferredWidth = sprite.rect.width; + } + + internal void ToggleTextureViewer(bool enabled) + { + m_textureViewerObj.SetActive(enabled); + + m_filterAreaObj.SetActive(!enabled); + //m_memberListObj.SetActive(!enabled); + m_updateRowObj.SetActive(!enabled); + } + } +} diff --git a/src/UI/Inspectors/Reflection/ReflectionInspector.cs b/src/UI/Inspectors/Reflection/ReflectionInspector.cs new file mode 100644 index 0000000..633b027 --- /dev/null +++ b/src/UI/Inspectors/Reflection/ReflectionInspector.cs @@ -0,0 +1,484 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core; +using UnityExplorer.Core.Config; +using UnityExplorer.Core.Runtime; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors.Reflection +{ + public class ReflectionInspector : InspectorBase + { + #region STATIC + + public static ReflectionInspector ActiveInstance { get; private set; } + + //static ReflectionInspector() + //{ + // PanelDragger.OnFinishResize += (RectTransform _) => OnContainerResized(); + // SceneExplorer.OnToggleShow += OnContainerResized; + //} + + //private static void OnContainerResized(bool _ = false) + //{ + // if (ActiveInstance == null) + // return; + + // ActiveInstance.m_widthUpdateWanted = true; + //} + + // Blacklists + private static readonly HashSet bl_typeAndMember = new HashSet + { +#if CPP + // these cause a crash in IL2CPP + "Type.DeclaringMethod", + "Rigidbody2D.Cast", + "Collider2D.Cast", + "Collider2D.Raycast", + "Texture2D.SetPixelDataImpl", + "Camera.CalculateProjectionMatrixFromPhysicalProperties", +#endif + }; + private static readonly HashSet bl_methodNameStartsWith = new HashSet + { + // these are redundant, just adds noise, properties are supported directly + "get_", + "set_", + }; + + #endregion + + #region INSTANCE + + public override string TabLabel => m_targetTypeShortName; + + public CacheObjectBase ParentMember { get; internal set; } + + public ScrollPool ScrollPool { get; private set; } + public CacheMemberList CacheMemberList { get; private set; } + + public GameObject InactiveHolder => m_inactiveHolder; + private GameObject m_inactiveHolder; + + internal readonly Type m_targetType; + internal readonly string m_targetTypeShortName; + + // all cached members of the target + internal CacheMember[] allMembers; + // filtered members based on current filters + internal readonly List filteredMembers = new List(); + // actual shortlist of displayed members + internal readonly List displayedMembers = new List(); + + // index: Index in filter list, Value: Actual real index in allMembers list. + internal readonly List filteredToRealIndices = new List(); + + internal bool autoUpdate; + + public override void OnPanelResized() + { + foreach (var member in displayedMembers) + { + member.memberLabelElement.minWidth = 0.4f * InspectorPanel.CurrentPanelWidth; + } + } + + public ReflectionInspector(object target) : base(target) + { + if (this is StaticInspector) + m_targetType = target as Type; + else + m_targetType = ReflectionUtility.GetActualType(target); + + m_targetTypeShortName = SignatureHighlighter.ParseFullSyntax(m_targetType, false); + + ConstructUI(); + + CacheMembers(m_targetType); + + FilterMembers(); + } + + public override void SetActive() + { + base.SetActive(); + ActiveInstance = this; + } + + public override void SetInactive() + { + base.SetInactive(); + ActiveInstance = null; + } + + public override void Destroy() + { + base.Destroy(); + + if (this.Content) + GameObject.Destroy(this.Content); + } + + internal bool IsBlacklisted(string sig) => bl_typeAndMember.Any(it => sig.Contains(it)); + internal bool IsBlacklisted(MethodInfo method) => bl_methodNameStartsWith.Any(it => method.Name.StartsWith(it)); + + internal string GetSig(MemberInfo member) => $"{member.DeclaringType.Name}.{member.Name}"; + internal string AppendArgsToSig(ParameterInfo[] args) + { + string ret = " ("; + foreach (var param in args) + ret += $"{param.ParameterType.Name} {param.Name}, "; + ret += ")"; + return ret; + } + + public void CacheMembers(Type type) + { + var list = new List(); + var cachedSigs = new HashSet(); + + var types = ReflectionUtility.GetAllBaseTypes(type); + + var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static; + if (this is InstanceInspector) + flags |= BindingFlags.Instance; + + foreach (var declaringType in types) + { + var target = Target; + target = target.TryCast(declaringType); + + IEnumerable infos = declaringType.GetMethods(flags); + infos = infos.Concat(declaringType.GetProperties(flags)); + infos = infos.Concat(declaringType.GetFields(flags)); + + foreach (var member in infos) + { + try + { + var sig = GetSig(member); + + //ExplorerCore.Log($"Trying to cache member {sig}..."); + //ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name); + + var mi = member as MethodInfo; + var pi = member as PropertyInfo; + var fi = member as FieldInfo; + + if (IsBlacklisted(sig) || (mi != null && IsBlacklisted(mi))) + continue; + + var args = mi?.GetParameters() ?? pi?.GetIndexParameters(); + if (args != null) + { + if (!CacheMember.CanProcessArgs(args)) + continue; + + sig += AppendArgsToSig(args); + } + + if (cachedSigs.Contains(sig)) + continue; + + cachedSigs.Add(sig); + + if (mi != null) + list.Add(new CacheMethod(mi, target, InactiveHolder)); + else if (pi != null) + list.Add(new CacheProperty(pi, target, InactiveHolder)); + else + list.Add(new CacheField(fi, target, InactiveHolder)); + + var cached = list[list.Count - 1]; + cached.ParentInspector = this; + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!"); + ExplorerCore.Log(e.ToString()); + } + } + } + + var typeList = types.ToList(); + + var sorted = new List(); + sorted.AddRange(list.Where(it => it is CacheProperty) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + sorted.AddRange(list.Where(it => it is CacheField) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + sorted.AddRange(list.Where(it => it is CacheMethod) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + + allMembers = sorted.ToArray(); + } + + public override void Update() + { + base.Update(); + + if (autoUpdate) + { + foreach (var member in displayedMembers) + { + if (member == null) break; + member.UpdateValue(); + } + } + + //if (m_widthUpdateWanted) + //{ + // if (!m_widthUpdateWaiting) + // m_widthUpdateWaiting = true; + // else + // { + // UpdateWidths(); + // m_widthUpdateWaiting = false; + // m_widthUpdateWanted = false; + // } + //} + } + + internal void OnMemberFilterClicked(MemberTypes type, Button button) + { + if (m_lastActiveMemButton) + RuntimeProvider.Instance.SetColorBlock(m_lastActiveMemButton, new Color(0.2f, 0.2f, 0.2f)); + + m_memberFilter = type; + m_lastActiveMemButton = button; + + RuntimeProvider.Instance.SetColorBlock(m_lastActiveMemButton, new Color(0.2f, 0.6f, 0.2f)); + + FilterMembers(null, true); + ScrollPool.EnableTempCache(); + ScrollPool.Rebuild(); + } + + public void FilterMembers(string nameFilter = null, bool force = false) + { + int lastCount = filteredMembers.Count; + filteredMembers.Clear(); + + nameFilter = nameFilter?.ToLower() ?? m_nameFilterText.text.ToLower(); + + filteredToRealIndices.Clear(); + + for (int i = 0; i < allMembers.Length; i++) + { + var mem = allMembers[i]; + + // membertype filter + if (m_memberFilter != MemberTypes.All && mem.MemInfo.MemberType != m_memberFilter) + continue; + + if (this is InstanceInspector ii && ii.m_scopeFilter != MemberScopes.All) + { + if (mem.IsStatic && ii.m_scopeFilter != MemberScopes.Static) + continue; + else if (!mem.IsStatic && ii.m_scopeFilter != MemberScopes.Instance) + continue; + } + + // name filter + if (!string.IsNullOrEmpty(nameFilter) && !mem.NameForFiltering.Contains(nameFilter)) + continue; + + filteredMembers.Add(mem); + filteredToRealIndices.Add(i); + } + + //if (force || lastCount != filteredMembers.Count) + //{ + // ScrollPool.EnableTempCache(); + // ScrollPool.Rebuild(); + //} + } + + #endregion + + #region UI + + private GameObject m_content; + public override GameObject Content + { + get => m_content; + set => m_content = value; + } + + internal Text m_nameFilterText; + internal MemberTypes m_memberFilter; + internal Button m_lastActiveMemButton; + + internal GameObject m_filterAreaObj; + internal GameObject m_updateRowObj; + + internal void ConstructUI() + { + var parent = InspectorPanel.Instance.ContentHolder; + + this.Content = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, false, true, true, 5, new Vector4(4, 4, 4, 4), + new Color(0.15f, 0.15f, 0.15f)); + + this.m_inactiveHolder = new GameObject("InactiveContentHolder"); + m_inactiveHolder.transform.SetParent(parent.transform, false); + m_inactiveHolder.SetActive(false); + + ConstructTopArea(); + + ConstructMemberList(); + } + + internal void ConstructTopArea() + { + var nameRowObj = UIFactory.CreateHorizontalGroup(Content, "NameRowObj", true, true, true, true, 2, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(nameRowObj, minHeight: 25, flexibleHeight: 0, minWidth: 200, flexibleWidth: 5000); + + var typeLabelText = UIFactory.CreateLabel(nameRowObj, "TypeLabel", "Type:", TextAnchor.MiddleLeft); + typeLabelText.horizontalOverflow = HorizontalWrapMode.Overflow; + UIFactory.SetLayoutElement(typeLabelText.gameObject, minWidth: 40, flexibleWidth: 0, minHeight: 25); + + var typeDisplay = UIFactory.CreateLabel(nameRowObj, "TypeDisplayText", SignatureHighlighter.ParseFullSyntax(m_targetType, true), + TextAnchor.MiddleLeft); + + UIFactory.SetLayoutElement(typeDisplay.gameObject, minHeight: 25, flexibleWidth: 5000); + + // instance helper tools + + if (this is InstanceInspector instanceInspector) + { + instanceInspector.ConstructUnityInstanceHelpers(); + } + + ConstructFilterArea(); + + ConstructUpdateRow(); + } + + internal void ConstructFilterArea() + { + // Filters + + m_filterAreaObj = UIFactory.CreateVerticalGroup(Content, "FilterGroup", true, true, true, true, 4, new Vector4(4, 4, 4, 4), + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(m_filterAreaObj, minHeight: 60); + + // name filter + + var nameFilterRowObj = UIFactory.CreateHorizontalGroup(m_filterAreaObj, "NameFilterRow", false, false, true, true, 5, default, + new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(nameFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000); + + var nameLabel = UIFactory.CreateLabel(nameFilterRowObj, "NameLabel", "Filter names:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(nameLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0); + + var nameInputObj = UIFactory.CreateInputField(nameFilterRowObj, "NameInput", "...", out InputField nameInput, 14, (int)TextAnchor.MiddleLeft, + (int)HorizontalWrapMode.Overflow); + UIFactory.SetLayoutElement(nameInputObj, flexibleWidth: 5000, minWidth: 100, minHeight: 25); + nameInput.onValueChanged.AddListener((string val) => + { + FilterMembers(val, true); + ScrollPool.EnableTempCache(); + ScrollPool.Rebuild(); + }); + m_nameFilterText = nameInput.textComponent; + + // membertype filter + + var memberFilterRowObj = UIFactory.CreateHorizontalGroup(m_filterAreaObj, "MemberFilter", false, false, true, true, 5, default, + new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(memberFilterRowObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000); + + var memLabel = UIFactory.CreateLabel(memberFilterRowObj, "MemberFilterLabel", "Filter members:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(memLabel.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 0); + + AddFilterButton(memberFilterRowObj, MemberTypes.All, true); + AddFilterButton(memberFilterRowObj, MemberTypes.Method); + AddFilterButton(memberFilterRowObj, MemberTypes.Property); + AddFilterButton(memberFilterRowObj, MemberTypes.Field); + + // Instance filters + + if (this is InstanceInspector instanceInspector) + { + instanceInspector.ConstructInstanceScopeFilters(m_filterAreaObj); + } + } + + private void AddFilterButton(GameObject parent, MemberTypes type, bool setEnabled = false) + { + var btn = UIFactory.CreateButton(parent, + "FilterButton_" + type, + type.ToString(), + null, + new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(btn.gameObject, minHeight: 25, minWidth: 70); + btn.onClick.AddListener(() => { OnMemberFilterClicked(type, btn); }); + + RuntimeProvider.Instance.SetColorBlock(btn, highlighted: new Color(0.3f, 0.7f, 0.3f)); + + if (setEnabled) + { + RuntimeProvider.Instance.SetColorBlock(btn, new Color(0.2f, 0.6f, 0.2f)); + m_memberFilter = type; + m_lastActiveMemButton = btn; + } + } + + internal void ConstructUpdateRow() + { + m_updateRowObj = UIFactory.CreateHorizontalGroup(Content, "UpdateRow", false, true, true, true, 10, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(m_updateRowObj, minHeight: 25); + + // update button + + var updateBtn = UIFactory.CreateButton(m_updateRowObj, "UpdateButton", "Update Values", null, new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(updateBtn.gameObject, minWidth: 110, flexibleWidth: 0); + updateBtn.onClick.AddListener(() => + { + bool orig = autoUpdate; + autoUpdate = true; + Update(); + if (!orig) + autoUpdate = orig; + }); + + // auto update + + var autoUpdateObj = UIFactory.CreateToggle(m_updateRowObj, "UpdateToggle", out Toggle autoUpdateToggle, out Text autoUpdateText); + var autoUpdateLayout = autoUpdateObj.AddComponent(); + autoUpdateLayout.minWidth = 150; + autoUpdateLayout.minHeight = 25; + autoUpdateText.text = "Auto-update?"; + autoUpdateToggle.isOn = false; + autoUpdateToggle.onValueChanged.AddListener((bool val) => { autoUpdate = val; }); + } + + internal void ConstructMemberList() + { + ScrollPool = UIFactory.CreateScrollPool(Content, "MemberList", out GameObject scrollRoot, out GameObject scrollContent, + new Color(0.05f, 0.05f, 0.05f)); + UIFactory.SetLayoutElement(scrollRoot, flexibleHeight: 9999); + UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); + + CacheMemberList = new CacheMemberList(ScrollPool, this); + + ScrollPool.Initialize(CacheMemberList, CellViewHolder.CreatePrototypeCell(scrollRoot)); + + // ScrollPool.Viewport.GetComponent().enabled = false; + } + } + + #endregion +} \ No newline at end of file diff --git a/src/UI/Inspectors/Reflection/StaticInspector.cs b/src/UI/Inspectors/Reflection/StaticInspector.cs new file mode 100644 index 0000000..eeeb537 --- /dev/null +++ b/src/UI/Inspectors/Reflection/StaticInspector.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.Reflection +{ + public class StaticInspector : ReflectionInspector + { + public override string TabLabel => $" [S] {base.TabLabel}"; + + public StaticInspector(Type type) : base(type) { } + } +} diff --git a/src/UI/Models/UIPanel.cs b/src/UI/Models/UIPanel.cs index 9c0dd4d..7b6fa77 100644 --- a/src/UI/Models/UIPanel.cs +++ b/src/UI/Models/UIPanel.cs @@ -16,6 +16,8 @@ namespace UnityExplorer.UI.Models { // STATIC + internal static void InvokeOnPanelsReordered() => OnPanelsReordered?.Invoke(); + public static event Action OnPanelsReordered; public static event Action OnClickedOutsidePanels; diff --git a/src/UI/Panels/InspectorPanel.cs b/src/UI/Panels/InspectorPanel.cs new file mode 100644 index 0000000..74f745a --- /dev/null +++ b/src/UI/Panels/InspectorPanel.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.Core.Runtime; +using UnityExplorer.UI.Inspectors; +using UnityExplorer.UI.Models; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Panels +{ + public class InspectorPanel : UIPanel + { + public static InspectorPanel Instance { get; private set; } + + public InspectorPanel() { Instance = this; } + + public override string Name => "Inspector"; + public override UIManager.Panels PanelType => UIManager.Panels.Inspector; + public override bool ShouldSaveActiveState => false; + + public GameObject NavbarHolder; + public GameObject ContentHolder; + + public static float CurrentPanelWidth => Instance.mainPanelRect.rect.width; + + public override void Update() + { + InspectorManager.Update(); + } + + public override void OnFinishResize(RectTransform panel) + { + base.OnFinishResize(panel); + + InspectorManager.OnPanelResized(); + } + + public override void LoadSaveData() + { + ApplySaveData(ConfigManager.GameObjectInspectorData.Value); + } + + public override void SaveToConfigManager() + { + ConfigManager.GameObjectInspectorData.Value = this.ToSaveData(); + } + + public override void SetDefaultPosAndAnchors() + { + mainPanelRect.localPosition = Vector2.zero; + mainPanelRect.pivot = new Vector2(0.5f, 0.5f); + mainPanelRect.anchorMin = new Vector2(0.5f, 0); + mainPanelRect.anchorMax = new Vector2(0.5f, 1); + mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom + mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top + mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); + mainPanelRect.anchoredPosition = new Vector2(-150, 0); + } + + public override void ConstructPanelContent() + { + // this.UIRoot.GetComponent().enabled = false; + + UIFactory.SetLayoutGroup(this.content, forceHeight: true, spacing: 10, padLeft: 5, padRight: 5); + + this.NavbarHolder = UIFactory.CreateGridGroup(this.content, "Navbar", new Vector2(200, 22), new Vector2(4, 2), + new Color(0.18f, 0.18f, 0.18f)); + //UIFactory.SetLayoutElement(NavbarHolder, flexibleWidth: 9999, minHeight: 0, preferredHeight: 0, flexibleHeight: 9999); + NavbarHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + this.ContentHolder = UIFactory.CreateVerticalGroup(this.content, "ContentHolder", true, true, true, true, 0, default, + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(ContentHolder, flexibleHeight: 9999); + + UIManager.SetPanelActive(PanelType, false); + } + } +} + +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.Core.Config; +//using UnityExplorer.Core.Runtime; +//using UnityExplorer.UI.Models; +//using UnityExplorer.UI.Utility; +//using UnityExplorer.UI.Widgets; + +//namespace UnityExplorer.UI.Panels +//{ +// public class InspectorPanel : UIPanel +// { +// public static InspectorPanel Instance { get; private set; } + +// public GameObject NavbarHolder; +// public GameObject ContentHolder; + +// public override string Name => "Inspector"; +// public override UIManager.Panels PanelType => UIManager.Panels.Inspector; +// public override bool ShouldSaveActiveState => false; + +// public InspectorPanel() { Instance = this; } + +// public override void Update() +// { + +// } + +// public override void LoadSaveData() +// { +// ApplySaveData(ConfigManager.GameObjectInspectorData.Value); +// } + +// public override void SaveToConfigManager() +// { +// ConfigManager.GameObjectInspectorData.Value = this.ToSaveData(); +// } + +// public override void SetDefaultPosAndAnchors() +// { +// mainPanelRect.localPosition = Vector2.zero; +// mainPanelRect.pivot = new Vector2(0.5f, 0.5f); +// mainPanelRect.anchorMin = new Vector2(0.5f, 0); +// mainPanelRect.anchorMax = new Vector2(0.5f, 1); +// mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom +// mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top +// mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); +// mainPanelRect.anchoredPosition = new Vector2(-150, 0); +// } + +// internal static DynamicListTest listInstance; + +// private ScrollPool scrollPool; + +// public override void ConstructPanelContent() +// { +// // temp test +// scrollPool = UIFactory.CreateScrollPool(content, "Test", out GameObject scrollObj, +// out GameObject scrollContent, new Color(0.15f, 0.15f, 0.15f)); +// UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); +// UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); + +// // disable masks for debug +// UIRoot.GetComponent().enabled = false; +// scrollPool.Viewport.GetComponent().enabled = false; +// scrollPool.Content.gameObject.AddComponent().color = new Color(1f, 0f, 1f, 0.3f); + +// listInstance = new DynamicListTest(scrollPool, this); +// listInstance.Init(); + +// //var prototype = DynamicCell.CreatePrototypeCell(scrollContent); +// //scrollPool.PrototypeCell = prototype.GetComponent(); + +// contentHolder = new GameObject("DummyHolder"); +// contentHolder.SetActive(false); +// contentHolder.transform.SetParent(this.content.transform, false); + +// ExplorerCore.Log("Creating dummy objects"); +// for (int i = 0; i < 150; i++) +// { +// dummyContents.Add(CreateDummyContent()); +// } +// ExplorerCore.Log("Done"); + +// //previousRectHeight = mainPanelRect.rect.height; + +// UIManager.SetPanelActive(PanelType, false); +// } + +// internal GameObject contentHolder; +// internal readonly List dummyContents = new List(); + +// private GameObject CreateDummyContent() +// { +// var obj = UIFactory.CreateVerticalGroup(contentHolder, "Content", true, true, true, true, 2, new Vector4(2, 2, 2, 2)); +// obj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + +// var horiGroup = UIFactory.CreateHorizontalGroup(obj, "topGroup", true, true, true, true); +// UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleHeight: 0); + +// var mainLabel = UIFactory.CreateLabel(horiGroup, "label", "Dummy " + dummyContents.Count, TextAnchor.MiddleCenter); +// UIFactory.SetLayoutElement(mainLabel.gameObject, minHeight: 25, flexibleHeight: 0); + +// var expandButton = UIFactory.CreateButton(horiGroup, "Expand", "V"); +// UIFactory.SetLayoutElement(expandButton.gameObject, minWidth: 25, flexibleWidth: 0); + +// var subContent = UIFactory.CreateVerticalGroup(obj, "SubContent", true, true, true, true); + +// var inputObj = UIFactory.CreateInputField(subContent, "input", "...", out var inputField); +// UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 9999); +// inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; +// inputField.lineType = InputField.LineType.MultiLineNewline; + +// int numLines = UnityEngine.Random.Range(0, 10); +// inputField.text = "This field has " + numLines + " lines"; +// for (int i = 0; i < numLines; i++) +// inputField.text += "\r\n"; + +// //subContent.SetActive(false); + +// var btnLabel = expandButton.GetComponentInChildren(); +// expandButton.onClick.AddListener(OnExpand); +// void OnExpand() +// { +// bool active = !subContent.activeSelf; +// if (active) +// { +// subContent.SetActive(true); +// btnLabel.text = "^"; +// } +// else +// { +// subContent.SetActive(false); +// btnLabel.text = "V"; +// } +// } + +// return obj; +// } +// } + +// public class DynamicListTest : IPoolDataSource +// { +// internal ScrollPool ScrollPool; +// internal InspectorPanel Inspector; + +// public DynamicListTest(ScrollPool scroller, InspectorPanel inspector) +// { +// ScrollPool = scroller; +// Inspector = inspector; +// } + +// public int ItemCount => filtering ? filteredIndices.Count : Inspector.dummyContents.Count; + +// private bool filtering; +// private readonly List filteredIndices = new List(); + +// public int GetRealIndexOfTempIndex(int index) +// { +// if (index < 0 || index >= filteredIndices.Count) +// return -1; +// return filteredIndices[index]; +// } + +// public void ToggleFilter() +// { +// if (filtering) +// { +// DisableFilter(); +// ScrollPool.DisableTempCache(); +// } +// else +// { +// EnableRandomFilter(); +// ScrollPool.EnableTempCache(); +// } + +// ExplorerCore.Log("Filter toggled, new count: " + ItemCount); +// ScrollPool.Rebuild(); +// } + +// public void EnableRandomFilter() +// { +// filteredIndices.Clear(); +// filtering = true; + +// int counter = UnityEngine.Random.Range(0, Inspector.dummyContents.Count); +// while (filteredIndices.Count < counter) +// { +// var i = UnityEngine.Random.Range(0, Inspector.dummyContents.Count); +// if (!filteredIndices.Contains(i)) +// filteredIndices.Add(i); +// } +// filteredIndices.Sort(); +// } + +// public void DisableFilter() +// { +// filtering = false; +// } + +// public void OnDisableCell(CellViewHolder cell, int dataIndex) +// { +// if (cell.UIRoot.transform.Find("Content") is Transform existing) +// existing.transform.SetParent(Inspector.contentHolder.transform, false); +// } + +// public void Init() +// { +// var prototype = CellViewHolder.CreatePrototypeCell(ScrollPool.UIRoot); + +// ScrollPool.DataSource = this; +// ScrollPool.Initialize(this, prototype); +// } + +// public ICell CreateCell(RectTransform cellTransform) => new CellViewHolder(cellTransform.gameObject); + +// public void DisableCell(ICell icell, int index) +// { +// var root = (icell as CellViewHolder).UIRoot; +// DisableContent(root); +// icell.Disable(); +// } + +// public void SetCell(ICell icell, int index) +// { +// var root = (icell as CellViewHolder).UIRoot; + +// if (index < 0 || index >= ItemCount) +// { +// DisableContent(root); +// icell.Disable(); +// return; +// } + +// if (filtering) +// index = GetRealIndexOfTempIndex(index); + +// var content = Inspector.dummyContents[index]; + +// if (content.transform.parent.ReferenceEqual(root.transform)) +// return; + +// ExplorerCore.Log("Setting index " + index + " to cell " + root.transform.name); + +// DisableContent(root); + +// content.transform.SetParent(root.transform, false); +// } + +// private void DisableContent(GameObject cellRoot) +// { +// if (cellRoot.transform.Find("Content") is Transform existing) +// existing.transform.SetParent(Inspector.contentHolder.transform, false); +// } +// } +//} \ No newline at end of file diff --git a/src/UI/Panels/InspectorTest.cs b/src/UI/Panels/InspectorTest.cs deleted file mode 100644 index 1a27e44..0000000 --- a/src/UI/Panels/InspectorTest.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; -using UnityEngine.UI; -using UnityExplorer.Core.Config; -using UnityExplorer.Core.Runtime; -using UnityExplorer.UI.Models; -using UnityExplorer.UI.Utility; -using UnityExplorer.UI.Widgets; - -namespace UnityExplorer.UI.Panels -{ - public class InspectorTest : UIPanel - { - public override string Name => "Inspector"; - public override UIManager.Panels PanelType => UIManager.Panels.Inspector; - public override bool ShouldSaveActiveState => false; - - //public SimpleListSource ComponentList; - - public override void Update() - { - - } - - public override void LoadSaveData() - { - ApplySaveData(ConfigManager.GameObjectInspectorData.Value); - } - - public override void SaveToConfigManager() - { - ConfigManager.GameObjectInspectorData.Value = this.ToSaveData(); - } - - public override void SetDefaultPosAndAnchors() - { - mainPanelRect.localPosition = Vector2.zero; - mainPanelRect.pivot = new Vector2(0.5f, 0.5f); - mainPanelRect.anchorMin = new Vector2(0.5f, 0); - mainPanelRect.anchorMax = new Vector2(0.5f, 1); - mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom - mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top - mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); - mainPanelRect.anchoredPosition = new Vector2(-150, 0); - } - - internal static DynamicListTest listInstance; - - private ScrollPool scrollPool; - - public override void ConstructPanelContent() - { - // temp test - scrollPool = UIFactory.CreateScrollPool(content, "Test", out GameObject scrollObj, - out GameObject scrollContent, new Color(0.15f, 0.15f, 0.15f)); - UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); - UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); - - //// disable masks for debug - //UIRoot.GetComponent().enabled = false; - //scrollPool.Viewport.GetComponent().enabled = false; - //scrollPool.Content.gameObject.AddComponent().color = new Color(1f, 0f, 1f, 0.3f); - - listInstance = new DynamicListTest(scrollPool, this); - listInstance.Init(); - - //var prototype = DynamicCell.CreatePrototypeCell(scrollContent); - //scrollPool.PrototypeCell = prototype.GetComponent(); - - contentHolder = new GameObject("DummyHolder"); - contentHolder.SetActive(false); - contentHolder.transform.SetParent(this.content.transform, false); - - ExplorerCore.Log("Creating dummy objects"); - for (int i = 0; i < 150; i++) - { - dummyContents.Add(CreateDummyContent()); - } - ExplorerCore.Log("Done"); - - //previousRectHeight = mainPanelRect.rect.height; - - UIManager.SetPanelActive(PanelType, false); - } - - internal GameObject contentHolder; - internal readonly List dummyContents = new List(); - - private GameObject CreateDummyContent() - { - var obj = UIFactory.CreateVerticalGroup(contentHolder, "Content", true, true, true, true, 2, new Vector4(2, 2, 2, 2)); - obj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - - var horiGroup = UIFactory.CreateHorizontalGroup(obj, "topGroup", true, true, true, true); - UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleHeight: 0); - - var mainLabel = UIFactory.CreateLabel(horiGroup, "label", "Dummy " + dummyContents.Count, TextAnchor.MiddleCenter); - UIFactory.SetLayoutElement(mainLabel.gameObject, minHeight: 25, flexibleHeight: 0); - - var expandButton = UIFactory.CreateButton(horiGroup, "Expand", "V"); - UIFactory.SetLayoutElement(expandButton.gameObject, minWidth: 25, flexibleWidth: 0); - - var subContent = UIFactory.CreateVerticalGroup(obj, "SubContent", true, true, true, true); - - var inputObj = UIFactory.CreateInputField(subContent, "input", "...", out var inputField); - UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 9999); - inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - inputField.lineType = InputField.LineType.MultiLineNewline; - - int numLines = UnityEngine.Random.Range(0, 10); - inputField.text = "This field has " + numLines + " lines"; - for (int i = 0; i < numLines; i++) - inputField.text += "\r\n"; - - subContent.SetActive(false); - - var btnLabel = expandButton.GetComponentInChildren(); - expandButton.onClick.AddListener(OnExpand); - void OnExpand() - { - bool active = !subContent.activeSelf; - if (active) - { - subContent.SetActive(true); - btnLabel.text = "^"; - } - else - { - subContent.SetActive(false); - btnLabel.text = "V"; - } - } - - return obj; - } - } - - public class DynamicListTest : IPoolDataSource - { - internal ScrollPool ScrollPool; - internal InspectorTest Inspector; - - public DynamicListTest(ScrollPool scroller, InspectorTest inspector) - { - ScrollPool = scroller; - Inspector = inspector; - } - - public int ItemCount => filtering ? filteredIndices.Count : Inspector.dummyContents.Count; - - private bool filtering; - private readonly List filteredIndices = new List(); - - public int GetRealIndexOfTempIndex(int index) - { - if (index < 0 || index >= filteredIndices.Count) - return -1; - return filteredIndices[index]; - } - - public void ToggleFilter() - { - if (filtering) - { - DisableFilter(); - ScrollPool.DisableTempCache(); - } - else - { - EnableRandomFilter(); - ScrollPool.EnableTempCache(); - } - - ExplorerCore.Log("Filter toggled, new count: " + ItemCount); - ScrollPool.Rebuild(); - } - - public void EnableRandomFilter() - { - filteredIndices.Clear(); - filtering = true; - - int counter = UnityEngine.Random.Range(0, Inspector.dummyContents.Count); - while (filteredIndices.Count < counter) - { - var i = UnityEngine.Random.Range(0, Inspector.dummyContents.Count); - if (!filteredIndices.Contains(i)) - filteredIndices.Add(i); - } - filteredIndices.Sort(); - } - - public void DisableFilter() - { - filtering = false; - } - - public void OnDisableCell(CellViewHolder cell, int dataIndex) - { - if (cell.UIRoot.transform.Find("Content") is Transform existing) - existing.transform.SetParent(Inspector.contentHolder.transform, false); - } - - public void Init() - { - var prototype = CellViewHolder.CreatePrototypeCell(ScrollPool.UIRoot); - - ScrollPool.DataSource = this; - ScrollPool.Initialize(this, prototype); - } - - public ICell CreateCell(RectTransform cellTransform) => new CellViewHolder(cellTransform.gameObject); - - public void DisableCell(ICell icell, int index) - { - var root = (icell as CellViewHolder).UIRoot; - DisableContent(root); - icell.Disable(); - } - - public void SetCell(ICell icell, int index) - { - var root = (icell as CellViewHolder).UIRoot; - - if (index < 0 || index >= ItemCount) - { - DisableContent(root); - icell.Disable(); - return; - } - - if (filtering) - index = GetRealIndexOfTempIndex(index); - - var content = Inspector.dummyContents[index]; - - if (content.transform.parent.ReferenceEqual(root.transform)) - return; - - DisableContent(root); - - content.transform.SetParent(root.transform, false); - } - - private void DisableContent(GameObject cellRoot) - { - if (cellRoot.transform.Find("Content") is Transform existing) - existing.transform.SetParent(Inspector.contentHolder.transform, false); - } - } -} diff --git a/src/UI/Panels/PanelDragger.cs b/src/UI/Panels/PanelDragger.cs index 4dbcf89..a375179 100644 --- a/src/UI/Panels/PanelDragger.cs +++ b/src/UI/Panels/PanelDragger.cs @@ -95,13 +95,11 @@ namespace UnityExplorer.UI.Panels internal readonly Vector2 minResize = new Vector2(200, 50); - private static int currentResizePanel; - private bool WasResizing { get; set; } private ResizeTypes m_currentResizeType = ResizeTypes.NONE; private Vector2 m_lastResizePos; - private bool WasHoveringResize { get; set; } + private bool WasHoveringResize => s_resizeCursorObj.activeInHierarchy; private ResizeTypes m_lastResizeHoverType; private Rect m_totalResizeRect; @@ -183,7 +181,7 @@ namespace UnityExplorer.UI.Panels handledInstanceThisFrame = true; } - else if (inResizePos) + else if (inResizePos || WasResizing) { if (WasResizing) OnEndResize(); @@ -333,7 +331,7 @@ namespace UnityExplorer.UI.Panels // we are entering resize, or the resize type has changed. - WasHoveringResize = true; + //WasHoveringResize = true; m_lastResizeHoverType = resizeType; s_resizeCursorObj.SetActive(true); @@ -369,7 +367,7 @@ namespace UnityExplorer.UI.Panels public void OnHoverResizeEnd() { - WasHoveringResize = false; + //WasHoveringResize = false; s_resizeCursorObj.SetActive(false); } @@ -378,7 +376,6 @@ namespace UnityExplorer.UI.Panels m_currentResizeType = resizeType; m_lastResizePos = InputManager.MousePosition; WasResizing = true; - currentResizePanel = this.Panel.GetInstanceID(); } public void OnResize() @@ -428,9 +425,9 @@ namespace UnityExplorer.UI.Panels public void OnEndResize() { WasResizing = false; + try { OnHoverResizeEnd(); } catch { } UpdateResizeCache(); OnFinishResize?.Invoke(Panel); - currentResizePanel = -1; } internal static void CreateCursorUI() diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index 573b41b..3c3cb3d 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -754,7 +754,7 @@ namespace UnityExplorer.UI scrollRect.movementType = ScrollRect.MovementType.Clamped; scrollRect.inertia = true; scrollRect.elasticity = 0.125f; - scrollRect.scrollSensitivity = 15; + scrollRect.scrollSensitivity = 25; scrollRect.horizontal = false; scrollRect.vertical = true; diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index f935e7c..6d4d3d2 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -36,7 +36,7 @@ namespace UnityExplorer.UI internal static GameObject PanelHolder { get; private set; } public static ObjectExplorer Explorer { get; private set; } - public static InspectorTest Inspector { get; private set; } + public static InspectorPanel Inspector { get; private set; } public static AutoCompleter AutoCompleter { get; private set; } private static readonly Dictionary navButtonDict = new Dictionary(); @@ -116,7 +116,13 @@ namespace UnityExplorer.UI public static void SetPanelActive(Panels panel, bool active) { - GetPanel(panel).SetActive(active); + var obj = GetPanel(panel); + obj.SetActive(active); + if (active) + { + obj.UIRoot.transform.SetAsLastSibling(); + UIPanel.InvokeOnPanelsReordered(); + } if (navButtonDict.ContainsKey(panel)) { @@ -142,7 +148,7 @@ namespace UnityExplorer.UI Explorer = new ObjectExplorer(); Explorer.ConstructUI(); - Inspector = new InspectorTest(); + Inspector = new InspectorPanel(); Inspector.ConstructUI(); ShowMenu = !ConfigManager.Hide_On_Startup.Value; diff --git a/src/UI/Widgets/AutoComplete/AutoCompleter.cs b/src/UI/Widgets/AutoComplete/AutoCompleter.cs index fc848ff..be03350 100644 --- a/src/UI/Widgets/AutoComplete/AutoCompleter.cs +++ b/src/UI/Widgets/AutoComplete/AutoCompleter.cs @@ -165,10 +165,8 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { var mainRect = uiRoot.GetComponent(); mainRect.pivot = new Vector2(0f, 1f); - mainRect.anchorMin = new Vector2(0, 1); - mainRect.anchorMax = new Vector2(0, 1); - mainRect.offsetMin = new Vector2(25, 0); - mainRect.offsetMax = new Vector2(525, 169); + mainRect.anchorMin = new Vector2(0.42f, 0.4f); + mainRect.anchorMax = new Vector2(0.68f, 0.6f); } public override void ConstructPanelContent() diff --git a/src/UI/Widgets/CacheObject/CacheConfigEntry.cs b/src/UI/Widgets/CacheObject/CacheConfigEntry.cs new file mode 100644 index 0000000..b77168e --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheConfigEntry.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.UI.InteractiveValues; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheConfigEntry : CacheObjectBase + { + public IConfigElement RefConfig { get; } + + public override Type FallbackType => RefConfig.ElementType; + + public override bool HasEvaluated => true; + public override bool HasParameters => false; + public override bool IsMember => false; + public override bool CanWrite => true; + + public CacheConfigEntry(IConfigElement config, GameObject parent) + { + RefConfig = config; + + m_parentContent = parent; + + config.OnValueChangedNotify += () => { UpdateValue(); }; + + CreateIValue(config.BoxedValue, config.ElementType); + } + + public override void CreateIValue(object value, Type fallbackType) + { + IValue = InteractiveValue.Create(value, fallbackType); + IValue.Owner = this; + IValue.m_mainContentParent = m_mainGroup; + IValue.m_subContentParent = this.SubContentGroup; + } + + public override void UpdateValue() + { + IValue.Value = RefConfig.BoxedValue; + + base.UpdateValue(); + } + + public override void SetValue() + { + RefConfig.BoxedValue = IValue.Value; + } + + internal GameObject m_mainGroup; + + internal override void ConstructUI() + { + base.ConstructUI(); + + m_mainGroup = UIFactory.CreateVerticalGroup(UIRoot, "ConfigHolder", true, false, true, true, 5, new Vector4(2, 2, 2, 2)); + + var horiGroup = UIFactory.CreateHorizontalGroup(m_mainGroup, "ConfigEntryHolder", false, false, true, true, childAlignment: TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(horiGroup, minHeight: 30, flexibleHeight: 0); + + // config entry label + + var configLabel = UIFactory.CreateLabel(horiGroup, "ConfigLabel", this.RefConfig.Name, TextAnchor.MiddleLeft); + var leftRect = configLabel.GetComponent(); + leftRect.anchorMin = Vector2.zero; + leftRect.anchorMax = Vector2.one; + leftRect.offsetMin = Vector2.zero; + leftRect.offsetMax = Vector2.zero; + leftRect.sizeDelta = Vector2.zero; + UIFactory.SetLayoutElement(configLabel.gameObject, minWidth: 250, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + + // Default button + + var defaultButton = UIFactory.CreateButton(horiGroup, + "RevertDefaultButton", + "Default", + () => { RefConfig.RevertToDefaultValue(); }, + new Color(0.3f, 0.3f, 0.3f)); + UIFactory.SetLayoutElement(defaultButton.gameObject, minWidth: 80, minHeight: 22, flexibleWidth: 0); + + // Description label + + var desc = UIFactory.CreateLabel(m_mainGroup, "Description", $"{RefConfig.Description}", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(desc.gameObject, minWidth: 250, minHeight: 20, flexibleWidth: 9999, flexibleHeight: 0); + + // IValue + + if (IValue != null) + { + IValue.m_mainContentParent = m_mainGroup; + IValue.m_subContentParent = this.SubContentGroup; + } + + // makes the subcontent look nicer + SubContentGroup.transform.SetParent(m_mainGroup.transform, false); + } + } +} diff --git a/src/UI/Widgets/CacheObject/CacheEnumerated.cs b/src/UI/Widgets/CacheObject/CacheEnumerated.cs new file mode 100644 index 0000000..6d360fd --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheEnumerated.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.UI; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.InteractiveValues; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheEnumerated : CacheObjectBase + { + public override Type FallbackType => ParentEnumeration.m_baseEntryType; + public override bool CanWrite => RefIList != null && ParentEnumeration.Owner.CanWrite; + + public int Index { get; set; } + public IList RefIList { get; set; } + public InteractiveEnumerable ParentEnumeration { get; set; } + + public CacheEnumerated(int index, InteractiveEnumerable parentEnumeration, IList refIList, GameObject parentContent) + { + this.ParentEnumeration = parentEnumeration; + this.Index = index; + this.RefIList = refIList; + this.m_parentContent = parentContent; + } + + public override void CreateIValue(object value, Type fallbackType) + { + IValue = InteractiveValue.Create(value, fallbackType); + IValue.Owner = this; + } + + public override void SetValue() + { + RefIList[Index] = IValue.Value; + ParentEnumeration.Value = RefIList; + + ParentEnumeration.Owner.SetValue(); + } + + internal override void ConstructUI() + { + base.ConstructUI(); + + var rowObj = UIFactory.CreateHorizontalGroup(UIRoot, "CacheEnumeratedGroup", false, true, true, true, 0, new Vector4(0,0,5,2), + new Color(1, 1, 1, 0)); + + var indexLabel = UIFactory.CreateLabel(rowObj, "IndexLabel", $"{this.Index}:", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(indexLabel.gameObject, minWidth: 20, flexibleWidth: 30, minHeight: 25); + + IValue.m_mainContentParent = rowObj; + } + } +} diff --git a/src/UI/Widgets/CacheObject/CacheField.cs b/src/UI/Widgets/CacheObject/CacheField.cs new file mode 100644 index 0000000..d3f5160 --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheField.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityExplorer.UI; +using UnityEngine; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheField : CacheMember + { + public override bool IsStatic => (MemInfo as FieldInfo).IsStatic; + + public override Type FallbackType => (MemInfo as FieldInfo).FieldType; + + public CacheField(FieldInfo fieldInfo, object declaringInstance, GameObject parent) : base(fieldInfo, declaringInstance, parent) + { + CreateIValue(null, fieldInfo.FieldType); + } + + public override void UpdateReflection() + { + var fi = MemInfo as FieldInfo; + IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance); + + m_evaluated = true; + ReflectionException = null; + } + + public override void SetValue() + { + var fi = MemInfo as FieldInfo; + fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value); + + if (this.ParentInspector?.ParentMember != null) + this.ParentInspector.ParentMember.SetValue(); + } + } +} diff --git a/src/UI/Widgets/CacheObject/CacheMember.cs b/src/UI/Widgets/CacheObject/CacheMember.cs new file mode 100644 index 0000000..2e60d55 --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheMember.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.Core.Runtime; +using UnityExplorer.Core; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.InteractiveValues; +using UnityExplorer.UI.Inspectors.Reflection; +using UnityExplorer.UI.Panels; + +namespace UnityExplorer.UI.CacheObject +{ + public abstract class CacheMember : CacheObjectBase + { + public override bool IsMember => true; + + public override Type FallbackType { get; } + + public ReflectionInspector ParentInspector { get; set; } + public MemberInfo MemInfo { get; set; } + public Type DeclaringType { get; set; } + public object DeclaringInstance { get; set; } + public virtual bool IsStatic { get; private set; } + + public string ReflectionException { get; set; } + + public override bool CanWrite => m_canWrite ?? GetCanWrite(); + private bool? m_canWrite; + + public override bool HasParameters => ParamCount > 0; + public virtual int ParamCount => m_arguments.Length; + public override bool HasEvaluated => m_evaluated; + public bool m_evaluated = false; + public bool m_isEvaluating; + public ParameterInfo[] m_arguments = new ParameterInfo[0]; + public string[] m_argumentInput = new string[0]; + + public string NameForFiltering => m_nameForFilter ?? (m_nameForFilter = $"{MemInfo.DeclaringType.Name}.{MemInfo.Name}".ToLower()); + private string m_nameForFilter; + + public string RichTextName => m_richTextName ?? GetRichTextName(); + private string m_richTextName; + + public CacheMember(MemberInfo memberInfo, object declaringInstance, GameObject parentContent) + { + MemInfo = memberInfo; + DeclaringType = memberInfo.DeclaringType; + DeclaringInstance = declaringInstance; + this.m_parentContent = parentContent; + + DeclaringInstance = ReflectionProvider.Instance.Cast(declaringInstance, DeclaringType); + } + + public override void Enable() + { + base.Enable(); + + ParentInspector.displayedMembers.Add(this); + + memberLabelElement.minWidth = 0.4f * InspectorPanel.CurrentPanelWidth; + } + + public override void Disable() + { + base.Disable(); + + ParentInspector.displayedMembers.Remove(this); + } + + public static bool CanProcessArgs(ParameterInfo[] parameters) + { + foreach (var param in parameters) + { + var pType = param.ParameterType; + + if (pType.IsByRef && pType.HasElementType) + pType = pType.GetElementType(); + + if (pType != null && (pType.IsPrimitive || pType == typeof(string))) + continue; + else + return false; + } + return true; + } + + public override void CreateIValue(object value, Type fallbackType) + { + IValue = InteractiveValue.Create(value, fallbackType); + IValue.Owner = this; + IValue.m_mainContentParent = this.ContentGroup; + IValue.m_subContentParent = this.SubContentGroup; + } + + public override void UpdateValue() + { + if (!HasParameters || m_isEvaluating) + { + try + { + Type baseType = ReflectionUtility.GetActualType(IValue.Value) ?? FallbackType; + + if (!ReflectionProvider.Instance.IsReflectionSupported(baseType)) + throw new Exception("Type not supported with reflection"); + + UpdateReflection(); + + if (IValue.Value != null) + IValue.Value = IValue.Value.TryCast(ReflectionUtility.GetActualType(IValue.Value)); + } + catch (Exception e) + { + ReflectionException = e.ReflectionExToString(true); + } + } + + base.UpdateValue(); + } + + public abstract void UpdateReflection(); + + public override void SetValue() + { + // no implementation for base class + } + + public object[] ParseArguments() + { + if (m_arguments.Length < 1) + return new object[0]; + + var parsedArgs = new List(); + for (int i = 0; i < m_arguments.Length; i++) + { + var input = m_argumentInput[i]; + var type = m_arguments[i].ParameterType; + + if (type.IsByRef) + type = type.GetElementType(); + + if (!string.IsNullOrEmpty(input)) + { + if (type == typeof(string)) + { + parsedArgs.Add(input); + continue; + } + else + { + try + { + var arg = type.GetMethod("Parse", new Type[] { typeof(string) }) + .Invoke(null, new object[] { input }); + + parsedArgs.Add(arg); + continue; + } + catch + { + ExplorerCore.Log($"Could not parse input '{input}' for argument #{i} '{m_arguments[i].Name}' ({type.FullName})"); + } + } + } + + // No input, see if there is a default value. + if (m_arguments[i].IsOptional) + { + parsedArgs.Add(m_arguments[i].DefaultValue); + continue; + } + + // Try add a null arg I guess + parsedArgs.Add(null); + } + + return parsedArgs.ToArray(); + } + + private bool GetCanWrite() + { + if (MemInfo is FieldInfo fi) + m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly); + else if (MemInfo is PropertyInfo pi) + m_canWrite = pi.CanWrite; + else + m_canWrite = false; + + return (bool)m_canWrite; + } + + private string GetRichTextName() + { + return m_richTextName = SignatureHighlighter.ParseFullSyntax(MemInfo.DeclaringType, false, MemInfo); + } + + #region UI + + internal Text memberLabelText; + internal GameObject ContentGroup; + + internal LayoutElement memberLabelElement; + + internal override void ConstructUI() + { + base.ConstructUI(); + + var horiGroup = UIFactory.CreateUIObject("HoriGroup", UIRoot); + var groupRect = horiGroup.GetComponent(); + groupRect.pivot = new Vector2(0, 1); + groupRect.anchorMin = Vector2.zero; + groupRect.anchorMax = Vector2.one; + UIFactory.SetLayoutElement(horiGroup, minHeight: 30, flexibleHeight: 9999); + UIFactory.SetLayoutGroup(horiGroup, true, true, true, true, 2, 2, 2, 2, 2, childAlignment: TextAnchor.UpperLeft); + + memberLabelText = UIFactory.CreateLabel(horiGroup, "MemLabelText", RichTextName, TextAnchor.UpperLeft); + memberLabelText.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(memberLabelText.gameObject, minHeight: 25, flexibleHeight: 9999, minWidth: 150, flexibleWidth: 0); + + memberLabelElement = memberLabelText.GetComponent(); + + ContentGroup = UIFactory.CreateUIObject("ContentGroup", horiGroup, default); + UIFactory.SetLayoutElement(ContentGroup, minHeight: 30, flexibleWidth: 9999); + var contentRect = ContentGroup.GetComponent(); + contentRect.pivot = new Vector2(0, 1); + contentRect.anchorMin = new Vector2(0, 0); + contentRect.anchorMax = new Vector2(1, 1); + UIFactory.SetLayoutGroup(ContentGroup, false, false, true, true, childAlignment: TextAnchor.MiddleLeft); + + ConstructArgInput(out GameObject argsHolder); + + ConstructEvaluateButtons(argsHolder); + + IValue.m_mainContentParent = this.ContentGroup; + + //RightContentGroup.SetActive(false); + + // ParentInspector.CacheObjectContents.Add(this.m_mainContent); + } + + internal void ConstructArgInput(out GameObject argsHolder) + { + argsHolder = null; + + if (HasParameters) + { + argsHolder = UIFactory.CreateVerticalGroup(ContentGroup, "ArgsHolder", true, false, true, true, 4, new Color(1, 1, 1, 0)); + + if (this is CacheMethod cm && cm.GenericArgs.Length > 0) + cm.ConstructGenericArgInput(argsHolder); + + if (m_arguments.Length > 0) + { + UIFactory.CreateLabel(argsHolder, "ArgumentsLabel", "Arguments:", TextAnchor.MiddleLeft); + + for (int i = 0; i < m_arguments.Length; i++) + AddArgRow(i, argsHolder); + } + + argsHolder.SetActive(false); + } + } + + internal void AddArgRow(int i, GameObject parent) + { + var arg = m_arguments[i]; + + var rowObj = UIFactory.CreateHorizontalGroup(parent, "ArgRow", true, false, true, true, 4, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000); + + var argTypeTxt = SignatureHighlighter.ParseFullSyntax(arg.ParameterType, false); + var argLabel = UIFactory.CreateLabel(rowObj, "ArgLabel", $"{argTypeTxt} {arg.Name}", + TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(argLabel.gameObject, minHeight: 25); + + var argInputObj = UIFactory.CreateInputField(rowObj, "ArgInput", "...", out InputField argInput, 14, (int)TextAnchor.MiddleLeft, 1); + UIFactory.SetLayoutElement(argInputObj, flexibleWidth: 1200, preferredWidth: 150, minWidth: 20, minHeight: 25, flexibleHeight: 0); + + argInput.onValueChanged.AddListener((string val) => { m_argumentInput[i] = val; }); + + if (arg.IsOptional) + { + var phInput = argInput.placeholder.GetComponent(); + phInput.text = " = " + arg.DefaultValue?.ToString() ?? "null"; + } + } + + internal void ConstructEvaluateButtons(GameObject argsHolder) + { + if (HasParameters) + { + var evalGroupObj = UIFactory.CreateHorizontalGroup(ContentGroup, "EvalGroup", false, false, true, true, 5, + default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(evalGroupObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000); + + var evalButton = UIFactory.CreateButton(evalGroupObj, + "EvalButton", + $"Evaluate ({ParamCount})", + null); + + RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f), + new Color(0.4f, 0.7f, 0.4f), new Color(0.3f, 0.3f, 0.3f)); + + UIFactory.SetLayoutElement(evalButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0); + + var evalText = evalButton.GetComponentInChildren(); + + var cancelButton = UIFactory.CreateButton(evalGroupObj, "CancelButton", "Close", null, new Color(0.3f, 0.3f, 0.3f)); + UIFactory.SetLayoutElement(cancelButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0); + + cancelButton.gameObject.SetActive(false); + + evalButton.onClick.AddListener(() => + { + if (!m_isEvaluating) + { + argsHolder.SetActive(true); + m_isEvaluating = true; + evalText.text = "Evaluate"; + RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.3f, 0.6f, 0.3f)); + + cancelButton.gameObject.SetActive(true); + } + else + { + if (this is CacheMethod cm) + cm.Evaluate(); + else + UpdateValue(); + } + }); + + cancelButton.onClick.AddListener(() => + { + cancelButton.gameObject.SetActive(false); + argsHolder.SetActive(false); + m_isEvaluating = false; + + evalText.text = $"Evaluate ({ParamCount})"; + RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f)); + }); + } + else if (this is CacheMethod) + { + // simple method evaluate button + + var evalButton = UIFactory.CreateButton(ContentGroup, "EvalButton", "Evaluate", () => { (this as CacheMethod).Evaluate(); }); + RuntimeProvider.Instance.SetColorBlock(evalButton, new Color(0.4f, 0.4f, 0.4f), + new Color(0.4f, 0.7f, 0.4f), new Color(0.3f, 0.3f, 0.3f)); + + UIFactory.SetLayoutElement(evalButton.gameObject, minWidth: 100, minHeight: 22, flexibleWidth: 0); + } + } + + #endregion + } +} diff --git a/src/UI/Widgets/CacheObject/CacheMethod.cs b/src/UI/Widgets/CacheObject/CacheMethod.cs new file mode 100644 index 0000000..e947c62 --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheMethod.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.Core; +using UnityExplorer.UI.Utility; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheMethod : CacheMember + { + //private CacheObjectBase m_cachedReturnValue; + + public override Type FallbackType => (MemInfo as MethodInfo).ReturnType; + + public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0; + + public override bool IsStatic => (MemInfo as MethodInfo).IsStatic; + + public override int ParamCount => base.ParamCount + m_genericArgInput.Length; + + public Type[] GenericArgs { get; private set; } + public Type[][] GenericConstraints { get; private set; } + + public string[] m_genericArgInput = new string[0]; + + public CacheMethod(MethodInfo methodInfo, object declaringInstance, GameObject parent) : base(methodInfo, declaringInstance, parent) + { + GenericArgs = methodInfo.GetGenericArguments(); + + GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints()) + .Where(x => x != null) + .ToArray(); + + m_genericArgInput = new string[GenericArgs.Length]; + + m_arguments = methodInfo.GetParameters(); + m_argumentInput = new string[m_arguments.Length]; + + CreateIValue(null, methodInfo.ReturnType); + } + + public override void UpdateReflection() + { + // CacheMethod cannot UpdateValue directly. Need to Evaluate. + } + + public void Evaluate() + { + MethodInfo mi; + if (GenericArgs.Length > 0) + { + mi = MakeGenericMethodFromInput(); + if (mi == null) return; + } + else + { + mi = MemInfo as MethodInfo; + } + + object ret = null; + + try + { + ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments()); + m_evaluated = true; + m_isEvaluating = false; + ReflectionException = null; + } + catch (Exception e) + { + while (e.InnerException != null) + e = e.InnerException; + + ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}"); + ReflectionException = ReflectionUtility.ReflectionExToString(e); + } + + IValue.Value = ret; + UpdateValue(); + } + + private MethodInfo MakeGenericMethodFromInput() + { + var mi = MemInfo as MethodInfo; + + var list = new List(); + for (int i = 0; i < GenericArgs.Length; i++) + { + var input = m_genericArgInput[i]; + if (ReflectionUtility.GetTypeByName(input) is Type t) + { + if (GenericConstraints[i].Length == 0) + { + list.Add(t); + } + else + { + foreach (var constraint in GenericConstraints[i].Where(x => x != null)) + { + if (!constraint.IsAssignableFrom(t)) + { + ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!"); + return null; + } + } + + list.Add(t); + } + } + else + { + ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" + + $" Make sure you use the full name including the namespace."); + return null; + } + } + + // make into a generic with type list + mi = mi.MakeGenericMethod(list.ToArray()); + + return mi; + } + + #region UI CONSTRUCTION + + internal void ConstructGenericArgInput(GameObject parent) + { + UIFactory.CreateLabel(parent, "GenericArgLabel", "Generic Arguments:", TextAnchor.MiddleLeft); + + for (int i = 0; i < GenericArgs.Length; i++) + AddGenericArgRow(i, parent); + } + + internal void AddGenericArgRow(int i, GameObject parent) + { + var arg = GenericArgs[i]; + + string constrainTxt = ""; + if (this.GenericConstraints[i].Length > 0) + { + foreach (var constraint in this.GenericConstraints[i]) + { + if (constrainTxt != "") + constrainTxt += ", "; + + constrainTxt += $"{SignatureHighlighter.ParseFullSyntax(constraint, false)}"; + } + } + else + constrainTxt = $"Any"; + + var rowObj = UIFactory.CreateHorizontalGroup(parent, "ArgRowObj", false, true, true, true, 4, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000); + + var argLabelObj = UIFactory.CreateLabel(rowObj, "ArgLabelObj", $"{constrainTxt} {arg.Name}", + TextAnchor.MiddleLeft); + + var argInputObj = UIFactory.CreateInputField(rowObj, "ArgInput", "...", out InputField argInput, 14, (int)TextAnchor.MiddleLeft, 1); + UIFactory.SetLayoutElement(argInputObj, flexibleWidth: 1200); + argInput.onValueChanged.AddListener((string val) => { m_genericArgInput[i] = val; }); + + } + + #endregion + } +} diff --git a/src/UI/Widgets/CacheObject/CacheObjectBase.cs b/src/UI/Widgets/CacheObject/CacheObjectBase.cs new file mode 100644 index 0000000..f0c1ecb --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheObjectBase.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityExplorer.UI; +using UnityEngine.UI; +using UnityExplorer.Core; +using UnityExplorer.UI.InteractiveValues; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.CacheObject +{ + public abstract class CacheObjectBase + { + public InteractiveValue IValue; + + public virtual bool CanWrite => false; + public virtual bool HasParameters => false; + public virtual bool IsMember => false; + public virtual bool HasEvaluated => true; + + public abstract Type FallbackType { get; } + + public abstract void CreateIValue(object value, Type fallbackType); + + public virtual void Enable() + { + if (!m_constructedUI) + { + ConstructUI(); + UpdateValue(); + } + + //if (!m_mainContent.activeSelf) + // m_mainContent.SetActive(true); + } + + public virtual void Disable() + { + if (UIRoot) + UIRoot.SetActive(false); + } + + public void Destroy() + { + if (this.UIRoot) + GameObject.Destroy(this.UIRoot); + } + + public virtual void UpdateValue() + { + var value = IValue.Value; + + // if the type has changed fundamentally, make a new interactivevalue for it + var type = value == null + ? FallbackType + : ReflectionUtility.GetActualType(value); + + var ivalueType = InteractiveValue.GetIValueForType(type); + + if (ivalueType != IValue.GetType()) + { + IValue.OnDestroy(); + CreateIValue(value, FallbackType); + SubContentGroup.SetActive(false); + } + + IValue.OnValueUpdated(); + + IValue.RefreshElementsAfterUpdate(); + } + + public virtual void SetValue() => throw new NotImplementedException(); + + #region UI CONSTRUCTION + + internal bool m_constructedUI; + internal GameObject m_parentContent; + internal RectTransform m_mainRect; + internal GameObject UIRoot; + internal GameObject SubContentGroup; + + // Make base UI holder for CacheObject, this doesnt actually display anything. + internal virtual void ConstructUI() + { + m_constructedUI = true; + + UIRoot = UIFactory.CreateVerticalGroup(m_parentContent, $"{this.GetType().Name}.MainContent", true, true, true, true, 2, + new Vector4(0, 5, 0, 0), new Color(0.1f, 0.1f, 0.1f), TextAnchor.UpperLeft); + m_mainRect = UIRoot.GetComponent(); + m_mainRect.pivot = new Vector2(0, 1); + m_mainRect.anchorMin = Vector2.zero; + m_mainRect.anchorMax = Vector2.one; + UIFactory.SetLayoutElement(UIRoot, minHeight: 30, flexibleHeight: 9999, minWidth: 200, flexibleWidth: 5000); + //UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + // subcontent + + SubContentGroup = UIFactory.CreateVerticalGroup(UIRoot, $"{this.GetType().Name}.SubContent", true, false, true, true, 0, default, + new Color(0.085f, 0.085f, 0.085f)); + UIFactory.SetLayoutElement(SubContentGroup, minHeight: 30, flexibleHeight: 9999, minWidth: 125, flexibleWidth: 9000); + + SubContentGroup.SetActive(false); + + IValue.m_subContentParent = SubContentGroup; + } + + #endregion + + } +} diff --git a/src/UI/Widgets/CacheObject/CachePaired.cs b/src/UI/Widgets/CacheObject/CachePaired.cs new file mode 100644 index 0000000..2c8f7ef --- /dev/null +++ b/src/UI/Widgets/CacheObject/CachePaired.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.UI; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.InteractiveValues; + +namespace UnityExplorer.UI.CacheObject +{ + public enum PairTypes + { + Key, + Value + } + + public class CachePaired : CacheObjectBase + { + public override Type FallbackType => PairType == PairTypes.Key + ? ParentDictionary.m_typeOfKeys + : ParentDictionary.m_typeofValues; + + public override bool CanWrite => false; // todo? + + public PairTypes PairType; + public int Index { get; private set; } + public InteractiveDictionary ParentDictionary { get; private set; } + internal IDictionary RefIDict; + + public CachePaired(int index, InteractiveDictionary parentDict, IDictionary refIDict, PairTypes pairType, GameObject parentContent) + { + Index = index; + ParentDictionary = parentDict; + RefIDict = refIDict; + this.PairType = pairType; + this.m_parentContent = parentContent; + } + + public override void CreateIValue(object value, Type fallbackType) + { + IValue = InteractiveValue.Create(value, fallbackType); + IValue.Owner = this; + } + + #region UI CONSTRUCTION + + internal override void ConstructUI() + { + base.ConstructUI(); + + Color bgColor = this.PairType == PairTypes.Key + ? new Color(0.07f, 0.07f, 0.07f) + : new Color(0.1f, 0.1f, 0.1f); + + var rowObj = UIFactory.CreateHorizontalGroup(UIRoot, "PairedGroup", false, false, true, true, 0, new Vector4(0,0,5,2), + bgColor); + + string lbl = $"{this.PairType}"; + if (this.PairType == PairTypes.Key) + lbl = $"[{Index}] {lbl}"; + + var indexLabel = UIFactory.CreateLabel(rowObj, "IndexLabel", lbl, TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(indexLabel.gameObject, minWidth: 80, flexibleWidth: 30, minHeight: 25); + + IValue.m_mainContentParent = rowObj; + } + + #endregion + } +} diff --git a/src/UI/Widgets/CacheObject/CacheProperty.cs b/src/UI/Widgets/CacheObject/CacheProperty.cs new file mode 100644 index 0000000..c7daefb --- /dev/null +++ b/src/UI/Widgets/CacheObject/CacheProperty.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityExplorer.UI; +using UnityEngine; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheProperty : CacheMember + { + public override Type FallbackType => (MemInfo as PropertyInfo).PropertyType; + + public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors(true)[0].IsStatic; + + public CacheProperty(PropertyInfo propertyInfo, object declaringInstance, GameObject parent) : base(propertyInfo, declaringInstance, parent) + { + this.m_arguments = propertyInfo.GetIndexParameters(); + this.m_argumentInput = new string[m_arguments.Length]; + + CreateIValue(null, propertyInfo.PropertyType); + } + + public override void UpdateReflection() + { + if (HasParameters && !m_isEvaluating) + { + // Need to enter parameters first. + return; + } + + var pi = MemInfo as PropertyInfo; + + if (pi.CanRead) + { + var target = pi.GetAccessors(true)[0].IsStatic ? null : DeclaringInstance; + + IValue.Value = pi.GetValue(target, ParseArguments()); + + m_evaluated = true; + ReflectionException = null; + } + else + { + if (FallbackType == typeof(string)) + { + IValue.Value = ""; + } + else if (FallbackType.IsPrimitive) + { + IValue.Value = Activator.CreateInstance(FallbackType); + } + m_evaluated = true; + ReflectionException = null; + } + } + + public override void SetValue() + { + var pi = MemInfo as PropertyInfo; + var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance; + + pi.SetValue(target, IValue.Value, ParseArguments()); + + if (this.ParentInspector?.ParentMember != null) + this.ParentInspector.ParentMember.SetValue(); + } + } +} diff --git a/src/UI/Widgets/InputFieldScroller.cs b/src/UI/Widgets/InputFieldScroller.cs index 3c1e62c..16db052 100644 --- a/src/UI/Widgets/InputFieldScroller.cs +++ b/src/UI/Widgets/InputFieldScroller.cs @@ -15,25 +15,15 @@ namespace UnityExplorer.UI.Utility public class InputFieldScroller : UIBehaviourModel { - //public static readonly List Instances = new List(); - - //public static void UpdateInstances() - //{ - // if (!Instances.Any()) - // return; - - // for (int i = 0; i < Instances.Count; i++) - // { - // var input = Instances[i]; - - // if (input.CheckDestroyed()) - // i--; - // else - // input.Update(); - // } - //} - - public override GameObject UIRoot => inputField.gameObject; + public override GameObject UIRoot + { + get + { + if (inputField) + return inputField.gameObject; + return null; + } + } internal SliderScrollbar sliderScroller; internal InputField inputField; diff --git a/src/UI/Widgets/InteractiveValues/InteractiveBool.cs b/src/UI/Widgets/InteractiveValues/InteractiveBool.cs new file mode 100644 index 0000000..79d2549 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveBool.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.UI.CacheObject; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveBool : InteractiveValue + { + public InteractiveBool(object value, Type valueType) : base(value, valueType) { } + + public override bool HasSubContent => false; + public override bool SubContentWanted => false; + public override bool WantInspectBtn => false; + + internal Toggle m_toggle; + internal Button m_applyBtn; + + public override void OnValueUpdated() + { + base.OnValueUpdated(); + } + + public override void RefreshUIForValue() + { + GetDefaultLabel(); + + if (Owner.HasEvaluated) + { + var val = (bool)Value; + + if (!m_toggle.gameObject.activeSelf) + m_toggle.gameObject.SetActive(true); + + if (val != m_toggle.isOn) + m_toggle.isOn = val; + + if (Owner.CanWrite) + { + if (!m_applyBtn.gameObject.activeSelf) + m_applyBtn.gameObject.SetActive(true); + } + + var color = val + ? "6bc981" // on + : "c96b6b"; // off + + m_baseLabel.text = $"{val}"; + } + else + { + m_baseLabel.text = DefaultLabel; + } + } + + public override void OnException(CacheMember member) + { + base.OnException(member); + + if (Owner.CanWrite) + { + if (m_toggle.gameObject.activeSelf) + m_toggle.gameObject.SetActive(false); + + if (m_applyBtn.gameObject.activeSelf) + m_applyBtn.gameObject.SetActive(false); + } + } + + internal void OnToggleValueChanged(bool val) + { + Value = val; + RefreshUIForValue(); + } + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + + var baseLayout = m_baseLabel.gameObject.GetComponent(); + baseLayout.flexibleWidth = 0; + baseLayout.minWidth = 50; + + var toggleObj = UIFactory.CreateToggle(m_mainContent, "InteractiveBoolToggle", out m_toggle, out _, new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(toggleObj, minWidth: 24); + m_toggle.onValueChanged.AddListener(OnToggleValueChanged); + + m_baseLabel.transform.SetAsLastSibling(); + + m_applyBtn = UIFactory.CreateButton(m_mainContent, + "ApplyButton", + "Apply", + () => { Owner.SetValue(); }, + new Color(0.2f, 0.2f, 0.2f)); + + UIFactory.SetLayoutElement(m_applyBtn.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0); + + toggleObj.SetActive(false); + + if (!Owner.CanWrite) + m_toggle.interactable = false; + + m_applyBtn.gameObject.SetActive(false); + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveColor.cs b/src/UI/Widgets/InteractiveValues/InteractiveColor.cs new file mode 100644 index 0000000..5854b95 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveColor.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveColor : InteractiveValue + { + //~~~~~~~~~ Instance ~~~~~~~~~~ + + public InteractiveColor(object value, Type valueType) : base(value, valueType) { } + + public override bool HasSubContent => true; + public override bool SubContentWanted => true; + public override bool WantInspectBtn => true; + + public override void RefreshUIForValue() + { + base.RefreshUIForValue(); + + if (m_subContentConstructed) + RefreshUI(); + } + + private void RefreshUI() + { + var color = (Color)this.Value; + + m_inputs[0].text = color.r.ToString(); + m_inputs[1].text = color.g.ToString(); + m_inputs[2].text = color.b.ToString(); + m_inputs[3].text = color.a.ToString(); + + if (m_colorImage) + m_colorImage.color = color; + } + + internal override void OnToggleSubcontent(bool toggle) + { + base.OnToggleSubcontent(toggle); + + RefreshUI(); + } + + #region UI CONSTRUCTION + + private Image m_colorImage; + + private readonly InputField[] m_inputs = new InputField[4]; + private readonly Slider[] m_sliders = new Slider[4]; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + + //// Limit the label width for colors, they're always about the same so make use of that space. + //UIFactory.SetLayoutElement(this.m_baseLabel.gameObject, flexibleWidth: 0, minWidth: 250); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + var horiGroup = UIFactory.CreateHorizontalGroup(m_subContentParent, "ColorEditor", false, false, true, true, 5, + default, default, TextAnchor.MiddleLeft); + + var editorContainer = UIFactory.CreateVerticalGroup(horiGroup, "EditorContent", false, true, true, true, 2, new Vector4(4, 4, 4, 4), + new Color(0.08f, 0.08f, 0.08f)); + UIFactory.SetLayoutElement(editorContainer, minWidth: 300, flexibleWidth: 0); + + for (int i = 0; i < 4; i++) + AddEditorRow(i, editorContainer); + + if (Owner.CanWrite) + { + var applyBtn = UIFactory.CreateButton(editorContainer, "ApplyButton", "Apply", OnSetValue, new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(applyBtn.gameObject, minWidth: 175, minHeight: 25, flexibleWidth: 0); + + void OnSetValue() + { + Owner.SetValue(); + RefreshUIForValue(); + } + } + + var imgHolder = UIFactory.CreateVerticalGroup(horiGroup, "ImgHolder", true, true, true, true, 0, new Vector4(1, 1, 1, 1), + new Color(0.08f, 0.08f, 0.08f)); + UIFactory.SetLayoutElement(imgHolder, minWidth: 128, minHeight: 128, flexibleWidth: 0, flexibleHeight: 0); + + var imgObj = UIFactory.CreateUIObject("ColorImageHelper", imgHolder, new Vector2(100, 25)); + m_colorImage = imgObj.AddComponent(); + m_colorImage.color = (Color)this.Value; + } + + private static readonly string[] s_fieldNames = new[] { "R", "G", "B", "A" }; + + internal void AddEditorRow(int index, GameObject groupObj) + { + var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow_" + s_fieldNames[index], + false, true, true, true, 5, default, new Color(1, 1, 1, 0)); + + var label = UIFactory.CreateLabel(row, "RowLabel", $"{s_fieldNames[index]}:", TextAnchor.MiddleRight, Color.cyan); + UIFactory.SetLayoutElement(label.gameObject, minWidth: 50, flexibleWidth: 0, minHeight: 25); + + var inputFieldObj = UIFactory.CreateInputField(row, "InputField", "...", out InputField inputField, 14, 3, 1); + UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0); + + m_inputs[index] = inputField; + inputField.characterValidation = InputField.CharacterValidation.Decimal; + + inputField.onValueChanged.AddListener((string value) => + { + float val = float.Parse(value); + SetValueToColor(val); + m_sliders[index].value = val; + }); + + var sliderObj = UIFactory.CreateSlider(row, "Slider", out Slider slider); + m_sliders[index] = slider; + UIFactory.SetLayoutElement(sliderObj, minWidth: 200, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + slider.minValue = 0; + slider.maxValue = 1; + slider.value = GetValueFromColor(); + + slider.onValueChanged.AddListener((float value) => + { + inputField.text = value.ToString(); + SetValueToColor(value); + m_inputs[index].text = value.ToString(); + }); + + // methods for writing to the color for this field + + void SetValueToColor(float floatValue) + { + Color _color = (Color)Value; + switch (index) + { + case 0: _color.r = floatValue; break; + case 1: _color.g = floatValue; break; + case 2: _color.b = floatValue; break; + case 3: _color.a = floatValue; break; + } + Value = _color; + m_colorImage.color = _color; + } + + float GetValueFromColor() + { + Color _color = (Color)Value; + switch (index) + { + case 0: return _color.r; + case 1: return _color.g; + case 2: return _color.b; + case 3: return _color.a; + default: throw new NotImplementedException(); + } + } + } + + #endregion + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveDictionary.cs b/src/UI/Widgets/InteractiveValues/InteractiveDictionary.cs new file mode 100644 index 0000000..2c838f9 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveDictionary.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.UI; +using System.Reflection; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.Core; +using UnityExplorer.UI.Utility; +#if CPP +using AltIDictionary = Il2CppSystem.Collections.IDictionary; +#else +using AltIDictionary = System.Collections.IDictionary; +#endif + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveDictionary : InteractiveValue + { + public InteractiveDictionary(object value, Type valueType) : base(value, valueType) + { + if (valueType.IsGenericType) + { + var gArgs = valueType.GetGenericArguments(); + m_typeOfKeys = gArgs[0]; + m_typeofValues = gArgs[1]; + } + else + { + m_typeOfKeys = typeof(object); + m_typeofValues = typeof(object); + } + } + + public override bool WantInspectBtn => false; + public override bool HasSubContent => true; + public override bool SubContentWanted + { + get + { + if (m_recacheWanted) + return true; + else return m_entries.Count > 0; + } + } + + internal IDictionary RefIDictionary; + internal AltIDictionary RefAltIDictionary; + internal Type m_typeOfKeys; + internal Type m_typeofValues; + + internal readonly List> m_entries + = new List>(); + + internal readonly KeyValuePair[] m_displayedEntries + = new KeyValuePair[ConfigManager.Default_Page_Limit.Value]; + + internal bool m_recacheWanted = true; + + public override void OnDestroy() + { + base.OnDestroy(); + } + + public override void OnValueUpdated() + { + RefIDictionary = Value as IDictionary; + + if (RefIDictionary == null) + { + try { RefAltIDictionary = Value.TryCast(); } + catch { } + } + + if (m_subContentParent.activeSelf) + { + GetCacheEntries(); + RefreshDisplay(); + } + else + m_recacheWanted = true; + + base.OnValueUpdated(); + } + + internal void OnPageTurned() + { + RefreshDisplay(); + } + + public override void RefreshUIForValue() + { + GetDefaultLabel(); + + if (Value != null) + { + string count = "?"; + if (m_recacheWanted && RefIDictionary != null) + count = RefIDictionary.Count.ToString(); + else if (!m_recacheWanted) + count = m_entries.Count.ToString(); + + m_baseLabel.text = $"[{count}] {m_richValueType}"; + } + else + { + m_baseLabel.text = DefaultLabel; + } + } + + public void GetCacheEntries() + { + if (m_entries.Any()) + { + // maybe improve this, probably could be more efficient i guess + + foreach (var pair in m_entries) + { + pair.Key.Destroy(); + pair.Value.Destroy(); + } + + m_entries.Clear(); + } + + if (RefIDictionary == null && Value != null) + RefIDictionary = RuntimeProvider.Instance.Reflection.EnumerateDictionary(Value, m_typeOfKeys, m_typeofValues); + + if (RefIDictionary != null) + { + int index = 0; + + foreach (var key in RefIDictionary.Keys) + { + var value = RefIDictionary[key]; + + var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent); + cacheKey.CreateIValue(key, this.m_typeOfKeys); + cacheKey.Disable(); + + var cacheValue = new CachePaired(index, this, this.RefIDictionary, PairTypes.Value, m_listContent); + cacheValue.CreateIValue(value, this.m_typeofValues); + cacheValue.Disable(); + + //holder.SetActive(false); + + m_entries.Add(new KeyValuePair(cacheKey, cacheValue)); + + index++; + } + } + + RefreshDisplay(); + } + + public void RefreshDisplay() + { + //var entries = m_entries; + //m_pageHandler.ListCount = entries.Count; + // + //for (int i = 0; i < m_displayedEntries.Length; i++) + //{ + // var entry = m_displayedEntries[i]; + // if (entry.Key != null && entry.Value != null) + // { + // //m_rowHolders[i].SetActive(false); + // entry.Key.Disable(); + // entry.Value.Disable(); + // } + // else + // break; + //} + // + //if (entries.Count < 1) + // return; + // + //foreach (var itemIndex in m_pageHandler) + //{ + // if (itemIndex >= entries.Count) + // break; + // + // var entry = entries[itemIndex]; + // m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry; + // + // //m_rowHolders[itemIndex].SetActive(true); + // entry.Key.Enable(); + // entry.Value.Enable(); + //} + // + ////UpdateSubcontentHeight(); + } + + internal override void OnToggleSubcontent(bool active) + { + base.OnToggleSubcontent(active); + + if (active && m_recacheWanted) + { + m_recacheWanted = false; + GetCacheEntries(); + RefreshUIForValue(); + } + + RefreshDisplay(); + } + + internal GameObject m_listContent; + internal LayoutElement m_listLayout; + + // internal PageHandler m_pageHandler; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + //m_pageHandler = new PageHandler(null); + //m_pageHandler.ConstructUI(m_subContentParent); + //m_pageHandler.OnPageChanged += OnPageTurned; + + m_listContent = UIFactory.CreateVerticalGroup(m_subContentParent, "DictionaryContent", true, true, true, true, 2, new Vector4(5,5,5,5), + new Color(0.08f, 0.08f, 0.08f)); + + var scrollRect = m_listContent.GetComponent(); + scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0); + + m_listLayout = Owner.UIRoot.GetComponent(); + m_listLayout.minHeight = 25; + m_listLayout.flexibleHeight = 0; + + Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25); + + var contentFitter = m_listContent.AddComponent(); + contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; + contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveEnum.cs b/src/UI/Widgets/InteractiveValues/InteractiveEnum.cs new file mode 100644 index 0000000..53bfcf1 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveEnum.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveEnum : InteractiveValue + { + internal static Dictionary[]> s_enumNamesCache = new Dictionary[]>(); + + public InteractiveEnum(object value, Type valueType) : base(value, valueType) + { + GetNames(); + } + + public override bool HasSubContent => true; + public override bool SubContentWanted => Owner.CanWrite; + public override bool WantInspectBtn => false; + + internal KeyValuePair[] m_values = new KeyValuePair[0]; + + internal Type m_lastEnumType; + + internal void GetNames() + { + var type = Value?.GetType() ?? FallbackType; + + if (m_lastEnumType == type) + return; + + m_lastEnumType = type; + + if (m_subContentConstructed) + { + DestroySubContent(); + } + + if (!s_enumNamesCache.ContainsKey(type)) + { + // using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags) + var values = Enum.GetValues(type); + + var list = new List>(); + var set = new HashSet(); + + foreach (var value in values) + { + var name = value.ToString(); + + if (set.Contains(name)) + continue; + + set.Add(name); + + var backingType = Enum.GetUnderlyingType(type); + int intValue; + try + { + // this approach is necessary, a simple '(int)value' is not sufficient. + + var unbox = Convert.ChangeType(value, backingType); + + intValue = (int)Convert.ChangeType(unbox, typeof(int)); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("[InteractiveEnum] Could not Unbox underlying type " + backingType.Name + " from " + type.FullName); + ExplorerCore.Log(ex.ToString()); + continue; + } + + list.Add(new KeyValuePair(intValue, name)); + } + + s_enumNamesCache.Add(type, list.ToArray()); + } + + m_values = s_enumNamesCache[type]; + } + + public override void OnValueUpdated() + { + GetNames(); + + base.OnValueUpdated(); + } + + public override void RefreshUIForValue() + { + base.RefreshUIForValue(); + + if (m_subContentConstructed && !(this is InteractiveFlags)) + { + m_dropdownText.text = Value?.ToString() ?? ""; + } + } + + internal override void OnToggleSubcontent(bool toggle) + { + base.OnToggleSubcontent(toggle); + + RefreshUIForValue(); + } + + private void SetValueFromDropdown() + { + var type = Value?.GetType() ?? FallbackType; + var index = m_dropdown.value; + + var value = Enum.Parse(type, s_enumNamesCache[type][index].Value); + + if (value != null) + { + Value = value; + Owner.SetValue(); + RefreshUIForValue(); + } + } + + internal Dropdown m_dropdown; + internal Text m_dropdownText; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + if (Owner.CanWrite) + { + var groupObj = UIFactory.CreateHorizontalGroup(m_subContentParent, "InteractiveEnumGroup", false, true, true, true, 5, + new Vector4(3,3,3,3),new Color(1, 1, 1, 0)); + + // apply button + + var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromDropdown, new Color(0.3f, 0.3f, 0.3f)); + UIFactory.SetLayoutElement(apply.gameObject, minHeight: 25, minWidth: 50); + + // dropdown + + var dropdownObj = UIFactory.CreateDropdown(groupObj, out m_dropdown, "", 14, null); + UIFactory.SetLayoutElement(dropdownObj, minWidth: 150, minHeight: 25, flexibleWidth: 120); + + foreach (var kvp in m_values) + { + m_dropdown.options.Add(new Dropdown.OptionData + { + text = $"{kvp.Key}: {kvp.Value}" + }); + } + + m_dropdownText = m_dropdown.transform.Find("Label").GetComponent(); + } + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveEnumerable.cs b/src/UI/Widgets/InteractiveValues/InteractiveEnumerable.cs new file mode 100644 index 0000000..7582951 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveEnumerable.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core; +using UnityExplorer.Core.Config; +using UnityExplorer.UI; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.Utility; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveEnumerable : InteractiveValue + { + public InteractiveEnumerable(object value, Type valueType) : base(value, valueType) + { + if (valueType.IsGenericType) + m_baseEntryType = valueType.GetGenericArguments()[0]; + else + m_baseEntryType = typeof(object); + } + + public override bool WantInspectBtn => false; + public override bool HasSubContent => true; + public override bool SubContentWanted + { + get + { + if (m_recacheWanted) + return true; + else return m_entries.Count > 0; + } + } + + internal IEnumerable RefIEnumerable; + internal IList RefIList; + + internal readonly Type m_baseEntryType; + + internal readonly List m_entries = new List(); + internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ConfigManager.Default_Page_Limit.Value]; + internal bool m_recacheWanted = true; + + public override void OnValueUpdated() + { + RefIEnumerable = Value as IEnumerable; + RefIList = Value as IList; + + if (m_subContentParent.activeSelf) + { + GetCacheEntries(); + RefreshDisplay(); + } + else + m_recacheWanted = true; + + base.OnValueUpdated(); + } + + public override void OnException(CacheMember member) + { + base.OnException(member); + } + + private void OnPageTurned() + { + RefreshDisplay(); + } + + public override void RefreshUIForValue() + { + GetDefaultLabel(); + + if (Value != null) + { + string count = "?"; + if (m_recacheWanted && RefIList != null) + count = RefIList.Count.ToString(); + else if (!m_recacheWanted) + count = m_entries.Count.ToString(); + + m_baseLabel.text = $"[{count}] {m_richValueType}"; + } + else + { + m_baseLabel.text = DefaultLabel; + } + } + + public void GetCacheEntries() + { + if (m_entries.Any()) + { + // maybe improve this, probably could be more efficient i guess + + foreach (var entry in m_entries) + entry.Destroy(); + + m_entries.Clear(); + } + + if (RefIEnumerable == null && Value != null) + RefIEnumerable = RuntimeProvider.Instance.Reflection.EnumerateEnumerable(Value); + + if (RefIEnumerable != null) + { + int index = 0; + foreach (var entry in RefIEnumerable) + { + var cache = new CacheEnumerated(index, this, RefIList, this.m_listContent); + cache.CreateIValue(entry, m_baseEntryType); + m_entries.Add(cache); + + cache.Disable(); + + index++; + } + } + + RefreshDisplay(); + } + + public void RefreshDisplay() + { + //var entries = m_entries; + //m_pageHandler.ListCount = entries.Count; + // + //for (int i = 0; i < m_displayedEntries.Length; i++) + //{ + // var entry = m_displayedEntries[i]; + // if (entry != null) + // entry.Disable(); + // else + // break; + //} + // + //if (entries.Count < 1) + // return; + // + //foreach (var itemIndex in m_pageHandler) + //{ + // if (itemIndex >= entries.Count) + // break; + // + // CacheEnumerated entry = entries[itemIndex]; + // m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry; + // entry.Enable(); + //} + // + ////UpdateSubcontentHeight(); + } + + internal override void OnToggleSubcontent(bool active) + { + base.OnToggleSubcontent(active); + + if (active && m_recacheWanted) + { + m_recacheWanted = false; + GetCacheEntries(); + RefreshUIForValue(); + } + + RefreshDisplay(); + } + + + internal GameObject m_listContent; + internal LayoutElement m_listLayout; + + //internal PageHandler m_pageHandler; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + //m_pageHandler = new PageHandler(null); + //m_pageHandler.ConstructUI(m_subContentParent); + //m_pageHandler.OnPageChanged += OnPageTurned; + + m_listContent = UIFactory.CreateVerticalGroup(this.m_subContentParent, "EnumerableContent", true, true, true, true, 2, new Vector4(5,5,5,5), + new Color(0.08f, 0.08f, 0.08f)); + + var scrollRect = m_listContent.GetComponent(); + scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0); + + m_listLayout = Owner.UIRoot.GetComponent(); + m_listLayout.minHeight = 25; + m_listLayout.flexibleHeight = 0; + Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25); + + var contentFitter = m_listContent.AddComponent(); + contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; + contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveFlags.cs b/src/UI/Widgets/InteractiveValues/InteractiveFlags.cs new file mode 100644 index 0000000..9a95abb --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveFlags.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveFlags : InteractiveEnum + { + public InteractiveFlags(object value, Type valueType) : base(value, valueType) + { + m_toggles = new Toggle[m_values.Length]; + m_enabledFlags = new bool[m_values.Length]; + } + + public override bool HasSubContent => true; + public override bool SubContentWanted => Owner.CanWrite; + public override bool WantInspectBtn => false; + + internal bool[] m_enabledFlags; + internal Toggle[] m_toggles; + + public override void OnValueUpdated() + { + if (Owner.CanWrite) + { + var enabledNames = new List(); + + var enabled = Value?.ToString().Split(',').Select(it => it.Trim()); + if (enabled != null) + enabledNames.AddRange(enabled); + + for (int i = 0; i < m_values.Length; i++) + m_enabledFlags[i] = enabledNames.Contains(m_values[i].Value); + } + + base.OnValueUpdated(); + } + + public override void RefreshUIForValue() + { + GetDefaultLabel(); + m_baseLabel.text = DefaultLabel; + + base.RefreshUIForValue(); + + if (m_subContentConstructed) + { + for (int i = 0; i < m_values.Length; i++) + { + var toggle = m_toggles[i]; + if (toggle.isOn != m_enabledFlags[i]) + toggle.isOn = m_enabledFlags[i]; + } + } + } + + private void SetValueFromToggles() + { + string val = ""; + for (int i = 0; i < m_values.Length; i++) + { + if (m_enabledFlags[i]) + { + if (val != "") val += ", "; + val += m_values[i].Value; + } + } + var type = Value?.GetType() ?? FallbackType; + Value = Enum.Parse(type, val); + RefreshUIForValue(); + Owner.SetValue(); + } + + internal override void OnToggleSubcontent(bool toggle) + { + base.OnToggleSubcontent(toggle); + + RefreshUIForValue(); + } + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + } + + public override void ConstructSubcontent() + { + m_subContentConstructed = true; + + if (Owner.CanWrite) + { + var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, "InteractiveFlagsContent", false, true, true, true, 5, + new Vector4(3,3,3,3), new Color(1, 1, 1, 0)); + + // apply button + + var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromToggles, new Color(0.3f, 0.3f, 0.3f)); + UIFactory.SetLayoutElement(apply.gameObject, minWidth: 50, minHeight: 25); + + // toggles + + for (int i = 0; i < m_values.Length; i++) + AddToggle(i, groupObj); + } + } + + internal void AddToggle(int index, GameObject groupObj) + { + var value = m_values[index]; + + var toggleObj = UIFactory.CreateToggle(groupObj, "FlagToggle", out Toggle toggle, out Text text, new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(toggleObj, minWidth: 100, flexibleWidth: 2000, minHeight: 25); + + m_toggles[index] = toggle; + + toggle.onValueChanged.AddListener((bool val) => { m_enabledFlags[index] = val; }); + + text.text = $"{value.Key}: {value.Value}"; + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveFloatStruct.cs b/src/UI/Widgets/InteractiveValues/InteractiveFloatStruct.cs new file mode 100644 index 0000000..a37d983 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveFloatStruct.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using System.Reflection; + +namespace UnityExplorer.UI.InteractiveValues +{ + // Class for supporting any "float struct" (ie Vector, Rect, etc). + // Supports any struct where all the public instance fields are floats (or types assignable to float) + + public class StructInfo + { + public string[] FieldNames { get; } + private readonly FieldInfo[] m_fields; + + public StructInfo(Type type) + { + m_fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .Where(it => !it.IsLiteral) + .ToArray(); + + FieldNames = m_fields.Select(it => it.Name) + .ToArray(); + } + + public object SetValue(ref object instance, int fieldIndex, float val) + { + m_fields[fieldIndex].SetValue(instance, val); + return instance; + } + + public float GetValue(object instance, int fieldIndex) + => (float)m_fields[fieldIndex].GetValue(instance); + + public void RefreshUI(InputField[] inputs, object instance) + { + try + { + for (int i = 0; i < m_fields.Length; i++) + { + var field = m_fields[i]; + float val = (float)field.GetValue(instance); + inputs[i].text = val.ToString(); + } + } + catch (Exception ex) + { + ExplorerCore.Log(ex); + } + } + } + + public class InteractiveFloatStruct : InteractiveValue + { + private static readonly Dictionary _typeSupportCache = new Dictionary(); + public static bool IsTypeSupported(Type type) + { + if (!type.IsValueType) + return false; + + if (string.IsNullOrEmpty(type.AssemblyQualifiedName)) + return false; + + if (_typeSupportCache.TryGetValue(type.AssemblyQualifiedName, out bool ret)) + return ret; + + ret = true; + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + foreach (var field in fields) + { + if (field.IsLiteral) + continue; + + if (!typeof(float).IsAssignableFrom(field.FieldType)) + { + ret = false; + break; + } + } + _typeSupportCache.Add(type.AssemblyQualifiedName, ret); + return ret; + } + + //~~~~~~~~~ Instance ~~~~~~~~~~ + + public InteractiveFloatStruct(object value, Type valueType) : base(value, valueType) { } + + public override bool HasSubContent => true; + public override bool SubContentWanted => true; + + public StructInfo StructInfo; + + public override void RefreshUIForValue() + { + InitializeStructInfo(); + + base.RefreshUIForValue(); + + if (m_subContentConstructed) + StructInfo.RefreshUI(m_inputs, this.Value); + } + + internal override void OnToggleSubcontent(bool toggle) + { + InitializeStructInfo(); + + base.OnToggleSubcontent(toggle); + + StructInfo.RefreshUI(m_inputs, this.Value); + } + + internal Type m_lastStructType; + + internal void InitializeStructInfo() + { + var type = Value?.GetType() ?? FallbackType; + + if (StructInfo != null && type == m_lastStructType) + return; + + if (StructInfo != null && m_subContentConstructed) + DestroySubContent(); + + m_lastStructType = type; + + StructInfo = new StructInfo(type); + + if (m_subContentParent.activeSelf) + ConstructSubcontent(); + } + + #region UI CONSTRUCTION + + internal InputField[] m_inputs; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + if (StructInfo == null) + { + ExplorerCore.LogWarning("Setting up subcontent but structinfo is null"); + return; + } + + var editorContainer = UIFactory.CreateVerticalGroup(m_subContentParent, "EditorContent", false, true, true, true, 2, new Vector4(4, 4, 4, 4), + new Color(0.08f, 0.08f, 0.08f)); + + m_inputs = new InputField[StructInfo.FieldNames.Length]; + + for (int i = 0; i < StructInfo.FieldNames.Length; i++) + AddEditorRow(i, editorContainer); + + RefreshUIForValue(); + } + + internal void AddEditorRow(int index, GameObject groupObj) + { + try + { + var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow", false, true, true, true, 5, default, new Color(1, 1, 1, 0)); + + string name = StructInfo.FieldNames[index]; + + var label = UIFactory.CreateLabel(row, "RowLabel", $"{name}:", TextAnchor.MiddleRight, Color.cyan); + UIFactory.SetLayoutElement(label.gameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25); + + var inputFieldObj = UIFactory.CreateInputField(row, "InputField", "...", out InputField inputField, 14, 3, 1); + UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0); + + m_inputs[index] = inputField; + + inputField.onValueChanged.AddListener((string val) => + { + try + { + float f = float.Parse(val); + Value = StructInfo.SetValue(ref this.Value, index, f); + Owner.SetValue(); + } + catch { } + }); + } + catch (Exception ex) + { + ExplorerCore.Log(ex); + } + } + + #endregion + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveNumber.cs b/src/UI/Widgets/InteractiveValues/InteractiveNumber.cs new file mode 100644 index 0000000..bd788a4 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveNumber.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.Core; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.CacheObject; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveNumber : InteractiveValue + { + public InteractiveNumber(object value, Type valueType) : base(value, valueType) { } + + public override bool HasSubContent => false; + public override bool SubContentWanted => false; + public override bool WantInspectBtn => false; + + public override void OnValueUpdated() + { + base.OnValueUpdated(); + } + + public override void OnException(CacheMember member) + { + base.OnException(member); + + if (m_valueInput.gameObject.activeSelf) + m_valueInput.gameObject.SetActive(false); + + if (Owner.CanWrite) + { + if (m_applyBtn.gameObject.activeSelf) + m_applyBtn.gameObject.SetActive(false); + } + } + + public override void RefreshUIForValue() + { + if (!Owner.HasEvaluated) + { + GetDefaultLabel(); + m_baseLabel.text = DefaultLabel; + return; + } + + m_baseLabel.text = SignatureHighlighter.ParseFullSyntax(FallbackType, false); + m_valueInput.text = Value.ToString(); + + var type = Value.GetType(); + if (type == typeof(float) + || type == typeof(double) + || type == typeof(decimal)) + { + m_valueInput.characterValidation = InputField.CharacterValidation.Decimal; + } + else + { + m_valueInput.characterValidation = InputField.CharacterValidation.Integer; + } + + if (Owner.CanWrite) + { + if (!m_applyBtn.gameObject.activeSelf) + m_applyBtn.gameObject.SetActive(true); + } + + if (!m_valueInput.gameObject.activeSelf) + m_valueInput.gameObject.SetActive(true); + } + + public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) })); + private MethodInfo m_parseMethod; + + internal void OnApplyClicked() + { + try + { + Value = ParseMethod.Invoke(null, new object[] { m_valueInput.text }); + Owner.SetValue(); + RefreshUIForValue(); + } + catch (Exception e) + { + ExplorerCore.LogWarning("Could not parse input! " + ReflectionUtility.ReflectionExToString(e, true)); + } + } + + internal InputField m_valueInput; + internal Button m_applyBtn; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + + var labelLayout = m_baseLabel.gameObject.GetComponent(); + labelLayout.minWidth = 50; + labelLayout.flexibleWidth = 0; + + var inputObj = UIFactory.CreateInputField(m_mainContent, "InteractiveNumberInput", "...", out m_valueInput); + UIFactory.SetLayoutElement(inputObj, minWidth: 120, minHeight: 25, flexibleWidth: 0); + m_valueInput.gameObject.SetActive(false); + + if (Owner.CanWrite) + { + m_applyBtn = UIFactory.CreateButton(m_mainContent, "ApplyButton", "Apply", OnApplyClicked, new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(m_applyBtn.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0); + } + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveString.cs b/src/UI/Widgets/InteractiveValues/InteractiveString.cs new file mode 100644 index 0000000..dfdf10a --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveString.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.Core.Runtime; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveString : InteractiveValue + { + public InteractiveString(object value, Type valueType) : base(value, valueType) { } + + public override bool HasSubContent => true; + public override bool SubContentWanted => true; + + public override bool WantInspectBtn => false; + + public override void OnValueUpdated() + { + if (!(Value is string) && Value != null) + Value = RuntimeProvider.Instance.Reflection.UnboxString(Value); + + base.OnValueUpdated(); + } + + public override void OnException(CacheMember member) + { + base.OnException(member); + + if (m_subContentConstructed && m_hiddenObj.gameObject.activeSelf) + m_hiddenObj.gameObject.SetActive(false); + + m_labelLayout.minWidth = 200; + m_labelLayout.flexibleWidth = 5000; + } + + public override void RefreshUIForValue() + { + GetDefaultLabel(false); + + if (!Owner.HasEvaluated) + { + m_baseLabel.text = DefaultLabel; + return; + } + + m_baseLabel.text = m_richValueType; + + if (m_subContentConstructed) + { + if (!m_hiddenObj.gameObject.activeSelf) + m_hiddenObj.gameObject.SetActive(true); + } + + if (!string.IsNullOrEmpty((string)Value)) + { + var toString = (string)Value; + if (toString.Length > 15000) + toString = toString.Substring(0, 15000); + + m_readonlyInput.text = toString; + + if (m_subContentConstructed) + { + m_valueInput.text = toString; + m_placeholderText.text = toString; + } + } + else + { + string s = Value == null + ? "null" + : "empty"; + + m_readonlyInput.text = $"{s}"; + + if (m_subContentConstructed) + { + m_valueInput.text = ""; + m_placeholderText.text = s; + } + } + + m_labelLayout.minWidth = 50; + m_labelLayout.flexibleWidth = 0; + } + + internal void SetValueFromInput() + { + Value = m_valueInput.text; + + if (!typeof(string).IsAssignableFrom(Owner.FallbackType)) + ReflectionProvider.Instance.BoxStringToType(ref Value, Owner.FallbackType); + + Owner.SetValue(); + + // revert back to string now + OnValueUpdated(); + + RefreshUIForValue(); + } + + // for the default label + internal LayoutElement m_labelLayout; + + //internal InputField m_readonlyInput; + internal Text m_readonlyInput; + + // for input + internal InputField m_valueInput; + internal GameObject m_hiddenObj; + internal Text m_placeholderText; + + public override void ConstructUI(GameObject parent, GameObject subGroup) + { + base.ConstructUI(parent, subGroup); + + GetDefaultLabel(false); + m_richValueType = SignatureHighlighter.ParseFullSyntax(FallbackType, false); + + m_labelLayout = m_baseLabel.gameObject.GetComponent(); + + m_readonlyInput = UIFactory.CreateLabel(m_mainContent, "ReadonlyLabel", "", TextAnchor.MiddleLeft); + m_readonlyInput.horizontalOverflow = HorizontalWrapMode.Overflow; + + var testFitter = m_readonlyInput.gameObject.AddComponent(); + testFitter.verticalFit = ContentSizeFitter.FitMode.MinSize; + + UIFactory.SetLayoutElement(m_readonlyInput.gameObject, minHeight: 25, preferredHeight: 25, flexibleHeight: 0); + } + + public override void ConstructSubcontent() + { + base.ConstructSubcontent(); + + var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, "SubContent", false, false, true, true, 4, new Vector4(3,3,3,3), + new Color(1, 1, 1, 0)); + + m_hiddenObj = UIFactory.CreateLabel(groupObj, "HiddenLabel", "", TextAnchor.MiddleLeft).gameObject; + m_hiddenObj.SetActive(false); + var hiddenText = m_hiddenObj.GetComponent(); + hiddenText.color = Color.clear; + hiddenText.fontSize = 14; + hiddenText.raycastTarget = false; + hiddenText.supportRichText = false; + var hiddenFitter = m_hiddenObj.AddComponent(); + hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + UIFactory.SetLayoutElement(m_hiddenObj, minHeight: 25, flexibleHeight: 500, minWidth: 250, flexibleWidth: 9000); + UIFactory.SetLayoutGroup(m_hiddenObj, true, true, true, true); + + var inputObj = UIFactory.CreateInputField(m_hiddenObj, "StringInputField", "...", out m_valueInput, 14, 3); + UIFactory.SetLayoutElement(inputObj, minWidth: 120, minHeight: 25, flexibleWidth: 5000, flexibleHeight: 5000); + + m_valueInput.lineType = InputField.LineType.MultiLineNewline; + + m_placeholderText = m_valueInput.placeholder.GetComponent(); + + m_placeholderText.supportRichText = false; + m_valueInput.textComponent.supportRichText = false; + + m_valueInput.onValueChanged.AddListener((string val) => + { + hiddenText.text = val ?? ""; + LayoutRebuilder.ForceRebuildLayoutImmediate(Owner.m_mainRect); + }); + + if (Owner.CanWrite) + { + var apply = UIFactory.CreateButton(groupObj, "ApplyButton", "Apply", SetValueFromInput, new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(apply.gameObject, minWidth: 50, minHeight: 25, flexibleWidth: 0); + } + else + { + m_valueInput.readOnly = true; + } + + RefreshUIForValue(); + } + } +} diff --git a/src/UI/Widgets/InteractiveValues/InteractiveValue.cs b/src/UI/Widgets/InteractiveValues/InteractiveValue.cs new file mode 100644 index 0000000..a0e36f2 --- /dev/null +++ b/src/UI/Widgets/InteractiveValues/InteractiveValue.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityExplorer.Core; +using UnityExplorer.Core.Runtime; +using UnityExplorer.UI; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.Inspectors; + +namespace UnityExplorer.UI.InteractiveValues +{ + public class InteractiveValue + { + /// + /// Get the subclass which supports the provided . + /// + /// The which you want the Type for. + /// The best subclass of which supports the provided . + public static Type GetIValueForType(Type type) + { + // rather ugly but I couldn't think of a cleaner way that was worth it. + // switch-case doesn't really work here. + + // arbitrarily check some types, fastest methods first. + if (type == typeof(bool)) + return typeof(InteractiveBool); + // if type is primitive then it must be a number if its not a bool. Also check for decimal. + else if (type.IsPrimitive || type == typeof(decimal)) + return typeof(InteractiveNumber); + // check for strings + else if (type == typeof(string)) + return typeof(InteractiveString); + // check for enum/flags + else if (typeof(Enum).IsAssignableFrom(type)) + { + // NET 3.5 doesn't have "GetCustomAttribute", gotta use the multiple version. + if (type.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any()) + return typeof(InteractiveFlags); + else + return typeof(InteractiveEnum); + } + // check for unity struct types + else if (typeof(Color).IsAssignableFrom(type)) + return typeof(InteractiveColor); + else if (InteractiveFloatStruct.IsTypeSupported(type)) + return typeof(InteractiveFloatStruct); + // check Transform, force InteractiveValue so they dont become InteractiveEnumerables. + else if (typeof(Transform).IsAssignableFrom(type)) + return typeof(InteractiveValue); + // check Dictionaries before Enumerables + else if (ReflectionUtility.IsDictionary(type)) + return typeof(InteractiveDictionary); + // finally check for Enumerables + else if (ReflectionUtility.IsEnumerable(type)) + return typeof(InteractiveEnumerable); + // fallback to default + else + return typeof(InteractiveValue); + } + + public static InteractiveValue Create(object value, Type fallbackType) + { + var type = ReflectionUtility.GetActualType(value) ?? fallbackType; + var iType = GetIValueForType(type); + + return (InteractiveValue)Activator.CreateInstance(iType, new object[] { value, type }); + } + + // ~~~~~~~~~ Instance ~~~~~~~~~ + + public InteractiveValue(object value, Type valueType) + { + this.Value = value; + this.FallbackType = valueType; + } + + public CacheObjectBase Owner; + + public object Value; + public readonly Type FallbackType; + + public virtual bool HasSubContent => false; + public virtual bool SubContentWanted => false; + public virtual bool WantInspectBtn => true; + + public string DefaultLabel => m_defaultLabel ?? GetDefaultLabel(); + internal string m_defaultLabel; + internal string m_richValueType; + + public bool m_UIConstructed; + + public virtual void OnDestroy() + { + if (this.m_mainContent) + { + m_mainContent.transform.SetParent(null, false); + m_mainContent.SetActive(false); + GameObject.Destroy(this.m_mainContent.gameObject); + } + + DestroySubContent(); + } + + public virtual void DestroySubContent() + { + if (this.m_subContentParent && HasSubContent) + { + for (int i = 0; i < this.m_subContentParent.transform.childCount; i++) + { + var child = m_subContentParent.transform.GetChild(i); + if (child) + GameObject.Destroy(child.gameObject); + } + } + + m_subContentConstructed = false; + } + + public virtual void OnValueUpdated() + { + if (!m_UIConstructed) + ConstructUI(m_mainContentParent, m_subContentParent); + + if (Owner is CacheMember ownerMember && !string.IsNullOrEmpty(ownerMember.ReflectionException)) + OnException(ownerMember); + else + RefreshUIForValue(); + } + + public virtual void OnException(CacheMember member) + { + if (m_UIConstructed) + m_baseLabel.text = "" + member.ReflectionException + ""; + + Value = null; + } + + public virtual void RefreshUIForValue() + { + GetDefaultLabel(); + m_baseLabel.text = DefaultLabel; + } + + public void RefreshElementsAfterUpdate() + { + if (WantInspectBtn) + { + bool shouldShowInspect = !Value.IsNullOrDestroyed(); + + if (m_inspectButton.activeSelf != shouldShowInspect) + m_inspectButton.SetActive(shouldShowInspect); + } + + bool subContentWanted = SubContentWanted; + if (Owner is CacheMember cm && (!cm.HasEvaluated || !string.IsNullOrEmpty(cm.ReflectionException))) + subContentWanted = false; + + if (HasSubContent) + { + if (m_subExpandBtn.gameObject.activeSelf != subContentWanted) + m_subExpandBtn.gameObject.SetActive(subContentWanted); + + if (!subContentWanted && m_subContentParent.activeSelf) + ToggleSubcontent(); + } + } + + public virtual void ConstructSubcontent() + { + m_subContentConstructed = true; + } + + public void ToggleSubcontent() + { + if (!this.m_subContentParent.activeSelf) + { + this.m_subContentParent.SetActive(true); + this.m_subContentParent.transform.SetAsLastSibling(); + m_subExpandBtn.GetComponentInChildren().text = "▼"; + } + else + { + this.m_subContentParent.SetActive(false); + m_subExpandBtn.GetComponentInChildren().text = "▲"; + } + + OnToggleSubcontent(m_subContentParent.activeSelf); + + RefreshElementsAfterUpdate(); + } + + internal virtual void OnToggleSubcontent(bool toggle) + { + if (!m_subContentConstructed) + ConstructSubcontent(); + } + + internal MethodInfo m_toStringMethod; + internal MethodInfo m_toStringFormatMethod; + internal bool m_gotToStringMethods; + + public string GetDefaultLabel(bool updateType = true) + { + var valueType = Value?.GetType() ?? this.FallbackType; + if (updateType) + m_richValueType = SignatureHighlighter.ParseFullSyntax(valueType, true); + + if (!Owner.HasEvaluated) + return m_defaultLabel = $"Not yet evaluated ({m_richValueType})"; + + if (Value.IsNullOrDestroyed()) + return m_defaultLabel = $"null ({m_richValueType})"; + + string label; + + // Two dirty fixes for TextAsset and EventSystem, which can have very long ToString results. + if (Value is TextAsset textAsset) + { + label = textAsset.text; + + if (label.Length > 10) + label = $"{label.Substring(0, 10)}..."; + + label = $"\"{label}\" {textAsset.name} ({m_richValueType})"; + } + else if (Value is EventSystem) + { + label = m_richValueType; + } + else // For everything else... + { + if (!m_gotToStringMethods) + { + m_gotToStringMethods = true; + + m_toStringMethod = valueType.GetMethod("ToString", new Type[0]); + m_toStringFormatMethod = valueType.GetMethod("ToString", new Type[] { typeof(string) }); + + // test format method actually works + try + { + m_toStringFormatMethod.Invoke(Value, new object[] { "F3" }); + } + catch + { + m_toStringFormatMethod = null; + } + } + + string toString; + if (m_toStringFormatMethod != null) + toString = (string)m_toStringFormatMethod.Invoke(Value, new object[] { "F3" }); + else + toString = (string)m_toStringMethod.Invoke(Value, new object[0]); + + toString = toString ?? ""; + + string typeName = valueType.FullName; + if (typeName.StartsWith("Il2CppSystem.")) + typeName = typeName.Substring(6, typeName.Length - 6); + + toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(valueType, toString, ref typeName); + + // If the ToString is just the type name, use our syntax highlighted type name instead. + if (toString == typeName) + { + label = m_richValueType; + } + else // Otherwise, parse the result and put our highlighted name in. + { + if (toString.Length > 200) + toString = toString.Substring(0, 200) + "..."; + + label = toString; + + var unityType = $"({valueType.FullName})"; + if (Value is UnityEngine.Object && label.Contains(unityType)) + label = label.Replace(unityType, $"({m_richValueType})"); + else + label += $" ({m_richValueType})"; + } + } + + return m_defaultLabel = label; + } + + #region UI CONSTRUCTION + + internal GameObject m_mainContentParent; + internal GameObject m_subContentParent; + + internal GameObject m_mainContent; + internal GameObject m_inspectButton; + internal Text m_baseLabel; + + internal Button m_subExpandBtn; + internal bool m_subContentConstructed; + + public virtual void ConstructUI(GameObject parent, GameObject subGroup) + { + m_UIConstructed = true; + + m_mainContent = UIFactory.CreateHorizontalGroup(parent, $"InteractiveValue_{this.GetType().Name}", false, false, true, true, 4, default, + new Color(1, 1, 1, 0), TextAnchor.UpperLeft); + + var mainRect = m_mainContent.GetComponent(); + mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25); + + UIFactory.SetLayoutElement(m_mainContent, flexibleWidth: 9000, minWidth: 175, minHeight: 25, flexibleHeight: 0); + + // subcontent expand button + if (HasSubContent) + { + m_subExpandBtn = UIFactory.CreateButton(m_mainContent, "ExpandSubcontentButton", "▲", ToggleSubcontent, new Color(0.3f, 0.3f, 0.3f)); + UIFactory.SetLayoutElement(m_subExpandBtn.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0, flexibleHeight: 0); + } + + // inspect button + + var inspectBtn = UIFactory.CreateButton(m_mainContent, + "InspectButton", + "Inspect", + () => + { + if (!Value.IsNullOrDestroyed(false)) + InspectorManager.Inspect(this.Value, this.Owner); + }, + new Color(0.3f, 0.3f, 0.3f, 0.2f)); + + m_inspectButton = inspectBtn.gameObject; + UIFactory.SetLayoutElement(m_inspectButton, minWidth: 60, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + + m_inspectButton.SetActive(false); + + // value label + + m_baseLabel = UIFactory.CreateLabel(m_mainContent, "ValueLabel", "", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(m_baseLabel.gameObject, flexibleWidth: 9000, minHeight: 25); + + m_subContentParent = subGroup; + } + +#endregion + } +} diff --git a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs index eba90d4..dfd5a52 100644 --- a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs @@ -5,6 +5,7 @@ using System.Text; using UnityEngine; using UnityEngine.UI; using UnityExplorer.Core.Search; +using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.Models; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; @@ -96,7 +97,10 @@ namespace UnityExplorer.UI.Widgets private void OnCellClicked(int dataIndex) { - ExplorerCore.Log("TODO"); + if (m_context == SearchContext.StaticClass) + InspectorManager.InspectType(currentResults[dataIndex] as Type); + else + InspectorManager.Inspect(currentResults[dataIndex]); } private bool ShouldDisplayCell(object arg1, string arg2) => true; diff --git a/src/UI/Widgets/ScrollPool/DataHeightCache.cs b/src/UI/Widgets/ScrollPool/DataHeightCache.cs index e8153e5..5a5fe56 100644 --- a/src/UI/Widgets/ScrollPool/DataHeightCache.cs +++ b/src/UI/Widgets/ScrollPool/DataHeightCache.cs @@ -18,7 +18,7 @@ namespace UnityExplorer.UI.Widgets public class DataHeightCache { private ScrollPool ScrollPool { get; } - private DataHeightCache SisterCache { get; } + //private DataHeightCache SisterCache { get; } public DataHeightCache(ScrollPool scrollPool) { @@ -27,10 +27,10 @@ namespace UnityExplorer.UI.Widgets public DataHeightCache(ScrollPool scrollPool, DataHeightCache sisterCache) : this(scrollPool) { - this.SisterCache = sisterCache; + //this.SisterCache = sisterCache; - for (int i = 0; i < scrollPool.DataSource.ItemCount; i++) - Add(sisterCache[ScrollPool.DataSource.GetRealIndexOfTempIndex(i)]); + //for (int i = 0; i < scrollPool.DataSource.ItemCount; i++) + // Add(sisterCache[ScrollPool.DataSource.GetRealIndexOfTempIndex(i)]); } private readonly List heightCache = new List(); @@ -199,6 +199,7 @@ namespace UnityExplorer.UI.Widgets int minStart = rangeCache[dataIndex]; for (int i = minStart; i < rangeCache.Count; i++) { + ExplorerCore.Log("manually searching for index | " + Time.realtimeSinceStartup); if (rangeCache[i] == dataIndex) { rangeIndex = i; @@ -248,13 +249,13 @@ namespace UnityExplorer.UI.Widgets } } - // if sister cache is set, then update it too. - if (SisterCache != null) - { - var realIdx = ScrollPool.DataSource.GetRealIndexOfTempIndex(dataIndex); - if (realIdx >= 0) - SisterCache.SetIndex(realIdx, height, true); - } + //// if sister cache is set, then update it too. + //if (SisterCache != null) + //{ + // var realIdx = ScrollPool.DataSource.GetRealIndexOfTempIndex(dataIndex); + // if (realIdx >= 0) + // SisterCache.SetIndex(realIdx, height, true); + //} } private void RebuildStartPositions(bool ignoreDataCount) diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index b18fb69..3b7bf6a 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -25,13 +25,21 @@ namespace UnityExplorer.UI.Widgets private float PrototypeHeight => PrototypeCell.rect.height; - public int ExtraPoolCells => 10; + public int ExtraPoolCells => 6; public float RecycleThreshold => PrototypeHeight * ExtraPoolCells; public float HalfThreshold => RecycleThreshold * 0.5f; // UI - public override GameObject UIRoot => ScrollRect.gameObject; + public override GameObject UIRoot + { + get + { + if (ScrollRect) + return ScrollRect.gameObject; + return null; + } + } public RectTransform Viewport => ScrollRect.viewport; public RectTransform Content => ScrollRect.content; @@ -217,7 +225,7 @@ namespace UnityExplorer.UI.Widgets //Instantiate and add to Pool RectTransform rect = GameObject.Instantiate(PrototypeCell.gameObject).GetComponent(); rect.gameObject.SetActive(true); - rect.name = $"Cell_{CellPool.Count + 1}"; + rect.name = $"Cell_{CellPool.Count}"; var cell = DataSource.CreateCell(rect); CellPool.Add(cell); rect.SetParent(ScrollRect.content, false); @@ -399,7 +407,7 @@ namespace UnityExplorer.UI.Widgets cachedCell.Enable(); DataSource.SetCell(cachedCell, dataIndex); - LayoutRebuilder.ForceRebuildLayoutImmediate(cachedCell.Rect); + //LayoutRebuilder.ForceRebuildLayoutImmediate(cachedCell.Rect); HeightCache.SetIndex(dataIndex, cachedCell.Rect.rect.height); } @@ -435,7 +443,7 @@ namespace UnityExplorer.UI.Widgets ScrollRect.m_ContentStartPosition += vector; ScrollRect.m_PrevPosition += vector; - LayoutRebuilder.ForceRebuildLayoutImmediate(Content); + // LayoutRebuilder.ForceRebuildLayoutImmediate(Content); prevAnchoredPos = ScrollRect.content.anchoredPosition; SetScrollBounds(); @@ -603,10 +611,82 @@ namespace UnityExplorer.UI.Widgets desiredAnchor = desiredMinY - topStartPos; Content.anchoredPosition = new Vector2(0, desiredAnchor); - bottomDataIndex = poolStartIndex + CellPool.Count - 1; - RefreshCells(true, false); + int desiredBottomIndex = poolStartIndex + CellPool.Count - 1; - UpdateSliderHandle(true); + // check if our pool indices contain the desired index. If so, rotate and set + if (bottomDataIndex == desiredBottomIndex) + { + // cells will be the same, do nothing? + } + else + { + if (TopDataIndex > poolStartIndex && TopDataIndex < desiredBottomIndex) + { + //ExplorerCore.Log("Scroll bottom to top"); + // top cell falls within the new range, rotate around that + int rotate = TopDataIndex - poolStartIndex; + for (int i = 0; i < rotate; i++) + { + CellPool[bottomPoolIndex].Rect.SetAsFirstSibling(); + + //set new indices + topPoolIndex = bottomPoolIndex; + bottomPoolIndex = (bottomPoolIndex - 1 + CellPool.Count) % CellPool.Count; + bottomDataIndex--; + + SetCell(CellPool[topPoolIndex], TopDataIndex); + } + } + else if (bottomDataIndex > poolStartIndex && bottomDataIndex < desiredBottomIndex) + { + //ExplorerCore.Log("Scroll top to bottom"); + // bottom cells falls within the new range, rotate around that + int rotate = desiredBottomIndex - bottomDataIndex; + for (int i = 0; i < rotate; i++) + { + CellPool[topPoolIndex].Rect.SetAsLastSibling(); + + //set new indices + bottomPoolIndex = topPoolIndex; + topPoolIndex = (topPoolIndex + 1) % CellPool.Count; + bottomDataIndex++; + + SetCell(CellPool[bottomPoolIndex], bottomDataIndex); + } + } + else + { + // new cells are completely different, set all cells + //ExplorerCore.Log("Scroll jump"); + + bottomDataIndex = desiredBottomIndex; + var enumerator = GetPoolEnumerator(); + while (enumerator.MoveNext()) + { + var curr = enumerator.Current; + var cell = CellPool[curr.cellIndex]; + SetCell(cell, curr.dataIndex); + } + } + } + + SetRecycleViewBounds(true); + + //CheckDataSourceCountChange(out bool jumpToBottom); + + //// force check recycles + //if (andReloadFromDataSource) + //{ + // RecycleBottomToTop(); + // RecycleTopToBottom(); + //} + + //LayoutRebuilder.ForceRebuildLayoutImmediate(Content); + + SetScrollBounds(); + ScrollRect.UpdatePrevData(); + + UpdateSliderHandle(false); } /// Use diff --git a/src/UI/Widgets/SliderScrollbar.cs b/src/UI/Widgets/SliderScrollbar.cs index 0b31499..7f9725a 100644 --- a/src/UI/Widgets/SliderScrollbar.cs +++ b/src/UI/Widgets/SliderScrollbar.cs @@ -15,27 +15,17 @@ namespace UnityExplorer.UI.Utility // Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar. public class SliderScrollbar : UIBehaviourModel { - //internal static readonly List Instances = new List(); - - //public static void UpdateInstances() - //{ - // if (!Instances.Any()) - // return; - - // for (int i = 0; i < Instances.Count; i++) - // { - // var slider = Instances[i]; - - // if (slider.CheckDestroyed()) - // i--; - // else - // slider.Update(); - // } - //} - public bool IsActive { get; private set; } - public override GameObject UIRoot => m_slider.gameObject; + public override GameObject UIRoot + { + get + { + if (m_slider) + return m_slider.gameObject; + return null; + } + } public event Action OnValueChanged; diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 29ba71c..b8a4fab 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -219,6 +219,17 @@ + + + + + + + + + + + @@ -262,11 +273,10 @@ - - + @@ -275,6 +285,24 @@ + + + + + + + + + + + + + + + + + +