DropDownList.cs 12 KB

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