2015-02-03 07:07:31 +08:00
///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 ;
using System.Linq ;
namespace UnityEngine.UI.Extensions
{
[RequireComponent(typeof(RectTransform))]
2023-02-07 22:35:43 +08:00
[AddComponentMenu("UI/Extensions/ComboBox/ComboBox")]
2015-02-03 07:07:31 +08:00
public class ComboBox : MonoBehaviour
{
2023-02-07 22:35:43 +08:00
public DropDownListItem SelectedItem { get ; private set ; }
2015-02-03 07:07:31 +08:00
2023-02-07 22:35:43 +08:00
[Header("Combo Box Items")]
2015-02-10 08:03:38 +08:00
public List < string > AvailableOptions ;
2015-02-03 07:07:31 +08:00
2023-02-07 22:35:43 +08:00
[Header("Properties")]
[SerializeField]
private bool isActive = true ;
2017-05-05 21:06:52 +08:00
[SerializeField]
private float _scrollBarWidth = 20.0f ;
[SerializeField]
private int _itemsToDisplay ;
2023-02-07 22:35:43 +08:00
[SerializeField]
private float dropdownOffset ;
2021-05-11 01:38:31 +08:00
2020-09-25 04:47:00 +08:00
[SerializeField]
2021-05-11 01:38:31 +08:00
private bool _displayPanelAbove = false ;
2020-09-25 04:47:00 +08:00
2023-02-07 22:35:43 +08:00
public bool SelectFirstItemOnStart = false ;
[SerializeField]
private int selectItemIndexOnStart = 0 ;
private bool shouldSelectItemOnStart = > SelectFirstItemOnStart | | selectItemIndexOnStart > 0 ;
2017-05-05 21:06:52 +08:00
[System.Serializable]
2023-02-07 22:35:43 +08:00
public class SelectionChangedEvent : Events . UnityEvent < string > { }
[Header("Events")]
2017-05-05 21:06:52 +08:00
// fires when item is changed;
public SelectionChangedEvent OnSelectionChanged ;
2015-02-03 07:07:31 +08:00
2023-02-07 22:35:43 +08:00
[System.Serializable]
public class ControlDisabledEvent : Events . UnityEvent < bool > { }
// fires when item is changed;
public ControlDisabledEvent OnControlDisabled ;
2015-02-10 08:03:38 +08:00
//private bool isInitialized = false;
private bool _isPanelActive = false ;
private bool _hasDrawnOnce = false ;
private InputField _mainInput ;
private RectTransform _inputRT ;
2015-02-03 07:07:31 +08:00
private RectTransform _rectTransform ;
2015-02-10 08:03:38 +08:00
private RectTransform _overlayRT ;
2015-02-03 07:07:31 +08:00
private RectTransform _scrollPanelRT ;
2015-02-10 08:03:38 +08:00
private RectTransform _scrollBarRT ;
2015-02-03 07:07:31 +08:00
private RectTransform _slidingAreaRT ;
2021-05-11 01:38:31 +08:00
private RectTransform _scrollHandleRT ;
2015-02-10 08:03:38 +08:00
private RectTransform _itemsPanelRT ;
private Canvas _canvas ;
private RectTransform _canvasRT ;
private ScrollRect _scrollRect ;
2020-07-09 03:38:28 +08:00
private List < string > _panelItems ; //items that will get shown in the drop-down
2015-02-10 08:03:38 +08:00
private Dictionary < string , GameObject > panelObjects ;
private GameObject itemTemplate ;
2023-02-07 22:35:43 +08:00
private bool _initialized ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
public string Text { get ; private set ; }
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
public float ScrollBarWidth
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
get { return _scrollBarWidth ; }
2015-02-03 07:07:31 +08:00
set
{
2015-02-10 08:03:38 +08:00
_scrollBarWidth = value ;
RedrawPanel ( ) ;
2015-02-03 07:07:31 +08:00
}
}
2015-02-10 08:03:38 +08:00
public int ItemsToDisplay
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
get { return _itemsToDisplay ; }
2015-02-03 07:07:31 +08:00
set
{
2015-02-10 08:03:38 +08:00
_itemsToDisplay = value ;
RedrawPanel ( ) ;
2015-02-03 07:07:31 +08:00
}
}
2015-02-10 08:03:38 +08:00
public void Awake ( )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
Initialize ( ) ;
2015-02-03 07:07:31 +08:00
}
2021-05-11 01:38:31 +08:00
public void Start ( )
{
2023-02-07 22:35:43 +08:00
if ( shouldSelectItemOnStart & & AvailableOptions . Count > 0 )
{
SelectItemIndex ( SelectFirstItemOnStart ? 0 : selectItemIndexOnStart ) ;
}
2021-05-11 01:38:31 +08:00
RedrawPanel ( ) ;
}
2015-02-10 08:03:38 +08:00
private bool Initialize ( )
2015-02-03 07:07:31 +08:00
{
2023-02-07 22:35:43 +08:00
if ( _initialized ) return true ;
2015-02-10 08:03:38 +08:00
bool success = true ;
try
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
_rectTransform = GetComponent < RectTransform > ( ) ;
2017-06-04 00:59:12 +08:00
_inputRT = _rectTransform . Find ( "InputField" ) . GetComponent < RectTransform > ( ) ;
2015-02-10 08:03:38 +08:00
_mainInput = _inputRT . GetComponent < InputField > ( ) ;
2015-02-03 07:07:31 +08:00
2017-06-04 00:59:12 +08:00
_overlayRT = _rectTransform . Find ( "Overlay" ) . GetComponent < RectTransform > ( ) ;
2015-02-10 08:03:38 +08:00
_overlayRT . gameObject . SetActive ( false ) ;
2015-02-03 07:07:31 +08:00
2017-06-04 00:59:12 +08:00
_scrollPanelRT = _overlayRT . Find ( "ScrollPanel" ) . GetComponent < RectTransform > ( ) ;
_scrollBarRT = _scrollPanelRT . Find ( "Scrollbar" ) . GetComponent < RectTransform > ( ) ;
_slidingAreaRT = _scrollBarRT . Find ( "SlidingArea" ) . GetComponent < RectTransform > ( ) ;
2021-05-11 01:38:31 +08:00
_scrollHandleRT = _slidingAreaRT . Find ( "Handle" ) . GetComponent < RectTransform > ( ) ;
2017-06-04 00:59:12 +08:00
_itemsPanelRT = _scrollPanelRT . Find ( "Items" ) . GetComponent < RectTransform > ( ) ;
2015-02-10 08:03:38 +08:00
//itemPanelLayout = itemsPanelRT.gameObject.GetComponent<LayoutGroup>();
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_canvas = GetComponentInParent < Canvas > ( ) ;
_canvasRT = _canvas . GetComponent < RectTransform > ( ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_scrollRect = _scrollPanelRT . GetComponent < ScrollRect > ( ) ;
_scrollRect . scrollSensitivity = _rectTransform . sizeDelta . y / 2 ;
_scrollRect . movementType = ScrollRect . MovementType . Clamped ;
_scrollRect . content = _itemsPanelRT ;
2015-02-03 07:07:31 +08:00
2017-06-04 00:59:12 +08:00
itemTemplate = _rectTransform . Find ( "ItemTemplate" ) . gameObject ;
2015-02-10 08:03:38 +08:00
itemTemplate . SetActive ( false ) ;
}
catch ( System . NullReferenceException ex )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
Debug . LogException ( ex ) ;
2020-07-09 03:38:28 +08:00
Debug . LogError ( "Something is setup incorrectly with the dropdownlist component causing a Null Reference Exception" ) ;
2015-02-10 08:03:38 +08:00
success = false ;
2015-02-03 07:07:31 +08:00
}
2015-02-10 08:03:38 +08:00
panelObjects = new Dictionary < string , GameObject > ( ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_panelItems = AvailableOptions . ToList ( ) ;
2023-02-07 22:35:43 +08:00
_initialized = true ;
2015-02-10 08:03:38 +08:00
RebuildPanel ( ) ;
return success ;
2015-02-03 07:07:31 +08:00
}
2023-02-07 22:35:43 +08:00
/// <summary>
/// Update the drop down selection to a specific index
/// </summary>
/// <param name="index"></param>
public void SelectItemIndex ( int index )
{
ToggleDropdownPanel ( false ) ;
OnItemClicked ( AvailableOptions [ index ] ) ;
}
2020-08-28 01:20:49 +08:00
public void AddItem ( string item )
2015-02-03 07:07:31 +08:00
{
2020-08-28 01:20:49 +08:00
AvailableOptions . Add ( item ) ;
RebuildPanel ( ) ;
}
public void RemoveItem ( string item )
{
AvailableOptions . Remove ( item ) ;
RebuildPanel ( ) ;
}
public void SetAvailableOptions ( List < string > newOptions )
{
2023-02-07 22:35:43 +08:00
var uniqueOptions = newOptions . Distinct ( ) . ToArray ( ) ;
SetAvailableOptions ( uniqueOptions ) ;
2020-08-28 01:20:49 +08:00
}
public void SetAvailableOptions ( string [ ] newOptions )
{
2023-02-07 22:35:43 +08:00
var uniqueOptions = newOptions . Distinct ( ) . ToList ( ) ;
if ( newOptions . Length ! = uniqueOptions . Count )
{
Debug . LogWarning ( $"{nameof(ComboBox)}.{nameof(SetAvailableOptions)}: items may only exists once. {newOptions.Length - uniqueOptions.Count} duplicates." ) ;
}
this . AvailableOptions . Clear ( ) ;
2020-08-28 01:20:49 +08:00
for ( int i = 0 ; i < newOptions . Length ; i + + )
2015-02-03 07:07:31 +08:00
{
2023-02-07 22:35:43 +08:00
this . AvailableOptions . Add ( newOptions [ i ] ) ;
2015-02-03 07:07:31 +08:00
}
2023-02-07 22:35:43 +08:00
this . RebuildPanel ( ) ;
this . RedrawPanel ( ) ;
2015-02-03 07:07:31 +08:00
}
2020-08-28 01:20:49 +08:00
public void ResetItems ( )
{
AvailableOptions . Clear ( ) ;
RebuildPanel ( ) ;
2023-02-07 22:35:43 +08:00
RedrawPanel ( ) ;
2020-08-28 01:20:49 +08:00
}
2015-02-03 07:07:31 +08:00
/// <summary>
2015-02-10 08:03:38 +08:00
/// Rebuilds the contents of the panel in response to items being added.
2015-02-03 07:07:31 +08:00
/// </summary>
2015-02-10 08:03:38 +08:00
private void RebuildPanel ( )
2015-02-03 07:07:31 +08:00
{
2023-02-07 22:35:43 +08:00
if ( ! _initialized )
{
Start ( ) ;
}
2015-02-10 08:03:38 +08:00
//panel starts with all options
_panelItems . Clear ( ) ;
foreach ( string option in AvailableOptions )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
_panelItems . Add ( option . ToLower ( ) ) ;
}
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
List < GameObject > itemObjs = new List < GameObject > ( panelObjects . Values ) ;
panelObjects . Clear ( ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
int indx = 0 ;
while ( itemObjs . Count < AvailableOptions . Count )
{
GameObject newItem = Instantiate ( itemTemplate ) as GameObject ;
newItem . name = "Item " + indx ;
newItem . transform . SetParent ( _itemsPanelRT , false ) ;
itemObjs . Add ( newItem ) ;
indx + + ;
}
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
for ( int i = 0 ; i < itemObjs . Count ; i + + )
{
itemObjs [ i ] . SetActive ( i < = AvailableOptions . Count ) ;
if ( i < AvailableOptions . Count )
{
itemObjs [ i ] . name = "Item " + i + " " + _panelItems [ i ] ;
2023-02-07 22:35:43 +08:00
#if UNITY_2022_1_OR_NEWER
itemObjs [ i ] . transform . Find ( "Text" ) . GetComponent < TMPro . TMP_Text > ( ) . text = AvailableOptions [ i ] ; //set the text value
# else
2021-05-11 01:38:31 +08:00
itemObjs [ i ] . transform . Find ( "Text" ) . GetComponent < Text > ( ) . text = AvailableOptions [ i ] ; //set the text value
2023-02-07 22:35:43 +08:00
# endif
2015-02-10 08:03:38 +08:00
Button itemBtn = itemObjs [ i ] . GetComponent < Button > ( ) ;
itemBtn . onClick . RemoveAllListeners ( ) ;
string textOfItem = _panelItems [ i ] ; //has to be copied for anonymous function or it gets garbage collected away
itemBtn . onClick . AddListener ( ( ) = >
{
OnItemClicked ( textOfItem ) ;
} ) ;
panelObjects [ _panelItems [ i ] ] = itemObjs [ i ] ;
}
2015-02-03 07:07:31 +08:00
}
}
/// <summary>
2015-02-10 08:03:38 +08:00
/// what happens when an item in the list is selected
2015-02-03 07:07:31 +08:00
/// </summary>
2015-02-10 08:03:38 +08:00
/// <param name="item"></param>
private void OnItemClicked ( string item )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
//Debug.Log("item " + item + " clicked");
Text = item ;
_mainInput . text = Text ;
ToggleDropdownPanel ( true ) ;
2015-02-03 07:07:31 +08:00
}
2015-02-10 08:03:38 +08:00
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 ) ;
2023-02-07 22:35:43 +08:00
float dropdownHeight = _itemsToDisplay > 0 ? _rectTransform . sizeDelta . y * Mathf . Min ( _itemsToDisplay , _panelItems . Count ) : _rectTransform . sizeDelta . y * _panelItems . Count ;
dropdownHeight + = dropdownOffset ;
2015-02-10 08:03:38 +08:00
if ( ! _hasDrawnOnce | | _rectTransform . sizeDelta ! = _inputRT . sizeDelta )
{
_hasDrawnOnce = true ;
_inputRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , _rectTransform . sizeDelta . x ) ;
_inputRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Vertical , _rectTransform . sizeDelta . y ) ;
2015-02-03 07:07:31 +08:00
2023-02-07 22:35:43 +08:00
var itemsRemaining = _panelItems . Count - ItemsToDisplay ;
itemsRemaining = itemsRemaining < 0 ? 0 : itemsRemaining ;
_scrollPanelRT . SetParent ( transform , true ) ;
2021-05-11 01:38:31 +08:00
_scrollPanelRT . anchoredPosition = _displayPanelAbove ?
2023-02-07 22:35:43 +08:00
new Vector2 ( 0 , dropdownOffset + dropdownHeight ) :
new Vector2 ( 0 , - ( dropdownOffset + _rectTransform . sizeDelta . y ) ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
//make the overlay fill the screen
2023-02-07 22:35:43 +08:00
_overlayRT . SetParent ( _canvas . transform , false ) ;
2015-02-10 08:03:38 +08:00
_overlayRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , _canvasRT . sizeDelta . x ) ;
_overlayRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Vertical , _canvasRT . sizeDelta . y ) ;
2015-02-03 07:07:31 +08:00
2023-02-07 22:35:43 +08:00
_overlayRT . SetParent ( transform , true ) ;
_scrollPanelRT . SetParent ( _overlayRT , true ) ;
2015-02-10 08:03:38 +08:00
}
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
if ( _panelItems . Count < 1 ) return ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_scrollPanelRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Vertical , dropdownHeight ) ;
_scrollPanelRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , _rectTransform . sizeDelta . x ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_itemsPanelRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , _scrollPanelRT . sizeDelta . x - scrollbarWidth - 5 ) ;
_itemsPanelRT . anchoredPosition = new Vector2 ( 5 , 0 ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_scrollBarRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , scrollbarWidth ) ;
_scrollBarRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Vertical , dropdownHeight ) ;
2021-05-11 01:38:31 +08:00
if ( scrollbarWidth = = 0 ) _scrollHandleRT . gameObject . SetActive ( false ) ; else _scrollHandleRT . gameObject . SetActive ( true ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
_slidingAreaRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Horizontal , 0 ) ;
_slidingAreaRT . SetSizeWithCurrentAnchors ( RectTransform . Axis . Vertical , dropdownHeight - _scrollBarRT . sizeDelta . x ) ;
2015-02-03 07:07:31 +08:00
}
2015-02-10 08:03:38 +08:00
public void OnValueChanged ( string currText )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
Text = currText ;
RedrawPanel ( ) ;
2015-02-03 07:07:31 +08:00
2015-02-10 08:03:38 +08:00
if ( _panelItems . Count = = 0 )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
_isPanelActive = true ; //this makes it get turned off
ToggleDropdownPanel ( false ) ;
2015-02-03 07:07:31 +08:00
}
2015-02-10 08:03:38 +08:00
else if ( ! _isPanelActive )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
ToggleDropdownPanel ( false ) ;
2015-02-03 07:07:31 +08:00
}
2017-05-05 21:06:52 +08:00
OnSelectionChanged . Invoke ( Text ) ;
2015-02-03 07:07:31 +08:00
}
/// <summary>
2015-02-10 08:03:38 +08:00
/// Toggle the drop down list
2015-02-03 07:07:31 +08:00
/// </summary>
2015-02-10 08:03:38 +08:00
/// <param name="directClick"> whether an item was directly clicked on</param>
public void ToggleDropdownPanel ( bool directClick )
2015-02-03 07:07:31 +08:00
{
2023-02-07 22:35:43 +08:00
if ( ! isActive ) return ;
2015-02-10 08:03:38 +08:00
_isPanelActive = ! _isPanelActive ;
_overlayRT . gameObject . SetActive ( _isPanelActive ) ;
if ( _isPanelActive )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
transform . SetAsLastSibling ( ) ;
2015-02-03 07:07:31 +08:00
}
2015-02-10 08:03:38 +08:00
else if ( directClick )
2015-02-03 07:07:31 +08:00
{
2015-02-10 08:03:38 +08:00
// scrollOffset = Mathf.RoundToInt(itemsPanelRT.anchoredPosition.y / _rectTransform.sizeDelta.y);
2015-02-03 07:07:31 +08:00
}
}
2023-02-07 22:35:43 +08:00
/// <summary>
/// Updates the control and sets its active status, determines whether the dropdown will open ot not
/// </summary>
/// <param name="status"></param>
public void SetActive ( bool status )
{
if ( status ! = isActive )
{
OnControlDisabled ? . Invoke ( status ) ;
}
isActive = status ;
}
2015-02-03 07:07:31 +08:00
}
}