diff --git a/src/Core/Utility/IOUtility.cs b/src/Core/Utility/IOUtility.cs new file mode 100644 index 0000000..9a97300 --- /dev/null +++ b/src/Core/Utility/IOUtility.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnityExplorer +{ + public static class IOUtility + { + public static string EnsureValid(string path) + { + path = RemoveInvalidChars(path); + + var dir = Path.GetDirectoryName(path); + + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + return path; + } + + public static string RemoveInvalidChars(string path) + { + return string.Concat(path.Split(Path.GetInvalidPathChars())); + } + } +} diff --git a/src/UI/CacheObject/CacheKeyValuePair.cs b/src/UI/CacheObject/CacheKeyValuePair.cs index a69d9b6..f330d8f 100644 --- a/src/UI/CacheObject/CacheKeyValuePair.cs +++ b/src/UI/CacheObject/CacheKeyValuePair.cs @@ -66,17 +66,17 @@ namespace UnityExplorer.UI.CacheObject if (KeyInputWanted) { - kvpCell.KeyInputField.gameObject.SetActive(true); + kvpCell.KeyInputField.UIRoot.SetActive(true); kvpCell.KeyInputTypeLabel.gameObject.SetActive(true); kvpCell.KeyLabel.gameObject.SetActive(false); kvpCell.KeyInspectButton.Button.gameObject.SetActive(false); - kvpCell.KeyInputField.text = KeyInputText; + kvpCell.KeyInputField.Text = KeyInputText; kvpCell.KeyInputTypeLabel.text = KeyInputTypeText; } else { - kvpCell.KeyInputField.gameObject.SetActive(false); + kvpCell.KeyInputField.UIRoot.SetActive(false); kvpCell.KeyInputTypeLabel.gameObject.SetActive(false); kvpCell.KeyLabel.gameObject.SetActive(true); kvpCell.KeyInspectButton.Button.gameObject.SetActive(InspectWanted); diff --git a/src/UI/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs index 549b8a8..b1cf7e4 100644 --- a/src/UI/CacheObject/CacheObjectBase.cs +++ b/src/UI/CacheObject/CacheObjectBase.cs @@ -18,7 +18,6 @@ namespace UnityExplorer.UI.CacheObject { NotEvaluated, Exception, - //NullValue, Boolean, Number, String, @@ -159,17 +158,25 @@ namespace UnityExplorer.UI.CacheObject { if (type == typeof(bool)) return ValueState.Boolean; + else if (type.IsPrimitive || type == typeof(decimal)) return ValueState.Number; + else if (type == typeof(string)) return ValueState.String; + else if (type.IsEnum) return ValueState.Enum; - // todo Color and ValueStruct + else if (type == typeof(Color) || type == typeof(Color32)) + return ValueState.Color; + + // else if (InteractiveValueStruct.SupportsType(type)) + // return ValueState.ValueStruct; else if (typeof(IDictionary).IsAssignableFrom(type)) return ValueState.Dictionary; + else if (typeof(IEnumerable).IsAssignableFrom(type)) return ValueState.Collection; else @@ -313,19 +320,20 @@ namespace UnityExplorer.UI.CacheObject } // inputfield for numbers - cell.InputField.gameObject.SetActive(args.inputActive); + cell.InputField.UIRoot.SetActive(args.inputActive); if (args.inputActive) { - cell.InputField.text = Value.ToString(); - cell.InputField.readOnly = !CanWrite; + cell.InputField.Text = Value.ToString(); + cell.InputField.InputField.readOnly = !CanWrite; } // apply for bool and numbers cell.ApplyButton.Button.gameObject.SetActive(args.applyActive); - // Inspect and IValue (subcontent) buttons - only if last value not null. + // Inspect button only if last value not null. cell.InspectButton.Button.gameObject.SetActive(args.inspectActive && !LastValueWasNull); - // allow IValue for null strings though. + + // allow IValue for null strings though cell.SubContentButton.Button.gameObject.SetActive(args.subContentButtonActive && (!LastValueWasNull || State == ValueState.String)); } @@ -333,12 +341,6 @@ namespace UnityExplorer.UI.CacheObject public virtual void OnCellApplyClicked() { - if (CellView == null) - { - ExplorerCore.LogWarning("Trying to apply CacheMember but current cell reference is null!"); - return; - } - if (State == ValueState.Boolean) SetUserValue(this.CellView.Toggle.isOn); else @@ -351,7 +353,7 @@ namespace UnityExplorer.UI.CacheObject } var val = numberParseMethods[type.AssemblyQualifiedName] - .Invoke(null, new object[] { CellView.InputField.text }); + .Invoke(null, new object[] { CellView.InputField.Text }); SetUserValue(val); } diff --git a/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs index ddecaa6..1b8a5e9 100644 --- a/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs +++ b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs @@ -18,7 +18,7 @@ namespace UnityExplorer.UI.CacheObject.Views public LayoutElement KeyGroupLayout; public Text KeyLabel; public ButtonRef KeyInspectButton; - public InputField KeyInputField; + public InputFieldRef KeyInputField; public Text KeyInputTypeLabel; public static Color EvenColor = new Color(0.07f, 0.07f, 0.07f); @@ -75,10 +75,10 @@ namespace UnityExplorer.UI.CacheObject.Views // input field - var keyInputObj = UIFactory.CreateInputField(keyGroup, "KeyInput", "empty", out KeyInputField); - UIFactory.SetLayoutElement(keyInputObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 0, preferredWidth: 200); + KeyInputField = UIFactory.CreateInputField(keyGroup, "KeyInput", "empty"); + UIFactory.SetLayoutElement(KeyInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 0, preferredWidth: 200); //KeyInputField.lineType = InputField.LineType.MultiLineNewline; - KeyInputField.readOnly = true; + KeyInputField.InputField.readOnly = true; return root; } diff --git a/src/UI/CacheObject/Views/CacheObjectCell.cs b/src/UI/CacheObject/Views/CacheObjectCell.cs index 14ae4dc..dca03c3 100644 --- a/src/UI/CacheObject/Views/CacheObjectCell.cs +++ b/src/UI/CacheObject/Views/CacheObjectCell.cs @@ -50,7 +50,7 @@ namespace UnityExplorer.UI.CacheObject.Views public Text ValueLabel; public Toggle Toggle; public Text ToggleText; - public InputField InputField; + public InputFieldRef InputField; public ButtonRef InspectButton; public ButtonRef SubContentButton; @@ -152,8 +152,8 @@ namespace UnityExplorer.UI.CacheObject.Views ToggleText.color = SignatureHighlighter.KeywordBlue; Toggle.onValueChanged.AddListener(ToggleClicked); - var inputObj = UIFactory.CreateInputField(rightHoriGroup, "InputField", "...", out InputField); - UIFactory.SetLayoutElement(inputObj, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + InputField = UIFactory.CreateInputField(rightHoriGroup, "InputField", "..."); + UIFactory.SetLayoutElement(InputField.UIRoot, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); // Inspect and apply buttons diff --git a/src/UI/CacheObject/Views/EvaluateWidget.cs b/src/UI/CacheObject/Views/EvaluateWidget.cs index af7d4dd..307591c 100644 --- a/src/UI/CacheObject/Views/EvaluateWidget.cs +++ b/src/UI/CacheObject/Views/EvaluateWidget.cs @@ -33,7 +33,7 @@ namespace UnityExplorer.UI.CacheObject.Views private readonly List genericArgLabels = new List(); private readonly List genericAutocompleters = new List(); - private readonly List inputFieldCache = new List(); + private readonly List inputFieldCache = new List(); public void OnBorrowedFromPool(CacheMember owner) { @@ -53,7 +53,7 @@ namespace UnityExplorer.UI.CacheObject.Views public void OnReturnToPool() { foreach (var input in inputFieldCache) - input.text = ""; + input.Text = ""; this.Owner = null; } @@ -223,11 +223,11 @@ namespace UnityExplorer.UI.CacheObject.Views labelList.Add(label); label.horizontalOverflow = HorizontalWrapMode.Wrap; - var inputObj = UIFactory.CreateInputField(horiGroup, "InputField", "...", out InputField inputField); - UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000); - inputField.lineType = InputField.LineType.MultiLineNewline; - inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - inputField.onValueChanged.AddListener((string val) => { inputArray[index] = val; }); + var inputField = UIFactory.CreateInputField(horiGroup, "InputField", "..."); + UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000); + inputField.InputField.lineType = InputField.LineType.MultiLineNewline; + inputField.UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + inputField.OnValueChanged += (string val) => { inputArray[index] = val; }; inputFieldCache.Add(inputField); if (autocomplete) diff --git a/src/UI/IValues/InteractiveString.cs b/src/UI/IValues/InteractiveString.cs new file mode 100644 index 0000000..26e7952 --- /dev/null +++ b/src/UI/IValues/InteractiveString.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.IValues +{ + public class InteractiveString : InteractiveValue + { + private string RealValue; + public string EditedValue = ""; + + public InputFieldRef inputField; + public ButtonRef ApplyButton; + + public GameObject SaveFileRow; + public InputFieldRef SaveFilePath; + + public override void OnBorrowed(CacheObjectBase owner) + { + base.OnBorrowed(owner); + + inputField.InputField.readOnly = !owner.CanWrite; + ApplyButton.Button.gameObject.SetActive(owner.CanWrite); + + SaveFilePath.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, "untitled.txt"); + } + + private bool IsStringTooLong(string s) + { + if (s == null) + return false; + + return s.Length >= UIManager.MAX_INPUTFIELD_CHARS; + } + + public override void SetValue(object value) + { + RealValue = value as string; + SaveFileRow.SetActive(IsStringTooLong(RealValue)); + + if (value == null) + { + inputField.Text = ""; + EditedValue = ""; + } + else + { + EditedValue = (string)value; + inputField.Text = EditedValue; + } + } + + private void OnApplyClicked() + { + CurrentOwner.SetValueFromIValue(EditedValue); + } + + private void OnInputChanged(string input) + { + EditedValue = input; + + if (IsStringTooLong(EditedValue)) + { + ExplorerCore.LogWarning("InputField length has reached maximum character count!"); + } + } + + private void OnSaveFileClicked() + { + if (RealValue == null) + return; + + if (string.IsNullOrEmpty(SaveFilePath.Text)) + { + ExplorerCore.LogWarning("Cannot save an empty file path!"); + return; + } + + var path = IOUtility.EnsureValid(SaveFilePath.Text); + + if (File.Exists(path)) + File.Delete(path); + + File.WriteAllText(path, RealValue); + } + + public override GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveString", false, false, true, true, 3, new Vector4(4, 4, 4, 4), + new Color(0.06f, 0.06f, 0.06f)); + + // Save to file helper + + SaveFileRow = UIFactory.CreateUIObject("SaveFileRow", UIRoot); + UIFactory.SetLayoutElement(SaveFileRow, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(SaveFileRow, false, true, true, true, 3); + + UIFactory.CreateLabel(SaveFileRow, "Info", "String is too long! Save to file if you want to see the full string.", + TextAnchor.MiddleLeft); + + var horizRow = UIFactory.CreateUIObject("Horiz", SaveFileRow); + UIFactory.SetLayoutGroup(horizRow, false, false, true, true, 4); + + var saveButton = UIFactory.CreateButton(horizRow, "SaveButton", "Save file"); + UIFactory.SetLayoutElement(saveButton.Button.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + saveButton.OnClick += OnSaveFileClicked; + + SaveFilePath = UIFactory.CreateInputField(horizRow, "SaveInput", "..."); + UIFactory.SetLayoutElement(SaveFilePath.UIRoot, minHeight: 25, flexibleWidth: 9999); + + // Main Input / apply + + ApplyButton = UIFactory.CreateButton(UIRoot, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f)); + UIFactory.SetLayoutElement(ApplyButton.Button.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + ApplyButton.OnClick += OnApplyClicked; + + inputField = UIFactory.CreateInputField(UIRoot, "InputField", "empty"); + inputField.UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 500, flexibleWidth: 9999); + inputField.InputField.lineType = InputField.LineType.MultiLineNewline; + inputField.OnValueChanged += OnInputChanged; + + return UIRoot; + } + + } +} diff --git a/src/UI/IValues/InteractiveValue.cs b/src/UI/IValues/InteractiveValue.cs index 361dcdd..730fa85 100644 --- a/src/UI/IValues/InteractiveValue.cs +++ b/src/UI/IValues/InteractiveValue.cs @@ -9,10 +9,29 @@ using UnityExplorer.UI.ObjectPool; namespace UnityExplorer.UI.IValues { - public class InteractiveValue : IPooledObject + public abstract class InteractiveValue : IPooledObject { - public GameObject UIRoot { get; set; } + public static Type GetIValueTypeForState(ValueState state) + { + switch (state) + { + case ValueState.String: + return typeof(InteractiveString); + //case ValueState.Enum: + // return typeof(InteractiveEnum); + case ValueState.Collection: + return typeof(InteractiveList); + case ValueState.Dictionary: + return typeof(InteractiveDictionary); + //case ValueState.ValueStruct: + // return typeof(InteractiveValueStruct); + //case ValueState.Color: + // return typeof(InteractiveColor); + default: return typeof(InteractiveValue); + } + } + public GameObject UIRoot { get; set; } public float DefaultHeight => -1f; public virtual bool CanWrite => this.CurrentOwner.CanWrite; @@ -20,30 +39,6 @@ namespace UnityExplorer.UI.IValues public CacheObjectBase CurrentOwner => m_owner; private CacheObjectBase m_owner; - //public object EditedValue { get; private set; } - - public virtual void SetLayout() { } - - public static Type GetIValueTypeForState(ValueState state) - { - switch (state) - { - //case ValueState.String: - // return null; - //case ValueState.Enum: - // return null; - case ValueState.Collection: - return typeof(InteractiveList); - case ValueState.Dictionary: - return typeof(InteractiveDictionary); - //case ValueState.ValueStruct: - // return null; - //case ValueState.Color: - // return null; - default: return typeof(InteractiveValue); - } - } - public virtual void OnBorrowed(CacheObjectBase owner) { if (this.m_owner != null) @@ -63,26 +58,26 @@ namespace UnityExplorer.UI.IValues this.m_owner = null; } - public virtual void SetValue(object value) { } + public abstract void SetValue(object value); - //public virtual void SetValue(object value) + public virtual void SetLayout() { } + + public abstract GameObject CreateContent(GameObject parent); + + // + //public virtual GameObject CreateContent(GameObject parent) //{ - // this.EditedValue = value; + // UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); + // UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + // UIFactory.SetLayoutGroup(UIRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); + // + // UIFactory.CreateLabel(UIRoot, "Label", "this is an ivalue", TextAnchor.MiddleLeft); + // UIFactory.CreateInputField(UIRoot, "InputFIeld", "...", out var input); + // UIFactory.SetLayoutElement(input.gameObject, minHeight: 25, flexibleHeight: 500); + // input.lineType = InputField.LineType.MultiLineNewline; + // input.gameObject.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + // + // return UIRoot; //} - - public virtual GameObject CreateContent(GameObject parent) - { - UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); - UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - UIFactory.SetLayoutGroup(UIRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); - - UIFactory.CreateLabel(UIRoot, "Label", "this is an ivalue", TextAnchor.MiddleLeft); - UIFactory.CreateInputField(UIRoot, "InputFIeld", "...", out var input); - UIFactory.SetLayoutElement(input.gameObject, minHeight: 25, flexibleHeight: 500); - input.lineType = InputField.LineType.MultiLineNewline; - input.gameObject.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - - return UIRoot; - } } } diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index a2ba9f7..9e9513f 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -52,15 +52,15 @@ namespace UnityExplorer.UI.Inspectors private bool TextureViewerWanted; private GameObject unityObjectRow; private ButtonRef gameObjectButton; - private InputField nameInput; - private InputField instanceIdInput; + private InputFieldRef nameInput; + private InputFieldRef instanceIdInput; private ButtonRef textureButton; private GameObject textureViewer; private readonly Color disabledButtonColor = new Color(0.24f, 0.24f, 0.24f); private readonly Color enabledButtonColor = new Color(0.2f, 0.27f, 0.2f); private readonly Dictionary scopeFilterButtons = new Dictionary(); - private InputField filterInputField; + private InputFieldRef filterInputField; //private LayoutElement memberTitleLayout; @@ -143,7 +143,7 @@ namespace UnityExplorer.UI.Inspectors // Get cache members, and set filter to default this.members = CacheMember.GetCacheMembers(Target, TargetType, this); - this.filterInputField.text = ""; + this.filterInputField.Text = ""; SetFilter("", StaticOnly ? BindingFlags.Static : BindingFlags.Instance); refreshWanted = true; } @@ -345,9 +345,10 @@ namespace UnityExplorer.UI.Inspectors var nameLabel = UIFactory.CreateLabel(filterRow, "NameFilterLabel", "Filter names:", TextAnchor.MiddleLeft, Color.grey); UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 90, flexibleWidth: 0); - var nameFilterObj = UIFactory.CreateInputField(filterRow, "NameFilterInput", "...", out filterInputField); - UIFactory.SetLayoutElement(nameFilterObj, minHeight: 25, flexibleWidth: 300); - filterInputField.onValueChanged.AddListener((string val) => { SetFilter(val); }); + + filterInputField = UIFactory.CreateInputField(filterRow, "NameFilterInput", "..."); + UIFactory.SetLayoutElement(filterInputField.UIRoot, minHeight: 25, flexibleWidth: 300); + filterInputField.OnValueChanged += (string val) => { SetFilter(val); }; var spacer = UIFactory.CreateUIObject("Spacer", filterRow); UIFactory.SetLayoutElement(spacer, minWidth: 25); @@ -407,8 +408,8 @@ namespace UnityExplorer.UI.Inspectors ObjectRef = (UnityEngine.Object)Target.TryCast(typeof(UnityEngine.Object)); unityObjectRow.SetActive(true); - nameInput.text = ObjectRef.name; - instanceIdInput.text = ObjectRef.GetInstanceID().ToString(); + nameInput.Text = ObjectRef.name; + instanceIdInput.Text = ObjectRef.GetInstanceID().ToString(); if (typeof(Component).IsAssignableFrom(TargetType)) { @@ -483,16 +484,16 @@ namespace UnityExplorer.UI.Inspectors var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); - var nameInputObj = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled", out nameInput); - UIFactory.SetLayoutElement(nameInputObj, minHeight: 25, minWidth: 100, flexibleWidth: 1000); - nameInput.readOnly = true; + nameInput = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled"); + UIFactory.SetLayoutElement(nameInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 1000); + nameInput.InputField.readOnly = true; var instanceLabel = UIFactory.CreateLabel(unityObjectRow, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); - var instanceInputObj = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR", out instanceIdInput); - UIFactory.SetLayoutElement(instanceInputObj, minHeight: 25, minWidth: 100, flexibleWidth: 0); - instanceIdInput.readOnly = true; + instanceIdInput = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR"); + UIFactory.SetLayoutElement(instanceIdInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 0); + instanceIdInput.InputField.readOnly = true; unityObjectRow.SetActive(false); @@ -501,7 +502,7 @@ namespace UnityExplorer.UI.Inspectors // Texture viewer helper - private InputField textureSavePathInput; + private InputFieldRef textureSavePathInput; private Image textureImage; private LayoutElement textureImageLayout; @@ -529,8 +530,8 @@ namespace UnityExplorer.UI.Inspectors UIFactory.SetLayoutElement(saveBtn.Button.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); saveBtn.OnClick += OnSaveTextureClicked; - var inputObj = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...", out textureSavePathInput); - UIFactory.SetLayoutElement(inputObj, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + textureSavePathInput = UIFactory.CreateInputField(saveRowObj, "SaveInput", "..."); + UIFactory.SetLayoutElement(textureSavePathInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 9999); // Actual texture viewer @@ -553,7 +554,7 @@ namespace UnityExplorer.UI.Inspectors if (string.IsNullOrEmpty(name)) name = "untitled"; - textureSavePathInput.text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); + textureSavePathInput.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); var sprite = TextureUtilProvider.Instance.CreateSprite(TextureRef); textureImage.sprite = sprite; @@ -570,22 +571,20 @@ namespace UnityExplorer.UI.Inspectors return; } - if (string.IsNullOrEmpty(textureSavePathInput.text)) + if (string.IsNullOrEmpty(textureSavePathInput.Text)) { ExplorerCore.LogWarning("Save path cannot be empty!"); return; } - var path = textureSavePathInput.text; + var path = textureSavePathInput.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); + path = IOUtility.EnsureValid(path); if (File.Exists(path)) File.Delete(path); diff --git a/src/UI/Widgets/ButtonRef.cs b/src/UI/Models/ButtonRef.cs similarity index 90% rename from src/UI/Widgets/ButtonRef.cs rename to src/UI/Models/ButtonRef.cs index ac5d9b2..8f00b17 100644 --- a/src/UI/Widgets/ButtonRef.cs +++ b/src/UI/Models/ButtonRef.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine.UI; +using UnityExplorer.UI.Models; -namespace UnityExplorer.UI.Widgets +namespace UnityExplorer.UI { // A simple helper class to handle a button's OnClick more effectively. diff --git a/src/UI/Models/InputFieldRef.cs b/src/UI/Models/InputFieldRef.cs new file mode 100644 index 0000000..5fc4596 --- /dev/null +++ b/src/UI/Models/InputFieldRef.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Models; + +namespace UnityExplorer.UI +{ + public class InputFieldRef : UIBehaviourModel + { + public InputFieldRef(InputField InputField) + { + this.InputField = InputField; + Rect = InputField.GetComponent(); + PlaceholderText = InputField.placeholder.TryCast(); + InputField.onValueChanged.AddListener(OnInputChanged); + } + + public event Action OnValueChanged; + + public InputField InputField; + public Text PlaceholderText; + private readonly RectTransform Rect; + + public string Text + { + get => InputField.text; + set => InputField.text = value; + } + + private bool updatedWanted; + + private void OnInputChanged(string value) + { + updatedWanted = true; + } + + public override void Update() + { + if (updatedWanted) + { + LayoutRebuilder.ForceRebuildLayoutImmediate(Rect); + + OnValueChanged?.Invoke(InputField.text); + updatedWanted = false; + } + } + + public override GameObject UIRoot => InputField.gameObject; + + public override void ConstructUI(GameObject parent) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/UI/ObjectExplorer/ObjectSearch.cs b/src/UI/ObjectExplorer/ObjectSearch.cs index 987a36f..c7f8a58 100644 --- a/src/UI/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/ObjectExplorer/ObjectSearch.cs @@ -43,7 +43,7 @@ namespace UnityExplorer.UI.Panels private GameObject sceneFilterRow; private GameObject childFilterRow; private GameObject unityObjectClassRow; - private InputField nameInputField; + private InputFieldRef nameInputField; private Text resultsLabel; @@ -54,16 +54,16 @@ namespace UnityExplorer.UI.Panels cachedCellTexts.Clear(); if (m_context == SearchContext.Singleton) - currentResults = SearchProvider.SingletonSearch(nameInputField.text); + currentResults = SearchProvider.SingletonSearch(nameInputField.Text); else if (m_context == SearchContext.StaticClass) - currentResults = SearchProvider.StaticClassSearch(nameInputField.text); + currentResults = SearchProvider.StaticClassSearch(nameInputField.Text); else { string compType = ""; if (m_context == SearchContext.UnityObject) compType = this.desiredTypeInput; - currentResults = SearchProvider.UnityObjectSearch(nameInputField.text, compType, m_context, m_childFilter, m_sceneFilter); + currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, compType, m_context, m_childFilter, m_sceneFilter); } dataHandler.RefreshData(); @@ -180,11 +180,11 @@ namespace UnityExplorer.UI.Panels var unityClassLbl = UIFactory.CreateLabel(unityObjectClassRow, "UnityClassLabel", "Custom Type:", TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(unityClassLbl.gameObject, minWidth: 110, flexibleWidth: 0); - var classInputObj = UIFactory.CreateInputField(unityObjectClassRow, "ClassInput", "...", out var classInputField); - UIFactory.SetLayoutElement(classInputObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + var classInputField = UIFactory.CreateInputField(unityObjectClassRow, "ClassInput", "..."); + UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField); - classInputField.onValueChanged.AddListener(OnTypeInputChanged); + classInputField.OnValueChanged += OnTypeInputChanged; //unityObjectClassRow.SetActive(false); @@ -226,8 +226,8 @@ namespace UnityExplorer.UI.Panels var nameLbl = UIFactory.CreateLabel(nameRow, "NameFilterLabel", "Name contains:", TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(nameLbl.gameObject, minWidth: 110, flexibleWidth: 0); - var nameInputObj = UIFactory.CreateInputField(nameRow, "NameFilterInput", "...", out this.nameInputField); - UIFactory.SetLayoutElement(nameInputObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + nameInputField = UIFactory.CreateInputField(nameRow, "NameFilterInput", "..."); + UIFactory.SetLayoutElement(nameInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); // Search button diff --git a/src/UI/ObjectExplorer/SceneExplorer.cs b/src/UI/ObjectExplorer/SceneExplorer.cs index e5c8505..4b07dbf 100644 --- a/src/UI/ObjectExplorer/SceneExplorer.cs +++ b/src/UI/ObjectExplorer/SceneExplorer.cs @@ -173,11 +173,12 @@ namespace UnityExplorer.UI.Panels UIFactory.SetLayoutElement(filterRow, minHeight: 25, flexibleHeight: 0); //Filter input field - var inputFieldObj = UIFactory.CreateInputField(filterRow, "FilterInput", "Search...", out InputField inputField, 13); - inputField.targetGraphic.color = new Color(0.2f, 0.2f, 0.2f); - RuntimeProvider.Instance.SetColorBlock(inputField, new Color(0.4f, 0.4f, 0.4f), new Color(0.2f, 0.2f, 0.2f), new Color(0.08f, 0.08f, 0.08f)); - UIFactory.SetLayoutElement(inputFieldObj, minHeight: 25); - inputField.onValueChanged.AddListener(OnFilterInput); + var inputField = UIFactory.CreateInputField(filterRow, "FilterInput", "Search..."); + inputField.InputField.targetGraphic.color = new Color(0.2f, 0.2f, 0.2f); + RuntimeProvider.Instance.SetColorBlock(inputField.InputField, new Color(0.4f, 0.4f, 0.4f), new Color(0.2f, 0.2f, 0.2f), + new Color(0.08f, 0.08f, 0.08f)); + UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25); + inputField.OnValueChanged += OnFilterInput; // refresh row diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs index 9c9dfd6..ec7ff4b 100644 --- a/src/UI/Panels/CSConsolePanel.cs +++ b/src/UI/Panels/CSConsolePanel.cs @@ -20,12 +20,12 @@ namespace UnityExplorer.UI.Panels public static CSConsolePanel Instance { get; private set; } - public InputField InputField { get; private set; } + public InputFieldRef InputField { get; private set; } public Text InputText { get; private set; } public Text HighlightText { get; private set; } public Action OnInputChanged; - private float m_timeOfLastInputInvoke; + //private float m_timeOfLastInputInvoke; public Action OnResetClicked; public Action OnCompileClicked; @@ -40,15 +40,15 @@ namespace UnityExplorer.UI.Panels public void UseSuggestion(string suggestion) { - string input = InputField.text; + string input = InputField.Text; input = input.Insert(m_lastCaretPosition, suggestion); - InputField.text = input; + InputField.Text = input; m_desiredCaretFix = m_lastCaretPosition += suggestion.Length; - var color = InputField.selectionColor; + var color = InputField.InputField.selectionColor; color.a = 0f; - InputField.selectionColor = color; + InputField.InputField.selectionColor = color; } private void InvokeOnValueChanged(string value) @@ -56,10 +56,10 @@ namespace UnityExplorer.UI.Panels if (value.Length == UIManager.MAX_INPUTFIELD_CHARS) ExplorerCore.LogWarning($"Reached maximum InputField character length! ({UIManager.MAX_INPUTFIELD_CHARS})"); - if (m_timeOfLastInputInvoke.OccuredEarlierThanDefault()) - return; - - m_timeOfLastInputInvoke = Time.realtimeSinceStartup; + //if (m_timeOfLastInputInvoke.OccuredEarlierThanDefault()) + // return; + // + //m_timeOfLastInputInvoke = Time.realtimeSinceStartup; OnInputChanged?.Invoke(value); } @@ -71,23 +71,23 @@ namespace UnityExplorer.UI.Panels { if (!m_fixWaiting) { - EventSystem.current.SetSelectedGameObject(InputField.gameObject, null); + EventSystem.current.SetSelectedGameObject(InputField.UIRoot, null); m_fixWaiting = true; } else { - InputField.caretPosition = m_desiredCaretFix; - InputField.selectionFocusPosition = m_desiredCaretFix; - var color = InputField.selectionColor; + InputField.InputField.caretPosition = m_desiredCaretFix; + InputField.InputField.selectionFocusPosition = m_desiredCaretFix; + var color = InputField.InputField.selectionColor; color.a = m_defaultInputFieldAlpha; - InputField.selectionColor = color; + InputField.InputField.selectionColor = color; m_fixWaiting = false; m_desiredCaretFix = -1; } } - else if (InputField.caretPosition > 0) - m_lastCaretPosition = InputField.caretPosition; + else if (InputField.InputField.caretPosition > 0) + m_lastCaretPosition = InputField.InputField.caretPosition; } // Saving @@ -160,15 +160,15 @@ namespace UnityExplorer.UI.Panels //var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsoleManager.STARTUP_TEXT, // out InputFieldScroller consoleScroll, fontSize); - var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsoleManager.STARTUP_TEXT, out var inputField, fontSize); - InputField = inputField.InputField; - m_defaultInputFieldAlpha = InputField.selectionColor.a; - InputField.onValueChanged.AddListener(InvokeOnValueChanged); + var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsoleManager.STARTUP_TEXT, out var inputScroller, fontSize); + InputField = inputScroller.InputField; + m_defaultInputFieldAlpha = InputField.InputField.selectionColor.a; + InputField.OnValueChanged += InvokeOnValueChanged; - var placeHolderText = InputField.placeholder.GetComponent(); + var placeHolderText = InputField.PlaceholderText; placeHolderText.fontSize = fontSize; - InputText = InputField.textComponent; + InputText = InputField.InputField.textComponent; InputText.supportRichText = false; InputText.color = new Color(1, 1, 1, 0.5f); diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index e629c65..d3c012c 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -454,36 +454,10 @@ namespace UnityExplorer.UI return toggleObj; } - // Little helper class to force rebuild of an input field's layout on value change. - // This is limited to once per frame per input field, so its not too expensive. - private class InputFieldRefresher - { - private float timeOfLastRebuild; - private readonly RectTransform rectTransform; - - public InputFieldRefresher(InputField inputField) - { - if (!inputField) - return; - - rectTransform = inputField.GetComponent(); - - inputField.onValueChanged.AddListener((string val) => - { - if (timeOfLastRebuild.OccuredEarlierThanDefault()) - { - timeOfLastRebuild = Time.realtimeSinceStartup; - LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform); - } - }); - } - } - /// /// Create a standard InputField control. /// - public static GameObject CreateInputField(GameObject parent, string name, string placeHolderText, out InputField inputField, - int fontSize = 14, int alignment = 3, int wrap = 0) + public static InputFieldRef CreateInputField(GameObject parent, string name, string placeHolderText) { GameObject mainObj = CreateUIObject(name, parent); //SetLayoutGroup(mainObj, true, true, true, true); @@ -492,7 +466,7 @@ namespace UnityExplorer.UI mainImage.type = Image.Type.Sliced; mainImage.color = new Color(0.04f, 0.04f, 0.04f, 0.75f); - inputField = mainObj.AddComponent(); + var inputField = mainObj.AddComponent(); Navigation nav = inputField.navigation; nav.mode = Navigation.Mode.None; inputField.navigation = nav; @@ -521,9 +495,9 @@ namespace UnityExplorer.UI SetDefaultTextValues(placeholderText); placeholderText.text = placeHolderText ?? "..."; placeholderText.color = new Color(0.5f, 0.5f, 0.5f, 1.0f); - placeholderText.horizontalOverflow = (HorizontalWrapMode)wrap; - placeholderText.alignment = (TextAnchor)alignment; - placeholderText.fontSize = fontSize; + placeholderText.horizontalOverflow = HorizontalWrapMode.Wrap; + placeholderText.alignment = TextAnchor.MiddleLeft; + placeholderText.fontSize = 14; RectTransform placeHolderRect = placeHolderObj.GetComponent(); placeHolderRect.anchorMin = Vector2.zero; @@ -540,9 +514,9 @@ namespace UnityExplorer.UI SetDefaultTextValues(inputText); inputText.text = ""; inputText.color = new Color(1f, 1f, 1f, 1f); - inputText.horizontalOverflow = (HorizontalWrapMode)wrap; - inputText.alignment = (TextAnchor)alignment; - inputText.fontSize = fontSize; + inputText.horizontalOverflow = HorizontalWrapMode.Wrap; + inputText.alignment = TextAnchor.MiddleLeft; + inputText.fontSize = 14; RectTransform inputTextRect = inputTextObj.GetComponent(); inputTextRect.anchorMin = Vector2.zero; @@ -555,9 +529,7 @@ namespace UnityExplorer.UI inputField.textComponent = inputText; inputField.characterLimit = UIManager.MAX_INPUTFIELD_CHARS; - new InputFieldRefresher(inputField); - - return mainObj; + return new InputFieldRef(inputField); } /// @@ -919,7 +891,10 @@ namespace UnityExplorer.UI // Input Field - var content = CreateInputField(viewportObj, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); + var inputField = CreateInputField(viewportObj, "InputField", placeHolderText); + var content = inputField.UIRoot; + + //var content = CreateInputField(viewportObj, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); SetLayoutElement(content, flexibleHeight: 9999, flexibleWidth: 9999); var contentRect = content.GetComponent(); contentRect.pivot = new Vector2(0, 1); @@ -927,8 +902,8 @@ namespace UnityExplorer.UI contentRect.anchorMax = new Vector2(1, 1); contentRect.offsetMin = new Vector2(2, 0); contentRect.offsetMax = new Vector2(2, 0); - inputField.lineType = InputField.LineType.MultiLineNewline; - inputField.targetGraphic.color = color; + inputField.InputField.lineType = InputField.LineType.MultiLineNewline; + inputField.InputField.targetGraphic.color = color; // Slider diff --git a/src/UI/Widgets/AutoComplete/AutoCompleter.cs b/src/UI/Widgets/AutoComplete/AutoCompleter.cs index 4faaa73..ccce019 100644 --- a/src/UI/Widgets/AutoComplete/AutoCompleter.cs +++ b/src/UI/Widgets/AutoComplete/AutoCompleter.cs @@ -76,11 +76,11 @@ namespace UnityExplorer.UI.Widgets.AutoComplete if (suggestions.Any() && CurrentHandler != null) { - if (!CurrentHandler.InputField.gameObject.activeInHierarchy) + if (!CurrentHandler.InputField.UIRoot.activeInHierarchy) ReleaseOwnership(CurrentHandler); else { - lastCaretPos = CurrentHandler.InputField.caretPosition; + lastCaretPos = CurrentHandler.InputField.InputField.caretPosition; UpdatePosition(); } } @@ -142,13 +142,13 @@ namespace UnityExplorer.UI.Widgets.AutoComplete private void UpdatePosition() { - if (CurrentHandler == null || !CurrentHandler.InputField.isFocused) + if (CurrentHandler == null || !CurrentHandler.InputField.InputField.isFocused) return; Vector3 pos; var input = CurrentHandler.InputField; - var textGen = input.textComponent.cachedTextGenerator; + var textGen = input.InputField.textComponent.cachedTextGenerator; int caretPos = 0; if (CurrentHandler.AnchorToCaretPosition) { @@ -159,7 +159,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete } pos = textGen.characters[caretPos].cursorPos; - pos = input.transform.TransformPoint(pos); + pos = input.UIRoot.transform.TransformPoint(pos); uiRoot.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0); diff --git a/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs b/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs index 61e8988..6ff064b 100644 --- a/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs +++ b/src/UI/Widgets/AutoComplete/ISuggestionProvider.cs @@ -9,7 +9,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { public interface ISuggestionProvider { - InputField InputField { get; } + InputFieldRef InputField { get; } bool AnchorToCaretPosition { get; } void OnSuggestionClicked(Suggestion suggestion); diff --git a/src/UI/Widgets/AutoComplete/TypeCompleter.cs b/src/UI/Widgets/AutoComplete/TypeCompleter.cs index ec63955..0668624 100644 --- a/src/UI/Widgets/AutoComplete/TypeCompleter.cs +++ b/src/UI/Widgets/AutoComplete/TypeCompleter.cs @@ -21,20 +21,20 @@ namespace UnityExplorer.UI.Widgets.AutoComplete public Type BaseType { get; set; } public Type[] GenericConstraints { get; set; } - public InputField InputField { get; } + public InputFieldRef InputField { get; } public bool AnchorToCaretPosition => false; private readonly List suggestions = new List(); - private float timeOfLastCheck; + //private float timeOfLastCheck; private HashSet allowedTypes; - public TypeCompleter(Type baseType, InputField inputField) + public TypeCompleter(Type baseType, InputFieldRef inputField) { BaseType = baseType; InputField = inputField; - inputField.onValueChanged.AddListener(OnInputFieldChanged); + inputField.OnValueChanged += OnInputFieldChanged; if (BaseType != null) CacheTypes(); @@ -47,9 +47,9 @@ namespace UnityExplorer.UI.Widgets.AutoComplete public void OnSuggestionClicked(Suggestion suggestion) { - timeOfLastCheck = Time.realtimeSinceStartup; + //timeOfLastCheck = Time.realtimeSinceStartup; - InputField.text = suggestion.UnderlyingValue; + InputField.Text = suggestion.UnderlyingValue; SuggestionClicked?.Invoke(suggestion); suggestions.Clear(); @@ -58,10 +58,10 @@ namespace UnityExplorer.UI.Widgets.AutoComplete private void OnInputFieldChanged(string value) { - if (!timeOfLastCheck.OccuredEarlierThanDefault()) - return; - - timeOfLastCheck = Time.realtimeSinceStartup; + //if (!timeOfLastCheck.OccuredEarlierThanDefault()) + // return; + // + //timeOfLastCheck = Time.realtimeSinceStartup; value = value ?? ""; diff --git a/src/UI/Widgets/InputFieldScroller.cs b/src/UI/Widgets/InputFieldScroller.cs index 11a3861..570edbb 100644 --- a/src/UI/Widgets/InputFieldScroller.cs +++ b/src/UI/Widgets/InputFieldScroller.cs @@ -19,28 +19,28 @@ namespace UnityExplorer.UI.Utility { get { - if (InputField) - return InputField.gameObject; + if (InputField.UIRoot) + return InputField.UIRoot; return null; } } internal AutoSliderScrollbar Slider; - internal InputField InputField; + internal InputFieldRef InputField; internal RectTransform ContentRect; internal RectTransform ViewportRect; internal static CanvasScaler RootScaler; - public InputFieldScroller(AutoSliderScrollbar sliderScroller, InputField inputField) + public InputFieldScroller(AutoSliderScrollbar sliderScroller, InputFieldRef inputField) { this.Slider = sliderScroller; this.InputField = inputField; - inputField.onValueChanged.AddListener(OnTextChanged); + inputField.OnValueChanged += OnTextChanged; - ContentRect = inputField.GetComponent(); + ContentRect = inputField.UIRoot.GetComponent(); ViewportRect = ContentRect.transform.parent.GetComponent(); if (!RootScaler) @@ -88,22 +88,22 @@ namespace UnityExplorer.UI.Utility internal void ProcessInputText() { - var curInputRect = InputField.textComponent.rectTransform.rect; + var curInputRect = InputField.InputField.textComponent.rectTransform.rect; var scaleFactor = RootScaler.scaleFactor; // Current text settings - var texGenSettings = InputField.textComponent.GetGenerationSettings(curInputRect.size); + var texGenSettings = InputField.InputField.textComponent.GetGenerationSettings(curInputRect.size); texGenSettings.generateOutOfBounds = false; texGenSettings.scaleFactor = scaleFactor; // Preferred text rect height - var textGen = InputField.textComponent.cachedTextGeneratorForLayout; + var textGen = InputField.InputField.textComponent.cachedTextGeneratorForLayout; m_desiredContentHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10; // jump to bottom - if (InputField.caretPosition == InputField.text.Length - && InputField.text.Length > 0 - && InputField.text[InputField.text.Length - 1] == '\n') + if (InputField.InputField.caretPosition == InputField.Text.Length + && InputField.Text.Length > 0 + && InputField.Text[InputField.Text.Length - 1] == '\n') { m_wantJumpToBottom = true; } diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index caca36f..9540117 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -256,11 +256,14 @@ + + + @@ -330,7 +333,7 @@ - +