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.
1159 lines
45 KiB
1159 lines
45 KiB
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
using UnityEngine.UI; |
|
using UnityEngine.Events; |
|
using UnityEngine.EventSystems; |
|
using UnityEngine.UI.CoroutineTween; |
|
|
|
namespace TMPro |
|
{ |
|
[AddComponentMenu("UI/Dropdown - TextMeshPro", 35)] |
|
[RequireComponent(typeof(RectTransform))] |
|
/// <summary> |
|
/// A standard dropdown that presents a list of options when clicked, of which one can be chosen. |
|
/// </summary> |
|
/// <remarks> |
|
/// 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. |
|
/// |
|
/// When a dropdown event occurs a callback is sent to any registered listeners of onValueChanged. |
|
/// </remarks> |
|
public class TMP_Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler |
|
{ |
|
protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler |
|
{ |
|
[SerializeField] |
|
private TMP_Text m_Text; |
|
[SerializeField] |
|
private Image m_Image; |
|
[SerializeField] |
|
private RectTransform m_RectTransform; |
|
[SerializeField] |
|
private Toggle m_Toggle; |
|
|
|
public TMP_Text text { get { return m_Text; } set { m_Text = value; } } |
|
public Image image { get { return m_Image; } set { m_Image = value; } } |
|
public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } } |
|
public Toggle toggle { get { return m_Toggle; } set { m_Toggle = value; } } |
|
|
|
public virtual void OnPointerEnter(PointerEventData eventData) |
|
{ |
|
EventSystem.current.SetSelectedGameObject(gameObject); |
|
} |
|
|
|
public virtual void OnCancel(BaseEventData eventData) |
|
{ |
|
TMP_Dropdown dropdown = GetComponentInParent<TMP_Dropdown>(); |
|
if (dropdown) |
|
dropdown.Hide(); |
|
} |
|
} |
|
|
|
[Serializable] |
|
/// <summary> |
|
/// Class to store the text and/or image of a single option in the dropdown list. |
|
/// </summary> |
|
public class OptionData |
|
{ |
|
[SerializeField] |
|
private string m_Text; |
|
[SerializeField] |
|
private Sprite m_Image; |
|
|
|
/// <summary> |
|
/// The text associated with the option. |
|
/// </summary> |
|
public string text { get { return m_Text; } set { m_Text = value; } } |
|
|
|
/// <summary> |
|
/// The image associated with the option. |
|
/// </summary> |
|
public Sprite image { get { return m_Image; } set { m_Image = value; } } |
|
|
|
public OptionData() { } |
|
|
|
public OptionData(string text) |
|
{ |
|
this.text = text; |
|
} |
|
|
|
public OptionData(Sprite image) |
|
{ |
|
this.image = image; |
|
} |
|
|
|
/// <summary> |
|
/// Create an object representing a single option for the dropdown list. |
|
/// </summary> |
|
/// <param name="text">Optional text for the option.</param> |
|
/// <param name="image">Optional image for the option.</param> |
|
public OptionData(string text, Sprite image) |
|
{ |
|
this.text = text; |
|
this.image = image; |
|
} |
|
} |
|
|
|
[Serializable] |
|
/// <summary> |
|
/// Class used internally to store the list of options for the dropdown list. |
|
/// </summary> |
|
/// <remarks> |
|
/// The usage of this class is not exposed in the runtime API. It's only relevant for the PropertyDrawer drawing the list of options. |
|
/// </remarks> |
|
public class OptionDataList |
|
{ |
|
[SerializeField] |
|
private List<OptionData> m_Options; |
|
|
|
/// <summary> |
|
/// The list of options for the dropdown list. |
|
/// </summary> |
|
public List<OptionData> options { get { return m_Options; } set { m_Options = value; } } |
|
|
|
|
|
public OptionDataList() |
|
{ |
|
options = new List<OptionData>(); |
|
} |
|
} |
|
|
|
[Serializable] |
|
/// <summary> |
|
/// UnityEvent callback for when a dropdown current option is changed. |
|
/// </summary> |
|
public class DropdownEvent : UnityEvent<int> { } |
|
|
|
// Template used to create the dropdown. |
|
[SerializeField] |
|
private RectTransform m_Template; |
|
|
|
/// <summary> |
|
/// The Rect Transform of the template for the dropdown list. |
|
/// </summary> |
|
public RectTransform template { get { return m_Template; } set { m_Template = value; RefreshShownValue(); } } |
|
|
|
// Text to be used as a caption for the current value. It's not required, but it's kept here for convenience. |
|
[SerializeField] |
|
private TMP_Text m_CaptionText; |
|
|
|
/// <summary> |
|
/// The Text component to hold the text of the currently selected option. |
|
/// </summary> |
|
public TMP_Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } } |
|
|
|
[SerializeField] |
|
private Image m_CaptionImage; |
|
|
|
/// <summary> |
|
/// The Image component to hold the image of the currently selected option. |
|
/// </summary> |
|
public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } } |
|
|
|
[SerializeField] |
|
private Graphic m_Placeholder; |
|
|
|
/// <summary> |
|
/// The placeholder Graphic component. Shown when no option is selected. |
|
/// </summary> |
|
public Graphic placeholder { get { return m_Placeholder; } set { m_Placeholder = value; RefreshShownValue(); } } |
|
|
|
[Space] |
|
|
|
[SerializeField] |
|
private TMP_Text m_ItemText; |
|
|
|
/// <summary> |
|
/// The Text component to hold the text of the item. |
|
/// </summary> |
|
public TMP_Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } } |
|
|
|
[SerializeField] |
|
private Image m_ItemImage; |
|
|
|
/// <summary> |
|
/// The Image component to hold the image of the item |
|
/// </summary> |
|
public Image itemImage { get { return m_ItemImage; } set { m_ItemImage = value; RefreshShownValue(); } } |
|
|
|
[Space] |
|
|
|
[SerializeField] |
|
private int m_Value; |
|
|
|
[Space] |
|
|
|
// Items that will be visible when the dropdown is shown. |
|
// We box this into its own class so we can use a Property Drawer for it. |
|
[SerializeField] |
|
private OptionDataList m_Options = new OptionDataList(); |
|
|
|
/// <summary> |
|
/// The list of possible options. A text string and an image can be specified for each option. |
|
/// </summary> |
|
/// <remarks> |
|
/// 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. |
|
/// This also unlocks the ability to edit the Dropdown, including the insertion, removal, and finding of options, as well as other useful tools |
|
/// </remarks> |
|
/// /// <example> |
|
/// <code> |
|
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. |
|
/// |
|
/// using UnityEngine; |
|
/// using UnityEngine.UI; |
|
/// using System.Collections.Generic; |
|
/// using TMPro; |
|
/// |
|
/// public class Example : MonoBehaviour |
|
/// { |
|
/// //Use these for adding options to the Dropdown List |
|
/// TMP_Dropdown.OptionData m_NewData, m_NewData2; |
|
/// //The list of messages for the Dropdown |
|
/// List<TMP_Dropdown.OptionData> m_Messages = new List<TMP_Dropdown.OptionData>(); |
|
/// |
|
/// |
|
/// //This is the Dropdown |
|
/// TMP_Dropdown m_Dropdown; |
|
/// string m_MyString; |
|
/// int m_Index; |
|
/// |
|
/// void Start() |
|
/// { |
|
/// //Fetch the Dropdown GameObject the script is attached to |
|
/// m_Dropdown = GetComponent<TMP_Dropdown>(); |
|
/// //Clear the old options of the Dropdown menu |
|
/// m_Dropdown.ClearOptions(); |
|
/// |
|
/// //Create a new option for the Dropdown menu which reads "Option 1" and add to messages List |
|
/// m_NewData = new TMP_Dropdown.OptionData(); |
|
/// m_NewData.text = "Option 1"; |
|
/// m_Messages.Add(m_NewData); |
|
/// |
|
/// //Create a new option for the Dropdown menu which reads "Option 2" and add to messages List |
|
/// m_NewData2 = new TMP_Dropdown.OptionData(); |
|
/// m_NewData2.text = "Option 2"; |
|
/// m_Messages.Add(m_NewData2); |
|
/// |
|
/// //Take each entry in the message List |
|
/// foreach (TMP_Dropdown.OptionData message in m_Messages) |
|
/// { |
|
/// //Add each entry to the Dropdown |
|
/// m_Dropdown.options.Add(message); |
|
/// //Make the index equal to the total number of entries |
|
/// m_Index = m_Messages.Count - 1; |
|
/// } |
|
/// } |
|
/// |
|
/// //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. |
|
/// void OnGUI() |
|
/// { |
|
/// //TextField for user to type new entry to add to Dropdown |
|
/// m_MyString = GUI.TextField(new Rect(0, 40, 100, 40), m_MyString); |
|
/// |
|
/// //Press the "Add" Button to add a new entry to the Dropdown |
|
/// if (GUI.Button(new Rect(0, 0, 100, 40), "Add")) |
|
/// { |
|
/// //Make the index the last number of entries |
|
/// m_Index = m_Messages.Count; |
|
/// //Create a temporary option |
|
/// TMP_Dropdown.OptionData temp = new TMP_Dropdown.OptionData(); |
|
/// //Make the option the data from the TextField |
|
/// temp.text = m_MyString; |
|
/// |
|
/// //Update the messages list with the TextField data |
|
/// m_Messages.Add(temp); |
|
/// |
|
/// //Add the Textfield data to the Dropdown |
|
/// m_Dropdown.options.Insert(m_Index, temp); |
|
/// } |
|
/// |
|
/// //Press the "Remove" button to delete the selected option |
|
/// if (GUI.Button(new Rect(110, 0, 100, 40), "Remove")) |
|
/// { |
|
/// //Remove the current selected item from the Dropdown from the messages List |
|
/// m_Messages.RemoveAt(m_Dropdown.value); |
|
/// //Remove the current selection from the Dropdown |
|
/// m_Dropdown.options.RemoveAt(m_Dropdown.value); |
|
/// } |
|
/// } |
|
/// } |
|
/// </code> |
|
/// </example> |
|
public List<OptionData> options |
|
{ |
|
get { return m_Options.options; } |
|
set { m_Options.options = value; RefreshShownValue(); } |
|
} |
|
|
|
[Space] |
|
|
|
// Notification triggered when the dropdown changes. |
|
[SerializeField] |
|
private DropdownEvent m_OnValueChanged = new DropdownEvent(); |
|
|
|
/// <summary> |
|
/// A UnityEvent that is invoked when a user has clicked one of the options in the dropdown list. |
|
/// </summary> |
|
/// <remarks> |
|
/// 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. |
|
/// </remarks> |
|
/// <example> |
|
/// <code> |
|
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. |
|
/// //Set your own Text in the Inspector window |
|
/// |
|
/// using UnityEngine; |
|
/// using UnityEngine.UI; |
|
/// using TMPro; |
|
/// |
|
/// public class Example : MonoBehaviour |
|
/// { |
|
/// TMP_Dropdown m_Dropdown; |
|
/// public Text m_Text; |
|
/// |
|
/// void Start() |
|
/// { |
|
/// //Fetch the Dropdown GameObject |
|
/// m_Dropdown = GetComponent<TMP_Dropdown>(); |
|
/// //Add listener for when the value of the Dropdown changes, to take action |
|
/// m_Dropdown.onValueChanged.AddListener(delegate { |
|
/// DropdownValueChanged(m_Dropdown); |
|
/// }); |
|
/// |
|
/// //Initialize the Text to say the first value of the Dropdown |
|
/// m_Text.text = "First Value : " + m_Dropdown.value; |
|
/// } |
|
/// |
|
/// //Output the new value of the Dropdown into Text |
|
/// void DropdownValueChanged(TMP_Dropdown change) |
|
/// { |
|
/// m_Text.text = "New Value : " + change.value; |
|
/// } |
|
/// } |
|
/// </code> |
|
/// </example> |
|
public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } |
|
|
|
[SerializeField] |
|
private float m_AlphaFadeSpeed = 0.15f; |
|
|
|
/// <summary> |
|
/// The time interval at which a drop down will appear and disappear |
|
/// </summary> |
|
public float alphaFadeSpeed { get { return m_AlphaFadeSpeed; } set { m_AlphaFadeSpeed = value; } } |
|
|
|
private GameObject m_Dropdown; |
|
private GameObject m_Blocker; |
|
private List<DropdownItem> m_Items = new List<DropdownItem>(); |
|
private TweenRunner<FloatTween> m_AlphaTweenRunner; |
|
private bool validTemplate = false; |
|
private Coroutine m_Coroutine = null; |
|
|
|
private static OptionData s_NoOptionData = new OptionData(); |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
/// <example> |
|
/// <code> |
|
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. |
|
/// //Set your own Text in the Inspector window |
|
/// |
|
/// using UnityEngine; |
|
/// using UnityEngine.UI; |
|
/// using TMPro; |
|
/// |
|
/// public class Example : MonoBehaviour |
|
/// { |
|
/// //Attach this script to a Dropdown GameObject |
|
/// TMP_Dropdown m_Dropdown; |
|
/// //This is the string that stores the current selection m_Text of the Dropdown |
|
/// string m_Message; |
|
/// //This Text outputs the current selection to the screen |
|
/// public Text m_Text; |
|
/// //This is the index value of the Dropdown |
|
/// int m_DropdownValue; |
|
/// |
|
/// void Start() |
|
/// { |
|
/// //Fetch the DropDown component from the GameObject |
|
/// m_Dropdown = GetComponent<TMP_Dropdown>(); |
|
/// //Output the first Dropdown index value |
|
/// Debug.Log("Starting Dropdown Value : " + m_Dropdown.value); |
|
/// } |
|
/// |
|
/// void Update() |
|
/// { |
|
/// //Keep the current index of the Dropdown in a variable |
|
/// m_DropdownValue = m_Dropdown.value; |
|
/// //Change the message to say the name of the current Dropdown selection using the value |
|
/// m_Message = m_Dropdown.options[m_DropdownValue].text; |
|
/// //Change the on screen Text to reflect the current Dropdown selection |
|
/// m_Text.text = m_Message; |
|
/// } |
|
/// } |
|
/// </code> |
|
/// </example> |
|
public int value |
|
{ |
|
get |
|
{ |
|
return m_Value; |
|
} |
|
set |
|
{ |
|
SetValue(value); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Set index number of the current selection in the Dropdown without invoking onValueChanged callback. |
|
/// </summary> |
|
/// <param name="input">The new index for the current selection.</param> |
|
public void SetValueWithoutNotify(int input) |
|
{ |
|
SetValue(input, false); |
|
} |
|
|
|
void SetValue(int value, bool sendCallback = true) |
|
{ |
|
if (Application.isPlaying && (value == m_Value || options.Count == 0)) |
|
return; |
|
|
|
m_Value = Mathf.Clamp(value, m_Placeholder ? -1 : 0, options.Count - 1); |
|
RefreshShownValue(); |
|
|
|
if (sendCallback) |
|
{ |
|
// Notify all listeners |
|
UISystemProfilerApi.AddMarker("Dropdown.value", this); |
|
m_OnValueChanged.Invoke(m_Value); |
|
} |
|
} |
|
|
|
public bool IsExpanded { get { return m_Dropdown != null; } } |
|
|
|
protected TMP_Dropdown() { } |
|
|
|
protected override void Awake() |
|
{ |
|
#if UNITY_EDITOR |
|
if (!Application.isPlaying) |
|
return; |
|
#endif |
|
|
|
if (m_CaptionImage) |
|
m_CaptionImage.enabled = (m_CaptionImage.sprite != null); |
|
|
|
if (m_Template) |
|
m_Template.gameObject.SetActive(false); |
|
} |
|
|
|
protected override void Start() |
|
{ |
|
m_AlphaTweenRunner = new TweenRunner<FloatTween>(); |
|
m_AlphaTweenRunner.Init(this); |
|
base.Start(); |
|
|
|
RefreshShownValue(); |
|
} |
|
|
|
#if UNITY_EDITOR |
|
protected override void OnValidate() |
|
{ |
|
base.OnValidate(); |
|
|
|
if (!IsActive()) |
|
return; |
|
|
|
RefreshShownValue(); |
|
} |
|
#endif |
|
|
|
protected override void OnDisable() |
|
{ |
|
//Destroy dropdown and blocker in case user deactivates the dropdown when they click an option (case 935649) |
|
ImmediateDestroyDropdownList(); |
|
|
|
if (m_Blocker != null) |
|
DestroyBlocker(m_Blocker); |
|
|
|
m_Blocker = null; |
|
|
|
base.OnDisable(); |
|
} |
|
|
|
/// <summary> |
|
/// Refreshes the text and image (if available) of the currently selected option. |
|
/// </summary> |
|
/// <remarks> |
|
/// 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. |
|
/// </remarks> |
|
public void RefreshShownValue() |
|
{ |
|
OptionData data = s_NoOptionData; |
|
|
|
if (options.Count > 0 && m_Value >= 0) |
|
data = options[Mathf.Clamp(m_Value, 0, options.Count - 1)]; |
|
|
|
if (m_CaptionText) |
|
{ |
|
if (data != null && data.text != null) |
|
m_CaptionText.text = data.text; |
|
else |
|
m_CaptionText.text = ""; |
|
} |
|
|
|
if (m_CaptionImage) |
|
{ |
|
if (data != null) |
|
m_CaptionImage.sprite = data.image; |
|
else |
|
m_CaptionImage.sprite = null; |
|
m_CaptionImage.enabled = (m_CaptionImage.sprite != null); |
|
} |
|
|
|
if (m_Placeholder) |
|
{ |
|
m_Placeholder.enabled = options.Count == 0 || m_Value == -1; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Add multiple options to the options of the Dropdown based on a list of OptionData objects. |
|
/// </summary> |
|
/// <param name="options">The list of OptionData to add.</param> |
|
/// /// <remarks> |
|
/// See AddOptions(List<string> options) for code example of usages. |
|
/// </remarks> |
|
public void AddOptions(List<OptionData> options) |
|
{ |
|
this.options.AddRange(options); |
|
RefreshShownValue(); |
|
} |
|
|
|
/// <summary> |
|
/// Add multiple text-only options to the options of the Dropdown based on a list of strings. |
|
/// </summary> |
|
/// <remarks> |
|
/// Add a List of string messages to the Dropdown. The Dropdown shows each member of the list as a separate option. |
|
/// </remarks> |
|
/// <param name="options">The list of text strings to add.</param> |
|
/// <example> |
|
/// <code> |
|
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown - TextMeshPro. Attach this script to the Dropdown GameObject. |
|
/// |
|
/// using System.Collections.Generic; |
|
/// using UnityEngine; |
|
/// using UnityEngine.UI; |
|
/// using TMPro; |
|
/// |
|
/// public class Example : MonoBehaviour |
|
/// { |
|
/// //Create a List of new Dropdown options |
|
/// List<string> m_DropOptions = new List<string> { "Option 1", "Option 2"}; |
|
/// //This is the Dropdown |
|
/// TMP_Dropdown m_Dropdown; |
|
/// |
|
/// void Start() |
|
/// { |
|
/// //Fetch the Dropdown GameObject the script is attached to |
|
/// m_Dropdown = GetComponent<TMP_Dropdown>(); |
|
/// //Clear the old options of the Dropdown menu |
|
/// m_Dropdown.ClearOptions(); |
|
/// //Add the options created in the List above |
|
/// m_Dropdown.AddOptions(m_DropOptions); |
|
/// } |
|
/// } |
|
/// </code> |
|
/// </example> |
|
public void AddOptions(List<string> options) |
|
{ |
|
for (int i = 0; i < options.Count; i++) |
|
this.options.Add(new OptionData(options[i])); |
|
|
|
RefreshShownValue(); |
|
} |
|
|
|
/// <summary> |
|
/// Add multiple image-only options to the options of the Dropdown based on a list of Sprites. |
|
/// </summary> |
|
/// <param name="options">The list of Sprites to add.</param> |
|
/// <remarks> |
|
/// See AddOptions(List<string> options) for code example of usages. |
|
/// </remarks> |
|
public void AddOptions(List<Sprite> options) |
|
{ |
|
for (int i = 0; i < options.Count; i++) |
|
this.options.Add(new OptionData(options[i])); |
|
|
|
RefreshShownValue(); |
|
} |
|
|
|
/// <summary> |
|
/// Clear the list of options in the Dropdown. |
|
/// </summary> |
|
public void ClearOptions() |
|
{ |
|
options.Clear(); |
|
m_Value = m_Placeholder ? -1 : 0; |
|
RefreshShownValue(); |
|
} |
|
|
|
private void SetupTemplate() |
|
{ |
|
validTemplate = false; |
|
|
|
if (!m_Template) |
|
{ |
|
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); |
|
return; |
|
} |
|
|
|
GameObject templateGo = m_Template.gameObject; |
|
templateGo.SetActive(true); |
|
Toggle itemToggle = m_Template.GetComponentInChildren<Toggle>(); |
|
|
|
validTemplate = true; |
|
if (!itemToggle || itemToggle.transform == template) |
|
{ |
|
validTemplate = false; |
|
Debug.LogError("The dropdown template is not valid. The template must have a child GameObject with a Toggle component serving as the item.", template); |
|
} |
|
else if (!(itemToggle.transform.parent is RectTransform)) |
|
{ |
|
validTemplate = false; |
|
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); |
|
} |
|
else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform)) |
|
{ |
|
validTemplate = false; |
|
Debug.LogError("The dropdown template is not valid. The Item Text must be on the item GameObject or children of it.", template); |
|
} |
|
else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform)) |
|
{ |
|
validTemplate = false; |
|
Debug.LogError("The dropdown template is not valid. The Item Image must be on the item GameObject or children of it.", template); |
|
} |
|
|
|
if (!validTemplate) |
|
{ |
|
templateGo.SetActive(false); |
|
return; |
|
} |
|
|
|
DropdownItem item = itemToggle.gameObject.AddComponent<DropdownItem>(); |
|
item.text = m_ItemText; |
|
item.image = m_ItemImage; |
|
item.toggle = itemToggle; |
|
item.rectTransform = (RectTransform)itemToggle.transform; |
|
|
|
// Find the Canvas that this dropdown is a part of |
|
Canvas parentCanvas = null; |
|
Transform parentTransform = m_Template.parent; |
|
while (parentTransform != null) |
|
{ |
|
parentCanvas = parentTransform.GetComponent<Canvas>(); |
|
if (parentCanvas != null) |
|
break; |
|
|
|
parentTransform = parentTransform.parent; |
|
} |
|
|
|
Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo); |
|
popupCanvas.overrideSorting = true; |
|
popupCanvas.sortingOrder = 30000; |
|
|
|
// If we have a parent canvas, apply the same raycasters as the parent for consistency. |
|
if (parentCanvas != null) |
|
{ |
|
Component[] components = parentCanvas.GetComponents<BaseRaycaster>(); |
|
for (int i = 0; i < components.Length; i++) |
|
{ |
|
Type raycasterType = components[i].GetType(); |
|
if (templateGo.GetComponent(raycasterType) == null) |
|
{ |
|
templateGo.AddComponent(raycasterType); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
GetOrAddComponent<GraphicRaycaster>(templateGo); |
|
} |
|
|
|
GetOrAddComponent<CanvasGroup>(templateGo); |
|
templateGo.SetActive(false); |
|
|
|
validTemplate = true; |
|
} |
|
|
|
private static T GetOrAddComponent<T>(GameObject go) where T : Component |
|
{ |
|
T comp = go.GetComponent<T>(); |
|
if (!comp) |
|
comp = go.AddComponent<T>(); |
|
return comp; |
|
} |
|
|
|
/// <summary> |
|
/// Handling for when the dropdown is initially 'clicked'. Typically shows the dropdown |
|
/// </summary> |
|
/// <param name="eventData">The associated event data.</param> |
|
public virtual void OnPointerClick(PointerEventData eventData) |
|
{ |
|
Show(); |
|
} |
|
|
|
/// <summary> |
|
/// Handling for when the dropdown is selected and a submit event is processed. Typically shows the dropdown |
|
/// </summary> |
|
/// <param name="eventData">The associated event data.</param> |
|
public virtual void OnSubmit(BaseEventData eventData) |
|
{ |
|
Show(); |
|
} |
|
|
|
/// <summary> |
|
/// This will hide the dropdown list. |
|
/// </summary> |
|
/// <remarks> |
|
/// Called by a BaseInputModule when a Cancel event occurs. |
|
/// </remarks> |
|
/// <param name="eventData">The associated event data.</param> |
|
public virtual void OnCancel(BaseEventData eventData) |
|
{ |
|
Hide(); |
|
} |
|
|
|
/// <summary> |
|
/// Show the dropdown. |
|
/// |
|
/// Plan for dropdown scrolling to ensure dropdown is contained within screen. |
|
/// |
|
/// We assume the Canvas is the screen that the dropdown must be kept inside. |
|
/// This is always valid for screen space canvas modes. |
|
/// For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor. |
|
/// We consider it a fair constraint that the canvas must be big enough to contain dropdowns. |
|
/// </summary> |
|
public void Show() |
|
{ |
|
if (m_Coroutine != null) |
|
{ |
|
StopCoroutine(m_Coroutine); |
|
ImmediateDestroyDropdownList(); |
|
} |
|
|
|
if (!IsActive() || !IsInteractable() || m_Dropdown != null) |
|
return; |
|
|
|
// Get root Canvas. |
|
var list = TMP_ListPool<Canvas>.Get(); |
|
gameObject.GetComponentsInParent(false, list); |
|
if (list.Count == 0) |
|
return; |
|
|
|
Canvas rootCanvas = list[list.Count - 1]; |
|
for (int i = 0; i < list.Count; i++) |
|
{ |
|
if (list[i].isRootCanvas) |
|
{ |
|
rootCanvas = list[i]; |
|
break; |
|
} |
|
} |
|
|
|
TMP_ListPool<Canvas>.Release(list); |
|
|
|
if (!validTemplate) |
|
{ |
|
SetupTemplate(); |
|
if (!validTemplate) |
|
return; |
|
} |
|
|
|
m_Template.gameObject.SetActive(true); |
|
|
|
// 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) |
|
m_Template.GetComponent<Canvas>().sortingLayerID = rootCanvas.sortingLayerID; |
|
|
|
// Instantiate the drop-down template |
|
m_Dropdown = CreateDropdownList(m_Template.gameObject); |
|
m_Dropdown.name = "Dropdown List"; |
|
m_Dropdown.SetActive(true); |
|
|
|
// Make drop-down RectTransform have same values as original. |
|
RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform; |
|
dropdownRectTransform.SetParent(m_Template.transform.parent, false); |
|
|
|
// Instantiate the drop-down list items |
|
|
|
// Find the dropdown item and disable it. |
|
DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>(); |
|
|
|
GameObject content = itemTemplate.rectTransform.parent.gameObject; |
|
RectTransform contentRectTransform = content.transform as RectTransform; |
|
itemTemplate.rectTransform.gameObject.SetActive(true); |
|
|
|
// Get the rects of the dropdown and item |
|
Rect dropdownContentRect = contentRectTransform.rect; |
|
Rect itemTemplateRect = itemTemplate.rectTransform.rect; |
|
|
|
// Calculate the visual offset between the item's edges and the background's edges |
|
Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition; |
|
Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition; |
|
Vector2 itemSize = itemTemplateRect.size; |
|
|
|
m_Items.Clear(); |
|
|
|
Toggle prev = null; |
|
for (int i = 0; i < options.Count; ++i) |
|
{ |
|
OptionData data = options[i]; |
|
DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items); |
|
if (item == null) |
|
continue; |
|
|
|
// Automatically set up a toggle state change listener |
|
item.toggle.isOn = value == i; |
|
item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle)); |
|
|
|
// Select current option |
|
if (item.toggle.isOn) |
|
item.toggle.Select(); |
|
|
|
// Automatically set up explicit navigation |
|
if (prev != null) |
|
{ |
|
Navigation prevNav = prev.navigation; |
|
Navigation toggleNav = item.toggle.navigation; |
|
prevNav.mode = Navigation.Mode.Explicit; |
|
toggleNav.mode = Navigation.Mode.Explicit; |
|
|
|
prevNav.selectOnDown = item.toggle; |
|
prevNav.selectOnRight = item.toggle; |
|
toggleNav.selectOnLeft = prev; |
|
toggleNav.selectOnUp = prev; |
|
|
|
prev.navigation = prevNav; |
|
item.toggle.navigation = toggleNav; |
|
} |
|
prev = item.toggle; |
|
} |
|
|
|
// Reposition all items now that all of them have been added |
|
Vector2 sizeDelta = contentRectTransform.sizeDelta; |
|
sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y; |
|
contentRectTransform.sizeDelta = sizeDelta; |
|
|
|
float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height; |
|
if (extraSpace > 0) |
|
dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace); |
|
|
|
// Invert anchoring and position if dropdown is partially or fully outside of canvas rect. |
|
// Typically this will have the effect of placing the dropdown above the button instead of below, |
|
// but it works as inversion regardless of initial setup. |
|
Vector3[] corners = new Vector3[4]; |
|
dropdownRectTransform.GetWorldCorners(corners); |
|
|
|
RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform; |
|
Rect rootCanvasRect = rootCanvasRectTransform.rect; |
|
for (int axis = 0; axis < 2; axis++) |
|
{ |
|
bool outside = false; |
|
for (int i = 0; i < 4; i++) |
|
{ |
|
Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]); |
|
if ((corner[axis] < rootCanvasRect.min[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.min[axis])) || |
|
(corner[axis] > rootCanvasRect.max[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.max[axis]))) |
|
{ |
|
outside = true; |
|
break; |
|
} |
|
} |
|
if (outside) |
|
RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false); |
|
} |
|
|
|
for (int i = 0; i < m_Items.Count; i++) |
|
{ |
|
RectTransform itemRect = m_Items[i].rectTransform; |
|
itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0); |
|
itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0); |
|
itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y); |
|
itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y); |
|
} |
|
|
|
// Fade in the popup |
|
AlphaFadeList(m_AlphaFadeSpeed, 0f, 1f); |
|
|
|
// Make drop-down template and item template inactive |
|
m_Template.gameObject.SetActive(false); |
|
itemTemplate.gameObject.SetActive(false); |
|
|
|
m_Blocker = CreateBlocker(rootCanvas); |
|
} |
|
|
|
/// <summary> |
|
/// Create a blocker that blocks clicks to other controls while the dropdown list is open. |
|
/// </summary> |
|
/// <remarks> |
|
/// Override this method to implement a different way to obtain a blocker GameObject. |
|
/// </remarks> |
|
/// <param name="rootCanvas">The root canvas the dropdown is under.</param> |
|
/// <returns>The created blocker object</returns> |
|
protected virtual GameObject CreateBlocker(Canvas rootCanvas) |
|
{ |
|
// Create blocker GameObject. |
|
GameObject blocker = new GameObject("Blocker"); |
|
|
|
// Setup blocker RectTransform to cover entire root canvas area. |
|
RectTransform blockerRect = blocker.AddComponent<RectTransform>(); |
|
blockerRect.SetParent(rootCanvas.transform, false); |
|
blockerRect.anchorMin = Vector3.zero; |
|
blockerRect.anchorMax = Vector3.one; |
|
blockerRect.sizeDelta = Vector2.zero; |
|
|
|
// Make blocker be in separate canvas in same layer as dropdown and in layer just below it. |
|
Canvas blockerCanvas = blocker.AddComponent<Canvas>(); |
|
blockerCanvas.overrideSorting = true; |
|
Canvas dropdownCanvas = m_Dropdown.GetComponent<Canvas>(); |
|
blockerCanvas.sortingLayerID = dropdownCanvas.sortingLayerID; |
|
blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1; |
|
|
|
// Find the Canvas that this dropdown is a part of |
|
Canvas parentCanvas = null; |
|
Transform parentTransform = m_Template.parent; |
|
while (parentTransform != null) |
|
{ |
|
parentCanvas = parentTransform.GetComponent<Canvas>(); |
|
if (parentCanvas != null) |
|
break; |
|
|
|
parentTransform = parentTransform.parent; |
|
} |
|
|
|
// If we have a parent canvas, apply the same raycasters as the parent for consistency. |
|
if (parentCanvas != null) |
|
{ |
|
Component[] components = parentCanvas.GetComponents<BaseRaycaster>(); |
|
for (int i = 0; i < components.Length; i++) |
|
{ |
|
Type raycasterType = components[i].GetType(); |
|
if (blocker.GetComponent(raycasterType) == null) |
|
{ |
|
blocker.AddComponent(raycasterType); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Add raycaster since it's needed to block. |
|
GetOrAddComponent<GraphicRaycaster>(blocker); |
|
} |
|
|
|
|
|
// Add image since it's needed to block, but make it clear. |
|
Image blockerImage = blocker.AddComponent<Image>(); |
|
blockerImage.color = Color.clear; |
|
|
|
// Add button since it's needed to block, and to close the dropdown when blocking area is clicked. |
|
Button blockerButton = blocker.AddComponent<Button>(); |
|
blockerButton.onClick.AddListener(Hide); |
|
|
|
return blocker; |
|
} |
|
|
|
/// <summary> |
|
/// Convenience method to explicitly destroy the previously generated blocker object |
|
/// </summary> |
|
/// <remarks> |
|
/// 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. |
|
/// </remarks> |
|
/// <param name="blocker">The blocker object to destroy.</param> |
|
protected virtual void DestroyBlocker(GameObject blocker) |
|
{ |
|
Destroy(blocker); |
|
} |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
/// <remarks> |
|
/// Override this method to implement a different way to obtain a dropdown list GameObject. |
|
/// </remarks> |
|
/// <param name="template">The template to create the dropdown list from.</param> |
|
/// <returns>The created drop down list gameobject.</returns> |
|
protected virtual GameObject CreateDropdownList(GameObject template) |
|
{ |
|
return (GameObject)Instantiate(template); |
|
} |
|
|
|
/// <summary> |
|
/// Convenience method to explicitly destroy the previously generated dropdown list |
|
/// </summary> |
|
/// <remarks> |
|
/// Override this method to implement a different way to dispose of a dropdown list GameObject. |
|
/// </remarks> |
|
/// <param name="dropdownList">The dropdown list GameObject to destroy</param> |
|
protected virtual void DestroyDropdownList(GameObject dropdownList) |
|
{ |
|
Destroy(dropdownList); |
|
} |
|
|
|
/// <summary> |
|
/// Create a dropdown item based upon the item template. |
|
/// </summary> |
|
/// <remarks> |
|
/// Override this method to implement a different way to obtain an option item. |
|
/// The option item should correspond to the provided template DropdownItem and its GameObject, equivalent to instantiating a copy of it. |
|
/// </remarks> |
|
/// <param name="itemTemplate">e template to create the option item from.</param> |
|
/// <returns>The created dropdown item component</returns> |
|
protected virtual DropdownItem CreateItem(DropdownItem itemTemplate) |
|
{ |
|
return (DropdownItem)Instantiate(itemTemplate); |
|
} |
|
|
|
/// <summary> |
|
/// Convenience method to explicitly destroy the previously generated Items. |
|
/// </summary> |
|
/// <remarks> |
|
/// Override this method to implement a different way to dispose of an option item. |
|
/// Likely no action needed since destroying the dropdown list destroys all contained items as well. |
|
/// </remarks> |
|
/// <param name="item">The Item to destroy.</param> |
|
protected virtual void DestroyItem(DropdownItem item) { } |
|
|
|
// Add a new drop-down list item with the specified values. |
|
private DropdownItem AddItem(OptionData data, bool selected, DropdownItem itemTemplate, List<DropdownItem> items) |
|
{ |
|
// Add a new item to the dropdown. |
|
DropdownItem item = CreateItem(itemTemplate); |
|
item.rectTransform.SetParent(itemTemplate.rectTransform.parent, false); |
|
|
|
item.gameObject.SetActive(true); |
|
item.gameObject.name = "Item " + items.Count + (data.text != null ? ": " + data.text : ""); |
|
|
|
if (item.toggle != null) |
|
{ |
|
item.toggle.isOn = false; |
|
} |
|
|
|
// Set the item's data |
|
if (item.text) |
|
item.text.text = data.text; |
|
if (item.image) |
|
{ |
|
item.image.sprite = data.image; |
|
item.image.enabled = (item.image.sprite != null); |
|
} |
|
|
|
items.Add(item); |
|
return item; |
|
} |
|
|
|
private void AlphaFadeList(float duration, float alpha) |
|
{ |
|
CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); |
|
AlphaFadeList(duration, group.alpha, alpha); |
|
} |
|
|
|
private void AlphaFadeList(float duration, float start, float end) |
|
{ |
|
if (end.Equals(start)) |
|
return; |
|
|
|
FloatTween tween = new FloatTween { duration = duration, startValue = start, targetValue = end }; |
|
tween.AddOnChangedCallback(SetAlpha); |
|
tween.ignoreTimeScale = true; |
|
m_AlphaTweenRunner.StartTween(tween); |
|
} |
|
|
|
private void SetAlpha(float alpha) |
|
{ |
|
if (!m_Dropdown) |
|
return; |
|
|
|
CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); |
|
group.alpha = alpha; |
|
} |
|
|
|
/// <summary> |
|
/// Hide the dropdown list. I.e. close it. |
|
/// </summary> |
|
public void Hide() |
|
{ |
|
if (m_Coroutine == null) |
|
{ |
|
if (m_Dropdown != null) |
|
{ |
|
AlphaFadeList(m_AlphaFadeSpeed, 0f); |
|
|
|
// User could have disabled the dropdown during the OnValueChanged call. |
|
if (IsActive()) |
|
m_Coroutine = StartCoroutine(DelayedDestroyDropdownList(m_AlphaFadeSpeed)); |
|
} |
|
|
|
if (m_Blocker != null) |
|
DestroyBlocker(m_Blocker); |
|
|
|
m_Blocker = null; |
|
Select(); |
|
} |
|
} |
|
|
|
private IEnumerator DelayedDestroyDropdownList(float delay) |
|
{ |
|
yield return new WaitForSecondsRealtime(delay); |
|
ImmediateDestroyDropdownList(); |
|
} |
|
|
|
private void ImmediateDestroyDropdownList() |
|
{ |
|
for (int i = 0; i < m_Items.Count; i++) |
|
{ |
|
if (m_Items[i] != null) |
|
DestroyItem(m_Items[i]); |
|
} |
|
|
|
m_Items.Clear(); |
|
|
|
if (m_Dropdown != null) |
|
DestroyDropdownList(m_Dropdown); |
|
|
|
if (m_AlphaTweenRunner != null) |
|
m_AlphaTweenRunner.StopTween(); |
|
|
|
m_Dropdown = null; |
|
m_Coroutine = null; |
|
} |
|
|
|
// Change the value and hide the dropdown. |
|
private void OnSelectItem(Toggle toggle) |
|
{ |
|
if (!toggle.isOn) |
|
toggle.isOn = true; |
|
|
|
int selectedIndex = -1; |
|
Transform tr = toggle.transform; |
|
Transform parent = tr.parent; |
|
for (int i = 0; i < parent.childCount; i++) |
|
{ |
|
if (parent.GetChild(i) == tr) |
|
{ |
|
// Subtract one to account for template child. |
|
selectedIndex = i - 1; |
|
break; |
|
} |
|
} |
|
|
|
if (selectedIndex < 0) |
|
return; |
|
|
|
value = selectedIndex; |
|
Hide(); |
|
} |
|
} |
|
}
|
|
|