1326 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			1326 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | |||
|  | using System.Collections; | |||
|  | using System.Collections.Generic; | |||
|  | using UnityEngine; | |||
|  | using UnityEngine.UI; | |||
|  | using UnityEngine.Events; | |||
|  | using UnityEngine.EventSystems; | |||
|  | using UnityEngine.UI.CoroutineTween; | |||
|  | 
 | |||
|  | namespace TMPro | |||
|  | { | |||
|  |     [AddComponentMenu("UI/Dropdown - TextMeshPro", 35)] | |||
|  |     [RequireComponent(typeof(RectTransform))] | |||
|  |     /// <summary> | |||
|  |     ///   A standard dropdown that presents a list of options when clicked, of which one can be chosen. | |||
|  |     /// </summary> | |||
|  |     /// <remarks> | |||
|  |     /// The dropdown component is a Selectable. When an option is chosen, the label and/or image of the control changes to show the chosen option. | |||
|  |     /// | |||
|  |     /// When a dropdown event occurs a callback is sent to any registered listeners of onValueChanged. | |||
|  |     /// </remarks> | |||
|  |     public class TMP_Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler | |||
|  |     { | |||
|  |         protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler | |||
|  |         { | |||
|  |             [SerializeField] | |||
|  |             private TMP_Text m_Text; | |||
|  |             [SerializeField] | |||
|  |             private Image m_Image; | |||
|  |             [SerializeField] | |||
|  |             private RectTransform m_RectTransform; | |||
|  |             [SerializeField] | |||
|  |             private Toggle m_Toggle; | |||
|  | 
 | |||
|  |             public TMP_Text text { get { return m_Text; } set { m_Text = value; } } | |||
|  |             public Image image { get { return m_Image; } set { m_Image = value; } } | |||
|  |             public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } } | |||
|  |             public Toggle toggle { get { return m_Toggle; } set { m_Toggle = value; } } | |||
|  | 
 | |||
|  |             public virtual void OnPointerEnter(PointerEventData eventData) | |||
|  |             { | |||
|  |                 EventSystem.current.SetSelectedGameObject(gameObject); | |||
|  |             } | |||
|  | 
 | |||
|  |             public virtual void OnCancel(BaseEventData eventData) | |||
|  |             { | |||
|  |                 TMP_Dropdown dropdown = GetComponentInParent<TMP_Dropdown>(); | |||
|  |                 if (dropdown) | |||
|  |                     dropdown.Hide(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         [Serializable] | |||
|  |         /// <summary> | |||
|  |         /// Class to store the text and/or image of a single option in the dropdown list. | |||
|  |         /// </summary> | |||
|  |         public class OptionData | |||
|  |         { | |||
|  |             [SerializeField] | |||
|  |             private string m_Text; | |||
|  |             [SerializeField] | |||
|  |             private Sprite m_Image; | |||
|  |             [SerializeField] | |||
|  |             private Color m_Color = Color.white; | |||
|  | 
 | |||
|  |             /// <summary> | |||
|  |             /// The text associated with the option. | |||
|  |             /// </summary> | |||
|  |             public string text { get { return m_Text; } set { m_Text = value; } } | |||
|  | 
 | |||
|  |             /// <summary> | |||
|  |             /// The image associated with the option. | |||
|  |             /// </summary> | |||
|  |             public Sprite image { get { return m_Image; } set { m_Image = value; } } | |||
|  | 
 | |||
|  |             /// <summary> | |||
|  |             /// The color associated with the option. | |||
|  |             /// </summary> | |||
|  |             public Color color { get { return m_Color; } set { m_Color = value; } } | |||
|  | 
 | |||
|  |             public OptionData() { } | |||
|  | 
 | |||
|  |             public OptionData(string text) | |||
|  |             { | |||
|  |                 this.text = text; | |||
|  |             } | |||
|  | 
 | |||
|  |             public OptionData(Sprite image) | |||
|  |             { | |||
|  |                 this.image = image; | |||
|  |             } | |||
|  | 
 | |||
|  |             /// <summary> | |||
|  |             /// Create an object representing a single option for the dropdown list. | |||
|  |             /// </summary> | |||
|  |             /// <param name="text">Optional text for the option.</param> | |||
|  |             /// <param name="image">Optional image for the option.</param> | |||
|  |             /// <param name="image">Optional color for the option.</param> | |||
|  |             public OptionData(string text, Sprite image, Color color) | |||
|  |             { | |||
|  |                 this.text = text; | |||
|  |                 this.image = image; | |||
|  |                 this.color = color; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         [Serializable] | |||
|  |         /// <summary> | |||
|  |         /// Class used internally to store the list of options for the dropdown list. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// The usage of this class is not exposed in the runtime API. It's only relevant for the PropertyDrawer drawing the list of options. | |||
|  |         /// </remarks> | |||
|  |         public class OptionDataList | |||
|  |         { | |||
|  |             [SerializeField] | |||
|  |             private List<OptionData> m_Options; | |||
|  | 
 | |||
|  |             /// <summary> | |||
|  |             /// The list of options for the dropdown list. | |||
|  |             /// </summary> | |||
|  |             public List<OptionData> options { get { return m_Options; } set { m_Options = value; } } | |||
|  | 
 | |||
|  | 
 | |||
|  |             public OptionDataList() | |||
|  |             { | |||
|  |                 options = new List<OptionData>(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         [Serializable] | |||
|  |         /// <summary> | |||
|  |         /// UnityEvent callback for when a dropdown current option is changed. | |||
|  |         /// </summary> | |||
|  |         public class DropdownEvent : UnityEvent<int> { } | |||
|  | 
 | |||
|  |         static readonly OptionData k_NothingOption = new OptionData { text = "Nothing" }; | |||
|  |         static readonly OptionData k_EverythingOption = new OptionData { text = "Everything" }; | |||
|  |         static readonly OptionData k_MixedOption = new OptionData { text = "Mixed..." }; | |||
|  | 
 | |||
|  |         // Template used to create the dropdown. | |||
|  |         [SerializeField] | |||
|  |         private RectTransform m_Template; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Rect Transform of the template for the dropdown list. | |||
|  |         /// </summary> | |||
|  |         public RectTransform template { get { return m_Template; } set { m_Template = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         // Text to be used as a caption for the current value. It's not required, but it's kept here for convenience. | |||
|  |         [SerializeField] | |||
|  |         private TMP_Text m_CaptionText; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Text component to hold the text of the currently selected option. | |||
|  |         /// </summary> | |||
|  |         public TMP_Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private Image m_CaptionImage; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Image component to hold the image of the currently selected option. | |||
|  |         /// </summary> | |||
|  |         public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private Graphic m_Placeholder; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The placeholder Graphic component. Shown when no option is selected. | |||
|  |         /// </summary> | |||
|  |         public Graphic placeholder { get { return m_Placeholder; } set { m_Placeholder = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         [Space] | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private TMP_Text m_ItemText; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Text component to hold the text of the item. | |||
|  |         /// </summary> | |||
|  |         public TMP_Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private Image m_ItemImage; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Image component to hold the image of the item | |||
|  |         /// </summary> | |||
|  |         public Image itemImage { get { return m_ItemImage; } set { m_ItemImage = value; RefreshShownValue(); } } | |||
|  | 
 | |||
|  |         [Space] | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private int m_Value; | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private bool m_MultiSelect; | |||
|  | 
 | |||
|  |         [Space] | |||
|  | 
 | |||
|  |         // Items that will be visible when the dropdown is shown. | |||
|  |         // We box this into its own class so we can use a Property Drawer for it. | |||
|  |         [SerializeField] | |||
|  |         private OptionDataList m_Options = new OptionDataList(); | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The list of possible options. A text string and an image can be specified for each option. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// This is the list of options within the Dropdown. Each option contains Text and/or image data that you can specify using UI.Dropdown.OptionData before adding to the Dropdown list. | |||
|  |         /// This also unlocks the ability to edit the Dropdown, including the insertion, removal, and finding of options, as well as other useful tools | |||
|  |         /// </remarks> | |||
|  |         /// /// <example> | |||
|  |         /// <code> | |||
|  |         /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. | |||
|  |         /// | |||
|  |         /// using UnityEngine; | |||
|  |         /// using UnityEngine.UI; | |||
|  |         /// using System.Collections.Generic; | |||
|  |         /// using TMPro; | |||
|  |         /// | |||
|  |         /// public class Example : MonoBehaviour | |||
|  |         /// { | |||
|  |         ///     //Use these for adding options to the Dropdown List | |||
|  |         ///     TMP_Dropdown.OptionData m_NewData, m_NewData2; | |||
|  |         ///     //The list of messages for the Dropdown | |||
|  |         ///     List<TMP_Dropdown.OptionData> m_Messages = new List<TMP_Dropdown.OptionData>(); | |||
|  |         /// | |||
|  |         /// | |||
|  |         ///     //This is the Dropdown | |||
|  |         ///     TMP_Dropdown m_Dropdown; | |||
|  |         ///     string m_MyString; | |||
|  |         ///     int m_Index; | |||
|  |         /// | |||
|  |         ///     void Start() | |||
|  |         ///     { | |||
|  |         ///         //Fetch the Dropdown GameObject the script is attached to | |||
|  |         ///         m_Dropdown = GetComponent<TMP_Dropdown>(); | |||
|  |         ///         //Clear the old options of the Dropdown menu | |||
|  |         ///         m_Dropdown.ClearOptions(); | |||
|  |         /// | |||
|  |         ///         //Create a new option for the Dropdown menu which reads "Option 1" and add to messages List | |||
|  |         ///         m_NewData = new TMP_Dropdown.OptionData(); | |||
|  |         ///         m_NewData.text = "Option 1"; | |||
|  |         ///         m_Messages.Add(m_NewData); | |||
|  |         /// | |||
|  |         ///         //Create a new option for the Dropdown menu which reads "Option 2" and add to messages List | |||
|  |         ///         m_NewData2 = new TMP_Dropdown.OptionData(); | |||
|  |         ///         m_NewData2.text = "Option 2"; | |||
|  |         ///         m_Messages.Add(m_NewData2); | |||
|  |         /// | |||
|  |         ///         //Take each entry in the message List | |||
|  |         ///         foreach (TMP_Dropdown.OptionData message in m_Messages) | |||
|  |         ///         { | |||
|  |         ///             //Add each entry to the Dropdown | |||
|  |         ///             m_Dropdown.options.Add(message); | |||
|  |         ///             //Make the index equal to the total number of entries | |||
|  |         ///             m_Index = m_Messages.Count - 1; | |||
|  |         ///         } | |||
|  |         ///     } | |||
|  |         /// | |||
|  |         ///     //This OnGUI function is used here for a quick demonstration. See the [[wiki:UISystem|UI Section]] for more information about setting up your own UI. | |||
|  |         ///     void OnGUI() | |||
|  |         ///     { | |||
|  |         ///         //TextField for user to type new entry to add to Dropdown | |||
|  |         ///         m_MyString = GUI.TextField(new Rect(0, 40, 100, 40), m_MyString); | |||
|  |         /// | |||
|  |         ///         //Press the "Add" Button to add a new entry to the Dropdown | |||
|  |         ///         if (GUI.Button(new Rect(0, 0, 100, 40), "Add")) | |||
|  |         ///         { | |||
|  |         ///             //Make the index the last number of entries | |||
|  |         ///             m_Index = m_Messages.Count; | |||
|  |         ///             //Create a temporary option | |||
|  |         ///             TMP_Dropdown.OptionData temp = new TMP_Dropdown.OptionData(); | |||
|  |         ///             //Make the option the data from the TextField | |||
|  |         ///             temp.text = m_MyString; | |||
|  |         /// | |||
|  |         ///             //Update the messages list with the TextField data | |||
|  |         ///             m_Messages.Add(temp); | |||
|  |         /// | |||
|  |         ///             //Add the Textfield data to the Dropdown | |||
|  |         ///             m_Dropdown.options.Insert(m_Index, temp); | |||
|  |         ///         } | |||
|  |         /// | |||
|  |         ///         //Press the "Remove" button to delete the selected option | |||
|  |         ///         if (GUI.Button(new Rect(110, 0, 100, 40), "Remove")) | |||
|  |         ///         { | |||
|  |         ///             //Remove the current selected item from the Dropdown from the messages List | |||
|  |         ///             m_Messages.RemoveAt(m_Dropdown.value); | |||
|  |         ///             //Remove the current selection from the Dropdown | |||
|  |         ///             m_Dropdown.options.RemoveAt(m_Dropdown.value); | |||
|  |         ///         } | |||
|  |         ///     } | |||
|  |         /// } | |||
|  |         /// </code> | |||
|  |         /// </example> | |||
|  |         public List<OptionData> options | |||
|  |         { | |||
|  |             get { return m_Options.options; } | |||
|  |             set { m_Options.options = value; RefreshShownValue(); } | |||
|  |         } | |||
|  | 
 | |||
|  |         [Space] | |||
|  | 
 | |||
|  |         // Notification triggered when the dropdown changes. | |||
|  |         [SerializeField] | |||
|  |         private DropdownEvent m_OnValueChanged = new DropdownEvent(); | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// A UnityEvent that is invoked when a user has clicked one of the options in the dropdown list. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Use this to detect when a user selects one or more options in the Dropdown. Add a listener to perform an action when this UnityEvent detects a selection by the user. See https://unity3d.com/learn/tutorials/topics/scripting/delegates for more information on delegates. | |||
|  |         /// </remarks> | |||
|  |         /// <example> | |||
|  |         ///  <code> | |||
|  |         /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. | |||
|  |         /// //Set your own Text in the Inspector window | |||
|  |         /// | |||
|  |         /// using UnityEngine; | |||
|  |         /// using UnityEngine.UI; | |||
|  |         /// using TMPro; | |||
|  |         /// | |||
|  |         /// public class Example : MonoBehaviour | |||
|  |         /// { | |||
|  |         ///     TMP_Dropdown m_Dropdown; | |||
|  |         ///     public Text m_Text; | |||
|  |         /// | |||
|  |         ///     void Start() | |||
|  |         ///     { | |||
|  |         ///         //Fetch the Dropdown GameObject | |||
|  |         ///         m_Dropdown = GetComponent<TMP_Dropdown>(); | |||
|  |         ///         //Add listener for when the value of the Dropdown changes, to take action | |||
|  |         ///         m_Dropdown.onValueChanged.AddListener(delegate { | |||
|  |         ///                 DropdownValueChanged(m_Dropdown); | |||
|  |         ///             }); | |||
|  |         /// | |||
|  |         ///         //Initialize the Text to say the first value of the Dropdown | |||
|  |         ///         m_Text.text = "First Value : " + m_Dropdown.value; | |||
|  |         ///     } | |||
|  |         /// | |||
|  |         ///     //Output the new value of the Dropdown into Text | |||
|  |         ///     void DropdownValueChanged(TMP_Dropdown change) | |||
|  |         ///     { | |||
|  |         ///         m_Text.text =  "New Value : " + change.value; | |||
|  |         ///     } | |||
|  |         /// } | |||
|  |         /// </code> | |||
|  |         /// </example> | |||
|  |         public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } | |||
|  | 
 | |||
|  |         [SerializeField] | |||
|  |         private float m_AlphaFadeSpeed = 0.15f; | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The time interval at which a drop down will appear and disappear | |||
|  |         /// </summary> | |||
|  |         public float alphaFadeSpeed { get { return m_AlphaFadeSpeed; } set { m_AlphaFadeSpeed = value; } } | |||
|  | 
 | |||
|  |         private GameObject m_Dropdown; | |||
|  |         private GameObject m_Blocker; | |||
|  |         private List<DropdownItem> m_Items = new List<DropdownItem>(); | |||
|  |         private TweenRunner<FloatTween> m_AlphaTweenRunner; | |||
|  |         private bool validTemplate = false; | |||
|  |         private Coroutine m_Coroutine = null; | |||
|  | 
 | |||
|  |         private static OptionData s_NoOptionData = new OptionData(); | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// The Value is the index number of the current selection in the Dropdown. 0 is the first option in the Dropdown, 1 is the second, and so on. | |||
|  |         /// </summary> | |||
|  |         /// <example> | |||
|  |         /// <code> | |||
|  |         /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. | |||
|  |         /// //Set your own Text in the Inspector window | |||
|  |         /// | |||
|  |         /// using UnityEngine; | |||
|  |         /// using UnityEngine.UI; | |||
|  |         /// using TMPro; | |||
|  |         /// | |||
|  |         /// public class Example : MonoBehaviour | |||
|  |         /// { | |||
|  |         ///     //Attach this script to a Dropdown GameObject | |||
|  |         ///     TMP_Dropdown m_Dropdown; | |||
|  |         ///     //This is the string that stores the current selection m_Text of the Dropdown | |||
|  |         ///     string m_Message; | |||
|  |         ///     //This Text outputs the current selection to the screen | |||
|  |         ///     public Text m_Text; | |||
|  |         ///     //This is the index value of the Dropdown | |||
|  |         ///     int m_DropdownValue; | |||
|  |         /// | |||
|  |         ///     void Start() | |||
|  |         ///     { | |||
|  |         ///         //Fetch the DropDown component from the GameObject | |||
|  |         ///         m_Dropdown = GetComponent<TMP_Dropdown>(); | |||
|  |         ///         //Output the first Dropdown index value | |||
|  |         ///         Debug.Log("Starting Dropdown Value : " + m_Dropdown.value); | |||
|  |         ///     } | |||
|  |         /// | |||
|  |         ///     void Update() | |||
|  |         ///     { | |||
|  |         ///         //Keep the current index of the Dropdown in a variable | |||
|  |         ///         m_DropdownValue = m_Dropdown.value; | |||
|  |         ///         //Change the message to say the name of the current Dropdown selection using the value | |||
|  |         ///         m_Message = m_Dropdown.options[m_DropdownValue].text; | |||
|  |         ///         //Change the on screen Text to reflect the current Dropdown selection | |||
|  |         ///         m_Text.text = m_Message; | |||
|  |         ///     } | |||
|  |         /// } | |||
|  |         /// </code> | |||
|  |         /// </example> | |||
|  |         public int value | |||
|  |         { | |||
|  |             get | |||
|  |             { | |||
|  |                 return m_Value; | |||
|  |             } | |||
|  |             set | |||
|  |             { | |||
|  |                 SetValue(value); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Set index number of the current selection in the Dropdown without invoking onValueChanged callback. | |||
|  |         /// </summary> | |||
|  |         /// <param name="input">The new index for the current selection.</param> | |||
|  |         public void SetValueWithoutNotify(int input) | |||
|  |         { | |||
|  |             SetValue(input, false); | |||
|  |         } | |||
|  | 
 | |||
|  |         void SetValue(int value, bool sendCallback = true) | |||
|  |         { | |||
|  |             if (Application.isPlaying && (value == m_Value || options.Count == 0)) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             if (m_MultiSelect) | |||
|  |                 m_Value = value; | |||
|  |             else | |||
|  |                 m_Value = Mathf.Clamp(value, m_Placeholder ? -1 : 0, options.Count - 1); | |||
|  | 
 | |||
|  |             RefreshShownValue(); | |||
|  | 
 | |||
|  |             if (sendCallback) | |||
|  |             { | |||
|  |                 // Notify all listeners | |||
|  |                 UISystemProfilerApi.AddMarker("Dropdown.value", this); | |||
|  |                 m_OnValueChanged.Invoke(m_Value); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         public bool IsExpanded { get { return m_Dropdown != null; } } | |||
|  | 
 | |||
|  |         public bool MultiSelect { get { return m_MultiSelect; } set { m_MultiSelect = value; } } | |||
|  | 
 | |||
|  |         protected TMP_Dropdown() { } | |||
|  | 
 | |||
|  |         protected override void Awake() | |||
|  |         { | |||
|  |             #if UNITY_EDITOR | |||
|  |             if (!Application.isPlaying) | |||
|  |                 return; | |||
|  |             #endif | |||
|  | 
 | |||
|  |             if (m_CaptionImage) | |||
|  |                 m_CaptionImage.enabled = (m_CaptionImage.sprite != null && m_CaptionImage.color.a > 0); | |||
|  | 
 | |||
|  |             if (m_Template) | |||
|  |                 m_Template.gameObject.SetActive(false); | |||
|  |         } | |||
|  | 
 | |||
|  |         protected override void Start() | |||
|  |         { | |||
|  |             m_AlphaTweenRunner = new TweenRunner<FloatTween>(); | |||
|  |             m_AlphaTweenRunner.Init(this); | |||
|  |             base.Start(); | |||
|  | 
 | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | 
 | |||
|  | #if UNITY_EDITOR | |||
|  |         protected override void OnValidate() | |||
|  |         { | |||
|  |             base.OnValidate(); | |||
|  | 
 | |||
|  |             if (!IsActive()) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | #endif | |||
|  | 
 | |||
|  |         protected override void OnDisable() | |||
|  |         { | |||
|  |             //Destroy dropdown and blocker in case user deactivates the dropdown when they click an option (case 935649) | |||
|  |             ImmediateDestroyDropdownList(); | |||
|  | 
 | |||
|  |             if (m_Blocker != null) | |||
|  |                 DestroyBlocker(m_Blocker); | |||
|  | 
 | |||
|  |             m_Blocker = null; | |||
|  | 
 | |||
|  |             base.OnDisable(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Refreshes the text and image (if available) of the currently selected option. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// If you have modified the list of options, you should call this method afterwards to ensure that the visual state of the dropdown corresponds to the updated options. | |||
|  |         /// </remarks> | |||
|  |         public void RefreshShownValue() | |||
|  |         { | |||
|  |             OptionData data = s_NoOptionData; | |||
|  | 
 | |||
|  |             if (options.Count > 0) | |||
|  |             { | |||
|  |                 if (m_MultiSelect) | |||
|  |                 { | |||
|  |                     int firstActiveFlag = FirstActiveFlagIndex(m_Value); | |||
|  |                     if (m_Value == 0 || firstActiveFlag >= options.Count) | |||
|  |                         data = k_NothingOption; | |||
|  |                     else if (IsEverythingValue(options.Count, m_Value)) | |||
|  |                         data = k_EverythingOption; | |||
|  |                     else if (Mathf.IsPowerOfTwo(m_Value) && m_Value > 0) | |||
|  |                         data = options[firstActiveFlag]; | |||
|  |                     else | |||
|  |                         data = k_MixedOption; | |||
|  |                 } | |||
|  |                 else if (m_Value >= 0) | |||
|  |                 { | |||
|  |                     data = options[Mathf.Clamp(m_Value, 0, options.Count - 1)]; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             if (m_CaptionText) | |||
|  |             { | |||
|  |                 if (data != null && data.text != null) | |||
|  |                     m_CaptionText.text = data.text; | |||
|  |                 else | |||
|  |                     m_CaptionText.text = ""; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (m_CaptionImage) | |||
|  |             { | |||
|  |                 m_CaptionImage.sprite = data.image; | |||
|  |                 m_CaptionImage.color = data.color; | |||
|  |                 m_CaptionImage.enabled = (m_CaptionImage.sprite != null && m_CaptionImage.color.a > 0); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (m_Placeholder) | |||
|  |             { | |||
|  |                 m_Placeholder.enabled = options.Count == 0 || m_Value == -1; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Add multiple options to the options of the Dropdown based on a list of OptionData objects. | |||
|  |         /// </summary> | |||
|  |         /// <param name="options">The list of OptionData to add.</param> | |||
|  |         /// /// <remarks> | |||
|  |         /// See AddOptions(List<string> options) for code example of usages. | |||
|  |         /// </remarks> | |||
|  |         public void AddOptions(List<OptionData> options) | |||
|  |         { | |||
|  |             this.options.AddRange(options); | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Add multiple text-only options to the options of the Dropdown based on a list of strings. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Add a List of string messages to the Dropdown. The Dropdown shows each member of the list as a separate option. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="options">The list of text strings to add.</param> | |||
|  |         /// <example> | |||
|  |         /// <code> | |||
|  |         /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. | |||
|  |         /// | |||
|  |         /// using System.Collections.Generic; | |||
|  |         /// using UnityEngine; | |||
|  |         /// using UnityEngine.UI; | |||
|  |         /// using TMPro; | |||
|  |         /// | |||
|  |         /// public class Example : MonoBehaviour | |||
|  |         /// { | |||
|  |         ///     //Create a List of new Dropdown options | |||
|  |         ///     List<string> m_DropOptions = new List<string> { "Option 1", "Option 2"}; | |||
|  |         ///     //This is the Dropdown | |||
|  |         ///     TMP_Dropdown m_Dropdown; | |||
|  |         /// | |||
|  |         ///     void Start() | |||
|  |         ///     { | |||
|  |         ///         //Fetch the Dropdown GameObject the script is attached to | |||
|  |         ///         m_Dropdown = GetComponent<TMP_Dropdown>(); | |||
|  |         ///         //Clear the old options of the Dropdown menu | |||
|  |         ///         m_Dropdown.ClearOptions(); | |||
|  |         ///         //Add the options created in the List above | |||
|  |         ///         m_Dropdown.AddOptions(m_DropOptions); | |||
|  |         ///     } | |||
|  |         /// } | |||
|  |         /// </code> | |||
|  |         /// </example> | |||
|  |         public void AddOptions(List<string> options) | |||
|  |         { | |||
|  |             for (int i = 0; i < options.Count; i++) | |||
|  |                 this.options.Add(new OptionData(options[i])); | |||
|  | 
 | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Add multiple image-only options to the options of the Dropdown based on a list of Sprites. | |||
|  |         /// </summary> | |||
|  |         /// <param name="options">The list of Sprites to add.</param> | |||
|  |         /// <remarks> | |||
|  |         /// See AddOptions(List<string> options) for code example of usages. | |||
|  |         /// </remarks> | |||
|  |         public void AddOptions(List<Sprite> options) | |||
|  |         { | |||
|  |             for (int i = 0; i < options.Count; i++) | |||
|  |                 this.options.Add(new OptionData(options[i])); | |||
|  | 
 | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Clear the list of options in the Dropdown. | |||
|  |         /// </summary> | |||
|  |         public void ClearOptions() | |||
|  |         { | |||
|  |             options.Clear(); | |||
|  |             m_Value = m_Placeholder ? -1 : 0; | |||
|  |             RefreshShownValue(); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void SetupTemplate() | |||
|  |         { | |||
|  |             validTemplate = false; | |||
|  | 
 | |||
|  |             if (!m_Template) | |||
|  |             { | |||
|  |                 Debug.LogError("The dropdown template is not assigned. The template needs to be assigned and must have a child GameObject with a Toggle component serving as the item.", this); | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             GameObject templateGo = m_Template.gameObject; | |||
|  |             templateGo.SetActive(true); | |||
|  |             Toggle itemToggle = m_Template.GetComponentInChildren<Toggle>(); | |||
|  | 
 | |||
|  |             validTemplate = true; | |||
|  |             if (!itemToggle || itemToggle.transform == template) | |||
|  |             { | |||
|  |                 validTemplate = false; | |||
|  |                 Debug.LogError("The dropdown template is not valid. The template must have a child GameObject with a Toggle component serving as the item.", template); | |||
|  |             } | |||
|  |             else if (!(itemToggle.transform.parent is RectTransform)) | |||
|  |             { | |||
|  |                 validTemplate = false; | |||
|  |                 Debug.LogError("The dropdown template is not valid. The child GameObject with a Toggle component (the item) must have a RectTransform on its parent.", template); | |||
|  |             } | |||
|  |             else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform)) | |||
|  |             { | |||
|  |                 validTemplate = false; | |||
|  |                 Debug.LogError("The dropdown template is not valid. The Item Text must be on the item GameObject or children of it.", template); | |||
|  |             } | |||
|  |             else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform)) | |||
|  |             { | |||
|  |                 validTemplate = false; | |||
|  |                 Debug.LogError("The dropdown template is not valid. The Item Image must be on the item GameObject or children of it.", template); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!validTemplate) | |||
|  |             { | |||
|  |                 templateGo.SetActive(false); | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             DropdownItem item = itemToggle.gameObject.AddComponent<DropdownItem>(); | |||
|  |             item.text = m_ItemText; | |||
|  |             item.image = m_ItemImage; | |||
|  |             item.toggle = itemToggle; | |||
|  |             item.rectTransform = (RectTransform)itemToggle.transform; | |||
|  | 
 | |||
|  |             // Find the Canvas that this dropdown is a part of | |||
|  |             Canvas parentCanvas = null; | |||
|  |             Transform parentTransform = m_Template.parent; | |||
|  |             while (parentTransform != null) | |||
|  |             { | |||
|  |                 parentCanvas = parentTransform.GetComponent<Canvas>(); | |||
|  |                 if (parentCanvas != null) | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 parentTransform = parentTransform.parent; | |||
|  |             } | |||
|  | 
 | |||
|  |             Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo); | |||
|  |             popupCanvas.overrideSorting = true; | |||
|  |             popupCanvas.sortingOrder = 30000; | |||
|  | 
 | |||
|  |             // If we have a parent canvas, apply the same raycasters as the parent for consistency. | |||
|  |             if (parentCanvas != null) | |||
|  |             { | |||
|  |                 Component[] components = parentCanvas.GetComponents<BaseRaycaster>(); | |||
|  |                 for (int i = 0; i < components.Length; i++) | |||
|  |                 { | |||
|  |                     Type raycasterType = components[i].GetType(); | |||
|  |                     if (templateGo.GetComponent(raycasterType) == null) | |||
|  |                     { | |||
|  |                         templateGo.AddComponent(raycasterType); | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 GetOrAddComponent<GraphicRaycaster>(templateGo); | |||
|  |             } | |||
|  | 
 | |||
|  |             GetOrAddComponent<CanvasGroup>(templateGo); | |||
|  |             templateGo.SetActive(false); | |||
|  | 
 | |||
|  |             validTemplate = true; | |||
|  |         } | |||
|  | 
 | |||
|  |         private static T GetOrAddComponent<T>(GameObject go) where T : Component | |||
|  |         { | |||
|  |             T comp = go.GetComponent<T>(); | |||
|  |             if (!comp) | |||
|  |                 comp = go.AddComponent<T>(); | |||
|  |             return comp; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Handling for when the dropdown is initially 'clicked'. Typically shows the dropdown | |||
|  |         /// </summary> | |||
|  |         /// <param name="eventData">The associated event data.</param> | |||
|  |         public virtual void OnPointerClick(PointerEventData eventData) | |||
|  |         { | |||
|  |             Show(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Handling for when the dropdown is selected and a submit event is processed. Typically shows the dropdown | |||
|  |         /// </summary> | |||
|  |         /// <param name="eventData">The associated event data.</param> | |||
|  |         public virtual void OnSubmit(BaseEventData eventData) | |||
|  |         { | |||
|  |             Show(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// This will hide the dropdown list. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Called by a BaseInputModule when a Cancel event occurs. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="eventData">The associated event data.</param> | |||
|  |         public virtual void OnCancel(BaseEventData eventData) | |||
|  |         { | |||
|  |             Hide(); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Show the dropdown. | |||
|  |         /// | |||
|  |         /// Plan for dropdown scrolling to ensure dropdown is contained within screen. | |||
|  |         /// | |||
|  |         /// We assume the Canvas is the screen that the dropdown must be kept inside. | |||
|  |         /// This is always valid for screen space canvas modes. | |||
|  |         /// For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor. | |||
|  |         /// We consider it a fair constraint that the canvas must be big enough to contain dropdowns. | |||
|  |         /// </summary> | |||
|  |         public void Show() | |||
|  |         { | |||
|  |             if (m_Coroutine != null) | |||
|  |             { | |||
|  |                 StopCoroutine(m_Coroutine); | |||
|  |                 ImmediateDestroyDropdownList(); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!IsActive() || !IsInteractable() || m_Dropdown != null) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             // Get root Canvas. | |||
|  |             var list = TMP_ListPool<Canvas>.Get(); | |||
|  |             gameObject.GetComponentsInParent(false, list); | |||
|  |             if (list.Count == 0) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             Canvas rootCanvas = list[list.Count - 1]; | |||
|  |             for (int i = 0; i < list.Count; i++) | |||
|  |             { | |||
|  |                 if (list[i].isRootCanvas) | |||
|  |                 { | |||
|  |                     rootCanvas = list[i]; | |||
|  |                     break; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             TMP_ListPool<Canvas>.Release(list); | |||
|  | 
 | |||
|  |             if (!validTemplate) | |||
|  |             { | |||
|  |                 SetupTemplate(); | |||
|  |                 if (!validTemplate) | |||
|  |                     return; | |||
|  |             } | |||
|  | 
 | |||
|  |             m_Template.gameObject.SetActive(true); | |||
|  | 
 | |||
|  |             // popupCanvas used to assume the root canvas had the default sorting Layer, next line fixes (case 958281 - [UI] Dropdown list does not copy the parent canvas layer when the panel is opened) | |||
|  |             m_Template.GetComponent<Canvas>().sortingLayerID = rootCanvas.sortingLayerID; | |||
|  | 
 | |||
|  |             // Instantiate the drop-down template | |||
|  |             m_Dropdown = CreateDropdownList(m_Template.gameObject); | |||
|  |             m_Dropdown.name = "Dropdown List"; | |||
|  |             m_Dropdown.SetActive(true); | |||
|  | 
 | |||
|  |             // Make drop-down RectTransform have same values as original. | |||
|  |             RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform; | |||
|  |             dropdownRectTransform.SetParent(m_Template.transform.parent, false); | |||
|  | 
 | |||
|  |             // Instantiate the drop-down list items | |||
|  | 
 | |||
|  |             // Find the dropdown item and disable it. | |||
|  |             DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>(); | |||
|  | 
 | |||
|  |             GameObject content = itemTemplate.rectTransform.parent.gameObject; | |||
|  |             RectTransform contentRectTransform = content.transform as RectTransform; | |||
|  |             itemTemplate.rectTransform.gameObject.SetActive(true); | |||
|  | 
 | |||
|  |             // Get the rects of the dropdown and item | |||
|  |             Rect dropdownContentRect = contentRectTransform.rect; | |||
|  |             Rect itemTemplateRect = itemTemplate.rectTransform.rect; | |||
|  | 
 | |||
|  |             // Calculate the visual offset between the item's edges and the background's edges | |||
|  |             Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition; | |||
|  |             Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition; | |||
|  |             Vector2 itemSize = itemTemplateRect.size; | |||
|  | 
 | |||
|  |             m_Items.Clear(); | |||
|  | 
 | |||
|  |             Toggle prev = null; | |||
|  |             if (m_MultiSelect && options.Count > 0) | |||
|  |             { | |||
|  |                 DropdownItem item = AddItem(k_NothingOption, value == 0, itemTemplate, m_Items); | |||
|  |                 if (item.image != null) | |||
|  |                     item.image.gameObject.SetActive(false); | |||
|  | 
 | |||
|  |                 Toggle nothingToggle = item.toggle; | |||
|  |                 nothingToggle.isOn = value == 0; | |||
|  |                 nothingToggle.onValueChanged.AddListener(x => OnSelectItem(nothingToggle)); | |||
|  |                 prev = nothingToggle; | |||
|  | 
 | |||
|  |                 bool isEverythingValue = IsEverythingValue(options.Count, value); | |||
|  |                 item = AddItem(k_EverythingOption, isEverythingValue, itemTemplate, m_Items); | |||
|  |                 if (item.image != null) | |||
|  |                     item.image.gameObject.SetActive(false); | |||
|  | 
 | |||
|  |                 Toggle everythingToggle = item.toggle; | |||
|  |                 everythingToggle.isOn = isEverythingValue; | |||
|  |                 everythingToggle.onValueChanged.AddListener(x => OnSelectItem(everythingToggle)); | |||
|  | 
 | |||
|  |                 // Automatically set up explicit navigation | |||
|  |                 if (prev != null) | |||
|  |                 { | |||
|  |                     Navigation prevNav = prev.navigation; | |||
|  |                     Navigation toggleNav = item.toggle.navigation; | |||
|  |                     prevNav.mode = Navigation.Mode.Explicit; | |||
|  |                     toggleNav.mode = Navigation.Mode.Explicit; | |||
|  | 
 | |||
|  |                     prevNav.selectOnDown = item.toggle; | |||
|  |                     prevNav.selectOnRight = item.toggle; | |||
|  |                     toggleNav.selectOnLeft = prev; | |||
|  |                     toggleNav.selectOnUp = prev; | |||
|  | 
 | |||
|  |                     prev.navigation = prevNav; | |||
|  |                     item.toggle.navigation = toggleNav; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             for (int i = 0; i < options.Count; ++i) | |||
|  |             { | |||
|  |                 OptionData data = options[i]; | |||
|  |                 DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items); | |||
|  |                 if (item == null) | |||
|  |                     continue; | |||
|  | 
 | |||
|  |                 // Automatically set up a toggle state change listener | |||
|  |                 if (m_MultiSelect) | |||
|  |                     item.toggle.isOn = (value & (1 << i)) != 0; | |||
|  |                 else | |||
|  |                     item.toggle.isOn = value == i; | |||
|  | 
 | |||
|  |                 item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle)); | |||
|  | 
 | |||
|  |                 // Select current option | |||
|  |                 if (item.toggle.isOn) | |||
|  |                     item.toggle.Select(); | |||
|  | 
 | |||
|  |                 // Automatically set up explicit navigation | |||
|  |                 if (prev != null) | |||
|  |                 { | |||
|  |                     Navigation prevNav = prev.navigation; | |||
|  |                     Navigation toggleNav = item.toggle.navigation; | |||
|  |                     prevNav.mode = Navigation.Mode.Explicit; | |||
|  |                     toggleNav.mode = Navigation.Mode.Explicit; | |||
|  | 
 | |||
|  |                     prevNav.selectOnDown = item.toggle; | |||
|  |                     prevNav.selectOnRight = item.toggle; | |||
|  |                     toggleNav.selectOnLeft = prev; | |||
|  |                     toggleNav.selectOnUp = prev; | |||
|  | 
 | |||
|  |                     prev.navigation = prevNav; | |||
|  |                     item.toggle.navigation = toggleNav; | |||
|  |                 } | |||
|  |                 prev = item.toggle; | |||
|  |             } | |||
|  | 
 | |||
|  |             // Reposition all items now that all of them have been added | |||
|  |             Vector2 sizeDelta = contentRectTransform.sizeDelta; | |||
|  |             sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y; | |||
|  |             contentRectTransform.sizeDelta = sizeDelta; | |||
|  | 
 | |||
|  |             float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height; | |||
|  |             if (extraSpace > 0) | |||
|  |                 dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace); | |||
|  | 
 | |||
|  |             // Invert anchoring and position if dropdown is partially or fully outside of canvas rect. | |||
|  |             // Typically this will have the effect of placing the dropdown above the button instead of below, | |||
|  |             // but it works as inversion regardless of initial setup. | |||
|  |             Vector3[] corners = new Vector3[4]; | |||
|  |             dropdownRectTransform.GetWorldCorners(corners); | |||
|  | 
 | |||
|  |             RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform; | |||
|  |             Rect rootCanvasRect = rootCanvasRectTransform.rect; | |||
|  |             for (int axis = 0; axis < 2; axis++) | |||
|  |             { | |||
|  |                 bool outside = false; | |||
|  |                 for (int i = 0; i < 4; i++) | |||
|  |                 { | |||
|  |                     Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]); | |||
|  |                     if ((corner[axis] < rootCanvasRect.min[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.min[axis])) || | |||
|  |                         (corner[axis] > rootCanvasRect.max[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.max[axis]))) | |||
|  |                     { | |||
|  |                         outside = true; | |||
|  |                         break; | |||
|  |                     } | |||
|  |                 } | |||
|  |                 if (outside) | |||
|  |                     RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false); | |||
|  |             } | |||
|  | 
 | |||
|  |             for (int i = 0; i < m_Items.Count; i++) | |||
|  |             { | |||
|  |                 RectTransform itemRect = m_Items[i].rectTransform; | |||
|  |                 itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0); | |||
|  |                 itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0); | |||
|  |                 itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y); | |||
|  |                 itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y); | |||
|  |             } | |||
|  | 
 | |||
|  |             // Fade in the popup | |||
|  |             AlphaFadeList(m_AlphaFadeSpeed, 0f, 1f); | |||
|  | 
 | |||
|  |             // Make drop-down template and item template inactive | |||
|  |             m_Template.gameObject.SetActive(false); | |||
|  |             itemTemplate.gameObject.SetActive(false); | |||
|  | 
 | |||
|  |             m_Blocker = CreateBlocker(rootCanvas); | |||
|  |         } | |||
|  | 
 | |||
|  |         static bool IsEverythingValue(int count, int value) | |||
|  |         { | |||
|  |             var result = true; | |||
|  |             for (var i = 0; i < count; i++) | |||
|  |             { | |||
|  |                 if ((value & 1 << i) == 0) | |||
|  |                     result = false; | |||
|  |             } | |||
|  | 
 | |||
|  |             return result; | |||
|  |         } | |||
|  | 
 | |||
|  |         static int EverythingValue(int count) | |||
|  |         { | |||
|  |             int result = 0; | |||
|  |             for (var i = 0; i < count; i++) | |||
|  |             { | |||
|  |                 result |= 1 << i; | |||
|  |             } | |||
|  | 
 | |||
|  |             return result; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Create a blocker that blocks clicks to other controls while the dropdown list is open. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to obtain a blocker GameObject. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="rootCanvas">The root canvas the dropdown is under.</param> | |||
|  |         /// <returns>The created blocker object</returns> | |||
|  |         protected virtual GameObject CreateBlocker(Canvas rootCanvas) | |||
|  |         { | |||
|  |             // Create blocker GameObject. | |||
|  |             GameObject blocker = new GameObject("Blocker"); | |||
|  | 
 | |||
|  |             // Set the game object layer to match the Canvas' game object layer, as not doing this can lead to issues | |||
|  |             // especially in XR applications like PolySpatial on VisionOS (UUM-62470). | |||
|  |             blocker.layer = rootCanvas.gameObject.layer; | |||
|  | 
 | |||
|  |             // Setup blocker RectTransform to cover entire root canvas area. | |||
|  |             RectTransform blockerRect = blocker.AddComponent<RectTransform>(); | |||
|  |             blockerRect.SetParent(rootCanvas.transform, false); | |||
|  |             blockerRect.anchorMin = Vector3.zero; | |||
|  |             blockerRect.anchorMax = Vector3.one; | |||
|  |             blockerRect.sizeDelta = Vector2.zero; | |||
|  | 
 | |||
|  |             // Make blocker be in separate canvas in same layer as dropdown and in layer just below it. | |||
|  |             Canvas blockerCanvas = blocker.AddComponent<Canvas>(); | |||
|  |             blockerCanvas.overrideSorting = true; | |||
|  |             Canvas dropdownCanvas = m_Dropdown.GetComponent<Canvas>(); | |||
|  |             blockerCanvas.sortingLayerID = dropdownCanvas.sortingLayerID; | |||
|  |             blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1; | |||
|  | 
 | |||
|  |             // Find the Canvas that this dropdown is a part of | |||
|  |             Canvas parentCanvas = null; | |||
|  |             Transform parentTransform = m_Template.parent; | |||
|  |             while (parentTransform != null) | |||
|  |             { | |||
|  |                 parentCanvas = parentTransform.GetComponent<Canvas>(); | |||
|  |                 if (parentCanvas != null) | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 parentTransform = parentTransform.parent; | |||
|  |             } | |||
|  | 
 | |||
|  |             // If we have a parent canvas, apply the same raycasters as the parent for consistency. | |||
|  |             if (parentCanvas != null) | |||
|  |             { | |||
|  |                 Component[] components = parentCanvas.GetComponents<BaseRaycaster>(); | |||
|  |                 for (int i = 0; i < components.Length; i++) | |||
|  |                 { | |||
|  |                     Type raycasterType = components[i].GetType(); | |||
|  |                     if (blocker.GetComponent(raycasterType) == null) | |||
|  |                     { | |||
|  |                         blocker.AddComponent(raycasterType); | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 // Add raycaster since it's needed to block. | |||
|  |                 GetOrAddComponent<GraphicRaycaster>(blocker); | |||
|  |             } | |||
|  | 
 | |||
|  | 
 | |||
|  |             // Add image since it's needed to block, but make it clear. | |||
|  |             Image blockerImage = blocker.AddComponent<Image>(); | |||
|  |             blockerImage.color = Color.clear; | |||
|  | 
 | |||
|  |             // Add button since it's needed to block, and to close the dropdown when blocking area is clicked. | |||
|  |             Button blockerButton = blocker.AddComponent<Button>(); | |||
|  |             blockerButton.onClick.AddListener(Hide); | |||
|  | 
 | |||
|  |             //add canvas group to ensure clicking outside the dropdown will hide it (UUM-33691) | |||
|  |             CanvasGroup blockerCanvasGroup = blocker.AddComponent<CanvasGroup>(); | |||
|  |             blockerCanvasGroup.ignoreParentGroups = true; | |||
|  | 
 | |||
|  |             return blocker; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Convenience method to explicitly destroy the previously generated blocker object | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to dispose of a blocker GameObject that blocks clicks to other controls while the dropdown list is open. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="blocker">The blocker object to destroy.</param> | |||
|  |         protected virtual void DestroyBlocker(GameObject blocker) | |||
|  |         { | |||
|  |             Destroy(blocker); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Create the dropdown list to be shown when the dropdown is clicked. The dropdown list should correspond to the provided template GameObject, equivalent to instantiating a copy of it. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to obtain a dropdown list GameObject. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="template">The template to create the dropdown list from.</param> | |||
|  |         /// <returns>The created drop down list gameobject.</returns> | |||
|  |         protected virtual GameObject CreateDropdownList(GameObject template) | |||
|  |         { | |||
|  |             return (GameObject)Instantiate(template); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Convenience method to explicitly destroy the previously generated dropdown list | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to dispose of a dropdown list GameObject. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="dropdownList">The dropdown list GameObject to destroy</param> | |||
|  |         protected virtual void DestroyDropdownList(GameObject dropdownList) | |||
|  |         { | |||
|  |             Destroy(dropdownList); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Create a dropdown item based upon the item template. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to obtain an option item. | |||
|  |         /// The option item should correspond to the provided template DropdownItem and its GameObject, equivalent to instantiating a copy of it. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="itemTemplate">e template to create the option item from.</param> | |||
|  |         /// <returns>The created dropdown item component</returns> | |||
|  |         protected virtual DropdownItem CreateItem(DropdownItem itemTemplate) | |||
|  |         { | |||
|  |             return (DropdownItem)Instantiate(itemTemplate); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         ///  Convenience method to explicitly destroy the previously generated Items. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Override this method to implement a different way to dispose of an option item. | |||
|  |         /// Likely no action needed since destroying the dropdown list destroys all contained items as well. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="item">The Item to destroy.</param> | |||
|  |         protected virtual void DestroyItem(DropdownItem item) { } | |||
|  | 
 | |||
|  |         // Add a new drop-down list item with the specified values. | |||
|  |         private DropdownItem AddItem(OptionData data, bool selected, DropdownItem itemTemplate, List<DropdownItem> items) | |||
|  |         { | |||
|  |             // Add a new item to the dropdown. | |||
|  |             DropdownItem item = CreateItem(itemTemplate); | |||
|  |             item.rectTransform.SetParent(itemTemplate.rectTransform.parent, false); | |||
|  | 
 | |||
|  |             item.gameObject.SetActive(true); | |||
|  |             item.gameObject.name = "Item " + items.Count + (data.text != null ? ": " + data.text : ""); | |||
|  | 
 | |||
|  |             if (item.toggle != null) | |||
|  |             { | |||
|  |                 item.toggle.isOn = false; | |||
|  |             } | |||
|  | 
 | |||
|  |             // Set the item's data | |||
|  |             if (item.text) | |||
|  |                 item.text.text = data.text; | |||
|  | 
 | |||
|  |             if (item.image) | |||
|  |             { | |||
|  |                 item.image.sprite = data.image; | |||
|  |                 item.image.color = data.color; | |||
|  |                 item.image.enabled = (item.image.sprite != null && data.color.a > 0); | |||
|  |             } | |||
|  | 
 | |||
|  |             items.Add(item); | |||
|  |             return item; | |||
|  |         } | |||
|  | 
 | |||
|  |         private void AlphaFadeList(float duration, float alpha) | |||
|  |         { | |||
|  |             CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); | |||
|  |             AlphaFadeList(duration, group.alpha, alpha); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void AlphaFadeList(float duration, float start, float end) | |||
|  |         { | |||
|  |             if (end.Equals(start)) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             FloatTween tween = new FloatTween { duration = duration, startValue = start, targetValue = end }; | |||
|  |             tween.AddOnChangedCallback(SetAlpha); | |||
|  |             tween.ignoreTimeScale = true; | |||
|  |             m_AlphaTweenRunner.StartTween(tween); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void SetAlpha(float alpha) | |||
|  |         { | |||
|  |             if (!m_Dropdown) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); | |||
|  |             group.alpha = alpha; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Hide the dropdown list. I.e. close it. | |||
|  |         /// </summary> | |||
|  |         public void Hide() | |||
|  |         { | |||
|  |             if (m_Coroutine == null) | |||
|  |             { | |||
|  |                 if (m_Dropdown != null) | |||
|  |                 { | |||
|  |                     AlphaFadeList(m_AlphaFadeSpeed, 0f); | |||
|  | 
 | |||
|  |                     // User could have disabled the dropdown during the OnValueChanged call. | |||
|  |                     if (IsActive()) | |||
|  |                         m_Coroutine = StartCoroutine(DelayedDestroyDropdownList(m_AlphaFadeSpeed)); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (m_Blocker != null) | |||
|  |                     DestroyBlocker(m_Blocker); | |||
|  | 
 | |||
|  |                 m_Blocker = null; | |||
|  |                 Select(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private IEnumerator DelayedDestroyDropdownList(float delay) | |||
|  |         { | |||
|  |             yield return new WaitForSecondsRealtime(delay); | |||
|  |             ImmediateDestroyDropdownList(); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void ImmediateDestroyDropdownList() | |||
|  |         { | |||
|  |             for (int i = 0; i < m_Items.Count; i++) | |||
|  |             { | |||
|  |                 if (m_Items[i] != null) | |||
|  |                     DestroyItem(m_Items[i]); | |||
|  |             } | |||
|  | 
 | |||
|  |             m_Items.Clear(); | |||
|  | 
 | |||
|  |             if (m_Dropdown != null) | |||
|  |                 DestroyDropdownList(m_Dropdown); | |||
|  | 
 | |||
|  |             if (m_AlphaTweenRunner != null) | |||
|  |                 m_AlphaTweenRunner.StopTween(); | |||
|  | 
 | |||
|  |             m_Dropdown = null; | |||
|  |             m_Coroutine = null; | |||
|  |         } | |||
|  | 
 | |||
|  |         // Change the value and hide the dropdown. | |||
|  |         private void OnSelectItem(Toggle toggle) | |||
|  |         { | |||
|  |             int selectedIndex = -1; | |||
|  |             Transform tr = toggle.transform; | |||
|  |             Transform parent = tr.parent; | |||
|  |             for (int i = 1; i < parent.childCount; i++) | |||
|  |             { | |||
|  |                 if (parent.GetChild(i) == tr) | |||
|  |                 { | |||
|  |                     // Subtract one to account for template child. | |||
|  |                     selectedIndex = i - 1; | |||
|  |                     break; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             if (selectedIndex < 0) | |||
|  |                 return; | |||
|  | 
 | |||
|  |             if (m_MultiSelect) | |||
|  |             { | |||
|  |                 switch (selectedIndex) | |||
|  |                 { | |||
|  |                     case 0: // Nothing | |||
|  |                         value = 0; | |||
|  |                         for (var i = 3; i < parent.childCount; i++) | |||
|  |                         { | |||
|  |                             var toggleComponent = parent.GetChild(i).GetComponentInChildren<Toggle>(); | |||
|  |                             if (toggleComponent) | |||
|  |                                 toggleComponent.SetIsOnWithoutNotify(false); | |||
|  |                         } | |||
|  | 
 | |||
|  |                         toggle.isOn = true; | |||
|  |                         break; | |||
|  |                     case 1: // Everything | |||
|  |                         value = EverythingValue(options.Count); | |||
|  |                         for (var i = 3; i < parent.childCount; i++) | |||
|  |                         { | |||
|  |                             var toggleComponent = parent.GetChild(i).GetComponentInChildren<Toggle>(); | |||
|  |                             if (toggleComponent) | |||
|  |                                 toggleComponent.SetIsOnWithoutNotify(i > 2); | |||
|  |                         } | |||
|  |                         break; | |||
|  |                     default: | |||
|  |                         var flagValue = 1 << (selectedIndex - 2); | |||
|  |                         var wasSelected = (value & flagValue) != 0; | |||
|  |                         toggle.SetIsOnWithoutNotify(!wasSelected); | |||
|  | 
 | |||
|  |                         if (wasSelected) | |||
|  |                             value &= ~flagValue; | |||
|  |                         else | |||
|  |                             value |= flagValue; | |||
|  | 
 | |||
|  |                         break; | |||
|  |                 } | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 if (!toggle.isOn) | |||
|  |                     toggle.SetIsOnWithoutNotify(true); | |||
|  | 
 | |||
|  |                 value = selectedIndex; | |||
|  |             } | |||
|  | 
 | |||
|  |             Hide(); | |||
|  |         } | |||
|  | 
 | |||
|  |         static int FirstActiveFlagIndex(int value) | |||
|  |         { | |||
|  |             if (value == 0) | |||
|  |                 return 0; | |||
|  | 
 | |||
|  |             const int bits = sizeof(int) * 8; | |||
|  |             for (var i = 0; i < bits; i++) | |||
|  |                 if ((value & 1 << i) != 0) | |||
|  |                     return i; | |||
|  | 
 | |||
|  |             return 0; | |||
|  |         } | |||
|  |     } | |||
|  | } |