From bd9e80f2b475ef11408d9c071c9fdff7a5783885 Mon Sep 17 00:00:00 2001 From: Sinai <49360850+sinai-dev@users.noreply.github.com> Date: Sun, 20 Mar 2022 21:21:01 +1100 Subject: [PATCH] Refactor unity inspector widgets into proper classes --- src/Inspectors/ReflectionInspector.cs | 390 ++++-------------- .../Widgets/UnityObjects/Texture2DWidget.cs | 251 +++++++++++ .../Widgets/UnityObjects/UnityObjectWidget.cs | 133 ++++++ src/UnityExplorer.csproj | 3 + 4 files changed, 465 insertions(+), 312 deletions(-) create mode 100644 src/UI/Widgets/UnityObjects/Texture2DWidget.cs create mode 100644 src/UI/Widgets/UnityObjects/UnityObjectWidget.cs diff --git a/src/Inspectors/ReflectionInspector.cs b/src/Inspectors/ReflectionInspector.cs index ee6d99e..615abdc 100644 --- a/src/Inspectors/ReflectionInspector.cs +++ b/src/Inspectors/ReflectionInspector.cs @@ -2,29 +2,34 @@ using System.Collections; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.Config; -using UnityExplorer.Runtime; using UnityExplorer.CacheObject; using UnityExplorer.CacheObject.Views; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Widgets; -using UnityExplorer.UI; -using UniverseLib.UI.Widgets; -using UniverseLib.UI; using UniverseLib; -using UniverseLib.Runtime; +using UniverseLib.UI; using UniverseLib.UI.Models; +using UniverseLib.UI.ObjectPool; using UniverseLib.UI.Widgets.ScrollView; using UniverseLib.Utility; namespace UnityExplorer.Inspectors { + [Flags] + public enum MemberFilter + { + None = 0, + Property = 1, + Field = 2, + Constructor = 4, + Method = 8, + All = Property | Field | Method | Constructor, + } + public class ReflectionInspector : InspectorBase, ICellPoolDataSource, ICacheObjectController { public CacheObjectBase ParentCacheObject { get; set; } @@ -32,44 +37,52 @@ namespace UnityExplorer.Inspectors public bool StaticOnly { get; internal set; } public bool CanWrite => true; + public bool AutoUpdateWanted => autoUpdateToggle.isOn; + private List members = new(); private readonly List filteredMembers = new(); - public bool AutoUpdateWanted => autoUpdateToggle.isOn; + private BindingFlags scopeFlagsFilter; + private string nameFilter; - private BindingFlags FlagsFilter; - private string NameFilter; + private MemberFilter MemberFilter = MemberFilter.All; - private MemberFlags MemberFilter = MemberFlags.All; - private enum MemberFlags - { - None = 0, - Property = 1, - Field = 2, - Constructor = 4, - Method = 8, - All = Property | Field | Method | Constructor, - } + // Updating + + private bool refreshWanted; + private string lastNameFilter; + private BindingFlags lastFlagsFilter; + private MemberFilter lastMemberFilter = MemberFilter.All; + private float timeOfLastAutoUpdate; // UI + internal GameObject mainContentHolder; + private static int LeftGroupWidth { get; set; } + private static int RightGroupWidth { get; set; } + public ScrollPool MemberScrollPool { get; private set; } + public int ItemCount => filteredMembers.Count; + + public UnityObjectWidget UnityWidget; public InputFieldRef HiddenNameText; public Text NameText; public Text AssemblyText; private Toggle autoUpdateToggle; - private string currentBaseTabText; - - private readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f); - private readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f); + internal string currentBaseTabText; private readonly Dictionary scopeFilterButtons = new(); private readonly List memberTypeToggles = new(); private InputFieldRef filterInputField; - // Setup / return + // const + + private readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f); + private readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f); + + // Setup public override void OnBorrowedFromPool(object target) { @@ -105,10 +118,12 @@ namespace UnityExplorer.Inspectors autoUpdateToggle.isOn = false; - UnityObjectRef = null; - ComponentRef = null; - TextureRef = null; - CleanupTextureViewer(); + if (UnityWidget != null) + { + UnityWidget.OnReturnToPool(); + Pool.Return(UnityWidget.GetType(), UnityWidget); + this.UnityWidget = null; + } base.OnReturnToPool(); } @@ -143,18 +158,19 @@ namespace UnityExplorer.Inspectors asmText = Path.GetFileName(TargetType.Assembly.Location); AssemblyText.text = $"Assembly: {asmText}"; - // unity helpers - SetUnityTargets(); + // Unity object helper widget + + this.UnityWidget = UnityObjectWidget.GetUnityWidget(target, TargetType, this); // Get cache members - this.members = CacheMember.GetCacheMembers(Target, TargetType, this); + this.members = CacheMemberFactory.GetCacheMembers(Target, TargetType, this); // reset filters - this.filterInputField.Text = ""; + this.filterInputField.Text = string.Empty; - SetFilter("", StaticOnly ? BindingFlags.Static : BindingFlags.Default); + SetFilter(string.Empty, StaticOnly ? BindingFlags.Static : BindingFlags.Default); scopeFilterButtons[BindingFlags.Default].Component.gameObject.SetActive(!StaticOnly); scopeFilterButtons[BindingFlags.Instance].Component.gameObject.SetActive(!StaticOnly); @@ -166,12 +182,6 @@ namespace UnityExplorer.Inspectors // Updating - private bool refreshWanted; - private string lastNameFilter; - private BindingFlags lastFlagsFilter; - private MemberFlags lastMemberFilter = MemberFlags.All; - private float timeOfLastAutoUpdate; - public override void Update() { if (!this.IsActive) @@ -184,10 +194,10 @@ namespace UnityExplorer.Inspectors } // check filter changes or force-refresh - if (refreshWanted || NameFilter != lastNameFilter || FlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter) + if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter) { - lastNameFilter = NameFilter; - lastFlagsFilter = FlagsFilter; + lastNameFilter = nameFilter; + lastFlagsFilter = scopeFlagsFilter; lastMemberFilter = MemberFilter; FilterMembers(); @@ -200,11 +210,8 @@ namespace UnityExplorer.Inspectors { timeOfLastAutoUpdate = Time.realtimeSinceStartup; - if (this.UnityObjectRef) - { - nameInput.Text = UnityObjectRef.name; - this.Tab.TabText.text = $"{currentBaseTabText} \"{UnityObjectRef.name}\""; - } + if (this.UnityWidget != null) + UnityWidget.Update(); if (AutoUpdateWanted) UpdateDisplayedMembers(); @@ -218,26 +225,26 @@ namespace UnityExplorer.Inspectors // Filtering - public void SetFilter(string filter) => SetFilter(filter, FlagsFilter); + public void SetFilter(string name) => SetFilter(name, scopeFlagsFilter); - public void SetFilter(BindingFlags flagsFilter) => SetFilter(NameFilter, flagsFilter); + public void SetFilter(BindingFlags flags) => SetFilter(nameFilter, flags); - public void SetFilter(string nameFilter, BindingFlags flagsFilter) + public void SetFilter(string name, BindingFlags flags) { - this.NameFilter = nameFilter; + this.nameFilter = name; - if (flagsFilter != FlagsFilter) + if (flags != scopeFlagsFilter) { - var btn = scopeFilterButtons[FlagsFilter].Component; + var btn = scopeFilterButtons[scopeFlagsFilter].Component; RuntimeHelper.SetColorBlock(btn, disabledButtonColor, disabledButtonColor * 1.3f); - this.FlagsFilter = flagsFilter; - btn = scopeFilterButtons[FlagsFilter].Component; + this.scopeFlagsFilter = flags; + btn = scopeFilterButtons[scopeFlagsFilter].Component; RuntimeHelper.SetColorBlock(btn, enabledButtonColor, enabledButtonColor * 1.3f); } } - private void OnMemberTypeToggled(MemberFlags flag, bool val) + private void OnMemberTypeToggled(MemberFilter flag, bool val) { if (!val) MemberFilter &= ~flag; @@ -253,20 +260,20 @@ namespace UnityExplorer.Inspectors { var member = members[i]; - if (FlagsFilter != BindingFlags.Default) + if (scopeFlagsFilter != BindingFlags.Default) { - if (FlagsFilter == BindingFlags.Instance && member.IsStatic - || FlagsFilter == BindingFlags.Static && !member.IsStatic) + if (scopeFlagsFilter == BindingFlags.Instance && member.IsStatic + || scopeFlagsFilter == BindingFlags.Static && !member.IsStatic) continue; } - if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFlags.Method)) - || (member is CacheField && !MemberFilter.HasFlag(MemberFlags.Field)) - || (member is CacheProperty && !MemberFilter.HasFlag(MemberFlags.Property)) - || (member is CacheConstructor && !MemberFilter.HasFlag(MemberFlags.Constructor))) + if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFilter.Method)) + || (member is CacheField && !MemberFilter.HasFlag(MemberFilter.Field)) + || (member is CacheProperty && !MemberFilter.HasFlag(MemberFilter.Property)) + || (member is CacheConstructor && !MemberFilter.HasFlag(MemberFilter.Constructor))) continue; - if (!string.IsNullOrEmpty(NameFilter) && !member.NameForFiltering.ContainsIgnoreCase(NameFilter)) + if (!string.IsNullOrEmpty(nameFilter) && !member.NameForFiltering.ContainsIgnoreCase(nameFilter)) continue; filteredMembers.Add(member); @@ -295,8 +302,6 @@ namespace UnityExplorer.Inspectors // Member cells - public int ItemCount => filteredMembers.Count; - public void OnCellBorrowed(CacheMemberCell cell) { } // not needed public void SetCell(CacheMemberCell cell, int index) @@ -306,9 +311,6 @@ namespace UnityExplorer.Inspectors // Cell layout (fake table alignment) - private static int LeftGroupWidth { get; set; } - private static int RightGroupWidth { get; set; } - internal void SetLayouts() { CalculateLayouts(); @@ -339,8 +341,6 @@ namespace UnityExplorer.Inspectors // UI Construction - private GameObject mainContentHolder; - public override GameObject CreateContent(GameObject parent) { UIRoot = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, true, true, true, 5, @@ -381,8 +381,6 @@ namespace UnityExplorer.Inspectors AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999); - ConstructUnityObjectRow(); - mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2), new Color(0.12f, 0.12f, 0.12f)); UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999); @@ -495,12 +493,12 @@ namespace UnityExplorer.Inspectors toggle.graphic.TryCast().color = color.ToColor() * 0.65f; - MemberFlags flag = type switch + MemberFilter flag = type switch { - MemberTypes.Method => MemberFlags.Method, - MemberTypes.Property => MemberFlags.Property, - MemberTypes.Field => MemberFlags.Field, - MemberTypes.Constructor => MemberFlags.Constructor, + MemberTypes.Method => MemberFilter.Method, + MemberTypes.Property => MemberFilter.Property, + MemberTypes.Field => MemberFilter.Field, + MemberTypes.Constructor => MemberFilter.Constructor, _ => throw new NotImplementedException() }; @@ -508,237 +506,5 @@ namespace UnityExplorer.Inspectors memberTypeToggles.Add(toggle); } - - - // Todo should probably put this in a separate class or maybe as a widget - - #region UNITY OBJECT SPECIFIC - - // Unity object helpers - - private UnityEngine.Object UnityObjectRef; - private Component ComponentRef; - private Texture2D TextureRef; - private bool TextureViewerWanted; - private GameObject unityObjectRow; - private ButtonRef gameObjectButton; - private InputFieldRef nameInput; - private InputFieldRef instanceIdInput; - private ButtonRef textureButton; - private GameObject textureViewer; - - private void SetUnityTargets() - { - if (StaticOnly || !typeof(UnityEngine.Object).IsAssignableFrom(TargetType)) - { - unityObjectRow.SetActive(false); - textureViewer.SetActive(false); - return; - } - - UnityObjectRef = (UnityEngine.Object)Target.TryCast(typeof(UnityEngine.Object)); - unityObjectRow.SetActive(true); - - nameInput.Text = UnityObjectRef.name; - instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString(); - - if (typeof(Component).IsAssignableFrom(TargetType)) - { - ComponentRef = (Component)Target.TryCast(typeof(Component)); - gameObjectButton.Component.gameObject.SetActive(true); - } - else - gameObjectButton.Component.gameObject.SetActive(false); - - if (typeof(Texture2D).IsAssignableFrom(TargetType)) - { - TextureRef = (Texture2D)Target.TryCast(typeof(Texture2D)); - textureButton.Component.gameObject.SetActive(true); - } - else - textureButton.Component.gameObject.SetActive(false); - } - - private void OnGameObjectButtonClicked() - { - if (!ComponentRef) - { - ExplorerCore.LogWarning("Component reference is null or destroyed!"); - return; - } - - InspectorManager.Inspect(ComponentRef.gameObject); - } - - private void ToggleTextureViewer() - { - if (TextureViewerWanted) - { - // disable - TextureViewerWanted = false; - textureViewer.SetActive(false); - mainContentHolder.SetActive(true); - textureButton.ButtonText.text = "View Texture"; - } - else - { - if (!textureImage.sprite) - { - // First show, need to create sprite for displaying texture - SetTextureViewer(); - } - - // enable - TextureViewerWanted = true; - textureViewer.SetActive(true); - mainContentHolder.gameObject.SetActive(false); - textureButton.ButtonText.text = "Hide Texture"; - } - } - - // UI construction - - private void ConstructUnityObjectRow() - { - unityObjectRow = UIFactory.CreateUIObject("UnityObjectRow", UIRoot); - UIFactory.SetLayoutGroup(unityObjectRow, false, false, true, true, 5); - UIFactory.SetLayoutElement(unityObjectRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); - - textureButton = UIFactory.CreateButton(unityObjectRow, "TextureButton", "View Texture", new Color(0.2f, 0.2f, 0.2f)); - UIFactory.SetLayoutElement(textureButton.Component.gameObject, minHeight: 25, minWidth: 150); - textureButton.OnClick += ToggleTextureViewer; - - var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); - UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); - - nameInput = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled"); - UIFactory.SetLayoutElement(nameInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 1000); - nameInput.Component.readOnly = true; - - gameObjectButton = UIFactory.CreateButton(unityObjectRow, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); - UIFactory.SetLayoutElement(gameObjectButton.Component.gameObject, minHeight: 25, minWidth: 160); - gameObjectButton.OnClick += OnGameObjectButtonClicked; - - var instanceLabel = UIFactory.CreateLabel(unityObjectRow, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); - UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); - - instanceIdInput = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR"); - UIFactory.SetLayoutElement(instanceIdInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 0); - instanceIdInput.Component.readOnly = true; - - unityObjectRow.SetActive(false); - - ConstructTextureHelper(); - } - - // Texture viewer helper - - private InputFieldRef textureSavePathInput; - private Image textureImage; - private LayoutElement textureImageLayout; - - private void CleanupTextureViewer() - { - if (textureImage.sprite) - GameObject.Destroy(textureImage.sprite); - - if (TextureViewerWanted) - ToggleTextureViewer(); - } - - private void ConstructTextureHelper() - { - textureViewer = UIFactory.CreateVerticalGroup(UIRoot, "TextureViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5), - new Color(0.1f, 0.1f, 0.1f)); - UIFactory.SetLayoutElement(textureViewer, flexibleWidth: 9999, flexibleHeight: 9999); - - // Save helper - - var saveRowObj = UIFactory.CreateHorizontalGroup(textureViewer, "SaveRow", false, false, true, true, 2, new Vector4(2, 2, 2, 2), - new Color(0.1f, 0.1f, 0.1f)); - - var saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", new Color(0.2f, 0.25f, 0.2f)); - UIFactory.SetLayoutElement(saveBtn.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); - saveBtn.OnClick += OnSaveTextureClicked; - - textureSavePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "..."); - UIFactory.SetLayoutElement(textureSavePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); - - // Actual texture viewer - - var imageViewport = UIFactory.CreateVerticalGroup(textureViewer, "Viewport", false, false, true, true); - imageViewport.GetComponent().color = Color.white; - imageViewport.AddComponent().showMaskGraphic = false; - - var imageObj = UIFactory.CreateUIObject("Image", imageViewport); - var fitter = imageObj.AddComponent(); - fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; - textureImage = imageObj.AddComponent(); - textureImageLayout = UIFactory.SetLayoutElement(imageObj, flexibleWidth: 1, flexibleHeight: 1); - - textureViewer.SetActive(false); - } - - private void SetTextureViewer() - { - if (!this.TextureRef) - return; - - var name = TextureRef.name; - if (string.IsNullOrEmpty(name)) - name = "untitled"; - - textureSavePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); - - var sprite = TextureHelper.CreateSprite(TextureRef); - textureImage.sprite = sprite; - - textureImageLayout.preferredHeight = sprite.rect.height; - // not really working, its always stretched horizontally for some reason. - textureImageLayout.preferredWidth = sprite.rect.width; - } - - private void OnSaveTextureClicked() - { - if (!TextureRef) - { - ExplorerCore.LogWarning("Ref Texture is null, maybe it was destroyed?"); - return; - } - - if (string.IsNullOrEmpty(textureSavePathInput.Text)) - { - ExplorerCore.LogWarning("Save path cannot be empty!"); - return; - } - - var path = textureSavePathInput.Text; - if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) - { - ExplorerCore.LogWarning("Desired save path must end with '.png'!"); - return; - } - - path = IOUtility.EnsureValidFilePath(path); - - if (File.Exists(path)) - File.Delete(path); - - var tex = TextureRef; - - if (!TextureHelper.IsReadable(tex)) - tex = TextureHelper.ForceReadTexture(tex); - - byte[] data = TextureHelper.EncodeToPNG(tex); - File.WriteAllBytes(path, data); - - if (tex != TextureRef) - { - // cleanup temp texture if we had to force-read it. - GameObject.Destroy(tex); - } - } - - #endregion } } diff --git a/src/UI/Widgets/UnityObjects/Texture2DWidget.cs b/src/UI/Widgets/UnityObjects/Texture2DWidget.cs new file mode 100644 index 0000000..e49c746 --- /dev/null +++ b/src/UI/Widgets/UnityObjects/Texture2DWidget.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Config; +using UnityExplorer.Inspectors; +using UnityExplorer.UI.Panels; +using UniverseLib; +using UniverseLib.Runtime; +using UniverseLib.UI; +using UniverseLib.UI.Models; +using UniverseLib.UI.ObjectPool; +using UniverseLib.Utility; + +namespace UnityExplorer.UI.Widgets +{ + public class Texture2DWidget : UnityObjectWidget + { + private Texture2D TextureRef; + + private bool textureViewerWanted; + + private InputFieldRef textureSavePathInput; + private Image textureImage; + private LayoutElement textureImageLayout; + + private ButtonRef textureButton; + private GameObject textureViewer; + + private float realWidth; + private float realHeight; + + public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) + { + base.OnBorrowed(target, targetType, inspector); + + TextureRef = (Texture2D)target.TryCast(typeof(Texture2D)); + textureButton.Component.gameObject.SetActive(true); + + realWidth = TextureRef.width; + realHeight = TextureRef.height; + + if (this.textureViewer) + this.textureViewer.transform.SetParent(inspector.UIRoot.transform); + + InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize; + } + + public override void OnReturnToPool() + { + InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize; + + TextureRef = null; + + if (textureImage.sprite) + GameObject.Destroy(textureImage.sprite); + + if (textureViewerWanted) + ToggleTextureViewer(); + + this.textureViewer.transform.SetParent(Pool.Instance.InactiveHolder.transform); + + base.OnReturnToPool(); + } + + private void ToggleTextureViewer() + { + if (textureViewerWanted) + { + // disable + textureViewerWanted = false; + textureViewer.SetActive(false); + textureButton.ButtonText.text = "View Texture"; + + ParentInspector.mainContentHolder.SetActive(true); + } + else + { + // enable + if (!textureImage.sprite) + SetupTextureViewer(); + + SetImageSize(); + + textureViewerWanted = true; + textureViewer.SetActive(true); + textureButton.ButtonText.text = "Hide Texture"; + + ParentInspector.mainContentHolder.gameObject.SetActive(false); + } + } + + private void SetupTextureViewer() + { + if (!this.TextureRef) + return; + + string name = TextureRef.name; + if (string.IsNullOrEmpty(name)) + name = "untitled"; + + textureSavePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); + + Sprite sprite = TextureHelper.CreateSprite(TextureRef); + textureImage.sprite = sprite; + } + + private void OnInspectorFinishResize(RectTransform _) + { + SetImageSize(); + } + + private void SetImageSize() + { + RuntimeHelper.StartCoroutine(SetImageSizeCoro()); + } + + IEnumerator SetImageSizeCoro() + { + // let unity rebuild layout etc + yield return null; + + RectTransform imageRect = InspectorPanel.Instance.Rect; + + float rectWidth = imageRect.rect.width - 25; + float rectHeight = imageRect.rect.height - 196; + + // If our image is smaller than the viewport, just use 100% scaling + if (realWidth < rectWidth && realHeight < rectHeight) + { + textureImageLayout.minWidth = realWidth; + textureImageLayout.minHeight = realHeight; + } + else // we will need to scale down the image to fit + { + // get the ratio of our viewport dimensions to width and height + float viewWidthRatio = (float)((decimal)rectWidth / (decimal)realWidth); + float viewHeightRatio = (float)((decimal)rectHeight / (decimal)realHeight); + + // if width needs to be scaled more than height + if (viewWidthRatio < viewHeightRatio) + { + textureImageLayout.minWidth = realWidth * viewWidthRatio; + textureImageLayout.minHeight = realHeight * viewWidthRatio; + } + else // if height needs to be scaled more than width + { + textureImageLayout.minWidth = realWidth * viewHeightRatio; + textureImageLayout.minHeight = realHeight * viewHeightRatio; + } + } + } + + private void OnSaveTextureClicked() + { + if (!TextureRef) + { + ExplorerCore.LogWarning("Ref Texture is null, maybe it was destroyed?"); + return; + } + + if (string.IsNullOrEmpty(textureSavePathInput.Text)) + { + ExplorerCore.LogWarning("Save path cannot be empty!"); + return; + } + + string path = textureSavePathInput.Text; + if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) + { + ExplorerCore.LogWarning("Desired save path must end with '.png'!"); + return; + } + + path = IOUtility.EnsureValidFilePath(path); + + if (File.Exists(path)) + File.Delete(path); + + Texture2D tex = TextureRef; + if (!TextureHelper.IsReadable(tex)) + tex = TextureHelper.ForceReadTexture(tex); + + byte[] data = TextureHelper.EncodeToPNG(tex); + File.WriteAllBytes(path, data); + + if (tex != TextureRef) + { + // cleanup temp texture if we had to force-read it. + GameObject.Destroy(tex); + } + } + + public override GameObject CreateContent(GameObject uiRoot) + { + GameObject ret = base.CreateContent(uiRoot); + + // Button + + textureButton = UIFactory.CreateButton(unityObjectRow, "TextureButton", "View Texture", new Color(0.2f, 0.3f, 0.2f)); + textureButton.Transform.SetSiblingIndex(0); + UIFactory.SetLayoutElement(textureButton.Component.gameObject, minHeight: 25, minWidth: 150); + textureButton.OnClick += ToggleTextureViewer; + + // Texture viewer + + textureViewer = UIFactory.CreateVerticalGroup(uiRoot, "TextureViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5), + new Color(0.1f, 0.1f, 0.1f), childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(textureViewer, flexibleWidth: 9999, flexibleHeight: 9999); + + // Save helper + + GameObject saveRowObj = UIFactory.CreateHorizontalGroup(textureViewer, "SaveRow", false, false, true, true, 2, new Vector4(2, 2, 2, 2), + new Color(0.1f, 0.1f, 0.1f)); + + ButtonRef saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", new Color(0.2f, 0.25f, 0.2f)); + UIFactory.SetLayoutElement(saveBtn.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + saveBtn.OnClick += OnSaveTextureClicked; + + textureSavePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "..."); + UIFactory.SetLayoutElement(textureSavePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + + // Actual texture viewer + + //GameObject imageViewport = UIFactory.CreateVerticalGroup(textureViewer, "ImageViewport", false, false, true, true); + //imageRect = imageViewport.GetComponent(); + //UIFactory.SetLayoutElement(imageViewport, flexibleWidth: 9999, flexibleHeight: 9999); + + GameObject imageHolder = UIFactory.CreateUIObject("ImageHolder", textureViewer); + textureImageLayout = UIFactory.SetLayoutElement(imageHolder, 1, 1, 0, 0); + imageHolder.AddComponent().color = Color.clear; + var outline = imageHolder.AddComponent(); + outline.effectColor = Color.black; + outline.effectDistance = new(2, 2); + + var actualImageObj = UIFactory.CreateUIObject("ActualImage", imageHolder); + var actualRect = actualImageObj.GetComponent(); + actualRect.anchorMin = new(0, 0); + actualRect.anchorMax = new(1, 1); + textureImage = actualImageObj.AddComponent(); + + textureViewer.SetActive(false); + + return ret; + } + } +} diff --git a/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs b/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs new file mode 100644 index 0000000..fe84aa1 --- /dev/null +++ b/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs @@ -0,0 +1,133 @@ +using System; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Inspectors; +using UniverseLib; +using UniverseLib.UI; +using UniverseLib.UI.Models; +using UniverseLib.UI.ObjectPool; + +namespace UnityExplorer.UI.Widgets +{ + public class UnityObjectWidget : IPooledObject + { + public UnityEngine.Object UnityObjectRef; + public Component ComponentRef; + public ReflectionInspector ParentInspector; + + protected GameObject unityObjectRow; + protected ButtonRef gameObjectButton; + protected InputFieldRef nameInput; + protected InputFieldRef instanceIdInput; + + // IPooledObject + public GameObject UIRoot { get; set; } + public float DefaultHeight => -1; + + public static UnityObjectWidget GetUnityWidget(object target, Type targetType, ReflectionInspector inspector) + { + if (!typeof(UnityEngine.Object).IsAssignableFrom(targetType)) + return null; + + UnityObjectWidget ret; + + if (targetType == typeof(Texture2D)) + ret = Pool.Borrow(); + else + ret = Pool.Borrow(); + + ret.OnBorrowed(target, targetType, inspector); + return ret; + } + + public virtual void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) + { + this.ParentInspector = inspector; + + if (!this.UIRoot) + CreateContent(inspector.UIRoot); + else + this.UIRoot.transform.SetParent(inspector.UIRoot.transform); + + this.UIRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2); + + UnityObjectRef = (UnityEngine.Object)target.TryCast(typeof(UnityEngine.Object)); + unityObjectRow.SetActive(true); + + nameInput.Text = UnityObjectRef.name; + instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString(); + + if (typeof(Component).IsAssignableFrom(targetType)) + { + ComponentRef = (Component)target.TryCast(typeof(Component)); + gameObjectButton.Component.gameObject.SetActive(true); + } + else + gameObjectButton.Component.gameObject.SetActive(false); + } + + public virtual void OnReturnToPool() + { + UnityObjectRef = null; + ComponentRef = null; + ParentInspector = null; + } + + // Update + + public virtual void Update() + { + if (this.UnityObjectRef) + { + nameInput.Text = UnityObjectRef.name; + ParentInspector.Tab.TabText.text = $"{ParentInspector.currentBaseTabText} \"{UnityObjectRef.name}\""; + } + } + + // UI Listeners + + private void OnGameObjectButtonClicked() + { + if (!ComponentRef) + { + ExplorerCore.LogWarning("Component reference is null or destroyed!"); + return; + } + + InspectorManager.Inspect(ComponentRef.gameObject); + } + + // UI construction + + public virtual GameObject CreateContent(GameObject uiRoot) + { + unityObjectRow = UIFactory.CreateUIObject("UnityObjectRow", uiRoot); + UIFactory.SetLayoutGroup(unityObjectRow, false, false, true, true, 5); + UIFactory.SetLayoutElement(unityObjectRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + + UIRoot = unityObjectRow; + + var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); + + nameInput = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled"); + UIFactory.SetLayoutElement(nameInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 1000); + nameInput.Component.readOnly = true; + + gameObjectButton = UIFactory.CreateButton(unityObjectRow, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(gameObjectButton.Component.gameObject, minHeight: 25, minWidth: 160); + gameObjectButton.OnClick += OnGameObjectButtonClicked; + + var instanceLabel = UIFactory.CreateLabel(unityObjectRow, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); + UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + + instanceIdInput = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR"); + UIFactory.SetLayoutElement(instanceIdInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 0); + instanceIdInput.Component.readOnly = true; + + unityObjectRow.SetActive(false); + + return UIRoot; + } + } +} diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 296659a..ba8b56d 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -226,6 +226,7 @@ + @@ -323,6 +324,8 @@ + +