No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TMP_Dropdown.cs 51KB

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