Search Unity

Dynamic GUI Issues - Unreliable Control Names and More!

Discussion in 'Scripting' started by Michael-Ryan, Nov 16, 2012.

  1. Michael-Ryan

    Michael-Ryan

    Joined:
    Apr 10, 2009
    Posts:
    184
    I created a small test script to demonstrate the following problems:

    1. Assigned control names are unreliable
    2. Non-existant controls can have keyboard focus
    3. 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

    Code (csharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public class DynamicGUITestEditor : EditorWindow
    6. {
    7.     static private DynamicGUITestEditor _instance = null;
    8.  
    9.     static public DynamicGUITestEditor Instance
    10.     {
    11.         get
    12.         {
    13.             if (null == _instance)
    14.             {
    15.                 Init();
    16.             }
    17.             return _instance;
    18.         }
    19.     }
    20.    
    21.     [MenuItem ("Tools/Dynamic GUI Test Editor %g")]
    22.     private static void Init()
    23.     {
    24.         _instance = (DynamicGUITestEditor)EditorWindow.GetWindow(typeof(DynamicGUITestEditor), true, "Dynamic GUI Test Editor");
    25.         Vector2 panelSize = new Vector2(256, 224);
    26.         _instance.minSize = panelSize;
    27.         _instance.maxSize = panelSize;
    28.     }
    29.  
    30.     public int _gridIndex = 0;
    31.     public string[] _gridOptions = new string[] {"A", "B", "C", "D"};
    32.     private int _intField = 42;
    33.     private float _floatField = Mathf.PI;
    34.     private string _textField = "TEXT";
    35.     private Color _colorField = Color.blue;
    36.    
    37.     void OnGUI()
    38.     {
    39.         EditorGUILayout.BeginVertical();
    40.         {
    41.             _gridIndex = GUILayout.SelectionGrid(_gridIndex, _gridOptions, 4);
    42.        
    43.             EditorGUILayout.Separator();
    44.  
    45.             EditorGUILayout.LabelField("Name of focused control:", "\"" + GUI.GetNameOfFocusedControl() + "\"");
    46.             EditorGUILayout.LabelField("keyboardControl:", GUIUtility.keyboardControl.ToString());
    47.             EditorGUILayout.LabelField("hotControl:", GUIUtility.hotControl.ToString());
    48.        
    49.             EditorGUILayout.Separator();
    50.            
    51.             EditorGUILayout.BeginVertical(GUI.skin.box);
    52.             {
    53.                 switch (_gridIndex)
    54.                 {
    55.                     case 0:
    56.                         _intField = EditorGUILayout.IntField(ControlName("IntField"), _intField);
    57.                         _textField = EditorGUILayout.TextField(ControlName("TextField_00"), _textField);
    58.                         _textField = EditorGUILayout.TextField(ControlName("TextField_01"), _textField);
    59.                         _textField = EditorGUILayout.TextField(ControlName("TextField_02"), _textField);
    60.                         break;
    61.                     case 1:
    62.                         _floatField = EditorGUILayout.FloatField(ControlName("FloatField"), _floatField);
    63.                         _textField = EditorGUILayout.TextField(ControlName("TextField_03"), _textField);
    64.                         _textField = EditorGUILayout.TextField(ControlName("TextField_04"), _textField);
    65.                         _textField = EditorGUILayout.TextField(ControlName("TextField_05"), _textField);
    66.                         break;
    67.                     case 2:
    68.                         _colorField = EditorGUILayout.ColorField(ControlName("ColorField"), _colorField);
    69.                         _textField = EditorGUILayout.TextField(ControlName("TextField_06"), _textField);
    70.                         _textField = EditorGUILayout.TextField(ControlName("TextField_07"), _textField);
    71.                         _textField = EditorGUILayout.TextField(ControlName("TextField_08"), _textField);
    72.                         break;
    73.                     case 3:
    74.                         _textField = EditorGUILayout.TextField(ControlName("TextField_09"), _textField);
    75.                         _textField = EditorGUILayout.TextField(ControlName("TextField_0A"), _textField);
    76.                         _textField = EditorGUILayout.TextField(ControlName("TextField_0B"), _textField);
    77.                   GUILayout.Space(19);
    78.                         break;
    79.                 }
    80.             }
    81.             EditorGUILayout.EndVertical();
    82.  
    83.             EditorGUILayout.Separator();
    84.            
    85.             if (GUILayout.Button("keyboardControl = 0"))
    86.             {
    87.                 GUIUtility.keyboardControl = 0;
    88.             }
    89.         }
    90.         EditorGUILayout.EndVertical();
    91.     }
    92.                    
    93.     private string ControlName(string name)
    94.     {
    95.         string result = _gridOptions[_gridIndex] + "_" + name;
    96.         GUI.SetNextControlName(result);
    97.         return result;
    98.     }
    99. }
    100.  
     

    Attached Files:

    Last edited: Nov 16, 2012
  2. ar0nax

    ar0nax

    Joined:
    May 26, 2011
    Posts:
    485
    Since GUI runs twice per frame, I tried to skip some frames after I made changes to the GUI, so it can set the names right or something. I tried to switch GUI-related values only when the GUI is redrawing itself... to no avail... I could not find a workaround to this issue, and I really need to use GUI.SetNextControlName.

    bump on this maybe Unity programmers will fix this.
     
  3. Michael-Ryan

    Michael-Ryan

    Joined:
    Apr 10, 2009
    Posts:
    184
    I received an update from Unity QA today stating that Issue #1 has been fixed and will be available in a future Unity release.

    Issues #2 and #3 are still assigned to developers.
     
  4. samizzo

    samizzo

    Joined:
    Sep 7, 2011
    Posts:
    487
    Have these issues been fixed? I get occasional bogus focused controls (controls that are no longer visible) particularly after Unity reloads assemblies after I've made code changes. I check focus in an editor tool so I can determine when a text field has lost focus, in order to commit a value after focus is lost, and these issues are causing the focus loss to not be detected.