using TMPro;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
{
///
/// Manages spawning and positioning of the global keyboard.
///
public class GlobalNonNativeKeyboard : MonoBehaviour
{
public static GlobalNonNativeKeyboard instance { get; private set; }
[SerializeField, Tooltip("The prefab with the XR Keyboard component to automatically instantiate.")]
GameObject m_KeyboardPrefab;
///
/// The prefab with the XR Keyboard component to automatically instantiate.
///
public GameObject keyboardPrefab
{
get => m_KeyboardPrefab;
set => m_KeyboardPrefab = value;
}
[SerializeField, Tooltip("The parent Transform to instantiate the Keyboard Prefab under.")]
Transform m_PlayerRoot;
///
/// The parent Transform to instantiate the Keyboard Prefab under.
///
public Transform playerRoot
{
get => m_PlayerRoot;
set => m_PlayerRoot = value;
}
[HideInInspector]
[SerializeField]
XRKeyboard m_Keyboard;
///
/// Global keyboard instance.
///
public XRKeyboard keyboard
{
get => m_Keyboard;
set => m_Keyboard = value;
}
[SerializeField, Tooltip("Position offset from the camera to place the keyboard.")]
Vector3 m_KeyboardOffset;
///
/// Position offset from the camera to place the keyboard.
///
public Vector3 keyboardOffset
{
get => m_KeyboardOffset;
set => m_KeyboardOffset = value;
}
[SerializeField, Tooltip("Transform of the camera. If left empty, this will default to Camera.main.")]
Transform m_CameraTransform;
///
/// Transform of the camera. If left empty, this will default to Camera.main.
///
public Transform cameraTransform
{
get => m_CameraTransform;
set => m_CameraTransform = value;
}
[SerializeField, Tooltip("If true, the keyboard will be repositioned to the starting position if it is out of view when Show Keyboard is called.")]
bool m_RepositionOutOfViewKeyboardOnOpen = true;
///
/// If true, the keyboard will be repositioned to the starting position if it is out of view when Show Keyboard is called.
///
public bool repositionOutOfViewKeyboardOnOpen
{
get => m_RepositionOutOfViewKeyboardOnOpen;
set => m_RepositionOutOfViewKeyboardOnOpen = value;
}
[SerializeField, Tooltip("Threshold for the dot product when determining if the keyboard is out of view and should be repositioned. The lower the threshold, the wider the field of view."), Range(0f, 1f)]
float m_FacingKeyboardThreshold = 0.15f;
///
/// Threshold for the dot product when determining if the keyboard is out of view and should be repositioned. The lower the threshold, the wider the field of view.
///
public float facingKeyboardThreshold
{
get => m_FacingKeyboardThreshold;
set => m_FacingKeyboardThreshold = value;
}
///
/// See .
///
void Awake()
{
if (instance != null && instance != this)
{
Destroy(this);
return;
}
instance = this;
if (m_CameraTransform == null)
{
var mainCamera = Camera.main;
if (mainCamera != null)
m_CameraTransform = mainCamera.transform;
else
Debug.LogWarning("Could not find main camera to assign the missing Camera Transform property.", this);
}
if (m_KeyboardPrefab != null)
{
keyboard = Instantiate(m_KeyboardPrefab, m_PlayerRoot).GetComponent();
keyboard.gameObject.SetActive(false);
}
}
///
/// Opens the global keyboard with a to monitor.
///
/// This will update the keyboard with as the existing string for the keyboard.
/// The input field for the global keyboard to monitor
/// If true, the global keyboard will respect the character limit of the
/// . This is false by default.
public virtual void ShowKeyboard(TMP_InputField inputField, bool observeCharacterLimit = false)
{
if (keyboard == null)
return;
// Check if keyboard is already open or should be repositioned
var shouldPositionKeyboard = !keyboard.isOpen || (m_RepositionOutOfViewKeyboardOnOpen && IsKeyboardOutOfView());
// Open keyboard
keyboard.Open(inputField, observeCharacterLimit);
// Position keyboard in front of user if the keyboard is closed
if (shouldPositionKeyboard)
PositionKeyboard(m_CameraTransform);
}
///
/// Opens the global keyboard with the option to populate it with existing text.
///
/// This will update the keyboard with as the existing string for the keyboard.
/// The existing text string to populate the keyboard with on open.
public virtual void ShowKeyboard(string text)
{
if (keyboard == null)
return;
// Check if keyboard is already open or should be repositioned
var shouldPositionKeyboard = !keyboard.isOpen || (m_RepositionOutOfViewKeyboardOnOpen && IsKeyboardOutOfView());
// Open keyboard
keyboard.Open(text);
// Position keyboard in front of user if the keyboard is closed
if (shouldPositionKeyboard)
PositionKeyboard(m_CameraTransform);
}
///
/// Opens the global keyboard with the option to clear any existing keyboard text.
///
/// If true, the keyboard will open with no string populated in the keyboard. If false,
/// the existing text will be maintained. This is false by default.
public void ShowKeyboard(bool clearKeyboardText = false)
{
if (keyboard == null)
return;
ShowKeyboard(clearKeyboardText ? string.Empty : keyboard.text);
}
///
/// Closes the global keyboard.
///
public virtual void HideKeyboard()
{
if (keyboard == null)
return;
keyboard.Close();
}
///
/// Reposition to starting position if it is out of view. Keyboard will only reposition if is active and enabled.
///
///
/// Field if view is defined by the , and the starting position
/// is defined by the in relation to the camera.
///
public void RepositionKeyboardIfOutOfView()
{
if (IsKeyboardOutOfView())
{
if (keyboard.isOpen)
PositionKeyboard(m_CameraTransform);
}
}
void PositionKeyboard(Transform target)
{
var position = target.position +
target.right * m_KeyboardOffset.x +
target.forward * m_KeyboardOffset.z +
Vector3.up * m_KeyboardOffset.y;
keyboard.transform.position = position;
FaceKeyboardAtTarget(m_CameraTransform);
}
void FaceKeyboardAtTarget(Transform target)
{
var forward = (keyboard.transform.position - target.position).normalized;
BurstMathUtility.OrthogonalLookRotation(forward, Vector3.up, out var newTarget);
keyboard.transform.rotation = newTarget;
}
bool IsKeyboardOutOfView()
{
if (m_CameraTransform == null || keyboard == null)
{
Debug.LogWarning("Camera or keyboard reference is null. Unable to determine if keyboard is out of view.", this);
return false;
}
var dotProduct = Vector3.Dot(m_CameraTransform.forward, (keyboard.transform.position - m_CameraTransform.position).normalized);
return dotProduct < m_FacingKeyboardThreshold;
}
}
}