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
}
}
}