diff --git a/src/UI/Widgets/UnityObjects/AudioClipWidget.cs b/src/UI/Widgets/UnityObjects/AudioClipWidget.cs index bb677d9..65ba924 100644 --- a/src/UI/Widgets/UnityObjects/AudioClipWidget.cs +++ b/src/UI/Widgets/UnityObjects/AudioClipWidget.cs @@ -22,18 +22,18 @@ namespace UnityExplorer.UI.Widgets static Coroutine CurrentlyPlayingCoroutine; static readonly string zeroLengthString = GetLengthString(0f); - public AudioClip RefAudioClip; - private string fullLengthText; + public AudioClip audioClip; + string fullLengthText; - private ButtonRef toggleButton; - private bool audioPlayerWanted; + ButtonRef toggleButton; + bool audioPlayerWanted; - private GameObject audioPlayerRoot; - private ButtonRef playStopButton; - private Text progressLabel; - private GameObject saveObjectRow; - private InputFieldRef savePathInput; - private GameObject cantSaveRow; + GameObject audioPlayerRoot; + ButtonRef playStopButton; + Text progressLabel; + GameObject saveObjectRow; + InputFieldRef savePathInput; + GameObject cantSaveRow; public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) { @@ -42,10 +42,10 @@ namespace UnityExplorer.UI.Widgets this.audioPlayerRoot.transform.SetParent(inspector.UIRoot.transform); this.audioPlayerRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2); - RefAudioClip = target.TryCast(); - this.fullLengthText = GetLengthString(RefAudioClip.length); + audioClip = target.TryCast(); + this.fullLengthText = GetLengthString(audioClip.length); - if (RefAudioClip.loadType == AudioClipLoadType.DecompressOnLoad) + if (audioClip.loadType == AudioClipLoadType.DecompressOnLoad) { cantSaveRow.SetActive(false); saveObjectRow.SetActive(true); @@ -62,7 +62,7 @@ namespace UnityExplorer.UI.Widgets public override void OnReturnToPool() { - RefAudioClip = null; + audioClip = null; if (audioPlayerWanted) ToggleAudioWidget(); @@ -95,7 +95,7 @@ namespace UnityExplorer.UI.Widgets void SetDefaultSavePath() { - string name = RefAudioClip.name; + string name = audioClip.name; if (string.IsNullOrEmpty(name)) name = "untitled"; savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.wav"); @@ -163,7 +163,7 @@ namespace UnityExplorer.UI.Widgets { playStopButton.ButtonText.text = "Stop Clip"; CurrentlyPlaying = this; - Source.clip = this.RefAudioClip; + Source.clip = this.audioClip; Source.Play(); while (Source.isPlaying) @@ -191,7 +191,7 @@ namespace UnityExplorer.UI.Widgets public void OnSaveClipClicked() { - if (!RefAudioClip) + if (!audioClip) { ExplorerCore.LogWarning("AudioClip is null, maybe it was destroyed?"); return; @@ -212,7 +212,7 @@ namespace UnityExplorer.UI.Widgets if (File.Exists(path)) File.Delete(path); - SavWav.Save(RefAudioClip, path); + SavWav.Save(audioClip, path); } public override GameObject CreateContent(GameObject uiRoot) diff --git a/src/UI/Widgets/UnityObjects/MaterialWidget.cs b/src/UI/Widgets/UnityObjects/MaterialWidget.cs new file mode 100644 index 0000000..e1fbabc --- /dev/null +++ b/src/UI/Widgets/UnityObjects/MaterialWidget.cs @@ -0,0 +1,342 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +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 MaterialWidget : UnityObjectWidget + { + static MaterialWidget() + { + mi_GetTexturePropertyNames = typeof(Material).GetMethod("GetTexturePropertyNames", ArgumentUtility.EmptyTypes); + MaterialWidgetSupported = mi_GetTexturePropertyNames != null; + } + + internal static bool MaterialWidgetSupported { get; } + static readonly MethodInfo mi_GetTexturePropertyNames; + + Material material; + Texture2D activeTexture; + readonly Dictionary textures = new(); + readonly HashSet texturesToDestroy = new(); + + bool textureViewerWanted; + ButtonRef toggleButton; + + GameObject textureViewerRoot; + Dropdown textureDropdown; + InputFieldRef savePathInput; + Image image; + LayoutElement imageLayout; + + public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) + { + base.OnBorrowed(target, targetType, inspector); + + material = target as Material; + + if (material.mainTexture) + SetActiveTexture(material.mainTexture); + + string[] propNames = mi_GetTexturePropertyNames.Invoke(material, ArgumentUtility.EmptyArgs) as string[]; + foreach (string property in propNames) + { + if (material.GetTexture(property) is Texture texture) + { + if (texture.TryCast() is null && texture.TryCast() is null) + continue; + + textures.Add(property, texture); + + if (!activeTexture) + SetActiveTexture(texture); + } + } + + if (textureViewerRoot) + { + textureViewerRoot.transform.SetParent(inspector.UIRoot.transform); + RefreshTextureDropdown(); + } + + InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize; + } + + void SetActiveTexture(Texture texture) + { + if (texture.TryCast() is Texture2D tex2D) + activeTexture = tex2D; + else if (texture.TryCast() is Cubemap cubemap) + { + activeTexture = TextureHelper.UnwrapCubemap(cubemap); + texturesToDestroy.Add(activeTexture); + } + } + + public override void OnReturnToPool() + { + InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize; + + if (texturesToDestroy.Any()) + { + foreach (Texture2D tex in texturesToDestroy) + UnityEngine.Object.Destroy(tex); + texturesToDestroy.Clear(); + } + + material = null; + activeTexture = null; + textures.Clear(); + + if (image.sprite) + UnityEngine.Object.Destroy(image.sprite); + + if (textureViewerWanted) + ToggleTextureViewer(); + + if (textureViewerRoot) + textureViewerRoot.transform.SetParent(Pool.Instance.InactiveHolder.transform); + + base.OnReturnToPool(); + } + + void ToggleTextureViewer() + { + if (textureViewerWanted) + { + // disable + + textureViewerWanted = false; + textureViewerRoot.SetActive(false); + toggleButton.ButtonText.text = "View Material"; + + owner.ContentRoot.SetActive(true); + } + else + { + // enable + + if (!image.sprite) + { + RefreshTextureViewer(); + RefreshTextureDropdown(); + } + + SetImageSize(); + + textureViewerWanted = true; + textureViewerRoot.SetActive(true); + toggleButton.ButtonText.text = "Hide Material"; + + owner.ContentRoot.gameObject.SetActive(false); + } + } + + void RefreshTextureViewer() + { + if (!this.activeTexture) + { + ExplorerCore.LogWarning($"Material has no active textures!"); + savePathInput.Text = string.Empty; + return; + } + + if (image.sprite) + UnityEngine.Object.Destroy(image.sprite); + + string name = activeTexture.name; + if (string.IsNullOrEmpty(name)) + name = "untitled"; + savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); + + Sprite sprite = TextureHelper.CreateSprite(activeTexture); + image.sprite = sprite; + } + + void RefreshTextureDropdown() + { + if (!textureDropdown) + return; + + textureDropdown.options.Clear(); + + foreach (string key in textures.Keys) + textureDropdown.options.Add(new(key)); + + int i = 0; + foreach (Texture value in textures.Values) + { + if (activeTexture.ReferenceEqual(value)) + { + textureDropdown.value = i; + break; + } + i++; + } + + textureDropdown.RefreshShownValue(); + } + + void OnTextureDropdownChanged(int value) + { + Texture tex = textures.ElementAt(value).Value; + if (activeTexture.ReferenceEqual(tex)) + return; + SetActiveTexture(tex); + RefreshTextureViewer(); + } + + void OnInspectorFinishResize() + { + SetImageSize(); + } + + void SetImageSize() + { + if (!imageLayout) + return; + + RuntimeHelper.StartCoroutine(SetImageSizeCoro()); + } + + IEnumerator SetImageSizeCoro() + { + if (!activeTexture) + yield break; + + // 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 (activeTexture.width < rectWidth && activeTexture.height < rectHeight) + { + imageLayout.minWidth = activeTexture.width; + imageLayout.minHeight = activeTexture.height; + } + 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)activeTexture.width); + float viewHeightRatio = (float)((decimal)rectHeight / (decimal)activeTexture.height); + + // if width needs to be scaled more than height + if (viewWidthRatio < viewHeightRatio) + { + imageLayout.minWidth = activeTexture.width * viewWidthRatio; + imageLayout.minHeight = activeTexture.height * viewWidthRatio; + } + else // if height needs to be scaled more than width + { + imageLayout.minWidth = activeTexture.width * viewHeightRatio; + imageLayout.minHeight = activeTexture.height * viewHeightRatio; + } + } + } + + void OnSaveTextureClicked() + { + if (!activeTexture) + { + ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?"); + return; + } + + if (string.IsNullOrEmpty(savePathInput.Text)) + { + ExplorerCore.LogWarning("Save path cannot be empty!"); + return; + } + + string path = savePathInput.Text; + if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) + path += ".png"; + + path = IOUtility.EnsureValidFilePath(path); + + if (File.Exists(path)) + File.Delete(path); + + TextureHelper.SaveTextureAsPNG(activeTexture, path); + } + + public override GameObject CreateContent(GameObject uiRoot) + { + GameObject ret = base.CreateContent(uiRoot); + + // Button + + toggleButton = UIFactory.CreateButton(UIRoot, "MaterialButton", "View Material", new Color(0.2f, 0.3f, 0.2f)); + toggleButton.Transform.SetSiblingIndex(0); + UIFactory.SetLayoutElement(toggleButton.Component.gameObject, minHeight: 25, minWidth: 150); + toggleButton.OnClick += ToggleTextureViewer; + + // Texture viewer + + textureViewerRoot = UIFactory.CreateVerticalGroup(uiRoot, "MaterialViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5), + new Color(0.1f, 0.1f, 0.1f), childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(textureViewerRoot, flexibleWidth: 9999, flexibleHeight: 9999); + + // Buttons holder + + GameObject dropdownRow = UIFactory.CreateHorizontalGroup(textureViewerRoot, "DropdownRow", false, true, true, true, 5, new(3, 3, 3, 3)); + UIFactory.SetLayoutElement(dropdownRow, minHeight: 30, flexibleWidth: 9999); + + Text dropdownLabel = UIFactory.CreateLabel(dropdownRow, "DropdownLabel", "Texture:"); + UIFactory.SetLayoutElement(dropdownLabel.gameObject, minWidth: 75, minHeight: 25); + + GameObject dropdownObj = UIFactory.CreateDropdown(dropdownRow, "TextureDropdown", out textureDropdown, "NOT SET", 13, OnTextureDropdownChanged); + UIFactory.SetLayoutElement(dropdownObj, minWidth: 350, minHeight: 25); + + // Save helper + + GameObject saveRowObj = UIFactory.CreateHorizontalGroup(textureViewerRoot, "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; + + savePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "..."); + UIFactory.SetLayoutElement(savePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + + // Actual texture viewer + + GameObject imageViewport = UIFactory.CreateVerticalGroup(textureViewerRoot, "ImageViewport", false, false, true, true, + bgColor: new(1, 1, 1, 0), childAlignment: TextAnchor.MiddleCenter); + UIFactory.SetLayoutElement(imageViewport, flexibleWidth: 9999, flexibleHeight: 9999); + + GameObject imageHolder = UIFactory.CreateUIObject("ImageHolder", imageViewport); + imageLayout = UIFactory.SetLayoutElement(imageHolder, 1, 1, 0, 0); + + GameObject actualImageObj = UIFactory.CreateUIObject("ActualImage", imageHolder); + RectTransform actualRect = actualImageObj.GetComponent(); + actualRect.anchorMin = new(0, 0); + actualRect.anchorMax = new(1, 1); + image = actualImageObj.AddComponent(); + + textureViewerRoot.SetActive(false); + + return ret; + } + } +} diff --git a/src/UI/Widgets/UnityObjects/Texture2DWidget.cs b/src/UI/Widgets/UnityObjects/Texture2DWidget.cs index 411f4cf..39bf9fc 100644 --- a/src/UI/Widgets/UnityObjects/Texture2DWidget.cs +++ b/src/UI/Widgets/UnityObjects/Texture2DWidget.cs @@ -17,32 +17,51 @@ namespace UnityExplorer.UI.Widgets { public class Texture2DWidget : UnityObjectWidget { - private Texture2D TextureRef; - private float realWidth; - private float realHeight; + Texture2D texture; + bool shouldDestroyTexture; - private bool textureViewerWanted; - private ButtonRef toggleButton; + bool textureViewerWanted; + ButtonRef toggleButton; - private GameObject textureViewerRoot; - private InputFieldRef savePathInput; - private Image image; - private LayoutElement imageLayout; + GameObject textureViewerRoot; + InputFieldRef savePathInput; + Image image; + LayoutElement imageLayout; public override void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) { base.OnBorrowed(target, targetType, inspector); - if (target.TryCast() is Cubemap cubemap) - TextureRef = TextureHelper.UnwrapCubemap(cubemap); - else - TextureRef = target.TryCast(); + if (target is Cubemap cubemap) + { + texture = TextureHelper.UnwrapCubemap(cubemap); + shouldDestroyTexture = true; + } + else if (target is Sprite sprite) + { + if (sprite.packingMode == SpritePackingMode.Tight) + texture = sprite.texture; + else + { + texture = TextureHelper.CopyTexture(sprite.texture, sprite.textureRect); + shouldDestroyTexture = true; + } + } + else if (target is Image image) + { + if (image.sprite.packingMode == SpritePackingMode.Tight) + texture = image.sprite.texture; + else + { + texture = TextureHelper.CopyTexture(image.sprite.texture, image.sprite.textureRect); + shouldDestroyTexture = true; + } + } + else + texture = target as Texture2D; - realWidth = TextureRef.width; - realHeight = TextureRef.height; - - if (this.textureViewerRoot) - this.textureViewerRoot.transform.SetParent(inspector.UIRoot.transform); + if (textureViewerRoot) + textureViewerRoot.transform.SetParent(inspector.UIRoot.transform); InspectorPanel.Instance.Dragger.OnFinishResize += OnInspectorFinishResize; } @@ -51,21 +70,25 @@ namespace UnityExplorer.UI.Widgets { InspectorPanel.Instance.Dragger.OnFinishResize -= OnInspectorFinishResize; - TextureRef = null; + if (shouldDestroyTexture) + UnityEngine.Object.Destroy(texture); + + texture = null; + shouldDestroyTexture = false; if (image.sprite) - GameObject.Destroy(image.sprite); + UnityEngine.Object.Destroy(image.sprite); if (textureViewerWanted) ToggleTextureViewer(); - if (this.textureViewerRoot) - this.textureViewerRoot.transform.SetParent(Pool.Instance.InactiveHolder.transform); + if (textureViewerRoot) + textureViewerRoot.transform.SetParent(Pool.Instance.InactiveHolder.transform); base.OnReturnToPool(); } - private void ToggleTextureViewer() + void ToggleTextureViewer() { if (textureViewerWanted) { @@ -74,7 +97,7 @@ namespace UnityExplorer.UI.Widgets textureViewerRoot.SetActive(false); toggleButton.ButtonText.text = "View Texture"; - ParentInspector.ContentRoot.SetActive(true); + owner.ContentRoot.SetActive(true); } else { @@ -88,30 +111,30 @@ namespace UnityExplorer.UI.Widgets textureViewerRoot.SetActive(true); toggleButton.ButtonText.text = "Hide Texture"; - ParentInspector.ContentRoot.gameObject.SetActive(false); + owner.ContentRoot.gameObject.SetActive(false); } } - private void SetupTextureViewer() + void SetupTextureViewer() { - if (!this.TextureRef) + if (!this.texture) return; - string name = TextureRef.name; + string name = texture.name; if (string.IsNullOrEmpty(name)) name = "untitled"; savePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); - Sprite sprite = TextureHelper.CreateSprite(TextureRef); + Sprite sprite = TextureHelper.CreateSprite(texture); image.sprite = sprite; } - private void OnInspectorFinishResize() + void OnInspectorFinishResize() { SetImageSize(); } - private void SetImageSize() + void SetImageSize() { if (!imageLayout) return; @@ -130,34 +153,34 @@ namespace UnityExplorer.UI.Widgets float rectHeight = imageRect.rect.height - 196; // If our image is smaller than the viewport, just use 100% scaling - if (realWidth < rectWidth && realHeight < rectHeight) + if (texture.width < rectWidth && texture.height < rectHeight) { - imageLayout.minWidth = realWidth; - imageLayout.minHeight = realHeight; + imageLayout.minWidth = texture.width; + imageLayout.minHeight = texture.height; } 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); + float viewWidthRatio = (float)((decimal)rectWidth / (decimal)texture.width); + float viewHeightRatio = (float)((decimal)rectHeight / (decimal)texture.height); // if width needs to be scaled more than height if (viewWidthRatio < viewHeightRatio) { - imageLayout.minWidth = realWidth * viewWidthRatio; - imageLayout.minHeight = realHeight * viewWidthRatio; + imageLayout.minWidth = texture.width * viewWidthRatio; + imageLayout.minHeight = texture.height * viewWidthRatio; } else // if height needs to be scaled more than width { - imageLayout.minWidth = realWidth * viewHeightRatio; - imageLayout.minHeight = realHeight * viewHeightRatio; + imageLayout.minWidth = texture.width * viewHeightRatio; + imageLayout.minHeight = texture.height * viewHeightRatio; } } } - private void OnSaveTextureClicked() + void OnSaveTextureClicked() { - if (!TextureRef) + if (!texture) { ExplorerCore.LogWarning("Texture is null, maybe it was destroyed?"); return; @@ -178,7 +201,7 @@ namespace UnityExplorer.UI.Widgets if (File.Exists(path)) File.Delete(path); - TextureHelper.SaveTextureAsPNG(TextureRef, path); + TextureHelper.SaveTextureAsPNG(texture, path); } public override GameObject CreateContent(GameObject uiRoot) diff --git a/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs b/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs index fa2c2e8..fcf44aa 100644 --- a/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs +++ b/src/UI/Widgets/UnityObjects/UnityObjectWidget.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityExplorer.Inspectors; @@ -11,9 +12,9 @@ namespace UnityExplorer.UI.Widgets { public class UnityObjectWidget : IPooledObject { - public UnityEngine.Object UnityObjectRef; - public Component ComponentRef; - public ReflectionInspector ParentInspector; + public UnityEngine.Object unityObject; + public Component component; + public ReflectionInspector owner; protected ButtonRef gameObjectButton; protected InputFieldRef nameInput; @@ -31,7 +32,13 @@ namespace UnityExplorer.UI.Widgets UnityObjectWidget widget = target switch { Texture2D or Cubemap => Pool.Borrow(), + Sprite s when s.texture => Pool.Borrow(), + Image i when i.sprite?.texture => Pool.Borrow(), + + Material when MaterialWidget.MaterialWidgetSupported => Pool.Borrow(), + AudioClip => Pool.Borrow(), + _ => Pool.Borrow() }; @@ -42,7 +49,7 @@ namespace UnityExplorer.UI.Widgets public virtual void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) { - this.ParentInspector = inspector ?? throw new ArgumentNullException(nameof(inspector)); + this.owner = inspector; if (!this.UIRoot) CreateContent(inspector.UIRoot); @@ -51,15 +58,15 @@ namespace UnityExplorer.UI.Widgets this.UIRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2); - UnityObjectRef = target.TryCast(); + unityObject = target.TryCast(); UIRoot.SetActive(true); - nameInput.Text = UnityObjectRef.name; - instanceIdInput.Text = UnityObjectRef.GetInstanceID().ToString(); + nameInput.Text = unityObject.name; + instanceIdInput.Text = unityObject.GetInstanceID().ToString(); if (typeof(Component).IsAssignableFrom(targetType)) { - ComponentRef = (Component)target.TryCast(typeof(Component)); + component = (Component)target.TryCast(typeof(Component)); gameObjectButton.Component.gameObject.SetActive(true); } else @@ -68,19 +75,20 @@ namespace UnityExplorer.UI.Widgets public virtual void OnReturnToPool() { - UnityObjectRef = null; - ComponentRef = null; - ParentInspector = null; + unityObject = null; + component = null; + owner = null; } // Update public virtual void Update() { - if (this.UnityObjectRef) + if (this.unityObject) { - nameInput.Text = UnityObjectRef.name; - ParentInspector.Tab.TabText.text = $"{ParentInspector.TabButtonText} \"{UnityObjectRef.name}\""; + nameInput.Text = unityObject.name; + + owner.Tab.TabText.text = $"{owner.TabButtonText} \"{unityObject.name}\""; } } @@ -88,13 +96,13 @@ namespace UnityExplorer.UI.Widgets private void OnGameObjectButtonClicked() { - if (!ComponentRef) + if (!component) { ExplorerCore.LogWarning("Component reference is null or destroyed!"); return; } - InspectorManager.Inspect(ComponentRef.gameObject); + InspectorManager.Inspect(component.gameObject); } // UI construction