I created a small test script to demonstrate the following problems:
- Assigned control names are unreliable
- Non-existant controls can have keyboard focus
- Non-selectable controls can have keyboard focus
The primary reason for starting this thread is to provide details of the current behavior, and so that this thread can be referred to in the bug reports I’m going to submit to the development team. I’m hoping these can be easily addressed.
The script is attached to this thread for review.
As you can see in the above screenshot, the GUI in the example script features the following sections from top to bottom:
- A Selection Grid with four options (or Tabs): A, B, C, and D
- Three non-editable, and seemingly non-selectable labels that show which control has focus
- A box that contain a dynamic selection of GUI controls as determined by the selected tab above
- A button to reset the keyboardControl to 0
Assigned control names are unreliable
This is the most frustrating of the three issues. Complex dynamic GUIs typically have many controls that only appear when the GUI is in a particular state.
Unfortunately, the current implementation of SetNextControlName() and GetNameOfFocusedControl() breaks when you’re dealing with this type of GUI, as the following four images will show.
When Tab A is selected, everything looks good. You can select any of the four TextField controls and GetNameOfFocusedControl() accurately returns the name of the selected control. Furthermore, GUI.keyboardControl and GUI.hotControl return the correct ControlID.
The problems start appearing as soon as Tab B is selected. Although the four dynamic controls were assigned the names “B_FloatField”, “B_TextField_03”, “B_TextField_04”, and “B_TextField_05”, respectively, the GetNameOfFocusedControl() actually returns the names of the controls we’ve assigned for Tab A.
Presumably, the problem lies with the fact that these new controls are temporarily assigned the same ControlIDs as those from the earlier Tab. I cannot see any reason why the GUI doesn’t drop those earlier associations, especially when new names are forcefully assigned to controls with the same ControlID. The new names appear to be ignored in this case. This is bad.
Things start to look a little better when Tab C is selected. Now GetNameOfFocusedControl() does accurately returns the assigned control names. The key difference here is that these four dynamic controls are using different ControlIDs. My guess is that the complex ColorField control is made up of multiple elements, each of which get their own ControlID. This bumps the ID assigned to later controls up a bit.
The final tab, Tab D, appears broken just like Tab B, but with one additional problem that I’ll cover next.
Non-existant controls can have keyboard focus
You might have noticed that Tab D only contains three dynamic controls, none of which are selected. You might have also noticed that GetNameOfFocusedControl() is wrongly returning the name of a non-existant control. Furthermore, GUI.keyboardControl still thinks a control has keyboard focus, when none actually does.
The problem occurs here, because I had the 4th control on Tab C selected before switching over to Tab D. Even though there is no longer a visible control that uses ControlID 706, Unity still thinks it’s selected. This is bad.
Non-selectable controls can have keyboard focus
The final issue is fairly minor, but still problematic. The LabelField control is really only useful for showing read-only information. Clicking on the control value portion of the control (the right side), such as the control name or ControlID, does nothing. If you previously had a TextField selected, it will remain selected.
Unfortunately, clicking on the control label (the left side) does de-select the TextField, even though nothing changes visibly to indicate that a new control has focus. If it wasn’t for the fact that the GUI.keyboardControl is being displayed, the user would have no idea that any control had focus.
Why does the label even get focus to begin with? You can’t do anything with it, such as copying its value to the clipboard.
This becomes more confusing if, like many users of any modern OS, you decide to use the Tab or Shift-Tab keyboard shortcuts to cycle through keyboard-focuable controls. In this case, you can press the Tab key to cycle through the four TextField controls, but then you have to also Tab over the three LabelField controls before you loop back around.
With the way the LabelField control currently behaves, it should never be allowed to have keyboard focus. Now if the control was updated and allowed the user to copy its value to the clipboard, and only if there was some type of visual indicator that the control actually had focus … then it would make sense for the control to get keyboard focus.
The script
using UnityEditor;
using UnityEngine;
public class DynamicGUITestEditor : EditorWindow
{
static private DynamicGUITestEditor _instance = null;
static public DynamicGUITestEditor Instance
{
get
{
if (null == _instance)
{
Init();
}
return _instance;
}
}
[MenuItem ("Tools/Dynamic GUI Test Editor %g")]
private static void Init()
{
_instance = (DynamicGUITestEditor)EditorWindow.GetWindow(typeof(DynamicGUITestEditor), true, "Dynamic GUI Test Editor");
Vector2 panelSize = new Vector2(256, 224);
_instance.minSize = panelSize;
_instance.maxSize = panelSize;
}
public int _gridIndex = 0;
public string[] _gridOptions = new string[] {"A", "B", "C", "D"};
private int _intField = 42;
private float _floatField = Mathf.PI;
private string _textField = "TEXT";
private Color _colorField = Color.blue;
void OnGUI()
{
EditorGUILayout.BeginVertical();
{
_gridIndex = GUILayout.SelectionGrid(_gridIndex, _gridOptions, 4);
EditorGUILayout.Separator();
EditorGUILayout.LabelField("Name of focused control:", "\"" + GUI.GetNameOfFocusedControl() + "\"");
EditorGUILayout.LabelField("keyboardControl:", GUIUtility.keyboardControl.ToString());
EditorGUILayout.LabelField("hotControl:", GUIUtility.hotControl.ToString());
EditorGUILayout.Separator();
EditorGUILayout.BeginVertical(GUI.skin.box);
{
switch (_gridIndex)
{
case 0:
_intField = EditorGUILayout.IntField(ControlName("IntField"), _intField);
_textField = EditorGUILayout.TextField(ControlName("TextField_00"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_01"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_02"), _textField);
break;
case 1:
_floatField = EditorGUILayout.FloatField(ControlName("FloatField"), _floatField);
_textField = EditorGUILayout.TextField(ControlName("TextField_03"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_04"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_05"), _textField);
break;
case 2:
_colorField = EditorGUILayout.ColorField(ControlName("ColorField"), _colorField);
_textField = EditorGUILayout.TextField(ControlName("TextField_06"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_07"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_08"), _textField);
break;
case 3:
_textField = EditorGUILayout.TextField(ControlName("TextField_09"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_0A"), _textField);
_textField = EditorGUILayout.TextField(ControlName("TextField_0B"), _textField);
GUILayout.Space(19);
break;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.Separator();
if (GUILayout.Button("keyboardControl = 0"))
{
GUIUtility.keyboardControl = 0;
}
}
EditorGUILayout.EndVertical();
}
private string ControlName(string name)
{
string result = _gridOptions[_gridIndex] + "_" + name;
GUI.SetNextControlName(result);
return result;
}
}
1087025–40743–$DynamicGUITestEditor.cs (3.16 KB)