using System; using UnityEngine.Serialization; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.EventSystems; using DuloGames.UI.Tweens; namespace DuloGames.UI { [ExecuteInEditMode, DisallowMultipleComponent, AddComponentMenu("UI/Select Field", 58), RequireComponent(typeof(Image))] public class UISelectField : Toggle { public enum Direction { Auto, Down, Up } public enum VisualState { Normal, Highlighted, Pressed, Active, ActiveHighlighted, ActivePressed, Disabled } public enum ListAnimationType { None, Fade, Animation } public enum OptionTextTransitionType { None, CrossFade } public enum OptionTextEffectType { None, Shadow, Outline } // Currently selected item [HideInInspector] [SerializeField] private string m_SelectedItem; private List m_OptionObjects = new List(); private VisualState m_CurrentVisualState = VisualState.Normal; private bool m_PointerWasUsedOnOption = false; private GameObject m_ListObject; private ScrollRect m_ScrollRect; private GameObject m_ListContentObject; private CanvasGroup m_ListCanvasGroup; private Vector2 m_LastListSize = Vector2.zero; private GameObject m_StartSeparatorObject; private Navigation.Mode m_LastNavigationMode; private GameObject m_LastSelectedGameObject; private GameObject m_Blocker; [SerializeField] private Direction m_Direction = Direction.Auto; /// /// The direction in which the list should pop. /// public Direction direction { get { return this.m_Direction; } set { this.m_Direction = value; } } /// /// Private list of the select options. /// [SerializeField, FormerlySerializedAs("options")] private List m_Options = new List(); /// /// Gets the list of options. /// public List options { get { return this.m_Options; } } /// /// Currently selected option. /// public string value { get { return this.m_SelectedItem; } set { this.SelectOption(value); } } /// /// Gets the index of the selected option. /// /// The index of the selected option. public int selectedOptionIndex { get { return this.GetOptionIndex(this.m_SelectedItem); } } // The label text [SerializeField] private Text m_LabelText; // Select Field layout properties public new ColorBlockExtended colors = ColorBlockExtended.defaultColorBlock; public new SpriteStateExtended spriteState; public new AnimationTriggersExtended animationTriggers = new AnimationTriggersExtended(); // List layout properties public Sprite listBackgroundSprite; public Image.Type listBackgroundSpriteType = Image.Type.Sliced; public Color listBackgroundColor = Color.white; public RectOffset listMargins; public RectOffset listPadding; public float listSpacing = 0f; public ListAnimationType listAnimationType = ListAnimationType.Fade; public float listAnimationDuration = 0.1f; public RuntimeAnimatorController listAnimatorController; public string listAnimationOpenTrigger = "Open"; public string listAnimationCloseTrigger = "Close"; // Scroll rect properties public bool allowScrollRect = true; public ScrollRect.MovementType scrollMovementType = ScrollRect.MovementType.Clamped; public float scrollElasticity = 0.1f; public bool scrollInertia = false; public float scrollDecelerationRate = 0.135f; public float scrollSensitivity = 1f; public int scrollMinOptions = 5; public float scrollListHeight = 512f; public GameObject scrollBarPrefab; public float scrollbarSpacing = 34f; // Option text layout properties public Font optionFont = FontData.defaultFontData.font; public int optionFontSize = FontData.defaultFontData.fontSize; public FontStyle optionFontStyle = FontData.defaultFontData.fontStyle; public Color optionColor = Color.white; public OptionTextTransitionType optionTextTransitionType = OptionTextTransitionType.CrossFade; public ColorBlockExtended optionTextTransitionColors = ColorBlockExtended.defaultColorBlock; public RectOffset optionPadding; // Option text effect properties public OptionTextEffectType optionTextEffectType = OptionTextEffectType.None; public Color optionTextEffectColor = new Color(0f, 0f, 0f, 128f); public Vector2 optionTextEffectDistance = new Vector2(1f, -1f); public bool optionTextEffectUseGraphicAlpha = true; // Option background properties public Sprite optionBackgroundSprite; public Color optionBackgroundSpriteColor = Color.white; public Image.Type optionBackgroundSpriteType = Image.Type.Sliced; public Selectable.Transition optionBackgroundTransitionType = Selectable.Transition.None; public ColorBlockExtended optionBackgroundTransColors = ColorBlockExtended.defaultColorBlock; public SpriteStateExtended optionBackgroundSpriteStates; public AnimationTriggersExtended optionBackgroundAnimationTriggers = new AnimationTriggersExtended(); public RuntimeAnimatorController optionBackgroundAnimatorController; public Sprite optionHoverOverlay; public Color optionHoverOverlayColor = Color.white; public ColorBlock optionHoverOverlayColorBlock = ColorBlock.defaultColorBlock; public Sprite optionPressOverlay; public Color optionPressOverlayColor = Color.white; public ColorBlock optionPressOverlayColorBlock = ColorBlock.defaultColorBlock; // List separator properties public Sprite listSeparatorSprite; public Image.Type listSeparatorType = Image.Type.Simple; public Color listSeparatorColor = Color.white; public float listSeparatorHeight = 0f; public bool startSeparator = false; [Serializable] public class ChangeEvent : UnityEvent { } [Serializable] public class TransitionEvent : UnityEvent { } /// /// Event delegate triggered when the selected option changes. /// public ChangeEvent onChange = new ChangeEvent(); /// /// Event delegate triggered when the select field transition to a visual state. /// public TransitionEvent onTransition = new TransitionEvent(); // Tween controls [NonSerialized] private readonly TweenRunner m_FloatTweenRunner; // Called by Unity prior to deserialization, // should not be called by users protected UISelectField() { if (this.m_FloatTweenRunner == null) this.m_FloatTweenRunner = new TweenRunner(); this.m_FloatTweenRunner.Init(this); } protected override void Awake() { base.Awake(); // Get the background image if (this.targetGraphic == null) this.targetGraphic = this.GetComponent(); } protected override void Start() { base.Start(); // Prepare the toggle this.toggleTransition = ToggleTransition.None; } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); // Make sure we always have a font if (this.optionFont == null) this.optionFont = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font; } #endif protected override void OnEnable() { base.OnEnable(); // Hook the on change event this.onValueChanged.AddListener(OnToggleValueChanged); } protected override void OnDisable() { base.OnDisable(); // Unhook the on change event this.onValueChanged.RemoveListener(OnToggleValueChanged); // Close if open this.isOn = false; // Transition to the current state this.DoStateTransition(SelectionState.Disabled, true); } /// /// Open the select field list. /// public void Open() { this.isOn = true; } /// /// Closes the select field list. /// public void Close() { this.isOn = false; } /// /// Gets a value indicating whether the list is open. /// /// true if the list is open; otherwise, false. public bool IsOpen { get { return this.isOn; } } /// /// Gets the index of the given option. /// /// The option index. (-1 if the option was not found) /// Option value. public int GetOptionIndex(string optionValue) { // Find the option index in the options list if (this.m_Options != null && this.m_Options.Count > 0 && !string.IsNullOrEmpty(optionValue)) for (int i = 0; i < this.m_Options.Count; i++) if (optionValue.Equals(this.m_Options[i], System.StringComparison.OrdinalIgnoreCase)) return i; // Default return -1; } /// /// Selects the option by index. /// /// Option index. public void SelectOptionByIndex(int index) { // If the list is open, use the toggle to select the option if (this.IsOpen) { UISelectField_Option option = this.m_OptionObjects[index]; if (option != null) option.isOn = true; } else // otherwise set as selected { // Set as selected this.m_SelectedItem = this.m_Options[index]; // Trigger change this.TriggerChangeEvent(); } } /// /// Selects the option by value. /// /// The option value. public void SelectOption(string optionValue) { if (string.IsNullOrEmpty(optionValue)) return; // Get the option int index = this.GetOptionIndex(optionValue); // Check if the option index is valid if (index < 0 || index >= this.m_Options.Count) return; // Select the option this.SelectOptionByIndex(index); } /// /// Adds an option. /// /// Option value. public void AddOption(string optionValue) { if (this.m_Options != null) { this.m_Options.Add(optionValue); this.OptionListChanged(); } } /// /// Adds an option at given index. /// /// Option value. /// Index. public void AddOptionAtIndex(string optionValue, int index) { if (this.m_Options == null) return; // Check if the index is outside the list if (index >= this.m_Options.Count) { this.m_Options.Add(optionValue); } else { this.m_Options.Insert(index, optionValue); } this.OptionListChanged(); } /// /// Removes the option. /// /// Option value. public void RemoveOption(string optionValue) { if (this.m_Options == null) return; // Remove the option if exists if (this.m_Options.Contains(optionValue)) { this.m_Options.Remove(optionValue); this.OptionListChanged(); this.ValidateSelectedOption(); } } /// /// Removes the option at the given index. /// /// Index. public void RemoveOptionAtIndex(int index) { if (this.m_Options == null) return; // Remove the option if the index is valid if (index >= 0 && index < this.m_Options.Count) { this.m_Options.RemoveAt(index); this.OptionListChanged(); this.ValidateSelectedOption(); } } /// /// Clears the option list. /// public void ClearOptions() { if (this.m_Options == null) return; this.m_Options.Clear(); this.OptionListChanged(); } /// /// Validates the selected option and makes corrections if it's missing. /// public void ValidateSelectedOption() { if (this.m_Options == null) return; // Fix the selected option if it no longer exists if (!this.m_Options.Contains(this.m_SelectedItem)) { // Select the first option this.SelectOptionByIndex(0); } } /// /// Raises the option select event. /// /// Event data. /// Option. public void OnOptionSelect(string option) { if (string.IsNullOrEmpty(option)) return; // Save the current string to compare later string current = this.m_SelectedItem; // Save the string this.m_SelectedItem = option; // Trigger change event if (!current.Equals(this.m_SelectedItem)) this.TriggerChangeEvent(); // Close the list if it's opened and the pointer was used to select the option if (this.IsOpen && this.m_PointerWasUsedOnOption) { // Reset the value this.m_PointerWasUsedOnOption = false; // Close the list this.Close(); // Deselect the toggle base.OnDeselect(new BaseEventData(EventSystem.current)); } } /// /// Raises the option pointer up event (Used to close the list). /// /// Event data. public void OnOptionPointerUp(BaseEventData eventData) { // Flag to close the list on selection this.m_PointerWasUsedOnOption = true; } /// /// Tiggers the change event. /// protected virtual void TriggerChangeEvent() { // Apply the string to the label componenet if (this.m_LabelText != null) this.m_LabelText.text = this.m_SelectedItem; // Invoke the on change event if (onChange != null) onChange.Invoke(this.selectedOptionIndex, this.m_SelectedItem); } /// /// Raises the toggle value changed event (used to toggle the list). /// /// If set to true state. private void OnToggleValueChanged(bool state) { if (!Application.isPlaying) return; // Transition to the current state this.DoStateTransition(this.currentSelectionState, false); // Open / Close the list this.ToggleList(this.isOn); // Destroy the block on close if (!this.isOn && this.m_Blocker != null) Destroy(this.m_Blocker); } /// /// Raises the deselect event. /// /// Event data. public override void OnDeselect(BaseEventData eventData) { // Check if the mouse is over our options list if (this.m_ListObject != null) { UISelectField_List list = this.m_ListObject.GetComponent(); if (list.IsHighlighted()) return; } // Check if the mouse is over one of our options foreach (UISelectField_Option option in this.m_OptionObjects) { if (option.IsHighlighted()) return; } // When the select field loses focus // close the list by deactivating the toggle this.Close(); // Pass to base base.OnDeselect(eventData); } /// /// Raises the move event. /// /// Event data. public override void OnMove(AxisEventData eventData) { // Handle navigation for opened list if (this.IsOpen) { int prevIndex = (this.selectedOptionIndex - 1); int nextIndex = (this.selectedOptionIndex + 1); // Highlight the new option switch (eventData.moveDir) { case MoveDirection.Up: { if (prevIndex >= 0) { this.SelectOptionByIndex(prevIndex); } break; } case MoveDirection.Down: { if (nextIndex < this.m_Options.Count) { this.SelectOptionByIndex(nextIndex); } break; } } // Use the event eventData.Use(); } else { // Pass to base base.OnMove(eventData); } } /// /// Dos the state transition of the select field. /// /// State. /// If set to true instant. protected override void DoStateTransition(Selectable.SelectionState state, bool instant) { if (!this.gameObject.activeInHierarchy) return; Color color = this.colors.normalColor; Sprite newSprite = null; string triggername = this.animationTriggers.normalTrigger; // Check if this is the disabled state before any others if (state == Selectable.SelectionState.Disabled) { this.m_CurrentVisualState = VisualState.Disabled; color = this.colors.disabledColor; newSprite = this.spriteState.disabledSprite; triggername = this.animationTriggers.disabledTrigger; } else { // Prepare the state values switch (state) { case Selectable.SelectionState.Normal: this.m_CurrentVisualState = (this.isOn) ? VisualState.Active : VisualState.Normal; color = (this.isOn) ? this.colors.activeColor : this.colors.normalColor; newSprite = (this.isOn) ? this.spriteState.activeSprite : null; triggername = (this.isOn) ? this.animationTriggers.activeTrigger : this.animationTriggers.normalTrigger; break; case Selectable.SelectionState.Highlighted: this.m_CurrentVisualState = (this.isOn) ? VisualState.ActiveHighlighted : VisualState.Highlighted; color = (this.isOn) ? this.colors.activeHighlightedColor : this.colors.highlightedColor; newSprite = (this.isOn) ? this.spriteState.activeHighlightedSprite : this.spriteState.highlightedSprite; triggername = (this.isOn) ? this.animationTriggers.activeHighlightedTrigger : this.animationTriggers.highlightedTrigger; break; case Selectable.SelectionState.Pressed: this.m_CurrentVisualState = (this.isOn) ? VisualState.ActivePressed : VisualState.Pressed; color = (this.isOn) ? this.colors.activePressedColor : this.colors.pressedColor; newSprite = (this.isOn) ? this.spriteState.activePressedSprite : this.spriteState.pressedSprite; triggername = (this.isOn) ? this.animationTriggers.activePressedTrigger : this.animationTriggers.pressedTrigger; break; } } // Do the transition switch (this.transition) { case Selectable.Transition.ColorTint: this.StartColorTween(color * this.colors.colorMultiplier, (instant ? 0f : this.colors.fadeDuration)); break; case Selectable.Transition.SpriteSwap: this.DoSpriteSwap(newSprite); break; case Selectable.Transition.Animation: this.TriggerAnimation(triggername); break; } // Invoke the transition event if (this.onTransition != null) { this.onTransition.Invoke(this.m_CurrentVisualState, instant); } } /// /// Starts the color tween of the select field. /// /// Color. /// If set to true instant. private void StartColorTween(Color color, float duration) { if (this.targetGraphic == null) return; this.targetGraphic.CrossFadeColor(color, duration, true, true); } /// /// Does the sprite swap of the select field. /// /// New sprite. private void DoSpriteSwap(Sprite newSprite) { Image image = this.targetGraphic as Image; if (image == null) return; image.overrideSprite = newSprite; } /// /// Triggers the animation of the select field. /// /// Trigger. private void TriggerAnimation(string trigger) { if (this.animator == null || !this.animator.enabled || !this.animator.isActiveAndEnabled || this.animator.runtimeAnimatorController == null || !this.animator.hasBoundPlayables || string.IsNullOrEmpty(trigger)) return; this.animator.ResetTrigger(this.animationTriggers.normalTrigger); this.animator.ResetTrigger(this.animationTriggers.pressedTrigger); this.animator.ResetTrigger(this.animationTriggers.highlightedTrigger); this.animator.ResetTrigger(this.animationTriggers.activeTrigger); this.animator.ResetTrigger(this.animationTriggers.activeHighlightedTrigger); this.animator.ResetTrigger(this.animationTriggers.activePressedTrigger); this.animator.ResetTrigger(this.animationTriggers.disabledTrigger); this.animator.SetTrigger(trigger); } /// /// Toggles the list. /// /// If set to true state. protected virtual void ToggleList(bool state) { if (!this.IsActive()) return; // Check if the list is not yet created if (this.m_ListObject == null) this.CreateList(); // Make sure the creating of the list was successful if (this.m_ListObject == null) return; // Make sure we have the canvas group if (this.m_ListCanvasGroup != null) { // Disable or enable list interaction this.m_ListCanvasGroup.blocksRaycasts = state; } // Make sure navigation is enabled in open state if (state) { this.m_LastNavigationMode = this.navigation.mode; this.m_LastSelectedGameObject = EventSystem.current.currentSelectedGameObject; Navigation newNav = this.navigation; newNav.mode = Navigation.Mode.Vertical; this.navigation = newNav; // Set the select field as selected EventSystem.current.SetSelectedGameObject(this.gameObject); } else { Navigation newNav = this.navigation; newNav.mode = this.m_LastNavigationMode; this.navigation = newNav; if (!EventSystem.current.alreadySelecting && this.m_LastSelectedGameObject != null) EventSystem.current.SetSelectedGameObject(this.m_LastSelectedGameObject); } // Bring to front if (state) UIUtility.BringToFront(this.m_ListObject); // Start the opening/closing animation if (this.listAnimationType == ListAnimationType.None || this.listAnimationType == ListAnimationType.Fade) { float targetAlpha = (state ? 1f : 0f); // Fade In / Out this.TweenListAlpha(targetAlpha, ((this.listAnimationType == ListAnimationType.Fade) ? this.listAnimationDuration : 0f), true); } else if (this.listAnimationType == ListAnimationType.Animation) { this.TriggerListAnimation(state ? this.listAnimationOpenTrigger : this.listAnimationCloseTrigger); } } /// /// Creates the list and it's options. /// protected void CreateList() { // Get the root canvas Canvas rootCanvas = UIUtility.FindInParents(this.gameObject); // Reset the last list size this.m_LastListSize = Vector2.zero; // Clear the option texts list this.m_OptionObjects.Clear(); // Create the list game object with the necessary components this.m_ListObject = new GameObject("UISelectField - List", typeof(RectTransform)); this.m_ListObject.layer = this.gameObject.layer; // Change the parent of the list this.m_ListObject.transform.SetParent(this.transform, false); // Get the select field list component UISelectField_List listComp = this.m_ListObject.AddComponent(); // Make sure it's the top-most element UIAlwaysOnTop aot = this.m_ListObject.AddComponent(); aot.order = UIAlwaysOnTop.SelectFieldOrder; // Get the list canvas group component this.m_ListCanvasGroup = this.m_ListObject.AddComponent(); // Change the anchor and pivot of the list RectTransform rect = (this.m_ListObject.transform as RectTransform); rect.localScale = new Vector3(1f, 1f, 1f); rect.localPosition = Vector3.zero; rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.zero; rect.pivot = new Vector2(0f, 1f); // Prepare the position of the list rect.anchoredPosition = new Vector3(this.listMargins.left, (this.listMargins.top * -1f), 0f); // Prepare the width of the list float width = (this.transform as RectTransform).sizeDelta.x; if (this.listMargins.left > 0) width -= this.listMargins.left; else width += Math.Abs(this.listMargins.left); if (this.listMargins.right > 0) width -= this.listMargins.right; else width += Math.Abs(this.listMargins.right); rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width); // Hook the Dimensions Change event listComp.onDimensionsChange.AddListener(ListDimensionsChanged); // Apply the background sprite Image image = this.m_ListObject.AddComponent(); if (this.listBackgroundSprite != null) image.sprite = this.listBackgroundSprite; image.type = this.listBackgroundSpriteType; image.color = this.listBackgroundColor; if (this.allowScrollRect && this.m_Options.Count >= this.scrollMinOptions) { // Set the list height rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, this.scrollListHeight); // Add scroll rect GameObject scrollRectGo = new GameObject("Scroll Rect", typeof(RectTransform)); scrollRectGo.layer = this.m_ListObject.layer; scrollRectGo.transform.SetParent(this.m_ListObject.transform, false); RectTransform scrollRectRect = (scrollRectGo.transform as RectTransform); scrollRectRect.localScale = new Vector3(1f, 1f, 1f); scrollRectRect.localPosition = Vector3.zero; scrollRectRect.anchorMin = Vector2.zero; scrollRectRect.anchorMax = Vector2.one; scrollRectRect.pivot = new Vector2(0f, 1f); scrollRectRect.anchoredPosition = Vector2.zero; scrollRectRect.offsetMin = new Vector2(this.listPadding.left, this.listPadding.bottom); scrollRectRect.offsetMax = new Vector2(this.listPadding.right * -1f, this.listPadding.top * -1f); // Add scroll rect component this.m_ScrollRect = scrollRectGo.AddComponent(); this.m_ScrollRect.horizontal = false; this.m_ScrollRect.movementType = this.scrollMovementType; this.m_ScrollRect.elasticity = this.scrollElasticity; this.m_ScrollRect.inertia = this.scrollInertia; this.m_ScrollRect.decelerationRate = this.scrollDecelerationRate; this.m_ScrollRect.scrollSensitivity = this.scrollSensitivity; // Create the viewport GameObject viewPortGo = new GameObject("View Port", typeof(RectTransform)); viewPortGo.layer = this.m_ListObject.layer; viewPortGo.transform.SetParent(scrollRectGo.transform, false); RectTransform viewPortRect = (viewPortGo.transform as RectTransform); viewPortRect.localScale = new Vector3(1f, 1f, 1f); viewPortRect.localPosition = Vector3.zero; viewPortRect.anchorMin = Vector2.zero; viewPortRect.anchorMax = Vector2.one; viewPortRect.pivot = new Vector2(0f, 1f); viewPortRect.anchoredPosition = Vector2.zero; viewPortRect.offsetMin = Vector2.zero; viewPortRect.offsetMax = Vector2.zero; // Add image to the viewport Image viewImage = viewPortGo.AddComponent(); viewImage.raycastTarget = false; // Add mask to the viewport Mask viewMask = viewPortGo.AddComponent(); viewMask.showMaskGraphic = false; // Create content this.m_ListContentObject = new GameObject("Content", typeof(RectTransform)); this.m_ListContentObject.layer = this.m_ListObject.layer; this.m_ListContentObject.transform.SetParent(viewPortRect, false); RectTransform contentRect = (this.m_ListContentObject.transform as RectTransform); contentRect.localScale = new Vector3(1f, 1f, 1f); contentRect.localPosition = Vector3.zero; contentRect.anchorMin = new Vector2(0f, 1f); contentRect.anchorMax = new Vector2(0f, 1f); contentRect.pivot = new Vector2(0f, 1f); contentRect.anchoredPosition = Vector2.zero; contentRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.sizeDelta.x); // Add image to the content for easy scrolling Image contentImage = this.m_ListContentObject.AddComponent(); contentImage.color = new Color(1f, 1f, 1f, 0f); // Get the select field list component UISelectField_List contentListComp = this.m_ListContentObject.AddComponent(); contentListComp.onDimensionsChange.AddListener(ScrollContentDimensionsChanged); // Set the content and viewport to the scroll rect this.m_ScrollRect.content = contentRect; this.m_ScrollRect.viewport = viewPortRect; // Prepare the scroll bar if (this.scrollBarPrefab != null) { GameObject scrollBarGo = Instantiate(this.scrollBarPrefab, scrollRectGo.transform); this.m_ScrollRect.verticalScrollbar = scrollBarGo.GetComponent(); this.m_ScrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; this.m_ScrollRect.verticalScrollbarSpacing = this.scrollbarSpacing; } // Prepare the vertical layout group without list padding this.m_ListContentObject.AddComponent(); } else { // Use the list object as list content object this.m_ListContentObject = this.m_ListObject; // Prepare the vertical layout group with list padding VerticalLayoutGroup layoutGroup = this.m_ListContentObject.AddComponent(); layoutGroup.padding = this.listPadding; layoutGroup.spacing = this.listSpacing; } // Prepare the content size fitter ContentSizeFitter fitter = this.m_ListContentObject.AddComponent(); fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; // Get the list toggle group ToggleGroup toggleGroup = this.m_ListObject.AddComponent(); // Create the options for (int i = 0; i < this.m_Options.Count; i++) { if (i == 0 && this.startSeparator) this.m_StartSeparatorObject = this.CreateSeparator(i - 1); // Create the option this.CreateOption(i, toggleGroup); // Create a separator if this is not the last option if (i < (this.m_Options.Count - 1)) this.CreateSeparator(i); } // Prepare the list for the animation if (this.listAnimationType == ListAnimationType.None || this.listAnimationType == ListAnimationType.Fade) { // Starting alpha should be zero this.m_ListCanvasGroup.alpha = 0f; } else if (this.listAnimationType == ListAnimationType.Animation) { // Attach animator component Animator animator = this.m_ListObject.AddComponent(); // Set the animator controller animator.runtimeAnimatorController = this.listAnimatorController; // Set the animation triggers so we can use them to detect when animations finish listComp.SetTriggers(this.listAnimationOpenTrigger, this.listAnimationCloseTrigger); // Hook a callback on the finish event listComp.onAnimationFinish.AddListener(OnListAnimationFinish); } // Check if the navigation is disabled if (this.navigation.mode == Navigation.Mode.None) { this.CreateBlocker(rootCanvas); } // If we are using a scroll rect invoke the list dimensions change if (this.allowScrollRect && this.m_Options.Count >= this.scrollMinOptions) { this.ListDimensionsChanged(); } } protected virtual void CreateBlocker(Canvas rootCanvas) { // Create blocker GameObject. GameObject blocker = new GameObject("Blocker"); // Setup blocker RectTransform to cover entire root canvas area. RectTransform blockerRect = blocker.AddComponent(); blockerRect.SetParent(rootCanvas.transform, false); blockerRect.localScale = Vector3.one; blockerRect.localPosition = Vector3.zero; blockerRect.anchorMin = Vector3.zero; blockerRect.anchorMax = Vector3.one; blockerRect.sizeDelta = Vector2.zero; // Add image since it's needed to block, but make it clear. Image blockerImage = blocker.AddComponent(); 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