12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160 |
- 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;
- /// <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; } }
- 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>
- public OptionData(string text, Sprite image)
- {
- this.text = text;
- this.image = image;
- }
- }
- [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> { }
- // 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;
- [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;
- 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; } }
- protected TMP_Dropdown() { }
- protected override void Awake()
- {
- //#if UNITY_EDITOR
- // if (!Application.isPlaying)
- // return;
- //#endif
- m_AlphaTweenRunner = new TweenRunner<FloatTween>();
- m_AlphaTweenRunner.Init(this);
- if (m_CaptionImage)
- m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
- if (m_Template)
- m_Template.gameObject.SetActive(false);
- }
- protected override void Start()
- {
- 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 && 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)
- {
- if (data != null)
- m_CaptionImage.sprite = data.image;
- else
- m_CaptionImage.sprite = null;
- m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
- }
- 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;
- 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
- 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);
- }
- /// <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");
- // 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);
- 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.enabled = (item.image.sprite != null);
- }
- 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)
- {
- if (!toggle.isOn)
- toggle.isOn = true;
- int selectedIndex = -1;
- Transform tr = toggle.transform;
- Transform parent = tr.parent;
- for (int i = 0; i < parent.childCount; i++)
- {
- if (parent.GetChild(i) == tr)
- {
- // Subtract one to account for template child.
- selectedIndex = i - 1;
- break;
- }
- }
- if (selectedIndex < 0)
- return;
- value = selectedIndex;
- Hide();
- }
- }
- }
|