2025-05-15 16:25:40 +02:00

290 lines
10 KiB
C#

using System;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace VRUIP
{
public class Keyboard : A_Canvas
{
// KEYBOARD PROPERTIES
[SerializeField] private KeyboardFieldType fieldType;
[SerializeField] private TMP_InputField tmpInput;
[SerializeField] private TextMeshProUGUI tmpText;
[SerializeField] [Tooltip("Number of spaces added when Tab is clicked.")] private int tabSpaces;
// KEYBOARD COMPONENTS
[SerializeField] private Image background;
[SerializeField] private GameObject normalSection;
[SerializeField] private GameObject shiftSection;
[SerializeField] private Transform contentTransform;
[SerializeField] private GameObject handles;
// KEYBOARD AUDIO
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip buttonClickSound;
[SerializeField] private AudioClip shiftCapsClickSound;
[SerializeField] private AudioClip spaceClickSound;
[SerializeField] private AudioClip backspaceClickSound;
[SerializeField] private AudioClip enterClickSound;
private bool _caps;
private bool _shift;
private EventSystem _eventSystem;
private int _legacyInputCaretPosition;
public bool IsTmpInput => fieldType == KeyboardFieldType.TMPInputField;
protected override void Start()
{
base.Start();
_eventSystem = EventSystem.current;
// if part of canvas, disable movement handles.
if (GetComponentInParent<Canvas>() != null) handles.SetActive(false);
}
public Keyboard Create(Vector3 position, Quaternion rotation, Vector3 contentRotation, bool fadeIn = false)
{
var clone = Instantiate(this, position, rotation);
var angle = clone.transform.eulerAngles;
clone.transform.eulerAngles = new Vector3(0, angle.y, 0f);
clone.SetContentRotation(contentRotation);
if (fadeIn)
{
clone.SetAlpha(0);
clone.FadeInCanvas();
}
return clone;
}
/// <summary>
/// Keyboard Button pressed.
/// </summary>
/// <param name="button"></param>
public void ButtonPressed(KeyboardButton button)
{
// Check if there is an input field assigned.
if (tmpInput == null && tmpText == null)
{
Debug.LogWarning("No input field is assigned to the keyboard.");
return;
}
// Choose what to do based on button type.
switch (button.Type)
{
case KeyboardButton.KeyboardButtonType.Character:
case KeyboardButton.KeyboardButtonType.Custom:
audioSource.clip = buttonClickSound;
audioSource.Play();
Type(button.Character);
// If shift was previously pressed and caps lock is off, turn it off.
if (_shift && !_caps)
{
normalSection.SetActive(true);
shiftSection.SetActive(false);
_shift = false;
}
break;
case KeyboardButton.KeyboardButtonType.Space:
audioSource.clip = spaceClickSound;
audioSource.Play();
Type(" ");
break;
case KeyboardButton.KeyboardButtonType.Enter:
audioSource.clip = enterClickSound;
audioSource.Play();
Type("\n");
break;
case KeyboardButton.KeyboardButtonType.Delete:
audioSource.clip = backspaceClickSound;
audioSource.Play();
Delete();
break;
case KeyboardButton.KeyboardButtonType.Tab:
audioSource.clip = spaceClickSound;
audioSource.Play();
var tab = new string(' ', Math.Abs(tabSpaces));
Type(tab);
break;
case KeyboardButton.KeyboardButtonType.Caps:
audioSource.clip = shiftCapsClickSound;
audioSource.Play();
normalSection.SetActive(_caps);
shiftSection.SetActive(!_caps);
// If caps lock is on, turn off shift.
if (_caps) _shift = false;
_caps = !_caps;
break;
case KeyboardButton.KeyboardButtonType.Shift:
audioSource.clip = shiftCapsClickSound;
audioSource.Play();
// If caps lock is on, don't do anything.
if (_caps) return;
normalSection.SetActive(_shift);
shiftSection.SetActive(!_shift);
_shift = !_shift;
break;
case KeyboardButton.KeyboardButtonType.Clear:
audioSource.clip = backspaceClickSound;
audioSource.Play();
switch (fieldType)
{
case KeyboardFieldType.TMPInputField:
tmpInput.text = "";
break;
case KeyboardFieldType.TMPText:
tmpText.text = "";
break;
}
break;
}
}
// Type in the keyboard's selected input field.
private void Type(string text)
{
switch (fieldType)
{
case KeyboardFieldType.TMPInputField:
TypeTMP(text);
break;
case KeyboardFieldType.TMPText:
tmpText.text += text;
break;
}
}
/// <summary>
/// Type in a TMP Input Field
/// </summary>
/// <param name="text"></param>
private void TypeTMP(string text)
{
// Set the input field text as the selected UI.
if (_eventSystem.currentSelectedGameObject != tmpInput.gameObject)
{
_eventSystem.SetSelectedGameObject(tmpInput.gameObject);
}
// If there is a selection, delete it.
if (tmpInput.selectionAnchorPosition != tmpInput.selectionFocusPosition)
{
DeleteSelectedTextTMP();
}
// Type the character at the caret position.
var caretPos = tmpInput.caretPosition;
tmpInput.text = tmpInput.text.Insert(caretPos, text);
tmpInput.caretPosition = caretPos + text.Length;
tmpInput.selectionAnchorPosition = tmpInput.caretPosition;
tmpInput.selectionFocusPosition = tmpInput.caretPosition;
// Activate the input field to show the caret
tmpInput.ActivateInputField();
}
/// <summary>
/// Delete button functionality.
/// </summary>
private void Delete()
{
switch (fieldType)
{
case KeyboardFieldType.TMPInputField:
DeleteTMP();
break;
case KeyboardFieldType.TMPText:
if (tmpText.text.Length == 0) return;
tmpText.text = tmpText.text.Substring(0, tmpText.text.Length - 1);
break;
}
}
/// <summary>
/// Delete the last character in a TMP Input Field
/// </summary>
private void DeleteTMP()
{
// if there is no text, return
if (tmpInput.text.Length == 0) return;
// Set the input field text as the selected UI.
if (_eventSystem.currentSelectedGameObject != tmpInput.gameObject)
{
_eventSystem.SetSelectedGameObject(tmpInput.gameObject);
}
// If there is a selection, delete it.
if (tmpInput.selectionAnchorPosition != tmpInput.selectionFocusPosition)
{
DeleteSelectedTextTMP();
}
// If there is no selection, delete the last character.
else
{
var caretPos = tmpInput.caretPosition;
if (caretPos <= tmpInput.text.Length)
{
if (caretPos == 0) return; // Can't delete if the caret is at the start of the text.
tmpInput.text = tmpInput.text.Remove(caretPos - 1, 1);
tmpInput.caretPosition = caretPos - 1;
}
tmpInput.ActivateInputField();
}
}
/// <summary>
/// Delete the selected text in a TMP Input Field.
/// </summary>
private void DeleteSelectedTextTMP()
{
var startPos = Mathf.Min(tmpInput.selectionAnchorPosition, tmpInput.selectionFocusPosition);
var endPos = Mathf.Max(tmpInput.selectionAnchorPosition, tmpInput.selectionFocusPosition);
if (startPos != endPos)
{
tmpInput.text = tmpInput.text.Remove(startPos, endPos - startPos);
tmpInput.selectionAnchorPosition = startPos;
tmpInput.selectionFocusPosition = startPos;
}
}
private enum KeyboardFieldType
{
TMPInputField,
TMPText
}
protected override void SetColors(ColorTheme theme)
{
background.color = theme.primaryColor;
}
public void SetInput(TMP_InputField input)
{
fieldType = KeyboardFieldType.TMPInputField;
tmpInput = input;
}
// Set the rotation of the content without the handle.
public void SetContentRotation(Vector3 rotation)
{
contentTransform.localEulerAngles = rotation;
}
}
}