In the beginning, I was trying reimplement all of the SelectableLabel
functionality but I ended up using Reflection to get access to EditorGUI.DoTextField(..)
. EditorGUI.DoTextField(..)
handles all of the nice text field features for many gui elements like TextField, TextArea, PasswordField, and SelectableLabel.
Usage:
bool foldoutOpen = false;
foldoutOpen = MLMEditorGUI.SelectableFoldout(EditorGUILayout.GetControlRect(), foldoutOpen, "Selectable Foldout", EditorStyles.label, EditorStyles.foldout);
MLMEditorGUI.cs:
using UnityEngine;
using UnityEngine.Internal;
using UnityEditor;
using System;
using System.Reflection;
public static class MLMEditorGUI
{
public struct GUIStructure
{
public Rect rect;
public GUIContent content;
}
static Type recycledTextEditorType;
static object recycledTextEditor;
static MethodInfo EditorGUI_DoTextField_MethodInfo;
static MethodInfo RecycledTextEditor_MouseDragSelectsWholeWords_MethodInfo;
static MethodInfo RecycledTextEditor_SelectAll_MethodInfo;
static DateTime lastClickTime = new DateTime();
static int lastClickCount = 0;
// Static Contstructor
static MLMEditorGUI()
{
lastClickTime = new DateTime();
lastClickCount = 0;
// Instantiate a EditorGUI.RecycledTextEditor which is internal...
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static;
Type editorGUIType = typeof(EditorGUI).Assembly.GetType("UnityEditor.EditorGUI");
if(editorGUIType != null)
{
recycledTextEditorType = editorGUIType.GetNestedType("RecycledTextEditor", bindingFlags);
if(recycledTextEditorType != null)
{
recycledTextEditor = Activator.CreateInstance(recycledTextEditorType);
if(recycledTextEditor != null)
{
RecycledTextEditor_MouseDragSelectsWholeWords_MethodInfo = recycledTextEditorType.GetMethod("MouseDragSelectsWholeWords");
RecycledTextEditor_SelectAll_MethodInfo = recycledTextEditorType.GetMethod("SelectAll");
EditorGUI_DoTextField_MethodInfo = typeof(EditorGUI).GetMethod("DoTextField", bindingFlags);
}
}
}
}
public static bool SelectableFoldout(Rect position, bool isExpanded, GUIContent content, [DefaultValue ("EditorStyles.label")] GUIStyle labelStyle, [DefaultValue ("EditorStyles.foldout")] GUIStyle foldoutStyle)
{
Rect controlPosition = EditorGUI.IndentedRect(position);
int controlID = 0;//GUIUtility.GetControlID ();//GUIUtility.GetControlID(content, FocusType.Keyboard, position);
controlID = GUIUtility.GetControlID(content, FocusType.Keyboard, controlPosition);
GUIStyle foldoutPaintStyle = new GUIStyle(foldoutStyle);
foldoutStyle.padding.right = 0;
foldoutStyle.margin.right = 0;
GUIStyle normalLabelStyle = new GUIStyle(labelStyle);
normalLabelStyle.margin.left = 0;
normalLabelStyle.padding.left = 0;
Rect startRect = new Rect(controlPosition);
startRect.width = 0;
GUIContent foldoutContent = new GUIContent("");
Rect foldoutRectPos = GetHorizPosFromLastPos(new GUIStructure{rect=startRect, content=foldoutContent}, foldoutPaintStyle);
Rect wholeRectPos = GetHorizPosFromLastPos(new GUIStructure{rect=foldoutRectPos, content=content}, normalLabelStyle);
EventType eventType = Event.current.type;
switch (eventType)
{
case EventType.MouseDown:
{
if (wholeRectPos.Contains(Event.current.mousePosition) && Event.current.button == 0)
{
// If we are triple clicking
// We have to do this because Event.current.clickCount never returns 3
if(lastClickCount == 2 && (DateTime.Now - lastClickTime).TotalSeconds < .3f)
{
//Debug.Log("Selecting All");
if(RecycledTextEditor_SelectAll_MethodInfo != null)
{
RecycledTextEditor_SelectAll_MethodInfo.Invoke(recycledTextEditor, new object[] {});
}
Event.current.Use();
}
lastClickTime = DateTime.Now;
lastClickCount = Event.current.clickCount;
}
// If we arn't clicking the label reset the select word feature in the text editor
else
{
// Make the texteditor by default not select whole words because that can get annoying
if(RecycledTextEditor_MouseDragSelectsWholeWords_MethodInfo != null)
{
// editor.MouseDragSelectsWholeWords(true);
RecycledTextEditor_MouseDragSelectsWholeWords_MethodInfo.Invoke(recycledTextEditor, new object[] { false });
}
}
break;
}
case EventType.MouseUp:
{
//Debug.Log("mouse up: " + Event.current.mousePosition);
//Debug.Log(position + " " + position.Contains(Event.current.mousePosition));
GUIUtility.hotControl = 0;
// Toggle the foldout
if(controlPosition.Contains(Event.current.mousePosition))
{
isExpanded = !isExpanded;
//Debug.Log(isExpanded);
Event.current.Use();
}
break;
}
case EventType.keyDown:
{
// Use up the key presses so they can't mess with the label
if (Event.current.keyCode != KeyCode.Tab)
{
Event.current.Use();
}
break;
}
case EventType.ExecuteCommand:
{
// Use the paste and cut so they can't mess with the label
if (Event.current.commandName == "Paste" || Event.current.commandName == "Cut")
{
Event.current.Use();
}
break;
}
case EventType.Repaint:
{
foldoutPaintStyle.Draw(foldoutRectPos, foldoutContent, controlID, isExpanded);
break;
}
}
// Call the EditorGUI.DoTextField
// Which handles all of the fancy selecting and stuff
if(recycledTextEditor != null)
{
if(EditorGUI_DoTextField_MethodInfo != null)
{
Rect labelRectPos = GetHorizPosFromLastPos(new GUIStructure{rect=foldoutRectPos, content=content}, normalLabelStyle);
object[] args = new object[] { recycledTextEditor, controlID, labelRectPos, content.text, normalLabelStyle, null, false, false, true, false };
// DoTextField(EditorGUI.RecycledTextEditor editor, int id, Rect position, string text, GUIStyle style, string allowedletters, out bool changed, bool reset, bool multiline, bool passwordField)
EditorGUI_DoTextField_MethodInfo.Invoke(null, args);
}
}
return isExpanded;
}
// Alternative construcotr if you don't want to deal with GUIContent
public static bool SelectableFoldout(Rect position, bool isExpanded, string text, [DefaultValue ("EditorStyles.label")] GUIStyle labelStyle, [DefaultValue ("EditorStyles.foldout")] GUIStyle foldoutStyle)
{
return SelectableFoldout(position, isExpanded, new GUIContent(text), labelStyle, foldoutStyle);
}
static Rect GetHorizPosFromLastPos(GUIStructure structure, GUIStyle style)
{
Vector2 newContentSize = style.CalcSize(structure.content);
return new Rect(structure.rect.x + structure.rect.width, structure.rect.y, newContentSize.x, newContentSize.y);
}
static string SubStringStartToEnd(string msg, int startIndex, int endIndex)
{
return msg.Substring(startIndex, endIndex-startIndex);
}
static GUIContent SubStringStartToEnd(GUIContent content, int startIndex, int endIndex)
{
return new GUIContent(SubStringStartToEnd(content.text, startIndex, endIndex));
}
}
Code without reflection:
Also you can see the code I had before I switched over to using reflection to gain access to DoTextField
in this GitHub gist. It is not complete but I think someone may find it very useful for their own GUI elements.