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; } } } }