public 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; } }
///<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> publicOptionData(string text, Sprite image) { this.text = text; this.image = image; } }
///<summary> /// The list of options for the dropdown list. ///</summary> public List<OptionData> options { get { return m_Options; } set { m_Options = value; } }
publicOptionDataList() { options = new List<OptionData>(); } }
protectedoverridevoidOnDisable() { //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;
///<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> protectedvirtualvoidDestroyItem(DropdownItem item) {}
// Get root Canvas. var list = ListPool<Canvas>.Get(); gameObject.GetComponentsInParent(false, list); if (list.Count == 0) return;
// case 1064466 rootCanvas should be last element returned by GetComponentsInParent() Canvas rootCanvas = list[list.Count - 1]; for (int i = 0; i < list.Count; i++) { if (list[i].isRootCanvas) { rootCanvas = list[i]; break; } }
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;
// 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>();
// 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;
// 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);
// 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;
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;
// 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); }
// 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;
// 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); }
privatevoidOnSelectItem(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; } }
// 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);
publicvoidHide() { if (m_Dropdown != null) { AlphaFadeList(m_AlphaFadeSpeed, 0f);
// User could have disabled the dropdown during the OnValueChanged call. if (IsActive()) StartCoroutine(DelayedDestroyDropdownList(m_AlphaFadeSpeed)); } if (m_Blocker != null) DestroyBlocker(m_Blocker); m_Blocker = null; Select(); }