using TMPro;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard
{
///
/// Keyboard key used to interface with .
///
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;
///
/// used for this key which is called when key is pressed. Used to communicate with
/// the Keyboard.
///
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;
///
/// (Optional) used for this key. Used in conjunction with Key Function or as a fallback for standard commands.
///
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;
///
/// 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)
///
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;
///
/// (Optional) Display character for this key in a non-shifted state. This string will be displayed on the
/// key text field. If left empty, will be used instead.
///
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;
///
/// (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.
///
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;
///
/// 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).
///
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;
///
/// (Optional) Display character for this key in a shifted state. This string will be displayed on the key text field.
/// If empty, will be used instead, and finally will
/// be capitalized and used if is empty.
///
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;
///
/// (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.
///
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;
///
/// If true, key pressed will fire on button down instead of on button up.
///
public bool updateOnKeyDown
{
get => m_UpdateOnKeyDown;
set => m_UpdateOnKeyDown = value;
}
[SerializeField, Tooltip("Text field used to display key character.")]
TMP_Text m_TextComponent;
///
/// Text field used to display key character.
///
public TMP_Text textComponent
{
get => m_TextComponent;
set => m_TextComponent = value;
}
[SerializeField, Tooltip("Image component used to display icons for key.")]
Image m_IconComponent;
///
/// Image component used to display icons for key.
///
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;
///
/// Image component used to highlight key indicating an active state.
///
public Image highlightComponent
{
get => m_HighlightComponent;
set => m_HighlightComponent = value;
}
[SerializeField, Tooltip("(Optional) Audio source played when key is pressed.")]
AudioSource m_AudioSource;
///
/// (Optional) Audio source played when key is pressed.
///
public AudioSource audioSource
{
get => m_AudioSource;
set => m_AudioSource = value;
}
XRKeyboard m_Keyboard;
float m_LastClickTime;
bool m_Shifted;
///
/// True if this key is in a shifted state, otherwise returns false.
///
public bool shifted
{
get => m_Shifted;
set
{
m_Shifted = value;
RefreshDisplayCharacter();
}
}
///
/// Time the key was last pressed.
///
public float lastClickTime => m_LastClickTime;
///
/// Time since the key was last pressed.
///
public float timeSinceLastClick => Time.time - m_LastClickTime;
///
/// The keyboard associated with this key.
///
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);
}
}
}
///
protected override void Start()
{
base.Start();
RefreshDisplayCharacter();
}
///
protected override void OnDestroy()
{
base.OnDestroy();
if (m_Keyboard != null)
{
m_Keyboard.onShifted.RemoveListener(OnKeyboardShift);
m_Keyboard.onLayoutChanged.RemoveListener(OnKeyboardLayoutChange);
}
}
///
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
if (m_UpdateOnKeyDown && interactable)
KeyClick();
}
///
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;
}
///
/// Local handling of this key being pressed.
///
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);
}
///
/// Enables or disables the key highlight image.
///
/// If true, the highlight image is enabled. If false, the highlight image is disabled.
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;
}
///
/// Helper function that returns the current effective character for this key based on shifted state.
///
/// Returns the when this key is in the shifted state or
/// when this key is not shifted.
public virtual string GetEffectiveCharacter()
{
if (m_Shifted)
{
if (!string.IsNullOrEmpty(m_ShiftCharacter))
return m_ShiftCharacter;
return m_Character.ToUpper();
}
return m_Character;
}
///
/// Enables or disables the key button being interactable. The icon and text alpha will be adjusted to reflect
/// the state of the button.
///
/// The desired interactable state of the key.
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;
}
}
}
}