Search Unity

Dropdown autoscroller

Discussion in 'UGUI & TextMesh Pro' started by RakNet, Nov 16, 2016.

  1. RakNet

    RakNet

    Joined:
    Oct 9, 2013
    Posts:
    315
    Unity's dropdown system in 5.4.1 is unusable in production because it doesn't scroll the list to maintain view of the selected item when using the arrow keys. It also does not show the dropdown list on arrow key press, which is a standard behavior that should have been implemented.

    This script fixes that problem.

    One issue with my script is if the mouse is over the dropdown list, Unity will select the current item according to the current mouse position. This fights / overrides the selection from the arrow keys. The fix would be to disable the mouse on arrow key press, and reenable the mouse when the mouse is moved. This script doesn't do that, but that can be performed at a higher level.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. using UnityEngine.EventSystems;
    5. using System.Collections.Generic;
    6.  
    7. public class DropdownAutoscroller : MonoBehaviour {
    8.  
    9.     [Tooltip("Assign to the dropdown that should automatically scroll according to the currently selected item.")]
    10.     public Dropdown dropdown;
    11.  
    12.     // Use this for initialization
    13.     void Start ()
    14.     {
    15.     }
    16.    
    17.     // Update is called once per frame
    18.     void Update () {
    19.         if (EventSystem.current.currentSelectedGameObject == dropdown.gameObject)
    20.         {
    21.             if (Input.GetButtonUp("Vertical"))
    22.             {
    23.                 Transform dropdownListTransform = dropdown.gameObject.transform.FindChild("Dropdown List");
    24.                 if (dropdownListTransform == null)
    25.                 {
    26.                     // Show the dropdown when the user hits the arrow keys if the dropdown is not already showing
    27.                     dropdown.Show();
    28.                 }
    29.             }
    30.         }
    31.         else
    32.         {  
    33.             PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
    34.             eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    35.             List<RaycastResult> results = new List<RaycastResult>();
    36.             EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
    37.             if (results.Count > 0)
    38.             {
    39.                 if (results[0].gameObject.transform.IsChildOf(dropdown.gameObject.transform))
    40.                 {
    41.                     // Pointer over the list, use default behavior
    42.                     return;
    43.                 }
    44.             }
    45.            
    46.             // Autoscroll list as the selected object is changed from the arrow keys
    47.             if (EventSystem.current.currentSelectedGameObject.transform.IsChildOf(dropdown.gameObject.transform))
    48.             {
    49.                 if (EventSystem.current.currentSelectedGameObject.name.StartsWith("Item "))
    50.                 {
    51.                     // Skip disabled items
    52.                     Transform parent = EventSystem.current.currentSelectedGameObject.transform.parent;
    53.                     int activeChildren = 0;
    54.                     int totalChildren = parent.childCount;
    55.                     for (int childIndex = 0; childIndex < totalChildren; childIndex++)
    56.                     {
    57.                         if (parent.GetChild(childIndex).gameObject.activeInHierarchy)
    58.                         {
    59.                             activeChildren++;
    60.                         }
    61.                     }
    62.                     int myActiveIndex = 0;
    63.                     for (int childIndex = 0; childIndex < totalChildren; childIndex++)
    64.                     {
    65.                         if (parent.GetChild(childIndex).gameObject == EventSystem.current.currentSelectedGameObject)
    66.                         {
    67.                             break;
    68.                         }
    69.                         else if (parent.GetChild(childIndex).gameObject.activeInHierarchy)
    70.                         {
    71.                             myActiveIndex++;
    72.                         }
    73.                     }
    74.  
    75.                     if (activeChildren > 1)
    76.                     {
    77.                         GameObject scrollbarGameObject = GameObject.Find ("Scrollbar");
    78.                         if (scrollbarGameObject != null && scrollbarGameObject.activeInHierarchy)
    79.                         {
    80.                             Scrollbar scrollbar = scrollbarGameObject.GetComponent<Scrollbar>();
    81.                             if (scrollbar.direction == Scrollbar.Direction.TopToBottom)
    82.                                 scrollbar.value = (float) myActiveIndex / (float) (activeChildren-1);
    83.                             else
    84.                                 scrollbar.value = 1.0f - (float) myActiveIndex / (float) (activeChildren-1);
    85.                         }
    86.                     }
    87.                 }
    88.             }
    89.         }
    90.     }
    91. }
    92.  
     
    FunFreighterGames likes this.
  2. abermann_O

    abermann_O

    Joined:
    Aug 16, 2018
    Posts:
    42
    Thanks, great script!
     
  3. Asdcxz01

    Asdcxz01

    Joined:
    Jun 10, 2019
    Posts:
    2
    This works great. Using Text mesh pro dropdowns and this made it way easier to autoscroll
     
  4. VeryHappyYoung

    VeryHappyYoung

    Unity Technologies

    Joined:
    Nov 19, 2018
    Posts:
    4
    I don't know it will work on your task or not. I simplified the above code and applied it the below way, and it worked fine on my project. :)


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class ExpandedDropdown : Dropdown
    6. {
    7.     public override void OnPointerClick(PointerEventData eventData)
    8.     {
    9.         base.OnPointerClick(eventData);
    10.  
    11.         if (eventData.selectedObject.GetComponentInChildren<Scrollbar>() != null || !IsActive() || !IsInteractable())
    12.             return;
    13.  
    14.         Scrollbar scrollbar = gameObject.GetComponentInChildren<ScrollRect>()?.verticalScrollbar;
    15.         if (options.Count > 1 && scrollbar != null)
    16.         {
    17.             if (scrollbar.direction == Scrollbar.Direction.TopToBottom)
    18.                 scrollbar.value = Mathf.Max(0.001f, (float) (value) / (float) (options.Count - 1));
    19.             else
    20.                 scrollbar.value = Mathf.Max(0.001f, 1.0f - (float) (value) / (float) (options.Count - 1));
    21.         }
    22.     }
    23. }
     
    ArtGon20, Telhurin and RunninglVlan like this.
  5. Nebuchaddy

    Nebuchaddy

    Joined:
    Apr 8, 2019
    Posts:
    2
    Excuse me but how can I add this to an already existing TMP Dropdwon? I'm a bit of a begginner and I'm twinkering with UI.

    Thanks a bunch!
     
  6. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    182
    Is this logic expected to go to Unity's Dropdown classes? As RakNet I also thought it's pretty standard behavior, e.g., it's supported in HTML
     
    Last edited: May 12, 2021
  7. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    182
    Here's a script you can add (I modified VeryHappyYoung's code a bit)
    It depends on order of components on the GameObject so be sure to place this below actual Dropdown.
    Code (CSharp):
    1. using TMPro;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5. using static UnityEngine.UI.Scrollbar;
    6.  
    7. /// <summary>Adds support for auto-scrolling to selected value</summary>
    8. /// <remarks>Place this after actual Dropdown Component, depends on GetComponents order logic</remarks>
    9. public class AutoscrollDropdown : MonoBehaviour, IPointerClickHandler {
    10.   [SerializeField] private TMP_Dropdown target = null!;
    11.  
    12.   public void OnPointerClick(PointerEventData _) {
    13.     if (!target.GetComponentInChildren<Scrollbar>() || !target.IsActive() || !target.IsInteractable()) {
    14.       return;
    15.     }
    16.  
    17.     var scrollbar = GetComponentInChildren<ScrollRect>()?.verticalScrollbar;
    18.     if (target.options.Count > 1 && scrollbar != null) {
    19.       var valuePosition = (float)target.value / (target.options.Count - 1);
    20.       var value = scrollbar.direction == Direction.TopToBottom ? valuePosition : 1f - valuePosition;
    21.       scrollbar.value = Mathf.Max(.001f, value);
    22.     }
    23.   }
    24. }
    BTW, @VeryHappyYoung, could you explain
    eventData.selectedObject.GetComponentInChildren<Scrollbar>() != null
    logic (why != null, not == null)? It didn't work for me (and also thrown NullReferenceException when nothing was selected yet) so I changed it to
    !target.GetComponentInChildren<Scrollbar>()
     
    rmacgillis, JasonFPL and x4000 like this.
  8. maskedimposter13

    maskedimposter13

    Joined:
    Sep 26, 2018
    Posts:
    1
    I came up with this method.

    Basically you have the items take their child index and divide it by the siblings to get an approximation of what the scroll value should be. Then when a child object is selected, you set the scrollbar's value to that number.
     
    MihaPro_CarX likes this.