DropDownList.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. ///Credit perchik
  2. ///Sourced from - http://forum.unity3d.com/threads/receive-onclick-event-and-pass-it-on-to-lower-ui-elements.293642/
  3. using System.Collections.Generic;
  4. namespace UnityEngine.UI.Extensions
  5. {
  6. /// <summary>
  7. /// Extension to the UI class which creates a dropdown list
  8. /// </summary>
  9. [RequireComponent(typeof(RectTransform))]
  10. [AddComponentMenu("UI/Extensions/Dropdown List")]
  11. public class DropDownList : MonoBehaviour
  12. {
  13. public Color disabledTextColor;
  14. public DropDownListItem SelectedItem { get; private set; } //outside world gets to get this, not set it
  15. [NonReorderable]
  16. public List<DropDownListItem> Items;
  17. public bool OverrideHighlighted = true;
  18. //private bool isInitialized = false;
  19. private bool _isPanelActive = false;
  20. private bool _hasDrawnOnce = false;
  21. private DropDownListButton _mainButton;
  22. private RectTransform _rectTransform;
  23. private RectTransform _overlayRT;
  24. private RectTransform _scrollPanelRT;
  25. private RectTransform _scrollBarRT;
  26. private RectTransform _slidingAreaRT;
  27. private RectTransform _scrollHandleRT;
  28. private RectTransform _itemsPanelRT;
  29. private Canvas _canvas;
  30. private RectTransform _canvasRT;
  31. private ScrollRect _scrollRect;
  32. private List<DropDownListButton> _panelItems;
  33. private GameObject _itemTemplate;
  34. [SerializeField]
  35. private float _scrollBarWidth = 20.0f;
  36. public float ScrollBarWidth
  37. {
  38. get { return _scrollBarWidth; }
  39. set
  40. {
  41. _scrollBarWidth = value;
  42. RedrawPanel();
  43. }
  44. }
  45. // private int scrollOffset; //offset of the selected item
  46. private int _selectedIndex = -1;
  47. [SerializeField]
  48. private int _itemsToDisplay;
  49. public int ItemsToDisplay
  50. {
  51. get { return _itemsToDisplay; }
  52. set
  53. {
  54. _itemsToDisplay = value;
  55. RedrawPanel();
  56. }
  57. }
  58. public bool SelectFirstItemOnStart = false;
  59. [SerializeField]
  60. private bool _displayPanelAbove = false;
  61. [System.Serializable]
  62. public class SelectionChangedEvent : UnityEngine.Events.UnityEvent<int> {
  63. }
  64. // fires when item is changed;
  65. public SelectionChangedEvent OnSelectionChanged;
  66. public void Start()
  67. {
  68. Initialize();
  69. if (SelectFirstItemOnStart && Items.Count > 0) {
  70. ToggleDropdownPanel (false);
  71. OnItemClicked (0);
  72. }
  73. RedrawPanel();
  74. }
  75. private bool Initialize()
  76. {
  77. bool success = true;
  78. try
  79. {
  80. _rectTransform = GetComponent<RectTransform>();
  81. _mainButton = new DropDownListButton(_rectTransform.Find("MainButton").gameObject);
  82. _overlayRT = _rectTransform.Find("Overlay").GetComponent<RectTransform>();
  83. _overlayRT.gameObject.SetActive(false);
  84. _scrollPanelRT = _overlayRT.Find("ScrollPanel").GetComponent<RectTransform>();
  85. _scrollBarRT = _scrollPanelRT.Find("Scrollbar").GetComponent<RectTransform>();
  86. _slidingAreaRT = _scrollBarRT.Find("SlidingArea").GetComponent<RectTransform>();
  87. _scrollHandleRT = _slidingAreaRT.Find("Handle").GetComponent<RectTransform>();
  88. _itemsPanelRT = _scrollPanelRT.Find("Items").GetComponent<RectTransform>();
  89. //itemPanelLayout = itemsPanelRT.gameObject.GetComponent<LayoutGroup>();
  90. _canvas = GetComponentInParent<Canvas>();
  91. _canvasRT = _canvas.GetComponent<RectTransform>();
  92. _scrollRect = _scrollPanelRT.GetComponent<ScrollRect>();
  93. _scrollRect.scrollSensitivity = _rectTransform.sizeDelta.y / 2;
  94. _scrollRect.movementType = ScrollRect.MovementType.Clamped;
  95. _scrollRect.content = _itemsPanelRT;
  96. _itemTemplate = _rectTransform.Find("ItemTemplate").gameObject;
  97. _itemTemplate.SetActive(false);
  98. }
  99. catch (System.NullReferenceException ex)
  100. {
  101. Debug.LogException(ex);
  102. Debug.LogError("Something is setup incorrectly with the dropdownlist component causing a Null Reference Exception");
  103. success = false;
  104. }
  105. _panelItems = new List<DropDownListButton>();
  106. RebuildPanel();
  107. RedrawPanel();
  108. return success;
  109. }
  110. // currently just using items in the list instead of being able to add to it.
  111. /// <summary>
  112. /// Rebuilds the list from a new collection.
  113. /// </summary>
  114. /// <remarks>
  115. /// NOTE, this will clear all existing items
  116. /// </remarks>
  117. /// <param name="list"></param>
  118. public void RefreshItems(params object[] list)
  119. {
  120. Items.Clear();
  121. List<DropDownListItem> ddItems = new List<DropDownListItem>();
  122. foreach (var obj in list)
  123. {
  124. if (obj is DropDownListItem)
  125. {
  126. ddItems.Add((DropDownListItem)obj);
  127. }
  128. else if (obj is string)
  129. {
  130. ddItems.Add(new DropDownListItem(caption: (string)obj));
  131. }
  132. else if (obj is Sprite)
  133. {
  134. ddItems.Add(new DropDownListItem(image: (Sprite)obj));
  135. }
  136. else
  137. {
  138. throw new System.Exception("Only ComboBoxItems, Strings, and Sprite types are allowed");
  139. }
  140. }
  141. Items.AddRange(ddItems);
  142. RebuildPanel();
  143. }
  144. /// <summary>
  145. /// Adds an additional item to the drop down list (recommended)
  146. /// </summary>
  147. /// <param name="item">Item of type DropDownListItem</param>
  148. public void AddItem(DropDownListItem item)
  149. {
  150. Items.Add(item);
  151. RebuildPanel();
  152. }
  153. /// <summary>
  154. /// Adds an additional drop down list item using a string name
  155. /// </summary>
  156. /// <param name="item">Item of type String</param>
  157. public void AddItem(string item)
  158. {
  159. Items.Add(new DropDownListItem(caption: (string)item));
  160. RebuildPanel();
  161. }
  162. /// <summary>
  163. /// Adds an additional drop down list item using a sprite image
  164. /// </summary>
  165. /// <param name="item">Item of type UI Sprite</param>
  166. public void AddItem(Sprite item)
  167. {
  168. Items.Add(new DropDownListItem(image: (Sprite)item));
  169. RebuildPanel();
  170. }
  171. /// <summary>
  172. /// Removes an item from the drop down list (recommended)
  173. /// </summary>
  174. /// <param name="item">Item of type DropDownListItem</param>
  175. public void RemoveItem(DropDownListItem item)
  176. {
  177. Items.Remove(item);
  178. RebuildPanel();
  179. }
  180. /// <summary>
  181. /// Removes an item from the drop down list item using a string name
  182. /// </summary>
  183. /// <param name="item">Item of type String</param>
  184. public void RemoveItem(string item)
  185. {
  186. Items.Remove(new DropDownListItem(caption: (string)item));
  187. RebuildPanel();
  188. }
  189. /// <summary>
  190. /// Removes an item from the drop down list item using a sprite image
  191. /// </summary>
  192. /// <param name="item">Item of type UI Sprite</param>
  193. public void RemoveItem(Sprite item)
  194. {
  195. Items.Remove(new DropDownListItem(image: (Sprite)item));
  196. RebuildPanel();
  197. }
  198. public void ResetItems()
  199. {
  200. Items.Clear();
  201. RebuildPanel();
  202. }
  203. /// <summary>
  204. /// Rebuilds the contents of the panel in response to items being added.
  205. /// </summary>
  206. private void RebuildPanel()
  207. {
  208. if (Items.Count == 0) return;
  209. int indx = _panelItems.Count;
  210. while (_panelItems.Count < Items.Count)
  211. {
  212. GameObject newItem = Instantiate(_itemTemplate) as GameObject;
  213. newItem.name = "Item " + indx;
  214. newItem.transform.SetParent(_itemsPanelRT, false);
  215. _panelItems.Add(new DropDownListButton(newItem));
  216. indx++;
  217. }
  218. for (int i = 0; i < _panelItems.Count; i++)
  219. {
  220. if (i < Items.Count)
  221. {
  222. DropDownListItem item = Items[i];
  223. _panelItems[i].txt.text = item.Caption;
  224. if (item.IsDisabled) _panelItems[i].txt.color = disabledTextColor;
  225. if (_panelItems[i].btnImg != null) _panelItems[i].btnImg.sprite = null;//hide the button image
  226. _panelItems[i].img.sprite = item.Image;
  227. _panelItems[i].img.color = (item.Image == null) ? new Color(1, 1, 1, 0)
  228. : item.IsDisabled ? new Color(1, 1, 1, .5f)
  229. : Color.white;
  230. int ii = i; //have to copy the variable for use in anonymous function
  231. _panelItems[i].btn.onClick.RemoveAllListeners();
  232. _panelItems[i].btn.onClick.AddListener(() =>
  233. {
  234. OnItemClicked(ii);
  235. if (item.OnSelect != null) item.OnSelect();
  236. });
  237. }
  238. _panelItems[i].gameobject.SetActive(i < Items.Count);// if we have more thanks in the panel than Items in the list hide them
  239. }
  240. }
  241. private void OnItemClicked(int indx)
  242. {
  243. //Debug.Log("item " + indx + " clicked");
  244. if (indx != _selectedIndex && OnSelectionChanged != null) OnSelectionChanged.Invoke(indx);
  245. _selectedIndex = indx;
  246. ToggleDropdownPanel(true);
  247. UpdateSelected();
  248. }
  249. private void UpdateSelected()
  250. {
  251. SelectedItem = (_selectedIndex > -1 && _selectedIndex < Items.Count) ? Items[_selectedIndex] : null;
  252. if (SelectedItem == null) return;
  253. bool hasImage = SelectedItem.Image != null;
  254. if (hasImage)
  255. {
  256. _mainButton.img.sprite = SelectedItem.Image;
  257. _mainButton.img.color = Color.white;
  258. //if (Interactable) mainButton.img.color = Color.white;
  259. //else mainButton.img.color = new Color(1, 1, 1, .5f);
  260. }
  261. else
  262. {
  263. _mainButton.img.sprite = null;
  264. }
  265. _mainButton.txt.text = SelectedItem.Caption;
  266. //update selected index color
  267. if (OverrideHighlighted)
  268. {
  269. for (int i = 0; i < _itemsPanelRT.childCount; i++)
  270. {
  271. _panelItems[i].btnImg.color = (_selectedIndex == i) ? _mainButton.btn.colors.highlightedColor : new Color(0, 0, 0, 0);
  272. }
  273. }
  274. }
  275. private void RedrawPanel()
  276. {
  277. float scrollbarWidth = Items.Count > ItemsToDisplay ? _scrollBarWidth : 0f;//hide the scrollbar if there's not enough items
  278. if (!_hasDrawnOnce || _rectTransform.sizeDelta != _mainButton.rectTransform.sizeDelta)
  279. {
  280. _hasDrawnOnce = true;
  281. _mainButton.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x);
  282. _mainButton.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _rectTransform.sizeDelta.y);
  283. _mainButton.txt.rectTransform.offsetMax = new Vector2(4, 0);
  284. _scrollPanelRT.SetParent(transform, true);//break the scroll panel from the overlay
  285. _scrollPanelRT.anchoredPosition = _displayPanelAbove ?
  286. new Vector2(0, _rectTransform.sizeDelta.y * ItemsToDisplay - 1) :
  287. new Vector2(0, -_rectTransform.sizeDelta.y);
  288. //make the overlay fill the screen
  289. _overlayRT.SetParent(_canvas.transform, false); //attach it to top level object
  290. _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _canvasRT.sizeDelta.x);
  291. _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _canvasRT.sizeDelta.y);
  292. _overlayRT.SetParent(transform, true);//reattach to this object
  293. _scrollPanelRT.SetParent(_overlayRT, true); //reattach the scrollpanel to the overlay
  294. }
  295. if (Items.Count < 1) return;
  296. float dropdownHeight = _rectTransform.sizeDelta.y * Mathf.Min(_itemsToDisplay, Items.Count);
  297. _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight);
  298. _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x);
  299. _itemsPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _scrollPanelRT.sizeDelta.x - scrollbarWidth - 5);
  300. _itemsPanelRT.anchoredPosition = new Vector2(5, 0);
  301. _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, scrollbarWidth);
  302. _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight);
  303. if (scrollbarWidth == 0) _scrollHandleRT.gameObject.SetActive(false); else _scrollHandleRT.gameObject.SetActive(true);
  304. _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0);
  305. _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight - _scrollBarRT.sizeDelta.x);
  306. }
  307. /// <summary>
  308. /// Toggle the drop down list
  309. /// </summary>
  310. /// <param name="directClick"> whether an item was directly clicked on</param>
  311. public void ToggleDropdownPanel(bool directClick)
  312. {
  313. _overlayRT.transform.localScale = new Vector3(1, 1, 1);
  314. _scrollBarRT.transform.localScale = new Vector3(1, 1, 1);
  315. _isPanelActive = !_isPanelActive;
  316. _overlayRT.gameObject.SetActive(_isPanelActive);
  317. if (_isPanelActive)
  318. {
  319. transform.SetAsLastSibling();
  320. }
  321. else if (directClick)
  322. {
  323. // scrollOffset = Mathf.RoundToInt(itemsPanelRT.anchoredPosition.y / _rectTransform.sizeDelta.y);
  324. }
  325. }
  326. }
  327. }