/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ using System.Collections.Generic; using Meta.WitAi; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; namespace Meta.Voice.Samples.TTSVoices { public class SimpleDropdownIndexEvent : UnityEvent {} public class SimpleDropdownOptionEvent : UnityEvent {} /// /// A dropdown ui interface that uses 'UnityEngine.Text' labels and /// builds content via a load method that accepts a list of strings. /// public class SimpleDropdownList : MonoBehaviour { [Header("Dropdown Toggle UI")] [SerializeField] [Tooltip("Button used for speaker dropdown toggle")] private Toggle _dropdownToggle; [SerializeField] [Tooltip("Button arrow image showing open or closed")] private Transform _dropdownButtonArrowImage; [SerializeField] [Tooltip("Button text to be displayed without option selected")] public string DropdownToggleUnselectedText = "..."; [Header("Dropdown List UI")] [SerializeField] [Tooltip("Canvas used for dropdown popup")] private CanvasGroup _dropdownListPopup; [SerializeField] [Tooltip("Dropdown scroll rect used for the dropdown list")] private ScrollRect _dropdownListScrollRect; [SerializeField] [Tooltip("Prefab used for canvas dropdown list")] private Toggle _dropdownListCellPrefab; [SerializeField] [Tooltip("Additional text padding for dropdown cells")] private float _dropdownCellTextPadding = 2f; [SerializeField] [Tooltip("All available options for this dropdown")] private string[] _options; public string[] Options => _options; [Header("Dropdown Default Settings")] [SerializeField] [Tooltip("The current option selected")] private int _selectedOptionIndex = -1; public int SelectedOptionIndex => _selectedOptionIndex; // Accessor for selected option public string SelectedOption { get { if (_options != null && SelectedOptionIndex >= 0 && SelectedOptionIndex < _options.Length) { return _options[SelectedOptionIndex]; } return null; } } [SerializeField] [Tooltip("Dropdown callback event for selection by int")] private SimpleDropdownIndexEvent _onIndexSelected = new SimpleDropdownIndexEvent(); public SimpleDropdownIndexEvent OnIndexSelected => _onIndexSelected; [SerializeField] [Tooltip("Dropdown callback event for selection by option")] private SimpleDropdownOptionEvent _onOptionSelected = new SimpleDropdownOptionEvent(); public SimpleDropdownOptionEvent OnOptionSelected => _onOptionSelected; /// /// Whether the popup is currently showing or not /// public bool IsShowing { get; private set; } // Data currently being used private List _cells = new List(); /// /// On awake, find dropdown cell /// protected virtual void Awake() { FindCellPrefab(); } // Find existing toggle if no prefab is assigned private void FindCellPrefab() { if (_dropdownListCellPrefab == null) { _dropdownListCellPrefab = _dropdownListScrollRect.content.GetComponentInChildren(); if (_dropdownListCellPrefab != null) { _dropdownListCellPrefab.gameObject.SetActive(false); } else { Debug.LogError("No Dropdown Cell Prefab Found"); } } } /// /// Perform dropdown setup methods /// protected virtual void Start() { // Hide SetShowing(false); // Load dropdown if (_options != null && _options.Length > 0) { LoadDropdown(Options); } // Force a selection refresh int index = _selectedOptionIndex; _selectedOptionIndex = index - 1; SelectOption(index); } /// /// Add toggle delegate /// protected virtual void OnEnable() { _dropdownToggle.onValueChanged.AddListener(OnToggleClick); } /// /// Remove toggle delegate /// protected virtual void OnDisable() { _dropdownToggle.onValueChanged.RemoveListener(OnToggleClick); } #if ENABLE_LEGACY_INPUT_MANAGER /// /// Hide if touch outside of the scroll rect & toggle button /// protected virtual void Update() { if (IsShowing && Input.GetMouseButtonDown(0) && _dropdownListScrollRect != null && _dropdownToggle != null) { Vector2 touchPosition = Input.mousePosition; if (!IsInRect(_dropdownListScrollRect.viewport, touchPosition) && !IsInRect(_dropdownToggle.GetComponent(), touchPosition)) { SetShowing(false); } } } // Check for rect private static bool IsInRect(RectTransform rectTransform, Vector2 touchPosition) { // No main camera or rect Camera cam = Camera.main; if (cam == null || rectTransform == null) { return false; } // Check mouse position screen point return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, touchPosition, cam); } #endif #region LOAD /// /// Load dropdown with specified options /// /// The options to be used public void LoadDropdown(string[] newOptions) { // Get options _options = newOptions; // Disable all previous cells foreach (var cell in _cells) { cell.gameObject.SetActive(false); } // Setup if needed, or log error FindCellPrefab(); if (_dropdownListCellPrefab == null) { VLog.W($"Cannot load {gameObject.name} without a cell prefab"); return; } // Iterate all options float height = 0f; int total = _options == null ? 0 : _options.Length; for (int c = 0; c < total; c++) { // Get existing cell if possible Toggle cell = c < _cells.Count ? _cells[c] : null; // Instantiate if needed if (cell == null) { // Instantiate cell = Instantiate(_dropdownListCellPrefab.gameObject).GetComponent(); cell.name = $"{_dropdownListCellPrefab.gameObject.name}_CELL_{c:000}"; cell.transform.SetParent(_dropdownListScrollRect.content, false); // Override cell if (c < _cells.Count) { _cells[c] = cell; } // Add new cell else { _cells.Add(cell); } } // Enable & load cell.gameObject.SetActive(true); LoadCell(cell, c, _options[c], ref height); } // Set height to content of scrolldown list _dropdownListScrollRect.content.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0f, height); } /// /// Load a specific cell /// /// Cell index /// Height of cell protected virtual void LoadCell(Toggle cell, int index, string option, ref float y) { // Apply name to cell Text cellText = cell.GetComponentInChildren(); if (cellText != null) { cellText.text = option; } // Apply callback to cell cell.onValueChanged.RemoveAllListeners(); cell.isOn = false; cell.onValueChanged.AddListener((isSelected) => OnCellClick(index, isSelected)); // Apply width to cell RectTransform cellRect = cell.GetComponent(); cellRect.anchorMin = new Vector2(0f, 1f); cellRect.anchorMax = new Vector2(1f, 1f); cellRect.pivot = new Vector2(0.5f, 1f); cellRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _dropdownListScrollRect.content.rect.width); cellRect.anchoredPosition = new Vector2(0f, -y); // Apply height float height = cellRect.rect.height; if (cellText != null) { float textMargin = Mathf.Max(0f, height - cellText.rectTransform.rect.height); height = Mathf.CeilToInt(cellText.preferredHeight + _dropdownCellTextPadding + textMargin); cellRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height); } // Add new height y += height; } #endregion #region SELECTION /// /// Select an item in the dropdown list /// /// The option index to be selected public void SelectOption(int newIndex) { // Ignore if same if (newIndex == SelectedOptionIndex) { return; } // Apply new index _selectedOptionIndex = newIndex; // Get option text string optionText = SelectedOption; // Apply to button Text buttonText = _dropdownToggle.GetComponentInChildren(); if (buttonText != null) { if (string.IsNullOrEmpty(optionText)) { buttonText.text = DropdownToggleUnselectedText; } else { buttonText.text = optionText; } } // Adjust cells int total = _cells == null ? 0 : _cells.Count; for (int i = 0; i < total; i++) { _cells[i].isOn = i == SelectedOptionIndex; _cells[i].interactable = i != SelectedOptionIndex; } // Call event OnOptionSelected?.Invoke(optionText); OnIndexSelected?.Invoke(SelectedOptionIndex); // Selected if (IsShowing) { SetShowing(false); } } /// /// Select option by name /// /// The option to be selected public void SelectOption(string option) => SelectOption(GetOptionIndex(option)); /// /// When a cell is clicked, select a specific option /// /// Cell index clicked /// Whether cell was selected or unselected private void OnCellClick(int index, bool toSelected) { if (toSelected && index != SelectedOptionIndex) { SelectOption(index); } } /// /// A method for obtaining the index of a dropdown option string. /// /// Accepts an option text string that matches a loaded text option. /// Returns the index of the provided option string public int GetOptionIndex(string option) { if (_options != null) { for (int o = 0; o < _options.Length; o++) { if (string.Equals(_options[o], option)) { return o; } } } return -1; } #endregion #region APPEARANCE /// /// Show or hide the popup /// /// The option index to be selected public void SetShowing(bool toShowing) { if (toShowing && (Options == null || Options.Length <= 0)) { Debug.Log("SimpleDropdownList - Cannot show without any options"); return; } IsShowing = toShowing; _dropdownToggle.isOn = IsShowing; if (_dropdownListPopup != null) { _dropdownListPopup.alpha = toShowing ? 1f : 0f; _dropdownListPopup.interactable = toShowing; _dropdownListPopup.blocksRaycasts = toShowing; } if (_dropdownButtonArrowImage != null) { _dropdownButtonArrowImage.localRotation = Quaternion.Euler(0f, 0f, IsShowing ? 0f : 180f); } } /// /// Called when toggle button is clicked /// private void OnToggleClick(bool toToggle) { if (toToggle != IsShowing) { SetShowing(toToggle); } } #endregion } }