MadLittleMods
5/20/2015 - 4:23 AM

Unity GUI text area: `MLMEditorGUI.TextAreaWithTabs(Rect position, string text)`

Unity GUI text area: MLMEditorGUI.TextAreaWithTabs(Rect position, string text)

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Reflection;

using self = MLMEditorGUI;
using System.Collections.Generic;

public class MLMEditorGUI : MonoBehaviour {

	static FieldInfo TextEditor_m_HasFocus__FieldInfo;

	static PropertyInfo GUIUtility_textFieldInput__PropertyInfo;
	static MethodInfo GUIUtility_CheckOnGUI__MethodInfo;

	static MethodInfo TextEditor_UpdateScrollOffsetIfNeeded__MethodInfo;

	static MethodInfo GUIContent_Temp__MethodInfo;
	
	static FieldInfo EditorGUI_s_RecycledEditor__FieldInfo;
	static FieldInfo EditorGUI_activeEditor__FieldInfo;
	static MethodInfo RecycledTextEditor_EndEditing__MethodInfo;
	static MethodInfo RecycledTextEditor_BeginEditing__MethodInfo;


	static MLMEditorGUI() {
		BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;

		self.TextEditor_m_HasFocus__FieldInfo = typeof(TextEditor).GetField("m_HasFocus", bindingFlags);

		self.GUIUtility_textFieldInput__PropertyInfo = typeof(GUIUtility).GetProperty("textFieldInput", bindingFlags);
		self.GUIUtility_CheckOnGUI__MethodInfo = typeof(GUIUtility).GetMethod("CheckOnGUI", bindingFlags);

		self.TextEditor_UpdateScrollOffsetIfNeeded__MethodInfo = typeof(TextEditor).GetMethod("UpdateScrollOffsetIfNeeded", bindingFlags);

		self.GUIContent_Temp__MethodInfo = typeof(GUIContent).GetMethod("Temp", bindingFlags, null, new [] { typeof(string) }, null);
	
		self.EditorGUI_s_RecycledEditor__FieldInfo = typeof(EditorGUI).GetField("s_RecycledEditor", bindingFlags);
		self.EditorGUI_activeEditor__FieldInfo = typeof(EditorGUI).GetField("activeEditor", bindingFlags);
		self.RecycledTextEditor_EndEditing__MethodInfo = typeof(EditorGUI).GetNestedType("RecycledTextEditor", bindingFlags).GetMethod("EndEditing", bindingFlags);
		self.RecycledTextEditor_BeginEditing__MethodInfo = typeof(EditorGUI).GetNestedType("RecycledTextEditor", bindingFlags).GetMethod("BeginEditing", bindingFlags);
	}


	public static string TextAreaWithTabs(Rect position, string text)
	{
		//GUIContent gUIContent = GUIContent.Temp(text);
		GUIContent gUIContent = (GUIContent)self.GUIContent_Temp__MethodInfo.Invoke(null, new object[]{ text });
		self.DoTextField(position, GUIUtility.GetControlID(FocusType.Keyboard, position), gUIContent, true, -1, GUI.skin.textArea);
		return gUIContent.text;
	}


	public static void DoTextField(Rect position, int id, GUIContent content, bool multiline, int maxLength, GUIStyle style)
	{
		if (maxLength >= 0 && content.text.Length > maxLength)
		{
			content.text = content.text.Substring(0, maxLength);
		}
		//GUIUtility.CheckOnGUI();
		self.GUIUtility_CheckOnGUI__MethodInfo.Invoke(null, null);
		TextEditor stateObject = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), id);
		stateObject.content.text = content.text;
		stateObject.SaveBackup();
		stateObject.position = position;
		stateObject.style = style;
		stateObject.multiline = multiline;
		stateObject.controlID = id;
		stateObject.ClampPos();
		if (GUIUtility.keyboardControl == id && Event.current.type != EventType.Layout)
		{
			//stateObject.UpdateScrollOffsetIfNeeded();
			if(self.TextEditor_UpdateScrollOffsetIfNeeded__MethodInfo != null) {
				self.TextEditor_UpdateScrollOffsetIfNeeded__MethodInfo.Invoke(stateObject, null);
			}
		}
		self.HandleTextFieldEventForDesktop(position, id, content, multiline, maxLength, style, stateObject);
	}

	// Keep track when the tab key was down
	// So that on a KeyUp event, we can check if it corresponds correctly
	// as the controlId would have moved on to the next focusable GUI item when tab is pushed
	static bool wasTabDown = false;
	public static void HandleTextFieldEventForDesktop(Rect position, int id, GUIContent content, bool multiline, int maxLength, GUIStyle style, TextEditor editor)
	{
		Event @event = Event.current;
		bool flag = false;
		string textAreaName = "TextArea" + id;

		switch (@event.type)
		{
			case EventType.MouseDown:
			{
				if (position.Contains(@event.mousePosition))
				{
					GUIUtility.hotControl = id;
					GUIUtility.keyboardControl = id;
					//editor.m_HasFocus = true;
					self.TextEditor_m_HasFocus__FieldInfo.SetValue(editor, true);
					editor.MoveCursorToPosition(Event.current.mousePosition);
					if (Event.current.clickCount == 2 && GUI.skin.settings.doubleClickSelectsWord)
					{
						editor.SelectCurrentWord();
						editor.DblClickSnap(TextEditor.DblClickSnapping.WORDS);
						editor.MouseDragSelectsWholeWords(true);
					}
					if (Event.current.clickCount == 3 && GUI.skin.settings.tripleClickSelectsLine)
					{
						editor.SelectCurrentParagraph();
						editor.MouseDragSelectsWholeWords(true);
						editor.DblClickSnap(TextEditor.DblClickSnapping.PARAGRAPHS);
					}
					@event.Use();
				}
				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
			case EventType.MouseUp:
			{
				if (GUIUtility.hotControl == id)
				{
					editor.MouseDragSelectsWholeWords(false);
					GUIUtility.hotControl = 0;
					@event.Use();
				}
				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
			case EventType.KeyUp:
			case EventType.MouseMove:
			case EventType.ScrollWheel:
			{
				// Keep focus on this input when tab is pushed
				if (self.wasTabDown)
				{
					if(@event.type == EventType.KeyUp && @event.keyCode == KeyCode.Tab) {
						GUIUtility.hotControl = id;
						GUIUtility.keyboardControl = id;
						//editor.m_HasFocus = true;
						self.TextEditor_m_HasFocus__FieldInfo.SetValue(editor, true);
					}

					self.wasTabDown = false;
				}


				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
			case EventType.MouseDrag:
			{
				if (GUIUtility.hotControl == id)
				{
					if (!@event.shift)
					{
						editor.SelectToPosition(Event.current.mousePosition);
					}
					else
					{
						editor.MoveCursorToPosition(Event.current.mousePosition);
					}
					@event.Use();
				}
				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
			case EventType.KeyDown:
			{
				if (GUIUtility.keyboardControl != id)
				{
					return;
				}
				if (!editor.HandleKeyEvent(@event))
				{
					char chr = @event.character;
					//if (@event.keyCode == KeyCode.Tab || @event.character == '\t') {
					//	editor.Insert(chr);
					//	flag = true;

					//	return;
					//}
					if (chr == '\n' && !multiline && !@event.alt)
					{
						return;
					}
					Font font = style.font;
					if (!font)
					{
						font = GUI.skin.font;
					}
					if(chr == '\t') {
						self.wasTabDown = true;
					}
					if (font.HasCharacter(chr) || chr == '\n' || chr == '\t')
					{
						// Shift tab should remove a tab if present
						if(chr == '\t' && @event.shift) {
							if(editor.pos > 0 && editor.content.text[editor.pos-1] == '\t') {
								editor.Backspace();
							}
						}
						else {
							editor.Insert(chr);
						}

						flag = true;
						if (GUIUtility.keyboardControl == id)
						{
							//GUIUtility.textFieldInput = true;
							self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
						}

						GUI.changed = true;
						content.text = editor.content.text;
						if (maxLength >= 0 && content.text.Length > maxLength)
						{
							content.text = content.text.Substring(0, maxLength);
						}
						@event.Use();

						return;
					}
					else
					{
						if (chr == 0)
						{
							if (Input.compositionString.Length > 0)
							{
								editor.ReplaceSelection(string.Empty);
								flag = true;
							}
							@event.Use();
						}
						if (GUIUtility.keyboardControl == id)
						{
							//GUIUtility.textFieldInput = true;
							self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
						}
						if (flag)
						{
							GUI.changed = true;
							content.text = editor.content.text;
							if (maxLength >= 0 && content.text.Length > maxLength)
							{
								content.text = content.text.Substring(0, maxLength);
							}
							@event.Use();
						}
						return;
					}
				}
				else
				{
					@event.Use();
					flag = true;
					content.text = editor.content.text;
					if (GUIUtility.keyboardControl == id)
					{
						//GUIUtility.textFieldInput = true;
						self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
					}
					if (flag)
					{
						GUI.changed = true;
						content.text = editor.content.text;
						if (maxLength >= 0 && content.text.Length > maxLength)
						{
							content.text = content.text.Substring(0, maxLength);
						}
						@event.Use();
					}
					return;
				}
			}
			case EventType.Repaint:
			{
				if (GUIUtility.keyboardControl == id)
				{
					editor.DrawCursor(content.text);
				}
				else
				{
					style.Draw(position, content, id, false);
				}
				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
			
			default:
			{
				switch (@event.type) {

					case EventType.ValidateCommand:
					{
						string commandName = @event.commandName;
						if (commandName != null)
						{
							switch (commandName)
							{
								
								case "UndoRedoPerformed":
								{
									editor.content.text = content.text;
									@event.Use();
									break;
								}
								case "Cut":
								{
									if (editor.hasSelection)
									{
										@event.Use();
									}
									break;
								}
								case "Copy":
								{
									if (editor.hasSelection)
									{
										@event.Use();
									}
									break;
								}
								case "Paste":
								{
									if (editor.CanPaste())
									{
										@event.Use();
									}
									break;
								}
								case "SelectAll":
								{
									@event.Use();
									break;
								}
							}
							
						}

						break;
					}

					case EventType.ExecuteCommand:
					{
						if (GUIUtility.keyboardControl == id)
						{
							string commandName = @event.commandName;
							if (commandName != null)
							{
								switch (commandName)
								{
									case "OnLostFocus":
									{
										var activeEditor = self.EditorGUI_activeEditor__FieldInfo.GetValue(null);
										if (activeEditor != null)
										{
											//EditorGUI.activeEditor.EndEditing();
											self.RecycledTextEditor_EndEditing__MethodInfo.Invoke(activeEditor, null);
										}
										@event.Use();
										break;
									}
									case "Cut":
									{
										//editor.BeginEditing(id, text, position, style, multiline, passwordField);
										self.RecycledTextEditor_BeginEditing__MethodInfo.Invoke(self.EditorGUI_s_RecycledEditor__FieldInfo.GetValue(null), new object[] {
											id, content.text, position, style, multiline, false
										});
										editor.Cut();
										flag = true;
										break;
									}
									case "Copy":
									{
										editor.Copy();
										@event.Use();
										break;
									}
									case "Paste":
									{
										//editor.BeginEditing(id, text, position, style, multiline, passwordField);
										self.RecycledTextEditor_BeginEditing__MethodInfo.Invoke(self.EditorGUI_s_RecycledEditor__FieldInfo.GetValue(null), new object[] {
											id, content.text, position, style, multiline, false
										});
										editor.Paste();
										flag = true;
										break;
									}
									case "SelectAll":
									{
										editor.SelectAll();
										@event.Use();
										break;
									}
								}
								
							}
						}
						break;
					}
				}


				if (GUIUtility.keyboardControl == id)
				{
					//GUIUtility.textFieldInput = true;
					self.GUIUtility_textFieldInput__PropertyInfo.SetValue(null, true, null);
				}
				if (flag)
				{
					GUI.changed = true;
					content.text = editor.content.text;
					if (maxLength >= 0 && content.text.Length > maxLength)
					{
						content.text = content.text.Substring(0, maxLength);
					}
					@event.Use();
				}
				return;
			}
		}
	}
}