using EasyTalk.Nodes.Utility; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using EasyTalk.Controller; using EasyTalk.Localization; #if TEXTMESHPRO_INSTALLED using TMPro; #endif namespace EasyTalk.Display { /// /// A display for obtaining text input from the plpayer during dialogue playback. /// public class TextInputDisplay : DialoguePanel { /// /// The text input field. /// [SerializeField] private InputField inputField; #if TEXTMESHPRO_INSTALLED /// /// The text input field (TextMesh Pro version). /// [SerializeField] private TMP_InputField textMeshProInputField; #endif /// /// The PlayerInput node being processed. /// private PlayerInputNode inputNode; /// /// Whether the input display should be hidden immediately on awake. /// private bool hideOnAwake = true; /// /// The default set of characters which the player can cycle through if using a gamepad. /// private char[] defaultCharacters = new char[] { ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; /// /// The set of input characters in use (only applicable when cycling via a gamepad). /// private char[] inputCharacters = null; /// /// The index of the character currently chosen as the gamepad input character. /// private int currentCharacter = 0; /// /// Whether the display should quickly cycle forward through input characters (if a gamepad button is held down). /// private bool cyclingCharactersForward = false; /// /// Whether the display should quickly cycle backwards through input characters (if a gamepad button is held down). /// private bool cyclingCharactersBackward = false; /// /// Keeps track of the last time at which the input character was cycled (only relevant when a gamepad button is held down). /// private float lastCycleTime = 0.0f; /// /// /The delay between switching input characters when they are being cycled (only relevant when a gamepad button is held down). /// private float cycleDelay = 0.1f; /// private void Awake() { Init(); if (hideOnAwake) { HideImmediately(); } onShowComplete.AddListener(FocusTextInput); if (forceStandardText) { if (inputField != null) { inputField.onSubmit.AddListener(TextInputEntered); } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { textMeshProInputField.onSubmit.AddListener(TextInputEntered); } #else if (inputField != null) { inputField.onSubmit.AddListener(TextInputEntered); } #endif } //Update the character set being used based on the currently set language UpdateInputCharacterSet(); //Register the display with the game state so that we can detect when the language changes and update the character set EasyTalkGameState.Instance.onLanguageChanged += LanguageChanged; } /// private void OnDestroy() { //Unregister from the game state EasyTalkGameState.Instance.onLanguageChanged -= LanguageChanged; } /// /// Called whenever the language is changed on the EasyTalkGameState. This will attempt to update the character set used by the input display. /// /// The prior language being used. /// The new language to use. protected void LanguageChanged(string oldLanguage, string newLanguage) { UpdateInputCharacterSet(); } /// /// Update the characters being used. /// private void UpdateInputCharacterSet() { bool foundCharacterSet = false; DialogueDisplay parentDisplay = DialogueDisplay.GetParentDialogueDisplay(this.gameObject); if (parentDisplay != null && parentDisplay.DialogueSettings != null && parentDisplay.DialogueSettings.LanguageInputs != null) { foreach (LanguageInputCharacterSet characterSet in parentDisplay.DialogueSettings.LanguageInputs.CharacterSet) { if (characterSet.LanguageCode.Equals(EasyTalkGameState.Instance.Language)) { inputCharacters = characterSet.Characters; foundCharacterSet = true; break; } } } if (!foundCharacterSet) { inputCharacters = defaultCharacters; } } /// private void Update() { if (Time.time - lastCycleTime > cycleDelay) { if (cyclingCharactersForward) { NextCharacter(); } else if (cyclingCharactersBackward) { LastCharacter(); } lastCycleTime = Time.time; } } /// /// Switches the currently selected input character to the last character in the character list. /// public void LastCharacter() { //Change the character at the cursor location to the last character. currentCharacter--; if(currentCharacter < 0) { currentCharacter = inputCharacters.Length - 1; } UpdateCharacterAtCursor(); int caretPos = GetCaretPosition(); if (caretPos < 1) { SetCaretPosition(1); } } /// /// Switches the currently selected input character to the next character in the character list. /// public void NextCharacter() { //Change the character at the cursor location to the next character currentCharacter++; if(currentCharacter >= inputCharacters.Length) { currentCharacter = 0; } UpdateCharacterAtCursor(); int caretPos = GetCaretPosition(); if (caretPos < 1) { SetCaretPosition(1); } } /// /// Turns on input character cycling, meaning the input display will cycle through to the next input character /// automatically while a gamepad input is held down. /// public void CycleNextCharacters() { cyclingCharactersForward = true; } /// /// Turns on input character cycling, meaning the input display will cycle through to the previous input character /// automatically while a gamepad input is held down. /// public void CycleLastCharacters() { cyclingCharactersBackward = true; } /// /// Turns automatic input character cycling off. /// public void StopCharacterCycling() { cyclingCharactersForward = false; cyclingCharactersBackward = false; } /// /// Changes the text input character at the current caret position to the currently selected character. /// private void UpdateCharacterAtCursor() { string currentText = GetText(); int caretPosition = GetCaretPosition(); string preText = ""; if (caretPosition > 1) { preText = currentText.Substring(0, caretPosition - 1); } string postText = ""; if ((caretPosition) < currentText.Length) { postText = currentText.Substring(caretPosition, currentText.Length - caretPosition); } string newText = preText + inputCharacters[currentCharacter] + postText; SetText(newText); } /// /// Moves the cursor (caret position) left by one character. /// public void MoveCursorLeft() { int caretPosition = GetCaretPosition(); caretPosition--; if(caretPosition < 1) { caretPosition = 1; } SetCurrentCharacter(GetText()[caretPosition-1]); SetCaretPosition(caretPosition ); } /// /// Moves the cursor (caret position) right by one character. /// public void MoveCursorRight() { int caretPosition = GetCaretPosition(); caretPosition++; //Extend the text by adding spaces to move the cursor over to the right. string currentText = GetText(); while(currentText.Length < caretPosition) { currentText += ' '; } SetText(currentText); SetCurrentCharacter(currentText[caretPosition - 1]); SetCaretPosition(caretPosition); } /// /// Searches through the input character list to find the specified character and sets the currently selected character index if found. /// /// private void SetCurrentCharacter(char character) { for(int i = 0; i < inputCharacters.Length; i++) { if(inputCharacters[i] == character) { currentCharacter = i; break; } } } /// /// Returns the current text value of the input display. /// /// public string GetText() { if (forceStandardText) { if (inputField != null) { return inputField.text; } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { return textMeshProInputField.text; } #else if (inputField != null) { return inputField.text; } #endif } return ""; } /// /// Sets the text value of the input display. /// /// The text to set. public void SetText(string text) { if (forceStandardText) { if (inputField != null) { inputField.text = text; } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { textMeshProInputField.text = text; } #else if (inputField != null) { inputField.text = text; } #endif } } /// /// Returns the current caret position of the input display. /// /// private int GetCaretPosition() { if (forceStandardText) { if (inputField != null) { return inputField.caretPosition; } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { return textMeshProInputField.caretPosition; } #else if (inputField != null) { return inputField.caretPosition; } #endif } return 1; } /// /// Sets the current caret position of the input display. /// /// The position to place the caret (cursor) at. private void SetCaretPosition(int pos) { if (forceStandardText) { if (inputField != null) { inputField.caretPosition = pos; } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { textMeshProInputField.caretPosition = pos; } #else if (inputField != null) { inputField.caretPosition = pos; } #endif } } /// /// Gets or sets wether or not the display will b e hidden immediately on Awake(). /// public bool HideOnAwake { get { return hideOnAwake; } set { this.hideOnAwake = value; } } /// /// Resets the text input. /// public void Reset() { currentCharacter = 0; SetText(""); } /// /// Sets the active PlayerInput node. This method also sets the hint/placeholder text configured in the node. /// /// The PlayerInput node to use. public void SetInputNode(PlayerInputNode node) { this.inputNode = node; if (forceStandardText) { if (inputField != null) { inputField.placeholder.GetComponent().text = inputNode.HintText; } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { textMeshProInputField.placeholder.GetComponent().text = inputNode.HintText; } #else if (inputField != null) { inputField.placeholder.GetComponent().text = inputNode.HintText; } #endif } } /// /// Gives the text input focus. /// private void FocusTextInput() { EventSystem.current.SetSelectedGameObject(null); if (forceStandardText) { if (inputField != null) { EventSystem.current.SetSelectedGameObject(inputField.gameObject); } } else { #if TEXTMESHPRO_INSTALLED if (textMeshProInputField != null) { EventSystem.current.SetSelectedGameObject(textMeshProInputField.gameObject); } #else if (inputField != null) { EventSystem.current.SetSelectedGameObject(inputField.gameObject); } #endif } } /// /// When called, this will call TextInputEntered() with the current text of the input field. /// public void TextInputEntered() { string text = GetText(); if (text == null) { text = ""; } TextInputEntered(text.TrimEnd()); } /// /// Sets the text entered by the player on the active PlayerInput node and calls ExecutionCompleted(); /// /// private void TextInputEntered(string text) { if (this.inputNode != null) { this.inputNode.InputText = text; this.inputNode.ExecutionCompleted(); this.inputNode = null; } Hide(); } private void OnValidate() { #if TEXTMESHPRO_INSTALLED if (this.textMeshProInputField == null) { TMP_InputField[] tmpTextComponents = GetComponentsInChildren(true); foreach (TMP_InputField txt in tmpTextComponents) { if (txt.gameObject.name.Contains("InputField") && txt.gameObject.name.Contains("TMP")) { this.textMeshProInputField = txt; break; } } } #endif } } }