TMP_Dropdown.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.UI;
  6. using UnityEngine.Events;
  7. using UnityEngine.EventSystems;
  8. using UnityEngine.UI.CoroutineTween;
  9. namespace TMPro
  10. {
  11. [AddComponentMenu("UI/Dropdown - TextMeshPro", 35)]
  12. [RequireComponent(typeof(RectTransform))]
  13. /// <summary>
  14. /// A standard dropdown that presents a list of options when clicked, of which one can be chosen.
  15. /// </summary>
  16. /// <remarks>
  17. /// 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.
  18. ///
  19. /// When a dropdown event occurs a callback is sent to any registered listeners of onValueChanged.
  20. /// </remarks>
  21. public class TMP_Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler
  22. {
  23. protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler
  24. {
  25. [SerializeField]
  26. private TMP_Text m_Text;
  27. [SerializeField]
  28. private Image m_Image;
  29. [SerializeField]
  30. private RectTransform m_RectTransform;
  31. [SerializeField]
  32. private Toggle m_Toggle;
  33. public TMP_Text text { get { return m_Text; } set { m_Text = value; } }
  34. public Image image { get { return m_Image; } set { m_Image = value; } }
  35. public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } }
  36. public Toggle toggle { get { return m_Toggle; } set { m_Toggle = value; } }
  37. public virtual void OnPointerEnter(PointerEventData eventData)
  38. {
  39. EventSystem.current.SetSelectedGameObject(gameObject);
  40. }
  41. public virtual void OnCancel(BaseEventData eventData)
  42. {
  43. TMP_Dropdown dropdown = GetComponentInParent<TMP_Dropdown>();
  44. if (dropdown)
  45. dropdown.Hide();
  46. }
  47. }
  48. [Serializable]
  49. /// <summary>
  50. /// Class to store the text and/or image of a single option in the dropdown list.
  51. /// </summary>
  52. public class OptionData
  53. {
  54. [SerializeField]
  55. private string m_Text;
  56. [SerializeField]
  57. private Sprite m_Image;
  58. /// <summary>
  59. /// The text associated with the option.
  60. /// </summary>
  61. public string text { get { return m_Text; } set { m_Text = value; } }
  62. /// <summary>
  63. /// The image associated with the option.
  64. /// </summary>
  65. public Sprite image { get { return m_Image; } set { m_Image = value; } }
  66. public OptionData() { }
  67. public OptionData(string text)
  68. {
  69. this.text = text;
  70. }
  71. public OptionData(Sprite image)
  72. {
  73. this.image = image;
  74. }
  75. /// <summary>
  76. /// Create an object representing a single option for the dropdown list.
  77. /// </summary>
  78. /// <param name="text">Optional text for the option.</param>
  79. /// <param name="image">Optional image for the option.</param>
  80. public OptionData(string text, Sprite image)
  81. {
  82. this.text = text;
  83. this.image = image;
  84. }
  85. }
  86. [Serializable]
  87. /// <summary>
  88. /// Class used internally to store the list of options for the dropdown list.
  89. /// </summary>
  90. /// <remarks>
  91. /// The usage of this class is not exposed in the runtime API. It's only relevant for the PropertyDrawer drawing the list of options.
  92. /// </remarks>
  93. public class OptionDataList
  94. {
  95. [SerializeField]
  96. private List<OptionData> m_Options;
  97. /// <summary>
  98. /// The list of options for the dropdown list.
  99. /// </summary>
  100. public List<OptionData> options { get { return m_Options; } set { m_Options = value; } }
  101. public OptionDataList()
  102. {
  103. options = new List<OptionData>();
  104. }
  105. }
  106. [Serializable]
  107. /// <summary>
  108. /// UnityEvent callback for when a dropdown current option is changed.
  109. /// </summary>
  110. public class DropdownEvent : UnityEvent<int> { }
  111. // Template used to create the dropdown.
  112. [SerializeField]
  113. private RectTransform m_Template;
  114. /// <summary>
  115. /// The Rect Transform of the template for the dropdown list.
  116. /// </summary>
  117. public RectTransform template { get { return m_Template; } set { m_Template = value; RefreshShownValue(); } }
  118. // Text to be used as a caption for the current value. It's not required, but it's kept here for convenience.
  119. [SerializeField]
  120. private TMP_Text m_CaptionText;
  121. /// <summary>
  122. /// The Text component to hold the text of the currently selected option.
  123. /// </summary>
  124. public TMP_Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } }
  125. [SerializeField]
  126. private Image m_CaptionImage;
  127. /// <summary>
  128. /// The Image component to hold the image of the currently selected option.
  129. /// </summary>
  130. public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } }
  131. [SerializeField]
  132. private Graphic m_Placeholder;
  133. /// <summary>
  134. /// The placeholder Graphic component. Shown when no option is selected.
  135. /// </summary>
  136. public Graphic placeholder { get { return m_Placeholder; } set { m_Placeholder = value; RefreshShownValue(); } }
  137. [Space]
  138. [SerializeField]
  139. private TMP_Text m_ItemText;
  140. /// <summary>
  141. /// The Text component to hold the text of the item.
  142. /// </summary>
  143. public TMP_Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } }
  144. [SerializeField]
  145. private Image m_ItemImage;
  146. /// <summary>
  147. /// The Image component to hold the image of the item
  148. /// </summary>
  149. public Image itemImage { get { return m_ItemImage; } set { m_ItemImage = value; RefreshShownValue(); } }
  150. [Space]
  151. [SerializeField]
  152. private int m_Value;
  153. [Space]
  154. // Items that will be visible when the dropdown is shown.
  155. // We box this into its own class so we can use a Property Drawer for it.
  156. [SerializeField]
  157. private OptionDataList m_Options = new OptionDataList();
  158. /// <summary>
  159. /// The list of possible options. A text string and an image can be specified for each option.
  160. /// </summary>
  161. /// <remarks>
  162. /// 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.
  163. /// This also unlocks the ability to edit the Dropdown, including the insertion, removal, and finding of options, as well as other useful tools
  164. /// </remarks>
  165. /// /// <example>
  166. /// <code>
  167. /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject.
  168. ///
  169. /// using UnityEngine;
  170. /// using UnityEngine.UI;
  171. /// using System.Collections.Generic;
  172. /// using TMPro;
  173. ///
  174. /// public class Example : MonoBehaviour
  175. /// {
  176. /// //Use these for adding options to the Dropdown List
  177. /// TMP_Dropdown.OptionData m_NewData, m_NewData2;
  178. /// //The list of messages for the Dropdown
  179. /// List<TMP_Dropdown.OptionData> m_Messages = new List<TMP_Dropdown.OptionData>();
  180. ///
  181. ///
  182. /// //This is the Dropdown
  183. /// TMP_Dropdown m_Dropdown;
  184. /// string m_MyString;
  185. /// int m_Index;
  186. ///
  187. /// void Start()
  188. /// {
  189. /// //Fetch the Dropdown GameObject the script is attached to
  190. /// m_Dropdown = GetComponent<TMP_Dropdown>();
  191. /// //Clear the old options of the Dropdown menu
  192. /// m_Dropdown.ClearOptions();
  193. ///
  194. /// //Create a new option for the Dropdown menu which reads "Option 1" and add to messages List
  195. /// m_NewData = new TMP_Dropdown.OptionData();
  196. /// m_NewData.text = "Option 1";
  197. /// m_Messages.Add(m_NewData);
  198. ///
  199. /// //Create a new option for the Dropdown menu which reads "Option 2" and add to messages List
  200. /// m_NewData2 = new TMP_Dropdown.OptionData();
  201. /// m_NewData2.text = "Option 2";
  202. /// m_Messages.Add(m_NewData2);
  203. ///
  204. /// //Take each entry in the message List
  205. /// foreach (TMP_Dropdown.OptionData message in m_Messages)
  206. /// {
  207. /// //Add each entry to the Dropdown
  208. /// m_Dropdown.options.Add(message);
  209. /// //Make the index equal to the total number of entries
  210. /// m_Index = m_Messages.Count - 1;
  211. /// }
  212. /// }
  213. ///
  214. /// //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.
  215. /// void OnGUI()
  216. /// {
  217. /// //TextField for user to type new entry to add to Dropdown
  218. /// m_MyString = GUI.TextField(new Rect(0, 40, 100, 40), m_MyString);
  219. ///
  220. /// //Press the "Add" Button to add a new entry to the Dropdown
  221. /// if (GUI.Button(new Rect(0, 0, 100, 40), "Add"))
  222. /// {
  223. /// //Make the index the last number of entries
  224. /// m_Index = m_Messages.Count;
  225. /// //Create a temporary option
  226. /// TMP_Dropdown.OptionData temp = new TMP_Dropdown.OptionData();
  227. /// //Make the option the data from the TextField
  228. /// temp.text = m_MyString;
  229. ///
  230. /// //Update the messages list with the TextField data
  231. /// m_Messages.Add(temp);
  232. ///
  233. /// //Add the Textfield data to the Dropdown
  234. /// m_Dropdown.options.Insert(m_Index, temp);
  235. /// }
  236. ///
  237. /// //Press the "Remove" button to delete the selected option
  238. /// if (GUI.Button(new Rect(110, 0, 100, 40), "Remove"))
  239. /// {
  240. /// //Remove the current selected item from the Dropdown from the messages List
  241. /// m_Messages.RemoveAt(m_Dropdown.value);
  242. /// //Remove the current selection from the Dropdown
  243. /// m_Dropdown.options.RemoveAt(m_Dropdown.value);
  244. /// }
  245. /// }
  246. /// }
  247. /// </code>
  248. /// </example>
  249. public List<OptionData> options
  250. {
  251. get { return m_Options.options; }
  252. set { m_Options.options = value; RefreshShownValue(); }
  253. }
  254. [Space]
  255. // Notification triggered when the dropdown changes.
  256. [SerializeField]
  257. private DropdownEvent m_OnValueChanged = new DropdownEvent();
  258. /// <summary>
  259. /// A UnityEvent that is invoked when a user has clicked one of the options in the dropdown list.
  260. /// </summary>
  261. /// <remarks>
  262. /// 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.
  263. /// </remarks>
  264. /// <example>
  265. /// <code>
  266. /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject.
  267. /// //Set your own Text in the Inspector window
  268. ///
  269. /// using UnityEngine;
  270. /// using UnityEngine.UI;
  271. /// using TMPro;
  272. ///
  273. /// public class Example : MonoBehaviour
  274. /// {
  275. /// TMP_Dropdown m_Dropdown;
  276. /// public Text m_Text;
  277. ///
  278. /// void Start()
  279. /// {
  280. /// //Fetch the Dropdown GameObject
  281. /// m_Dropdown = GetComponent<TMP_Dropdown>();
  282. /// //Add listener for when the value of the Dropdown changes, to take action
  283. /// m_Dropdown.onValueChanged.AddListener(delegate {
  284. /// DropdownValueChanged(m_Dropdown);
  285. /// });
  286. ///
  287. /// //Initialize the Text to say the first value of the Dropdown
  288. /// m_Text.text = "First Value : " + m_Dropdown.value;
  289. /// }
  290. ///
  291. /// //Output the new value of the Dropdown into Text
  292. /// void DropdownValueChanged(TMP_Dropdown change)
  293. /// {
  294. /// m_Text.text = "New Value : " + change.value;
  295. /// }
  296. /// }
  297. /// </code>
  298. /// </example>
  299. public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
  300. [SerializeField]
  301. private float m_AlphaFadeSpeed = 0.15f;
  302. /// <summary>
  303. /// The time interval at which a drop down will appear and disappear
  304. /// </summary>
  305. public float alphaFadeSpeed { get { return m_AlphaFadeSpeed; } set { m_AlphaFadeSpeed = value; } }
  306. private GameObject m_Dropdown;
  307. private GameObject m_Blocker;
  308. private List<DropdownItem> m_Items = new List<DropdownItem>();
  309. private TweenRunner<FloatTween> m_AlphaTweenRunner;
  310. private bool validTemplate = false;
  311. private Coroutine m_Coroutine = null;
  312. private static OptionData s_NoOptionData = new OptionData();
  313. /// <summary>
  314. /// 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.
  315. /// </summary>
  316. /// <example>
  317. /// <code>
  318. /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject.
  319. /// //Set your own Text in the Inspector window
  320. ///
  321. /// using UnityEngine;
  322. /// using UnityEngine.UI;
  323. /// using TMPro;
  324. ///
  325. /// public class Example : MonoBehaviour
  326. /// {
  327. /// //Attach this script to a Dropdown GameObject
  328. /// TMP_Dropdown m_Dropdown;
  329. /// //This is the string that stores the current selection m_Text of the Dropdown
  330. /// string m_Message;
  331. /// //This Text outputs the current selection to the screen
  332. /// public Text m_Text;
  333. /// //This is the index value of the Dropdown
  334. /// int m_DropdownValue;
  335. ///
  336. /// void Start()
  337. /// {
  338. /// //Fetch the DropDown component from the GameObject
  339. /// m_Dropdown = GetComponent<TMP_Dropdown>();
  340. /// //Output the first Dropdown index value
  341. /// Debug.Log("Starting Dropdown Value : " + m_Dropdown.value);
  342. /// }
  343. ///
  344. /// void Update()
  345. /// {
  346. /// //Keep the current index of the Dropdown in a variable
  347. /// m_DropdownValue = m_Dropdown.value;
  348. /// //Change the message to say the name of the current Dropdown selection using the value
  349. /// m_Message = m_Dropdown.options[m_DropdownValue].text;
  350. /// //Change the on screen Text to reflect the current Dropdown selection
  351. /// m_Text.text = m_Message;
  352. /// }
  353. /// }
  354. /// </code>
  355. /// </example>
  356. public int value
  357. {
  358. get
  359. {
  360. return m_Value;
  361. }
  362. set
  363. {
  364. SetValue(value);
  365. }
  366. }
  367. /// <summary>
  368. /// Set index number of the current selection in the Dropdown without invoking onValueChanged callback.
  369. /// </summary>
  370. /// <param name="input">The new index for the current selection.</param>
  371. public void SetValueWithoutNotify(int input)
  372. {
  373. SetValue(input, false);
  374. }
  375. void SetValue(int value, bool sendCallback = true)
  376. {
  377. if (Application.isPlaying && (value == m_Value || options.Count == 0))
  378. return;
  379. m_Value = Mathf.Clamp(value, m_Placeholder ? -1 : 0, options.Count - 1);
  380. RefreshShownValue();
  381. if (sendCallback)
  382. {
  383. // Notify all listeners
  384. UISystemProfilerApi.AddMarker("Dropdown.value", this);
  385. m_OnValueChanged.Invoke(m_Value);
  386. }
  387. }
  388. public bool IsExpanded { get { return m_Dropdown != null; } }
  389. protected TMP_Dropdown() { }
  390. protected override void Awake()
  391. {
  392. //#if UNITY_EDITOR
  393. // if (!Application.isPlaying)
  394. // return;
  395. //#endif
  396. m_AlphaTweenRunner = new TweenRunner<FloatTween>();
  397. m_AlphaTweenRunner.Init(this);
  398. if (m_CaptionImage)
  399. m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
  400. if (m_Template)
  401. m_Template.gameObject.SetActive(false);
  402. }
  403. protected override void Start()
  404. {
  405. base.Start();
  406. RefreshShownValue();
  407. }
  408. #if UNITY_EDITOR
  409. protected override void OnValidate()
  410. {
  411. base.OnValidate();
  412. if (!IsActive())
  413. return;
  414. RefreshShownValue();
  415. }
  416. #endif
  417. protected override void OnDisable()
  418. {
  419. //Destroy dropdown and blocker in case user deactivates the dropdown when they click an option (case 935649)
  420. ImmediateDestroyDropdownList();
  421. if (m_Blocker != null)
  422. DestroyBlocker(m_Blocker);
  423. m_Blocker = null;
  424. base.OnDisable();
  425. }
  426. /// <summary>
  427. /// Refreshes the text and image (if available) of the currently selected option.
  428. /// </summary>
  429. /// <remarks>
  430. /// 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.
  431. /// </remarks>
  432. public void RefreshShownValue()
  433. {
  434. OptionData data = s_NoOptionData;
  435. if (options.Count > 0 && m_Value >= 0)
  436. data = options[Mathf.Clamp(m_Value, 0, options.Count - 1)];
  437. if (m_CaptionText)
  438. {
  439. if (data != null && data.text != null)
  440. m_CaptionText.text = data.text;
  441. else
  442. m_CaptionText.text = "";
  443. }
  444. if (m_CaptionImage)
  445. {
  446. if (data != null)
  447. m_CaptionImage.sprite = data.image;
  448. else
  449. m_CaptionImage.sprite = null;
  450. m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
  451. }
  452. if (m_Placeholder)
  453. {
  454. m_Placeholder.enabled = options.Count == 0 || m_Value == -1;
  455. }
  456. }
  457. /// <summary>
  458. /// Add multiple options to the options of the Dropdown based on a list of OptionData objects.
  459. /// </summary>
  460. /// <param name="options">The list of OptionData to add.</param>
  461. /// /// <remarks>
  462. /// See AddOptions(List<string> options) for code example of usages.
  463. /// </remarks>
  464. public void AddOptions(List<OptionData> options)
  465. {
  466. this.options.AddRange(options);
  467. RefreshShownValue();
  468. }
  469. /// <summary>
  470. /// Add multiple text-only options to the options of the Dropdown based on a list of strings.
  471. /// </summary>
  472. /// <remarks>
  473. /// Add a List of string messages to the Dropdown. The Dropdown shows each member of the list as a separate option.
  474. /// </remarks>
  475. /// <param name="options">The list of text strings to add.</param>
  476. /// <example>
  477. /// <code>
  478. /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject.
  479. ///
  480. /// using System.Collections.Generic;
  481. /// using UnityEngine;
  482. /// using UnityEngine.UI;
  483. /// using TMPro;
  484. ///
  485. /// public class Example : MonoBehaviour
  486. /// {
  487. /// //Create a List of new Dropdown options
  488. /// List<string> m_DropOptions = new List<string> { "Option 1", "Option 2"};
  489. /// //This is the Dropdown
  490. /// TMP_Dropdown m_Dropdown;
  491. ///
  492. /// void Start()
  493. /// {
  494. /// //Fetch the Dropdown GameObject the script is attached to
  495. /// m_Dropdown = GetComponent<TMP_Dropdown>();
  496. /// //Clear the old options of the Dropdown menu
  497. /// m_Dropdown.ClearOptions();
  498. /// //Add the options created in the List above
  499. /// m_Dropdown.AddOptions(m_DropOptions);
  500. /// }
  501. /// }
  502. /// </code>
  503. /// </example>
  504. public void AddOptions(List<string> options)
  505. {
  506. for (int i = 0; i < options.Count; i++)
  507. this.options.Add(new OptionData(options[i]));
  508. RefreshShownValue();
  509. }
  510. /// <summary>
  511. /// Add multiple image-only options to the options of the Dropdown based on a list of Sprites.
  512. /// </summary>
  513. /// <param name="options">The list of Sprites to add.</param>
  514. /// <remarks>
  515. /// See AddOptions(List<string> options) for code example of usages.
  516. /// </remarks>
  517. public void AddOptions(List<Sprite> options)
  518. {
  519. for (int i = 0; i < options.Count; i++)
  520. this.options.Add(new OptionData(options[i]));
  521. RefreshShownValue();
  522. }
  523. /// <summary>
  524. /// Clear the list of options in the Dropdown.
  525. /// </summary>
  526. public void ClearOptions()
  527. {
  528. options.Clear();
  529. m_Value = m_Placeholder ? -1 : 0;
  530. RefreshShownValue();
  531. }
  532. private void SetupTemplate()
  533. {
  534. validTemplate = false;
  535. if (!m_Template)
  536. {
  537. 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);
  538. return;
  539. }
  540. GameObject templateGo = m_Template.gameObject;
  541. templateGo.SetActive(true);
  542. Toggle itemToggle = m_Template.GetComponentInChildren<Toggle>();
  543. validTemplate = true;
  544. if (!itemToggle || itemToggle.transform == template)
  545. {
  546. validTemplate = false;
  547. Debug.LogError("The dropdown template is not valid. The template must have a child GameObject with a Toggle component serving as the item.", template);
  548. }
  549. else if (!(itemToggle.transform.parent is RectTransform))
  550. {
  551. validTemplate = false;
  552. 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);
  553. }
  554. else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform))
  555. {
  556. validTemplate = false;
  557. Debug.LogError("The dropdown template is not valid. The Item Text must be on the item GameObject or children of it.", template);
  558. }
  559. else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform))
  560. {
  561. validTemplate = false;
  562. Debug.LogError("The dropdown template is not valid. The Item Image must be on the item GameObject or children of it.", template);
  563. }
  564. if (!validTemplate)
  565. {
  566. templateGo.SetActive(false);
  567. return;
  568. }
  569. DropdownItem item = itemToggle.gameObject.AddComponent<DropdownItem>();
  570. item.text = m_ItemText;
  571. item.image = m_ItemImage;
  572. item.toggle = itemToggle;
  573. item.rectTransform = (RectTransform)itemToggle.transform;
  574. // Find the Canvas that this dropdown is a part of
  575. Canvas parentCanvas = null;
  576. Transform parentTransform = m_Template.parent;
  577. while (parentTransform != null)
  578. {
  579. parentCanvas = parentTransform.GetComponent<Canvas>();
  580. if (parentCanvas != null)
  581. break;
  582. parentTransform = parentTransform.parent;
  583. }
  584. Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo);
  585. popupCanvas.overrideSorting = true;
  586. popupCanvas.sortingOrder = 30000;
  587. // If we have a parent canvas, apply the same raycasters as the parent for consistency.
  588. if (parentCanvas != null)
  589. {
  590. Component[] components = parentCanvas.GetComponents<BaseRaycaster>();
  591. for (int i = 0; i < components.Length; i++)
  592. {
  593. Type raycasterType = components[i].GetType();
  594. if (templateGo.GetComponent(raycasterType) == null)
  595. {
  596. templateGo.AddComponent(raycasterType);
  597. }
  598. }
  599. }
  600. else
  601. {
  602. GetOrAddComponent<GraphicRaycaster>(templateGo);
  603. }
  604. GetOrAddComponent<CanvasGroup>(templateGo);
  605. templateGo.SetActive(false);
  606. validTemplate = true;
  607. }
  608. private static T GetOrAddComponent<T>(GameObject go) where T : Component
  609. {
  610. T comp = go.GetComponent<T>();
  611. if (!comp)
  612. comp = go.AddComponent<T>();
  613. return comp;
  614. }
  615. /// <summary>
  616. /// Handling for when the dropdown is initially 'clicked'. Typically shows the dropdown
  617. /// </summary>
  618. /// <param name="eventData">The associated event data.</param>
  619. public virtual void OnPointerClick(PointerEventData eventData)
  620. {
  621. Show();
  622. }
  623. /// <summary>
  624. /// Handling for when the dropdown is selected and a submit event is processed. Typically shows the dropdown
  625. /// </summary>
  626. /// <param name="eventData">The associated event data.</param>
  627. public virtual void OnSubmit(BaseEventData eventData)
  628. {
  629. Show();
  630. }
  631. /// <summary>
  632. /// This will hide the dropdown list.
  633. /// </summary>
  634. /// <remarks>
  635. /// Called by a BaseInputModule when a Cancel event occurs.
  636. /// </remarks>
  637. /// <param name="eventData">The associated event data.</param>
  638. public virtual void OnCancel(BaseEventData eventData)
  639. {
  640. Hide();
  641. }
  642. /// <summary>
  643. /// Show the dropdown.
  644. ///
  645. /// Plan for dropdown scrolling to ensure dropdown is contained within screen.
  646. ///
  647. /// We assume the Canvas is the screen that the dropdown must be kept inside.
  648. /// This is always valid for screen space canvas modes.
  649. /// For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor.
  650. /// We consider it a fair constraint that the canvas must be big enough to contain dropdowns.
  651. /// </summary>
  652. public void Show()
  653. {
  654. if (m_Coroutine != null)
  655. {
  656. StopCoroutine(m_Coroutine);
  657. ImmediateDestroyDropdownList();
  658. }
  659. if (!IsActive() || !IsInteractable() || m_Dropdown != null)
  660. return;
  661. // Get root Canvas.
  662. var list = TMP_ListPool<Canvas>.Get();
  663. gameObject.GetComponentsInParent(false, list);
  664. if (list.Count == 0)
  665. return;
  666. Canvas rootCanvas = list[list.Count - 1];
  667. for (int i = 0; i < list.Count; i++)
  668. {
  669. if (list[i].isRootCanvas)
  670. {
  671. rootCanvas = list[i];
  672. break;
  673. }
  674. }
  675. TMP_ListPool<Canvas>.Release(list);
  676. if (!validTemplate)
  677. {
  678. SetupTemplate();
  679. if (!validTemplate)
  680. return;
  681. }
  682. m_Template.gameObject.SetActive(true);
  683. // 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)
  684. m_Template.GetComponent<Canvas>().sortingLayerID = rootCanvas.sortingLayerID;
  685. // Instantiate the drop-down template
  686. m_Dropdown = CreateDropdownList(m_Template.gameObject);
  687. m_Dropdown.name = "Dropdown List";
  688. m_Dropdown.SetActive(true);
  689. // Make drop-down RectTransform have same values as original.
  690. RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform;
  691. dropdownRectTransform.SetParent(m_Template.transform.parent, false);
  692. // Instantiate the drop-down list items
  693. // Find the dropdown item and disable it.
  694. DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>();
  695. GameObject content = itemTemplate.rectTransform.parent.gameObject;
  696. RectTransform contentRectTransform = content.transform as RectTransform;
  697. itemTemplate.rectTransform.gameObject.SetActive(true);
  698. // Get the rects of the dropdown and item
  699. Rect dropdownContentRect = contentRectTransform.rect;
  700. Rect itemTemplateRect = itemTemplate.rectTransform.rect;
  701. // Calculate the visual offset between the item's edges and the background's edges
  702. Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition;
  703. Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition;
  704. Vector2 itemSize = itemTemplateRect.size;
  705. m_Items.Clear();
  706. Toggle prev = null;
  707. for (int i = 0; i < options.Count; ++i)
  708. {
  709. OptionData data = options[i];
  710. DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items);
  711. if (item == null)
  712. continue;
  713. // Automatically set up a toggle state change listener
  714. item.toggle.isOn = value == i;
  715. item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle));
  716. // Select current option
  717. if (item.toggle.isOn)
  718. item.toggle.Select();
  719. // Automatically set up explicit navigation
  720. if (prev != null)
  721. {
  722. Navigation prevNav = prev.navigation;
  723. Navigation toggleNav = item.toggle.navigation;
  724. prevNav.mode = Navigation.Mode.Explicit;
  725. toggleNav.mode = Navigation.Mode.Explicit;
  726. prevNav.selectOnDown = item.toggle;
  727. prevNav.selectOnRight = item.toggle;
  728. toggleNav.selectOnLeft = prev;
  729. toggleNav.selectOnUp = prev;
  730. prev.navigation = prevNav;
  731. item.toggle.navigation = toggleNav;
  732. }
  733. prev = item.toggle;
  734. }
  735. // Reposition all items now that all of them have been added
  736. Vector2 sizeDelta = contentRectTransform.sizeDelta;
  737. sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y;
  738. contentRectTransform.sizeDelta = sizeDelta;
  739. float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height;
  740. if (extraSpace > 0)
  741. dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace);
  742. // Invert anchoring and position if dropdown is partially or fully outside of canvas rect.
  743. // Typically this will have the effect of placing the dropdown above the button instead of below,
  744. // but it works as inversion regardless of initial setup.
  745. Vector3[] corners = new Vector3[4];
  746. dropdownRectTransform.GetWorldCorners(corners);
  747. RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform;
  748. Rect rootCanvasRect = rootCanvasRectTransform.rect;
  749. for (int axis = 0; axis < 2; axis++)
  750. {
  751. bool outside = false;
  752. for (int i = 0; i < 4; i++)
  753. {
  754. Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]);
  755. if ((corner[axis] < rootCanvasRect.min[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.min[axis])) ||
  756. (corner[axis] > rootCanvasRect.max[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.max[axis])))
  757. {
  758. outside = true;
  759. break;
  760. }
  761. }
  762. if (outside)
  763. RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false);
  764. }
  765. for (int i = 0; i < m_Items.Count; i++)
  766. {
  767. RectTransform itemRect = m_Items[i].rectTransform;
  768. itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0);
  769. itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0);
  770. itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y);
  771. itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y);
  772. }
  773. // Fade in the popup
  774. AlphaFadeList(m_AlphaFadeSpeed, 0f, 1f);
  775. // Make drop-down template and item template inactive
  776. m_Template.gameObject.SetActive(false);
  777. itemTemplate.gameObject.SetActive(false);
  778. m_Blocker = CreateBlocker(rootCanvas);
  779. }
  780. /// <summary>
  781. /// Create a blocker that blocks clicks to other controls while the dropdown list is open.
  782. /// </summary>
  783. /// <remarks>
  784. /// Override this method to implement a different way to obtain a blocker GameObject.
  785. /// </remarks>
  786. /// <param name="rootCanvas">The root canvas the dropdown is under.</param>
  787. /// <returns>The created blocker object</returns>
  788. protected virtual GameObject CreateBlocker(Canvas rootCanvas)
  789. {
  790. // Create blocker GameObject.
  791. GameObject blocker = new GameObject("Blocker");
  792. // Setup blocker RectTransform to cover entire root canvas area.
  793. RectTransform blockerRect = blocker.AddComponent<RectTransform>();
  794. blockerRect.SetParent(rootCanvas.transform, false);
  795. blockerRect.anchorMin = Vector3.zero;
  796. blockerRect.anchorMax = Vector3.one;
  797. blockerRect.sizeDelta = Vector2.zero;
  798. // Make blocker be in separate canvas in same layer as dropdown and in layer just below it.
  799. Canvas blockerCanvas = blocker.AddComponent<Canvas>();
  800. blockerCanvas.overrideSorting = true;
  801. Canvas dropdownCanvas = m_Dropdown.GetComponent<Canvas>();
  802. blockerCanvas.sortingLayerID = dropdownCanvas.sortingLayerID;
  803. blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1;
  804. // Find the Canvas that this dropdown is a part of
  805. Canvas parentCanvas = null;
  806. Transform parentTransform = m_Template.parent;
  807. while (parentTransform != null)
  808. {
  809. parentCanvas = parentTransform.GetComponent<Canvas>();
  810. if (parentCanvas != null)
  811. break;
  812. parentTransform = parentTransform.parent;
  813. }
  814. // If we have a parent canvas, apply the same raycasters as the parent for consistency.
  815. if (parentCanvas != null)
  816. {
  817. Component[] components = parentCanvas.GetComponents<BaseRaycaster>();
  818. for (int i = 0; i < components.Length; i++)
  819. {
  820. Type raycasterType = components[i].GetType();
  821. if (blocker.GetComponent(raycasterType) == null)
  822. {
  823. blocker.AddComponent(raycasterType);
  824. }
  825. }
  826. }
  827. else
  828. {
  829. // Add raycaster since it's needed to block.
  830. GetOrAddComponent<GraphicRaycaster>(blocker);
  831. }
  832. // Add image since it's needed to block, but make it clear.
  833. Image blockerImage = blocker.AddComponent<Image>();
  834. blockerImage.color = Color.clear;
  835. // Add button since it's needed to block, and to close the dropdown when blocking area is clicked.
  836. Button blockerButton = blocker.AddComponent<Button>();
  837. blockerButton.onClick.AddListener(Hide);
  838. return blocker;
  839. }
  840. /// <summary>
  841. /// Convenience method to explicitly destroy the previously generated blocker object
  842. /// </summary>
  843. /// <remarks>
  844. /// 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.
  845. /// </remarks>
  846. /// <param name="blocker">The blocker object to destroy.</param>
  847. protected virtual void DestroyBlocker(GameObject blocker)
  848. {
  849. Destroy(blocker);
  850. }
  851. /// <summary>
  852. /// 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.
  853. /// </summary>
  854. /// <remarks>
  855. /// Override this method to implement a different way to obtain a dropdown list GameObject.
  856. /// </remarks>
  857. /// <param name="template">The template to create the dropdown list from.</param>
  858. /// <returns>The created drop down list gameobject.</returns>
  859. protected virtual GameObject CreateDropdownList(GameObject template)
  860. {
  861. return (GameObject)Instantiate(template);
  862. }
  863. /// <summary>
  864. /// Convenience method to explicitly destroy the previously generated dropdown list
  865. /// </summary>
  866. /// <remarks>
  867. /// Override this method to implement a different way to dispose of a dropdown list GameObject.
  868. /// </remarks>
  869. /// <param name="dropdownList">The dropdown list GameObject to destroy</param>
  870. protected virtual void DestroyDropdownList(GameObject dropdownList)
  871. {
  872. Destroy(dropdownList);
  873. }
  874. /// <summary>
  875. /// Create a dropdown item based upon the item template.
  876. /// </summary>
  877. /// <remarks>
  878. /// Override this method to implement a different way to obtain an option item.
  879. /// The option item should correspond to the provided template DropdownItem and its GameObject, equivalent to instantiating a copy of it.
  880. /// </remarks>
  881. /// <param name="itemTemplate">e template to create the option item from.</param>
  882. /// <returns>The created dropdown item component</returns>
  883. protected virtual DropdownItem CreateItem(DropdownItem itemTemplate)
  884. {
  885. return (DropdownItem)Instantiate(itemTemplate);
  886. }
  887. /// <summary>
  888. /// Convenience method to explicitly destroy the previously generated Items.
  889. /// </summary>
  890. /// <remarks>
  891. /// Override this method to implement a different way to dispose of an option item.
  892. /// Likely no action needed since destroying the dropdown list destroys all contained items as well.
  893. /// </remarks>
  894. /// <param name="item">The Item to destroy.</param>
  895. protected virtual void DestroyItem(DropdownItem item) { }
  896. // Add a new drop-down list item with the specified values.
  897. private DropdownItem AddItem(OptionData data, bool selected, DropdownItem itemTemplate, List<DropdownItem> items)
  898. {
  899. // Add a new item to the dropdown.
  900. DropdownItem item = CreateItem(itemTemplate);
  901. item.rectTransform.SetParent(itemTemplate.rectTransform.parent, false);
  902. item.gameObject.SetActive(true);
  903. item.gameObject.name = "Item " + items.Count + (data.text != null ? ": " + data.text : "");
  904. if (item.toggle != null)
  905. {
  906. item.toggle.isOn = false;
  907. }
  908. // Set the item's data
  909. if (item.text)
  910. item.text.text = data.text;
  911. if (item.image)
  912. {
  913. item.image.sprite = data.image;
  914. item.image.enabled = (item.image.sprite != null);
  915. }
  916. items.Add(item);
  917. return item;
  918. }
  919. private void AlphaFadeList(float duration, float alpha)
  920. {
  921. CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>();
  922. AlphaFadeList(duration, group.alpha, alpha);
  923. }
  924. private void AlphaFadeList(float duration, float start, float end)
  925. {
  926. if (end.Equals(start))
  927. return;
  928. FloatTween tween = new FloatTween { duration = duration, startValue = start, targetValue = end };
  929. tween.AddOnChangedCallback(SetAlpha);
  930. tween.ignoreTimeScale = true;
  931. m_AlphaTweenRunner.StartTween(tween);
  932. }
  933. private void SetAlpha(float alpha)
  934. {
  935. if (!m_Dropdown)
  936. return;
  937. CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>();
  938. group.alpha = alpha;
  939. }
  940. /// <summary>
  941. /// Hide the dropdown list. I.e. close it.
  942. /// </summary>
  943. public void Hide()
  944. {
  945. if (m_Coroutine == null)
  946. {
  947. if (m_Dropdown != null)
  948. {
  949. AlphaFadeList(m_AlphaFadeSpeed, 0f);
  950. // User could have disabled the dropdown during the OnValueChanged call.
  951. if (IsActive())
  952. m_Coroutine = StartCoroutine(DelayedDestroyDropdownList(m_AlphaFadeSpeed));
  953. }
  954. if (m_Blocker != null)
  955. DestroyBlocker(m_Blocker);
  956. m_Blocker = null;
  957. Select();
  958. }
  959. }
  960. private IEnumerator DelayedDestroyDropdownList(float delay)
  961. {
  962. yield return new WaitForSecondsRealtime(delay);
  963. ImmediateDestroyDropdownList();
  964. }
  965. private void ImmediateDestroyDropdownList()
  966. {
  967. for (int i = 0; i < m_Items.Count; i++)
  968. {
  969. if (m_Items[i] != null)
  970. DestroyItem(m_Items[i]);
  971. }
  972. m_Items.Clear();
  973. if (m_Dropdown != null)
  974. DestroyDropdownList(m_Dropdown);
  975. if (m_AlphaTweenRunner != null)
  976. m_AlphaTweenRunner.StopTween();
  977. m_Dropdown = null;
  978. m_Coroutine = null;
  979. }
  980. // Change the value and hide the dropdown.
  981. private void OnSelectItem(Toggle toggle)
  982. {
  983. if (!toggle.isOn)
  984. toggle.isOn = true;
  985. int selectedIndex = -1;
  986. Transform tr = toggle.transform;
  987. Transform parent = tr.parent;
  988. for (int i = 0; i < parent.childCount; i++)
  989. {
  990. if (parent.GetChild(i) == tr)
  991. {
  992. // Subtract one to account for template child.
  993. selectedIndex = i - 1;
  994. break;
  995. }
  996. }
  997. if (selectedIndex < 0)
  998. return;
  999. value = selectedIndex;
  1000. Hide();
  1001. }
  1002. }
  1003. }