///Credit perchik ///Sourced from - http://forum.unity3d.com/threads/receive-onclick-event-and-pass-it-on-to-lower-ui-elements.293642/ using System.Collections.Generic; namespace UnityEngine.UI.Extensions { /// /// Extension to the UI class which creates a dropdown list /// [RequireComponent(typeof(RectTransform))] [AddComponentMenu("UI/Extensions/ComboBox/Dropdown List")] public class DropDownList : MonoBehaviour { public Color disabledTextColor; public DropDownListItem SelectedItem { get; private set; } //outside world gets to get this, not set it [Header("Dropdown List Items")] public List Items; [Header("Properties")] [SerializeField] private bool isActive = true; public bool OverrideHighlighted = true; //private bool isInitialized = false; private bool _isPanelActive = false; private bool _hasDrawnOnce = false; private DropDownListButton _mainButton; private RectTransform _rectTransform; private RectTransform _overlayRT; private RectTransform _scrollPanelRT; private RectTransform _scrollBarRT; private RectTransform _slidingAreaRT; private RectTransform _scrollHandleRT; private RectTransform _itemsPanelRT; private Canvas _canvas; private RectTransform _canvasRT; private ScrollRect _scrollRect; private List _panelItems = new List(); private GameObject _itemTemplate; private bool _initialized; private string _defaultMainButtonCaption = null; private Color _defaultNormalColor; [SerializeField] private float _scrollBarWidth = 20.0f; public float ScrollBarWidth { get { return _scrollBarWidth; } set { _scrollBarWidth = value; RedrawPanel(); } } // private int scrollOffset; //offset of the selected item private int _selectedIndex = -1; [SerializeField] private int _itemsToDisplay; public int ItemsToDisplay { get { return _itemsToDisplay; } set { _itemsToDisplay = value; RedrawPanel(); } } [SerializeField] private float dropdownOffset; [SerializeField] private bool _displayPanelAbove = false; public bool SelectFirstItemOnStart = false; [SerializeField] private int selectItemIndexOnStart = 0; private bool shouldSelectItemOnStart => SelectFirstItemOnStart || selectItemIndexOnStart > 0; [System.Serializable] public class SelectionChangedEvent : Events.UnityEvent { } // fires when item is changed; [Header("Events")] public SelectionChangedEvent OnSelectionChanged; [System.Serializable] public class ControlDisabledEvent : Events.UnityEvent { } // fires when item is changed; public ControlDisabledEvent OnControlDisabled; public void Start() { Initialize(); if (shouldSelectItemOnStart && Items.Count > 0) { SelectItemIndex(SelectFirstItemOnStart ? 0 : selectItemIndexOnStart); } RedrawPanel(); } private bool Initialize() { if (_initialized) return true; bool success = true; try { _rectTransform = GetComponent(); _mainButton = new DropDownListButton(_rectTransform.Find("MainButton").gameObject); _defaultMainButtonCaption = _mainButton.txt.text; _defaultNormalColor = _mainButton.btn.colors.normalColor; _overlayRT = _rectTransform.Find("Overlay").GetComponent(); _overlayRT.gameObject.SetActive(false); _scrollPanelRT = _overlayRT.Find("ScrollPanel").GetComponent(); _scrollBarRT = _scrollPanelRT.Find("Scrollbar").GetComponent(); _slidingAreaRT = _scrollBarRT.Find("SlidingArea").GetComponent(); _scrollHandleRT = _slidingAreaRT.Find("Handle").GetComponent(); _itemsPanelRT = _scrollPanelRT.Find("Items").GetComponent(); //itemPanelLayout = itemsPanelRT.gameObject.GetComponent(); _canvas = GetComponentInParent(); _canvasRT = _canvas.GetComponent(); _scrollRect = _scrollPanelRT.GetComponent(); _scrollRect.scrollSensitivity = _rectTransform.sizeDelta.y / 2; _scrollRect.movementType = ScrollRect.MovementType.Clamped; _scrollRect.content = _itemsPanelRT; _itemTemplate = _rectTransform.Find("ItemTemplate").gameObject; _itemTemplate.SetActive(false); } catch (System.NullReferenceException ex) { Debug.LogException(ex); Debug.LogError("Something is setup incorrectly with the dropdownlist component causing a Null Reference Exception"); success = false; } _initialized = true; RebuildPanel(); RedrawPanel(); return success; } /// /// Update the drop down selection to a specific index /// /// public void SelectItemIndex(int index) { ToggleDropdownPanel(false); OnItemClicked(index); } // currently just using items in the list instead of being able to add to it. /// /// Rebuilds the list from a new collection. /// /// /// NOTE, this will clear all existing items /// /// public void RefreshItems(params object[] list) { Items.Clear(); List ddItems = new List(); foreach (var obj in list) { if (obj is DropDownListItem) { ddItems.Add((DropDownListItem)obj); } else if (obj is string) { ddItems.Add(new DropDownListItem(caption: (string)obj)); } else if (obj is Sprite) { ddItems.Add(new DropDownListItem(image: (Sprite)obj)); } else { throw new System.Exception("Only ComboBoxItems, Strings, and Sprite types are allowed"); } } Items.AddRange(ddItems); RebuildPanel(); RedrawPanel(); } /// /// Adds an additional item to the drop down list (recommended) /// /// Item of type DropDownListItem public void AddItem(DropDownListItem item) { Items.Add(item); RebuildPanel(); RedrawPanel(); } /// /// Adds an additional drop down list item using a string name /// /// Item of type String public void AddItem(string item) { Items.Add(new DropDownListItem(caption: (string)item)); RebuildPanel(); RedrawPanel(); } /// /// Adds an additional drop down list item using a sprite image /// /// Item of type UI Sprite public void AddItem(Sprite item) { Items.Add(new DropDownListItem(image: (Sprite)item)); RebuildPanel(); RedrawPanel(); } /// /// Removes an item from the drop down list (recommended) /// /// Item of type DropDownListItem public void RemoveItem(DropDownListItem item) { Items.Remove(item); RebuildPanel(); RedrawPanel(); } /// /// Removes an item from the drop down list item using a string name /// /// Item of type String public void RemoveItem(string item) { Items.Remove(new DropDownListItem(caption: (string)item)); RebuildPanel(); RedrawPanel(); } /// /// Removes an item from the drop down list item using a sprite image /// /// Item of type UI Sprite public void RemoveItem(Sprite item) { Items.Remove(new DropDownListItem(image: (Sprite)item)); RebuildPanel(); RedrawPanel(); } public void ResetDropDown() { if (!_initialized) return; _mainButton.txt.text = _defaultMainButtonCaption; for (int i = 0; i < _itemsPanelRT.childCount; i++) _panelItems[i].btnImg.color = _defaultNormalColor; _selectedIndex = -1; _initialized = false; Initialize(); } public void ResetItems() { Items.Clear(); RebuildPanel(); RedrawPanel(); } /// /// Rebuilds the contents of the panel in response to items being added. /// private void RebuildPanel() { if (Items.Count == 0) return; if (!_initialized) { Start(); } int indx = _panelItems.Count; while (_panelItems.Count < Items.Count) { GameObject newItem = Instantiate(_itemTemplate) as GameObject; newItem.name = "Item " + indx; newItem.transform.SetParent(_itemsPanelRT, false); _panelItems.Add(new DropDownListButton(newItem)); indx++; } for (int i = 0; i < _panelItems.Count; i++) { if (i < Items.Count) { DropDownListItem item = Items[i]; _panelItems[i].txt.text = item.Caption; if (item.IsDisabled) _panelItems[i].txt.color = disabledTextColor; if (_panelItems[i].btnImg != null) _panelItems[i].btnImg.sprite = null;//hide the button image _panelItems[i].img.sprite = item.Image; _panelItems[i].img.color = (item.Image == null) ? new Color(1, 1, 1, 0) : item.IsDisabled ? new Color(1, 1, 1, .5f) : Color.white; int ii = i; //have to copy the variable for use in anonymous function _panelItems[i].btn.onClick.RemoveAllListeners(); _panelItems[i].btn.onClick.AddListener(() => { OnItemClicked(ii); if (item.OnSelect != null) item.OnSelect(); }); } _panelItems[i].gameobject.SetActive(i < Items.Count);// if we have more thanks in the panel than Items in the list hide them } } private void OnItemClicked(int indx) { //Debug.Log("item " + indx + " clicked"); if (indx != _selectedIndex && OnSelectionChanged != null) OnSelectionChanged.Invoke(indx); _selectedIndex = indx; ToggleDropdownPanel(true); UpdateSelected(); } private void UpdateSelected() { SelectedItem = (_selectedIndex > -1 && _selectedIndex < Items.Count) ? Items[_selectedIndex] : null; if (SelectedItem == null) return; bool hasImage = SelectedItem.Image != null; if (hasImage) { _mainButton.img.sprite = SelectedItem.Image; _mainButton.img.color = Color.white; } else { _mainButton.img.sprite = null; } _mainButton.txt.text = SelectedItem.Caption; //update selected index color if (OverrideHighlighted) { for (int i = 0; i < _itemsPanelRT.childCount; i++) { _panelItems[i].btnImg.color = (_selectedIndex == i) ? _mainButton.btn.colors.highlightedColor : new Color(0, 0, 0, 0); } } } private void RedrawPanel() { float scrollbarWidth = _panelItems.Count > ItemsToDisplay ? _scrollBarWidth : 0f;//hide the scrollbar if there's not enough items _scrollBarRT.gameObject.SetActive(_panelItems.Count > ItemsToDisplay); float dropdownHeight = _itemsToDisplay > 0 ? _rectTransform.sizeDelta.y * Mathf.Min(_itemsToDisplay, _panelItems.Count) : _rectTransform.sizeDelta.y * _panelItems.Count; dropdownHeight += dropdownOffset; if (!_hasDrawnOnce || _rectTransform.sizeDelta != _mainButton.rectTransform.sizeDelta) { _hasDrawnOnce = true; _mainButton.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x); _mainButton.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _rectTransform.sizeDelta.y); var itemsRemaining = _panelItems.Count - ItemsToDisplay; itemsRemaining = itemsRemaining < 0 ? 0 : itemsRemaining; _scrollPanelRT.SetParent(transform, true); _scrollPanelRT.anchoredPosition = _displayPanelAbove ? new Vector2(0, dropdownOffset + dropdownHeight) : new Vector2(0, -(dropdownOffset + _rectTransform.sizeDelta.y)); //make the overlay fill the screen _overlayRT.SetParent(_canvas.transform, false); _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _canvasRT.sizeDelta.x); _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _canvasRT.sizeDelta.y); _overlayRT.SetParent(transform, true); _scrollPanelRT.SetParent(_overlayRT, true); } if (_panelItems.Count < 1) return; _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight); _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x); _itemsPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _scrollPanelRT.sizeDelta.x - scrollbarWidth - 5); _itemsPanelRT.anchoredPosition = new Vector2(5, 0); _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, scrollbarWidth); _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight); if (scrollbarWidth == 0) _scrollHandleRT.gameObject.SetActive(false); else _scrollHandleRT.gameObject.SetActive(true); _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0); _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight - _scrollBarRT.sizeDelta.x); } /// /// Toggle the drop down list /// /// whether an item was directly clicked on public void ToggleDropdownPanel(bool directClick) { if (!isActive) return; _overlayRT.transform.localScale = new Vector3(1, 1, 1); _scrollBarRT.transform.localScale = new Vector3(1, 1, 1); _isPanelActive = !_isPanelActive; _overlayRT.gameObject.SetActive(_isPanelActive); if (_isPanelActive) { transform.SetAsLastSibling(); } else if (directClick) { // scrollOffset = Mathf.RoundToInt(itemsPanelRT.anchoredPosition.y / _rectTransform.sizeDelta.y); } } /// /// Updates the control and sets its active status, determines whether the dropdown will open ot not /// /// public void SetActive(bool status) { if (status != isActive) { OnControlDisabled?.Invoke(status); } isActive = status; } } }