From 968546d43cea797579432227a2cdabdd50669395 Mon Sep 17 00:00:00 2001 From: sinaioutlander <49360850+sinaioutlander@users.noreply.github.com> Date: Wed, 14 Oct 2020 20:47:19 +1100 Subject: [PATCH] 2.0.6 * Unstrip fixes and cleanups --- src/Explorer.csproj | 2 + src/ExplorerCore.cs | 6 +- src/Helpers/Texture2DHelpers.cs | 44 +- src/Input/InputManager.cs | 46 +- .../Reflection/InstanceInspector.cs | 18 +- src/UI/Inspectors/ReflectionInspector.cs | 5 +- .../Object/InteractiveEnumerable.cs | 4 + src/UI/Main/ConsolePage.cs | 10 +- src/UI/Main/ScenePage.cs | 106 +- src/UI/Main/SearchPage.cs | 35 +- src/UI/TabViewWindow.cs | 2 +- src/Unstrip/IMGUI/GUIUnstrip.cs | 20 +- src/Unstrip/IMGUI/Internal.cs | 230 ++- src/Unstrip/IMGUI/Internal_GUIUtility.cs | 66 + src/Unstrip/IMGUI/Internal_ScrollViewState.cs | 80 +- src/Unstrip/IMGUI/Internal_SliderHandler.cs | 26 +- src/Unstrip/IMGUI/Internal_SliderState.cs | 12 - src/Unstrip/IMGUI/Internal_TextEditor.cs | 1557 +++++++++++++++++ 18 files changed, 1972 insertions(+), 297 deletions(-) create mode 100644 src/Unstrip/IMGUI/Internal_GUIUtility.cs create mode 100644 src/Unstrip/IMGUI/Internal_TextEditor.cs diff --git a/src/Explorer.csproj b/src/Explorer.csproj index dbe84a7..738e97f 100644 --- a/src/Explorer.csproj +++ b/src/Explorer.csproj @@ -262,6 +262,8 @@ + + diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index d88fcb1..a702835 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -1,4 +1,6 @@ -using Explorer.Config; +using System.Collections; +using System.Linq; +using Explorer.Config; using Explorer.UI; using Explorer.UI.Inspectors; using Explorer.UI.Main; @@ -10,7 +12,7 @@ namespace Explorer public class ExplorerCore { public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")"; - public const string VERSION = "2.0.5"; + public const string VERSION = "2.0.6"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.explorer"; diff --git a/src/Helpers/Texture2DHelpers.cs b/src/Helpers/Texture2DHelpers.cs index 2875b8e..791d4fb 100644 --- a/src/Helpers/Texture2DHelpers.cs +++ b/src/Helpers/Texture2DHelpers.cs @@ -13,7 +13,7 @@ namespace Explorer.Helpers { public static class Texture2DHelpers { -#if CPP +#if CPP // If Mono #else private static bool isNewEncodeMethod = false; private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod(); @@ -56,16 +56,16 @@ namespace Explorer.Helpers } } - public static Texture2D Copy(Texture2D other, Rect rect, bool isDTXnmNormal = false) + public static Texture2D Copy(Texture2D orig, Rect rect, bool isDTXnmNormal = false) { Color[] pixels; - if (!other.IsReadable()) + if (!orig.IsReadable()) { - other = ForceReadTexture(other, isDTXnmNormal); + orig = ForceReadTexture(orig); } - pixels = other.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); + pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); var _newTex = new Texture2D((int)rect.width, (int)rect.height); _newTex.SetPixels(pixels); @@ -73,26 +73,21 @@ namespace Explorer.Helpers return _newTex; } - public static Texture2D ForceReadTexture(Texture2D tex, bool isDTXnmNormal = false) + public static Texture2D ForceReadTexture(Texture2D tex) { try { var origFilter = tex.filterMode; tex.filterMode = FilterMode.Point; - RenderTexture rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32); + var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32); rt.filterMode = FilterMode.Point; RenderTexture.active = rt; Graphics.Blit(tex, rt); - Texture2D _newTex = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false); + var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false); + _newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); - - if (isDTXnmNormal) - { - _newTex = DTXnmToRGBA(_newTex); - } - _newTex.Apply(false, false); RenderTexture.active = null; @@ -117,8 +112,11 @@ namespace Explorer.Helpers byte[] data; var savepath = dir + @"\" + name + ".png"; - // Fix for non-Readable or Compressed textures. - tex = ForceReadTexture(tex, isDTXnmNormal); + // Make sure we can EncodeToPNG it. + if (tex.format != TextureFormat.ARGB32 || !tex.IsReadable()) + { + tex = ForceReadTexture(tex); + } if (isDTXnmNormal) { @@ -129,15 +127,13 @@ namespace Explorer.Helpers #if CPP data = tex.EncodeToPNG(); #else - var method = EncodeToPNGMethod; - if (isNewEncodeMethod) { - data = (byte[])method.Invoke(null, new object[] { tex }); + data = (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex }); } else { - data = (byte[])method.Invoke(tex, new object[0]); + data = (byte[])EncodeToPNGMethod.Invoke(tex, new object[0]); } #endif @@ -148,8 +144,10 @@ namespace Explorer.Helpers else { #if CPP - // The IL2CPP method will return invalid byte data. - // However, we can just iterate into safe C# byte[] array. + // The Il2Cpp EncodeToPNG() method does return System.Byte[], + // but for some reason it is not recognized or valid. + // Simple fix is iterating into a new array manually. + byte[] safeData = new byte[data.Length]; for (int i = 0; i < data.Length; i++) { @@ -185,7 +183,7 @@ namespace Explorer.Helpers ); } - var newtex = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false); + var newtex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false); newtex.SetPixels(colors); return newtex; diff --git a/src/Input/InputManager.cs b/src/Input/InputManager.cs index 855513a..358b264 100644 --- a/src/Input/InputManager.cs +++ b/src/Input/InputManager.cs @@ -50,33 +50,37 @@ namespace Explorer public static void ResetInputAxes() => UnityEngine.Input.ResetInputAxes(); #endif - //#if CPP - //#pragma warning disable IDE1006 - // // public extern static string compositionString { get; } +#if CPP +#pragma warning disable IDE1006 + // public extern static string compositionString { get; } - // internal delegate string get_compositionString_delegate(); - // internal static get_compositionString_delegate get_compositionString_iCall = - // IL2CPP.ResolveICall("UnityEngine.Input::get_compositionString"); + internal delegate IntPtr d_get_compositionString(); + internal static d_get_compositionString get_compositionString_iCall = + IL2CPP.ResolveICall("UnityEngine.Input::get_compositionString"); - // public static string compositionString => get_compositionString_iCall(); + public static string compositionString => IL2CPP.Il2CppStringToManaged(get_compositionString_iCall()); - // // public extern static Vector2 compositionCursorPos { get; set; } + // public extern static Vector2 compositionCursorPos { get; set; } - // internal delegate Vector2 get_compositionCursorPos_delegate(); - // internal static get_compositionCursorPos_delegate get_compositionCursorPos_iCall = - // IL2CPP.ResolveICall("UnityEngine.Input::get_compositionCursorPos"); + internal delegate void d_get_compositionCursorPos(out Vector2 ret); + internal static d_get_compositionCursorPos get_compositionCursorPos_iCall = + IL2CPP.ResolveICall("UnityEngine.Input::get_compositionCursorPos_Injected"); - // internal delegate void set_compositionCursorPos_delegate(Vector2 value); - // internal static set_compositionCursorPos_delegate set_compositionCursorPos_iCall = - // IL2CPP.ResolveICall("UnityEngine.Input::set_compositionCursorPos"); + internal delegate void set_compositionCursorPos_delegate(ref Vector2 value); + internal static set_compositionCursorPos_delegate set_compositionCursorPos_iCall = + IL2CPP.ResolveICall("UnityEngine.Input::set_compositionCursorPos_Injected"); - // public static Vector2 compositionCursorPos - // { - // get => get_compositionCursorPos_iCall(); - // set => set_compositionCursorPos_iCall(value); - // } + public static Vector2 compositionCursorPos + { + get + { + get_compositionCursorPos_iCall(out Vector2 ret); + return ret; + } + set => set_compositionCursorPos_iCall(ref value); + } - //#pragma warning restore IDE1006 - //#endif +#pragma warning restore IDE1006 +#endif } } \ No newline at end of file diff --git a/src/UI/Inspectors/Reflection/InstanceInspector.cs b/src/UI/Inspectors/Reflection/InstanceInspector.cs index 172ad01..013f823 100644 --- a/src/UI/Inspectors/Reflection/InstanceInspector.cs +++ b/src/UI/Inspectors/Reflection/InstanceInspector.cs @@ -72,11 +72,11 @@ namespace Explorer.UI.Inspectors public void DrawInstanceControls(Rect rect) { - if (m_uObj) - { - GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]); - } - GUILayout.EndHorizontal(); + //if (m_uObj) + //{ + // GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]); + //} + //GUILayout.EndHorizontal(); if (m_uObj) { @@ -89,13 +89,17 @@ namespace Explorer.UI.Inspectors GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) }); var charWidth = obj.name.Length * 15; var maxWidth = rect.width - 350; - var labelWidth = charWidth < maxWidth ? charWidth : maxWidth; - if (GUILayout.Button("" + obj.name + "", new GUILayoutOption[] { GUILayout.Width(labelWidth) })) + var btnWidth = charWidth < maxWidth ? charWidth : maxWidth; + if (GUILayout.Button("" + obj.name + "", new GUILayoutOption[] { GUILayout.Width(btnWidth) })) { WindowManager.InspectObject(obj, out bool _); } GUI.skin.label.alignment = TextAnchor.UpperLeft; } + else + { + GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]); + } GUILayout.EndHorizontal(); } } diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index eb9ac3e..189e22c 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -263,15 +263,12 @@ namespace Explorer.UI.Inspectors ? new GUILayoutOption[] { GUILayout.Width(245f) } : new GUILayoutOption[0]; GUILayout.Label("Type: " + TargetType.FullName + "", labelWidth); + GUILayout.EndHorizontal(); if (asInstance != null) { asInstance.DrawInstanceControls(rect); } - else - { - GUILayout.EndHorizontal(); - } UIStyles.HorizontalLine(Color.grey); diff --git a/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs b/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs index 8d62f00..32fc71d 100644 --- a/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs +++ b/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs @@ -6,6 +6,10 @@ using System.Reflection; using UnityEngine; using Explorer.UI.Shared; using Explorer.CacheObject; +using System.Linq; +#if CPP +using UnhollowerBaseLib; +#endif namespace Explorer.UI { diff --git a/src/UI/Main/ConsolePage.cs b/src/UI/Main/ConsolePage.cs index 8d03a38..3777c05 100644 --- a/src/UI/Main/ConsolePage.cs +++ b/src/UI/Main/ConsolePage.cs @@ -33,7 +33,7 @@ namespace Explorer.UI.Main public static TextEditor textEditor; private bool shouldRefocus; - public static GUIStyle AutocompleteStyle => autocompleteStyle ?? GetCompletionStyle(); + public static GUIStyle AutocompleteStyle => autocompleteStyle ?? GetAutocompleteStyle(); private static GUIStyle autocompleteStyle; public static readonly string[] DefaultUsing = new string[] @@ -342,9 +342,9 @@ Help();"; } // Credit ManlyMarco - private static GUIStyle GetCompletionStyle() + private static GUIStyle GetAutocompleteStyle() { - return autocompleteStyle = new GUIStyle(GUI.skin.button) + var style = new GUIStyle { border = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0), @@ -353,8 +353,10 @@ Help();"; normal = { background = null }, focused = { background = Texture2D.whiteTexture, textColor = Color.black }, active = { background = Texture2D.whiteTexture, textColor = Color.black }, - alignment = TextAnchor.MiddleLeft, + alignment = TextAnchor.MiddleLeft }; + + return autocompleteStyle = style; } private class VoidType diff --git a/src/UI/Main/ScenePage.cs b/src/UI/Main/ScenePage.cs index f9a0fdf..2b2c706 100644 --- a/src/UI/Main/ScenePage.cs +++ b/src/UI/Main/ScenePage.cs @@ -19,8 +19,6 @@ namespace Explorer.UI.Main private float m_timeOfLastUpdate = -1f; private const int PASSIVE_UPDATE_INTERVAL = 1; - private static bool m_getRootObjectsFailed; - private static string m_currentScene = ""; // gameobject list @@ -50,7 +48,7 @@ namespace Explorer.UI.Main if (m_searching) CancelSearch(); - Update_Impl(true); + Update_Impl(); } public void TraverseUp() @@ -75,11 +73,6 @@ namespace Explorer.UI.Main public void CancelSearch() { m_searching = false; - - if (m_getRootObjectsFailed && !m_currentTransform) - { - GetRootObjectsManual_Impl(); - } } public List SearchSceneObjects(string _search) @@ -112,7 +105,7 @@ namespace Explorer.UI.Main Update_Impl(); } - private void Update_Impl(bool manual = false) + private void Update_Impl() { List allTransforms = new List(); @@ -126,39 +119,23 @@ namespace Explorer.UI.Main } else { - if (!m_getRootObjectsFailed) + for (int i = 0; i < SceneManager.sceneCount; i++) { - try + var scene = SceneManager.GetSceneAt(i); + + if (scene.name == m_currentScene) { - for (int i = 0; i < SceneManager.sceneCount; i++) - { - var scene = SceneManager.GetSceneAt(i); + var rootObjects = +#if CPP + Unstrip.Scenes.SceneUnstrip.GetRootGameObjects(scene) + .Select(it => it.transform); +#else + scene.GetRootGameObjects().Select(it => it.transform); +#endif + allTransforms.AddRange(rootObjects); - if (scene.name == m_currentScene) - { - allTransforms.AddRange(scene.GetRootGameObjects() - .Select(it => it.transform)); - - break; - } - } + break; } - catch - { - ExplorerCore.Log("Exception getting root scene objects, falling back to backup method..."); - - m_getRootObjectsFailed = true; - allTransforms.AddRange(GetRootObjectsManual_Impl()); - } - } - else - { - if (!manual) - { - return; - } - - allTransforms.AddRange(GetRootObjectsManual_Impl()); } } @@ -178,36 +155,6 @@ namespace Explorer.UI.Main } } - private IEnumerable GetRootObjectsManual_Impl() - { - try - { - var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType); - - var list = new List(); - foreach (var obj in array) - { -#if CPP - var transform = obj.TryCast(); -#else - var transform = obj as Transform; -#endif - if (transform.parent == null && transform.gameObject.scene.name == m_currentScene) - { - list.Add(transform); - } - } - return list; - } - catch (Exception e) - { - ExplorerCore.Log("Exception getting root scene objects (manual): " - + e.GetType() + ", " + e.Message + "\r\n" - + e.StackTrace); - return new Transform[0]; - } - } - // --------- GUI Draw Function --------- // public override void DrawWindow() @@ -292,15 +239,12 @@ namespace Explorer.UI.Main { int index = names.IndexOf(m_currentScene); index += changeWanted; - if (index > scenes.Count - 1) + + if (index >= 0 && index < SceneManager.sceneCount) { - index = 0; + m_currentScene = scenes[index].name; + Update_Impl(); } - else if (index < 0) - { - index = scenes.Count - 1; - } - m_currentScene = scenes[index].name; } } } @@ -317,7 +261,7 @@ namespace Explorer.UI.Main { Pages.TurnPage(Turn.Left, ref this.scroll); - Update_Impl(true); + Update_Impl(); } Pages.CurrentPageLabel(); @@ -326,7 +270,7 @@ namespace Explorer.UI.Main { Pages.TurnPage(Turn.Right, ref this.scroll); - Update_Impl(true); + Update_Impl(); } } @@ -356,14 +300,6 @@ namespace Explorer.UI.Main else { GUILayout.Label("Scene Root GameObjects:", new GUILayoutOption[0]); - - if (m_getRootObjectsFailed) - { - if (GUILayout.Button("Update Root Object List (auto-update failed!)", new GUILayoutOption[0])) - { - Update_Impl(true); - } - } } if (m_objectList.Count > 0) diff --git a/src/UI/Main/SearchPage.cs b/src/UI/Main/SearchPage.cs index 5a92949..f4b50ad 100644 --- a/src/UI/Main/SearchPage.cs +++ b/src/UI/Main/SearchPage.cs @@ -117,13 +117,6 @@ namespace Explorer.UI.Main Pages.PageOffset = 0; - // Would use Task, but Explorer is .NET 3.5-compatible. - var objectsOfType = FindAllObjectsOfType(m_searchInput, m_typeInput); - CacheResults(objectsOfType); - } - - private List FindAllObjectsOfType(string searchQuery, string typeName) - { #if CPP Il2CppSystem.Type searchType = null; @@ -132,24 +125,18 @@ namespace Explorer.UI.Main #endif if (TypeMode == TypeFilter.Custom) { - try + if (ReflectionHelpers.GetTypeByName(m_typeInput) is Type t) { - if (ReflectionHelpers.GetTypeByName(typeName) is Type t) - { #if CPP - searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName); + searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName); #else - searchType = t; + searchType = t; #endif - } - else - { - throw new Exception($"Could not find a Type by the name of '{typeName}'!"); - } } - catch (Exception e) + else { - ExplorerCore.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message); + ExplorerCore.Log($"Could not find a Type by the name of '{m_typeInput}'!"); + return; } } else if (TypeMode == TypeFilter.Object) @@ -171,7 +158,7 @@ namespace Explorer.UI.Main { ExplorerCore.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!"); } - return new List(); + return; } var matches = new List(); @@ -185,7 +172,7 @@ namespace Explorer.UI.Main { if (i >= MaxSearchResults) break; - if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower())) + if (m_searchInput != "" && !obj.name.ToLower().Contains(m_searchInput.ToLower())) { continue; } @@ -215,11 +202,7 @@ namespace Explorer.UI.Main i++; } - allObjectsOfType = null; - searchType = null; - searchQuery = null; - - return matches; + CacheResults(matches); } public static bool FilterScene(object obj, SceneFilter filter) diff --git a/src/UI/TabViewWindow.cs b/src/UI/TabViewWindow.cs index 524d1cb..1c34b65 100644 --- a/src/UI/TabViewWindow.cs +++ b/src/UI/TabViewWindow.cs @@ -66,7 +66,7 @@ namespace Explorer.UI GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, null); GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]); GUI.skin.button.alignment = TextAnchor.MiddleLeft; - int tabPerRow = Mathf.FloorToInt((float)((decimal)m_rect.width / 238)); + int tabPerRow = (int)Math.Floor((float)((decimal)m_rect.width / 238)); int rowCount = 0; for (int i = 0; i < WindowManager.Windows.Count; i++) { diff --git a/src/Unstrip/IMGUI/GUIUnstrip.cs b/src/Unstrip/IMGUI/GUIUnstrip.cs index b5912ac..3b4f8e8 100644 --- a/src/Unstrip/IMGUI/GUIUnstrip.cs +++ b/src/Unstrip/IMGUI/GUIUnstrip.cs @@ -53,12 +53,21 @@ namespace Explorer public static string TextField(string text, GUILayoutOption[] options) { #if CPP - return Internal.TextField(text, options); + return Internal.TextField(text, options, false); #else return GUILayout.TextField(text, options); #endif } + public static string TextArea(string text, params GUILayoutOption[] options) + { +#if CPP + return Internal.TextField(text, options, true); +#else + return GUILayout.TextArea(text, options); +#endif + } + public static Rect Window(int id, Rect rect, GUI.WindowFunction windowFunc, string title) { #if CPP @@ -77,15 +86,6 @@ namespace Explorer #endif } - public static string TextArea(string text, params GUILayoutOption[] options) - { -#if CPP - return GUILayout.DoTextField(text, -1, true, GUI.skin.textArea, options); -#else - return GUILayout.TextArea(text, options); -#endif - } - public static void BringWindowToFront(int id) { #if CPP diff --git a/src/Unstrip/IMGUI/Internal.cs b/src/Unstrip/IMGUI/Internal.cs index 3c2d056..f9e433b 100644 --- a/src/Unstrip/IMGUI/Internal.cs +++ b/src/Unstrip/IMGUI/Internal.cs @@ -16,12 +16,9 @@ namespace Explorer.Unstrip.IMGUI public static bool ScrollFailed = false; public static bool ManualUnstripFailed = false; - public static GenericStack ScrollStack => m_scrollStack ?? GetScrollStack(); - public static PropertyInfo m_scrollViewStatesInfo; - public static GenericStack m_scrollStack; - - public static Dictionary StateCache => m_stateCacheDict ?? GetStateCacheDict(); - public static Dictionary m_stateCacheDict; + public static Stack ScrollStack => m_scrollStack ?? GetScrollStack(); + public static Stack m_scrollStack; + //public static PropertyInfo m_scrollViewStatesInfo; public static GUIStyle SpaceStyle => m_spaceStyle ?? GetSpaceStyle(); public static GUIStyle m_spaceStyle; @@ -34,53 +31,13 @@ namespace Explorer.Unstrip.IMGUI public static MethodInfo m_bringWindowToFrontMethod; public static bool m_bringWindowFrontAttempted; - private static GenericStack GetScrollStack() + private static Stack GetScrollStack() { - if (m_scrollViewStatesInfo == null) - { - if (typeof(GUI).GetProperty("scrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo scrollStatesInfo) - { - m_scrollViewStatesInfo = scrollStatesInfo; - } - else if (typeof(GUI).GetProperty("s_ScrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo s_scrollStatesInfo) - { - m_scrollViewStatesInfo = s_scrollStatesInfo; - } - } - - if (m_scrollViewStatesInfo?.GetValue(null, null) is GenericStack stack) - { - m_scrollStack = stack; - } - else - { - m_scrollStack = new GenericStack(); - } + m_scrollStack = new Stack(); return m_scrollStack; } - private static Dictionary GetStateCacheDict() - { - if (m_stateCacheDict == null) - { - try - { - var type = ReflectionHelpers.GetTypeByName("UnityEngine.GUIStateObjects"); - m_stateCacheDict = type.GetProperty("s_StateCache") - .GetValue(null, null) - as Dictionary; - - if (m_stateCacheDict == null) throw new Exception(); - } - catch - { - m_stateCacheDict = new Dictionary(); - } - } - return m_stateCacheDict; - } - private static GUIStyle GetSpaceStyle() { try @@ -115,7 +72,7 @@ namespace Explorer.Unstrip.IMGUI GUI.Box(g.rect, content, style); } - public static string TextField(string text, GUILayoutOption[] options) + public static string TextField(string text, GUILayoutOption[] options, bool multiLine) { text = text ?? string.Empty; @@ -137,13 +94,13 @@ namespace Explorer.Unstrip.IMGUI { guicontent = GUIContent.Temp(text); } - DoTextField(rect, controlID, guicontent, false, -1, GUI.skin.textField); + DoTextField(rect, controlID, guicontent, multiLine, -1, GUI.skin.textField); return guicontent.text; } internal static void DoTextField(Rect position, int id, GUIContent content, bool multiline, int maxLength, GUIStyle style) { - if (GetStateObject(Il2CppType.Of(), id).TryCast() is TextEditor textEditor) + if (Internal_GUIUtility.GetMonoStateObject(typeof(Internal_TextEditor), id) is Internal_TextEditor textEditor) { if (maxLength >= 0 && content.text.Length > maxLength) { @@ -156,11 +113,138 @@ namespace Explorer.Unstrip.IMGUI textEditor.multiline = multiline; textEditor.controlID = id; textEditor.DetectFocusChange(); - GUI.HandleTextFieldEventForDesktop(position, id, content, multiline, maxLength, style, textEditor); + HandleTextFieldEventForDesktop(position, id, content, multiline, maxLength, style, textEditor); textEditor.UpdateScrollOffsetIfNeeded(Event.current); } } + private static void HandleTextFieldEventForDesktop(Rect position, int id, GUIContent content, bool multiline, int maxLength, + GUIStyle style, Internal_TextEditor editor) + { + var evt = Event.current; + + bool change = false; + switch (evt.type) + { + case EventType.MouseDown: + if (position.Contains(evt.mousePosition)) + { + GUIUtility.hotControl = id; + GUIUtility.keyboardControl = id; + editor.m_HasFocus = true; + editor.MoveCursorToPosition(Event.current.mousePosition); + if (Event.current.clickCount == 2 && GUI.skin.settings.doubleClickSelectsWord) + { + editor.SelectCurrentWord(); + editor.DblClickSnap(Internal_TextEditor.DblClickSnapping.WORDS); + editor.MouseDragSelectsWholeWords(true); + } + if (Event.current.clickCount == 3 && GUI.skin.settings.tripleClickSelectsLine) + { + editor.SelectCurrentParagraph(); + editor.MouseDragSelectsWholeWords(true); + editor.DblClickSnap(Internal_TextEditor.DblClickSnapping.PARAGRAPHS); + } + evt.Use(); + } + break; + case EventType.MouseDrag: + if (GUIUtility.hotControl == id) + { + if (evt.shift) + editor.MoveCursorToPosition(Event.current.mousePosition); + else + editor.SelectToPosition(Event.current.mousePosition); + evt.Use(); + } + break; + case EventType.MouseUp: + if (GUIUtility.hotControl == id) + { + editor.MouseDragSelectsWholeWords(false); + GUIUtility.hotControl = 0; + evt.Use(); + } + break; + case EventType.KeyDown: + if (GUIUtility.keyboardControl != id) + return; + + if (editor.HandleKeyEvent(evt)) + { + evt.Use(); + change = true; + content.text = editor.text; + break; + } + + // Ignore tab & shift-tab in textfields + if (evt.keyCode == KeyCode.Tab || evt.character == '\t') + return; + + char c = evt.character; + + if (c == '\n' && !multiline && !evt.alt) + return; + + + // Simplest test: only allow the character if the display font supports it. + Font font = style.font; + if (!font) + font = GUI.skin.font; + + if (font.HasCharacter(c) || c == '\n') + { + editor.Insert(c); + change = true; + break; + } + + // On windows, keypresses also send events with keycode but no character. Eat them up here. + if (c == 0) + { + // if we have a composition string, make sure we clear the previous selection. + if (InputManager.compositionString.Length > 0) + { + editor.ReplaceSelection(""); + change = true; + } + + evt.Use(); + } + // else { + // REALLY USEFUL: + // Debug.Log ("unhandled " +evt); + // evt.Use (); + // } + break; + case EventType.Repaint: + // If we have keyboard focus, draw the cursor + // TODO: check if this OpenGL view has keyboard focus + if (GUIUtility.keyboardControl != id) + { + style.Draw(position, content, id, false); + } + else + { + editor.DrawCursor(content.text); + } + break; + } + + if (GUIUtility.keyboardControl == id) + GUIUtility.textFieldInput = true; + + if (change) + { + GUI.changed = true; + content.text = editor.text; + if (maxLength >= 0 && content.text.Length > maxLength) + content.text = content.text.Substring(0, maxLength); + evt.Use(); + } + } + public static bool DoRepeatButton(GUIContent content, GUIStyle style, GUILayoutOption[] options) { return GUI.DoRepeatButton(Internal_LayoutUtility.GetRect(content, style, options), content, style, FocusType.Passive); @@ -279,22 +363,6 @@ namespace Explorer.Unstrip.IMGUI #region Scrolling - private static Il2CppSystem.Object GetStateObject(Il2CppSystem.Type type, int controlID) - { - Il2CppSystem.Object obj; - if (StateCache.ContainsKey(controlID)) - { - obj = StateCache[controlID]; - } - else - { - obj = Il2CppSystem.Activator.CreateInstance(type); - StateCache.Add(controlID, obj); - } - - return obj; - } - public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options) { // First, just try normal way, may not have been stripped or was unstripped successfully. @@ -348,10 +416,12 @@ namespace Explorer.Unstrip.IMGUI if (ScrollStack.Count <= 0) return; - var state = ScrollStack.Peek().TryCast(); - var scrollExt = Internal_ScrollViewState.FromPointer(state.Pointer); + var scrollExt = ScrollStack.Peek() as Internal_ScrollViewState; - if (scrollExt == null) throw new Exception("Could not get scrollExt!"); + //var state = ScrollStack.Peek().TryCast(); + //var scrollExt = Internal_ScrollViewState.FromPointer(state.Pointer); + + //if (scrollExt == null) throw new Exception("Could not get scrollExt!"); GUIClip.Pop(); @@ -419,16 +489,18 @@ namespace Explorer.Unstrip.IMGUI int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive); - var scrollViewState = GetStateObject(Il2CppType.Of(), controlID) - .TryCast(); + var scrollExt = (Internal_ScrollViewState)Internal_GUIUtility.GetMonoStateObject(typeof(Internal_ScrollViewState), controlID); - if (scrollViewState == null) - return scrollPosition; + //var scrollViewState = Internal_GUIUtility.GetStateObject(Il2CppType.Of(), controlID) + // .TryCast(); - var scrollExt = Internal_ScrollViewState.FromPointer(scrollViewState.Pointer); + //if (scrollViewState == null) + // return scrollPosition; - if (scrollExt == null) - return scrollPosition; + //var scrollExt = Internal_ScrollViewState.FromPointer(scrollViewState.Pointer); + + //if (scrollExt == null) + // return scrollPosition; bool apply = scrollExt.apply; if (apply) @@ -446,7 +518,7 @@ namespace Explorer.Unstrip.IMGUI rect.width = position.width; rect.height = position.height; - ScrollStack.Push(scrollViewState); + ScrollStack.Push(scrollExt); Rect screenRect = new Rect(position.x, position.y, position.width, position.height); EventType type = Event.current.type; diff --git a/src/Unstrip/IMGUI/Internal_GUIUtility.cs b/src/Unstrip/IMGUI/Internal_GUIUtility.cs new file mode 100644 index 0000000..5f6c36f --- /dev/null +++ b/src/Unstrip/IMGUI/Internal_GUIUtility.cs @@ -0,0 +1,66 @@ +#if CPP +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace Explorer.Unstrip.IMGUI +{ + public class Internal_GUIUtility + { + public static Dictionary MonoStateCache = new Dictionary(); + + public static object GetMonoStateObject(Type type, int controlID) + { + if (!MonoStateCache.ContainsKey(controlID)) + { + MonoStateCache.Add(controlID, Activator.CreateInstance(type)); + } + + return MonoStateCache[controlID]; + } + + public static Dictionary StateCache => m_stateCacheDict ?? GetStateCacheDict(); + public static Dictionary m_stateCacheDict; + + public static Il2CppSystem.Object GetStateObject(Il2CppSystem.Type type, int controlID) + { + Il2CppSystem.Object obj; + if (StateCache.ContainsKey(controlID)) + { + obj = StateCache[controlID]; + } + else + { + obj = Il2CppSystem.Activator.CreateInstance(type); + StateCache.Add(controlID, obj); + } + + return obj; + } + + private static Dictionary GetStateCacheDict() + { + if (m_stateCacheDict == null) + { + try + { + m_stateCacheDict = ReflectionHelpers.GetTypeByName("UnityEngine.GUIStateObjects") + .GetProperty("s_StateCache") + .GetValue(null, null) + as Dictionary; + + if (m_stateCacheDict == null) throw new Exception(); + } + catch + { + m_stateCacheDict = new Dictionary(); + } + } + return m_stateCacheDict; + } + } +} + +#endif \ No newline at end of file diff --git a/src/Unstrip/IMGUI/Internal_ScrollViewState.cs b/src/Unstrip/IMGUI/Internal_ScrollViewState.cs index 472e254..c19f0e0 100644 --- a/src/Unstrip/IMGUI/Internal_ScrollViewState.cs +++ b/src/Unstrip/IMGUI/Internal_ScrollViewState.cs @@ -13,17 +13,77 @@ namespace Explorer.Unstrip.IMGUI public Vector2 scrollPosition; public bool apply; - public static Dictionary Dict = new Dictionary(); + public void ScrollTo(Rect pos) + { + this.ScrollTowards(pos, float.PositiveInfinity); + } - public static Internal_ScrollViewState FromPointer(IntPtr ptr) - { - if (!Dict.ContainsKey(ptr)) - { - Dict.Add(ptr, new Internal_ScrollViewState()); - } + public bool ScrollTowards(Rect pos, float maxDelta) + { + Vector2 b = this.ScrollNeeded(pos); + bool result; + if (b.sqrMagnitude < 0.0001f) + { + result = false; + } + else if (maxDelta == 0f) + { + result = true; + } + else + { + if (b.magnitude > maxDelta) + { + b = b.normalized * maxDelta; + } + this.scrollPosition += b; + this.apply = true; + result = true; + } + return result; + } - return Dict[ptr]; - } - } + private Vector2 ScrollNeeded(Rect pos) + { + Rect rect = this.visibleRect; + rect.x += this.scrollPosition.x; + rect.y += this.scrollPosition.y; + float num = pos.width - this.visibleRect.width; + if (num > 0f) + { + pos.width -= num; + pos.x += num * 0.5f; + } + num = pos.height - this.visibleRect.height; + if (num > 0f) + { + pos.height -= num; + pos.y += num * 0.5f; + } + Vector2 zero = Vector2.zero; + if (pos.xMax > rect.xMax) + { + zero.x += pos.xMax - rect.xMax; + } + else if (pos.xMin < rect.xMin) + { + zero.x -= rect.xMin - pos.xMin; + } + if (pos.yMax > rect.yMax) + { + zero.y += pos.yMax - rect.yMax; + } + else if (pos.yMin < rect.yMin) + { + zero.y -= rect.yMin - pos.yMin; + } + Rect rect2 = this.viewRect; + rect2.width = Mathf.Max(rect2.width, this.visibleRect.width); + rect2.height = Mathf.Max(rect2.height, this.visibleRect.height); + zero.x = Mathf.Clamp(zero.x, rect2.xMin - this.scrollPosition.x, rect2.xMax - this.visibleRect.width - this.scrollPosition.x); + zero.y = Mathf.Clamp(zero.y, rect2.yMin - this.scrollPosition.y, rect2.yMax - this.visibleRect.height - this.scrollPosition.y); + return zero; + } + } } #endif \ No newline at end of file diff --git a/src/Unstrip/IMGUI/Internal_SliderHandler.cs b/src/Unstrip/IMGUI/Internal_SliderHandler.cs index 5cf9c02..e6d16f9 100644 --- a/src/Unstrip/IMGUI/Internal_SliderHandler.cs +++ b/src/Unstrip/IMGUI/Internal_SliderHandler.cs @@ -118,8 +118,8 @@ namespace Explorer.Unstrip.IMGUI GUI.changed = true; if (this.SupportsPageMovements()) { - var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer); - ext.isDragging = false; + var state = GetSliderState(); + state.isDragging = false; Internal.nextScrollStepTime = DateTime.Now.AddMilliseconds(250.0); ScrollTroughSide = this.CurrentScrollTroughSide(); result = this.PageMovementValue(); @@ -144,8 +144,8 @@ namespace Explorer.Unstrip.IMGUI } else { - var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer); - if (!ext.isDragging) + var state = GetSliderState(); + if (!state.isDragging) { result = this.currentValue; } @@ -153,8 +153,8 @@ namespace Explorer.Unstrip.IMGUI { GUI.changed = true; this.CurrentEvent().Use(); - float num = this.MousePosition() - ext.dragStartPos; - float value = ext.dragStartValue + num / this.ValuesPerPixel(); + float num = this.MousePosition() - state.dragStartPos; + float value = state.dragStartValue + num / this.ValuesPerPixel(); result = this.Clamp(value); } } @@ -207,7 +207,7 @@ namespace Explorer.Unstrip.IMGUI Internal.nextScrollStepTime = DateTime.Now.AddMilliseconds(30.0); if (this.SupportsPageMovements()) { - Internal_SliderState.FromPointer(GetSliderState().Pointer).isDragging = false; + GetSliderState().isDragging = false; GUI.changed = true; result = this.PageMovementValue(); } @@ -303,15 +303,15 @@ namespace Explorer.Unstrip.IMGUI private void StartDraggingWithValue(float dragStartValue) { - var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer); - ext.dragStartPos = this.MousePosition(); - ext.dragStartValue = dragStartValue; - ext.isDragging = true; + var state = GetSliderState(); + state.dragStartPos = this.MousePosition(); + state.dragStartValue = dragStartValue; + state.isDragging = true; } - private SliderState GetSliderState() + private Internal_SliderState GetSliderState() { - return GUIUtility.GetStateObject(Il2CppType.Of(), this.id).TryCast(); + return (Internal_SliderState)Internal_GUIUtility.GetMonoStateObject(typeof(Internal_SliderState), this.id); } private Rect ThumbRect() diff --git a/src/Unstrip/IMGUI/Internal_SliderState.cs b/src/Unstrip/IMGUI/Internal_SliderState.cs index 989e8af..437ed4b 100644 --- a/src/Unstrip/IMGUI/Internal_SliderState.cs +++ b/src/Unstrip/IMGUI/Internal_SliderState.cs @@ -10,17 +10,5 @@ namespace Explorer.Unstrip.IMGUI public float dragStartPos; public float dragStartValue; public bool isDragging; - - public static Dictionary Dict = new Dictionary(); - - public static Internal_SliderState FromPointer(IntPtr ptr) - { - if (!Dict.ContainsKey(ptr)) - { - Dict.Add(ptr, new Internal_SliderState()); - } - - return Dict[ptr]; - } } } diff --git a/src/Unstrip/IMGUI/Internal_TextEditor.cs b/src/Unstrip/IMGUI/Internal_TextEditor.cs new file mode 100644 index 0000000..0efdc45 --- /dev/null +++ b/src/Unstrip/IMGUI/Internal_TextEditor.cs @@ -0,0 +1,1557 @@ +#if CPP +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Explorer.Unstrip.IMGUI +{ + public class Internal_TextEditor + { + public Internal_TextEditor() { } + // public Internal_TextEditor(IntPtr ptr) : base(ptr) { } + + public TouchScreenKeyboard keyboardOnScreen = null; + public int controlID = 0; + public GUIStyle style = GUIStyle.none; + public bool multiline = false; + public bool hasHorizontalCursorPos = false; + public bool isPasswordField = false; + internal bool m_HasFocus; + public Vector2 scrollOffset = Vector2.zero; // The text field can have a scroll offset in order to display its contents + + public GUIContent m_Content = new GUIContent(); + private Rect m_Position; + private int m_CursorIndex = 0; + private int m_SelectIndex = 0; + private bool m_RevealCursor = false; + + public string text + { + get { return m_Content.text; } + set + { + m_Content.text = value ?? string.Empty; + EnsureValidCodePointIndex(ref m_CursorIndex); + EnsureValidCodePointIndex(ref m_SelectIndex); + } + } + + public Rect position + { + get { return m_Position; } + set + { + if (m_Position == value) + return; + + m_Position = value; + + UpdateScrollOffset(); + } + } + + internal virtual Rect localPosition + { + get { return position; } + } + + public int cursorIndex + { + get { return m_CursorIndex; } + set + { + int oldCursorIndex = m_CursorIndex; + m_CursorIndex = value; + EnsureValidCodePointIndex(ref m_CursorIndex); + + if (m_CursorIndex != oldCursorIndex) + { + m_RevealCursor = true; + OnCursorIndexChange(); + } + } + } + + public int selectIndex + { + get { return m_SelectIndex; } + set + { + int oldSelectIndex = m_SelectIndex; + m_SelectIndex = value; + EnsureValidCodePointIndex(ref m_SelectIndex); + + if (m_SelectIndex != oldSelectIndex) + OnSelectIndexChange(); + } + } + + // are we up/downing? + public Vector2 graphicalCursorPos; + public Vector2 graphicalSelectCursorPos; + + // Clear the cursor position for vertical movement... + void ClearCursorPos() { hasHorizontalCursorPos = false; m_iAltCursorPos = -1; } + + // selection + bool m_MouseDragSelectsWholeWords = false; + int m_DblClickInitPos = 0; + + public DblClickSnapping doubleClickSnapping { get; set; } = DblClickSnapping.WORDS; + bool m_bJustSelected = false; + + int m_iAltCursorPos = -1; + public int altCursorPosition { get { return m_iAltCursorPos; } set { m_iAltCursorPos = value; } } + + public enum DblClickSnapping : byte { WORDS, PARAGRAPHS }; + + public void OnFocus() + { + if (multiline) + cursorIndex = selectIndex = 0; + else + SelectAll(); + m_HasFocus = true; + } + + public void OnLostFocus() + { + m_HasFocus = false; + scrollOffset = Vector2.zero; + } + + void GrabGraphicalCursorPos() + { + if (!hasHorizontalCursorPos) + { + graphicalCursorPos = style.GetCursorPixelPosition(localPosition, m_Content, cursorIndex); + graphicalSelectCursorPos = style.GetCursorPixelPosition(localPosition, m_Content, selectIndex); + hasHorizontalCursorPos = false; + } + } + + // Handle a key event. + // Looks up the platform-dependent key-action table & performs the event + // return true if the event was recognized. + public bool HandleKeyEvent(Event e) + { + InitKeyActions(); + EventModifiers m = e.modifiers; + e.modifiers &= ~EventModifiers.CapsLock; + var key = e.ToString(); + if (s_Keyactions.ContainsKey(key)) + { + TextEditOp op = (TextEditOp)s_Keyactions[key]; + PerformOperation(op); + e.modifiers = m; + return true; + } + e.modifiers = m; + return false; + } + + // Deletes previous text on the line + public bool DeleteLineBack() + { + if (hasSelection) + { + DeleteSelection(); + return true; + } + int p = cursorIndex; + int i = p; + while (i-- != 0) + if (text[i] == '\n') + { + p = i + 1; + break; + } + if (i == -1) + p = 0; + if (cursorIndex != p) + { + m_Content.text = text.Remove(p, cursorIndex - p); + selectIndex = cursorIndex = p; + return true; + } + return false; + } + + // Deletes the previous word + public bool DeleteWordBack() + { + if (hasSelection) + { + DeleteSelection(); + return true; + } + + int prevWordEnd = FindEndOfPreviousWord(cursorIndex); + if (cursorIndex != prevWordEnd) + { + m_Content.text = text.Remove(prevWordEnd, cursorIndex - prevWordEnd); + selectIndex = cursorIndex = prevWordEnd; + return true; + } + return false; + } + + // Deletes the following word + public bool DeleteWordForward() + { + if (hasSelection) + { + DeleteSelection(); + return true; + } + + int nextWordStart = FindStartOfNextWord(cursorIndex); + if (cursorIndex < text.Length) + { + m_Content.text = text.Remove(cursorIndex, nextWordStart - cursorIndex); + return true; + } + return false; + } + + // perform a right-delete + public bool Delete() + { + if (hasSelection) + { + DeleteSelection(); + return true; + } + else if (cursorIndex < text.Length) + { + m_Content.text = text.Remove(cursorIndex, NextCodePointIndex(cursorIndex) - cursorIndex); + return true; + } + return false; + } + + public bool CanPaste() + { + return GUIUtility.systemCopyBuffer.Length != 0; + } + + // Perform a left-delete + public bool Backspace() + { + if (hasSelection) + { + DeleteSelection(); + return true; + } + else if (cursorIndex > 0) + { + var startIndex = PreviousCodePointIndex(cursorIndex); + m_Content.text = text.Remove(startIndex, cursorIndex - startIndex); + selectIndex = cursorIndex = startIndex; + ClearCursorPos(); + return true; + } + return false; + } + + /// Select all the text + public void SelectAll() + { + cursorIndex = 0; selectIndex = text.Length; + ClearCursorPos(); + } + + /// Select none of the text + public void SelectNone() + { + selectIndex = cursorIndex; + ClearCursorPos(); + } + + /// Does this text field has a selection + public bool hasSelection { get { return cursorIndex != selectIndex; } } + + /// Returns the selected text + public string SelectedText + { + get + { + if (cursorIndex == selectIndex) + return ""; + if (cursorIndex < selectIndex) + return text.Substring(cursorIndex, selectIndex - cursorIndex); + else + return text.Substring(selectIndex, cursorIndex - selectIndex); + } + } + + /// Delete the current selection. If there is no selection, this function does not do anything... + public bool DeleteSelection() + { + if (cursorIndex == selectIndex) + return false; + if (cursorIndex < selectIndex) + { + m_Content.text = text.Substring(0, cursorIndex) + text.Substring(selectIndex, text.Length - selectIndex); + selectIndex = cursorIndex; + } + else + { + m_Content.text = text.Substring(0, selectIndex) + text.Substring(cursorIndex, text.Length - cursorIndex); + cursorIndex = selectIndex; + } + ClearCursorPos(); + + return true; + } + + /// Replace the selection with /replace/. If there is no selection, /replace/ is inserted at the current cursor point. + public void ReplaceSelection(string replace) + { + DeleteSelection(); + m_Content.text = text.Insert(cursorIndex, replace); + selectIndex = cursorIndex += replace.Length; + ClearCursorPos(); + } + + /// Replacted the selection with /c/ + public void Insert(char c) + { + ReplaceSelection(c.ToString()); + } + + /// Move selection to alt cursor /position/ + public void MoveSelectionToAltCursor() + { + if (m_iAltCursorPos == -1) + return; + int p = m_iAltCursorPos; + string tmp = SelectedText; + m_Content.text = text.Insert(p, tmp); + + if (p < cursorIndex) + { + cursorIndex += tmp.Length; + selectIndex += tmp.Length; + } + + DeleteSelection(); + + selectIndex = cursorIndex = p; + ClearCursorPos(); + } + + /// Move the cursor one character to the right and deselect. + public void MoveRight() + { + ClearCursorPos(); + if (selectIndex == cursorIndex) + { + cursorIndex = NextCodePointIndex(cursorIndex); + DetectFocusChange(); // TODO: Is this necessary? + selectIndex = cursorIndex; + } + else + { + if (selectIndex > cursorIndex) + cursorIndex = selectIndex; + else + selectIndex = cursorIndex; + } + } + + /// Move the cursor one character to the left and deselect. + public void MoveLeft() + { + if (selectIndex == cursorIndex) + { + cursorIndex = PreviousCodePointIndex(cursorIndex); + selectIndex = cursorIndex; + } + else + { + if (selectIndex > cursorIndex) + selectIndex = cursorIndex; + else + cursorIndex = selectIndex; + } + ClearCursorPos(); + } + + /// Move the cursor up and deselects. + public void MoveUp() + { + if (selectIndex < cursorIndex) + selectIndex = cursorIndex; + else + cursorIndex = selectIndex; + GrabGraphicalCursorPos(); + graphicalCursorPos.y -= 1; + cursorIndex = selectIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); + if (cursorIndex <= 0) + ClearCursorPos(); + } + + /// Move the cursor down and deselects. + public void MoveDown() + { + if (selectIndex > cursorIndex) + selectIndex = cursorIndex; + else + cursorIndex = selectIndex; + GrabGraphicalCursorPos(); + graphicalCursorPos.y += style.lineHeight + 5; + cursorIndex = selectIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); + if (cursorIndex == text.Length) + ClearCursorPos(); + } + + /// Moves the cursor to the start of the current line. + public void MoveLineStart() + { + // we start from the left-most selected character + int p = selectIndex < cursorIndex ? selectIndex : cursorIndex; + // then we scan back to find the first newline + int i = p; + while (i-- != 0) + if (text[i] == '\n') + { + selectIndex = cursorIndex = i + 1; + return; + } + selectIndex = cursorIndex = 0; + } + + /// Moves the selection to the end of the current line + public void MoveLineEnd() + { + // we start from the right-most selected character + int p = selectIndex > cursorIndex ? selectIndex : cursorIndex; + // then we scan forward to find the first newline + int i = p; + int strlen = text.Length; + while (i < strlen) + { + if (text[i] == '\n') + { + selectIndex = cursorIndex = i; + return; + } + i++; + } + selectIndex = cursorIndex = strlen; + } + + /// Move to the start of the current graphical line. This takes word-wrapping into consideration. + public void MoveGraphicalLineStart() + { + cursorIndex = selectIndex = GetGraphicalLineStart(cursorIndex < selectIndex ? cursorIndex : selectIndex); + } + + /// Move to the end of the current graphical line. This takes word-wrapping into consideration. + public void MoveGraphicalLineEnd() + { + cursorIndex = selectIndex = GetGraphicalLineEnd(cursorIndex > selectIndex ? cursorIndex : selectIndex); + } + + /// Moves the cursor to the beginning of the text + public void MoveTextStart() + { + selectIndex = cursorIndex = 0; + } + + /// Moves the cursor to the end of the text + public void MoveTextEnd() + { + selectIndex = cursorIndex = text.Length; + } + + private int IndexOfEndOfLine(int startIndex) + { + int index = text.IndexOf('\n', startIndex); + return (index != -1 ? index : text.Length); + } + + /// Move to the next paragraph + public void MoveParagraphForward() + { + cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; + if (cursorIndex < text.Length) + { + selectIndex = cursorIndex = IndexOfEndOfLine(cursorIndex + 1); + } + } + + /// Move to the previous paragraph + public void MoveParagraphBackward() + { + cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; + if (cursorIndex > 1) + { + selectIndex = cursorIndex = text.LastIndexOf('\n', cursorIndex - 2) + 1; + } + else + selectIndex = cursorIndex = 0; + } + + // + + // Move the cursor to a graphical position. Used for moving the cursor on MouseDown events. + public void MoveCursorToPosition(Vector2 cursorPosition) + { + MoveCursorToPosition_Internal(cursorPosition, Event.current.shift); + } + + // Move the cursor to a graphical position. Used for moving the cursor on MouseDown events. + protected internal void MoveCursorToPosition_Internal(Vector2 cursorPosition, bool shift) + { + selectIndex = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); + + if (!shift) + { + cursorIndex = selectIndex; + } + + DetectFocusChange(); // TODO: Is this necessary? + } + + public void MoveAltCursorToPosition(Vector2 cursorPosition) + { + int index = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); + m_iAltCursorPos = Mathf.Min(text.Length, index); + DetectFocusChange(); // TODO: Is this necessary? + } + + public bool IsOverSelection(Vector2 cursorPosition) + { + int p = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); + return ((p < Mathf.Max(cursorIndex, selectIndex)) && (p > Mathf.Min(cursorIndex, selectIndex))); + } + + // Do a drag selection. Used to expand the selection in MouseDrag events. + public void SelectToPosition(Vector2 cursorPosition) + { + if (!m_MouseDragSelectsWholeWords) + cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); + else // snap to words/paragraphs + { + int p = style.GetCursorStringIndex(localPosition, m_Content, cursorPosition + scrollOffset); + + EnsureValidCodePointIndex(ref p); + EnsureValidCodePointIndex(ref m_DblClickInitPos); + + if (doubleClickSnapping == DblClickSnapping.WORDS) + { + if (p < m_DblClickInitPos) + { + cursorIndex = FindEndOfClassification(p, Direction.Backward); + selectIndex = FindEndOfClassification(m_DblClickInitPos, Direction.Forward); + } + else + { + cursorIndex = FindEndOfClassification(p, Direction.Forward); + selectIndex = FindEndOfClassification(m_DblClickInitPos, Direction.Backward); + } + } // paragraph + else + { + if (p < m_DblClickInitPos) + { + if (p > 0) + cursorIndex = text.LastIndexOf('\n', Mathf.Max(0, p - 2)) + 1; + else + cursorIndex = 0; + + selectIndex = text.LastIndexOf('\n', m_DblClickInitPos); + } + else + { + if (p < text.Length) + { + cursorIndex = IndexOfEndOfLine(p); + } + else + cursorIndex = text.Length; + + selectIndex = text.LastIndexOf('\n', Mathf.Max(0, m_DblClickInitPos - 2)) + 1; + } + } + } + } + + /// Expand the selection to the left + public void SelectLeft() + { + if (m_bJustSelected) + if (cursorIndex > selectIndex) + { // swap + int tmp = cursorIndex; + cursorIndex = selectIndex; + selectIndex = tmp; + } + m_bJustSelected = false; + + cursorIndex = PreviousCodePointIndex(cursorIndex); + } + + public void SelectRight() + { + if (m_bJustSelected) + if (cursorIndex < selectIndex) + { // swap + int tmp = cursorIndex; + cursorIndex = selectIndex; + selectIndex = tmp; + } + m_bJustSelected = false; + + cursorIndex = NextCodePointIndex(cursorIndex); + } + + public void SelectUp() + { + GrabGraphicalCursorPos(); + graphicalCursorPos.y -= 1; + cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); + } + + public void SelectDown() + { + GrabGraphicalCursorPos(); + graphicalCursorPos.y += style.lineHeight + 5; + cursorIndex = style.GetCursorStringIndex(localPosition, m_Content, graphicalCursorPos); + } + + /// Select to the end of the text + public void SelectTextEnd() + { + // This is not quite like the mac - there, when you select to end of text, the position of the cursor becomes somewhat i'll defined + // Hard to explain. In textedit, try: CMD-SHIFT-down, SHIFT-LEFT for case 1. then do CMD-SHIFT-down, SHIFT-RIGHT, SHIFT-LEFT for case 2. + // Anyways, it's wrong so we won't do that + cursorIndex = text.Length; + } + + /// Select to the start of the text + public void SelectTextStart() + { + // Same thing as SelectTextEnd... + cursorIndex = 0; + } + + /// sets whether the text selection is done by dbl click or not + public void MouseDragSelectsWholeWords(bool on) + { + m_MouseDragSelectsWholeWords = on; + m_DblClickInitPos = cursorIndex; + } + + public void DblClickSnap(DblClickSnapping snapping) + { + doubleClickSnapping = snapping; + } + + int GetGraphicalLineStart(int p) + { + Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); + point.x = 0; + return style.GetCursorStringIndex(localPosition, m_Content, point); + } + + int GetGraphicalLineEnd(int p) + { + Vector2 point = style.GetCursorPixelPosition(localPosition, m_Content, p); + point.x += 5000; + return style.GetCursorStringIndex(localPosition, m_Content, point); + } + + int FindNextSeperator(int startPos) + { + int textLen = text.Length; + while (startPos < textLen && ClassifyChar(startPos) != CharacterType.LetterLike) + startPos = NextCodePointIndex(startPos); + while (startPos < textLen && ClassifyChar(startPos) == CharacterType.LetterLike) + startPos = NextCodePointIndex(startPos); + return startPos; + } + + int FindPrevSeperator(int startPos) + { + startPos = PreviousCodePointIndex(startPos); + while (startPos > 0 && ClassifyChar(startPos) != CharacterType.LetterLike) + startPos = PreviousCodePointIndex(startPos); + + if (startPos == 0) + return 0; + + while (startPos > 0 && ClassifyChar(startPos) == CharacterType.LetterLike) + startPos = PreviousCodePointIndex(startPos); + + if (ClassifyChar(startPos) == CharacterType.LetterLike) + return startPos; + return NextCodePointIndex(startPos); + } + + /// Move to the end of the word. + /// If the cursor is over some space characters, these are skipped + /// Then, the cursor moves to the end of the following word. + /// This corresponds to Alt-RightArrow on a Mac + public void MoveWordRight() + { + cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; + cursorIndex = selectIndex = FindNextSeperator(cursorIndex); + ClearCursorPos(); + } + + public void MoveToStartOfNextWord() + { + ClearCursorPos(); + if (cursorIndex != selectIndex) + { + MoveRight(); + return; + } + cursorIndex = selectIndex = FindStartOfNextWord(cursorIndex); + } + + public void MoveToEndOfPreviousWord() + { + ClearCursorPos(); + if (cursorIndex != selectIndex) + { + MoveLeft(); + return; + } + cursorIndex = selectIndex = FindEndOfPreviousWord(cursorIndex); + } + + public void SelectToStartOfNextWord() + { + ClearCursorPos(); + cursorIndex = FindStartOfNextWord(cursorIndex); + } + + public void SelectToEndOfPreviousWord() + { + ClearCursorPos(); + cursorIndex = FindEndOfPreviousWord(cursorIndex); + } + + enum CharacterType + { + LetterLike, + Symbol, Symbol2, + WhiteSpace + } + + CharacterType ClassifyChar(int index) + { + if (char.IsWhiteSpace(text, index)) + return CharacterType.WhiteSpace; + if (char.IsLetterOrDigit(text, index) || text[index] == '\'') + return CharacterType.LetterLike; + return CharacterType.Symbol; + } + + /// Move to start of next word. + /// This corresponds to Ctrl-RightArrow on Windows + /// If the cursor is over a whitespace, it's moved forwards ''till the first non-whitespace character + /// If the cursor is over an alphanumeric character, it''s moved forward 'till it encounters space or a punctuation mark. + /// If the stopping character is a space, this is skipped as well. + /// If the cursor is over an punctuation mark, it's moved forward ''till it a letter or a space of a punctuation mark. If the stopping character is a space, this is skipped as well + public int FindStartOfNextWord(int p) + { + int textLen = text.Length; + if (p == textLen) + return p; + + // Find out which char type we're at... + CharacterType t = ClassifyChar(p); + if (t != CharacterType.WhiteSpace) + { + p = NextCodePointIndex(p); + while (p < textLen && ClassifyChar(p) == t) + p = NextCodePointIndex(p); + } + else + { + if (text[p] == '\t' || text[p] == '\n') + return NextCodePointIndex(p); + } + + if (p == textLen) + return p; + + // Skip spaces + if (text[p] == ' ') // If we're at a space, skip over any number of spaces + { + while (p < textLen && ClassifyChar(p) == CharacterType.WhiteSpace) + p = NextCodePointIndex(p); + } + else if (text[p] == '\t' || text[p] == '\n') // If we're at a tab or a newline, just step one char ahead + { + return p; + } + return p; + } + + int FindEndOfPreviousWord(int p) + { + if (p == 0) + return p; + p = PreviousCodePointIndex(p); + + // Skip spaces + while (p > 0 && text[p] == ' ') + p = PreviousCodePointIndex(p); + + CharacterType t = ClassifyChar(p); + if (t != CharacterType.WhiteSpace) + { + while (p > 0 && ClassifyChar(PreviousCodePointIndex(p)) == t) + p = PreviousCodePointIndex(p); + } + return p; + } + + public void MoveWordLeft() + { + cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; + cursorIndex = FindPrevSeperator(cursorIndex); + selectIndex = cursorIndex; + } + + public void SelectWordRight() + { + ClearCursorPos(); + int cachedPos = selectIndex; + if (cursorIndex < selectIndex) + { + selectIndex = cursorIndex; + MoveWordRight(); + selectIndex = cachedPos; + cursorIndex = cursorIndex < selectIndex ? cursorIndex : selectIndex; + return; + } + selectIndex = cursorIndex; + MoveWordRight(); + selectIndex = cachedPos; + } + + public void SelectWordLeft() + { + ClearCursorPos(); + int cachedPos = selectIndex; + if (cursorIndex > selectIndex) + { + selectIndex = cursorIndex; + MoveWordLeft(); + selectIndex = cachedPos; + cursorIndex = cursorIndex > selectIndex ? cursorIndex : selectIndex; + return; + } + selectIndex = cursorIndex; + MoveWordLeft(); + selectIndex = cachedPos; + } + + /// Expand the selection to the start of the line + /// Used on a mac for CMD-SHIFT-LEFT + public void ExpandSelectGraphicalLineStart() + { + ClearCursorPos(); + if (cursorIndex < selectIndex) + cursorIndex = GetGraphicalLineStart(cursorIndex); + else + { + int temp = cursorIndex; + cursorIndex = GetGraphicalLineStart(selectIndex); + selectIndex = temp; + } + } + + /// Expand the selection to the end of the line + /// Used on a mac for CMD-SHIFT-RIGHT + public void ExpandSelectGraphicalLineEnd() + { + ClearCursorPos(); + if (cursorIndex > selectIndex) + cursorIndex = GetGraphicalLineEnd(cursorIndex); + else + { + int temp = cursorIndex; + cursorIndex = GetGraphicalLineEnd(selectIndex); + selectIndex = temp; + } + } + + /// Move the selection point to the start of the line + /// Used on a Windows for SHIFT-Home + public void SelectGraphicalLineStart() + { + ClearCursorPos(); + cursorIndex = GetGraphicalLineStart(cursorIndex); + } + + /// Expand the selection to the end of the line + /// Used on a mac for SHIFT-End + public void SelectGraphicalLineEnd() + { + ClearCursorPos(); + cursorIndex = GetGraphicalLineEnd(cursorIndex); + } + + public void SelectParagraphForward() + { + ClearCursorPos(); + bool wasBehind = cursorIndex < selectIndex; + if (cursorIndex < text.Length) + { + cursorIndex = IndexOfEndOfLine(cursorIndex + 1); + if (wasBehind && cursorIndex > selectIndex) + cursorIndex = selectIndex; + } + } + + public void SelectParagraphBackward() + { + ClearCursorPos(); + bool wasInFront = cursorIndex > selectIndex; + if (cursorIndex > 1) + { + cursorIndex = text.LastIndexOf('\n', cursorIndex - 2) + 1; + if (wasInFront && cursorIndex < selectIndex) + cursorIndex = selectIndex; + } + else + selectIndex = cursorIndex = 0; + } + + /// Select the word under the cursor + public void SelectCurrentWord() + { + var index = cursorIndex; + if (cursorIndex < selectIndex) + { + cursorIndex = FindEndOfClassification(index, Direction.Backward); + selectIndex = FindEndOfClassification(index, Direction.Forward); + } + else + { + cursorIndex = FindEndOfClassification(index, Direction.Forward); + selectIndex = FindEndOfClassification(index, Direction.Backward); + } + + ClearCursorPos(); + m_bJustSelected = true; + } + + enum Direction + { + Forward, + Backward, + } + + int FindEndOfClassification(int p, Direction dir) + { + if (text.Length == 0) + return 0; + + if (p == text.Length) + p = PreviousCodePointIndex(p); + + var t = ClassifyChar(p); + do + { + switch (dir) + { + case Direction.Backward: + p = PreviousCodePointIndex(p); + if (p == 0) + return ClassifyChar(0) == t ? 0 : NextCodePointIndex(0); + break; + + case Direction.Forward: + p = NextCodePointIndex(p); + if (p == text.Length) + return text.Length; + break; + } + } + while (ClassifyChar(p) == t); + if (dir == Direction.Forward) + return p; + return NextCodePointIndex(p); + } + + // Select the entire paragraph the cursor is on (separated by \n) + public void SelectCurrentParagraph() + { + ClearCursorPos(); + int textLen = text.Length; + + if (cursorIndex < textLen) + { + cursorIndex = IndexOfEndOfLine(cursorIndex) + 1; + } + if (selectIndex != 0) + selectIndex = text.LastIndexOf('\n', selectIndex - 1) + 1; + } + + public void UpdateScrollOffsetIfNeeded(Event evt) + { + if (evt.type != EventType.Repaint && evt.type != EventType.Layout) + { + UpdateScrollOffset(); + } + } + + internal void UpdateScrollOffset() + { + int cursorPos = cursorIndex; + graphicalCursorPos = style.GetCursorPixelPosition(new Rect(0, 0, position.width, position.height), m_Content, cursorPos); + + Rect r = style.padding.Remove(position); + + Vector2 contentSize = new Vector2(style.CalcSize(m_Content).x, style.CalcHeight(m_Content, position.width)); + + // If there is plenty of room, simply show entire string + if (contentSize.x < position.width) + { + scrollOffset.x = 0; + } + else if (m_RevealCursor) + { + //go right + if (graphicalCursorPos.x + 1 > scrollOffset.x + r.width) + // do we want html or apple behavior? this is html behavior + scrollOffset.x = graphicalCursorPos.x - r.width; + //go left + if (graphicalCursorPos.x < scrollOffset.x + style.padding.left) + scrollOffset.x = graphicalCursorPos.x - style.padding.left; + } + // ... and height/y as well + // If there is plenty of room, simply show entire string + if (contentSize.y < r.height) + { + scrollOffset.y = 0; + } + else if (m_RevealCursor) + { + //go down + if (graphicalCursorPos.y + style.lineHeight > scrollOffset.y + r.height + style.padding.top) + scrollOffset.y = graphicalCursorPos.y - r.height - style.padding.top + style.lineHeight; + //go up + if (graphicalCursorPos.y < scrollOffset.y + style.padding.top) + scrollOffset.y = graphicalCursorPos.y - style.padding.top; + } + + // This case takes many words to explain: + // 1. Text field has more text than it can fit vertically, and the cursor is at the very bottom (text field is scrolled down) + // 2. user e.g. deletes some lines of text at the bottom (backspace or select+delete) + // 3. now suddenly we have space at the bottom of text field, that is now not filled with any content + // 4. scroll text field up to fill in that space (this is what other text editors do) + if (scrollOffset.y > 0 && contentSize.y - scrollOffset.y < r.height + style.padding.top + style.padding.bottom) + scrollOffset.y = contentSize.y - r.height - style.padding.top - style.padding.bottom; + + scrollOffset.y = scrollOffset.y < 0 ? 0 : scrollOffset.y; + + m_RevealCursor = false; + } + + // TODO: get the height from the font + + public void DrawCursor(string newText) + { + string realText = text; + int cursorPos = cursorIndex; + if (InputManager.compositionString.Length > 0) + { + m_Content.text = newText.Substring(0, cursorIndex) + InputManager.compositionString + newText.Substring(selectIndex); + cursorPos += InputManager.compositionString.Length; + } + else + m_Content.text = newText; + + graphicalCursorPos = style.GetCursorPixelPosition(new Rect(0, 0, position.width, position.height), m_Content, cursorPos); + + //Debug.Log("Cursor pos: " + graphicalCursorPos); + + Vector2 originalContentOffset = style.contentOffset; + style.contentOffset -= scrollOffset; + style.Internal_clipOffset = scrollOffset; + + // Debug.Log ("ScrollOffset : " + scrollOffset); + + InputManager.compositionCursorPos = graphicalCursorPos + new Vector2(position.x, position.y + style.lineHeight) - scrollOffset; + + if (InputManager.compositionString.Length > 0) + style.DrawWithTextSelection(position, m_Content, controlID, cursorIndex, cursorIndex + InputManager.compositionString.Length, true); + else + style.DrawWithTextSelection(position, m_Content, controlID, cursorIndex, selectIndex); + + if (m_iAltCursorPos != -1) + style.DrawCursor(position, m_Content, controlID, m_iAltCursorPos); + + // reset + style.contentOffset = originalContentOffset; + style.Internal_clipOffset = Vector2.zero; + + m_Content.text = realText; + } + + bool PerformOperation(TextEditOp operation) + { + m_RevealCursor = true; + + switch (operation) + { + // NOTE the TODOs below: + case TextEditOp.MoveLeft: MoveLeft(); break; + case TextEditOp.MoveRight: MoveRight(); break; + case TextEditOp.MoveUp: MoveUp(); break; + case TextEditOp.MoveDown: MoveDown(); break; + case TextEditOp.MoveLineStart: MoveLineStart(); break; + case TextEditOp.MoveLineEnd: MoveLineEnd(); break; + case TextEditOp.MoveWordRight: MoveWordRight(); break; + case TextEditOp.MoveToStartOfNextWord: MoveToStartOfNextWord(); break; + case TextEditOp.MoveToEndOfPreviousWord: MoveToEndOfPreviousWord(); break; + case TextEditOp.MoveWordLeft: MoveWordLeft(); break; + case TextEditOp.MoveTextStart: MoveTextStart(); break; + case TextEditOp.MoveTextEnd: MoveTextEnd(); break; + case TextEditOp.MoveParagraphForward: MoveParagraphForward(); break; + case TextEditOp.MoveParagraphBackward: MoveParagraphBackward(); break; + // case TextEditOp.MovePageUp: return MovePageUp (); break; + // case TextEditOp.MovePageDown: return MovePageDown (); break; + case TextEditOp.MoveGraphicalLineStart: MoveGraphicalLineStart(); break; + case TextEditOp.MoveGraphicalLineEnd: MoveGraphicalLineEnd(); break; + case TextEditOp.SelectLeft: SelectLeft(); break; + case TextEditOp.SelectRight: SelectRight(); break; + case TextEditOp.SelectUp: SelectUp(); break; + case TextEditOp.SelectDown: SelectDown(); break; + case TextEditOp.SelectWordRight: SelectWordRight(); break; + case TextEditOp.SelectWordLeft: SelectWordLeft(); break; + case TextEditOp.SelectToEndOfPreviousWord: SelectToEndOfPreviousWord(); break; + case TextEditOp.SelectToStartOfNextWord: SelectToStartOfNextWord(); break; + + case TextEditOp.SelectTextStart: SelectTextStart(); break; + case TextEditOp.SelectTextEnd: SelectTextEnd(); break; + case TextEditOp.ExpandSelectGraphicalLineStart: ExpandSelectGraphicalLineStart(); break; + case TextEditOp.ExpandSelectGraphicalLineEnd: ExpandSelectGraphicalLineEnd(); break; + case TextEditOp.SelectParagraphForward: SelectParagraphForward(); break; + case TextEditOp.SelectParagraphBackward: SelectParagraphBackward(); break; + case TextEditOp.SelectGraphicalLineStart: SelectGraphicalLineStart(); break; + case TextEditOp.SelectGraphicalLineEnd: SelectGraphicalLineEnd(); break; + // case TextEditOp.SelectPageUp: return SelectPageUp (); break; + // case TextEditOp.SelectPageDown: return SelectPageDown (); break; + case TextEditOp.Delete: return Delete(); + case TextEditOp.Backspace: return Backspace(); + case TextEditOp.Cut: return Cut(); + case TextEditOp.Copy: Copy(); break; + case TextEditOp.Paste: return Paste(); + case TextEditOp.SelectAll: SelectAll(); break; + case TextEditOp.SelectNone: SelectNone(); break; + // case TextEditOp.ScrollStart: return ScrollStart (); break; + // case TextEditOp.ScrollEnd: return ScrollEnd (); break; + // case TextEditOp.ScrollPageUp: return ScrollPageUp (); break; + // case TextEditOp.ScrollPageDown: return ScrollPageDown (); break; + case TextEditOp.DeleteWordBack: return DeleteWordBack(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code + case TextEditOp.DeleteLineBack: return DeleteLineBack(); + case TextEditOp.DeleteWordForward: return DeleteWordForward(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code + default: + //Debug.Log("Unimplemented: " + operation); + break; + } + + return false; + } + + enum TextEditOp + { + MoveLeft, MoveRight, MoveUp, MoveDown, MoveLineStart, MoveLineEnd, MoveTextStart, MoveTextEnd, MovePageUp, MovePageDown, + MoveGraphicalLineStart, MoveGraphicalLineEnd, MoveWordLeft, MoveWordRight, + MoveParagraphForward, MoveParagraphBackward, MoveToStartOfNextWord, MoveToEndOfPreviousWord, + SelectLeft, SelectRight, SelectUp, SelectDown, SelectTextStart, SelectTextEnd, SelectPageUp, SelectPageDown, + ExpandSelectGraphicalLineStart, ExpandSelectGraphicalLineEnd, SelectGraphicalLineStart, SelectGraphicalLineEnd, + SelectWordLeft, SelectWordRight, SelectToEndOfPreviousWord, SelectToStartOfNextWord, + SelectParagraphBackward, SelectParagraphForward, + Delete, Backspace, DeleteWordBack, DeleteWordForward, DeleteLineBack, + Cut, Copy, Paste, SelectAll, SelectNone, + ScrollStart, ScrollEnd, ScrollPageUp, ScrollPageDown + }; + + string oldText; + int oldPos, oldSelectPos; + + public void SaveBackup() + { + oldText = text; + oldPos = cursorIndex; + oldSelectPos = selectIndex; + } + + public void Undo() + { + m_Content.text = oldText; + cursorIndex = oldPos; + selectIndex = oldSelectPos; + } + + public bool Cut() + { + //Debug.Log ("Cut"); + if (isPasswordField) + return false; + Copy(); + return DeleteSelection(); + } + + public void Copy() + { + //Debug.Log ("Copy"); + if (selectIndex == cursorIndex) + return; + + if (isPasswordField) + return; + + string copyStr; + if (cursorIndex < selectIndex) + copyStr = text.Substring(cursorIndex, selectIndex - cursorIndex); + else + copyStr = text.Substring(selectIndex, cursorIndex - selectIndex); + + GUIUtility.systemCopyBuffer = copyStr; + } + + static string ReplaceNewlinesWithSpaces(string value) + { + // First get rid of Windows style new lines and then *nix so we don't leave '\r' around. + value = value.Replace("\r\n", " "); + value = value.Replace('\n', ' '); + // This probably won't happen, but just in case... + value = value.Replace('\r', ' '); + return value; + } + + public bool Paste() + { + //Debug.Log ("Paste"); + string pasteval = GUIUtility.systemCopyBuffer; + if (pasteval != "") + { + if (!multiline) + pasteval = ReplaceNewlinesWithSpaces(pasteval); + ReplaceSelection(pasteval); + return true; + } + return false; + } + + static void MapKey(string key, TextEditOp action) + { + s_Keyactions[KeyboardEvent(key).ToString()] = action; + } + + private static Event KeyboardEvent(string key) + { + Event evt = new Event(0) { type = EventType.KeyDown }; + if (string.IsNullOrEmpty(key)) + return evt; + int startIdx = 0; + bool found = false; + do + { + found = true; + if (startIdx >= key.Length) + { + found = false; break; + } + switch (key[startIdx]) + { + case '&': // Alt + evt.modifiers |= EventModifiers.Alt; startIdx++; + break; + case '^': // Ctrl + evt.modifiers |= EventModifiers.Control; startIdx++; + break; + case '%': + evt.modifiers |= EventModifiers.Command; startIdx++; + break; + case '#': + evt.modifiers |= EventModifiers.Shift; startIdx++; + break; + default: + found = false; + break; + } + } + while (found); + string subStr = key.Substring(startIdx, key.Length - startIdx).ToLowerInvariant(); + switch (subStr) + { + case "[0]": evt.character = '0'; evt.keyCode = KeyCode.Keypad0; break; + case "[1]": evt.character = '1'; evt.keyCode = KeyCode.Keypad1; break; + case "[2]": evt.character = '2'; evt.keyCode = KeyCode.Keypad2; break; + case "[3]": evt.character = '3'; evt.keyCode = KeyCode.Keypad3; break; + case "[4]": evt.character = '4'; evt.keyCode = KeyCode.Keypad4; break; + case "[5]": evt.character = '5'; evt.keyCode = KeyCode.Keypad5; break; + case "[6]": evt.character = '6'; evt.keyCode = KeyCode.Keypad6; break; + case "[7]": evt.character = '7'; evt.keyCode = KeyCode.Keypad7; break; + case "[8]": evt.character = '8'; evt.keyCode = KeyCode.Keypad8; break; + case "[9]": evt.character = '9'; evt.keyCode = KeyCode.Keypad9; break; + case "[.]": evt.character = '.'; evt.keyCode = KeyCode.KeypadPeriod; break; + case "[/]": evt.character = '/'; evt.keyCode = KeyCode.KeypadDivide; break; + case "[-]": evt.character = '-'; evt.keyCode = KeyCode.KeypadMinus; break; + case "[+]": evt.character = '+'; evt.keyCode = KeyCode.KeypadPlus; break; + case "[=]": evt.character = '='; evt.keyCode = KeyCode.KeypadEquals; break; + case "[equals]": evt.character = '='; evt.keyCode = KeyCode.KeypadEquals; break; + case "[enter]": evt.character = '\n'; evt.keyCode = KeyCode.KeypadEnter; break; + case "up": evt.keyCode = KeyCode.UpArrow; evt.modifiers |= EventModifiers.FunctionKey; break; + case "down": evt.keyCode = KeyCode.DownArrow; evt.modifiers |= EventModifiers.FunctionKey; break; + case "left": evt.keyCode = KeyCode.LeftArrow; evt.modifiers |= EventModifiers.FunctionKey; break; + case "right": evt.keyCode = KeyCode.RightArrow; evt.modifiers |= EventModifiers.FunctionKey; break; + case "insert": evt.keyCode = KeyCode.Insert; evt.modifiers |= EventModifiers.FunctionKey; break; + case "home": evt.keyCode = KeyCode.Home; evt.modifiers |= EventModifiers.FunctionKey; break; + case "end": evt.keyCode = KeyCode.End; evt.modifiers |= EventModifiers.FunctionKey; break; + case "pgup": evt.keyCode = KeyCode.PageDown; evt.modifiers |= EventModifiers.FunctionKey; break; + case "page up": evt.keyCode = KeyCode.PageUp; evt.modifiers |= EventModifiers.FunctionKey; break; + case "pgdown": evt.keyCode = KeyCode.PageUp; evt.modifiers |= EventModifiers.FunctionKey; break; + case "page down": evt.keyCode = KeyCode.PageDown; evt.modifiers |= EventModifiers.FunctionKey; break; + case "backspace": evt.keyCode = KeyCode.Backspace; evt.modifiers |= EventModifiers.FunctionKey; break; + case "delete": evt.keyCode = KeyCode.Delete; evt.modifiers |= EventModifiers.FunctionKey; break; + case "tab": evt.keyCode = KeyCode.Tab; break; + case "f1": evt.keyCode = KeyCode.F1; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f2": evt.keyCode = KeyCode.F2; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f3": evt.keyCode = KeyCode.F3; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f4": evt.keyCode = KeyCode.F4; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f5": evt.keyCode = KeyCode.F5; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f6": evt.keyCode = KeyCode.F6; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f7": evt.keyCode = KeyCode.F7; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f8": evt.keyCode = KeyCode.F8; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f9": evt.keyCode = KeyCode.F9; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f10": evt.keyCode = KeyCode.F10; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f11": evt.keyCode = KeyCode.F11; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f12": evt.keyCode = KeyCode.F12; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f13": evt.keyCode = KeyCode.F13; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f14": evt.keyCode = KeyCode.F14; evt.modifiers |= EventModifiers.FunctionKey; break; + case "f15": evt.keyCode = KeyCode.F15; evt.modifiers |= EventModifiers.FunctionKey; break; + case "[esc]": evt.keyCode = KeyCode.Escape; break; + case "return": evt.character = '\n'; evt.keyCode = KeyCode.Return; evt.modifiers &= ~EventModifiers.FunctionKey; break; + case "space": evt.keyCode = KeyCode.Space; evt.character = ' '; evt.modifiers &= ~EventModifiers.FunctionKey; break; + default: + if (subStr.Length != 1) + { + try + { + evt.keyCode = (KeyCode)Enum.Parse(typeof(KeyCode), subStr, true); + } + catch (ArgumentException) + { + ExplorerCore.LogError(string.Format("Unable to find key name that matches '{0}'", subStr)); + } + } + else + { + evt.character = subStr.ToLower()[0]; + evt.keyCode = (KeyCode)evt.character; + if (evt.modifiers != 0) + evt.character = (char)0; + } + break; + } + return evt; + } + + static Dictionary s_Keyactions; + /// Set up a platform independant keyboard->Edit action map. This varies depending on whether we are on mac or windows. + void InitKeyActions() + { + if (s_Keyactions != null) + return; + s_Keyactions = new Dictionary(); + + // key mappings shared by the platforms + MapKey("left", TextEditOp.MoveLeft); + MapKey("right", TextEditOp.MoveRight); + MapKey("up", TextEditOp.MoveUp); + MapKey("down", TextEditOp.MoveDown); + + MapKey("#left", TextEditOp.SelectLeft); + MapKey("#right", TextEditOp.SelectRight); + MapKey("#up", TextEditOp.SelectUp); + MapKey("#down", TextEditOp.SelectDown); + + MapKey("delete", TextEditOp.Delete); + MapKey("backspace", TextEditOp.Backspace); + MapKey("#backspace", TextEditOp.Backspace); + + // OSX is the special case for input shortcuts + if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) + { + // Keyboard mappings for mac + // TODO MapKey ("home", TextEditOp.ScrollStart); + // TODO MapKey ("end", TextEditOp.ScrollEnd); + // TODO MapKey ("page up", TextEditOp.ScrollPageUp); + // TODO MapKey ("page down", TextEditOp.ScrollPageDown); + + MapKey("^left", TextEditOp.MoveGraphicalLineStart); + MapKey("^right", TextEditOp.MoveGraphicalLineEnd); + // TODO MapKey ("^up", TextEditOp.ScrollPageUp); + // TODO MapKey ("^down", TextEditOp.ScrollPageDown); + + MapKey("&left", TextEditOp.MoveWordLeft); + MapKey("&right", TextEditOp.MoveWordRight); + MapKey("&up", TextEditOp.MoveParagraphBackward); + MapKey("&down", TextEditOp.MoveParagraphForward); + + MapKey("%left", TextEditOp.MoveGraphicalLineStart); + MapKey("%right", TextEditOp.MoveGraphicalLineEnd); + MapKey("%up", TextEditOp.MoveTextStart); + MapKey("%down", TextEditOp.MoveTextEnd); + + MapKey("#home", TextEditOp.SelectTextStart); + MapKey("#end", TextEditOp.SelectTextEnd); + // TODO MapKey ("#page up", TextEditOp.SelectPageUp); + // TODO MapKey ("#page down", TextEditOp.SelectPageDown); + + MapKey("#^left", TextEditOp.ExpandSelectGraphicalLineStart); + MapKey("#^right", TextEditOp.ExpandSelectGraphicalLineEnd); + MapKey("#^up", TextEditOp.SelectParagraphBackward); + MapKey("#^down", TextEditOp.SelectParagraphForward); + + MapKey("#&left", TextEditOp.SelectWordLeft); + MapKey("#&right", TextEditOp.SelectWordRight); + MapKey("#&up", TextEditOp.SelectParagraphBackward); + MapKey("#&down", TextEditOp.SelectParagraphForward); + + MapKey("#%left", TextEditOp.ExpandSelectGraphicalLineStart); + MapKey("#%right", TextEditOp.ExpandSelectGraphicalLineEnd); + MapKey("#%up", TextEditOp.SelectTextStart); + MapKey("#%down", TextEditOp.SelectTextEnd); + + MapKey("%a", TextEditOp.SelectAll); + MapKey("%x", TextEditOp.Cut); + MapKey("%c", TextEditOp.Copy); + MapKey("%v", TextEditOp.Paste); + + // emacs-like keybindings + MapKey("^d", TextEditOp.Delete); + MapKey("^h", TextEditOp.Backspace); + MapKey("^b", TextEditOp.MoveLeft); + MapKey("^f", TextEditOp.MoveRight); + MapKey("^a", TextEditOp.MoveLineStart); + MapKey("^e", TextEditOp.MoveLineEnd); + + MapKey("&delete", TextEditOp.DeleteWordForward); + MapKey("&backspace", TextEditOp.DeleteWordBack); + MapKey("%backspace", TextEditOp.DeleteLineBack); + } + else + { + // Windows/Linux keymappings + MapKey("home", TextEditOp.MoveGraphicalLineStart); + MapKey("end", TextEditOp.MoveGraphicalLineEnd); + // TODO MapKey ("page up", TextEditOp.MovePageUp); + // TODO MapKey ("page down", TextEditOp.MovePageDown); + + MapKey("%left", TextEditOp.MoveWordLeft); + MapKey("%right", TextEditOp.MoveWordRight); + MapKey("%up", TextEditOp.MoveParagraphBackward); + MapKey("%down", TextEditOp.MoveParagraphForward); + + MapKey("^left", TextEditOp.MoveToEndOfPreviousWord); + MapKey("^right", TextEditOp.MoveToStartOfNextWord); + MapKey("^up", TextEditOp.MoveParagraphBackward); + MapKey("^down", TextEditOp.MoveParagraphForward); + + MapKey("#^left", TextEditOp.SelectToEndOfPreviousWord); + MapKey("#^right", TextEditOp.SelectToStartOfNextWord); + MapKey("#^up", TextEditOp.SelectParagraphBackward); + MapKey("#^down", TextEditOp.SelectParagraphForward); + + MapKey("#home", TextEditOp.SelectGraphicalLineStart); + MapKey("#end", TextEditOp.SelectGraphicalLineEnd); + // TODO MapKey ("#page up", TextEditOp.SelectPageUp); + // TODO MapKey ("#page down", TextEditOp.SelectPageDown); + + MapKey("^delete", TextEditOp.DeleteWordForward); + MapKey("^backspace", TextEditOp.DeleteWordBack); + MapKey("%backspace", TextEditOp.DeleteLineBack); + + MapKey("^a", TextEditOp.SelectAll); + MapKey("^x", TextEditOp.Cut); + MapKey("^c", TextEditOp.Copy); + MapKey("^v", TextEditOp.Paste); + MapKey("#delete", TextEditOp.Cut); + MapKey("^insert", TextEditOp.Copy); + MapKey("#insert", TextEditOp.Paste); + } + } + + public void DetectFocusChange() + { + OnDetectFocusChange(); + } + + internal virtual void OnDetectFocusChange() + { + if (m_HasFocus == true && controlID != GUIUtility.keyboardControl) + OnLostFocus(); + if (m_HasFocus == false && controlID == GUIUtility.keyboardControl) + OnFocus(); + } + + internal virtual void OnCursorIndexChange() + { + } + + internal virtual void OnSelectIndexChange() + { + } + + private void ClampTextIndex(ref int index) + { + index = Mathf.Clamp(index, 0, text.Length); + } + + void EnsureValidCodePointIndex(ref int index) + { + ClampTextIndex(ref index); + if (!IsValidCodePointIndex(index)) + index = NextCodePointIndex(index); + } + + bool IsValidCodePointIndex(int index) + { + if (index < 0 || index > text.Length) + return false; + if (index == 0 || index == text.Length) + return true; + return !char.IsLowSurrogate(text[index]); + } + + int PreviousCodePointIndex(int index) + { + if (index > 0) + index--; + while (index > 0 && char.IsLowSurrogate(text[index])) + index--; + return index; + } + + int NextCodePointIndex(int index) + { + if (index < text.Length) + index++; + while (index < text.Length && char.IsLowSurrogate(text[index])) + index++; + return index; + } + } +} // namespace + +#endif \ No newline at end of file