791 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			791 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| // Recompile at 9/29/2025 7:21:05 PM
 | |
| // Copyright (c) Pixel Crushers. All rights reserved.
 | |
| 
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Reflection;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Events;
 | |
| 
 | |
| namespace PixelCrushers.DialogueSystem
 | |
| {
 | |
| 
 | |
|     [AddComponentMenu("")] // Use wrapper.
 | |
|     public class StandardUIMenuPanel : UIPanel
 | |
|     {
 | |
| 
 | |
|         #region Serialized Fields
 | |
| 
 | |
|         [Tooltip("(Optional) Main response menu panel.")]
 | |
|         public UnityEngine.UI.Graphic panel;
 | |
| 
 | |
|         [Tooltip("(Optional) Image to show PC portrait during response menu.")]
 | |
|         public UnityEngine.UI.Image pcImage;
 | |
| 
 | |
|         [Tooltip("(Optional) Text element to show PC name during response menu.")]
 | |
|         public UITextField pcName;
 | |
| 
 | |
|         [Tooltip("Set PC Image to actor portrait's native size. Image's Rect Transform can't use Stretch anchors.")]
 | |
|         public bool usePortraitNativeSize = false;
 | |
| 
 | |
|         [Tooltip("(Optional) Slider for timed menus.")]
 | |
|         public UnityEngine.UI.Slider timerSlider;
 | |
| 
 | |
|         [Tooltip("Assign design-time positioned buttons starting with first or last button.")]
 | |
|         public ResponseButtonAlignment buttonAlignment = ResponseButtonAlignment.ToFirst;
 | |
| 
 | |
|         [Tooltip("Show buttons that aren't assigned to any responses. If using a 'dialogue wheel' for example, you'll want to show unused buttons so the entire wheel structure is visible.")]
 | |
|         public bool showUnusedButtons = false;
 | |
| 
 | |
|         [Tooltip("Design-time positioned response buttons. (Optional if Button Template is assigned.)")]
 | |
|         public StandardUIResponseButton[] buttons;
 | |
| 
 | |
|         [Tooltip("Template from which to instantiate response buttons. (Optional if using Buttons list above.)")]
 | |
|         public StandardUIResponseButton buttonTemplate;
 | |
| 
 | |
|         [Tooltip("If using Button Template, instantiate buttons under this GameObject.")]
 | |
|         public UnityEngine.UI.Graphic buttonTemplateHolder;
 | |
| 
 | |
|         [Tooltip("(Optional) Scrollbar to use if instantiated button holder is in a scroll rect.")]
 | |
|         public UnityEngine.UI.Scrollbar buttonTemplateScrollbar;
 | |
| 
 | |
|         [Tooltip("(Optional) Component that enables or disables scrollbar as necessary for content.")]
 | |
|         public UIScrollbarEnabler scrollbarEnabler;
 | |
| 
 | |
|         [Tooltip("Reset the scroll bar to this value when preparing response menu. To skip resetting the scrollbar, specify a negative value.")]
 | |
|         public float buttonTemplateScrollbarResetValue = 1;
 | |
| 
 | |
|         [Tooltip("Automatically set up explicit joystick/keyboard navigation for instantiated template buttons instead of using Automatic navigation.")]
 | |
|         public bool explicitNavigationForTemplateButtons = true;
 | |
| 
 | |
|         [Tooltip("If explicit navigation is enabled, loop around when navigating past end of menu.")]
 | |
|         public bool loopExplicitNavigation = false;
 | |
| 
 | |
|         public UIAutonumberSettings autonumber = new UIAutonumberSettings();
 | |
| 
 | |
|         [Tooltip("If non-zero, prevent input for this duration in seconds when opening menu.")]
 | |
|         public float blockInputDuration = 0;
 | |
| 
 | |
|         [Tooltip("During block input duration, keep selected response button in selected visual state.")]
 | |
|         public bool showSelectionWhileInputBlocked = false;
 | |
| 
 | |
|         [Tooltip("Log a warning if a response button text is blank.")]
 | |
|         public bool warnOnEmptyResponseText = false;
 | |
| 
 | |
|         public UnityEvent onContentChanged = new UnityEvent();
 | |
| 
 | |
|         [Tooltip("When focusing panel, set this animator trigger.")]
 | |
|         public string focusAnimationTrigger = string.Empty;
 | |
| 
 | |
|         [Tooltip("When unfocusing panel, set this animator trigger.")]
 | |
|         public string unfocusAnimationTrigger = string.Empty;
 | |
| 
 | |
|         [Tooltip("Wait for panels within this dialogue UI (not external) to close before showing menu.")]
 | |
|         public bool waitForClose = false;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Invoked when the subtitle panel gains focus.
 | |
|         /// </summary>
 | |
|         public UnityEvent onFocus = new UnityEvent();
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Invoked when the subtitle panel loses focus.
 | |
|         /// </summary>
 | |
|         public UnityEvent onUnfocus = new UnityEvent();
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Public Properties
 | |
| 
 | |
|         [SerializeField, Tooltip("Panel is currently in focused state.")]
 | |
|         private bool m_hasFocus = false;
 | |
|         public virtual bool hasFocus
 | |
|         {
 | |
|             get { return m_hasFocus; }
 | |
|             protected set { m_hasFocus = value; }
 | |
|         }
 | |
| 
 | |
|         public override bool waitForShowAnimation { get { return true; } }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The instantiated buttons. These are only valid during a specific response menu,
 | |
|         /// and only if you're using templates. Each showing of the response menu clears 
 | |
|         /// this list and re-populates it with new buttons.
 | |
|         /// </summary>
 | |
|         public List<GameObject> instantiatedButtons { get { return m_instantiatedButtons; } }
 | |
|         private List<GameObject> m_instantiatedButtons = new List<GameObject>();
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Internal Fields
 | |
| 
 | |
|         protected List<GameObject> instantiatedButtonPool { get { return m_instantiatedButtonPool; } }
 | |
|         private List<GameObject> m_instantiatedButtonPool = new List<GameObject>();
 | |
|         private string m_processedAutonumberFormat = string.Empty;
 | |
|         private Coroutine m_scrollbarCoroutine = null;
 | |
|         protected const float WaitForCloseTimeoutDuration = 8f;
 | |
| 
 | |
|         protected StandardUITimer m_timer = null;
 | |
|         protected System.Action m_timeoutHandler = null;
 | |
|         protected CanvasGroup m_mainCanvasGroup = null;
 | |
|         protected static bool s_isInputDisabled = false;
 | |
|         private StandardDialogueUI m_dialogueUI = null;
 | |
|         protected StandardDialogueUI dialogueUI
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 if (m_dialogueUI == null) m_dialogueUI = GetComponentInParent<StandardDialogueUI>();
 | |
|                 return m_dialogueUI ?? DialogueManager.standardDialogueUI;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Initialization
 | |
| 
 | |
|         public virtual void Awake()
 | |
|         {
 | |
|             Tools.SetGameObjectActive(buttonTemplate, false);
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Show & Hide
 | |
| 
 | |
|         protected override void Update()
 | |
|         {
 | |
|             if (s_isInputDisabled)
 | |
|             {
 | |
|                 if (eventSystem != null) eventSystem.SetSelectedGameObject(null);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 base.Update();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override void CheckFocus()
 | |
|         {
 | |
|             if (s_isInputDisabled) return;
 | |
|             base.CheckFocus();
 | |
|         }
 | |
| 
 | |
|         public virtual void SetPCPortrait(Sprite portraitSprite, string portraitName)
 | |
|         {
 | |
|             if (pcImage != null)
 | |
|             {
 | |
|                 Tools.SetGameObjectActive(pcImage, portraitSprite != null);
 | |
|                 pcImage.sprite = portraitSprite;
 | |
|                 if (usePortraitNativeSize && portraitSprite != null)
 | |
|                 {
 | |
|                     pcImage.rectTransform.sizeDelta = portraitSprite.packed ?
 | |
|                         new Vector2(portraitSprite.rect.width, portraitSprite.rect.height) :
 | |
|                         new Vector2(portraitSprite.texture.width, portraitSprite.texture.height);
 | |
|                 }
 | |
|             }
 | |
|             pcName.text = portraitName;
 | |
|         }
 | |
| 
 | |
|         [System.Obsolete("Use SetPCPortrait(Sprite,string) instead.")]
 | |
|         public virtual void SetPCPortrait(Texture2D portraitTexture, string portraitName)
 | |
|         {
 | |
|             SetPCPortrait(UITools.CreateSprite(portraitTexture), portraitName);
 | |
|         }
 | |
| 
 | |
|         public virtual void ShowResponses(Subtitle subtitle, Response[] responses, Transform target)
 | |
|         {
 | |
|             if (waitForClose && dialogueUI != null)
 | |
|             {
 | |
|                 if (dialogueUI.AreAnyPanelsClosing())
 | |
|                 {
 | |
|                     DialogueManager.instance.StartCoroutine(ShowAfterPanelsClose(subtitle, responses, target));
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|             CheckForBlankResponses(responses);
 | |
|             ShowResponsesNow(subtitle, responses, target);
 | |
|         }
 | |
| 
 | |
|         private void CheckForBlankResponses(Response[] responses)
 | |
|         {
 | |
|             if (!DialogueDebug.logWarnings) return;
 | |
|             if (responses == null) return;
 | |
|             foreach (Response response in responses)
 | |
|             {
 | |
|                 if (string.IsNullOrEmpty(response.formattedText.text))
 | |
|                 {
 | |
|                     Debug.LogWarning($"Dialogue System: Response [{response.destinationEntry.conversationID}:{response.destinationEntry.id}] has no text for a response button.");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected virtual void ShowResponsesNow(Subtitle subtitle, Response[] responses, Transform target)
 | |
|         {
 | |
|             if (responses == null || responses.Length == 0)
 | |
|             {
 | |
|                 if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: StandardDialogueUI ShowResponses received an empty list of responses.", this);
 | |
|                 return;
 | |
|             }
 | |
|             ClearResponseButtons();
 | |
|             SetResponseButtons(responses, target);
 | |
|             ActivateUIElements();
 | |
|             Open();
 | |
|             Focus();
 | |
|             RefreshSelectablesList();
 | |
|             if (blockInputDuration > 0)
 | |
|             {
 | |
|                 DisableInput();
 | |
|                 if (InputDeviceManager.autoFocus) SetFocus(firstSelected);
 | |
|                 if (Mathf.Approximately(0, Time.timeScale))
 | |
|                 { 
 | |
|                     StartCoroutine(EnableInputAfterDuration(blockInputDuration));
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     Invoke(nameof(EnableInput), blockInputDuration);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (InputDeviceManager.autoFocus) SetFocus(firstSelected);
 | |
|                 if (s_isInputDisabled) EnableInput();
 | |
|             }
 | |
| #if TMP_PRESENT
 | |
|             DialogueManager.instance.StartCoroutine(CheckTMProAutoScroll());
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         private IEnumerator EnableInputAfterDuration(float duration)
 | |
|         {
 | |
|             yield return new WaitForSecondsRealtime(duration);
 | |
|             EnableInput();
 | |
|         }
 | |
| 
 | |
| #if TMP_PRESENT
 | |
|         // Handles edge case where TMPro uses autoscroll but entry ends before typing starts.
 | |
|         // In this case, this method updates the autoscroll size.
 | |
|         protected IEnumerator CheckTMProAutoScroll()
 | |
|         {
 | |
|             var ui = GetComponentInParent<StandardDialogueUI>();
 | |
|             if (ui == null || ui.conversationUIElements.defaultNPCSubtitlePanel == null || ui.conversationUIElements.defaultNPCSubtitlePanel.subtitleText == null) yield break;
 | |
|             var tmp = ui.conversationUIElements.defaultNPCSubtitlePanel.subtitleText.textMeshProUGUI;
 | |
|             if (tmp == null) yield break;
 | |
|             var layoutElement = tmp.GetComponent<UnityEngine.UI.LayoutElement>();
 | |
|             if (layoutElement != null) layoutElement.preferredHeight = -1;
 | |
|             var uiScrollbarEnabler = GetComponentInParent<UIScrollbarEnabler>();
 | |
|             if (uiScrollbarEnabler != null)
 | |
|             {
 | |
|                 yield return null;
 | |
|                 uiScrollbarEnabler.CheckScrollbarWithResetValue(buttonTemplateScrollbarResetValue);
 | |
|             }
 | |
|         }
 | |
| #endif
 | |
| 
 | |
|         protected virtual IEnumerator ShowAfterPanelsClose(Subtitle subtitle, Response[] responses, Transform target)
 | |
|         {
 | |
|             if (dialogueUI != null)
 | |
|             {
 | |
|                 float safeguardTime = Time.realtimeSinceStartup + WaitForCloseTimeoutDuration;
 | |
|                 while (dialogueUI.AreAnyPanelsClosing() && Time.realtimeSinceStartup < safeguardTime)
 | |
|                 {
 | |
|                     yield return null;
 | |
|                 }
 | |
|             }
 | |
|             ShowResponsesNow(subtitle, responses, target);
 | |
|         }
 | |
| 
 | |
|         public virtual void HideResponses()
 | |
|         {
 | |
|             StopTimer();
 | |
|             Unfocus();
 | |
|             Close();
 | |
|         }
 | |
| 
 | |
|         public override void Close()
 | |
|         {
 | |
|             if (isOpen) base.Close();
 | |
|         }
 | |
| 
 | |
|         public virtual void Focus()
 | |
|         {
 | |
|             if (hasFocus) return;
 | |
|             if (panelState == PanelState.Opening && enabled && gameObject.activeInHierarchy)
 | |
|             {
 | |
|                 StartCoroutine(FocusWhenOpen());
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 FocusNow();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected IEnumerator FocusWhenOpen()
 | |
|         {
 | |
|             float timeout = Time.realtimeSinceStartup + 5f;
 | |
|             while (panelState != PanelState.Open && Time.realtimeSinceStartup < timeout)
 | |
|             {
 | |
|                 yield return null;
 | |
|             }
 | |
|             FocusNow();
 | |
|         }
 | |
| 
 | |
|         protected virtual void FocusNow()
 | |
|         {
 | |
|             panelState = PanelState.Open;
 | |
|             animatorMonitor.SetTrigger(focusAnimationTrigger, null, false);
 | |
|             UITools.EnableInteractivity(gameObject);
 | |
|             if (hasFocus) return;
 | |
|             if (string.IsNullOrEmpty(focusAnimationTrigger))
 | |
|             {
 | |
|                 OnFocused();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 animatorMonitor.SetTrigger(focusAnimationTrigger, OnFocused, true);
 | |
|             }
 | |
|             onFocus.Invoke();
 | |
|         }
 | |
| 
 | |
|         private void OnFocused()
 | |
|         {
 | |
|             hasFocus = true;
 | |
|         }
 | |
| 
 | |
|         public virtual void Unfocus()
 | |
|         {
 | |
|             if (!hasFocus) return;
 | |
|             hasFocus = false;
 | |
|             animatorMonitor.SetTrigger(unfocusAnimationTrigger, null, false);
 | |
|             onUnfocus.Invoke();
 | |
|         }
 | |
| 
 | |
|         protected void ActivateUIElements()
 | |
|         {
 | |
|             SetUIElementsActive(true);
 | |
|         }
 | |
| 
 | |
|         protected void DeactivateUIElements()
 | |
|         {
 | |
|             SetUIElementsActive(false);
 | |
|         }
 | |
| 
 | |
|         protected virtual void SetUIElementsActive(bool value)
 | |
|         {
 | |
|             Tools.SetGameObjectActive(panel, value);
 | |
|             Tools.SetGameObjectActive(pcImage, value && pcImage != null && pcImage.sprite != null);
 | |
|             pcName.SetActive(value);
 | |
|             Tools.SetGameObjectActive(timerSlider, false); // Let StartTimer activate if needed.
 | |
|             if (value == false) ClearResponseButtons();
 | |
|         }
 | |
| 
 | |
|         public virtual void HideImmediate()
 | |
|         {
 | |
|             OnHidden();
 | |
|         }
 | |
| 
 | |
|         protected virtual void ClearResponseButtons()
 | |
|         {
 | |
|             DestroyInstantiatedButtons();
 | |
|             if (buttons != null)
 | |
|             {
 | |
|                 for (int i = 0; i < buttons.Length; i++)
 | |
|                 {
 | |
|                     if (buttons[i] == null) continue;
 | |
|                     buttons[i].Reset();
 | |
|                     buttons[i].isVisible = showUnusedButtons;
 | |
|                     buttons[i].gameObject.SetActive(showUnusedButtons);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets the response buttons.
 | |
|         /// </summary>
 | |
|         /// <param name='responses'>Responses.</param>
 | |
|         /// <param name='target'>Target that will receive OnClick events from the buttons.</param>
 | |
|         protected virtual void SetResponseButtons(Response[] responses, Transform target)
 | |
|         {
 | |
|             firstSelected = null;
 | |
|             DestroyInstantiatedButtons();
 | |
|             var hasDisabledButton = false;
 | |
| 
 | |
|             // Prep autonumber format:
 | |
|             if (autonumber.enabled)
 | |
|             {
 | |
|                 m_processedAutonumberFormat = FormattedText.Parse(autonumber.format.Replace("\\t", "\t").Replace("\\n", "\n")).text;
 | |
|             }
 | |
| 
 | |
|             if ((buttons != null) && (responses != null))
 | |
|             {
 | |
|                 // Add explicitly-positioned buttons:
 | |
|                 int buttonNumber = 0;
 | |
|                 for (int i = 0; i < responses.Length; i++)
 | |
|                 {
 | |
|                     if (responses[i].formattedText.position != FormattedText.NoAssignedPosition)
 | |
|                     {
 | |
|                         int position = responses[i].formattedText.position;
 | |
|                         if (0 <= position && position < buttons.Length && buttons[position] != null)
 | |
|                         {
 | |
|                             SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             Debug.LogWarning("Dialogue System: Buttons list doesn't contain a button for position " + position + ".", this);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if ((buttonTemplate != null) && (buttonTemplateHolder != null))
 | |
|                 {
 | |
|                     if (scrollbarEnabler != null) CheckScrollbar();
 | |
| 
 | |
|                     // Instantiate buttons from template:
 | |
|                     for (int i = 0; i < responses.Length; i++)
 | |
|                     {
 | |
|                         if (responses[i].formattedText.position != FormattedText.NoAssignedPosition) continue;
 | |
|                         GameObject buttonGameObject = InstantiateButton();
 | |
|                         if (buttonGameObject == null)
 | |
|                         {
 | |
|                             Debug.LogError("Dialogue System: Couldn't instantiate response button template.");
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             instantiatedButtons.Add(buttonGameObject);
 | |
|                             buttonGameObject.transform.SetParent(buttonTemplateHolder.transform, false);
 | |
|                             buttonGameObject.transform.SetAsLastSibling();
 | |
|                             buttonGameObject.SetActive(true);
 | |
|                             StandardUIResponseButton responseButton = buttonGameObject.GetComponent<StandardUIResponseButton>();
 | |
|                             SetResponseButton(responseButton, responses[i], target, buttonNumber++);
 | |
|                             if (responseButton != null)
 | |
|                             {
 | |
|                                 buttonGameObject.name = "Response: " + responseButton.text;
 | |
|                                 if (explicitNavigationForTemplateButtons && !responseButton.isClickable) hasDisabledButton = true;
 | |
|                             }
 | |
|                             if (firstSelected == null) firstSelected = buttonGameObject;
 | |
| 
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // Auto-position remaining buttons:
 | |
|                     if (buttonAlignment == ResponseButtonAlignment.ToFirst)
 | |
|                     {
 | |
|                         // Align to first, so add in order to front:
 | |
|                         for (int i = 0; i < Mathf.Min(buttons.Length, responses.Length); i++)
 | |
|                         {
 | |
|                             if (responses[i].formattedText.position == FormattedText.NoAssignedPosition)
 | |
|                             {
 | |
|                                 int position = Mathf.Clamp(GetNextAvailableResponseButtonPosition(0, 1), 0, buttons.Length - 1);
 | |
|                                 SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
 | |
|                                 if (firstSelected == null) firstSelected = buttons[position].gameObject;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         // Align to last, so add in reverse order to back:
 | |
|                         for (int i = Mathf.Min(buttons.Length, responses.Length) - 1; i >= 0; i--)
 | |
|                         {
 | |
|                             if (responses[i].formattedText.position == FormattedText.NoAssignedPosition)
 | |
|                             {
 | |
|                                 int position = Mathf.Clamp(GetNextAvailableResponseButtonPosition(buttons.Length - 1, -1), 0, buttons.Length - 1);
 | |
|                                 SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
 | |
|                                 firstSelected = buttons[position].gameObject;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (explicitNavigationForTemplateButtons) SetupTemplateButtonNavigation(hasDisabledButton);
 | |
| 
 | |
|             NotifyContentChanged();
 | |
|         }
 | |
| 
 | |
|         protected virtual void CheckScrollbar()
 | |
|         {
 | |
|             if (scrollbarEnabler == null) return;
 | |
|             if (m_scrollbarCoroutine != null) StopCoroutine(m_scrollbarCoroutine);
 | |
|             m_scrollbarCoroutine = dialogueUI.StartCoroutine(CheckScrollbarCoroutine());
 | |
|         }
 | |
| 
 | |
|         protected IEnumerator CheckScrollbarCoroutine()
 | |
|         {
 | |
|             var timeout = Time.realtimeSinceStartup + UIAnimatorMonitor.MaxWaitDuration;
 | |
|             while (!isOpen && Time.realtimeSinceStartup < timeout)
 | |
|             {
 | |
|                 yield return null;
 | |
|             }
 | |
|             if (buttonTemplateScrollbarResetValue >= 0)
 | |
|             {
 | |
|                 if (buttonTemplateScrollbar != null) buttonTemplateScrollbar.value = buttonTemplateScrollbarResetValue;
 | |
|                 if (scrollbarEnabler != null)
 | |
|                 {
 | |
|                     scrollbarEnabler.CheckScrollbarWithResetValue(buttonTemplateScrollbarResetValue);
 | |
|                 }
 | |
|             }
 | |
|             else if (scrollbarEnabler != null)
 | |
|             {
 | |
|                 scrollbarEnabler.CheckScrollbar();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected virtual void SetResponseButton(StandardUIResponseButton button, Response response, Transform target, int buttonNumber)
 | |
|         {
 | |
|             if (button != null)
 | |
|             {
 | |
|                 button.response = response;
 | |
|                 button.gameObject.SetActive(true);
 | |
|                 button.isVisible = true;
 | |
|                 button.isClickable = response.enabled;
 | |
|                 button.target = target;
 | |
|                 if (response != null)
 | |
|                 {
 | |
|                     if (warnOnEmptyResponseText && DialogueDebug.logWarnings && string.IsNullOrEmpty(response.formattedText.text))
 | |
|                     {
 | |
|                         Debug.LogWarning($"Dialogue System: Response entry [{response.destinationEntry.id}] menu text is blank.", button);
 | |
|                     }
 | |
|                     button.SetFormattedText(response.formattedText);
 | |
|                 }
 | |
| 
 | |
|                 // Auto-number:
 | |
|                 if (autonumber.enabled)
 | |
|                 {
 | |
|                     button.text = string.Format(m_processedAutonumberFormat, buttonNumber + 1, button.text);
 | |
|                     // Add UIButtonKeyTrigger(s) if needed:
 | |
|                     var numKeyTriggersNeeded = 0;
 | |
|                     if (autonumber.regularNumberHotkeys) numKeyTriggersNeeded++;
 | |
|                     if (autonumber.numpadHotkeys) numKeyTriggersNeeded++;
 | |
|                     var keyTriggers = button.GetComponents<UIButtonKeyTrigger>();
 | |
|                     if (keyTriggers.Length < numKeyTriggersNeeded)
 | |
|                     {
 | |
|                         for (int i = keyTriggers.Length; i < numKeyTriggersNeeded; i++)
 | |
|                         {
 | |
|                             button.gameObject.AddComponent<UIButtonKeyTrigger>();
 | |
|                         }
 | |
|                         keyTriggers = button.GetComponents<UIButtonKeyTrigger>();
 | |
|                     }
 | |
|                     int index = 0;
 | |
|                     if (autonumber.regularNumberHotkeys)
 | |
|                     {
 | |
|                         keyTriggers[index++].key = (KeyCode)((int)KeyCode.Alpha1 + buttonNumber);
 | |
|                     }
 | |
|                     if (autonumber.numpadHotkeys)
 | |
|                     {
 | |
|                         keyTriggers[index].key = (KeyCode)((int)KeyCode.Keypad1 + buttonNumber);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected int GetNextAvailableResponseButtonPosition(int start, int direction)
 | |
|         {
 | |
|             if (buttons != null)
 | |
|             {
 | |
|                 int position = start;
 | |
|                 while ((0 <= position) && (position < buttons.Length))
 | |
|                 {
 | |
|                     if (buttons[position].isVisible && buttons[position].response != null)
 | |
|                     {
 | |
|                         position += direction;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         return position;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             return 5;
 | |
|         }
 | |
| 
 | |
|         public virtual void SetupTemplateButtonNavigation(bool hasDisabledButton)
 | |
|         {
 | |
|             // Assumes buttons are active (since uses GetComponent), so call after activating panel.
 | |
|             if (instantiatedButtons == null || instantiatedButtons.Count == 0) return;
 | |
|             var buttons = new List<GameObject>();
 | |
|             if (hasDisabledButton)
 | |
|             {
 | |
|                 // If some buttons are disabled, make a list of only the clickable ones:
 | |
|                 buttons.AddRange(instantiatedButtons.FindAll(x => x.GetComponent<StandardUIResponseButton>().isClickable));
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 buttons.AddRange(instantiatedButtons);
 | |
|             }
 | |
| 
 | |
|             for (int i = 0; i < buttons.Count; i++)
 | |
|             {
 | |
|                 var button = buttons[i].GetComponent<UnityEngine.UI.Button>();
 | |
|                 if (button == null) continue;
 | |
|                 var above = (i == 0) ? (loopExplicitNavigation ? buttons[buttons.Count - 1].GetComponent<UnityEngine.UI.Button>() : null)
 | |
|                     : buttons[i - 1].GetComponent<UnityEngine.UI.Button>();
 | |
|                 var below = (i == buttons.Count - 1) ? (loopExplicitNavigation ? buttons[0].GetComponent<UnityEngine.UI.Button>() : null)
 | |
|                     : buttons[i + 1].GetComponent<UnityEngine.UI.Button>();
 | |
|                 var navigation = new UnityEngine.UI.Navigation();
 | |
| 
 | |
|                 navigation.mode = UnityEngine.UI.Navigation.Mode.Explicit;
 | |
|                 navigation.selectOnUp = above;
 | |
|                 navigation.selectOnLeft = above;
 | |
|                 navigation.selectOnDown = below;
 | |
|                 navigation.selectOnRight = below;
 | |
|                 button.navigation = navigation;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected virtual GameObject InstantiateButton()
 | |
|         {
 | |
|             // Try to pull from pool first:
 | |
|             if (m_instantiatedButtonPool.Count > 0)
 | |
|             {
 | |
|                 var button = m_instantiatedButtonPool[0];
 | |
|                 m_instantiatedButtonPool.RemoveAt(0);
 | |
|                 return button;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return GameObject.Instantiate(buttonTemplate.gameObject) as GameObject;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void DestroyInstantiatedButtons()
 | |
|         {
 | |
|             // Return buttons to pool:
 | |
|             for (int i = 0; i < instantiatedButtons.Count; i++)
 | |
|             {
 | |
|                 instantiatedButtons[i].SetActive(false);
 | |
|             }
 | |
|             m_instantiatedButtonPool.AddRange(instantiatedButtons);
 | |
| 
 | |
|             instantiatedButtons.Clear();
 | |
|             NotifyContentChanged();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Makes the panel's buttons non-clickable.
 | |
|         /// Typically called by the dialogue UI as soon as a button has been
 | |
|         /// clicked to make sure the player can't click another one while the
 | |
|         /// menu is playing its hide animation.
 | |
|         /// </summary>
 | |
|         public virtual void MakeButtonsNonclickable()
 | |
|         {
 | |
|             for (int i = 0; i < instantiatedButtons.Count; i++)
 | |
|             {
 | |
|                 var responseButton = (instantiatedButtons[i] != null) ? instantiatedButtons[i].GetComponent<StandardUIResponseButton>() : null;
 | |
|                 if (responseButton != null) responseButton.isClickable = false;
 | |
|             }
 | |
|             for (int i = 0; i < buttons.Length; i++)
 | |
|             {
 | |
|                 if (buttons[i] != null) buttons[i].isClickable = false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected void NotifyContentChanged()
 | |
|         {
 | |
|             onContentChanged.Invoke();
 | |
|         }
 | |
| 
 | |
|         protected void DisableInput()
 | |
|         {
 | |
|             SetInput(false);
 | |
|         }
 | |
| 
 | |
|         protected void EnableInput()
 | |
|         {
 | |
|             SetInput(true);
 | |
|         }
 | |
| 
 | |
|         protected void SetInput(bool value)
 | |
|         {
 | |
|             s_isInputDisabled = (value == false);
 | |
|             if (m_mainCanvasGroup == null)
 | |
|             {
 | |
|                 // Try to get dialogue UI's main panel:
 | |
|                 var ui = GetComponentInParent<StandardDialogueUI>();
 | |
|                 if (ui != null && ui.conversationUIElements.mainPanel != null)
 | |
|                 {
 | |
|                     var mainPanel = ui.conversationUIElements.mainPanel;
 | |
|                     m_mainCanvasGroup = mainPanel.GetComponent<CanvasGroup>() ?? mainPanel.gameObject.AddComponent<CanvasGroup>();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // Otherwise try the menu's panel:
 | |
|                     var menuPanel = panel;
 | |
|                     if (menuPanel == null) menuPanel = buttonTemplateHolder;
 | |
|                     if (menuPanel != null)
 | |
|                     {
 | |
|                         m_mainCanvasGroup = menuPanel.GetComponent<CanvasGroup>() ?? menuPanel.gameObject.AddComponent<CanvasGroup>();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             if (m_mainCanvasGroup != null) m_mainCanvasGroup.interactable = value;
 | |
|             if (value == false)
 | |
|             {
 | |
|                 // If auto focus, show firstSelected in selected state:
 | |
|                 if (InputDeviceManager.autoFocus && firstSelected != null)
 | |
|                 {
 | |
|                     var button = firstSelected.GetComponent<UnityEngine.UI.Button>();
 | |
|                     MethodInfo methodInfo = typeof(UnityEngine.UI.Button).GetMethod("DoStateTransition", BindingFlags.Instance | BindingFlags.NonPublic);
 | |
|                     methodInfo.Invoke(button, new object[] { 3, true }); // 3 = SelectionState.Selected
 | |
|                 }
 | |
|             }
 | |
|             if (eventSystem != null)
 | |
|             {
 | |
|                 var inputModule = eventSystem.GetComponent<UnityEngine.EventSystems.PointerInputModule>();
 | |
|                 if (inputModule != null) inputModule.enabled = value;
 | |
|             }
 | |
|             UIButtonKeyTrigger.monitorInput = value;
 | |
|             if (value == true)
 | |
|             {
 | |
|                 RefreshSelectablesList();
 | |
|                 CheckFocus();
 | |
|                 if (eventSystem != null && eventSystem.currentSelectedGameObject != null)
 | |
|                 { // Also show in focused/selected state:
 | |
|                     UIUtility.Select(eventSystem.currentSelectedGameObject.GetComponent<UnityEngine.UI.Selectable>());
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Timer
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starts the timer.
 | |
|         /// </summary>
 | |
|         /// <param name='timeout'>Timeout duration in seconds.</param>
 | |
|         /// <param name="timeoutHandler">Invoke this handler on timeout.</param>
 | |
|         public virtual void StartTimer(float timeout, System.Action timeoutHandler)
 | |
|         {
 | |
|             if (m_timer == null)
 | |
|             {
 | |
|                 if (timerSlider != null)
 | |
|                 {
 | |
|                     Tools.SetGameObjectActive(timerSlider, true);
 | |
|                     m_timer = timerSlider.GetComponent<StandardUITimer>();
 | |
|                     if (m_timer == null) m_timer = timerSlider.gameObject.AddComponent<StandardUITimer>();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     m_timer = GetComponentInChildren<StandardUITimer>();
 | |
|                     if (m_timer == null) m_timer = gameObject.AddComponent<StandardUITimer>();
 | |
|                 }
 | |
|             }
 | |
|             Tools.SetGameObjectActive(m_timer, true);
 | |
|             m_timer.StartCountdown(timeout, timeoutHandler);
 | |
|         }
 | |
| 
 | |
|         public virtual void StopTimer()
 | |
|         {
 | |
|             if (m_timer != null)
 | |
|             {
 | |
|                 m_timer.StopCountdown();
 | |
|                 Tools.SetGameObjectActive(m_timer, false);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|     }
 | |
| 
 | |
| }
 | 
