- Home /
Custom Editor GUI Elements
The closest thing I can find is AngryAnts untested post for a `MyFocusControl`. Another resource is using Go to Declaration
in monodevelop on some of the built in gui elements which just uses reflection. If anyone has some examples on GitHub, Pastebin, etc; please drop a link.
I know there is Extending UnityGUI page in the Unity manual but it only goes over combining two elements.
I am trying to create a selectable foldout for use in a EditorWindow. One issue I am having is that the gui is not updating properly with the state of the toggle. I tried setting GUI.changed
because the reflection foldout source did it. I know there are ways to [set data as dirty](EditorUtility.SetDirty) in order for Unity to save the new data but I can't find anything for the GUI.
Mockup:
You might suggest that I just combine a foldout with empty label and a selectable label but then I couldn't have the label make the foldout toggle. Also I am not entirely sure on what how to get the proper control id. I have a feeling it is just a unique int so almost any of the commented out lines(below) should probably work.
Here is the decompiled reflection source for the `EditorGUI.SelectableLabel()` and `EditorGUI.Foldout()` for reference.
I realize the code below doesn't have the "selectable" part with the MouseMove
event in yet.
public static bool SelectableFoldout(Rect position, bool isExpanded, GUIContent content, [DefaultValue ("EditorStyles.foldout")] GUIStyle style)
{
int controlID = 0;//GUIUtility.GetControlID ();//GUIUtility.GetControlID(content, FocusType.Keyboard, position);
controlID = GUIUtility.GetControlID(content, FocusType.Keyboard, position);
//controlID = GUIUtility.GetControlID(content, FocusType.Passive, position);
//controlID = GUIUtility.GetControlID(content, FocusType.Native, position);
//controlID = EditorGUIUtility.GetControlID(FocusType.Native);
//controlID = "SelectableFoldout".GetHashCode();
//Debug.Log("Control id: " + controlID);
EventType eventType = Event.current.type;
switch (eventType)
{
case EventType.MouseDown:
{
GUIUtility.hotControl = controlID;
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(position.Contains(Event.current.mousePosition))
{
isExpanded = !isExpanded;
}
Debug.Log(isExpanded);
//GUI.changed = true;
break;
}
case EventType.Repaint:
{
style.Draw(EditorGUI.IndentedRect(position), content, controlID, isExpanded);
break;
}
}
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.foldout")] GUIStyle style)
{
return SelectableFoldout(position, isExpanded, new GUIContent(text), style);
}
Answer by MLM · Jun 15, 2014 at 01:27 AM
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.