Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Tab between input fields

Discussion in 'UGUI & TextMesh Pro' started by akyoto_official, Aug 22, 2014.

  1. ARares

    ARares

    Joined:
    Mar 18, 2016
    Posts:
    167
    Dude those code are broken, why? well you need to have just the input field, i made my own code which can switch between 2 ore more Inputs Fields.
    Code (CSharp):
    1.     public InputField loginNameField;
    2.     public InputField loginPassField;
    3.  
    4.     private void Update()
    5.     {
    6.         if (Input.GetKeyDown(KeyCode.Tab))
    7.         {
    8.             if (loginNameField.isFocused)
    9.             {
    10.                 EventSystem.current.SetSelectedGameObject(loginPassField.gameObject, null);
    11.                 loginPassField.OnPointerClick(new PointerEventData(EventSystem.current));
    12.             }
    13.  
    14.             if(loginPassField.isFocused)
    15.             {
    16.                 EventSystem.current.SetSelectedGameObject(loginNameField.gameObject, null);
    17.                 loginNameField.OnPointerClick(new PointerEventData(EventSystem.current));
    18.             }
    19.         }
    20.     }
     
    kontere and yc960 like this.
  2. ConflictedDev

    ConflictedDev

    Joined:
    May 19, 2017
    Posts:
    1
    I made an implementation of this myself after needing more than the basic suggestions people have provided - I thought I'd share it here for anyone interested.

    I found:
    • Selectable.allSelectables[0] does NOT always provide the top-most selection
    • Getting the next selectable for a complex layout isn't straight-forward

    I've implemented:
    • A simple sorting method that orders all active selectables based on their X and Y positioning (left-to-right followed by top-to-bottom)
    • A simple, reliable way to navigate forward/backward; includes optional wrap-around to first/last, respectively
    • Navigation with Tab, Enter and Numpad Enter
      (Enter is setup to only work from input fields, where it will also only select the next selection if it is an input field or button)
    • If nothing is selected, the first selectable is selected (Enter is setup to only work if it is an input field or button)
    NOTE: The sorting of selectables is done each time (as this is a simple implementation), you could do this elsewhere when UI elements are enabled/disabled instead.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5.  
    6. public class UIHotkeySelect : MonoBehaviour
    7. {
    8.     private List<Selectable> m_orderedSelectables;
    9.  
    10.     private void Awake()
    11.     {
    12.         m_orderedSelectables = new List<Selectable>();
    13.     }
    14.  
    15.     private void Update()
    16.     {
    17.         if (Input.GetKeyDown(KeyCode.Tab))
    18.         {
    19.             HandleHotkeySelect(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift), true, false); // Navigate backward when holding shift, else navigate forward.
    20.         }
    21.  
    22.         if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
    23.         {
    24.             HandleHotkeySelect(false, false, true);
    25.         }
    26.     }
    27.  
    28.     private void HandleHotkeySelect(bool _isNavigateBackward, bool _isWrapAround, bool _isEnterSelect)
    29.     {
    30.         SortSelectables();
    31.  
    32.         GameObject selectedObject = EventSystem.current.currentSelectedGameObject;
    33.         if (selectedObject != null && selectedObject.activeInHierarchy) // Ensure a selection exists and is not an inactive object.
    34.         {
    35.             Selectable currentSelection = selectedObject.GetComponent<Selectable>();
    36.             if (currentSelection != null)
    37.             {
    38.                 if (_isEnterSelect)
    39.                 {
    40.                     if (currentSelection.GetComponent<InputField>() != null)
    41.                     {
    42.                         ApplyEnterSelect(FindNextSelectable(m_orderedSelectables.IndexOf(currentSelection), _isNavigateBackward, _isWrapAround));
    43.                     }
    44.                 }
    45.                 else // Tab select.
    46.                 {
    47.                     Selectable nextSelection = FindNextSelectable(m_orderedSelectables.IndexOf(currentSelection), _isNavigateBackward, _isWrapAround);
    48.                     if (nextSelection != null)
    49.                     {
    50.                         nextSelection.Select();
    51.                     }
    52.                 }
    53.             }
    54.             else
    55.             {
    56.                 SelectFirstSelectable(_isEnterSelect);
    57.             }
    58.         }
    59.         else
    60.         {
    61.             SelectFirstSelectable(_isEnterSelect);
    62.         }
    63.     }
    64.  
    65.     ///<summary> Selects an input field or button, activating the button if one is found. </summary>
    66.     private void ApplyEnterSelect(Selectable _selectionToApply)
    67.     {
    68.         if (_selectionToApply != null)
    69.         {
    70.             if (_selectionToApply.GetComponent<InputField>() != null)
    71.             {
    72.                 _selectionToApply.Select();
    73.             }
    74.             else
    75.             {
    76.                 Button selectedButton = _selectionToApply.GetComponent<Button>();
    77.                 if (selectedButton != null)
    78.                 {
    79.                     _selectionToApply.Select();
    80.                     selectedButton.OnPointerClick(new PointerEventData(EventSystem.current));
    81.                 }
    82.             }
    83.         }
    84.     }
    85.  
    86.     private void SelectFirstSelectable(bool _isEnterSelect)
    87.     {
    88.         if (m_orderedSelectables.Count > 0)
    89.         {
    90.             Selectable firstSelectable = m_orderedSelectables[0];
    91.             if (_isEnterSelect)
    92.             {
    93.                 ApplyEnterSelect(firstSelectable);
    94.             }
    95.             else
    96.             {
    97.                 firstSelectable.Select();
    98.             }
    99.         }
    100.     }
    101.  
    102.     private Selectable FindNextSelectable(int _currentSelectableIndex, bool _isNavigateBackward, bool _isWrapAround)
    103.     {
    104.         Selectable nextSelection = null;
    105.  
    106.         int totalSelectables = m_orderedSelectables.Count;
    107.         if (totalSelectables > 1)
    108.         {
    109.             if (_isNavigateBackward)
    110.             {
    111.                 if (_currentSelectableIndex == 0)
    112.                 {
    113.                     nextSelection = (_isWrapAround) ? m_orderedSelectables[totalSelectables - 1] : null;
    114.                 }
    115.                 else
    116.                 {
    117.                     nextSelection = m_orderedSelectables[_currentSelectableIndex - 1];
    118.                 }
    119.             }
    120.             else // Navigate forward.
    121.             {
    122.                 if (_currentSelectableIndex == (totalSelectables - 1))
    123.                 {
    124.                     nextSelection = (_isWrapAround) ? m_orderedSelectables[0] : null;
    125.                 }
    126.                 else
    127.                 {
    128.                     nextSelection = m_orderedSelectables[_currentSelectableIndex + 1];
    129.                 }
    130.             }
    131.         }
    132.  
    133.         return (nextSelection);
    134.     }
    135.  
    136.     private void SortSelectables()
    137.     {
    138.         List<Selectable> originalSelectables = Selectable.allSelectables;
    139.         int totalSelectables = originalSelectables.Count;
    140.         m_orderedSelectables = new List<Selectable>(totalSelectables);
    141.         for (int index = 0; index < totalSelectables; ++index)
    142.         {
    143.             Selectable selectable = originalSelectables[index];
    144.             m_orderedSelectables.Insert(FindSortedIndexForSelectable(index, selectable), selectable);
    145.         }
    146.     }
    147.  
    148.     ///<summary> Recursively finds the sorted index by positional order within m_orderedSelectables (positional order is determined from left-to-right followed by top-to-bottom). </summary>
    149.     private int FindSortedIndexForSelectable(int _selectableIndex, Selectable _selectableToSort)
    150.     {
    151.         int sortedIndex = _selectableIndex;
    152.         if (_selectableIndex > 0)
    153.         {
    154.             int previousIndex = _selectableIndex - 1;
    155.             Vector3 previousSelectablePosition = m_orderedSelectables[previousIndex].transform.position;
    156.             Vector3 selectablePositionToSort = _selectableToSort.transform.position;
    157.  
    158.             if (previousSelectablePosition.y == selectablePositionToSort.y)
    159.             {
    160.                 if (previousSelectablePosition.x > selectablePositionToSort.x)
    161.                 {
    162.                     // Previous selectable is in front, try the previous index:
    163.                     sortedIndex = FindSortedIndexForSelectable(previousIndex, _selectableToSort);
    164.                 }
    165.             }
    166.             else if (previousSelectablePosition.y < selectablePositionToSort.y)
    167.             {
    168.                 // Previous selectable is in front, try the previous index:
    169.                 sortedIndex = FindSortedIndexForSelectable(previousIndex, _selectableToSort);
    170.             }
    171.         }
    172.  
    173.         return (sortedIndex);
    174.     }
    175. }
    176.  
     
    Last edited: Jul 27, 2017
    Energy0124, kontere, tuccci and 6 others like this.
  3. kzaarka

    kzaarka

    Joined:
    Aug 30, 2017
    Posts:
    1
    Excellent job!!
     
  4. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    498
    Thanks Kzaarka. I will see how that functionality suits me too :).

    It is mind-boggling how tab and shift-tab is not the default form of navigation, like it is everywhere else.
    "I really want arrow keys to navigate, it's not like I use them to move cursors in input boxes", said no one, ever.
     
    Mikael-H likes this.
  5. MM-Mat

    MM-Mat

    Joined:
    Dec 11, 2015
    Posts:
    13
    Hi,

    Thanks for sharing your script!

    I'm using a modified version of this script and found that it doesn't work for items in an ILayoutGroup, since their reported X and Y coords are the same. For that reason, you might want to add an extra clause in FindSortedIndexForSelectable to compare GetSiblingOrder of the previous and current items in case their coords were the same, as that would fix this edge case.
     
  6. Vicomte

    Vicomte

    Joined:
    Sep 18, 2013
    Posts:
    5
    Thanks a bunch ConflictedDev, this works great, very nice work!
     
  7. CityWizardGames

    CityWizardGames

    Joined:
    Nov 6, 2013
    Posts:
    21
    Thanks ConflictedDev, that works great.

    For those using his script, you may want to refactor so that SortSelectables() is not getting called everytime a hotkey is pressed. It's a little expensive. You could instead do it in Start() and then again whenever you add/remove UI elements.

    Here's my iteration of the script. I have:
    • Moved SortSelectables() to Start() and made it public
    • Removed 'Enter' functionality because I didn't need it. Sorry :rolleyes:
    • Turned off 'wrap-around' by default. Simply change argument to HandleHotkeySelect().
    • Added a couple comments.
    • Tried to structure code a bit differently to make it easier to understand.
    • Re-formatted the way I like it. Sorry again :rolleyes:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5.  
    6. public class UITabNavigator : MonoBehaviour
    7. {
    8.     private void Awake()
    9.     {
    10.         this._orderedSelectables = new List<Selectable>();
    11.     }
    12.  
    13.     private void Start()
    14.     {
    15.         this.SortSelectables();
    16.     }
    17.  
    18.     private void Update()
    19.     {
    20.         if (Input.GetKeyDown(KeyCode.Tab))
    21.         {
    22.             // Navigate backward when holding shift, else navigate forward.
    23.             this.HandleHotkeySelect(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift), false);
    24.         }
    25.     }
    26.  
    27.     /// <summary>
    28.     /// Iterates through all selectables in scene and orders them based on their position.
    29.     /// </summary>
    30.     public void SortSelectables()
    31.     {
    32.         List<Selectable> originalSelectables = Selectable.allSelectables;
    33.         int totalSelectables = originalSelectables.Count;
    34.         this._orderedSelectables = new List<Selectable>(totalSelectables);
    35.         for (int index = 0; index < totalSelectables; ++index)
    36.         {
    37.             Selectable selectable = originalSelectables[index];
    38.             this._orderedSelectables.Insert(
    39.                 this.FindSortedIndexForSelectable(index, selectable), selectable);
    40.         }
    41.     }
    42.  
    43.     private void HandleHotkeySelect(bool isNavigateBackward, bool isWrapAround)
    44.     {
    45.         GameObject selectedObject = EventSystem.current.currentSelectedGameObject;
    46.         if (selectedObject != null && selectedObject.activeInHierarchy) // Ensure a selection exists and is not an inactive object.
    47.         {
    48.             Selectable currentSelection = selectedObject.GetComponent<Selectable>();
    49.             if (currentSelection != null)
    50.             {
    51.                 Selectable nextSelection = this.FindNextSelectable(
    52.                     this._orderedSelectables.IndexOf(currentSelection), isNavigateBackward, isWrapAround);
    53.                 if (nextSelection != null)
    54.                 {
    55.                     nextSelection.Select();
    56.                 }
    57.             }
    58.             else
    59.             {
    60.                 this.SelectFirstSelectable();
    61.             }
    62.         }
    63.         else
    64.         {
    65.             this.SelectFirstSelectable();
    66.         }
    67.     }
    68.  
    69.     private void SelectFirstSelectable()
    70.     {
    71.         if (this._orderedSelectables != null && this._orderedSelectables.Count > 0)
    72.         {
    73.             Selectable firstSelectable = this._orderedSelectables[0];
    74.             firstSelectable.Select();
    75.         }
    76.     }
    77.  
    78.     /// <summary>
    79.     /// Looks at ordered selectable list to find the selectable we are trying to navigate to and returns it.
    80.     /// </summary>
    81.     private Selectable FindNextSelectable(int currentSelectableIndex, bool isNavigateBackward, bool isWrapAround)
    82.     {
    83.         Selectable nextSelection = null;
    84.  
    85.         int totalSelectables = this._orderedSelectables.Count;
    86.         if (totalSelectables > 1)
    87.         {
    88.             if (isNavigateBackward)
    89.             {
    90.                 if (currentSelectableIndex == 0)
    91.                 {
    92.                     nextSelection = (isWrapAround) ? this._orderedSelectables[totalSelectables - 1] : null;
    93.                 }
    94.                 else
    95.                 {
    96.                     nextSelection = this._orderedSelectables[currentSelectableIndex - 1];
    97.                 }
    98.             }
    99.             else // Navigate forward.
    100.             {
    101.                 if (currentSelectableIndex == (totalSelectables - 1))
    102.                 {
    103.                     nextSelection = (isWrapAround) ? this._orderedSelectables[0] : null;
    104.                 }
    105.                 else
    106.                 {
    107.                     nextSelection = this._orderedSelectables[currentSelectableIndex + 1];
    108.                 }
    109.             }
    110.         }
    111.  
    112.         return nextSelection;
    113.     }
    114.  
    115.     /// <summary>
    116.     /// Recursively finds the sorted index by positional order within _orderedSelectables (positional order is determined from left-to-right followed by top-to-bottom).
    117.     /// </summary>
    118.     private int FindSortedIndexForSelectable(int selectableIndex, Selectable selectableToSort)
    119.     {
    120.         int sortedIndex = selectableIndex;
    121.         if (selectableIndex > 0)
    122.         {
    123.             int previousIndex = selectableIndex - 1;
    124.             Vector3 previousSelectablePosition = this._orderedSelectables[previousIndex].transform.position;
    125.             Vector3 selectablePositionToSort = selectableToSort.transform.position;
    126.  
    127.             if ((previousSelectablePosition.y < selectablePositionToSort.y)
    128.                 || (previousSelectablePosition.y == selectablePositionToSort.y
    129.                     && previousSelectablePosition.x > selectablePositionToSort.x))
    130.             {
    131.                 // Previous selectable is in front, try the previous index:
    132.                 sortedIndex = this.FindSortedIndexForSelectable(previousIndex, selectableToSort);
    133.             }
    134.         }
    135.  
    136.         return sortedIndex;
    137.     }
    138.  
    139.     private List<Selectable> _orderedSelectables = null;
    140. }
     
    tuccci and Sssyt like this.
  8. sarynth

    sarynth

    Joined:
    May 16, 2017
    Posts:
    98
    Urgh... tab should be basic functionality. I also wanted the two most recent UITabNavigators classes to ignore selectables that were on canvas groups that were not interactable. So I had to modify...

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Iterates through all selectables in scene and orders them based on their position.
    4.         /// </summary>
    5.         public void SortSelectables()
    6.         {
    7.             var originalSelectables = Selectable.allSelectables;
    8.             var totalSelectables = originalSelectables.Count;
    9.             _orderedSelectables = new List<Selectable>(totalSelectables);
    10.             var sortIndex = 0;
    11.             for (var index = 0; index < totalSelectables; ++index)
    12.             {
    13.                 var selectable = originalSelectables[index];
    14.                
    15.                 if (!selectable.IsInteractable()) continue;
    16.                
    17.                 _orderedSelectables.Insert(
    18.                     FindSortedIndexForSelectable(sortIndex, selectable), selectable);
    19.                 sortIndex++;
    20.             }
    21.         }
    22.  
    But, after I did this, they work fabulously! I can suddenly navigate through my form, and was even pleasantly surprised when I was able to tab through the form, past the submit button to my back button, press space bar, and it went back to the previous form. Where I was able to hit tab, get the first element, and tab through to the button to navigate back to my other form.... all with tab and space. Phew.... this was bugging me all evening. Thanks folks.
     
    Ruslank100, mandisaw and jj_unity328 like this.
  9. Michieal

    Michieal

    Joined:
    Jul 7, 2013
    Posts:
    92
    This is probably necro'ing, but.... if anyone is curious, you would initiate the Move Next code when the user enters the letter. (It's obvious to me, but, since it wasn't obvious to Zealous, I'm stating it here.)
    Unity's EventSystem raises an event on editing the InputField. This allows you to check, validate, etc., the input text in the field. Since Zealous is doing a single character entry per field, it makes sense to just put in the OnValueChanged hook to move to the next field. (See code for when the user presses tab above for actual code.)
     
  10. Michieal

    Michieal

    Joined:
    Jul 7, 2013
    Posts:
    92
    Also...
    Unity still hasn't fixed this as of 2018.1...
    But, on a plus side, you can do Photogrammetry like a champ in unity! (Just... hope that you don't need an UI in unity!!! or, other bug fixes...) :p
     
    Ruslank100, mandisaw and jj_unity328 like this.
  11. Andergraw

    Andergraw

    Joined:
    Nov 28, 2016
    Posts:
    6
    What a wonderful, neat piece of code.
    Thanks for sharing, it works seamlessly!
    Cheers,
     
  12. jlt

    jlt

    Joined:
    Nov 20, 2014
    Posts:
    1
    I'm using this version, which I modified (from kryzodoze's rev), that uses a list where you set references to Selectables, rather than dynamically try to fetch all Selectables and work out an order based on positioning

    Features:
    - Explict control over tabbing order.
    - Can provide guarantee of existence of selectables (can be wired up statically from Unity or set explicitly from code).
    - Can nest Selectable game objects under KeyboardSelectableGroup and disabling the tree of game objects works as expected (disables that keyboard selectable group).

    Not Features:
    - Does not dynamically detect UI, all references must be set explicitly (either through the Unity editor or code).
    - Having more than 1 keyboard selectable group doesn't work with this snippet (but this could be rewritten to have a group of groups that controls the UI and uses the concatenation of all selectables from groups within the group).

    Code (CSharp):
    1.  
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. public class KeyboardSelectableGroup : MonoBehaviour
    8. {
    9.     public List<Selectable> selectables = new List<Selectable>();
    10.  
    11.     private void Update()
    12.     {
    13.         if (Input.GetKeyDown(KeyCode.Tab))
    14.         {
    15.             // Navigate backward when holding shift, else navigate forward.
    16.             this.HandleHotkeySelect(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift), true);
    17.         }
    18.         if (Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.Return))
    19.         {
    20.             EventSystem.current.SetSelectedGameObject(null, null);
    21.         }
    22.     }
    23.  
    24.     private void HandleHotkeySelect(bool isNavigateBackward, bool isWrapAround)
    25.     {
    26.         GameObject selectedObject = EventSystem.current.currentSelectedGameObject;
    27.         if (selectedObject != null && selectedObject.activeInHierarchy) // Ensure a selection exists and is not an inactive object.
    28.         {
    29.             Selectable currentSelection = selectedObject.GetComponent<Selectable>();
    30.             if (currentSelection != null)
    31.             {
    32.                 Selectable nextSelection = this.FindNextSelectable(
    33.                     selectables.IndexOf(currentSelection), isNavigateBackward, isWrapAround);
    34.                 if (nextSelection != null)
    35.                 {
    36.                     nextSelection.Select();
    37.                 }
    38.             }
    39.             else
    40.             {
    41.                 this.SelectFirstSelectable();
    42.             }
    43.         }
    44.         else
    45.         {
    46.             this.SelectFirstSelectable();
    47.         }
    48.     }
    49.  
    50.     private void SelectFirstSelectable()
    51.     {
    52.         if (selectables != null && selectables.Count > 0)
    53.         {
    54.             Selectable firstSelectable = selectables[0];
    55.             firstSelectable.Select();
    56.         }
    57.     }
    58.  
    59.     /// <summary>
    60.     /// Looks at ordered selectable list to find the selectable we are trying to navigate to and returns it.
    61.     /// </summary>
    62.     private Selectable FindNextSelectable(int currentSelectableIndex, bool isNavigateBackward, bool isWrapAround)
    63.     {
    64.         Selectable nextSelection = null;
    65.  
    66.         int totalSelectables = selectables.Count;
    67.         if (totalSelectables > 1)
    68.         {
    69.             if (isNavigateBackward)
    70.             {
    71.                 if (currentSelectableIndex == 0)
    72.                 {
    73.                     nextSelection = (isWrapAround) ? selectables[totalSelectables - 1] : null;
    74.                 }
    75.                 else
    76.                 {
    77.                     nextSelection = selectables[currentSelectableIndex - 1];
    78.                 }
    79.             }
    80.             else // Navigate forward.
    81.             {
    82.                 if (currentSelectableIndex == (totalSelectables - 1))
    83.                 {
    84.                     nextSelection = (isWrapAround) ? selectables[0] : null;
    85.                 }
    86.                 else
    87.                 {
    88.                     nextSelection = selectables[currentSelectableIndex + 1];
    89.                 }
    90.             }
    91.         }
    92.  
    93.         return nextSelection;
    94.     }
    95.  
    96. }
     
    Last edited: Dec 18, 2018
    Calixte, antonsem, DustyDev and 6 others like this.
  13. hypernaturalgames

    hypernaturalgames

    Joined:
    Dec 31, 2018
    Posts:
    2
    Works great, thank you
     
  14. injerto

    injerto

    Joined:
    Sep 22, 2019
    Posts:
    6
    Amazing!! Works like charm!!
     
  15. EZaca

    EZaca

    Joined:
    Dec 9, 2017
    Posts:
    32
    While this is a very simple thing to implement by ourselves, I can't think a reason Unity doesn't have it. Is there some technical issue or they just don't want to do that? Even the new Input System has nothing about that.
     
  16. xxShana

    xxShana

    Joined:
    Jul 4, 2019
    Posts:
    23
    Thx ! working well !
     
  17. Zynigma

    Zynigma

    Joined:
    Sep 23, 2012
    Posts:
    22
    Thanks jlt for the final touch on the script, and the rest of you guys. This works great for tabbing between input fields, I only have 11 max input fields on the screen at any time, but you can set the order and go in reverse. Saved me hours of doing it manually for sure...
     
  18. ericspataru

    ericspataru

    Joined:
    Jan 31, 2017
    Posts:
    54
    Thank you for the script. It works wonders!

    Also, could you *pretty please* modify it so it accepts multiple groups?
     
  19. altropetrolio

    altropetrolio

    Joined:
    Nov 8, 2017
    Posts:
    1
    Fantastic! Thank you a lot! Easy to Set and Full Working
     
  20. snowmobeetle

    snowmobeetle

    Joined:
    May 23, 2017
    Posts:
    7
    This gave me a null error when I got to the end of the available selectable objects. I had to add:

    if (system.currentSelectedObject.GetComponent<Selectable>().FindSelectableOnDown() == null) return;

    after the KeyCode.Tab line to prevent the null error.


     
  21. whidzee

    whidzee

    Joined:
    Nov 20, 2012
    Posts:
    166

    Thanks for this mate. It worked an absolute treat
     
  22. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    THE SIMPLEST SOLUTION until Unity wake up

    Code (CSharp):
    1. using UnityEngine.EventSystems;
    2.  
    3. ...
    4. void Update()
    5.     {
    6.         if (Input.GetKeyDown(KeyCode.Tab))
    7.         {
    8.             GameObject c = EventSystem.current.currentSelectedGameObject;
    9.             if (c == null) { return; }
    10.          
    11.             Selectable s = c.GetComponent<Selectable>();
    12.             if (s == null) { return; }
    13.  
    14.             Selectable jump = Input.GetKey(KeyCode.LeftShift)
    15.                 ? s.FindSelectableOnUp() : s.FindSelectableOnDown();
    16.             if (jump != null) { jump.Select(); }
    17.         }
    18.     }
     
    Kreshi, Ghetaldus, Matsyir and 4 others like this.
  23. ItsDelta

    ItsDelta

    Joined:
    Jan 26, 2014
    Posts:
    3
    Based on @Fattie script but using the new input system + also handles left / right selectable.
    Should be fairly easy to understand the basic idea ;)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.InputSystem;
    4. using UnityEngine.UI;
    5.  
    6. public class TabSelectHelper : MonoBehaviour
    7. {
    8.     void Update() {
    9.         if (Keyboard.current.tabKey.wasPressedThisFrame) {
    10.             GameObject c = EventSystem.current.currentSelectedGameObject;
    11.             if (c == null) return;
    12.  
    13.             Selectable s = c.GetComponent<Selectable>();
    14.             if (s == null) return;
    15.  
    16.             Selectable jump = Keyboard.current.shiftKey.isPressed ? s.FindSelectableOnUp() : s.FindSelectableOnDown();
    17.            
    18.             // try similar direction
    19.             if (!jump) {
    20.                 jump = Keyboard.current.shiftKey.isPressed ? s.FindSelectableOnLeft() : s.FindSelectableOnRight();
    21.                 if (!jump) return;
    22.             }
    23.  
    24.             jump.Select();
    25.         }
    26.     }
    27. }
    28.  
     
    telarin likes this.
  24. BitPax

    BitPax

    Joined:
    Oct 4, 2019
    Posts:
    89
    I liked @Fattie 's version but wanted a default selected option for my login screen.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4.  
    5. public class TabNav : MonoBehaviour {
    6.     public GameObject d = null; // default selected
    7.  
    8.     private void Start() {
    9.         EventSystem.current.SetSelectedGameObject( d );
    10.     }
    11.  
    12.     private void Update() {
    13.         if (Input.GetKeyDown( KeyCode.Tab )) {
    14.             GameObject c = EventSystem.current.currentSelectedGameObject;
    15.             if (c == null) { return; }
    16.  
    17.             Selectable s = c.GetComponent<Selectable>();
    18.             if (s == null) { return; }
    19.  
    20.             Selectable jump = Input.GetKey( KeyCode.LeftShift ) ? s.FindSelectableOnUp() : s.FindSelectableOnDown();
    21.             if (jump != null) { jump.Select(); }
    22.         }
    23.     }
    24. }
    25.  
     
    travlake likes this.
  25. tmcallis

    tmcallis

    Joined:
    Sep 1, 2020
    Posts:
    1
    Anyone who wants to use a script like this with multiline input fields will find that the tab overwrites / gets written into multiline fields. To prevent this, you can add a validation function to prevent tab characters in those input fields.

    Code (CSharp):
    1. private void Start()
    2. {
    3.     InputField field = GetComponent<InputField>();
    4.     if(field != null)
    5.     {
    6.         field.onValidateInput += NoTabValidation;
    7.     }
    8. }
    9.  
    10. private char NoTabValidation(string input, int charIndex, char newChar)
    11. {
    12.       if(newChar == '\t')
    13.       {
    14.          return '\0';
    15.       }
    16.       return newChar;
    17. }
     
    wlwl2 likes this.
  26. omelchor

    omelchor

    Joined:
    Jun 15, 2011
    Posts:
    20
    Perfect for me: valid for InputFields, Buttons, Toggles... together

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;

    public class mbFormTabOrder : MonoBehaviour
    {

    [SerializeField]
    private Selectable[] gascFieldsNext; // InputFields, Buttons, Toggles

    private int giIdCur;

    //=======================

    void Start() {
    giIdCur = 0;
    vSetPos( giIdCur );
    }

    //=======================
    void Update() {
    if (Input.GetKeyUp( KeyCode.Return ) || Input.GetKeyUp( KeyCode.Tab )) {
    giIdCur = ( giIdCur + 1 ) % gascFieldsNext.Length;
    vSetPos( giIdCur );
    }
    }

    //=======================
    private void vSetPos( int i_Id ) {
    gascFieldsNext[ i_Id ].Select();
    }
    }
     
  27. merlin4

    merlin4

    Joined:
    Jan 24, 2015
    Posts:
    3
    Added logic to make shift-tab work too

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class TabNavigation : MonoBehaviour
    6. {
    7.     void Update()
    8.     {
    9.         if (Input.GetKeyDown(KeyCode.Tab))
    10.         {
    11.             EventSystem system = EventSystem.current;
    12.             GameObject curObj = system.currentSelectedGameObject;
    13.             GameObject nextObj = null;
    14.             if (!curObj)
    15.             {
    16.                 nextObj = system.firstSelectedGameObject;
    17.             }
    18.             else
    19.             {
    20.                 Selectable curSelect = curObj.GetComponent<Selectable>();
    21.                 Selectable nextSelect =
    22.                     Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)
    23.                         ? curSelect.FindSelectableOnUp()
    24.                         : curSelect.FindSelectableOnDown();
    25.                 if (nextSelect)
    26.                 {
    27.                     nextObj = nextSelect.gameObject;
    28.                 }
    29.             }
    30.             if (nextObj)
    31.             {
    32.                 system.SetSelectedGameObject(nextObj, new BaseEventData(system));
    33.             }
    34.         }
    35.     }
    36. }
    37.  
     
  28. kontere

    kontere

    Joined:
    Jun 3, 2021
    Posts:
    1


    How can you modify this to work top to bottom first and then left to right?
     
  29. ETGgames

    ETGgames

    Joined:
    Jul 10, 2015
    Posts:
    101
    Code (CSharp):
    1. using TMPro;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UnityEngine;
    6. using UnityEngine.InputSystem;
    7.  
    8. //used so we can easily selcet the next text box when enter or tab is pressed
    9. public class InputFieldGroup : MonoBehaviour
    10. {
    11.     [SerializeField] private List<TMP_InputField> _inputFields;
    12.  
    13.     private int _prevSelectedIdx = 0;
    14.     private bool _preventSelection = false;
    15.  
    16.     private void Start()
    17.     {
    18.         for (int i = 0; i < _inputFields.Count; i++)
    19.         {
    20.             var field = _inputFields[i];
    21.             var tempIdx = i; //needed or all teh lambdas get the same value by reference for some dum S*** reason
    22.             field.onSelect.AddListener((data) =>
    23.            {
    24.                _preventSelection = false;
    25.                _prevSelectedIdx = tempIdx;
    26.            });
    27.  
    28.             field.onDeselect.AddListener((data) =>
    29.                 {
    30.                     if (!AreAnySelected())
    31.                         _preventSelection = true;
    32.                 });
    33.         }
    34.     }
    35.  
    36.  
    37.     void Update()
    38.     {
    39.         if (Keyboard.current.tabKey.wasPressedThisFrame || Keyboard.current.enterKey.wasPressedThisFrame)
    40.         {
    41.             if (!_preventSelection) //so it works with multiple input field groups on screen at once
    42.             {
    43.                 SelectNextInputField();
    44.  
    45.             }
    46.         }
    47.     }
    48.  
    49.     private void SelectNextInputField()
    50.     {
    51.         var next = _inputFields[(_prevSelectedIdx + 1) % _inputFields.Count];
    52.         next.Select();
    53.         next.ActivateInputField();
    54.     }
    55.  
    56.     private bool AreAnySelected()
    57.     {
    58.         for (int i = 0; i < _inputFields.Count; i++)
    59.         {
    60.             if (_inputFields[i].isFocused) return true;
    61.         }
    62.         return false;
    63.     }
    64.  
    65. }
    66.  
     
    Ghosthowl, Alec-Slayden and Ian094 like this.
  30. SamuelKeller

    SamuelKeller

    Joined:
    Nov 9, 2020
    Posts:
    9
    I get that this is an old post, but if anyone is looking for a simplified solution, here it is:

    Code (CSharp):
    1.         if (Input.GetKeyDown(KeyCode.Tab)) {
    2.             if (emailInputField.isFocused) {
    3.                 usernameInputField.ActivateInputField();
    4.                 usernameInputField.Select();
    5.             } else if (usernameInputField.isFocused) {
    6.                 passwordInputField.ActivateInputField();
    7.                 passwordInputField.Select();
    8.             } else if (passwordInputField.isFocused) {
    9.                 emailInputField.ActivateInputField();
    10.                 emailInputField.Select();
    11.             }
    12.         }
     
    jterry likes this.
  31. bisrit

    bisrit

    Joined:
    Nov 20, 2021
    Posts:
    2
    This was extremely helpful to me.
     
  32. RageByte

    RageByte

    Joined:
    Jul 2, 2017
    Posts:
    33
    Was trying to solve this too. But wanted something more generic. Drag your UI elements into the List in the inspector and wala!

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5.  
    6. public class TabToNextGUIElement : MonoBehaviour
    7. {
    8.     public List<Selectable> elements;   // add UI elements in inspector in desired tabbing order
    9.     int index;
    10.  
    11.     void Start()
    12.     {
    13.         index = -1;           // always leave at -1 initially
    14.         //elements[0].Select(); // uncomment to have focus on first element in the list
    15.     }
    16.  
    17.     void Update()
    18.     {
    19.         if (Input.GetKeyDown(KeyCode.Tab))
    20.         {
    21.             for (int i = 0; i < elements.Count; i ++)
    22.             {
    23.                 if (elements[i].gameObject.Equals(EventSystem.current.currentSelectedGameObject))
    24.                 {
    25.                     index = i;
    26.                     break;
    27.                 }
    28.             }
    29.  
    30.             if (Input.GetKey(KeyCode.LeftShift))
    31.             {
    32.                 index = index > 0 ? --index : index = elements.Count - 1;
    33.             }
    34.             else
    35.             {
    36.                 index = index < elements.Count - 1 ? ++index : 0;
    37.             }
    38.             elements[index].Select();
    39.         }
    40.     }
    41. }
    42.  
     
    Last edited: Mar 4, 2022
    GainfulSage and vovo801 like this.
  33. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    443
    super easy + useful, thx :).
     
  34. JPMay

    JPMay

    Joined:
    Dec 13, 2021
    Posts:
    6
    TY, yeah - that's the cleanest most refined version we have come up with.

    "workarounds for batshit unity problems" is like a whole industry :/
     
    jonathanglitchers likes this.
  35. Briezar

    Briezar

    Joined:
    Jun 14, 2021
    Posts:
    2
    This is my version that doesn't use the Update loop so it only starts waiting for input when the player clicks on the input field. Just put this on your Container for input fields, plop in the order of tab navigation and you're good to go. Works great when your tab orders are a little bit whack. You can just use StopAllCoroutines or StopCoroutine(WaitForInput) but I go the longer way so it's further customizable, perhaps you would also want to check for more inputs.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using TMPro;
    4. using UnityEngine;
    5. using UnityEngine.EventSystems;
    6.  
    7. public class InputFieldTabNavigation : MonoBehaviour
    8. {
    9.     [SerializeField] private List<TMP_InputField> _inputFields;
    10.     private int _index;
    11.     private Coroutine _waitForInputRoutine;
    12.  
    13.     private void Start()
    14.     {
    15.         foreach (var inputField in _inputFields)
    16.         {
    17.             inputField.onSelect.AddListener((string text)=>{
    18.                 StartWaitForInput();
    19.             });
    20.         }
    21.     }
    22.  
    23.     public void StartWaitForInput()
    24.     {
    25.         if (_waitForInputRoutine != null) StopCoroutine(_waitForInputRoutine);
    26.         _waitForInputRoutine = StartCoroutine(WaitForInput());
    27.     }
    28.  
    29.     public void StopWaitForInput()
    30.     {
    31.         if (_waitForInputRoutine != null) StopCoroutine(_waitForInputRoutine);
    32.     }
    33.  
    34.     private IEnumerator WaitForInput()
    35.     {
    36.         yield return null;
    37.         while (true)
    38.         {
    39.             bool isAnySelected = _inputFields.Exists((inputField) => { return inputField.gameObject.Equals(EventSystem.current.currentSelectedGameObject); });
    40.             if (!isAnySelected)
    41.             {
    42.                 yield break;
    43.             }
    44.  
    45.             if (Input.GetKeyDown(KeyCode.Tab))
    46.             {
    47.                 for (var i = 0; i < _inputFields.Count; i++)
    48.                 {
    49.                     if (_inputFields[i].gameObject.Equals(EventSystem.current.currentSelectedGameObject))
    50.                     {
    51.                         _index = i;
    52.                         break;
    53.                     }
    54.  
    55.                 }
    56.                 if (Input.GetKey(KeyCode.LeftShift))
    57.                 {
    58.                     _index = _index > 0 ? _index - 1 : _inputFields.Count - 1;
    59.                 }
    60.                 else
    61.                 {
    62.                     _index = (_index + 1) % _inputFields.Count;
    63.                 }
    64.  
    65.                 _inputFields[_index].ActivateInputField();
    66.                 _inputFields[_index].Select();
    67.  
    68.             }
    69.  
    70.             yield return null;
    71.         }
    72.     }
    73. }
    74.  
     
    OtakuD, Sunriser and Fingerbob like this.
  36. shieldgenerator7

    shieldgenerator7

    Joined:
    Dec 20, 2015
    Posts:
    39
    My problem: when I press TAB it inserts a tab character, which I don't want.
    My solution: throw an error whenever it tries to add a tab character

    It's a bit unorthodox, but it works!

    Code (CSharp):
    1.         inputField.onValidateInput += (txt, index, chr) =>
    2.         {
    3.             if (chr == '\t')
    4.             {
    5.                 throw new System.ArgumentException("Tab character not allowed");
    6.             }
    7.             return chr;
    8.         };
    This would go in your
    Start()
    or
    Awake()


    EDIT: Nvm, i just did more research and you're supposed to
    return '\0';
     
    Last edited: Nov 13, 2022
  37. Acreates

    Acreates

    Joined:
    Dec 12, 2016
    Posts:
    41
    This should be standard with Unity TMP Pro. Spent two days looking for a working solution, thanks from the future.
     
  38. jonathanglitchers

    jonathanglitchers

    Joined:
    Jan 5, 2023
    Posts:
    8
    Thread's almost 9 years old. Guys, I don't think they're fixing it...
     
  39. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,845
    Here's my approach that traverses the Hierarchy to find the next InputField, rather than comparing the InputFields' positions. It also tries to snap the parent ScrollRect (if exists) to the target InputField if the target InputField isn't visible by the ScrollRect.
    Code (CSharp):
    1. private readonly List<TMP_InputField> selectableInputFieldsCache = new List<TMP_InputField>(32);
    2.  
    3. void Update()
    4. {
    5.     if (Input.GetKeyDown(KeyCode.Tab) && EventSystem.current.currentSelectedGameObject && EventSystem.current.currentSelectedGameObject.TryGetComponent(out TMP_InputField inputField) && inputField.lineType == TMP_InputField.LineType.SingleLine)
    6.         SelectNextInputField(inputField, !Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift));
    7. }
    8.  
    9. void SelectNextInputField(TMP_InputField currentInputField, bool forwards)
    10. {
    11.     Transform containerTransform = null;
    12.  
    13.     // (Optional) Constraint the navigation to a specific parent object's children (including nested children)
    14.     // containerTransform = ...;
    15.     // UnityEngine.Assertions.Assert.IsTrue(containerTransform == null || (currentInputField.transform != containerTransform && currentInputField.transform.IsChildOf(containerTransform)));
    16.  
    17.     // Find the next selectable input field in the specified direction
    18.     int searchDirection = forwards ? 1 : -1;
    19.     Transform currentParent = currentInputField.transform;
    20.     while (currentParent != containerTransform)
    21.     {
    22.         int childIndex = currentParent.GetSiblingIndex();
    23.         currentParent = currentParent.parent;
    24.         if (currentParent == null)
    25.             return;
    26.  
    27.         for (childIndex += searchDirection; childIndex >= 0 && childIndex < currentParent.childCount; childIndex += searchDirection)
    28.         {
    29.             selectableInputFieldsCache.Clear();
    30.             currentParent.GetChild(childIndex).GetComponentsInChildren(selectableInputFieldsCache);
    31.  
    32.             for (int i = forwards ? 0 : selectableInputFieldsCache.Count - 1; forwards ? (i < selectableInputFieldsCache.Count) : (i >= 0); i += searchDirection)
    33.             {
    34.                 TMP_InputField inputField = selectableInputFieldsCache[i];
    35.                 if (inputField.isActiveAndEnabled && inputField.IsInteractable())
    36.                 {
    37.                     inputField.Select();
    38.  
    39.                     // (Optional) Force select the whole text
    40.                     if (!inputField.onFocusSelectAll)
    41.                         inputField.ProcessEvent(new Event() { modifiers = EventModifiers.Control | EventModifiers.Command, keyCode = KeyCode.A });
    42.  
    43.                     // (Optional) Focus the scroll view (if exists) on the input field, if needed
    44.                     if (inputField.GetComponentInParent<ScrollRect>() is ScrollRect scrollRect)
    45.                     {
    46.                         Rect inputFieldRect = ((RectTransform)inputField.transform).rect;
    47.                         Vector2 inputFieldLocalPosition = scrollRect.viewport.InverseTransformPoint(inputField.transform.TransformPoint(inputFieldRect.center));
    48.                         Vector2 inputFieldRectMin = inputFieldLocalPosition - inputFieldRect.size * 0.5f, inputFieldRectMax = inputFieldLocalPosition + inputFieldRect.size * 0.5f;
    49.  
    50.                         Rect viewportRect = scrollRect.viewport.rect;
    51.                         Vector2 viewportRectMin = viewportRect.min, viewportRectMax = viewportRect.max;
    52.  
    53.                         if (scrollRect.horizontal)
    54.                         {
    55.                             if (inputFieldRectMax.x > viewportRectMax.x)
    56.                                 scrollRect.content.anchoredPosition -= new Vector2(inputFieldRectMax.x - viewportRectMax.x, 0f);
    57.                             else if (inputFieldRectMin.x < viewportRectMin.x)
    58.                                 scrollRect.content.anchoredPosition -= new Vector2(inputFieldRectMin.x - viewportRectMin.x, 0f);
    59.                         }
    60.  
    61.                         if (scrollRect.vertical)
    62.                         {
    63.                             if (inputFieldRectMax.y > viewportRectMax.y)
    64.                                 scrollRect.content.anchoredPosition -= new Vector2(0f, inputFieldRectMax.y - viewportRectMax.y);
    65.                             else if (inputFieldRectMin.y < viewportRectMin.y)
    66.                                 scrollRect.content.anchoredPosition -= new Vector2(0f, inputFieldRectMin.y - viewportRectMin.y);
    67.                         }
    68.                     }
    69.  
    70.                     return;
    71.                 }
    72.             }
    73.         }
    74.     }
    75. }
     
    Last edited: Jul 11, 2023
  40. EZaca

    EZaca

    Joined:
    Dec 9, 2017
    Posts:
    32
    Maybe in the new UI system, if it's not fixed yet (I think it is). But where is your faith? The netcode was released almost a decade after the deprecation of UNET :D
     
  41. jonathanglitchers

    jonathanglitchers

    Joined:
    Jan 5, 2023
    Posts:
    8
    Hah, you're right. I take it back... 9 years is very impatient of me :)
     
    EZaca likes this.
  42. unity_028AE3B1F1BC5DECE8AD

    unity_028AE3B1F1BC5DECE8AD

    Joined:
    Jul 31, 2022
    Posts:
    94
    Seriously, a standard feature that should be implemented... This is seriously nearly ten years old and not even fixed? Wow.
     
    biebelbrott and Delcasda like this.
  43. biebelbrott

    biebelbrott

    Joined:
    May 5, 2022
    Posts:
    11
    My guys, its been almost TEN (10!) years! Thats a decade!
    When can we expect this functionality?
     
  44. GXMark

    GXMark

    Joined:
    Oct 13, 2012
    Posts:
    513
    I don't no why but i'm always having issues when pressing either tab or enter key between controls in unity. The navigation system should work but unsure why it does not respond. If anyone else is having issues with this you could place this little script on your controls and tell it where to focus on next. Its using the old input system.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class TabToNextControl : MonoBehaviour, IUpdateSelectedHandler
    6. {
    7.     public Selectable nextField;
    8.  
    9.     public void OnUpdateSelected(BaseEventData data)
    10.     {
    11.         if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.Return))
    12.             nextField.Select();
    13.     }
    14. }
     
  45. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,276
    I still like @SirRogers version best, of all the solutions in this thread.

    However, I have tweaked it slightly, and made it usable for the new Input System or the old, and managed the prior/next navigation slightly differently to handle more layouts naturally. I add this onto the same object that the EventSystem is on.

    Code (CSharp):
    1. // TabNextSelection.cs
    2. // Adapted from a forum posting by SirRogers:
    3. //   https://forum.unity.com/threads/
    4. //       tab-between-input-fields.263779/#post-2404236
    5. //
    6. using UnityEngine;
    7. using UnityEngine.UI;
    8. using UnityEngine.EventSystems;
    9. #if ENABLE_INPUT_SYSTEM
    10. using UnityEngine.InputSystem;
    11. #endif
    12.  
    13. public class TabNextSelection: MonoBehaviour
    14. {
    15.     [Tooltip("Also support Shift+Tab to move backwards to prior selection")]
    16.     public bool backtab = true;
    17.  
    18.     private EventSystem system;
    19.  
    20.     private void OnEnable()
    21.     {
    22.         system = EventSystem.current;
    23.     }
    24.  
    25.     private bool WasTabPressed()
    26.     {
    27. #if ENABLE_INPUT_SYSTEM
    28.         bool tab =
    29.             Keyboard.current.tabKey.wasPressedThisFrame;
    30. #else
    31.         bool tab =
    32.             Input.GetKeyDown(KeyCode.Tab);
    33. #endif
    34.         return tab;
    35.     }
    36.  
    37.     private bool IsShiftPressed()
    38.     {
    39. #if ENABLE_INPUT_SYSTEM
    40.         bool shift =
    41.             Keyboard.current.leftShiftKey.isPressed ||
    42.             Keyboard.current.rightShiftKey.isPressed;
    43. #else
    44.         bool shift =
    45.             Input.GetKey(KeyCode.LeftShift) ||
    46.             Input.GetKey(KeyCode.RightShift);
    47. #endif
    48.         return shift;
    49.     }
    50.  
    51.     private Selectable PriorSelectable(Selectable current)
    52.     {
    53.         Selectable prior = current.FindSelectableOnLeft();
    54.         if (prior == null)
    55.             prior = current.FindSelectableOnUp();
    56.         return prior;
    57.     }
    58.  
    59.     private Selectable NextSelectable(Selectable current)
    60.     {
    61.         Selectable next = current.FindSelectableOnRight();
    62.         if (next == null)
    63.             next = current.FindSelectableOnDown();
    64.         return next;
    65.     }
    66.  
    67.     private void Update()
    68.     {
    69.         if (system == null)
    70.             return;
    71.  
    72.         GameObject selected = system.currentSelectedGameObject;
    73.         if (selected == null)
    74.             return;
    75.  
    76.         if (!WasTabPressed())
    77.             return;
    78.  
    79.         Selectable current = selected.GetComponent<Selectable>();
    80.         if (current == null)
    81.             return;
    82.  
    83.         bool up = IsShiftPressed();
    84.         Selectable next = up? PriorSelectable(current) : NextSelectable(current);
    85.         // Wrap from end to beginning, or vice versa.
    86.         if (next == null)
    87.         {
    88.             next = current;
    89.             Selectable pnext;
    90.             if (up)
    91.                 while ((pnext = NextSelectable(next)) != null)
    92.                     next = pnext;
    93.             else
    94.                 while ((pnext = PriorSelectable(next)) != null)
    95.                     next = pnext;
    96.         }
    97.  
    98.         if (next == null)
    99.             return;
    100.  
    101.         // Simulate mouse click for InputFields.
    102.         InputField inputfield = next.GetComponent<InputField>();
    103.         if (inputfield != null)
    104.             inputfield.OnPointerClick(new PointerEventData(system));
    105.  
    106.         // Select the next item in the tab-order of our direction.
    107.         system.SetSelectedGameObject(next.gameObject);
    108.     }
    109. }