Add XR sample assets and update settings
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using TMPro;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Utilities;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages spawning and positioning of the global keyboard.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// The prefab with the XR Keyboard component to automatically instantiate.
|
||||
/// </summary>
|
||||
public GameObject keyboardPrefab
|
||||
{
|
||||
get => m_KeyboardPrefab;
|
||||
set => m_KeyboardPrefab = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The parent Transform to instantiate the Keyboard Prefab under.")]
|
||||
Transform m_PlayerRoot;
|
||||
|
||||
/// <summary>
|
||||
/// The parent Transform to instantiate the Keyboard Prefab under.
|
||||
/// </summary>
|
||||
public Transform playerRoot
|
||||
{
|
||||
get => m_PlayerRoot;
|
||||
set => m_PlayerRoot = value;
|
||||
}
|
||||
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
XRKeyboard m_Keyboard;
|
||||
|
||||
/// <summary>
|
||||
/// Global keyboard instance.
|
||||
/// </summary>
|
||||
public XRKeyboard keyboard
|
||||
{
|
||||
get => m_Keyboard;
|
||||
set => m_Keyboard = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Position offset from the camera to place the keyboard.")]
|
||||
Vector3 m_KeyboardOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset from the camera to place the keyboard.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Transform of the camera. If left empty, this will default to Camera.main.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the keyboard will be repositioned to the starting position if it is out of view when Show Keyboard is called.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public float facingKeyboardThreshold
|
||||
{
|
||||
get => m_FacingKeyboardThreshold;
|
||||
set => m_FacingKeyboardThreshold = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
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<XRKeyboard>();
|
||||
keyboard.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Opens the global keyboard with a <see cref="TMP_InputField"/> to monitor.
|
||||
/// </summary>
|
||||
/// <remarks>This will update the keyboard with <see cref="TMP_InputField.text"/> as the existing string for the keyboard.</remarks>
|
||||
/// <param name="inputField">The input field for the global keyboard to monitor</param>
|
||||
/// <param name="observeCharacterLimit">If true, the global keyboard will respect the character limit of the
|
||||
/// <see cref="inputField"/>. This is false by default.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the global keyboard with the option to populate it with existing text.
|
||||
/// </summary>
|
||||
/// <remarks>This will update the keyboard with <see cref="text"/> as the existing string for the keyboard.</remarks>
|
||||
/// <param name="text">The existing text string to populate the keyboard with on open.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the global keyboard with the option to clear any existing keyboard text.
|
||||
/// </summary>
|
||||
/// <param name="clearKeyboardText">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.</param>
|
||||
public void ShowKeyboard(bool clearKeyboardText = false)
|
||||
{
|
||||
if (keyboard == null)
|
||||
return;
|
||||
|
||||
ShowKeyboard(clearKeyboardText ? string.Empty : keyboard.text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the global keyboard.
|
||||
/// </summary>
|
||||
public virtual void HideKeyboard()
|
||||
{
|
||||
if (keyboard == null)
|
||||
return;
|
||||
|
||||
keyboard.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reposition <see cref="keyboard"/> to starting position if it is out of view. Keyboard will only reposition if is active and enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Field if view is defined by the <see cref="facingKeyboardThreshold"/>, and the starting position
|
||||
/// is defined by the <see cref="keyboardOffset"/> in relation to the camera.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 015b50343a7cf174ebb7703245f6ba72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class defining callbacks for key functionality. Allows users to extend
|
||||
/// custom functionality of keys and keyboard.
|
||||
/// </summary>
|
||||
public abstract class KeyFunction : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Pre-process function when a key is pressed.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">The current keyboard associated with <see cref="key"/>.</param>
|
||||
/// <param name="key">The key that is being pressed.</param>
|
||||
public virtual void PreprocessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.PreprocessKeyPress(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primary function callback when a key is pressed. Use this function to interface directly with a keyboard
|
||||
/// and process logic based on the current keyboard and key context.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">The current keyboard associated with <see cref="key"/>.</param>
|
||||
/// <param name="key">The key that is being pressed.</param>
|
||||
public abstract void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key);
|
||||
|
||||
/// <summary>
|
||||
/// Post-process function when a key is pressed. This function calls <see cref="XRKeyboard.PostprocessKeyPress"/> on the keyboard.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">The current keyboard associated with <see cref="key"/>.</param>
|
||||
/// <param name="key">The key that is being pressed.</param>
|
||||
public virtual void PostprocessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.PostprocessKeyPress(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses keyboard and key context to determine if this key function should override the key's display icon.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">Current keyboard context.</param>
|
||||
/// <param name="key">Current keyboard key.</param>
|
||||
/// <returns>Returns true if this key function should override the display icon.</returns>
|
||||
public virtual bool OverrideDisplayIcon(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns display icon for this key function based on the context of the key and keyboard.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">Current keyboard context.</param>
|
||||
/// <param name="key">Current keyboard key.</param>
|
||||
/// <returns>Returns display icon for this key.</returns>
|
||||
public virtual Sprite GetDisplayIcon(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows this key function to process when a key is refreshing its display.
|
||||
/// </summary>
|
||||
/// <param name="keyboardContext">The current keyboard associated with <see cref="key"/>.</param>
|
||||
/// <param name="key">The key that is refreshing the display.</param>
|
||||
public virtual void ProcessRefreshDisplay(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18e905492db2a3e4e9fd486d79848865
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89613eddc1ae20046a22fe8d45de2d0d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard.KeyFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key function used to hide the keyboard.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Hide Function", menuName = "XR/Spatial Keyboard/Hide Key Function", order = 1)]
|
||||
public class HideFunction : KeyFunction
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.Close(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58528143acafcd44e93258c8e6a59dc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard.KeyFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key function used to send a key code for the keyboard to process.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Key Code Function", menuName = "XR/Spatial Keyboard/Key Code Key Function", order = 1)]
|
||||
public class KeyCodeFunction : KeyFunction
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.ProcessKeyCode(key.keyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92861f72e977a244cb0be6d8410a8d99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard.KeyFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key function used to update the keyboard layout.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Layout Function", menuName = "XR/Spatial Keyboard/Layout Key Function", order = 1)]
|
||||
public class LayoutFunction : KeyFunction
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.UpdateLayout(key.GetEffectiveCharacter());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f25471b9f361d64ba3aebe064d73092
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard.KeyFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key function used to process shift and caps lock functionality.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Shift Function", menuName = "XR/Spatial Keyboard/Shift Key Function", order = 1)]
|
||||
public class ShiftFunction : KeyFunction
|
||||
{
|
||||
[SerializeField]
|
||||
Sprite m_CapsLockDisplayIcon;
|
||||
|
||||
public Sprite capsLockDisplayIcon
|
||||
{
|
||||
get => m_CapsLockDisplayIcon;
|
||||
set => m_CapsLockDisplayIcon = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Sprite GetDisplayIcon(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
// This method won't be called unless OverrideDisplayIcon below returns true,
|
||||
// so no need for logic to return a shift display icon, which is already set up
|
||||
// as the default in the UI.
|
||||
return m_CapsLockDisplayIcon;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OverrideDisplayIcon(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
return keyboardContext != null && keyboardContext.capsLocked;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext == null)
|
||||
return;
|
||||
|
||||
var keyCode = KeyCode.LeftShift;
|
||||
|
||||
// Check the caps lock state of the keyboard. If they key is shifted, check if there is a double click.
|
||||
if (keyboardContext.capsLocked || (keyboardContext.shifted && key.timeSinceLastClick < keyboardContext.doubleClickInterval))
|
||||
keyCode = KeyCode.CapsLock;
|
||||
|
||||
keyboardContext.ProcessKeyCode(keyCode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PostprocessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
base.PostprocessKey(keyboardContext, key);
|
||||
RefreshKeyHighlight(keyboardContext, key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ProcessRefreshDisplay(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
base.ProcessRefreshDisplay(keyboardContext, key);
|
||||
RefreshKeyHighlight(keyboardContext, key);
|
||||
}
|
||||
|
||||
protected void RefreshKeyHighlight(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext == null)
|
||||
return;
|
||||
|
||||
var highlight = keyboardContext.capsLocked || keyboardContext.shifted;
|
||||
key.EnableHighlight(highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cad704687d7908f42b71e1e4516c590a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard.KeyFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key function used to update the keyboard text with a string value.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Value Key Function", menuName = "XR/Spatial Keyboard/Value Key Function", order = 1)]
|
||||
public class ValueKeyFunction : KeyFunction
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ProcessKey(XRKeyboard keyboardContext, XRKeyboardKey key)
|
||||
{
|
||||
if (keyboardContext != null)
|
||||
keyboardContext.UpdateText(key.GetEffectiveCharacter());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4857d519405a3874cb91aea6424e314d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// This component moves a set of transforms to the same local z-axis position as a poke follow transform.
|
||||
/// This is useful for batchable objects that need to move together.
|
||||
/// </summary>
|
||||
public class KeyboardBatchFollow : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The transform to follow.")]
|
||||
[SerializeField]
|
||||
Transform m_FollowTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The transform to follow.
|
||||
/// </summary>
|
||||
public Transform followTransform
|
||||
{
|
||||
get => m_FollowTransform;
|
||||
set => m_FollowTransform = value;
|
||||
}
|
||||
|
||||
[Tooltip("The transforms to move to the same local z-axis position as the poke follow transform.")]
|
||||
[SerializeField]
|
||||
Transform[] m_FollowerTransforms;
|
||||
|
||||
/// <summary>
|
||||
/// The transforms to move to the same local z-axis position as the poke follow transform.
|
||||
/// </summary>
|
||||
public Transform[] followerTransforms
|
||||
{
|
||||
get => m_FollowerTransforms;
|
||||
set => m_FollowerTransforms = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void OnDisable()
|
||||
{
|
||||
if (m_FollowerTransforms == null || m_FollowerTransforms.Length == 0)
|
||||
return;
|
||||
|
||||
for (var index = 0; index < m_FollowerTransforms.Length; ++index)
|
||||
{
|
||||
var follower = m_FollowerTransforms[index];
|
||||
var localPosition = follower.localPosition;
|
||||
localPosition.z = 0f;
|
||||
follower.localPosition = localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void LateUpdate()
|
||||
{
|
||||
if (m_FollowTransform == null || m_FollowerTransforms == null || m_FollowerTransforms.Length == 0)
|
||||
return;
|
||||
|
||||
var followLocalZ = m_FollowTransform.localPosition.z;
|
||||
|
||||
for (var index = 0; index < m_FollowerTransforms.Length; ++index)
|
||||
{
|
||||
var follower = m_FollowerTransforms[index];
|
||||
var localPosition = follower.localPosition;
|
||||
localPosition.z = followLocalZ;
|
||||
follower.localPosition = localPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a82a58ed0ea7024b86e87eac0be67c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,299 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// This script is used to optimize the keyboard rendering performance by updating the canvas hierarchy
|
||||
/// into separate parent transforms based on UI Grouping. This will greatly reduce the number of draw calls.
|
||||
/// Optimization is only done at runtime to prevent breaking the prefab.
|
||||
/// </summary>
|
||||
public class KeyboardOptimizer : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
bool m_OptimizeOnStart = true;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, the optimization will be called on <see cref="Start"/>.
|
||||
/// </summary>
|
||||
public bool optimizeOnStart
|
||||
{
|
||||
get => m_OptimizeOnStart;
|
||||
set => m_OptimizeOnStart = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_BatchGroupParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for batch groups.
|
||||
/// </summary>
|
||||
public Transform batchGroupParentTransform
|
||||
{
|
||||
get => m_BatchGroupParentTransform;
|
||||
set => m_BatchGroupParentTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_ButtonParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for buttons.
|
||||
/// </summary>
|
||||
public Transform buttonParentTransform
|
||||
{
|
||||
get => m_ButtonParentTransform;
|
||||
set => m_ButtonParentTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_ImageParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for images.
|
||||
/// </summary>
|
||||
public Transform imageParentTransform
|
||||
{
|
||||
get => m_ImageParentTransform;
|
||||
set => m_ImageParentTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_TextParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for text elements.
|
||||
/// </summary>
|
||||
public Transform textParentTransform
|
||||
{
|
||||
get => m_TextParentTransform;
|
||||
set => m_TextParentTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_IconParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for icons.
|
||||
/// </summary>
|
||||
public Transform iconParentTransform
|
||||
{
|
||||
get => m_IconParentTransform;
|
||||
set => m_IconParentTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Transform m_HighlightParentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The parent transform for highlights.
|
||||
/// </summary>
|
||||
public Transform highlightParentTransform
|
||||
{
|
||||
get => m_HighlightParentTransform;
|
||||
set => m_HighlightParentTransform = value;
|
||||
}
|
||||
|
||||
bool m_IsCurrentlyOptimized;
|
||||
|
||||
/// <summary>
|
||||
/// Is the keyboard currently optimized?
|
||||
/// </summary>
|
||||
public bool isCurrentlyOptimized => m_IsCurrentlyOptimized;
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal layout groups need to be disabled when optimizing the keyboard
|
||||
/// otherwise the input field will not position correctly.
|
||||
/// </summary>
|
||||
HorizontalLayoutGroup[] m_LayoutGroups;
|
||||
|
||||
/// <summary>
|
||||
/// List of key data. This is used to store information that allows us
|
||||
/// to revert the keyboard back to its original state (aka unoptimize).
|
||||
/// </summary>
|
||||
readonly List<KeyData> m_KeyData = new List<KeyData>();
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
CheckReferences();
|
||||
Canvas.ForceUpdateCanvases();
|
||||
|
||||
if (m_OptimizeOnStart)
|
||||
Optimize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all the references needed for optimization.
|
||||
/// </summary>
|
||||
void CheckReferences()
|
||||
{
|
||||
if (!TryGetOrCreateTransformReferences())
|
||||
{
|
||||
Debug.LogError("Failed to get or create transform references. Optimization will not be possible.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_KeyData.Count == 0)
|
||||
GetKeys();
|
||||
|
||||
if (m_LayoutGroups == null || m_LayoutGroups.Length == 0)
|
||||
GetLayoutGroups();
|
||||
}
|
||||
|
||||
bool TryGetOrCreateTransformReferences()
|
||||
{
|
||||
if (m_BatchGroupParentTransform == null)
|
||||
{
|
||||
var canvasComponent = GetComponentInChildren<Canvas>(true);
|
||||
if (canvasComponent == null)
|
||||
{
|
||||
Debug.LogError("No Canvas component found in hierarchy. Optimization will not be possible.", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_BatchGroupParentTransform = CreateTransformAndSetParent("BatchingGroup", canvasComponent.transform);
|
||||
}
|
||||
|
||||
if (m_ButtonParentTransform == null)
|
||||
m_ButtonParentTransform = CreateTransformAndSetParent("Buttons", m_BatchGroupParentTransform);
|
||||
|
||||
if (m_ImageParentTransform == null)
|
||||
m_ImageParentTransform = CreateTransformAndSetParent("Images", m_BatchGroupParentTransform);
|
||||
|
||||
if (m_TextParentTransform == null)
|
||||
m_TextParentTransform = CreateTransformAndSetParent("Text", m_BatchGroupParentTransform);
|
||||
|
||||
if (m_IconParentTransform == null)
|
||||
m_IconParentTransform = CreateTransformAndSetParent("Icons", m_BatchGroupParentTransform);
|
||||
|
||||
if (m_HighlightParentTransform == null)
|
||||
m_HighlightParentTransform = CreateTransformAndSetParent("Highlights", m_BatchGroupParentTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetKeys()
|
||||
{
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
XRKeyboardKey[] keys = GetComponentsInChildren<XRKeyboardKey>();
|
||||
foreach (var keyboardKey in keys)
|
||||
{
|
||||
m_KeyData.Add(new KeyData
|
||||
{
|
||||
key = keyboardKey,
|
||||
batchFollow = keyboardKey.GetComponent<KeyboardBatchFollow>(),
|
||||
parent = keyboardKey.transform.parent,
|
||||
childPosition = keyboardKey.transform.GetSiblingIndex(),
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GetLayoutGroups()
|
||||
{
|
||||
m_LayoutGroups = GetComponentsInChildren<HorizontalLayoutGroup>();
|
||||
}
|
||||
|
||||
static Transform CreateTransformAndSetParent(string name, Transform parent)
|
||||
{
|
||||
var t = new GameObject(name).transform;
|
||||
t.SetParent(parent);
|
||||
t.SetLocalPose(Pose.identity);
|
||||
t.localScale = Vector3.one;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimize the keyboard. This will set all the different components of each keyboard key into separate parent transforms for batching.
|
||||
/// </summary>
|
||||
public void Optimize()
|
||||
{
|
||||
m_IsCurrentlyOptimized = true;
|
||||
foreach (var layoutGroup in m_LayoutGroups)
|
||||
{
|
||||
layoutGroup.enabled = false;
|
||||
}
|
||||
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
foreach (var keyData in m_KeyData)
|
||||
{
|
||||
var key = keyData.key;
|
||||
if (key == null)
|
||||
continue;
|
||||
|
||||
key.transform.SetParent(m_ButtonParentTransform);
|
||||
|
||||
if (key.targetGraphic != null)
|
||||
key.targetGraphic.transform.SetParent(m_ImageParentTransform);
|
||||
|
||||
if (key.textComponent != null)
|
||||
key.textComponent.transform.SetParent(m_TextParentTransform);
|
||||
|
||||
if (key.iconComponent != null)
|
||||
key.iconComponent.transform.SetParent(m_IconParentTransform);
|
||||
|
||||
if (key.highlightComponent != null)
|
||||
key.highlightComponent.transform.SetParent(m_HighlightParentTransform);
|
||||
|
||||
if (keyData.batchFollow != null)
|
||||
keyData.batchFollow.enabled = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unoptimize the keyboard. This will set the keyboard back to its original state.
|
||||
/// </summary>
|
||||
public void Unoptimize()
|
||||
{
|
||||
m_IsCurrentlyOptimized = false;
|
||||
foreach (var layoutGroup in GetComponentsInChildren<HorizontalLayoutGroup>())
|
||||
{
|
||||
layoutGroup.enabled = true;
|
||||
}
|
||||
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
foreach (var keyData in m_KeyData)
|
||||
{
|
||||
var key = keyData.key;
|
||||
if (key == null)
|
||||
continue;
|
||||
|
||||
// NOTE: Order of objects setting their parent is important for sorting order.
|
||||
key.transform.SetParent(keyData.parent);
|
||||
key.transform.SetSiblingIndex(keyData.childPosition);
|
||||
|
||||
if (key.targetGraphic != null)
|
||||
key.targetGraphic.transform.SetParent(key.transform);
|
||||
|
||||
if (key.textComponent != null)
|
||||
key.textComponent.transform.SetParent(key.targetGraphic.transform);
|
||||
|
||||
if (key.iconComponent != null)
|
||||
key.iconComponent.transform.SetParent(key.targetGraphic.transform);
|
||||
|
||||
if (key.highlightComponent != null)
|
||||
key.highlightComponent.transform.SetParent(key.targetGraphic.transform);
|
||||
|
||||
if (keyData.batchFollow != null)
|
||||
keyData.batchFollow.enabled = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct KeyData
|
||||
{
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
public XRKeyboardKey key;
|
||||
#endif
|
||||
public KeyboardBatchFollow batchFollow;
|
||||
public Transform parent;
|
||||
public int childPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfb2fb55e0f7aff4db346e2c43f69469
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,798 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Virtual spatial keyboard.
|
||||
/// </summary>
|
||||
public class XRKeyboard : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Layout this keyboard is able to switch to with the corresponding layout command.
|
||||
/// </summary>
|
||||
/// <seealso cref="subsetLayout"/>
|
||||
[Serializable]
|
||||
public struct SubsetMapping
|
||||
{
|
||||
[SerializeField, Tooltip("This drives what GameObject layout is displayed.")]
|
||||
string m_LayoutString;
|
||||
|
||||
/// <summary>
|
||||
/// This drives what GameObject layout is displayed.
|
||||
/// </summary>
|
||||
public string layoutString
|
||||
{
|
||||
get => m_LayoutString;
|
||||
set => m_LayoutString = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("GameObject root of the layout which contains the set of keys.")]
|
||||
XRKeyboardLayout m_LayoutRoot;
|
||||
|
||||
/// <summary>
|
||||
/// GameObject root of the layout which contains the set of keys.
|
||||
/// </summary>
|
||||
public XRKeyboardLayout layoutRoot
|
||||
{
|
||||
get => m_LayoutRoot;
|
||||
set => m_LayoutRoot = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Config asset which contains the key definitions for the layout when this is turned on.")]
|
||||
XRKeyboardConfig m_ToggleOnConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Config asset which contains the key definitions for the layout when this is turned on.
|
||||
/// </summary>
|
||||
public XRKeyboardConfig toggleOnConfig
|
||||
{
|
||||
get => m_ToggleOnConfig;
|
||||
set => m_ToggleOnConfig = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Config asset which is the default config when this is turned off.")]
|
||||
XRKeyboardConfig m_ToggleOffConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Config asset which is the default config when this is turned off.
|
||||
/// </summary>
|
||||
public XRKeyboardConfig toggleOffConfig
|
||||
{
|
||||
get => m_ToggleOffConfig;
|
||||
set => m_ToggleOffConfig = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
string m_Text = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// String of text currently in the keyboard. Setter invokes <see cref="onTextUpdated"/> when updated.
|
||||
/// </summary>
|
||||
public string text
|
||||
{
|
||||
get => m_Text;
|
||||
protected set
|
||||
{
|
||||
if (m_Text != value)
|
||||
{
|
||||
m_Text = value;
|
||||
caretPosition = Math.Clamp(caretPosition, 0, m_Text.Length);
|
||||
using (m_KeyboardTextEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.keyboardText = text;
|
||||
onTextUpdated?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
TMP_InputField m_CurrentInputField;
|
||||
|
||||
/// <summary>
|
||||
/// Current input field this keyboard is observing.
|
||||
/// </summary>
|
||||
protected TMP_InputField currentInputField
|
||||
{
|
||||
get => m_CurrentInputField;
|
||||
set
|
||||
{
|
||||
if (m_CurrentInputField == value)
|
||||
return;
|
||||
|
||||
StopObservingInputField(m_CurrentInputField);
|
||||
m_CurrentInputField = value;
|
||||
StartObservingInputField(m_CurrentInputField);
|
||||
|
||||
using (m_KeyboardTextEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.keyboardText = text;
|
||||
onFocusChanged?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardTextEvent m_OnTextSubmitted = new KeyboardTextEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when keyboard submits text.
|
||||
/// </summary>
|
||||
public KeyboardTextEvent onTextSubmitted
|
||||
{
|
||||
get => m_OnTextSubmitted;
|
||||
set => m_OnTextSubmitted = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardTextEvent m_OnTextUpdated = new KeyboardTextEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when keyboard text is updated.
|
||||
/// </summary>
|
||||
public KeyboardTextEvent onTextUpdated
|
||||
{
|
||||
get => m_OnTextUpdated;
|
||||
set => m_OnTextUpdated = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardKeyEvent m_OnKeyPressed = new KeyboardKeyEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked after a key is pressed.
|
||||
/// </summary>
|
||||
public KeyboardKeyEvent onKeyPressed
|
||||
{
|
||||
get => m_OnKeyPressed;
|
||||
set => m_OnKeyPressed = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardModifiersEvent m_OnShifted = new KeyboardModifiersEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked after keyboard shift is changed. These event args also contain the value for the caps lock state.
|
||||
/// </summary>
|
||||
public KeyboardModifiersEvent onShifted
|
||||
{
|
||||
get => m_OnShifted;
|
||||
set => m_OnShifted = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardLayoutEvent m_OnLayoutChanged = new KeyboardLayoutEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when keyboard layout is changed.
|
||||
/// </summary>
|
||||
public KeyboardLayoutEvent onLayoutChanged
|
||||
{
|
||||
get => m_OnLayoutChanged;
|
||||
set => m_OnLayoutChanged = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardTextEvent m_OnOpened = new KeyboardTextEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when the keyboard is opened.
|
||||
/// </summary>
|
||||
public KeyboardTextEvent onOpened
|
||||
{
|
||||
get => m_OnOpened;
|
||||
set => m_OnOpened = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardTextEvent m_OnClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked after the keyboard is closed.
|
||||
/// </summary>
|
||||
public KeyboardTextEvent onClosed
|
||||
{
|
||||
get => m_OnClosed;
|
||||
set => m_OnClosed = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardTextEvent m_OnFocusChanged = new KeyboardTextEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when the keyboard changes or gains input field focus.
|
||||
/// </summary>
|
||||
public KeyboardTextEvent onFocusChanged
|
||||
{
|
||||
get => m_OnFocusChanged;
|
||||
set => m_OnFocusChanged = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyboardEvent m_OnCharacterLimitReached = new KeyboardEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when the keyboard tries to update text, but the character of the input field is reached.
|
||||
/// </summary>
|
||||
public KeyboardEvent onCharacterLimitReached
|
||||
{
|
||||
get => m_OnCharacterLimitReached;
|
||||
set => m_OnCharacterLimitReached = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_SubmitOnEnter = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, <see cref="onTextSubmitted"/> will be invoked when the keyboard receives a return or enter command. Otherwise,
|
||||
/// it will treat return or enter as a newline.
|
||||
/// </summary>
|
||||
public bool submitOnEnter
|
||||
{
|
||||
get => m_SubmitOnEnter;
|
||||
set => m_SubmitOnEnter = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_CloseOnSubmit;
|
||||
|
||||
/// <summary>
|
||||
/// If true, keyboard will close on enter or return command.
|
||||
/// </summary>
|
||||
public bool closeOnSubmit
|
||||
{
|
||||
get => m_CloseOnSubmit;
|
||||
set => m_CloseOnSubmit = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float m_DoubleClickInterval = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Interval in which a key pressed twice would be considered a double click.
|
||||
/// </summary>
|
||||
public float doubleClickInterval
|
||||
{
|
||||
get => m_DoubleClickInterval;
|
||||
set => m_DoubleClickInterval = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
List<SubsetMapping> m_SubsetLayout;
|
||||
|
||||
/// <summary>
|
||||
/// List of layouts this keyboard is able to switch between given the corresponding layout command.
|
||||
/// </summary>
|
||||
/// <remarks>This supports multiple layout roots updating with the same <see cref="SubsetMapping.layoutString"/>.</remarks>
|
||||
public List<SubsetMapping> subsetLayout
|
||||
{
|
||||
get => m_SubsetLayout;
|
||||
set => m_SubsetLayout = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of keys associated with this keyboard.
|
||||
/// </summary>
|
||||
public List<XRKeyboardKey> keys { get; set; }
|
||||
|
||||
int m_CaretPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Caret index of this keyboard.
|
||||
/// </summary>
|
||||
public int caretPosition
|
||||
{
|
||||
get => m_CaretPosition;
|
||||
protected set => m_CaretPosition = value;
|
||||
}
|
||||
|
||||
bool m_Shifted;
|
||||
|
||||
/// <summary>
|
||||
/// (Read Only) Gets the shift state of the keyboard.
|
||||
/// </summary>
|
||||
public bool shifted => m_Shifted;
|
||||
|
||||
bool m_CapsLocked;
|
||||
|
||||
/// <summary>
|
||||
/// (Read Only) Gets the caps lock state of the keyboard.
|
||||
/// </summary>
|
||||
public bool capsLocked => m_CapsLocked;
|
||||
|
||||
bool m_IsOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the keyboard has been opened with the open function and the keyboard is active and enabled, otherwise returns false.
|
||||
/// </summary>
|
||||
public bool isOpen => (m_IsOpen && isActiveAndEnabled);
|
||||
|
||||
Dictionary<string, List<SubsetMapping>> m_SubsetLayoutMap;
|
||||
HashSet<XRKeyboardLayout> m_KeyboardLayouts;
|
||||
|
||||
// Reusable event args
|
||||
readonly LinkedPool<KeyboardTextEventArgs> m_KeyboardTextEventArgs = new LinkedPool<KeyboardTextEventArgs>(() => new KeyboardTextEventArgs(), collectionCheck: false);
|
||||
readonly LinkedPool<KeyboardLayoutEventArgs> m_KeyboardLayoutEventArgs = new LinkedPool<KeyboardLayoutEventArgs>(() => new KeyboardLayoutEventArgs(), collectionCheck: false);
|
||||
readonly LinkedPool<KeyboardModifiersEventArgs> m_KeyboardModifiersEventArgs = new LinkedPool<KeyboardModifiersEventArgs>(() => new KeyboardModifiersEventArgs(), collectionCheck: false);
|
||||
readonly LinkedPool<KeyboardKeyEventArgs> m_KeyboardKeyEventArgs = new LinkedPool<KeyboardKeyEventArgs>(() => new KeyboardKeyEventArgs(), collectionCheck: false);
|
||||
readonly LinkedPool<KeyboardBaseEventArgs> m_KeyboardBaseEventArgs = new LinkedPool<KeyboardBaseEventArgs>(() => new KeyboardBaseEventArgs(), collectionCheck: false);
|
||||
|
||||
int m_CharacterLimit = -1;
|
||||
bool m_MonitorCharacterLimit;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_SubsetLayoutMap = new Dictionary<string, List<SubsetMapping>>();
|
||||
m_KeyboardLayouts = new HashSet<XRKeyboardLayout>();
|
||||
|
||||
foreach (var subsetMapping in m_SubsetLayout)
|
||||
{
|
||||
if (m_SubsetLayoutMap.TryGetValue(subsetMapping.layoutString, out var subsetMappings))
|
||||
subsetMappings.Add(subsetMapping);
|
||||
else
|
||||
m_SubsetLayoutMap[subsetMapping.layoutString] = new List<SubsetMapping> { subsetMapping };
|
||||
|
||||
m_KeyboardLayouts.Add(subsetMapping.layoutRoot);
|
||||
}
|
||||
|
||||
keys = new List<XRKeyboardKey>();
|
||||
GetComponentsInChildren(true, keys);
|
||||
keys.ForEach(key => key.keyboard = this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
// Reset if this component is turned off without first calling close function
|
||||
m_IsOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a <see cref="KeyCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="keyCode">Key code to process.</param>
|
||||
/// <returns>True on supported KeyCode.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to add support for additional <see cref="KeyCode"/>.
|
||||
/// </remarks>
|
||||
public virtual bool ProcessKeyCode(KeyCode keyCode)
|
||||
{
|
||||
var success = true;
|
||||
switch (keyCode)
|
||||
{
|
||||
case KeyCode.LeftShift:
|
||||
case KeyCode.RightShift:
|
||||
Shift(!m_Shifted);
|
||||
break;
|
||||
case KeyCode.CapsLock:
|
||||
CapsLock(!m_CapsLocked);
|
||||
break;
|
||||
case KeyCode.Backspace:
|
||||
Backspace();
|
||||
break;
|
||||
case KeyCode.Delete:
|
||||
Delete();
|
||||
break;
|
||||
case KeyCode.Clear:
|
||||
Clear();
|
||||
break;
|
||||
case KeyCode.Space:
|
||||
UpdateText(" ");
|
||||
break;
|
||||
case KeyCode.Return:
|
||||
case KeyCode.KeypadEnter:
|
||||
if (submitOnEnter)
|
||||
{
|
||||
Submit();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateText("\n");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to process the key based on the key's character. Used as a fallback when KeyFunction is
|
||||
/// empty on the key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to attempt to process</param>
|
||||
public virtual void TryProcessKeyPress(XRKeyboardKey key)
|
||||
{
|
||||
if (key == null || !ReferenceEquals(key.keyboard, this))
|
||||
return;
|
||||
|
||||
// Process key stroke
|
||||
if (onKeyPressed != null)
|
||||
{
|
||||
// Try to process key code
|
||||
if (ProcessKeyCode(key.keyCode))
|
||||
return;
|
||||
|
||||
var keyPress = key.GetEffectiveCharacter();
|
||||
|
||||
// Monitor for subset change
|
||||
if (UpdateLayout(keyPress))
|
||||
return;
|
||||
|
||||
switch (keyPress)
|
||||
{
|
||||
case "\\s":
|
||||
// Shift
|
||||
Shift(!m_Shifted);
|
||||
break;
|
||||
case "\\caps":
|
||||
CapsLock(!m_CapsLocked);
|
||||
break;
|
||||
case "\\b":
|
||||
// Backspace
|
||||
Backspace();
|
||||
break;
|
||||
case "\\c":
|
||||
// cancel
|
||||
break;
|
||||
case "\\r" when submitOnEnter:
|
||||
{
|
||||
Submit();
|
||||
break;
|
||||
}
|
||||
case "\\cl":
|
||||
// Clear
|
||||
Clear();
|
||||
break;
|
||||
case "\\h":
|
||||
// Hide
|
||||
Close();
|
||||
break;
|
||||
default:
|
||||
{
|
||||
UpdateText(keyPress);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-process function when a key is pressed.
|
||||
/// </summary>
|
||||
/// <param name="key">Key that is about to process.</param>
|
||||
public virtual void PreprocessKeyPress(XRKeyboardKey key)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post-process function when a key is pressed.
|
||||
/// </summary>
|
||||
/// <param name="key">Key that has just been processed.</param>
|
||||
public virtual void PostprocessKeyPress(XRKeyboardKey key)
|
||||
{
|
||||
using (m_KeyboardKeyEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.key = key;
|
||||
onKeyPressed.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
#region Process Key Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the keyboard text by inserting the <see cref="newText"/> string into the existing <see cref="text"/>.
|
||||
/// </summary>
|
||||
/// <param name="newText">The new text to insert into the current keyboard text.</param>
|
||||
/// <remarks>If the keyboard is set to monitor the input field's character limit, the keyboard will ensure
|
||||
/// the text does not exceed the <see cref="TMP_InputField.characterLimit"/>.</remarks>
|
||||
public virtual void UpdateText(string newText)
|
||||
{
|
||||
// Attempt to add key press to current text
|
||||
var updatedText = text;
|
||||
|
||||
updatedText = updatedText.Insert(caretPosition, newText);
|
||||
|
||||
var isUpdatedTextWithinLimits = !m_MonitorCharacterLimit || updatedText.Length <= m_CharacterLimit;
|
||||
if (isUpdatedTextWithinLimits)
|
||||
{
|
||||
caretPosition += newText.Length;
|
||||
text = updatedText;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (m_KeyboardBaseEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
onCharacterLimitReached?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
// Turn off shift after typing a letter
|
||||
if (m_Shifted && !m_CapsLocked)
|
||||
Shift(!m_Shifted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process shift command for keyboard.
|
||||
/// </summary>
|
||||
public virtual void Shift(bool shiftValue)
|
||||
{
|
||||
m_Shifted = shiftValue;
|
||||
using (m_KeyboardModifiersEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.shiftValue = m_Shifted;
|
||||
args.capsLockValue = m_CapsLocked;
|
||||
onShifted.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process caps lock command for keyboard.
|
||||
/// </summary>
|
||||
public virtual void CapsLock(bool capsLockValue)
|
||||
{
|
||||
m_CapsLocked = capsLockValue;
|
||||
Shift(capsLockValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process backspace command for keyboard.
|
||||
/// </summary>
|
||||
public virtual void Backspace()
|
||||
{
|
||||
if (caretPosition > 0)
|
||||
{
|
||||
--caretPosition;
|
||||
text = text.Remove(caretPosition, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process delete command for keyboard and deletes one character.
|
||||
/// </summary>
|
||||
public virtual void Delete()
|
||||
{
|
||||
if (caretPosition < text.Length)
|
||||
{
|
||||
text = text.Remove(caretPosition, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="onTextSubmitted"/> event and closes keyboard if <see cref="closeOnSubmit"/> is true.
|
||||
/// </summary>
|
||||
public virtual void Submit()
|
||||
{
|
||||
using (m_KeyboardTextEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.keyboardText = text;
|
||||
onTextSubmitted?.Invoke(args);
|
||||
}
|
||||
|
||||
if (closeOnSubmit)
|
||||
Close(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears text to an empty string.
|
||||
/// </summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
text = string.Empty;
|
||||
caretPosition = text.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the <see cref="SubsetMapping"/> associated with the <see cref="layoutKey"/> and updates the
|
||||
/// <see cref="XRKeyboardLayout"/> on the <see cref="SubsetMapping.layoutRoot"/>. If the
|
||||
/// <see cref="XRKeyboardLayout.activeKeyMapping"/> is already <see cref="SubsetMapping.toggleOnConfig"/>,
|
||||
/// <see cref="SubsetMapping.toggleOffConfig"/> will be set as the active key mapping.
|
||||
/// </summary>
|
||||
/// <param name="layoutKey">The string of the new layout as it is registered in the <see cref="subsetLayout"/>.</param>
|
||||
/// <returns>Returns true if the layout was successfully found and changed.</returns>
|
||||
/// <remarks>By default, shift or caps lock will be turned off on layout change.</remarks>
|
||||
public virtual bool UpdateLayout(string layoutKey)
|
||||
{
|
||||
if (m_SubsetLayoutMap.TryGetValue(layoutKey, out var subsetMappings))
|
||||
{
|
||||
foreach (var subsetMapping in subsetMappings)
|
||||
{
|
||||
var layout = subsetMapping.layoutRoot;
|
||||
layout.activeKeyMapping = layout.activeKeyMapping != subsetMapping.toggleOnConfig ? subsetMapping.toggleOnConfig : subsetMapping.toggleOffConfig;
|
||||
}
|
||||
|
||||
if (m_Shifted || m_CapsLocked)
|
||||
CapsLock(false);
|
||||
|
||||
using (m_KeyboardLayoutEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.layout = layoutKey;
|
||||
onLayoutChanged.Invoke(args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Open Functions
|
||||
|
||||
/// <summary>
|
||||
/// Opens the keyboard with a <see cref="TMP_InputField"/> parameter as the active input field.
|
||||
/// </summary>
|
||||
/// <param name="inputField">The input field opening this keyboard.</param>
|
||||
/// <param name="observeCharacterLimit">If true, keyboard will observe the character limit from the <see cref="inputField"/>.</param>
|
||||
public virtual void Open(TMP_InputField inputField, bool observeCharacterLimit = false)
|
||||
{
|
||||
currentInputField = inputField;
|
||||
m_MonitorCharacterLimit = observeCharacterLimit;
|
||||
m_CharacterLimit = observeCharacterLimit ? currentInputField.characterLimit : -1;
|
||||
|
||||
Open(currentInputField.text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the keyboard with any existing text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortcut for <c>Open(text)</c>.
|
||||
/// </remarks>
|
||||
public void Open() => Open(text);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the keyboard with an empty string and clear any existing text in the input field or keyboard.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortcut for <c>Open(string.Empty)</c>.
|
||||
/// </remarks>
|
||||
public void OpenCleared() => Open(string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the keyboard with a given string to populate the keyboard text.
|
||||
/// </summary>
|
||||
/// <param name="newText">Text string to set the keyboard <see cref="text"/> to.</param>
|
||||
/// <remarks>The <see cref="onOpened"/> event is fired before the text is updating with <see cref="newText"/>
|
||||
/// to give any observers that would be listening the opportunity to close and stop observing before the text is updated.
|
||||
/// This is a common use case for any <see cref="XRKeyboardDisplay"/> utilizing the global keyboard. </remarks>
|
||||
public virtual void Open(string newText)
|
||||
{
|
||||
if (!isActiveAndEnabled)
|
||||
{
|
||||
// Fire event before updating text because any displays observing keyboards will be listening to that text change
|
||||
// This gives them the opportunity to close and stop observing before the text is updated.
|
||||
using (m_KeyboardTextEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.keyboardText = text;
|
||||
onOpened?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
caretPosition = newText.Length;
|
||||
text = newText;
|
||||
gameObject.SetActive(true);
|
||||
m_IsOpen = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Close Functions
|
||||
|
||||
/// <summary>
|
||||
/// Process close command for keyboard.
|
||||
/// </summary>
|
||||
/// <remarks>Stops observing active input field, resets variables, and hides this GameObject.</remarks>
|
||||
public virtual void Close()
|
||||
{
|
||||
// Clear any input field the keyboard is observing
|
||||
currentInputField = null;
|
||||
|
||||
m_MonitorCharacterLimit = false;
|
||||
m_CharacterLimit = -1;
|
||||
|
||||
if (m_Shifted || m_CapsLocked)
|
||||
CapsLock(false);
|
||||
|
||||
using (m_KeyboardTextEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.keyboardText = text;
|
||||
onClosed?.Invoke(args);
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
m_IsOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process close command for keyboard. Optional overload for clearing text and resetting layout on close.
|
||||
/// </summary>
|
||||
/// <param name="clearText">If true, text will be cleared upon keyboard closing. This will happen after the
|
||||
/// <see cref="onClosed"/> event is fired so the observers have time to stop listening.</param>
|
||||
/// <param name="resetLayout">If true, each <see cref="XRKeyboardLayout"/> will reset to the <see cref="XRKeyboardLayout.defaultKeyMapping"/>.</param>
|
||||
/// <remarks>Please note, if <see cref="clearText"/> is true, the text will be cleared and the <see cref="onTextUpdated"/>
|
||||
/// event will be fired. This means any observers will be notified of an empty string. To avoid unwanted behavior of
|
||||
/// the text clearing, use the <see cref="onClosed"/> event to unsubscribe to the keyboard events before the text is cleared.</remarks>
|
||||
public virtual void Close(bool clearText, bool resetLayout = true)
|
||||
{
|
||||
Close();
|
||||
|
||||
if (clearText)
|
||||
text = string.Empty;
|
||||
|
||||
// Reset keyboard layout on close
|
||||
if (resetLayout)
|
||||
{
|
||||
// Loop through each layout root and reset to default layouts
|
||||
foreach (var layoutRoot in m_KeyboardLayouts)
|
||||
{
|
||||
layoutRoot.SetDefaultLayout();
|
||||
}
|
||||
|
||||
// Fire event of layout change to ensure highlighted buttons are reset
|
||||
using (m_KeyboardLayoutEventArgs.Get(out var args))
|
||||
{
|
||||
args.keyboard = this;
|
||||
args.layout = "default";
|
||||
onLayoutChanged.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Input Field Handling
|
||||
|
||||
protected virtual void StopObservingInputField(TMP_InputField inputField)
|
||||
{
|
||||
if (inputField == null)
|
||||
return;
|
||||
|
||||
currentInputField.onValueChanged.RemoveListener(OnInputFieldValueChange);
|
||||
}
|
||||
|
||||
protected virtual void StartObservingInputField(TMP_InputField inputField)
|
||||
{
|
||||
if (inputField == null)
|
||||
return;
|
||||
|
||||
currentInputField.onValueChanged.AddListener(OnInputFieldValueChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method invoked when the input field's text value changes.
|
||||
/// </summary>
|
||||
/// <param name="updatedText">The text of the input field.</param>
|
||||
protected virtual void OnInputFieldValueChange(string updatedText)
|
||||
{
|
||||
caretPosition = updatedText.Length;
|
||||
text = updatedText;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f17a1b36fcb274449d030ef08f66941
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,177 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Scriptable object that defines key mappings to support swapping <see cref="XRKeyboardLayout"/>. There should be one
|
||||
/// instance of the <see cref="XRKeyboardConfig"/> for each layout (i.e. alphanumeric, symbols, etc.).
|
||||
/// </summary>
|
||||
public class XRKeyboardConfig : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing the data needed to populate keys.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class KeyMapping
|
||||
{
|
||||
[SerializeField]
|
||||
string m_Character;
|
||||
|
||||
/// <summary>
|
||||
/// Character for this key in non-shifted state. This string will be passed to the keyboard and appended to the keyboard text string or processed as a keyboard command.
|
||||
/// </summary>
|
||||
public string character
|
||||
{
|
||||
get => m_Character;
|
||||
set => m_Character = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
string m_DisplayCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Display character for this key in a non-shifted state. This string will be displayed on the key text field.
|
||||
/// If empty, character will be used as a fallback.
|
||||
/// </summary>
|
||||
public string displayCharacter
|
||||
{
|
||||
get => m_DisplayCharacter;
|
||||
set => m_DisplayCharacter = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Sprite m_DisplayIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Display icon for this key in a non-shifted state. This icon will be displayed on the key image field.
|
||||
/// If empty, the display character or character will be used as a fallback.
|
||||
/// </summary>
|
||||
public Sprite displayIcon
|
||||
{
|
||||
get => m_DisplayIcon;
|
||||
set => m_DisplayIcon = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
string m_ShiftCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Character for this key in a shifted state. This string will be passed to the keyboard and appended to
|
||||
/// the keyboard text string or processed as a keyboard command.
|
||||
/// </summary>
|
||||
public string shiftCharacter
|
||||
{
|
||||
get => m_ShiftCharacter;
|
||||
set => m_ShiftCharacter = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
string m_ShiftDisplayCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Display character for this key in a shifted state. This string will be displayed on the key
|
||||
/// text field. If empty, shift character will be used as a fallback.
|
||||
/// </summary>
|
||||
public string shiftDisplayCharacter
|
||||
{
|
||||
get => m_ShiftDisplayCharacter;
|
||||
set => m_ShiftDisplayCharacter = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Sprite m_ShiftDisplayIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Display icon for this key in a shifted state. This icon will be displayed on the key image field.
|
||||
/// If empty, the shift display character or shift character will be used as a fallback.
|
||||
/// </summary>
|
||||
public Sprite shiftDisplayIcon
|
||||
{
|
||||
get => m_ShiftDisplayIcon;
|
||||
set => m_ShiftDisplayIcon = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_OverrideDefaultKeyFunction;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this will expose a key function property to override the default key function of this config.
|
||||
/// </summary>
|
||||
public bool overrideDefaultKeyFunction
|
||||
{
|
||||
get => m_OverrideDefaultKeyFunction;
|
||||
set => m_OverrideDefaultKeyFunction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyFunction m_KeyFunction;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="KeyFunction"/> used for this key. The function callback will be called on key press
|
||||
/// and used to communicate with the keyboard API.
|
||||
/// </summary>
|
||||
public KeyFunction keyFunction
|
||||
{
|
||||
get => m_KeyFunction;
|
||||
set => m_KeyFunction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KeyCode m_KeyCode;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) <see cref="KeyCode"/> used for this key. Used with <see cref="keyFunction"/> to
|
||||
/// support already defined KeyCode values.
|
||||
/// </summary>
|
||||
public KeyCode keyCode
|
||||
{
|
||||
get => m_KeyCode;
|
||||
set => m_KeyCode = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the key button interactable property will be set to false.
|
||||
/// </summary>
|
||||
public bool disabled
|
||||
{
|
||||
get => m_Disabled;
|
||||
set => m_Disabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Default key function for each key in this mapping.")]
|
||||
KeyFunction m_DefaultKeyFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Default key function for each key in this mapping.
|
||||
/// </summary>
|
||||
/// <remarks>This is a utility feature that reduces the authoring needed when most key mappings share the same
|
||||
/// functionality (i.e. value keys that append characters).</remarks>
|
||||
public KeyFunction defaultKeyFunction
|
||||
{
|
||||
get => m_DefaultKeyFunction;
|
||||
set => m_DefaultKeyFunction = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of each key mapping in this layout.
|
||||
/// </summary>
|
||||
[SerializeField, Tooltip("List of each key mapping in this layout.")]
|
||||
List<KeyMapping> m_KeyMappings;
|
||||
|
||||
/// <summary>
|
||||
/// List of each key mapping in this layout.
|
||||
/// </summary>
|
||||
public List<KeyMapping> keyMappings
|
||||
{
|
||||
get => m_KeyMappings;
|
||||
set => m_KeyMappings = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 944d8cf1c59c0e8468bcd7e2fd86fe4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,414 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using TMPro;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to help facilitate input field relationship with <see cref="XRKeyboard"/>
|
||||
/// </summary>
|
||||
public class XRKeyboardDisplay : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Input field linked to this display.")]
|
||||
TMP_InputField m_InputField;
|
||||
|
||||
/// <summary>
|
||||
/// Input field linked to this display.
|
||||
/// </summary>
|
||||
public TMP_InputField inputField
|
||||
{
|
||||
get => m_InputField;
|
||||
set
|
||||
{
|
||||
if (inputField != null)
|
||||
m_InputField.onSelect.RemoveListener(OnInputFieldGainedFocus);
|
||||
|
||||
m_InputField = value;
|
||||
|
||||
if (inputField != null)
|
||||
{
|
||||
m_InputField.resetOnDeActivation = false;
|
||||
m_InputField.onSelect.AddListener(OnInputFieldGainedFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The script requires setter property logic to be run, so disable when playing
|
||||
[SerializeField, Tooltip("Keyboard for this display to monitor and interact with. If empty this will default to the GlobalNonNativeKeyboard keyboard.")]
|
||||
XRKeyboard m_Keyboard;
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard for this display to monitor and interact with. If empty this will default to the <see cref="GlobalNonNativeKeyboard"/> keyboard.
|
||||
/// </summary>
|
||||
public XRKeyboard keyboard
|
||||
{
|
||||
get => m_Keyboard;
|
||||
set => SetKeyboard(value);
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will use the keyboard reference. If false or if the keyboard field is empty, this display will use global keyboard.")]
|
||||
bool m_UseSceneKeyboard;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will use the keyboard reference. If false or if the keyboard field is empty,
|
||||
/// this display will use global keyboard.
|
||||
/// </summary>
|
||||
public bool useSceneKeyboard
|
||||
{
|
||||
get => m_UseSceneKeyboard;
|
||||
set => m_UseSceneKeyboard = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will update with each key press. If false, this display will update on OnTextSubmit.")]
|
||||
bool m_UpdateOnKeyPress = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will update with each key press. If false, this display will update on OnTextSubmit.
|
||||
/// </summary>
|
||||
public bool updateOnKeyPress
|
||||
{
|
||||
get => m_UpdateOnKeyPress;
|
||||
set => m_UpdateOnKeyPress = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will always subscribe to the keyboard updates. If false, this display will subscribe to keyboard when the input field gains focus.")]
|
||||
bool m_AlwaysObserveKeyboard;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will always subscribe to the keyboard updates. If false, this display will subscribe
|
||||
/// to keyboard when the input field gains focus.
|
||||
/// </summary>
|
||||
public bool alwaysObserveKeyboard
|
||||
{
|
||||
get => m_AlwaysObserveKeyboard;
|
||||
set => m_AlwaysObserveKeyboard = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will use the input field's character limit to limit the update text from the keyboard and will pass this into the keyboard when opening.")]
|
||||
public bool m_MonitorInputFieldCharacterLimit;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will use the input field's character limit to limit the update text from the keyboard
|
||||
/// and will pass this into the keyboard when opening if.
|
||||
/// </summary>
|
||||
public bool monitorInputFieldCharacterLimit
|
||||
{
|
||||
get => m_MonitorInputFieldCharacterLimit;
|
||||
set => m_MonitorInputFieldCharacterLimit = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will clear the input field text on text submit from the keyboard.")]
|
||||
public bool m_ClearTextOnSubmit;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will clear the input field text on text submit from the keyboard.
|
||||
/// </summary>
|
||||
public bool clearTextOnSubmit
|
||||
{
|
||||
get => m_ClearTextOnSubmit;
|
||||
set => m_ClearTextOnSubmit = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will clear the input field text when the keyboard opens.")]
|
||||
public bool m_ClearTextOnOpen;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will clear the input field text on text submit from the keyboard.
|
||||
/// </summary>
|
||||
public bool clearTextOnOpen
|
||||
{
|
||||
get => m_ClearTextOnOpen;
|
||||
set => m_ClearTextOnOpen = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, this display will close the keyboard it is observing when this GameObject is disabled.")]
|
||||
public bool m_HideKeyboardOnDisable = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this display will close the keyboard it is observing when this GameObject is disabled.
|
||||
/// </summary>
|
||||
/// <remarks>If this display is not observing a keyboard when disabled, this will have not effect on open keyboards.</remarks>
|
||||
public bool hideKeyboardOnDisable
|
||||
{
|
||||
get => m_HideKeyboardOnDisable;
|
||||
set => m_HideKeyboardOnDisable = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The event that is called when this display receives a text submitted event from the keyboard. Invoked with the keyboard text as a parameter.")]
|
||||
UnityEvent<string> m_OnTextSubmitted = new UnityEvent<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The event that is called when this display receives a text submitted event from the keyboard.
|
||||
/// </summary>
|
||||
public UnityEvent<string> onTextSubmitted
|
||||
{
|
||||
get => m_OnTextSubmitted;
|
||||
set => m_OnTextSubmitted = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The event that is called when this display opens a keyboard.")]
|
||||
UnityEvent m_OnKeyboardOpened = new UnityEvent();
|
||||
|
||||
/// <summary>
|
||||
/// The event that is called when this display opens a keyboard.
|
||||
/// </summary>
|
||||
public UnityEvent onKeyboardOpened
|
||||
{
|
||||
get => m_OnKeyboardOpened;
|
||||
set => m_OnKeyboardOpened = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The event that is called when the keyboard this display is observing is closed.")]
|
||||
UnityEvent m_OnKeyboardClosed = new UnityEvent();
|
||||
|
||||
/// <summary>
|
||||
/// The event that is called when the keyboard this display is observing is closed.
|
||||
/// </summary>
|
||||
public UnityEvent onKeyboardClosed
|
||||
{
|
||||
get => m_OnKeyboardClosed;
|
||||
set => m_OnKeyboardClosed = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The event that is called when the keyboard changes focus and this display is not focused.")]
|
||||
UnityEvent m_OnKeyboardFocusChanged = new UnityEvent();
|
||||
|
||||
/// <summary>
|
||||
/// The event that is called when the keyboard changes focus and this display is not focused.
|
||||
/// </summary>
|
||||
public UnityEvent onKeyboardFocusChanged
|
||||
{
|
||||
get => m_OnKeyboardFocusChanged;
|
||||
set => m_OnKeyboardFocusChanged = value;
|
||||
}
|
||||
|
||||
// Active keyboard for this display
|
||||
XRKeyboard m_ActiveKeyboard;
|
||||
|
||||
bool m_IsActivelyObservingKeyboard;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
// Set active keyboard to any serialized keyboard
|
||||
m_ActiveKeyboard = m_Keyboard;
|
||||
|
||||
if (m_InputField != null)
|
||||
{
|
||||
// resetOnDeActivation should be false so the caret position does not break with the keyboard interaction
|
||||
m_InputField.resetOnDeActivation = false;
|
||||
|
||||
// shouldHideSoftKeyboard should be true so there is no conflict with the spatial keyboard and the system keyboard
|
||||
m_InputField.shouldHideSoftKeyboard = true;
|
||||
}
|
||||
|
||||
if (m_AlwaysObserveKeyboard && m_ActiveKeyboard != null)
|
||||
StartObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_InputField != null)
|
||||
m_InputField.onSelect.AddListener(OnInputFieldGainedFocus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
if (m_InputField != null)
|
||||
m_InputField.onSelect.RemoveListener(OnInputFieldGainedFocus);
|
||||
|
||||
// Close the keyboard this display is observing
|
||||
var isObservingKeyboard = m_ActiveKeyboard != null && m_ActiveKeyboard.gameObject.activeInHierarchy && m_IsActivelyObservingKeyboard;
|
||||
if (m_HideKeyboardOnDisable && isObservingKeyboard && m_ActiveKeyboard.isOpen)
|
||||
m_ActiveKeyboard.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
StopObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
// Set active keyboard to global keyboard if needed
|
||||
if (m_ActiveKeyboard == null || !m_UseSceneKeyboard)
|
||||
m_ActiveKeyboard = GlobalNonNativeKeyboard.instance.keyboard;
|
||||
|
||||
// Observe keyboard if always observe is true
|
||||
var observeOnStart = m_AlwaysObserveKeyboard && m_ActiveKeyboard != null & !m_IsActivelyObservingKeyboard;
|
||||
if (observeOnStart)
|
||||
StartObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
void SetKeyboard(XRKeyboard updateKeyboard, bool observeKeyboard = true)
|
||||
{
|
||||
if (ReferenceEquals(updateKeyboard, m_Keyboard))
|
||||
return;
|
||||
|
||||
StopObservingKeyboard(m_ActiveKeyboard);
|
||||
|
||||
// Update serialized referenced
|
||||
m_Keyboard = updateKeyboard;
|
||||
|
||||
// Update private keyboard
|
||||
m_ActiveKeyboard = m_Keyboard;
|
||||
|
||||
if (m_ActiveKeyboard != null && (observeKeyboard || m_AlwaysObserveKeyboard))
|
||||
StartObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
void StartObservingKeyboard(XRKeyboard activeKeyboard)
|
||||
{
|
||||
if (activeKeyboard == null || m_IsActivelyObservingKeyboard)
|
||||
return;
|
||||
|
||||
activeKeyboard.onTextUpdated.AddListener(OnTextUpdate);
|
||||
activeKeyboard.onTextSubmitted.AddListener(OnTextSubmit);
|
||||
activeKeyboard.onClosed.AddListener(KeyboardClosing);
|
||||
activeKeyboard.onOpened.AddListener(KeyboardOpening);
|
||||
activeKeyboard.onFocusChanged.AddListener(KeyboardFocusChanged);
|
||||
|
||||
m_IsActivelyObservingKeyboard = true;
|
||||
}
|
||||
|
||||
void StopObservingKeyboard(XRKeyboard activeKeyboard)
|
||||
{
|
||||
if (activeKeyboard == null)
|
||||
return;
|
||||
|
||||
activeKeyboard.onTextUpdated.RemoveListener(OnTextUpdate);
|
||||
activeKeyboard.onTextSubmitted.RemoveListener(OnTextSubmit);
|
||||
activeKeyboard.onClosed.RemoveListener(KeyboardClosing);
|
||||
activeKeyboard.onOpened.RemoveListener(KeyboardOpening);
|
||||
activeKeyboard.onFocusChanged.RemoveListener(KeyboardFocusChanged);
|
||||
|
||||
m_IsActivelyObservingKeyboard = false;
|
||||
}
|
||||
|
||||
void OnInputFieldGainedFocus(string text)
|
||||
{
|
||||
// If this display is already observing keyboard, sync, attempt to reposition, and early out
|
||||
// Displays that are always observing keyboards call open to ensure they sync with the keyboard
|
||||
if (m_IsActivelyObservingKeyboard && !alwaysObserveKeyboard)
|
||||
{
|
||||
if (!m_UseSceneKeyboard || m_Keyboard == null)
|
||||
GlobalNonNativeKeyboard.instance.RepositionKeyboardIfOutOfView();
|
||||
|
||||
// Sync input field caret position with keyboard caret position
|
||||
if (m_InputField.stringPosition != m_ActiveKeyboard.caretPosition)
|
||||
m_InputField.stringPosition = m_ActiveKeyboard.caretPosition;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ClearTextOnOpen)
|
||||
m_InputField.text = string.Empty;
|
||||
|
||||
// If not using a scene keyboard, use global keyboard.
|
||||
if (!m_UseSceneKeyboard || m_Keyboard == null)
|
||||
{
|
||||
GlobalNonNativeKeyboard.instance.ShowKeyboard(m_InputField, m_MonitorInputFieldCharacterLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ActiveKeyboard.Open(m_InputField, m_MonitorInputFieldCharacterLimit);
|
||||
}
|
||||
|
||||
// Sync input field caret position with keyboard caret position
|
||||
if (m_InputField.stringPosition != m_ActiveKeyboard.caretPosition)
|
||||
m_InputField.stringPosition = m_ActiveKeyboard.caretPosition;
|
||||
|
||||
// This display is opening the keyboard
|
||||
m_OnKeyboardOpened.Invoke();
|
||||
|
||||
StartObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
void OnTextSubmit(KeyboardTextEventArgs args)
|
||||
{
|
||||
UpdateText(args.keyboardText);
|
||||
m_OnTextSubmitted?.Invoke(args.keyboardText);
|
||||
if (m_ClearTextOnSubmit)
|
||||
{
|
||||
inputField.text = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTextUpdate(KeyboardTextEventArgs args)
|
||||
{
|
||||
if (!m_UpdateOnKeyPress)
|
||||
return;
|
||||
|
||||
UpdateText(args.keyboardText);
|
||||
}
|
||||
|
||||
void UpdateText(string text)
|
||||
{
|
||||
var updatedText = text;
|
||||
|
||||
// Clip updated text to substring
|
||||
if (m_MonitorInputFieldCharacterLimit && updatedText.Length >= m_InputField.characterLimit)
|
||||
updatedText = updatedText.Substring(0, m_InputField.characterLimit);
|
||||
|
||||
m_InputField.text = updatedText;
|
||||
|
||||
// Update input field caret position with keyboard caret position
|
||||
if (m_InputField.stringPosition != m_ActiveKeyboard.caretPosition)
|
||||
m_InputField.stringPosition = m_ActiveKeyboard.caretPosition;
|
||||
}
|
||||
|
||||
void KeyboardOpening(KeyboardTextEventArgs args)
|
||||
{
|
||||
Debug.Assert(args.keyboard == m_ActiveKeyboard);
|
||||
|
||||
if (args.keyboard != m_ActiveKeyboard)
|
||||
return;
|
||||
|
||||
if (!m_InputField.isFocused && !m_AlwaysObserveKeyboard)
|
||||
StopObservingKeyboard(m_ActiveKeyboard);
|
||||
}
|
||||
|
||||
void KeyboardClosing(KeyboardTextEventArgs args)
|
||||
{
|
||||
Debug.Assert(args.keyboard == m_ActiveKeyboard);
|
||||
|
||||
if (args.keyboard != m_ActiveKeyboard)
|
||||
return;
|
||||
|
||||
if (!m_AlwaysObserveKeyboard)
|
||||
StopObservingKeyboard(m_ActiveKeyboard);
|
||||
|
||||
m_OnKeyboardClosed.Invoke();
|
||||
}
|
||||
|
||||
void KeyboardFocusChanged(KeyboardTextEventArgs args)
|
||||
{
|
||||
Debug.Assert(args.keyboard == m_ActiveKeyboard);
|
||||
|
||||
if (args.keyboard != m_ActiveKeyboard)
|
||||
return;
|
||||
|
||||
if (!m_InputField.isFocused && !m_AlwaysObserveKeyboard)
|
||||
StopObservingKeyboard(m_ActiveKeyboard);
|
||||
|
||||
// The keyboard changed focus and this input field is no longer in focus
|
||||
if (!m_InputField.isFocused)
|
||||
m_OnKeyboardFocusChanged.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2317ceab4d3d38419fe16eebc1c0cd3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
#region EventArgs
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with a keyboard event.
|
||||
/// </summary>
|
||||
public class KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The XR Keyboard associated with this keyboard event.
|
||||
/// </summary>
|
||||
public XRKeyboard keyboard { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with a keyboard event that includes text.
|
||||
/// </summary>
|
||||
public class KeyboardTextEventArgs : KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The current keyboard text when this event is fired.
|
||||
/// </summary>
|
||||
public string keyboardText { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with a keyboard event that includes a keyboard key.
|
||||
/// </summary>
|
||||
public class KeyboardKeyEventArgs : KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The key associated with this event.
|
||||
/// </summary>
|
||||
public XRKeyboardKey key { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with a keyboard event that includes a bool value.
|
||||
/// </summary>
|
||||
public class KeyboardBoolEventArgs : KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The bool value associated with this event.
|
||||
/// </summary>
|
||||
public bool value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with a keyboard event that includes a layout string.
|
||||
/// </summary>
|
||||
public class KeyboardLayoutEventArgs : KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The layout string associated with this event.
|
||||
/// </summary>
|
||||
public string layout { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data associated with modifiers of the keyboard.
|
||||
/// </summary>
|
||||
public class KeyboardModifiersEventArgs : KeyboardBaseEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The shift value associated with this event.
|
||||
/// </summary>
|
||||
public bool shiftValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The caps lock value associated with this event.
|
||||
/// </summary>
|
||||
public bool capsLockValue { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that Unity invokes on a keyboard.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KeyboardEvent : UnityEvent<KeyboardBaseEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that includes text that Unity invokes on a keyboard.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KeyboardTextEvent : UnityEvent<KeyboardTextEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that includes a key that Unity invokes on a keyboard.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KeyboardKeyEvent : UnityEvent<KeyboardKeyEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that includes a bool value that Unity invokes on a keyboard.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KeyboardBoolEvent : UnityEvent<KeyboardBoolEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that includes a layout string that Unity invokes on a keyboard.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KeyboardLayoutEvent : UnityEvent<KeyboardLayoutEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UnityEvent"/> that includes supported keyboard modifiers.
|
||||
/// </summary>
|
||||
/// <remarks>Currently supported keyboard modifiers include shift and caps lock.</remarks>
|
||||
[Serializable]
|
||||
public sealed class KeyboardModifiersEvent : UnityEvent<KeyboardModifiersEventArgs>
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f37871f6d8da54babd815aa4184559
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,453 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
using TMPro;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Keyboard key used to interface with <see cref="XRKeyboard"/>.
|
||||
/// </summary>
|
||||
public class XRKeyboardKey : Button
|
||||
{
|
||||
[SerializeField, Tooltip("KeyFunction used for this key which is called when key is pressed. Used to communicate with the Keyboard.")]
|
||||
KeyFunction m_KeyFunction;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="KeyFunction"/> used for this key which is called when key is pressed. Used to communicate with
|
||||
/// the Keyboard.
|
||||
/// </summary>
|
||||
public KeyFunction keyFunction
|
||||
{
|
||||
get => m_KeyFunction;
|
||||
set => m_KeyFunction = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) KeyCode used for this key. Used in conjunction with Key Function or as a fallback for standard commands.")]
|
||||
KeyCode m_KeyCode;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) <see cref="KeyCode"/> used for this key. Used in conjunction with Key Function or as a fallback for standard commands.
|
||||
/// </summary>
|
||||
public KeyCode keyCode
|
||||
{
|
||||
get => m_KeyCode;
|
||||
set => m_KeyCode = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Character for this key in non-shifted state. This string will be passed to the keyboard and appended to the keyboard text string or processed as a keyboard command.")]
|
||||
string m_Character;
|
||||
|
||||
/// <summary>
|
||||
/// Character for this key in non-shifted state. This string will be passed to the keyboard and appended
|
||||
/// to the keyboard text string or processed as a keyboard command (i.e. '\s' for shift)
|
||||
/// </summary>
|
||||
public string character
|
||||
{
|
||||
get => m_Character;
|
||||
set => m_Character = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) Display character for this key in a non-shifted state. This string will be displayed on the key text field. If empty, character will be used as a fall back.")]
|
||||
string m_DisplayCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Display character for this key in a non-shifted state. This string will be displayed on the
|
||||
/// key text field. If left empty, <see cref="character"/> will be used instead.
|
||||
/// </summary>
|
||||
public string displayCharacter
|
||||
{
|
||||
get => m_DisplayCharacter;
|
||||
set
|
||||
{
|
||||
m_DisplayCharacter = value;
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) Display icon for this key in a non-shifted state. This icon will be displayed on the key icon image. If empty, the display character or character will be used as a fall back.")]
|
||||
Sprite m_DisplayIcon;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Display icon for this key in a non-shifted state. This icon will be displayed on the key icon image.
|
||||
/// If empty, the display character or character will be used instead.
|
||||
/// </summary>
|
||||
public Sprite displayIcon
|
||||
{
|
||||
get => m_DisplayIcon;
|
||||
set
|
||||
{
|
||||
m_DisplayIcon = value;
|
||||
if (m_IconComponent != null)
|
||||
{
|
||||
m_IconComponent.sprite = m_DisplayIcon;
|
||||
m_IconComponent.enabled = m_DisplayIcon != null;
|
||||
}
|
||||
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Character for this key in a shifted state. This string will be passed to the keyboard and appended to the keyboard text string or processed as a keyboard command.")]
|
||||
string m_ShiftCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Character for this key in a shifted state. This string will be passed to the keyboard and appended
|
||||
/// to the keyboard text string or processed as a keyboard command (i.e. '\s' for shift).
|
||||
/// </summary>
|
||||
public string shiftCharacter
|
||||
{
|
||||
get => m_ShiftCharacter;
|
||||
set => m_ShiftCharacter = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) Display character for this key in a shifted state. This string will be displayed on the key text field. If empty, shift character will be used as a fall back.")]
|
||||
string m_ShiftDisplayCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Display character for this key in a shifted state. This string will be displayed on the key text field.
|
||||
/// If empty, <see cref="shiftCharacter"/> will be used instead, and finally <see cref="character"/> will
|
||||
/// be capitalized and used if <see cref="shiftCharacter"/> is empty.
|
||||
/// </summary>
|
||||
public string shiftDisplayCharacter
|
||||
{
|
||||
get => m_ShiftDisplayCharacter;
|
||||
set
|
||||
{
|
||||
m_ShiftDisplayCharacter = value;
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) Display icon for this key in a shifted state. This icon will be displayed on the key icon image. If empty, the shift display character or shift character will be used as a fall back.")]
|
||||
Sprite m_ShiftDisplayIcon;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Display icon for this key in a shifted state. This icon will be displayed on the key icon image.
|
||||
/// If empty, the shift display character or shift character will be used instead.
|
||||
/// </summary>
|
||||
public Sprite shiftDisplayIcon
|
||||
{
|
||||
get => m_ShiftDisplayIcon;
|
||||
set
|
||||
{
|
||||
m_ShiftDisplayIcon = value;
|
||||
if (m_IconComponent != null)
|
||||
{
|
||||
m_IconComponent.sprite = shiftDisplayIcon;
|
||||
m_IconComponent.enabled = shiftDisplayIcon != null;
|
||||
}
|
||||
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("If true, the key pressed event will fire on button down. If false, the key pressed event will fire on On Click.")]
|
||||
bool m_UpdateOnKeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// If true, key pressed will fire on button down instead of on button up.
|
||||
/// </summary>
|
||||
public bool updateOnKeyDown
|
||||
{
|
||||
get => m_UpdateOnKeyDown;
|
||||
set => m_UpdateOnKeyDown = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Text field used to display key character.")]
|
||||
TMP_Text m_TextComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Text field used to display key character.
|
||||
/// </summary>
|
||||
public TMP_Text textComponent
|
||||
{
|
||||
get => m_TextComponent;
|
||||
set => m_TextComponent = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Image component used to display icons for key.")]
|
||||
Image m_IconComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Image component used to display icons for key.
|
||||
/// </summary>
|
||||
public Image iconComponent
|
||||
{
|
||||
get => m_IconComponent;
|
||||
set => m_IconComponent = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Image component used to highlight key indicating an active state.")]
|
||||
Image m_HighlightComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Image component used to highlight key indicating an active state.
|
||||
/// </summary>
|
||||
public Image highlightComponent
|
||||
{
|
||||
get => m_HighlightComponent;
|
||||
set => m_HighlightComponent = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("(Optional) Audio source played when key is pressed.")]
|
||||
AudioSource m_AudioSource;
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Audio source played when key is pressed.
|
||||
/// </summary>
|
||||
public AudioSource audioSource
|
||||
{
|
||||
get => m_AudioSource;
|
||||
set => m_AudioSource = value;
|
||||
}
|
||||
|
||||
XRKeyboard m_Keyboard;
|
||||
|
||||
float m_LastClickTime;
|
||||
bool m_Shifted;
|
||||
|
||||
/// <summary>
|
||||
/// True if this key is in a shifted state, otherwise returns false.
|
||||
/// </summary>
|
||||
public bool shifted
|
||||
{
|
||||
get => m_Shifted;
|
||||
set
|
||||
{
|
||||
m_Shifted = value;
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time the key was last pressed.
|
||||
/// </summary>
|
||||
public float lastClickTime => m_LastClickTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time since the key was last pressed.
|
||||
/// </summary>
|
||||
public float timeSinceLastClick => Time.time - m_LastClickTime;
|
||||
|
||||
/// <summary>
|
||||
/// The keyboard associated with this key.
|
||||
/// </summary>
|
||||
public XRKeyboard keyboard
|
||||
{
|
||||
get => m_Keyboard;
|
||||
set
|
||||
{
|
||||
if (m_Keyboard != null)
|
||||
{
|
||||
m_Keyboard.onShifted.RemoveListener(OnKeyboardShift);
|
||||
m_Keyboard.onLayoutChanged.RemoveListener(OnKeyboardLayoutChange);
|
||||
}
|
||||
|
||||
m_Keyboard = value;
|
||||
|
||||
if (m_Keyboard != null)
|
||||
{
|
||||
m_Keyboard.onShifted.AddListener(OnKeyboardShift);
|
||||
m_Keyboard.onLayoutChanged.AddListener(OnKeyboardLayoutChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
RefreshDisplayCharacter();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
if (m_Keyboard != null)
|
||||
{
|
||||
m_Keyboard.onShifted.RemoveListener(OnKeyboardShift);
|
||||
m_Keyboard.onLayoutChanged.RemoveListener(OnKeyboardLayoutChange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
base.OnPointerDown(eventData);
|
||||
if (m_UpdateOnKeyDown && interactable)
|
||||
KeyClick();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
base.OnPointerClick(eventData);
|
||||
if (!m_UpdateOnKeyDown)
|
||||
KeyClick();
|
||||
}
|
||||
|
||||
protected virtual void KeyClick()
|
||||
{
|
||||
// Local function of things to do to the key when pressed (Audio, etc.)
|
||||
KeyPressed();
|
||||
|
||||
if (m_KeyFunction != null)
|
||||
{
|
||||
m_KeyFunction.PreprocessKey(m_Keyboard, this);
|
||||
m_KeyFunction.ProcessKey(m_Keyboard, this);
|
||||
m_KeyFunction.PostprocessKey(m_Keyboard, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if key function is null
|
||||
m_Keyboard.PreprocessKeyPress(this);
|
||||
m_Keyboard.TryProcessKeyPress(this);
|
||||
m_Keyboard.PostprocessKeyPress(this);
|
||||
}
|
||||
|
||||
m_LastClickTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local handling of this key being pressed.
|
||||
/// </summary>
|
||||
protected virtual void KeyPressed()
|
||||
{
|
||||
if (m_AudioSource != null)
|
||||
{
|
||||
if (m_AudioSource.isPlaying)
|
||||
m_AudioSource.Stop();
|
||||
|
||||
float pitchVariance = Random.Range(0.95f, 1.05f);
|
||||
m_AudioSource.pitch = pitchVariance;
|
||||
m_AudioSource.Play();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnKeyboardShift(KeyboardModifiersEventArgs args)
|
||||
{
|
||||
shifted = args.shiftValue;
|
||||
}
|
||||
|
||||
protected virtual void OnKeyboardLayoutChange(KeyboardLayoutEventArgs args)
|
||||
{
|
||||
var enableHighlight = args.layout == m_Character && !m_HighlightComponent.enabled;
|
||||
EnableHighlight(enableHighlight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the key highlight image.
|
||||
/// </summary>
|
||||
/// <param name="enable">If true, the highlight image is enabled. If false, the highlight image is disabled.</param>
|
||||
public void EnableHighlight(bool enable)
|
||||
{
|
||||
if (m_HighlightComponent != null)
|
||||
{
|
||||
m_HighlightComponent.enabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
protected void RefreshDisplayCharacter()
|
||||
{
|
||||
if (m_KeyFunction != null && m_Keyboard != null)
|
||||
m_KeyFunction.ProcessRefreshDisplay(m_Keyboard, this);
|
||||
|
||||
if (m_IconComponent != null)
|
||||
{
|
||||
m_IconComponent.sprite = GetEffectiveDisplayIcon();
|
||||
if (m_IconComponent.sprite != null)
|
||||
{
|
||||
m_TextComponent.enabled = false;
|
||||
m_IconComponent.enabled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_TextComponent != null)
|
||||
{
|
||||
m_TextComponent.text = GetEffectiveDisplayCharacter();
|
||||
m_TextComponent.enabled = true;
|
||||
m_IconComponent.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string GetEffectiveDisplayCharacter()
|
||||
{
|
||||
// If we've got a display character, prioritize that.
|
||||
string value;
|
||||
if (!string.IsNullOrEmpty(m_DisplayCharacter))
|
||||
value = m_DisplayCharacter;
|
||||
else if (!string.IsNullOrEmpty(m_Character))
|
||||
value = m_Character;
|
||||
else
|
||||
value = string.Empty;
|
||||
|
||||
// If we're in shift mode, check our shift overrides.
|
||||
if (m_Shifted)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_ShiftDisplayCharacter))
|
||||
value = m_ShiftDisplayCharacter;
|
||||
else if (!string.IsNullOrEmpty(m_ShiftCharacter))
|
||||
value = m_ShiftCharacter;
|
||||
else
|
||||
value = value.ToUpper();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected virtual Sprite GetEffectiveDisplayIcon()
|
||||
{
|
||||
if (m_KeyFunction != null && m_Keyboard != null && m_KeyFunction.OverrideDisplayIcon(m_Keyboard, this))
|
||||
return m_KeyFunction.GetDisplayIcon(m_Keyboard, this);
|
||||
|
||||
return m_Shifted ? m_ShiftDisplayIcon : m_DisplayIcon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function that returns the current effective character for this key based on shifted state.
|
||||
/// </summary>
|
||||
/// <returns>Returns the <see cref="shiftCharacter"/> when this key is in the shifted state or <see cref="character"/>
|
||||
/// when this key is not shifted.</returns>
|
||||
public virtual string GetEffectiveCharacter()
|
||||
{
|
||||
if (m_Shifted)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_ShiftCharacter))
|
||||
return m_ShiftCharacter;
|
||||
|
||||
return m_Character.ToUpper();
|
||||
}
|
||||
|
||||
return m_Character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the key button being interactable. The icon and text alpha will be adjusted to reflect
|
||||
/// the state of the button.
|
||||
/// </summary>
|
||||
/// <param name="enable">The desired interactable state of the key.</param>
|
||||
public virtual void SetButtonInteractable(bool enable)
|
||||
{
|
||||
const float enabledAlpha = 1f;
|
||||
const float disabledAlpha = 0.25f;
|
||||
|
||||
interactable = enable;
|
||||
if (m_TextComponent != null)
|
||||
{
|
||||
m_TextComponent.alpha = enable ? enabledAlpha : disabledAlpha;
|
||||
}
|
||||
|
||||
if (m_IconComponent != null)
|
||||
{
|
||||
var c = m_IconComponent.color;
|
||||
c.a = enable ? enabledAlpha : disabledAlpha;
|
||||
m_IconComponent.color = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84bf39f7245577d4491b0aa8ea6016ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
#if TEXT_MESH_PRO_PRESENT || (UGUI_2_0_PRESENT && UNITY_6000_0_OR_NEWER)
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Manage the reuse and updating of data for each child <see cref="XRKeyboardKey"/> button.
|
||||
/// </summary>
|
||||
public class XRKeyboardLayout : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
XRKeyboardConfig m_DefaultKeyMapping;
|
||||
|
||||
/// <summary>
|
||||
/// Default key mapping for resetting key layout.
|
||||
/// </summary>
|
||||
public XRKeyboardConfig defaultKeyMapping
|
||||
{
|
||||
get => m_DefaultKeyMapping;
|
||||
set => m_DefaultKeyMapping = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
XRKeyboardConfig m_ActiveKeyMapping;
|
||||
|
||||
/// <summary>
|
||||
/// Active <see cref="XRKeyboardConfig"/> which data is populated in these keys.
|
||||
/// </summary>
|
||||
public XRKeyboardConfig activeKeyMapping
|
||||
{
|
||||
get => m_ActiveKeyMapping;
|
||||
set
|
||||
{
|
||||
m_ActiveKeyMapping = value;
|
||||
PopulateKeys();
|
||||
}
|
||||
}
|
||||
|
||||
XRKeyboardKey [] m_Keys;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_Keys = GetComponentsInChildren<XRKeyboardKey>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
PopulateKeys();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active key mapping to the default key mapping.
|
||||
/// </summary>
|
||||
/// <seealso cref="activeKeyMapping"/>
|
||||
/// <seealso cref="defaultKeyMapping"/>
|
||||
public void SetDefaultLayout()
|
||||
{
|
||||
if (m_DefaultKeyMapping != null)
|
||||
activeKeyMapping = m_DefaultKeyMapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates all child <see cref="XRKeyboardKey"/> buttons with the data from the <see cref="activeKeyMapping"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function returns without changing the keys if the number of child <see cref="XRKeyboardKey"/> buttons is less than
|
||||
/// the number of mappings in the <see cref="activeKeyMapping"/>.
|
||||
/// </remarks>
|
||||
public void PopulateKeys()
|
||||
{
|
||||
if (m_ActiveKeyMapping == null)
|
||||
return;
|
||||
|
||||
var keyMappings = m_ActiveKeyMapping.keyMappings;
|
||||
|
||||
if (m_Keys == null || m_Keys.Length == 0)
|
||||
m_Keys = GetComponentsInChildren<XRKeyboardKey>();
|
||||
|
||||
|
||||
if (m_Keys.Length < keyMappings.Count)
|
||||
{
|
||||
Debug.LogWarning("Keyboard layout update failed: There are fewer keys than key mappings in the current config. Ensure there is a correct number of keys and key mappings.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < keyMappings.Count; ++i)
|
||||
{
|
||||
var mapping = keyMappings[i];
|
||||
var key = m_Keys[i];
|
||||
|
||||
key.character = mapping.character;
|
||||
key.displayCharacter = mapping.displayCharacter;
|
||||
|
||||
key.shiftCharacter = mapping.shiftCharacter;
|
||||
key.shiftDisplayCharacter = mapping.shiftDisplayCharacter;
|
||||
|
||||
key.keyFunction = mapping.overrideDefaultKeyFunction ? mapping.keyFunction : m_ActiveKeyMapping.defaultKeyFunction;
|
||||
key.keyCode = mapping.keyCode;
|
||||
|
||||
key.displayIcon = mapping.displayIcon;
|
||||
key.shiftDisplayIcon = mapping.shiftDisplayIcon;
|
||||
|
||||
key.SetButtonInteractable(!mapping.disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84662ebe3530d154a80c5c813e8ce186
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user