1079 lines
34 KiB
C#
1079 lines
34 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Input;
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using System;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Experimental.UI
|
|
{
|
|
/// <summary>
|
|
/// A simple general use keyboard that is ideal for AR/VR applications that do not provide a native keyboard.
|
|
/// </summary>
|
|
///
|
|
/// NOTE: This keyboard will not automatically appear when you select an InputField in your
|
|
/// Canvas. In order for the keyboard to appear you must call Keyboard.Instance.PresentKeyboard(string).
|
|
/// To retrieve the input from the Keyboard, subscribe to the textEntered event. Note that
|
|
/// tapping 'Close' on the Keyboard will not fire the textEntered event. You must tap 'Enter' to
|
|
/// get the textEntered event.
|
|
public class NonNativeKeyboard : InputSystemGlobalHandlerListener, IMixedRealityDictationHandler
|
|
{
|
|
public static NonNativeKeyboard Instance { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Layout type enum for the type of keyboard layout to use.
|
|
/// This is used when spawning to enable the correct keys based on layout type.
|
|
/// </summary>
|
|
public enum LayoutType
|
|
{
|
|
Alpha,
|
|
Symbol,
|
|
URL,
|
|
Email,
|
|
}
|
|
|
|
#region Callbacks
|
|
|
|
/// <summary>
|
|
/// Sent when the 'Enter' button is pressed. To retrieve the text from the event,
|
|
/// cast the sender to 'Keyboard' and get the text from the TextInput field.
|
|
/// (Cleared when keyboard is closed.)
|
|
/// </summary>
|
|
public event EventHandler OnTextSubmitted = delegate { };
|
|
|
|
/// <summary>
|
|
/// Fired every time the text in the InputField changes.
|
|
/// (Cleared when keyboard is closed.)
|
|
/// </summary>
|
|
public event Action<string> OnTextUpdated = delegate { };
|
|
|
|
/// <summary>
|
|
/// Fired every time the close button is pressed.
|
|
/// (Cleared when keyboard is closed.)
|
|
/// </summary>
|
|
public event EventHandler OnClosed = delegate { };
|
|
|
|
/// <summary>
|
|
/// Sent when the 'Previous' button is pressed. Ideally you would use this event
|
|
/// to set your targeted text input to the previous text field in your document.
|
|
/// (Cleared when keyboard is closed.)
|
|
/// </summary>
|
|
public event EventHandler OnPrevious = delegate { };
|
|
|
|
/// <summary>
|
|
/// Sent when the 'Next' button is pressed. Ideally you would use this event
|
|
/// to set your targeted text input to the next text field in your document.
|
|
/// (Cleared when keyboard is closed.)
|
|
/// </summary>
|
|
public event EventHandler OnNext = delegate { };
|
|
|
|
/// <summary>
|
|
/// Sent when the keyboard is placed. This allows listener to know when someone else is co-opting the keyboard.
|
|
/// </summary>
|
|
public event EventHandler OnPlacement = delegate { };
|
|
|
|
#endregion Callbacks
|
|
|
|
/// <summary>
|
|
/// The InputField that the keyboard uses to show the currently edited text.
|
|
/// If you are using the Keyboard prefab you can ignore this field as it will
|
|
/// be already assigned.
|
|
/// </summary>
|
|
[Experimental]
|
|
public TMP_InputField InputField = null;
|
|
|
|
/// <summary>
|
|
/// Move the axis slider based on the camera forward and the keyboard plane projection.
|
|
/// </summary>
|
|
public AxisSlider InputFieldSlide = null;
|
|
|
|
/// <summary>
|
|
/// Bool for toggling the slider being enabled.
|
|
/// </summary>
|
|
public bool SliderEnabled = true;
|
|
|
|
/// <summary>
|
|
/// Bool to flag submitting on enter
|
|
/// </summary>
|
|
public bool SubmitOnEnter = true;
|
|
|
|
/// <summary>
|
|
/// The panel that contains the alpha keys.
|
|
/// </summary>
|
|
public Image AlphaKeyboard = null;
|
|
|
|
/// <summary>
|
|
/// The panel that contains the number and symbol keys.
|
|
/// </summary>
|
|
public Image SymbolKeyboard = null;
|
|
|
|
/// <summary>
|
|
/// References abc bottom panel.
|
|
/// </summary>
|
|
public Image AlphaSubKeys = null;
|
|
|
|
/// <summary>
|
|
/// References .com bottom panel.
|
|
/// </summary>
|
|
public Image AlphaWebKeys = null;
|
|
|
|
/// <summary>
|
|
/// References @ bottom panel.
|
|
/// </summary>
|
|
public Image AlphaMailKeys = null;
|
|
|
|
private LayoutType m_LastKeyboardLayout = LayoutType.Alpha;
|
|
|
|
/// <summary>
|
|
/// The scale the keyboard should be at its maximum distance.
|
|
/// </summary>
|
|
[Header("Positioning")]
|
|
[SerializeField]
|
|
private float m_MaxScale = 1.0f;
|
|
|
|
/// <summary>
|
|
/// The scale the keyboard should be at its minimum distance.
|
|
/// </summary>
|
|
[SerializeField]
|
|
private float m_MinScale = 1.0f;
|
|
|
|
/// <summary>
|
|
/// The maximum distance the keyboard should be from the user.
|
|
/// </summary>
|
|
[SerializeField]
|
|
private float m_MaxDistance = 3.5f;
|
|
|
|
/// <summary>
|
|
/// The minimum distance the keyboard needs to be away from the user.
|
|
/// </summary>
|
|
[SerializeField]
|
|
private float m_MinDistance = 0.25f;
|
|
|
|
/// <summary>
|
|
/// Make the keyboard disappear automatically after a timeout
|
|
/// </summary>
|
|
public bool CloseOnInactivity = true;
|
|
|
|
/// <summary>
|
|
/// Inactivity time that makes the keyboard disappear automatically.
|
|
/// </summary>
|
|
public float CloseOnInactivityTime = 15;
|
|
|
|
/// <summary>
|
|
/// Time on which the keyboard should close on inactivity
|
|
/// </summary>
|
|
private float _closingTime;
|
|
|
|
/// <summary>
|
|
/// Event fired when shift key on keyboard is pressed.
|
|
/// </summary>
|
|
public event Action<bool> OnKeyboardShifted = delegate { };
|
|
|
|
/// <summary>
|
|
/// Event fired when char key on keyboard is pressed.
|
|
/// </summary>
|
|
public event Action<KeyboardValueKey> OnKeyboardValueKeyPressed = delegate { };
|
|
|
|
/// <summary>
|
|
/// Event fired when function key on keyboard is pressed.
|
|
/// Fires before internal keyboard state is updated.
|
|
/// </summary>
|
|
public event Action<KeyboardKeyFunc> OnKeyboardFunctionKeyPressed = delegate { };
|
|
|
|
/// <summary>
|
|
/// Current shift state of keyboard.
|
|
/// </summary>
|
|
private bool m_IsShifted = false;
|
|
|
|
/// <summary>
|
|
/// Current caps lock state of keyboard.
|
|
/// </summary>
|
|
private bool m_IsCapslocked = false;
|
|
|
|
/// <summary>
|
|
/// Accessor reporting shift state of keyboard.
|
|
/// </summary>
|
|
public bool IsShifted
|
|
{
|
|
get { return m_IsShifted; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accessor reporting caps lock state of keyboard.
|
|
/// </summary>
|
|
public bool IsCapsLocked
|
|
{
|
|
get { return m_IsCapslocked; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The position of the caret in the text field.
|
|
/// </summary>
|
|
private int m_CaretPosition = 0;
|
|
|
|
/// <summary>
|
|
/// The starting scale of the keyboard.
|
|
/// </summary>
|
|
private Vector3 m_StartingScale = Vector3.one;
|
|
|
|
/// <summary>
|
|
/// The default bounds of the keyboard.
|
|
/// </summary>
|
|
private Vector3 m_ObjectBounds;
|
|
|
|
/// <summary>
|
|
/// The default color of the mike key.
|
|
/// </summary>
|
|
private Color _defaultColor;
|
|
|
|
/// <summary>
|
|
/// The image on the mike key.
|
|
/// </summary>
|
|
private Image _recordImage;
|
|
|
|
/// <summary>
|
|
/// User can add an audio source to the keyboard to have a click be heard on tapping a key
|
|
/// </summary>
|
|
private AudioSource _audioSource;
|
|
|
|
/// <summary>
|
|
/// Dictation System
|
|
/// </summary>
|
|
private IMixedRealityDictationSystem dictationSystem;
|
|
|
|
/// <summary>
|
|
/// Deactivate on Awake.
|
|
/// </summary>
|
|
void Awake()
|
|
{
|
|
Instance = this;
|
|
|
|
m_StartingScale = transform.localScale;
|
|
Bounds canvasBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(transform);
|
|
|
|
RectTransform rect = GetComponent<RectTransform>();
|
|
m_ObjectBounds = new Vector3(canvasBounds.size.x * rect.localScale.x, canvasBounds.size.y * rect.localScale.y, canvasBounds.size.z * rect.localScale.z);
|
|
|
|
// Actually find microphone key in the keyboard
|
|
var dictationButton = TransformExtensions.GetChildRecursive(gameObject.transform, "Dictation");
|
|
if (dictationButton != null)
|
|
{
|
|
var dictationIcon = dictationButton.Find("keyboard_closeIcon");
|
|
if (dictationIcon != null)
|
|
{
|
|
_recordImage = dictationIcon.GetComponentInChildren<Image>();
|
|
var material = new Material(_recordImage.material);
|
|
_defaultColor = material.color;
|
|
_recordImage.material = material;
|
|
}
|
|
}
|
|
|
|
// Setting the keyboardType to an undefined TouchScreenKeyboardType,
|
|
// which prevents the MRTK keyboard from triggering the system keyboard itself.
|
|
InputField.keyboardType = (TouchScreenKeyboardType)(int.MaxValue);
|
|
|
|
// Keep keyboard deactivated until needed
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Set up Dictation, CanvasEX, and automatically select the TextInput object.
|
|
/// </summary>
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
|
|
dictationSystem = CoreServices.GetInputSystemDataProvider<IMixedRealityDictationSystem>();
|
|
|
|
// Delegate Subscription
|
|
InputField.onValueChanged.AddListener(DoTextUpdated);
|
|
}
|
|
|
|
protected override void RegisterHandlers()
|
|
{
|
|
CoreServices.InputSystem?.RegisterHandler<IMixedRealityDictationHandler>(this);
|
|
}
|
|
|
|
protected override void UnregisterHandlers()
|
|
{
|
|
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityDictationHandler>(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Intermediary function for text update events.
|
|
/// Workaround for strange leftover reference when unsubscribing.
|
|
/// </summary>
|
|
/// <param name="value">String value.</param>
|
|
private void DoTextUpdated(string value) => OnTextUpdated?.Invoke(value);
|
|
|
|
/// <summary>
|
|
/// Makes sure the input field is always selected while the keyboard is up.
|
|
/// </summary>
|
|
private void LateUpdate()
|
|
{
|
|
// Axis Slider
|
|
if (SliderEnabled)
|
|
{
|
|
Vector3 nearPoint = Vector3.ProjectOnPlane(CameraCache.Main.transform.forward, transform.forward);
|
|
Vector3 relPos = transform.InverseTransformPoint(nearPoint);
|
|
InputFieldSlide.TargetPoint = relPos;
|
|
}
|
|
|
|
CheckForCloseOnInactivityTimeExpired();
|
|
}
|
|
|
|
private void UpdateCaretPosition(int newPos) => InputField.caretPosition = newPos;
|
|
|
|
/// <summary>
|
|
/// Called whenever the keyboard is disabled or deactivated.
|
|
/// </summary>
|
|
protected override void OnDisable()
|
|
{
|
|
base.OnDisable();
|
|
m_LastKeyboardLayout = LayoutType.Alpha;
|
|
Clear();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called when dictation hypothesis is found. Not used here
|
|
/// </summary>
|
|
/// <param name="eventData">Dictation event data</param>
|
|
public void OnDictationHypothesis(DictationEventData eventData) { }
|
|
|
|
/// <summary>
|
|
/// Called when dictation result is obtained
|
|
/// </summary>
|
|
/// <param name="eventData">Dictation event data</param>
|
|
public void OnDictationResult(DictationEventData eventData)
|
|
{
|
|
if (eventData.used)
|
|
{
|
|
return;
|
|
}
|
|
var text = eventData.DictationResult;
|
|
ResetClosingTime();
|
|
if (text != null)
|
|
{
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
InputField.text = InputField.text.Insert(m_CaretPosition, text);
|
|
m_CaretPosition += text.Length;
|
|
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
eventData.Use();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when dictation is completed
|
|
/// </summary>
|
|
/// <param name="eventData">Dictation event data</param>
|
|
public void OnDictationComplete(DictationEventData eventData)
|
|
{
|
|
ResetClosingTime();
|
|
SetMicrophoneDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called on dictation error. Not used here.
|
|
/// </summary>
|
|
/// <param name="eventData">Dictation event data</param>
|
|
public void OnDictationError(DictationEventData eventData) { }
|
|
|
|
/// <summary>
|
|
/// Destroy unmanaged memory links.
|
|
/// </summary>
|
|
void OnDestroy()
|
|
{
|
|
if (dictationSystem != null && IsMicrophoneActive())
|
|
{
|
|
dictationSystem.StopRecording();
|
|
}
|
|
|
|
Instance = null;
|
|
}
|
|
|
|
#region Present Functions
|
|
|
|
/// <summary>
|
|
/// Present the default keyboard to the camera.
|
|
/// </summary>
|
|
public void PresentKeyboard()
|
|
{
|
|
ResetClosingTime();
|
|
gameObject.SetActive(true);
|
|
ActivateSpecificKeyboard(LayoutType.Alpha);
|
|
|
|
OnPlacement(this, EventArgs.Empty);
|
|
|
|
// todo: if the app is built for xaml, our prefab and the system keyboard may be displayed.
|
|
InputField.ActivateInputField();
|
|
|
|
SetMicrophoneDefault();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Presents the default keyboard to the camera, with start text.
|
|
/// </summary>
|
|
/// <param name="startText">The initial text to show in the keyboard's input field.</param>
|
|
public void PresentKeyboard(string startText)
|
|
{
|
|
PresentKeyboard();
|
|
Clear();
|
|
InputField.text = startText;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Presents a specific keyboard to the camera.
|
|
/// </summary>
|
|
/// <param name="keyboardType">Specify the keyboard type.</param>
|
|
public void PresentKeyboard(LayoutType keyboardType)
|
|
{
|
|
PresentKeyboard();
|
|
ActivateSpecificKeyboard(keyboardType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Presents a specific keyboard to the camera, with start text.
|
|
/// </summary>
|
|
/// <param name="startText">The initial text to show in the keyboard's input field.</param>
|
|
/// <param name="keyboardType">Specify the keyboard type.</param>
|
|
public void PresentKeyboard(string startText, LayoutType keyboardType)
|
|
{
|
|
PresentKeyboard(startText);
|
|
ActivateSpecificKeyboard(keyboardType);
|
|
}
|
|
|
|
#endregion Present Functions
|
|
/// <summary>
|
|
/// Function to reposition the Keyboard based on target position and vertical offset
|
|
/// </summary>
|
|
/// <param name="kbPos">World position for keyboard</param>
|
|
/// <param name="verticalOffset">Optional vertical offset of keyboard</param>
|
|
public void RepositionKeyboard(Vector3 kbPos, float verticalOffset = 0.0f)
|
|
{
|
|
transform.position = kbPos;
|
|
ScaleToSize();
|
|
LookAtTargetOrigin();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to reposition the keyboard based on target transform and collider information
|
|
/// </summary>
|
|
/// <param name="objectTransform">Transform of target object to remain relative to</param>
|
|
/// <param name="aCollider">Optional collider information for offset placement</param>
|
|
/// <param name="verticalOffset">Optional vertical offset from the target</param>
|
|
public void RepositionKeyboard(Transform objectTransform, BoxCollider aCollider = null, float verticalOffset = 0.0f)
|
|
{
|
|
transform.position = objectTransform.position;
|
|
|
|
if (aCollider != null)
|
|
{
|
|
float yTranslation = -((aCollider.bounds.size.y * 0.5f) + verticalOffset);
|
|
transform.Translate(0.0f, yTranslation, -0.6f, objectTransform);
|
|
}
|
|
else
|
|
{
|
|
float yTranslation = -((m_ObjectBounds.y * 0.5f) + verticalOffset);
|
|
transform.Translate(0.0f, yTranslation, -0.6f, objectTransform);
|
|
}
|
|
|
|
ScaleToSize();
|
|
LookAtTargetOrigin();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to scale keyboard to the appropriate size based on distance
|
|
/// </summary>
|
|
private void ScaleToSize()
|
|
{
|
|
float distance = (transform.position - CameraCache.Main.transform.position).magnitude;
|
|
float distancePercent = (distance - m_MinDistance) / (m_MaxDistance - m_MinDistance);
|
|
float scale = m_MinScale + (m_MaxScale - m_MinScale) * distancePercent;
|
|
|
|
scale = Mathf.Clamp(scale, m_MinScale, m_MaxScale);
|
|
transform.localScale = m_StartingScale * scale;
|
|
|
|
Debug.LogFormat("Setting scale: {0} for distance: {1}", scale, distance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Look at function to have the keyboard face the user
|
|
/// </summary>
|
|
private void LookAtTargetOrigin()
|
|
{
|
|
transform.LookAt(CameraCache.Main.transform.position);
|
|
transform.Rotate(Vector3.up, 180.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Activates a specific keyboard layout, and any sub keys.
|
|
/// </summary>
|
|
/// <param name="keyboardType">The keyboard layout type that should be activated</param>
|
|
private void ActivateSpecificKeyboard(LayoutType keyboardType)
|
|
{
|
|
DisableAllKeyboards();
|
|
ResetKeyboardState();
|
|
|
|
switch (keyboardType)
|
|
{
|
|
case LayoutType.URL:
|
|
{
|
|
ShowAlphaKeyboard();
|
|
TryToShowURLSubkeys();
|
|
break;
|
|
}
|
|
|
|
case LayoutType.Email:
|
|
{
|
|
ShowAlphaKeyboard();
|
|
TryToShowEmailSubkeys();
|
|
break;
|
|
}
|
|
|
|
case LayoutType.Symbol:
|
|
{
|
|
ShowSymbolKeyboard();
|
|
break;
|
|
}
|
|
|
|
case LayoutType.Alpha:
|
|
default:
|
|
{
|
|
ShowAlphaKeyboard();
|
|
TryToShowAlphaSubkeys();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Keyboard Functions
|
|
|
|
#region Dictation
|
|
|
|
/// <summary>
|
|
/// Initialize dictation mode.
|
|
/// </summary>
|
|
private void BeginDictation()
|
|
{
|
|
ResetClosingTime();
|
|
dictationSystem.StartRecording(gameObject);
|
|
SetMicrophoneRecording();
|
|
}
|
|
|
|
private bool IsMicrophoneActive()
|
|
{
|
|
var result = _recordImage.color != _defaultColor;
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set mike default look
|
|
/// </summary>
|
|
private void SetMicrophoneDefault()
|
|
{
|
|
_recordImage.color = _defaultColor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set mike recording look (red)
|
|
/// </summary>
|
|
private void SetMicrophoneRecording()
|
|
{
|
|
_recordImage.color = Color.red;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Terminate dictation mode.
|
|
/// </summary>
|
|
public void EndDictation()
|
|
{
|
|
dictationSystem.StopRecording();
|
|
SetMicrophoneDefault();
|
|
}
|
|
|
|
#endregion Dictation
|
|
|
|
/// <summary>
|
|
/// Primary method for typing individual characters to a text field.
|
|
/// </summary>
|
|
/// <param name="valueKey">The valueKey of the pressed key.</param>
|
|
public void AppendValue(KeyboardValueKey valueKey)
|
|
{
|
|
IndicateActivity();
|
|
string value = "";
|
|
|
|
OnKeyboardValueKeyPressed(valueKey);
|
|
|
|
// Shift value should only be applied if a shift value is present.
|
|
if (m_IsShifted && !string.IsNullOrEmpty(valueKey.ShiftValue))
|
|
{
|
|
value = valueKey.ShiftValue;
|
|
}
|
|
else
|
|
{
|
|
value = valueKey.Value;
|
|
}
|
|
|
|
if (!m_IsCapslocked)
|
|
{
|
|
Shift(false);
|
|
}
|
|
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
InputField.text = InputField.text.Insert(m_CaretPosition, value);
|
|
m_CaretPosition += value.Length;
|
|
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Trigger specific keyboard functionality.
|
|
/// </summary>
|
|
/// <param name="functionKey">The functionKey of the pressed key.</param>
|
|
public void FunctionKey(KeyboardKeyFunc functionKey)
|
|
{
|
|
IndicateActivity();
|
|
OnKeyboardFunctionKeyPressed(functionKey);
|
|
switch (functionKey.ButtonFunction)
|
|
{
|
|
case KeyboardKeyFunc.Function.Enter:
|
|
{
|
|
Enter();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Tab:
|
|
{
|
|
Tab();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.ABC:
|
|
{
|
|
ActivateSpecificKeyboard(m_LastKeyboardLayout);
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Symbol:
|
|
{
|
|
ActivateSpecificKeyboard(LayoutType.Symbol);
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Previous:
|
|
{
|
|
MoveCaretLeft();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Next:
|
|
{
|
|
MoveCaretRight();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Close:
|
|
{
|
|
Close();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Dictate:
|
|
{
|
|
if (dictationSystem == null) { break; }
|
|
|
|
if (IsMicrophoneActive())
|
|
{
|
|
EndDictation();
|
|
}
|
|
else
|
|
{
|
|
BeginDictation();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Shift:
|
|
{
|
|
Shift(!m_IsShifted);
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.CapsLock:
|
|
{
|
|
CapsLock(!m_IsCapslocked);
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Space:
|
|
{
|
|
Space();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.Backspace:
|
|
{
|
|
Backspace();
|
|
break;
|
|
}
|
|
|
|
case KeyboardKeyFunc.Function.UNDEFINED:
|
|
{
|
|
Debug.LogErrorFormat("The {0} key on this keyboard hasn't been assigned a function.", functionKey.name);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete the character before the caret.
|
|
/// </summary>
|
|
public void Backspace()
|
|
{
|
|
// check if text is selected
|
|
if (InputField.selectionFocusPosition != InputField.caretPosition || InputField.selectionAnchorPosition != InputField.caretPosition)
|
|
{
|
|
if (InputField.selectionAnchorPosition > InputField.selectionFocusPosition) // right to left
|
|
{
|
|
InputField.text = InputField.text.Substring(0, InputField.selectionFocusPosition) + InputField.text.Substring(InputField.selectionAnchorPosition);
|
|
InputField.caretPosition = InputField.selectionFocusPosition;
|
|
}
|
|
else // left to right
|
|
{
|
|
InputField.text = InputField.text.Substring(0, InputField.selectionAnchorPosition) + InputField.text.Substring(InputField.selectionFocusPosition);
|
|
InputField.caretPosition = InputField.selectionAnchorPosition;
|
|
}
|
|
|
|
m_CaretPosition = InputField.caretPosition;
|
|
InputField.selectionAnchorPosition = m_CaretPosition;
|
|
InputField.selectionFocusPosition = m_CaretPosition;
|
|
}
|
|
else
|
|
{
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
if (m_CaretPosition > 0)
|
|
{
|
|
--m_CaretPosition;
|
|
InputField.text = InputField.text.Remove(m_CaretPosition, 1);
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send the "previous" event.
|
|
/// </summary>
|
|
public void Previous()
|
|
{
|
|
OnPrevious(this, EventArgs.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send the "next" event.
|
|
/// </summary>
|
|
public void Next()
|
|
{
|
|
OnNext(this, EventArgs.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fire the text entered event for objects listening to keyboard.
|
|
/// Immediately closes keyboard.
|
|
/// </summary>
|
|
public void Enter()
|
|
{
|
|
if (SubmitOnEnter)
|
|
{
|
|
// Send text entered event and close the keyboard
|
|
OnTextSubmitted?.Invoke(this, EventArgs.Empty);
|
|
|
|
Close();
|
|
}
|
|
else
|
|
{
|
|
string enterString = "\n";
|
|
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
InputField.text = InputField.text.Insert(m_CaretPosition, enterString);
|
|
m_CaretPosition += enterString.Length;
|
|
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the keyboard to a single action shift state.
|
|
/// </summary>
|
|
/// <param name="newShiftState">value the shift key should have after calling the method</param>
|
|
public void Shift(bool newShiftState)
|
|
{
|
|
m_IsShifted = newShiftState;
|
|
OnKeyboardShifted(m_IsShifted);
|
|
|
|
if (m_IsCapslocked && !newShiftState)
|
|
{
|
|
m_IsCapslocked = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the keyboard to a permanent shift state.
|
|
/// </summary>
|
|
/// <param name="newCapsLockState">Caps lock state the method is switching to</param>
|
|
public void CapsLock(bool newCapsLockState)
|
|
{
|
|
m_IsCapslocked = newCapsLockState;
|
|
Shift(newCapsLockState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Insert a space character.
|
|
/// </summary>
|
|
public void Space()
|
|
{
|
|
m_CaretPosition = InputField.caretPosition;
|
|
InputField.text = InputField.text.Insert(m_CaretPosition++, " ");
|
|
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Insert a tab character.
|
|
/// </summary>
|
|
public void Tab()
|
|
{
|
|
string tabString = "\t";
|
|
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
InputField.text = InputField.text.Insert(m_CaretPosition, tabString);
|
|
m_CaretPosition += tabString.Length;
|
|
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move caret to the left.
|
|
/// </summary>
|
|
public void MoveCaretLeft()
|
|
{
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
if (m_CaretPosition > 0)
|
|
{
|
|
--m_CaretPosition;
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move caret to the right.
|
|
/// </summary>
|
|
public void MoveCaretRight()
|
|
{
|
|
m_CaretPosition = InputField.caretPosition;
|
|
|
|
if (m_CaretPosition < InputField.text.Length)
|
|
{
|
|
++m_CaretPosition;
|
|
UpdateCaretPosition(m_CaretPosition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the keyboard.
|
|
/// (Clears all event subscriptions.)
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
if (IsMicrophoneActive())
|
|
{
|
|
dictationSystem.StopRecording();
|
|
}
|
|
SetMicrophoneDefault();
|
|
OnClosed(this, EventArgs.Empty);
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the text input field.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
ResetKeyboardState();
|
|
if (InputField.caretPosition != 0)
|
|
{
|
|
InputField.MoveTextStart(false);
|
|
}
|
|
InputField.text = "";
|
|
m_CaretPosition = InputField.caretPosition;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Method to set the sizes by code, as the properties are private.
|
|
/// Useful for scaling 'from the outside', for instance taking care of differences between
|
|
/// immersive headsets and HoloLens
|
|
/// </summary>
|
|
/// <param name="minScale">Min scale factor</param>
|
|
/// <param name="maxScale">Max scale factor</param>
|
|
/// <param name="minDistance">Min distance from camera</param>
|
|
/// <param name="maxDistance">Max distance from camera</param>
|
|
public void SetScaleSizeValues(float minScale, float maxScale, float minDistance, float maxDistance)
|
|
{
|
|
m_MinScale = minScale;
|
|
m_MaxScale = maxScale;
|
|
m_MinDistance = minDistance;
|
|
m_MaxDistance = maxDistance;
|
|
}
|
|
|
|
#region Keyboard Layout Modes
|
|
|
|
/// <summary>
|
|
/// Enable the alpha keyboard.
|
|
/// </summary>
|
|
public void ShowAlphaKeyboard()
|
|
{
|
|
AlphaKeyboard.gameObject.SetActive(true);
|
|
m_LastKeyboardLayout = LayoutType.Alpha;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the default subkeys only on the Alphanumeric keyboard.
|
|
/// </summary>
|
|
/// <returns>Returns true if default subkeys were activated, false if alphanumeric keyboard isn't active</returns>
|
|
private bool TryToShowAlphaSubkeys()
|
|
{
|
|
if (AlphaKeyboard.IsActive())
|
|
{
|
|
AlphaSubKeys.gameObject.SetActive(true);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the email subkeys only on the Alphanumeric keyboard.
|
|
/// </summary>
|
|
/// <returns>Returns true if the email subkey was activated, false if alphanumeric keyboard is not active and key can't be activated</returns>
|
|
private bool TryToShowEmailSubkeys()
|
|
{
|
|
if (AlphaKeyboard.IsActive())
|
|
{
|
|
AlphaMailKeys.gameObject.SetActive(true);
|
|
m_LastKeyboardLayout = LayoutType.Email;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show the URL subkeys only on the Alphanumeric keyboard.
|
|
/// </summary>
|
|
/// <returns>Returns true if the URL subkey was activated, false if alphanumeric keyboard is not active and key can't be activated</returns>
|
|
private bool TryToShowURLSubkeys()
|
|
{
|
|
if (AlphaKeyboard.IsActive())
|
|
{
|
|
AlphaWebKeys.gameObject.SetActive(true);
|
|
m_LastKeyboardLayout = LayoutType.URL;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable the symbol keyboard.
|
|
/// </summary>
|
|
public void ShowSymbolKeyboard()
|
|
{
|
|
SymbolKeyboard.gameObject.SetActive(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable GameObjects for all keyboard elements.
|
|
/// </summary>
|
|
private void DisableAllKeyboards()
|
|
{
|
|
AlphaKeyboard.gameObject.SetActive(false);
|
|
SymbolKeyboard.gameObject.SetActive(false);
|
|
|
|
AlphaWebKeys.gameObject.SetActive(false);
|
|
AlphaMailKeys.gameObject.SetActive(false);
|
|
AlphaSubKeys.gameObject.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset temporary states of keyboard.
|
|
/// </summary>
|
|
private void ResetKeyboardState()
|
|
{
|
|
CapsLock(false);
|
|
}
|
|
|
|
#endregion Keyboard Layout Modes
|
|
|
|
/// <summary>
|
|
/// Respond to keyboard activity: reset timeout timer, play sound
|
|
/// </summary>
|
|
private void IndicateActivity()
|
|
{
|
|
ResetClosingTime();
|
|
if (_audioSource == null)
|
|
{
|
|
_audioSource = GetComponent<AudioSource>();
|
|
}
|
|
if (_audioSource != null)
|
|
{
|
|
_audioSource.Play();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset inactivity closing timer
|
|
/// </summary>
|
|
private void ResetClosingTime()
|
|
{
|
|
if (CloseOnInactivity)
|
|
{
|
|
_closingTime = Time.time + CloseOnInactivityTime;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the keyboard has been left alone for too long and close
|
|
/// </summary>
|
|
private void CheckForCloseOnInactivityTimeExpired()
|
|
{
|
|
if (Time.time > _closingTime && CloseOnInactivity)
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
}
|
|
}
|