Dropdown.cs 44 KB

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