wirm/Game/Assets/Samples/XR Interaction Toolkit/3.0.3/Spatial Keyboard/Scripts/XRKeyboardDisplay.cs

391 lines
14 KiB
C#
Raw Normal View History

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("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);
}
/// <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
m_InputField.caretPosition = 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
m_InputField.caretPosition = 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
m_InputField.caretPosition = 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();
}
}
}