- Home /
How to make OnGUI TextField in gameView accept keyboard input during edit mode?
I've been learning about reflection lately and got the idea to build a runtime GUI inspector. Everything works properly in play mode, but then I discovered I could add the [ExecuteInEditMode]
attribute to preview the GUI during edit mode.
THEN I discovered I can add this little blurb to OnGUI
// makes mouse work in gameview edit mode!
// https://answers.unity.com/questions/1679704/is-there-a-way-to-implement-mouse-events-in-the-ga.html
if (Event.current.type == EventType.Layout || Event.current.type == EventType.Repaint)
{
UnityEditor.EditorUtility.SetDirty(this);
}
which works surprisingly well to capture mouse input in edit mode, so I can interact with toggles, sliders, buttons, draggable windows, etc, in the game view while still in edit mode. This is a totally unnecessary feature, but honestly it blows my freaking mind.
The only edit-mode feature it seems to lack is keyboard input. GUI text field controls will display the mouse caret when clicked, but keyboard input seems to be eaten by the editor for hotkeys or whatever.
I can reach keyboard input through this workaround
// https://github.com/pjc0247/UnityHack
[InitializeOnLoadMethod]
static void EditorInit()
{
FieldInfo info = typeof(EditorApplication).GetField("globalEventHandler", BindingFlags.Static | BindingFlags.NonPublic);
EditorApplication.CallbackFunction value = (EditorApplication.CallbackFunction)info.GetValue(null);
value += EditorKeyPress;
info.SetValue(null, value);
}
static void EditorKeyPress()
{
var key = Event.current.keyCode;
if (key != KeyCode.None)
{
Debug.Log($"Global key event: {key}");
// TODO: reroute event to gameView?
// ???
//var view = EditorWindow.GetWindow(System.Type.GetType("UnityEditor.GameView,UnityEditor"));
//view.SetDirty();
//view.SendEvent(Event.current);
//view.SendEvent(Event.KeyboardEvent(key.ToString()));
//Event.current.Use(); // block event ???
//GUIDrawer.PressKey(key);
}
}
but all my attempts to reroute the event have failed.
Hopefully someone has a solution?
Rest of the code:
[ExecuteInEditMode]
public class GUIDrawer : MonoBehaviour
{
// https://docs.unity3d.com/ScriptReference/GUI.Window.html
public Rect windowRect = new Rect(0, 0, 600, 1000);
public Object obj; // object to create an inspector for
private float prefixWidth = 100;
private FieldInfo[] fields;
private bool reflected;
private void OnEnable()
{
reflected = false;
Reflect();
}
void OnGUI()
{
#if UNITY_EDITOR
// makes mouse work in gameview edit mode!
// https://answers.unity.com/questions/1679704/is-there-a-way-to-implement-mouse-events-in-the-ga.html
if (Event.current.type == EventType.Layout || Event.current.type == EventType.Repaint)
{
UnityEditor.EditorUtility.SetDirty(this);
}
else if (Event.current.isKey)
{
// never called
Debug.Log($"Key event: {Event.current.keyCode}");
}
else if (Event.current.isMouse)
{
// called only when clicking outside gui window in gameview
Debug.Log($"Mouse event: {Event.current}");
}
else
{
// mouse events show up here with the output:
// Unhandled event: Used
Debug.Log($"Unhandled event: {Event.current}");
}
#endif
if (obj != null)
{
// Register the window. Notice the 3rd parameter
windowRect = GUI.Window(0, windowRect, DrawWindow, "Cool Window");
}
}
private void Reflect(bool reflected = false)
{
if (!reflected)
{
// guess which fields get serialized without Editor?
var type = obj.GetType();
fields = type.GetFields();
this.reflected = true;
// TODO: BindingFlags, attributes, properties, methods
// var bindingFlags = BindingFlags.Public | ...;
// var methods = type.GetMethods
// var props = type.GetProperties
// var attributes = type.GetCustomAttributes
}
}
private void DrawWindow(int windowID)
{
// finding fields to draw
if (!reflected)
Reflect();
// draw fields based on type
foreach (var field in fields)
DrawField(field);
// top-bar drag handle
Rect dragArea = new Rect(0, 0, windowRect.width, 15);
GUI.DragWindow(dragArea);
}
private void DrawField(FieldInfo x)
{
// WORK IN PROGRESS
GUILayout.BeginHorizontal();
System.Type _type = x.FieldType;
object value = x.GetValue(obj);
GUILayout.Label(x.Name, GUILayout.Width(prefixWidth));
if (value is string)
{
x.SetValue(obj, GUILayout.TextField(value.ToString()));
//if (GUI.GetNameOfFocusedControl() != "str_input0")
//GUI.SetNextControlName($"str_input{count++}");
//GUI.FocusControl("str_input0");
}
else if (value is bool)
{
x.SetValue(obj, GUILayout.Toggle((bool)value, ""));
}
else if (value is float)
{
var range = x.GetCustomAttribute<RangeAttribute>();
if (range != null)
{
x.SetValue(obj, GUILayout.HorizontalSlider((float)value, range.min, range.max));
}
else
{
var input = GUILayout.TextField(value.ToString());
if (float.TryParse(input, out float result))
{
x.SetValue(obj, result);
}
}
}
else if (value is int)
{
var range = x.GetCustomAttribute<RangeAttribute>();
if (range != null)
{
float f = (int)value;
float input = GUILayout.HorizontalSlider(f, range.min, range.max);
x.SetValue(obj, Mathf.RoundToInt(input));
}
else
{
var input = GUILayout.TextField(value.ToString());
if (int.TryParse(input, out int result))
{
x.SetValue(obj, result);
}
}
}
else
{
GUILayout.Label($"{value}");
}
GUILayout.EndHorizontal();
}