Move third party assets to ThirdParty folder

This commit is contained in:
2024-08-08 11:26:28 +02:00
parent 386f303057
commit bd91af6f98
10340 changed files with 100 additions and 175 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 96fc6bb91a5f4cbc9dfa3d80488cb691
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b7f841a5ab5c49e1b8882865b2d33115
timeCreated: 1643966683

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHiScoresEntry.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.Helpers.HiScores
{
/// <summary>
/// UI component for a hi-scores entry.
/// </summary>
public class UxrHiScoresEntry : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Text _name;
[SerializeField] private Text _value;
[SerializeField] private Image _image;
#endregion
#region Public Methods
/// <summary>
/// Sets up the content.
/// </summary>
/// <param name="userName">Name to show on the label</param>
/// <param name="value">Value to show as a score</param>
/// <param name="sprite">Optional sprite to show next to the score</param>
public void Setup(string userName, string value, Sprite sprite)
{
_name.text = userName;
_value.text = value;
if (sprite != null)
{
_image.overrideSprite = sprite;
_image.gameObject.SetActive(true);
}
else
{
_image.sprite = null;
//_image.gameObject.SetActive(false);
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9d54601c17c87e44ae4402dd733147c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHiScoresPanelEnterName.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Animation.UI;
using UltimateXR.Core.Components;
using UltimateXR.UI.Helpers.Keyboard;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.Helpers.HiScores
{
/// <summary>
/// UI component for a hi-scores panel that requests the user name.
/// </summary>
public class UxrHiScoresPanelEnterName : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private UxrKeyboardUI _keyboard;
[SerializeField] private Text _textCongratulations;
[SerializeField] private Text _textEnterName;
[SerializeField] private UxrControlInput _buttonOk;
[SerializeField] private Text _textButtonOk;
#endregion
#region Public Types & Data
/// <summary>
/// Event called when the user finished entering the name and pressed the OK button.
/// </summary>
public event Action<string> NameEntered;
/// <summary>
/// Gets the <see cref="UxrKeyboardUI" /> component that is used to enter the name.
/// </summary>
public UxrKeyboardUI Keyboard => _keyboard;
#endregion
#region Public Methods
/// <summary>
/// Shows the panel using a fade effect. The panel GameObject should be in an inactive state.
/// </summary>
/// <param name="textCongratulations">Congratulations text</param>
/// <param name="textEnterName">Enter name text on top</param>
/// <param name="textEnter">Enter name text right above the name</param>
/// <param name="fadeDurationSeconds">Seconds it takes for the panel to fade in</param>
/// <param name="fadeDelaySeconds">Seconds to wait before the panel fades in</param>
public void Show(string textCongratulations, string textEnterName, string textEnter, float fadeDurationSeconds, float fadeDelaySeconds = 0.0f)
{
gameObject.SetActive(true);
UxrCanvasAlphaTween.FadeIn(_canvasGroup, fadeDurationSeconds, fadeDelaySeconds);
_textCongratulations.text = textCongratulations;
_textEnterName.text = textEnterName;
_textButtonOk.text = textEnter;
}
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_buttonOk.Clicked += ButtonOk_Clicked;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_buttonOk.Clicked -= ButtonOk_Clicked;
}
/// <summary>
/// Updates the OK button interactive state depending on whether there is currently any content in the name box.
/// </summary>
private void Update()
{
_buttonOk.Enabled = !string.IsNullOrEmpty(_keyboard.CurrentLine);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever the OK button was clicked. Closes the panel.
/// </summary>
/// <param name="controlInput">Control that was clicked</param>
/// <param name="eventData">Event parameters</param>
private void ButtonOk_Clicked(UxrControlInput controlInput, PointerEventData eventData)
{
if (!string.IsNullOrEmpty(_keyboard.CurrentLine) && !UxrTween.HasActiveTween<UxrCanvasAlphaTween>(_canvasGroup))
{
UxrCanvasAlphaTween.FadeOut(_canvasGroup, 0.2f, 0.0f, t => NameEntered?.Invoke(_keyboard.CurrentLine)).SetFinishedActions(UxrTweenFinishedActions.DeactivateGameObject);
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 263bfbe786344a545b0b5cc503acca73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,114 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHiScoresPanelEntries.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Animation.UI;
using UltimateXR.Core.Components;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.Helpers.HiScores
{
/// <summary>
/// UI component a hi-scores panel that shows user+score entries.
/// </summary>
public class UxrHiScoresPanelEntries : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Text _textTitle;
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private Transform _entriesRoot;
[SerializeField] private UxrHiScoresEntry _prefabEntry;
[SerializeField] private UxrControlInput _buttonOk;
[SerializeField] private Text _textButtonOk;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever the OK button was clicked.
/// </summary>
public event Action OkButtonClicked;
#endregion
#region Public Methods
/// <summary>
/// Enables the Hi-scores panel so that it shows up with a fade-in effect. The hi-scores panel's root GameObject should
/// be in an inactive state.
/// </summary>
/// <param name="title">Text to show on the title</param>
/// <param name="buttonOk">Text to show as the OK button</param>
/// <param name="fadeDurationSeconds">Seconds it takes for the panel to fade in</param>
/// <param name="fadeDelaySeconds">Seconds to wait before the panel fades in</param>
public void Show(string title, string buttonOk, float fadeDurationSeconds, float fadeDelaySeconds = 0.0f)
{
_textTitle.text = title;
_textButtonOk.text = buttonOk;
gameObject.SetActive(true);
UxrCanvasAlphaTween.FadeIn(_canvasGroup, fadeDurationSeconds, fadeDelaySeconds);
}
/// <summary>
/// Adds a hi-score entry to the panel.
/// </summary>
/// <param name="entryName">Entry name</param>
/// <param name="entryValue">Entry score</param>
/// <param name="icon">Optional icon to show next to the score</param>
public void AddEntry(string entryName, string entryValue, Sprite icon)
{
UxrHiScoresEntry entry = Instantiate(_prefabEntry, _entriesRoot);
entry.Setup(entryName, entryValue, icon);
}
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_buttonOk.Clicked += ButtonOk_Clicked;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_buttonOk.Clicked -= ButtonOk_Clicked;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever the OK button was clicked. Closes the panel.
/// </summary>
/// <param name="controlInput">Control that was clicked</param>
/// <param name="eventData">Event parameters</param>
private void ButtonOk_Clicked(UxrControlInput controlInput, PointerEventData eventData)
{
if (!UxrTween.HasActiveTween<UxrCanvasAlphaTween>(_canvasGroup))
{
UxrCanvasAlphaTween.FadeOut(_canvasGroup, 0.2f, 0.2f, t => OkButtonClicked?.Invoke()).SetFinishedActions(UxrTweenFinishedActions.DeactivateGameObject);
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 57e9e370504f517459fa8c9ffc9b37b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8266e8fe80b14fe1a6ee770a8d369419
timeCreated: 1643968098

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrKeyLayoutType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// Enumerates the different layouts of labels in a keyboard key.
/// </summary>
public enum UxrKeyLayoutType
{
/// <summary>
/// Single character label.
/// </summary>
SingleChar,
/// <summary>
/// Key supports multiple outputs depending on shift/alt gr and has multiple labels because of that.
/// </summary>
MultipleChar
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 171929de408c4a0aa3522c0c969b3408
timeCreated: 1643212986

View File

@@ -0,0 +1,27 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrKeyType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// Enumerates the different key types.
/// </summary>
public enum UxrKeyType
{
Printable,
Tab,
Shift,
CapsLock,
Control,
Alt,
AltGr,
Enter,
Backspace,
Del,
ToggleSymbols,
ToggleViewPassword,
Escape
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 91d401316c344f7d873da24bb2bef16b
timeCreated: 1643213319

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrKeyboardKeyEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// Key press/release event parameters.
/// </summary>
public class UxrKeyboardKeyEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the key that was pressed/released.
/// </summary>
public UxrKeyboardKeyUI Key { get; }
/// <summary>
/// Gets whether it was a press (true) or release (false).
/// </summary>
public bool IsPress { get; }
/// <summary>
/// Gets the current line content. If it was a keypress event and the the keypress was the ENTER key then the line
/// before pressing ENTER is passed.
/// </summary>
public string Line { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="key">Key that was pressed</param>
/// <param name="isPress">Is it a press or a release?</param>
/// <param name="line">Current line</param>
public UxrKeyboardKeyEventArgs(UxrKeyboardKeyUI key, bool isPress, string line)
{
Key = key;
IsPress = isPress;
Line = line;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02c109b87bfd46afb1d1aa7614d12f47
timeCreated: 1632987224

View File

@@ -0,0 +1,502 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrKeyboardKeyUI.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.UI;
#pragma warning disable 0414
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// UI component for a keyboard key.
/// </summary>
[ExecuteInEditMode]
public class UxrKeyboardKeyUI : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrKeyType _keyType;
[SerializeField] private UxrKeyLayoutType _layout;
[SerializeField] private string _printShift;
[SerializeField] private string _printNoShift;
[SerializeField] private string _printAltGr;
[SerializeField] private string _forceLabel;
[SerializeField] private Text _singleLayoutValue;
[SerializeField] private Text _multipleLayoutValueTopLeft;
[SerializeField] private Text _multipleLayoutValueBottomLeft;
[SerializeField] private Text _multipleLayoutValueBottomRight;
[SerializeField] private List<UxrToggleSymbolsPage> _toggleSymbols;
// Hidden in the custom inspector
[SerializeField] private bool _nameDirty;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the key type.
/// </summary>
public UxrKeyType KeyType => _keyType;
/// <summary>
/// Gets the layout use for the labels on the key.
/// </summary>
public UxrKeyLayoutType KeyLayoutType => _layout;
/// <summary>
/// Gets the character used when the key has a single label.
/// </summary>
public char SingleLayoutValue => _singleLayoutValue != null && _singleLayoutValue.text.Length > 0 ? _singleLayoutValue.text[0] : '?';
/// <summary>
/// Gets the character used in the top left corner when the key has multiple labels, because it supports combination
/// with shift and alt gr.
/// </summary>
public char MultipleLayoutValueTopLeft => _multipleLayoutValueTopLeft != null && _multipleLayoutValueTopLeft.text.Length > 0 ? _multipleLayoutValueTopLeft.text[0] : '?';
/// <summary>
/// Gets the character used in the bottom left corner when the key has multiple labels, because it supports combination
/// with shift and alt gr.
/// </summary>
public char MultipleLayoutValueBottomLeft => _multipleLayoutValueBottomLeft != null && _multipleLayoutValueBottomLeft.text.Length > 0 ? _multipleLayoutValueBottomLeft.text[0] : '?';
/// <summary>
/// Gets the character used in the bottom right corner when the key has multiple labels, because it supports
/// combination with shift and alt gr.
/// </summary>
public char MultipleLayoutValueBottomRight => _multipleLayoutValueBottomRight != null ? _multipleLayoutValueBottomRight.text[0] : '?';
/// <summary>
/// Gets whether the key supports combination with shift and alt gr, and has a character specified for the bottom
/// right.
/// </summary>
public bool HasMultipleLayoutValueBottomRight => _multipleLayoutValueBottomRight != null && _multipleLayoutValueBottomRight.text.Length > 0;
/// <summary>
/// Gets whether the key is a letter.
/// </summary>
public bool IsLetterKey => KeyType == UxrKeyType.Printable && char.IsLetter(SingleLayoutValue);
/// <summary>
/// Gets the current symbols group selected, for a key that has a <see cref="KeyType" /> role of
/// <see cref="UxrKeyType.ToggleSymbols" />.
/// </summary>
public UxrToggleSymbolsPage CurrentToggleSymbolsPage => KeyType == UxrKeyType.ToggleSymbols && _toggleSymbols != null && _currentSymbolsIndex < _toggleSymbols.Count ? _toggleSymbols[_currentSymbolsIndex] : null;
/// <summary>
/// Gets the next symbols group, for a key that has a <see cref="KeyType" /> role of
/// <see cref="UxrKeyType.ToggleSymbols" />, that would be selected if pressed.
/// </summary>
public UxrToggleSymbolsPage NextToggleSymbolsPage => KeyType == UxrKeyType.ToggleSymbols && _toggleSymbols != null && _toggleSymbols.Count > 0 ? _toggleSymbols[(_currentSymbolsIndex + 1) % _toggleSymbols.Count] : null;
/// <summary>
/// Gets the <see cref="UxrKeyboardKeyUI" /> component the key belongs to.
/// </summary>
public UxrKeyboardUI Keyboard
{
get
{
if (_keyboard == null)
{
_keyboard = GetComponentInParent<UxrKeyboardUI>();
}
return _keyboard;
}
}
/// <summary>
/// Gets the <see cref="UxrControlInput" /> component for the key.
/// </summary>
public UxrControlInput ControlInput { get; private set; }
/// <summary>
/// Gets or sets whether the key can be interacted with.
/// </summary>
public bool Enabled
{
get => ControlInput.Enabled;
set => ControlInput.Enabled = value;
}
/// <summary>
/// Gets or sets the string that, if non-empty, will override the label content on the key.
/// </summary>
public string ForceLabel
{
get => _forceLabel;
set
{
_forceLabel = value;
SetupKeyLabels();
}
}
#endregion
#region Public Methods
/// <summary>
/// Gets the character that would be printed if the key was pressed.
/// </summary>
/// <param name="shift">Whether shift is pressed</param>
/// <param name="altGr">Whether alt gr is pressed</param>
/// <returns>Character that would be printed</returns>
public char GetSingleLayoutValueNoForceLabel(bool shift, bool altGr)
{
if (shift && !string.IsNullOrEmpty(_printShift))
{
return _printShift[0];
}
if (altGr && !string.IsNullOrEmpty(_printAltGr))
{
return _printAltGr[0];
}
return !string.IsNullOrEmpty(_printNoShift) ? _printNoShift[0] : ' ';
}
/// <summary>
/// Updates the label on the key.
/// </summary>
/// <param name="shiftEnabled">Whether shift is enabled</param>
public void UpdateLetterKeyLabel(bool shiftEnabled)
{
if (KeyType == UxrKeyType.Printable && _singleLayoutValue)
{
_singleLayoutValue.text = shiftEnabled ? _printShift : _printNoShift;
}
}
/// <summary>
/// Sets up the toggle symbol entries.
/// </summary>
/// <param name="entries">Entries</param>
public void SetupToggleSymbolsPages(List<UxrToggleSymbolsPage> entries)
{
if (_keyType == UxrKeyType.ToggleSymbols)
{
_toggleSymbols = entries;
_currentSymbolsIndex = 0;
if (entries != null)
{
for (int i = 0; i < entries.Count; ++i)
{
entries[i].KeysRoot.SetActive(i == 0);
}
}
SetupKeyLabels();
}
}
/// <summary>
/// Sets the default symbols as the ones currently active.
/// </summary>
public void SetDefaultSymbols()
{
if (_keyType == UxrKeyType.ToggleSymbols && _toggleSymbols != null && _toggleSymbols.Count > 0)
{
_currentSymbolsIndex = 0;
for (int i = 0; i < _toggleSymbols.Count; ++i)
{
_toggleSymbols[i].KeysRoot.SetActive(i == _currentSymbolsIndex);
}
SetupKeyLabels();
}
}
/// <summary>
/// Toggles to the next symbols.
/// </summary>
public void ToggleSymbols()
{
if (_keyType == UxrKeyType.ToggleSymbols && _toggleSymbols != null && _toggleSymbols.Count > 0)
{
_currentSymbolsIndex = (_currentSymbolsIndex + 1) % _toggleSymbols.Count;
for (int i = 0; i < _toggleSymbols.Count; ++i)
{
_toggleSymbols[i].KeysRoot.SetActive(i == _currentSymbolsIndex);
}
SetupKeyLabels();
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (_keyboard == null)
{
_keyboard = GetComponentInParent<UxrKeyboardUI>();
}
if (_keyboard == null && !Application.isEditor)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} {nameof(UxrKeyboardUI)} component not found in parent hierarchy of key " + name);
}
}
ControlInput = GetComponent<UxrControlInput>();
if (ControlInput == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.UiModule} Keyboard key {name} has no control input");
}
}
SetupKeyLabels();
if (_keyboard && Application.isPlaying)
{
_keyboard.RegisterKey(this);
}
}
/// <summary>
/// Called when the component is destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (_keyboard && Application.isPlaying)
{
_keyboard.UnregisterKey(this);
}
}
#if UNITY_EDITOR
/// <summary>
/// Updates the labels and the GameObject's name in editor-mode depending on the labels that are set in the inspector.
/// </summary>
private void Update()
{
if (Application.isEditor)
{
if (!Application.isPlaying)
{
SetupKeyLabels();
}
if (_nameDirty)
{
UpdateName();
}
}
}
#endif
#endregion
#region Private Methods
/// <summary>
/// Sets up the labels on the key based on the current values in the inspector.
/// </summary>
private void SetupKeyLabels()
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "";
}
if (_multipleLayoutValueTopLeft)
{
_multipleLayoutValueTopLeft.text = "";
}
if (_multipleLayoutValueBottomLeft)
{
_multipleLayoutValueBottomLeft.text = "";
}
if (_multipleLayoutValueBottomRight)
{
_multipleLayoutValueBottomRight.text = "";
}
if (!string.IsNullOrEmpty(_forceLabel) && _singleLayoutValue)
{
_singleLayoutValue.text = _forceLabel;
return;
}
if (_keyType == UxrKeyType.Printable)
{
if (_layout == UxrKeyLayoutType.SingleChar)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = _printShift.Length > 0 && (_keyboard.ShiftEnabled || _keyboard.CapsLockEnabled) ? _printShift : _printNoShift;
}
}
else
{
if (_multipleLayoutValueTopLeft)
{
_multipleLayoutValueTopLeft.text = _printShift;
}
if (_multipleLayoutValueBottomLeft)
{
_multipleLayoutValueBottomLeft.text = _printNoShift;
}
if (_multipleLayoutValueBottomRight)
{
_multipleLayoutValueBottomRight.text = _printAltGr;
}
}
}
else if (_keyType == UxrKeyType.Tab)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Tab";
}
}
else if (_keyType == UxrKeyType.Shift)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Shift";
}
}
else if (_keyType == UxrKeyType.CapsLock)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Caps Lock";
}
}
else if (_keyType == UxrKeyType.Control)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Ctrl";
}
}
else if (_keyType == UxrKeyType.Alt)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Alt";
}
}
else if (_keyType == UxrKeyType.AltGr)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Alt Gr";
}
}
else if (_keyType == UxrKeyType.Enter)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Enter";
}
}
else if (_keyType == UxrKeyType.Backspace)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Backspace";
}
}
else if (_keyType == UxrKeyType.Del)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Del";
}
}
else if (_keyType == UxrKeyType.ToggleSymbols)
{
_singleLayoutValue.text = NextToggleSymbolsPage != null ? NextToggleSymbolsPage.Label :
CurrentToggleSymbolsPage != null ? CurrentToggleSymbolsPage.Label : string.Empty;
}
else if (_keyType == UxrKeyType.Escape)
{
if (_singleLayoutValue)
{
_singleLayoutValue.text = "Esc";
}
}
}
/// <summary>
/// Updates the GameObject name based on the labels set up in the inspector.
/// </summary>
private void UpdateName()
{
if (_keyType == UxrKeyType.Printable)
{
if (_singleLayoutValue && _layout == UxrKeyLayoutType.SingleChar)
{
if (_singleLayoutValue.text == " ")
{
name = "Key Space";
}
else
{
name = "Key " + _singleLayoutValue.text;
}
}
else
{
name = "Key" + (!string.IsNullOrEmpty(_printShift) ? " " + _printShift : "") + (!string.IsNullOrEmpty(_printNoShift) ? " " + _printNoShift : "") + (!string.IsNullOrEmpty(_printAltGr) ? " " + _printAltGr : "");
}
}
else if (_keyType == UxrKeyType.ToggleSymbols)
{
name = "Key Toggle Symbols";
}
else if (_keyType == UxrKeyType.ToggleViewPassword)
{
name = "Key Toggle View Password";
}
else if (_singleLayoutValue)
{
name = $"Key {_singleLayoutValue.text}";
}
_nameDirty = false;
}
#endregion
#region Private Types & Data
private UxrKeyboardUI _keyboard;
private int _currentSymbolsIndex;
#endregion
}
}
#pragma warning restore 0414

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 28d709e46a22d17489dcb6f84e149a2e
timeCreated: 1511538150
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,620 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrKeyboardUI.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// Component that handles a keyboard in VR for user input
/// </summary>
public class UxrKeyboardUI : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _multiline = true;
[SerializeField] private int _maxLineLength;
[SerializeField] private int _maxLineCount;
[SerializeField] private Text _consoleDisplay;
[SerializeField] private Text _currentLineDisplay;
[SerializeField] private bool _consoleDisplayUsesCursor = true;
[SerializeField] private bool _lineDisplayUsesCursor = true;
[SerializeField] private GameObject _capsLockEnabledObject;
[SerializeField] private bool _capsLockEnabled;
[SerializeField] private bool _previewCaps;
[SerializeField] private GameObject _passwordPreviewRootObject;
[SerializeField] private GameObject _passwordPreviewEnabledObject;
[SerializeField] private bool _isPassword;
[SerializeField] private bool _hidePassword = true;
#endregion
#region Public Types & Data
/// <summary>
/// Event called on key presses/releases.
/// </summary>
public event EventHandler<UxrKeyboardKeyEventArgs> KeyPressed;
/// <summary>
/// Event called on key presses/releases when the input is disabled using <see cref="AllowInput" />.
/// </summary>
public event EventHandler<UxrKeyboardKeyEventArgs> DisallowedKeyPressed;
/// <summary>
/// Event we can subscribe to if we want notifications whenever the current line
/// being typed in using the keyboard changed.
/// </summary>
public event EventHandler<string> CurrentLineChanged;
/// <summary>
/// Contains information about the key in our internal dictionary.
/// </summary>
public class KeyInfo
{
}
/// <summary>
/// Gets whether a shift key is being pressed.
/// </summary>
public bool ShiftEnabled => _shiftEnabled > 0;
/// <summary>
/// Gets whether a Control key is pressed.
/// </summary>
public bool ControlEnabled => _controlEnabled > 0;
/// <summary>
/// Gets the current console text content including the cursor.
/// </summary>
public string ConsoleContentWithCursor => ConsoleContent + CurrentCursor;
/// <summary>
/// Gets the current console line including the cursor.
/// </summary>
public string CurrentLineWithCursor => CurrentLine + CurrentCursor;
/// <summary>
/// Gets the current console cursor (can be empty or the cursor character as a string).
/// </summary>
public string CurrentCursor => AllowInput && Mathf.RoundToInt(Time.time * 1000) / 200 % 2 == 0 ? "_" : string.Empty;
/// <summary>
/// Gets whether caps lock is enabled.
/// </summary>
public bool CapsLockEnabled
{
get => _capsLockEnabled;
set => _capsLockEnabled = value;
}
/// <summary>
/// Gets whether the Alt key is pressed.
/// </summary>
public bool AltEnabled { get; private set; }
/// <summary>
/// Gets whether the Alt GR key is pressed.
/// </summary>
public bool AltGrEnabled { get; private set; }
/// <summary>
/// Gets the current console text content.
/// </summary>
public string ConsoleContent { get; private set; }
/// <summary>
/// Gets the current console line without the cursor.
/// </summary>
public string CurrentLine
{
get => _currentLine;
private set
{
if (value != _currentLine)
{
_currentLine = value;
OnCurrentLineChanged(value);
}
}
}
/// <summary>
/// Gets or sets whether keyboard input is allowed.
/// </summary>
public bool AllowInput { get; set; }
/// <summary>
/// Gets or sets whether the key labels casing changes when the shift of caps lock key is pressed.
/// </summary>
public bool PreviewCaps
{
get => _previewCaps;
set
{
_previewCaps = value;
UpdateLabelsCase();
}
}
/// <summary>
/// Gets or sets whether the keyboard is being used to type in a password. This can be used to hide the content behind
/// asterisk characters.
/// </summary>
public bool IsPassword
{
get => _isPassword;
set => _isPassword = value;
}
/// <summary>
/// Gets or sets whether to hide password characters when <see cref="IsPassword" /> is used.
/// </summary>
public bool HidePassword
{
get => _hidePassword;
set => _hidePassword = value;
}
#endregion
#region Public Methods
/// <summary>
/// Clears the console content.
/// </summary>
public void Clear()
{
_currentLineCount = 1;
ConsoleContent = string.Empty;
CurrentLine = string.Empty;
}
/// <summary>
/// If different symbols are present (through a ToggleSymbols keyboard key), sets the default symbols
/// as the currently enabled. Usually the default symbols are the regular alphabet letters.
/// </summary>
public void EnableDefaultSymbols()
{
if (_keyToggleSymbols != null)
{
_keyToggleSymbols.SetDefaultSymbols();
}
}
/// <summary>
/// Adds content to the console. This method should be used instead of the <see cref="ConsoleContent" /> property since
/// <see cref="ConsoleContent" /> will not process lines.
/// </summary>
/// <param name="newContent">Text content to append</param>
public void AddConsoleContent(string newContent)
{
if (string.IsNullOrEmpty(newContent))
{
return;
}
// Count the number of lines we are adding:
int newLineCount = newContent.GetOccurrenceCount("\n", false);
ConsoleContent += newContent;
_currentLineCount += newLineCount;
// Check if we exceeded the maximum line amount
CheckMaxLines();
}
/// <summary>
/// Called to register a new key in the keyboard.
/// </summary>
/// <param name="key">Key to register</param>
public void RegisterKey(UxrKeyboardKeyUI key)
{
Debug.Assert(key != null, "Keyboard key is null");
Debug.Assert(key.ControlInput != null, "Keyboard key's ControlInput is null");
if (!_keys.ContainsKey(key))
{
_keys.Add(key, new KeyInfo());
key.ControlInput.Pressed += KeyButton_KeyDown;
key.ControlInput.Released += KeyButton_KeyUp;
if (key.KeyType == UxrKeyType.ToggleSymbols)
{
_keyToggleSymbols = key;
}
}
}
/// <summary>
/// Called to unregister a key from the keyboard.
/// </summary>
/// <param name="key">Key to unregister</param>
public void UnregisterKey(UxrKeyboardKeyUI key)
{
Debug.Assert(key != null, "Keyboard key is null");
if (_keys.ContainsKey(key))
{
_keys.Remove(key);
key.ControlInput.Pressed -= KeyButton_KeyDown;
key.ControlInput.Released -= KeyButton_KeyUp;
if (key == _keyToggleSymbols)
{
_keyToggleSymbols = null;
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the keyboard and clears the content.
/// </summary>
protected override void Awake()
{
base.Awake();
AllowInput = true;
Clear();
if (_previewCaps)
{
UpdateLabelsCase();
}
}
/// <summary>
/// If there is a console display Text component specified, it becomes updated with the content plus the cursor.
/// If there is a caps lock GameObject specified it is updated to reflect the caps lock state as well.
/// </summary>
private void Update()
{
if (_consoleDisplay != null)
{
_consoleDisplay.text = FormatStringOutput(_consoleDisplayUsesCursor ? ConsoleContentWithCursor : ConsoleContent, _consoleDisplayUsesCursor);
}
if (_currentLineDisplay != null)
{
_currentLineDisplay.text = FormatStringOutput(_lineDisplayUsesCursor ? CurrentLineWithCursor : CurrentLine, _consoleDisplayUsesCursor);
}
if (_capsLockEnabledObject != null)
{
_capsLockEnabledObject.SetActive(_capsLockEnabled);
}
if (_passwordPreviewRootObject != null)
{
_passwordPreviewRootObject.SetActive(IsPassword);
}
if (_passwordPreviewEnabledObject != null)
{
_passwordPreviewEnabledObject.SetActive(!_hidePassword && _isPassword);
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a keyboard key was pressed.
/// </summary>
/// <param name="controlInput">The control that was pressed</param>
/// <param name="eventData">Event data</param>
private void KeyButton_KeyDown(UxrControlInput controlInput, PointerEventData eventData)
{
UxrKeyboardKeyUI key = controlInput.GetComponent<UxrKeyboardKeyUI>();
if (!AllowInput)
{
// Event notification
DisallowedKeyPressed?.Invoke(this, new UxrKeyboardKeyEventArgs(key, true, null));
return;
}
string lastLine = string.Empty;
if (key.KeyType == UxrKeyType.Printable)
{
if (!(_maxLineLength > 0 && CurrentLine.Length >= _maxLineLength))
{
if (key.KeyLayoutType == UxrKeyLayoutType.SingleChar)
{
if (!string.IsNullOrEmpty(key.ForceLabel))
{
ConsoleContent += key.GetSingleLayoutValueNoForceLabel(_capsLockEnabled || _shiftEnabled > 0, AltGrEnabled);
CurrentLine += key.GetSingleLayoutValueNoForceLabel(_capsLockEnabled || _shiftEnabled > 0, AltGrEnabled);
}
else
{
if (char.IsLetter(key.SingleLayoutValue))
{
char newCar = _capsLockEnabled || _shiftEnabled > 0 ? char.ToUpper(key.SingleLayoutValue) : char.ToLower(key.SingleLayoutValue);
ConsoleContent += newCar;
CurrentLine += newCar;
}
else
{
char newCar = key.GetSingleLayoutValueNoForceLabel(_shiftEnabled > 0 || _capsLockEnabled, AltGrEnabled);
ConsoleContent += newCar;
CurrentLine += newCar;
}
}
}
else if (key.KeyLayoutType == UxrKeyLayoutType.MultipleChar)
{
if (_shiftEnabled > 0)
{
ConsoleContent += key.MultipleLayoutValueTopLeft;
CurrentLine += key.MultipleLayoutValueTopLeft;
}
else if (AltGrEnabled)
{
if (key.HasMultipleLayoutValueBottomRight)
{
ConsoleContent += key.MultipleLayoutValueBottomRight;
CurrentLine += key.MultipleLayoutValueBottomRight;
}
}
else
{
ConsoleContent += key.MultipleLayoutValueBottomLeft;
CurrentLine += key.MultipleLayoutValueBottomLeft;
}
}
}
}
else if (key.KeyType == UxrKeyType.Tab)
{
string tab = " ";
int charsAddedCount = _maxLineLength > 0 ? CurrentLine.Length + tab.Length > _maxLineLength ? _maxLineLength - CurrentLine.Length : tab.Length : tab.Length;
ConsoleContent += tab.Substring(0, charsAddedCount);
CurrentLine += tab.Substring(0, charsAddedCount);
}
else if (key.KeyType == UxrKeyType.Shift)
{
_shiftEnabled++;
if (_previewCaps)
{
UpdateLabelsCase();
}
}
else if (key.KeyType == UxrKeyType.CapsLock)
{
_capsLockEnabled = !_capsLockEnabled;
if (_previewCaps)
{
UpdateLabelsCase();
}
}
else if (key.KeyType == UxrKeyType.Control)
{
_controlEnabled++;
}
else if (key.KeyType == UxrKeyType.Alt)
{
AltEnabled = true;
}
else if (key.KeyType == UxrKeyType.AltGr)
{
AltGrEnabled = true;
}
else if (key.KeyType == UxrKeyType.Enter)
{
#if !UNITY_WSA
lastLine = string.Copy(CurrentLine);
#else
lastLine = string.Empty + CurrentLine;
#endif
if (_multiline)
{
ConsoleContent += "\n";
CurrentLine = string.Empty;
_currentLineCount++;
CheckMaxLines();
}
}
else if (key.KeyType == UxrKeyType.Backspace)
{
if (CurrentLine.Length > 0)
{
ConsoleContent = ConsoleContent.Substring(0, ConsoleContent.Length - 1);
CurrentLine = CurrentLine.Substring(0, CurrentLine.Length - 1);
}
}
else if (key.KeyType == UxrKeyType.Del)
{
}
else if (key.KeyType == UxrKeyType.ToggleSymbols)
{
key.ToggleSymbols();
}
else if (key.KeyType == UxrKeyType.ToggleViewPassword)
{
_hidePassword = !_hidePassword;
}
else if (key.KeyType == UxrKeyType.Escape)
{
}
// Event notification
KeyPressed?.Invoke(this, new UxrKeyboardKeyEventArgs(key, true, key.KeyType == UxrKeyType.Enter ? lastLine : CurrentLine));
}
/// <summary>
/// Called when a keyboard keypress was released.
/// </summary>
/// <param name="controlInput">The control that was released</param>
/// <param name="eventData">Event data</param>
private void KeyButton_KeyUp(UxrControlInput controlInput, PointerEventData eventData)
{
UxrKeyboardKeyUI key = controlInput.GetComponent<UxrKeyboardKeyUI>();
if (!AllowInput)
{
// Event notification
DisallowedKeyPressed?.Invoke(this, new UxrKeyboardKeyEventArgs(key, false, null));
return;
}
if (key.KeyType == UxrKeyType.Printable)
{
}
else if (key.KeyType == UxrKeyType.Tab)
{
}
else if (key.KeyType == UxrKeyType.Shift)
{
_shiftEnabled--;
}
else if (key.KeyType == UxrKeyType.CapsLock)
{
}
else if (key.KeyType == UxrKeyType.Control)
{
_controlEnabled--;
}
else if (key.KeyType == UxrKeyType.Alt)
{
AltEnabled = false;
}
else if (key.KeyType == UxrKeyType.AltGr)
{
AltGrEnabled = false;
}
else if (key.KeyType == UxrKeyType.Enter)
{
}
else if (key.KeyType == UxrKeyType.Backspace)
{
}
else if (key.KeyType == UxrKeyType.Del)
{
}
else if (key.KeyType == UxrKeyType.Escape)
{
}
// Event notification
KeyPressed?.Invoke(this, new UxrKeyboardKeyEventArgs(key, false, CurrentLine));
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for the <see cref="CurrentLineChanged" /> event.
/// </summary>
/// <param name="value">New line value</param>
protected virtual void OnCurrentLineChanged(string value)
{
CurrentLineChanged?.Invoke(this, value);
}
#endregion
#region Private Methods
/// <summary>
/// Formats the given string to show it to the user. This is mainly used to make sure that passwords are hidden behind
/// asterisk characters.
/// </summary>
/// <param name="content">Content to format using the current settings</param>
/// <param name="isUsingCursor">
/// Tells whether content is a string that may have a cursor appended
/// </param>
/// <returns>Formatted string ready to show to the user</returns>
private string FormatStringOutput(string content, bool isUsingCursor)
{
if (string.IsNullOrEmpty(content))
{
return string.Empty;
}
return _isPassword && _hidePassword ? new string('*', content.Length - CurrentCursor.Length) + CurrentCursor : content;
}
/// <summary>
/// Checks if the maximum number of lines was reached in the console and if so removes lines from the beginning.
/// </summary>
private void CheckMaxLines()
{
if (_maxLineCount > 0 && _currentLineCount > _maxLineCount)
{
int linesCounted = 0;
for (int i = 0; i < ConsoleContent.Length; ++i)
{
if (ConsoleContent[i] == '\n')
{
linesCounted++;
if (linesCounted == _currentLineCount - _maxLineCount)
{
ConsoleContent = ConsoleContent.Remove(0, i + 1);
_currentLineCount -= linesCounted;
break;
}
}
}
}
}
/// <summary>
/// Updates uppercase/lowercase labels depending on the shift and caps lock state.
/// </summary>
private void UpdateLabelsCase()
{
if (_keys == null)
{
return;
}
foreach (KeyValuePair<UxrKeyboardKeyUI, KeyInfo> keyPair in _keys)
{
if (keyPair.Key.IsLetterKey)
{
keyPair.Key.UpdateLetterKeyLabel(ShiftEnabled || CapsLockEnabled);
}
}
}
#endregion
#region Private Types & Data
private readonly Dictionary<UxrKeyboardKeyUI, KeyInfo> _keys = new Dictionary<UxrKeyboardKeyUI, KeyInfo>();
private string _currentLine;
private int _currentLineCount;
private int _shiftEnabled;
private int _controlEnabled;
private UxrKeyboardKeyUI _keyToggleSymbols;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 85e65ed8ff0832245869f1b6941e9eb1
timeCreated: 1511538177
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleSymbolsPage.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.UI.Helpers.Keyboard
{
/// <summary>
/// Symbols page for keyboard symbols. A keyboard may have multiple symbol pages.
/// </summary>
[Serializable]
public class UxrToggleSymbolsPage
{
#region Inspector Properties/Serialized Fields
[SerializeField] private GameObject _keysRoot;
[SerializeField] private string _label;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the root <see cref="GameObject" /> where all the keys in the page hang from.
/// </summary>
public GameObject KeysRoot => _keysRoot;
/// <summary>
/// Gets the label that describes the symbols in the page.
/// </summary>
public string Label => _label;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="keysRoot">Root where are symbols in the page hang from</param>
/// <param name="label">Label that describes the symbols in the page</param>
public UxrToggleSymbolsPage(GameObject keysRoot, string label)
{
_keysRoot = keysRoot;
_label = label;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e13b8d6b491b4f19b22ae91cc599f1c5
timeCreated: 1643213310

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9a0b5de2afed4b00902ebabef5a4465d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da3d1ea9989c4cab9f15910108b907a2
timeCreated: 1648244279

View File

@@ -0,0 +1,780 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControlInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Extensions.System.Threading;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public delegate void DragStartedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DraggedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DragEndedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DroppedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void PressedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void ReleasedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void ClickedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void PressHeldEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void CursorEnteredEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void CursorExitedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void UpdateSelectedEventHandler(UxrControlInput controlInput, BaseEventData eventData);
public delegate void InputSubmittedEventHandler(UxrControlInput controlInput, BaseEventData eventData);
/// <summary>
/// A component derived from <see cref="EventTrigger" /> that simplifies the handling of events triggered by UI
/// controls. Among the key benefits are:
/// <list type="bullet">
/// <item>
/// Be able to write UI code by subscribing to events generated by UI controls. Global static events are
/// also provided to handle events coming from any control.
/// </item>
/// <item>
/// New controls with more complex behaviour can inherit from this class and add their own logic. Event
/// triggers are provided so that handling events can be done by overriding the appropriate methods, making
/// sure the base class is always called at the beginning. An example is <see cref="UxrToggleControlInput" />.
/// </item>
/// <item>
/// Each <see cref="UxrControlInput" /> can specify the audio/haptic feedback for the click/down/up events.
/// </item>
/// </list>
/// </summary>
public class UxrControlInput : EventTrigger
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _pressAndHoldDuration = 1.0f;
[SerializeField] private UxrControlFeedback _feedbackOnPress;
[SerializeField] private UxrControlFeedback _feedbackOnRelease;
[SerializeField] private UxrControlFeedback _feedbackOnClick;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> is pressed.
/// </summary>
public static event PressedEventHandler GlobalPressed;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> press is released.
/// </summary>
public static event ReleasedEventHandler GlobalReleased;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> is clicked. A click depending on the operating mode can be
/// a press or a release after a press.
/// </summary>
public static event ClickedEventHandler GlobalClicked;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> started being dragged.
/// </summary>
public static event DragStartedEventHandler GlobalDragStarted;
/// <summary>
/// Event called during the frames any <see cref="UxrControlInput" /> is being dragged.
/// </summary>
public static event DraggedEventHandler GlobalDragged;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> stopped being dragged.
/// </summary>
public static event DragEndedEventHandler GlobalDragEnded;
/// <summary>
/// Event called whenever the control started being dragged.
/// </summary>
public event DragStartedEventHandler DragStarted;
/// <summary>
/// Event called each frame the control is being dragged.
/// </summary>
public event DraggedEventHandler Dragged;
/// <summary>
/// Event called whenever the control stopped being dragged.
/// </summary>
public event DragEndedEventHandler DragEnded;
/// <summary>
/// Event called whenever the control was dropped.
/// </summary>
public event DroppedEventHandler Dropped;
/// <summary>
/// Event called whenever the control was pressed.
/// </summary>
public event PressedEventHandler Pressed;
/// <summary>
/// Event called whenever the control was released after being pressed.
/// </summary>
public event ReleasedEventHandler Released;
/// <summary>
/// Event called whenever the control was clicked. A click depending on the operating mode can be a press or a release
/// after a press.
/// </summary>
public event ClickedEventHandler Clicked;
/// <summary>
/// Event called whenever the control was kept being pressed for <see cref="PressAndHoldDuration" /> seconds without
/// being dragged.
/// </summary>
public event PressHeldEventHandler PressHeld;
/// <summary>
/// Event called whenever the pointer entered the control.
/// </summary>
public event CursorEnteredEventHandler CursorEntered;
/// <summary>
/// Event called whenever the pointer exited the control.
/// </summary>
public event CursorExitedEventHandler CursorExited;
/// <summary>
/// Event called whenever the selected control's input field is updated/changed.
/// </summary>
public event UpdateSelectedEventHandler UpdateSelected;
/// <summary>
/// Event called whenever the control's input field was submitted (OK was pressed).
/// </summary>
public event InputSubmittedEventHandler InputSubmitted;
/// <summary>
/// Gets the control's GameObject
/// </summary>
public GameObject GameObject => gameObject;
/// <summary>
/// Gets the Unity RectTransform component.
/// </summary>
public RectTransform RectTransform { get; private set; }
/// <summary>
/// Gets the Unity Image component on the same object if it exists.
/// </summary>
public Image Image { get; private set; }
/// <summary>
/// Gets the ScrollRect reference used in drag/scroll events.
/// </summary>
public ScrollRect ScrollRect { get; private set; }
/// <summary>
/// Gets whether the control is currently being dragged.
/// </summary>
public bool IsDragging { get; private set; }
/// <summary>
/// Gets whether the component is being destroyed. This means OnDestroy() was called the same frame
/// and will effectively be destroyed at the end of it.
/// </summary>
public bool IsBeingDestroyed { get; private set; }
/// <summary>
/// Gets or sets whether the object can be interacted with and will send any events.
/// </summary>
public bool Enabled
{
get => enabled;
set
{
enabled = value;
if (_selectable != null)
{
_selectable.interactable = value && _interactable;
}
if (_graphic != null)
{
_graphic.raycastTarget = value && _raycastTarget;
}
}
}
/// <summary>
/// Gets or sets whether the widget is interactable or not. The widget should have a <see cref="Selectable" />
/// component.
/// </summary>
public bool Interactable
{
get => _interactable;
set
{
if (_selectable != null)
{
_selectable.interactable = value;
}
_interactable = value;
}
}
/// <summary>
/// Gets or sets the custom data property.
/// </summary>
public object Tag { get; set; }
/// <summary>
/// Gets whether the control was clicked since the last time it was set to false.
/// </summary>
public bool WasClicked { get; set; }
/// <summary>
/// Gets or sets how many seconds need to pass to trigger a <see cref="PressHeld" /> event
/// </summary>
public float PressAndHoldDuration
{
get => _pressAndHoldDuration;
set => _pressAndHoldDuration = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was pressed.
/// </summary>
public UxrControlFeedback FeedbackOnDown
{
get => _feedbackOnPress;
set => _feedbackOnPress = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was released.
/// </summary>
public UxrControlFeedback FeedbackOnUp
{
get => _feedbackOnRelease;
set => _feedbackOnRelease = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was clicked.
/// </summary>
public UxrControlFeedback FeedbackOnClick
{
get => _feedbackOnClick;
set => _feedbackOnClick = value;
}
#endregion
#region Public Methods
/// <summary>
/// Creates an awaitable task that blocks until a control from a given set is clicked, and returns the control that was
/// clicked.
/// </summary>
/// <param name="ct">Cancellation token, to cancel the task</param>
/// <param name="controls">Controls to listen to</param>
/// <returns>Awaitable <see cref="Task" /> returning the control that was clicked, or null if the task was cancelled</returns>
public static async Task<UxrControlInput> WaitForClickAsync(CancellationToken ct, params UxrControlInput[] controls)
{
async Task<UxrControlInput> ReadControl(UxrControlInput control, CancellationToken ct = default)
{
await control.WaitForClickAsync(ct);
return ct.IsCancellationRequested ? null : control;
}
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
IEnumerable<Task<UxrControlInput>> tasks = controls.Select(b => ReadControl(b, ct));
Task<UxrControlInput> finishedTask = await Task.WhenAny(tasks);
if (!finishedTask.IsCanceled)
{
cts.Cancel();
}
return await finishedTask;
}
/// <summary>
/// Creates an awaitable task that blocks until the control is clicked.
/// </summary>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /> returning the control that was clicked or null if the task was cancelled</returns>
public async Task WaitForClickAsync(CancellationToken ct = default)
{
bool isClicked = false;
void ControlClicked(UxrControlInput localControl, PointerEventData eventData)
{
isClicked = localControl.Interactable;
}
Clicked += ControlClicked;
await TaskExt.WaitUntil(() => isClicked, ct);
Clicked -= ControlClicked;
}
#endregion
#region Unity
/// <summary>
/// Sets up the internal references.
/// </summary>
protected virtual void Awake()
{
RectTransform = GetComponent<RectTransform>();
Image = GetComponent<Image>();
ScrollRect = GetComponentInParent<ScrollRect>();
_selectable = GetComponent<Selectable>();
_graphic = GetComponent<Graphic>();
if (_graphic)
{
_raycastTarget = _graphic.raycastTarget;
}
if (_selectable != null)
{
Interactable &= _selectable.interactable;
}
}
/// <summary>
/// Unity OnDestroy() method.
/// </summary>
protected virtual void OnDestroy()
{
IsBeingDestroyed = true;
}
/// <summary>
/// Unity OnEnable() method.
/// </summary>
protected virtual void OnEnable()
{
}
/// <summary>
/// Unity OnDisable() method.
/// </summary>
protected virtual void OnDisable()
{
}
/// <summary>
/// Resets the component.
/// </summary>
protected virtual void Reset()
{
_feedbackOnPress = UxrControlFeedback.FeedbackDown;
_feedbackOnRelease = UxrControlFeedback.FeedbackUp;
_feedbackOnClick = UxrControlFeedback.FeedbackClick;
}
/// <summary>
/// Unity Start() method.
/// </summary>
protected virtual void Start()
{
}
/// <summary>
/// Checks for the press held event.
/// </summary>
protected virtual void Update()
{
CheckPressHeldEvent();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Method called by Unity when the control started being dragged.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnBeginDrag(PointerEventData eventData)
{
base.OnBeginDrag(eventData);
OnDragStarted(eventData);
}
/// <summary>
/// Method called by Unity each frame the control is being dragged.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDrag(PointerEventData eventData)
{
base.OnDrag(eventData);
OnDragged(eventData);
}
/// <summary>
/// Method called by Unity when a drag event ended on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnEndDrag(PointerEventData eventData)
{
base.OnEndDrag(eventData);
OnDragEnded(eventData);
}
/// <summary>
/// Method called by Unity when the control was dropped.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDrop(PointerEventData eventData)
{
base.OnDrop(eventData);
OnDropped(eventData);
}
/// <summary>
/// Method called by Unity when the control was pressed.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
OnPressed(eventData);
}
/// <summary>
/// Method called by Unity when the control was released after being pressed.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
OnReleased(eventData);
}
/// <summary>
/// Method called by Unity when the control was clicked. A click depending on the operating mode can be a press or a
/// release after a press.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerClick(PointerEventData eventData)
{
base.OnPointerClick(eventData);
OnClicked(eventData);
}
/// <summary>
/// Method called by Unity when the cursor entered the control rect.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerEnter(PointerEventData eventData)
{
base.OnPointerEnter(eventData);
OnCursorEntered(eventData);
}
/// <summary>
/// Method called by Unity when the cursor exited the control rect.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerExit(PointerEventData eventData)
{
base.OnPointerExit(eventData);
OnCursorExited(eventData);
}
/// <summary>
/// Method called by Unity when the content of an InputField was updated on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnUpdateSelected(BaseEventData eventData)
{
base.OnUpdateSelected(eventData);
if (enabled && UpdateSelected != null)
{
UpdateSelected?.Invoke(this, eventData);
}
}
/// <summary>
/// Method called by Unity when the content of an InputField was validated (OK was pressed) on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnSubmit(BaseEventData eventData)
{
base.OnSubmit(eventData);
OnInputSubmitted(eventData);
}
/// <summary>
/// Method called by Unity when the content was scrolled on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnScroll(PointerEventData eventData)
{
base.OnScroll(eventData);
if (ScrollRect != null)
{
ScrollRect.OnScroll(eventData);
}
}
/// <summary>
/// Method called by Unity when the control was selected.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
}
/// <summary>
/// Method called by Unity when a Cancel event was sent to control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnCancel(BaseEventData eventData)
{
base.OnCancel(eventData);
}
/// <summary>
/// Method called by Unity when the control was deselected.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDeselect(BaseEventData eventData)
{
base.OnDeselect(eventData);
}
/// <summary>
/// Method called by Unity when a potential drag could be started on the the control but the drag did not start yet.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnInitializePotentialDrag(PointerEventData eventData)
{
base.OnInitializePotentialDrag(eventData);
}
/// <summary>
/// Method called when navigating through the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnMove(AxisEventData eventData)
{
base.OnMove(eventData);
}
/// <summary>
/// Overridable event trigger for <see cref="DragStarted" /> and <see cref="GlobalDragStarted" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragStarted(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnBeginDrag(eventData);
}
if (enabled)
{
IsDragging = true;
DragStarted?.Invoke(this, eventData);
GlobalDragStarted?.Invoke(this, eventData);
}
ResetTapAndHoldEventInfo();
}
/// <summary>
/// Overridable event trigger for <see cref="Dragged" /> and <see cref="GlobalDragged" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragged(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnDrag(eventData);
}
if (enabled)
{
Dragged?.Invoke(this, eventData);
GlobalDragged?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="DragEnded" /> and <see cref="GlobalDragEnded" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragEnded(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnEndDrag(eventData);
}
if (enabled)
{
IsDragging = false;
DragEnded?.Invoke(this, eventData);
GlobalDragEnded?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Dropped" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDropped(PointerEventData eventData)
{
if (enabled && Dropped != null)
{
Dropped?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Pressed" /> and <see cref="GlobalPressed" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnPressed(PointerEventData eventData)
{
if (enabled)
{
_isPressAndHold = true;
_pressAndHoldEventData = eventData;
_pressAndHoldTimer = 0.0f;
GlobalPressed?.Invoke(this, eventData);
Pressed?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Released" /> and <see cref="GlobalReleased" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnReleased(PointerEventData eventData)
{
if (enabled)
{
GlobalReleased?.Invoke(this, eventData);
Released?.Invoke(this, eventData);
}
ResetTapAndHoldEventInfo();
}
/// <summary>
/// Overridable event trigger for <see cref="Clicked" /> and <see cref="GlobalClicked" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnClicked(PointerEventData eventData)
{
if (!IsDragging && enabled && Interactable)
{
WasClicked = true;
GlobalClicked?.Invoke(this, eventData);
Clicked?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="CursorEntered" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnCursorEntered(PointerEventData eventData)
{
if (enabled)
{
CursorEntered?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="CursorExited" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnCursorExited(PointerEventData eventData)
{
if (enabled)
{
CursorExited?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="InputSubmitted" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnInputSubmitted(BaseEventData eventData)
{
if (enabled && InputSubmitted != null)
{
InputSubmitted?.Invoke(this, eventData);
}
}
#endregion
#region Private Methods
/// <summary>
/// Checks if the TapAndHold timer reached its goal.
/// </summary>
private void CheckPressHeldEvent()
{
if (PressHeld != null && _isPressAndHold)
{
_pressAndHoldTimer += Time.deltaTime;
if (_pressAndHoldTimer > _pressAndHoldDuration)
{
PressHeld(this, _pressAndHoldEventData);
ResetTapAndHoldEventInfo();
}
}
}
/// <summary>
/// Resets the TapAndHold timers and state
/// </summary>
private void ResetTapAndHoldEventInfo()
{
_isPressAndHold = false;
_pressAndHoldEventData = null;
_pressAndHoldTimer = 0.0f;
}
#endregion
#region Private Types & Data
private Selectable _selectable;
private Graphic _graphic;
private bool _raycastTarget;
private float _pressAndHoldTimer;
private bool _isPressAndHold;
private PointerEventData _pressAndHoldEventData;
private bool _interactable = true;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 88631d58f15603847b5bde7ae5865d1f
timeCreated: 1457945442
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.InitState.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleControlInput
{
#region Public Types & Data
/// <summary>
/// Enumerates the different initial states of the toggle control.
/// </summary>
public enum InitState
{
/// <summary>
/// Initially toggled off.
/// </summary>
ToggledOff = 0,
/// <summary>
/// Initially toggled on.
/// </summary>
ToggledOn = 1,
/// <summary>
/// Don't change the toggle.
/// </summary>
DontChange = 2
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bded173e3f90483c826317e3b6fe50ed
timeCreated: 1686586842

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.TextColorChange.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleControlInput
{
#region Private Types & Data
/// <summary>
/// Allows to specify separate selected/not-selected colors for a text.
/// </summary>
[Serializable]
private class TextColorChange
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Text _text;
[SerializeField] private Color _colorSelected;
[SerializeField] private Color _colorNotSelected;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the text component.
/// </summary>
public Text TextComponent => _text;
/// <summary>
/// Gets the color used when the element is selected.
/// </summary>
public Color ColorSelected => _colorSelected;
/// <summary>
/// Gets the color used when the element is not selected.
/// </summary>
public Color ColorNotSelected => _colorNotSelected;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a26af86e3fe64c0fbc1ce1f0fb47d9cd
timeCreated: 1643734079

View File

@@ -0,0 +1,241 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
#pragma warning disable 67 // Disable warnings due to unused events
namespace UltimateXR.UI.UnityInputModule.Controls
{
/// <summary>
/// Type of <see cref="UxrControlInput" /> that implements toggle functionality.
/// </summary>
public partial class UxrToggleControlInput : UxrControlInput
{
#region Inspector Properties/Serialized Fields
[FormerlySerializedAs("_initialStateIsSelected")] [SerializeField] private InitState _initialState = InitState.DontChange;
[SerializeField] private bool _canToggleOnlyOnce;
[SerializeField] private Text _text;
[SerializeField] private List<GameObject> _enableWhenSelected;
[SerializeField] private List<GameObject> _enableWhenNotSelected;
[SerializeField] private List<TextColorChange> _textColorChanges;
[SerializeField] private AudioClip _audioToggleOn;
[SerializeField] private AudioClip _audioToggleOff;
[SerializeField] [Range(0, 1)] private float _audioToggleOnVolume = 1.0f;
[SerializeField] [Range(0, 1)] private float _audioToggleOffVolume = 1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever the state is toggled.
/// </summary>
public event Action<UxrToggleControlInput> Toggled;
/// <summary>
/// Gets or sets whether the current toggled state.
/// To set the state of the control without triggering any events, use <see cref="SetIsSelected" /> instead.
/// </summary>
public bool IsSelected
{
get => _isSelected;
set => SetIsSelected(value, true);
}
/// <summary>
/// Gets or sets whether the control can be toggled or not.
/// </summary>
public bool CanBeToggled { get; set; } = true;
/// <summary>
/// Gets or sets the text value. If no <see cref="Text" /> component is configured it will return
/// <see cref="string.Empty" />.
/// </summary>
public string Text
{
get => _text != null ? _text.text : string.Empty;
set
{
if (_text != null)
{
_text.text = value;
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Changes the current state of the control like <see cref="IsSelected" /> but allowing to control whether
/// <see cref="Toggled" /> events are propagated or not.
/// </summary>
/// <param name="value">State (selected/not-selected)</param>
/// <param name="propagateEvents">Whether to propagate events</param>
public void SetIsSelected(bool value, bool propagateEvents)
{
if (_isSelected == value && _isInitialized)
{
return;
}
_isSelected = value;
foreach (GameObject goToEnable in _enableWhenSelected)
{
if (goToEnable == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} {transform.GetPathUnderScene()} has null enableWhenSelected entry");
}
}
else
{
goToEnable.SetActive(_isSelected);
}
}
foreach (GameObject goToEnable in _enableWhenNotSelected)
{
if (goToEnable == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} {transform.GetPathUnderScene()} has null enableWhenNotSelected entry");
}
}
else
{
goToEnable.SetActive(!_isSelected);
}
}
foreach (TextColorChange textEntry in _textColorChanges)
{
textEntry.TextComponent.color = _isSelected ? textEntry.ColorSelected : textEntry.ColorNotSelected;
}
_isInitialized = true;
if (propagateEvents)
{
Toggled?.Invoke(this);
}
}
#endregion
#region Unity
/// <summary>
/// Sets up the events and initializes the current state.
/// </summary>
protected override void Awake()
{
base.Awake();
if (!_isInitialized && _initialState != InitState.DontChange)
{
SetIsSelected(_initialState == InitState.ToggledOn, true);
}
_alreadyToggled = false;
}
/// <summary>
/// Called when the component is destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
_text = null;
_enableWhenSelected = null;
_enableWhenNotSelected = null;
_textColorChanges = null;
}
/// <summary>
/// Checks for a <see cref="UxrToggleGroup" /> in any parent object to refresh the content.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrToggleGroup group = GetComponentInParent<UxrToggleGroup>();
if (group != null)
{
group.RefreshToggleChildrenList();
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Control was clicked. Toggle state.
/// </summary>
/// <param name="eventData">Event data</param>
protected override void OnClicked(PointerEventData eventData)
{
base.OnClicked(eventData);
if (!CanBeToggled || (_alreadyToggled && _canToggleOnlyOnce))
{
return;
}
if (Interactable)
{
_alreadyToggled = true;
if (_canToggleOnlyOnce)
{
Enabled = false;
}
Vector3 audioPosition = UxrAvatar.LocalAvatarCamera ? UxrAvatar.LocalAvatar.CameraPosition : transform.position;
if (_audioToggleOff && !_isSelected)
{
AudioSource.PlayClipAtPoint(_audioToggleOff, audioPosition, _audioToggleOffVolume);
}
else if (_audioToggleOn && _isSelected)
{
AudioSource.PlayClipAtPoint(_audioToggleOn, audioPosition, _audioToggleOnVolume);
}
SetIsSelected(!_isSelected, true);
}
}
#endregion
#region Private Types & Data
private bool _isInitialized;
private bool _isSelected;
private bool _alreadyToggled;
#endregion
}
}
#pragma warning restore 67

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bccdfcd78e8283419cf8f9d4214cd49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.InitState.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleGroup
{
#region Public Types & Data
/// <summary>
/// Enumerates the possible initial states, telling which child is currently selected.
/// </summary>
public enum InitState
{
/// <summary>
/// Initial state is determined by the state at edit-time.
/// </summary>
DontChange,
/// <summary>
/// First child is toggled on, the rest are toggled off.
/// </summary>
FirstChild
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 126143e2841b48b89285dfbf55f46861
timeCreated: 1686581341

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.StateSave;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleGroup
{
#region Protected Overrides UxrComponent
protected override int SerializationOrder => UxrConstants.Serialization.SerializationOrderDefault + 1;
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
if (level >= UxrStateSaveLevel.ChangesSinceBeginning)
{
int currentSelectionIndex = _toggles?.IndexOf(CurrentSelection) ?? -1;
SerializeStateValue(level, options, nameof(currentSelectionIndex), ref currentSelectionIndex);
if (isReading)
{
CurrentSelection = _toggles != null && currentSelectionIndex >= 0 && currentSelectionIndex < _toggles.Count ? _toggles[currentSelectionIndex] : null;
if (CurrentSelection != null)
{
CurrentSelection.IsSelected = true;
}
else
{
if (_toggles != null)
{
foreach (UxrToggleControlInput t in _toggles)
{
t.CanBeToggled = true;
t.SetIsSelected(false, false);
}
}
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f30cc83c3d143fc965842a9f4269e94
timeCreated: 1710686450

View File

@@ -0,0 +1,199 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.UI.UnityInputModule.Controls
{
/// <summary>
/// Component that, added to a GameObject, will make all child <see cref="UxrToggleControlInput" /> components behave
/// like a single group where only one selection can be active at a time.
/// </summary>
public partial class UxrToggleGroup : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private InitState _initialToggleState = InitState.DontChange;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever the currently selected toggle control was changed.
/// </summary>
public event Action<UxrToggleControlInput> SelectionChanged;
/// <summary>
/// Gets all the current toggle components.
/// </summary>
public IList<UxrToggleControlInput> Toggles => _toggles;
/// <summary>
/// Gets the currently selected toggle component.
/// </summary>
public UxrToggleControlInput CurrentSelection { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Refreshes the internal list of children toggle components. This can be used to refresh the list whenever a new
/// child toggle component was added dynamically, or was removed.
/// </summary>
public void RefreshToggleChildrenList()
{
_toggles.ForEach(t => t.Toggled -= Toggle_Toggled);
_toggles.Clear();
_toggles = new List<UxrToggleControlInput>();
for (int i = 0; i < transform.childCount; ++i)
{
UxrToggleControlInput toggle = transform.GetChild(i).GetComponent<UxrToggleControlInput>();
if (toggle != null && !toggle.IsBeingDestroyed && toggle.isActiveAndEnabled)
{
_toggles.Add(toggle);
toggle.Toggled += Toggle_Toggled;
}
}
CurrentSelection = _toggles.FirstOrDefault(t => t.IsSelected);
_toggles.ForEach(t => t.CanBeToggled = t != CurrentSelection);
}
/// <summary>
/// Clears the current selection if there is any.
/// </summary>
public void ClearCurrentSelection()
{
BeginSync();
if (CurrentSelection != null)
{
CurrentSelection.IsSelected = false;
CurrentSelection.CanBeToggled = true;
CurrentSelection = null;
}
EndSyncMethod();
}
#endregion
#region Unity
/// <summary>
/// Refreshes the toggle list and subscribes to toggle events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (!_toggles.Any())
{
RefreshToggleChildrenList();
}
}
/// <summary>
/// Unsubscribes from toggle events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_toggles.ForEach(t => t.Toggled -= Toggle_Toggled);
}
/// <summary>
/// Initializes the current selection if required.
/// </summary>
protected override void Start()
{
base.Start();
switch (_initialToggleState)
{
case InitState.DontChange: break;
case InitState.FirstChild:
if (_toggles.Any())
{
_toggles.First().IsSelected = true;
}
break;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever a toggle component was toggled.
/// </summary>
/// <param name="toggle">The toggle component that was toggled</param>
private void Toggle_Toggled(UxrToggleControlInput toggle)
{
NotifyToggle(_toggles.IndexOf(toggle), true);
}
#endregion
#region Private Methods
/// <summary>
/// Notifies that a toggle component was toggled.
/// </summary>
/// <param name="index">The index of the toggle</param>
/// <param name="isFromEvent">Whether the call comes from handling a toggle event</param>
private void NotifyToggle(int index, bool isFromEvent)
{
BeginSync();
UxrToggleControlInput toggle = index >= 0 && index < _toggles.Count ? _toggles[index] : null;
foreach (UxrToggleControlInput t in _toggles)
{
t.CanBeToggled = t != toggle;
if (t != null && t != toggle)
{
t.SetIsSelected(false, false);
}
}
CurrentSelection = toggle;
if (CurrentSelection != null && !isFromEvent)
{
// Since it doesn't come from an event, we need to update the selection manually.
CurrentSelection.SetIsSelected(true, false);
}
SelectionChanged?.Invoke(toggle);
EndSyncMethod(new object[] { index, false });
}
#endregion
#region Private Types & Data
private List<UxrToggleControlInput> _toggles = new List<UxrToggleControlInput>();
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91a12d6f9e0c02441a4a3316c35c956d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9ab744bd16f548f987238abb3edb36f6
timeCreated: 1643966807

View File

@@ -0,0 +1,159 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3D.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Base class to simplify interacting with 3D button objects by programming 2D UI elements.
/// A 2D Unity UI Canvas is placed on top of the 3D buttons. The Canvas will contain invisible
/// <see cref="UxrControlInput" /> UI components by using <see cref="UxrNonDrawingGraphic" /> instead of images.
/// The <see cref="UxrControlInput" /> components will get the user input and through child implementations of
/// <see cref="UxrButton3D" /> the 3D objects will be "pushed", "rotated" creating 3D behaviour using 2D logic.
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrButton3D : UxrComponent<Canvas, UxrButton3D>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _targetTransform;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the UI input component.
/// </summary>
public UxrControlInput ControlInput => GetCachedComponent<UxrControlInput>();
/// <summary>
/// Gets the <see cref="Transform" /> of the 3D object that is going to move, rotate, scale...
/// </summary>
public Transform Target => _targetTransform;
/// <summary>
/// Gets <see cref="Target" />'s local position during Awake().
/// </summary>
public Vector3 InitialTargetLocalPosition { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s local rotation during Awake().
/// </summary>
public Quaternion InitialTargetLocalRotation { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s world position during Awake().
/// </summary>
public Vector3 InitialTargetPosition { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s world rotation during Awake().
/// </summary>
public Quaternion InitialTargetRotation { get; private set; }
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (Target != null)
{
InitialTargetLocalPosition = Target.localPosition;
InitialTargetLocalRotation = Target.localRotation;
InitialTargetPosition = Target.position;
InitialTargetRotation = Target.rotation;
}
}
/// <summary>
/// Subscribes to the input control events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (ControlInput)
{
ControlInput.Pressed += ControlInput_Pressed;
ControlInput.Released += ControlInput_Released;
}
}
/// <summary>
/// Unsubscribes from the input control events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (ControlInput)
{
ControlInput.Pressed -= ControlInput_Pressed;
ControlInput.Released -= ControlInput_Released;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Receives the key down event.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
private void ControlInput_Pressed(UxrControlInput controlInput, PointerEventData eventData)
{
OnKeyPressed(controlInput, eventData);
}
/// <summary>
/// Receives the key up event.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
private void ControlInput_Released(UxrControlInput controlInput, PointerEventData eventData)
{
OnKeyReleased(controlInput, eventData);
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for the key pressed event. It can be overridden in child classes to handle key presses without
/// subscribing to events.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected virtual void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
}
/// <summary>
/// Event trigger for the key released event. It can be overridden in child classes to handle key releases without
/// subscribing to events.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected virtual void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bb3459a5e0294a478dcf6712b27704b1
timeCreated: 1640709321

View File

@@ -0,0 +1,60 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3DPress.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that moves a 3D object when a given UI control is being pressed
/// </summary>
public class UxrButton3DPress : UxrButton3D
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Vector3 _pressedLocalOffset;
#endregion
#region Event Trigger Methods
/// <summary>
/// Key down event. The object is moved to the pressed local coordinates.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Vector3 pressedLocalOffset = _pressedLocalOffset;
if (Target.parent != null)
{
pressedLocalOffset = Target.parent.InverseTransformDirection(Target.TransformDirection(pressedLocalOffset));
}
Target.localPosition = InitialTargetLocalPosition + pressedLocalOffset;
}
}
/// <summary>
/// Key up event. The original object position is restored.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Target.localPosition = InitialTargetLocalPosition;
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 11f37e36dfbb79d448e633e26928b45b
timeCreated: 1531470375
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3DRotate.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that rotates a 3D object when a given UI control is being pressed.
/// This allows to model buttons that rotate depending on the point of pressure.
/// The axis of rotation will be computed automatically, the center will be given by <see cref="UxrButton3D.Target" />
/// and the pressure applied will be on the transform of this component.
/// </summary>
public class UxrButton3DRotate : UxrButton3D
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Vector3 _buttonLocalUpAxis = Vector3.up;
[SerializeField] private float _pressedDegrees = 2.0f;
#endregion
#region Event Trigger Methods
/// <summary>
/// Key down event. The object is rotated according to the pressing point.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Vector3 rotationAxis = Vector3.Cross(_buttonLocalUpAxis, Target.InverseTransformVector(Target.position - transform.position).normalized);
Target.Rotate(rotationAxis, -_pressedDegrees, Space.Self);
}
}
/// <summary>
/// Key up event. The original object rotation is restored.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Target.localRotation = InitialTargetLocalRotation;
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b4edbb5eee02f6f41a8193c5b29c0421
timeCreated: 1531470375
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControlInputDestroyOnPress.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="UxrControlInput" /> component, will destroy
/// the GameObject whenever the control is clicked.
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrControlInputDestroyOnPress : UxrComponent
{
#region Public Types & Data
/// <summary>
/// Gets the <see cref="UxrControlInput" /> component.
/// </summary>
public UxrControlInput ControlInput => GetCachedComponent<UxrControlInput>();
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
ControlInput.Clicked += Control_Clicked;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
ControlInput.Clicked -= Control_Clicked;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the object was clicked.
/// </summary>
/// <param name="controlInput">Control that was clicked</param>
/// <param name="eventData">Event data</param>
private void Control_Clicked(UxrControlInput controlInput, PointerEventData eventData)
{
Destroy(gameObject);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eea1951b7bec08642a2234cbfa8dc391
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,92 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDynamicPixelsPerUnit.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that adjusts the dynamic pixels per unit value in a <see cref="Canvas" /> component depending on
/// the distance to the avatar. It helps removing filtering artifacts when using composition layers is not
/// possible.
/// </summary>
[RequireComponent(typeof(CanvasScaler))]
public class UxrDynamicPixelsPerUnit : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _updateSeconds = 0.3f;
[SerializeField] private float _rangeNear = 0.3f;
[SerializeField] private float _rangeFar = 4.0f;
[SerializeField] private float _pixelsPerUnitNear = 1.0f;
[SerializeField] private float _pixelsPerUnitFar = 0.1f;
#endregion
#region Unity
/// <summary>
/// Caches components.
/// </summary>
protected override void Awake()
{
base.Awake();
_canvasScaler = GetComponent<CanvasScaler>();
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when an avatar moved: Adjusts the dynamic pixels per unit.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
UxrAvatar avatar = sender as UxrAvatar;
if (avatar == UxrAvatar.LocalAvatar && Time.time - _timeLastUpdate > _updateSeconds)
{
_timeLastUpdate = Time.time;
float distance = Vector3.Distance(avatar.CameraPosition, _canvasScaler.transform.position);
_canvasScaler.dynamicPixelsPerUnit = Mathf.Lerp(_pixelsPerUnitNear, _pixelsPerUnitFar, Mathf.Clamp01((distance - _rangeNear) / (_rangeFar - _rangeNear)));
}
}
#endregion
#region Private Types & Data
private float _timeLastUpdate = -1.0f;
private CanvasScaler _canvasScaler;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96e14cd674acb2e458e6fcb725350787
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,155 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHoverTimerClick.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="UxrControlInput" /> component, will
/// automatically generate a Click event on the control whenever the cursor spends a given amount of time over it.
/// It can be used to implement clicks using the gaze pointer (<see cref="UxrCameraPointer" />).
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrHoverTimerClick : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] [Tooltip("Number of seconds the user will need to hover over this element to trigger the Click event")] private float _lookAtSecondsToClick = 2.0f;
[SerializeField] [Tooltip("Unscaled time will use the real device timer. If this parameter is unchecked it will use the scaled timer affected by pauses, bullet-times etc.")] private bool _useUnscaledTime = true;
[SerializeField] [Tooltip("Will update the fill value of the Image component on this same GameObject to represent the timer progress. Needs an Image component.")] private bool _useFillImage = true;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_image = GetComponent<Image>();
_controlInput = GetComponent<UxrControlInput>();
_timer = _lookAtSecondsToClick;
if (_useFillImage && _image == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} UseFillImage was specified on {GetType().Name} component of GameObject {name} but there is no Image component on it to update fill.");
}
}
else if (_useFillImage && _image != null && _image.type != Image.Type.Filled)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} UseFillImage was specified on {GetType().Name} component of GameObject {name} but the Image component is not of a filled type (Image Type property).");
}
}
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_controlInput.CursorEntered += Input_Entered;
_controlInput.CursorExited += Input_Exited;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_controlInput.CursorEntered -= Input_Entered;
_controlInput.CursorExited -= Input_Exited;
}
/// <summary>
/// Updates the progress and checks whether to generate the Click event.
/// </summary>
private void Update()
{
if (_timer > 0.0f)
{
_timer -= _useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
if (_timer <= 0.0f)
{
_timer = -1.0f;
if (_useFillImage && _image)
{
_image.fillAmount = 1.0f;
}
ExecuteEvents.ExecuteHierarchy(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
}
else
{
if (_useFillImage && _image)
{
_image.fillAmount = Mathf.Clamp01(1.0f - _timer / _lookAtSecondsToClick);
}
}
}
else
{
if (_useFillImage && _image)
{
_image.fillAmount = 0.0f;
}
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever the pointer entered the control's Rect.
/// </summary>
/// <param name="controlInput">Control input</param>
/// <param name="eventData">Event data</param>
private void Input_Entered(UxrControlInput controlInput, PointerEventData eventData)
{
_timer = _lookAtSecondsToClick;
}
/// <summary>
/// Called whenever the pointer exited the control's Rect.
/// </summary>
/// <param name="controlInput">Control input</param>
/// <param name="eventData">Event data</param>
private void Input_Exited(UxrControlInput controlInput, PointerEventData eventData)
{
_timer = -1.0f;
}
#endregion
#region Private Types & Data
private Image _image;
private UxrControlInput _controlInput;
private float _timer;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bac92ce8624fe1144ab04b50f88194c8
timeCreated: 1521406790
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,326 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrRightToLeftSupport.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
using UnityEngine.UI;
#if ULTIMATEXR_UNITY_TMPRO
using TMPro;
#endif
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// <para>
/// Component that, added to a UI element, will enable support for left-to-right and right-to-left languages.
/// Right-to-left languages not only work by setting text alignment to right, they also require the whole layout to
/// be right-to-left.
/// </para>
/// <para>
/// To switch from one to another use the static property <see cref="UseRightToLeft" />.
/// </para>
/// <para>
/// The supported UI components are:
/// </para>
/// <list type="bullet">
/// <item><see cref="Text" /> (Unity UI)</item>
/// <item>Text UI (TextMeshPro)</item>
/// <item><see cref="HorizontalLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="VerticalLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="GridLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="Image" /> fill origin (Unity UI)</item>
/// </list>
/// </summary>
public class UxrRightToLeftSupport : UxrComponent<UxrRightToLeftSupport>
{
#region Public Types & Data
/// <summary>
/// Sets the global right-to-left setting, changing all <see cref="UxrRightToLeftSupport" /> components.
/// Disabled components, or newly instantiated components, will be aligned correctly too.
/// </summary>
public static bool UseRightToLeft
{
get => s_useRightToLeft;
set
{
s_useRightToLeft = value;
EnabledComponents.ForEach(c => c.SetRightToLeft(value));
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
/// <summary>
/// Sets the RtoL setting when the component is enabled.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
SetRightToLeft(s_useRightToLeft);
}
#endregion
#region Private Methods
#if ULTIMATEXR_UNITY_TMPRO
/// <summary>
/// Transforms a TMPro alignment value from LtoR to RtoL.
/// </summary>
/// <param name="alignment">Alignment to transform</param>
/// <returns>RtoL value</returns>
private static TextAlignmentOptions GetRtoLAlignmentTMPro(TextAlignmentOptions alignment)
{
switch (alignment)
{
case TextAlignmentOptions.TopRight: return TextAlignmentOptions.TopLeft;
case TextAlignmentOptions.Right: return TextAlignmentOptions.Left;
case TextAlignmentOptions.BottomRight: return TextAlignmentOptions.BottomLeft;
case TextAlignmentOptions.BaselineRight: return TextAlignmentOptions.BaselineLeft;
case TextAlignmentOptions.MidlineRight: return TextAlignmentOptions.MidlineLeft;
case TextAlignmentOptions.CaplineRight: return TextAlignmentOptions.CaplineLeft;
case TextAlignmentOptions.TopLeft: return TextAlignmentOptions.TopRight;
case TextAlignmentOptions.Left: return TextAlignmentOptions.Right;
case TextAlignmentOptions.BottomLeft: return TextAlignmentOptions.BottomRight;
case TextAlignmentOptions.BaselineLeft: return TextAlignmentOptions.BaselineRight;
case TextAlignmentOptions.MidlineLeft: return TextAlignmentOptions.MidlineRight;
case TextAlignmentOptions.CaplineLeft: return TextAlignmentOptions.CaplineRight;
}
return alignment;
}
#endif
/// <summary>
/// Transforms a Unity UI alignment value from LtoR to RtoL.
/// </summary>
/// <param name="alignment">Alignment to transform</param>
/// <returns>RtoL value</returns>
private static TextAnchor GetRtoLAlignment(TextAnchor alignment)
{
switch (alignment)
{
case TextAnchor.UpperLeft: return TextAnchor.UpperRight;
case TextAnchor.UpperRight: return TextAnchor.UpperLeft;
case TextAnchor.MiddleLeft: return TextAnchor.MiddleRight;
case TextAnchor.MiddleRight: return TextAnchor.MiddleLeft;
case TextAnchor.LowerLeft: return TextAnchor.LowerRight;
case TextAnchor.LowerRight: return TextAnchor.LowerLeft;
}
return alignment;
}
/// <summary>
/// Transforms a Unity UI corner value from LtoR to RtoL.
/// </summary>
/// <param name="corner">Corner to transform</param>
/// <returns>RtoL value</returns>
private static GridLayoutGroup.Corner GetRtoLCorner(GridLayoutGroup.Corner corner)
{
switch (corner)
{
case GridLayoutGroup.Corner.UpperLeft: return GridLayoutGroup.Corner.UpperRight;
case GridLayoutGroup.Corner.UpperRight: return GridLayoutGroup.Corner.UpperLeft;
case GridLayoutGroup.Corner.LowerLeft: return GridLayoutGroup.Corner.LowerRight;
case GridLayoutGroup.Corner.LowerRight: return GridLayoutGroup.Corner.LowerLeft;
}
return corner;
}
/// <summary>
/// Transforms a Unity UI fill origin value from LtoR to RtoL.
/// </summary>
/// <param name="fillMethod">Fill method used</param>
/// <param name="fillOrigin">fill origin to transform</param>
/// <returns>RtoL value</returns>
private static int GetRtoLFillOrigin(Image.FillMethod fillMethod, int fillOrigin)
{
if (fillMethod == Image.FillMethod.Horizontal)
{
switch (fillOrigin)
{
case (int)Image.OriginHorizontal.Left: return (int)Image.OriginHorizontal.Right;
case (int)Image.OriginHorizontal.Right: return (int)Image.OriginHorizontal.Left;
}
}
else if (fillMethod == Image.FillMethod.Radial90)
{
switch (fillOrigin)
{
case (int)Image.Origin90.BottomLeft: return (int)Image.Origin90.BottomRight;
case (int)Image.Origin90.BottomRight: return (int)Image.Origin90.BottomLeft;
case (int)Image.Origin90.TopLeft: return (int)Image.Origin90.TopRight;
case (int)Image.Origin90.TopRight: return (int)Image.Origin90.TopLeft;
}
}
else if (fillMethod == Image.FillMethod.Radial180)
{
switch (fillOrigin)
{
case (int)Image.Origin180.Left: return (int)Image.Origin180.Right;
case (int)Image.Origin180.Right: return (int)Image.Origin180.Left;
}
}
else if (fillMethod == Image.FillMethod.Radial360)
{
switch (fillOrigin)
{
case (int)Image.Origin360.Left: return (int)Image.Origin360.Right;
case (int)Image.Origin360.Right: return (int)Image.Origin360.Left;
}
}
return fillOrigin;
}
/// <summary>
/// Gets the component references and stores the initial values.
/// </summary>
private void CheckInitialize()
{
if (_initialized)
{
return;
}
#if ULTIMATEXR_UNITY_TMPRO
_textTMPro = GetComponent<TextMeshProUGUI>();
if (_textTMPro != null)
{
_alignmentTMPro = _textTMPro.alignment;
}
#endif
_text = GetComponent<Text>();
if (_text != null)
{
_textAlignment = _text.alignment;
}
_horizontalLayout = GetComponent<HorizontalLayoutGroup>();
if (_horizontalLayout)
{
_horLayoutAlignment = _horizontalLayout.childAlignment;
_horLayoutReversed = _horizontalLayout.reverseArrangement;
}
_verticalLayout = GetComponent<VerticalLayoutGroup>();
if (_verticalLayout)
{
_verLayoutAlignment = _verticalLayout.childAlignment;
}
_gridLayout = GetComponent<GridLayoutGroup>();
if (_gridLayout)
{
_gridStartCorner = _gridLayout.startCorner;
_gridChildAlignment = _gridLayout.childAlignment;
}
_fillImage = GetComponent<Image>();
if (_fillImage != null && _fillImage.type == Image.Type.Filled)
{
_fillOrigin = _fillImage.fillOrigin;
}
else
{
_fillImage = null;
}
_initialized = true;
}
/// <summary>
/// Switches this component to LtoR or RtoL.
/// </summary>
/// <param name="useRightToLeft">Whether to use RtoL (true) or LtoR (false)</param>
private void SetRightToLeft(bool useRightToLeft)
{
CheckInitialize();
#if ULTIMATEXR_UNITY_TMPRO
if (_textTMPro != null)
{
_textTMPro.alignment = useRightToLeft ? GetRtoLAlignmentTMPro(_alignmentTMPro) : _alignmentTMPro;
}
#endif
if (_text != null)
{
_text.alignment = useRightToLeft ? GetRtoLAlignment(_textAlignment) : _textAlignment;
}
if (_horizontalLayout != null)
{
_horizontalLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_horLayoutAlignment) : _horLayoutAlignment;
_horizontalLayout.reverseArrangement = useRightToLeft ? !_horLayoutReversed : _horLayoutReversed;
}
if (_verticalLayout != null)
{
_verticalLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_verLayoutAlignment) : _verLayoutAlignment;
}
if (_gridLayout != null)
{
_gridLayout.startCorner = useRightToLeft ? GetRtoLCorner(_gridStartCorner) : _gridStartCorner;
_gridLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_gridChildAlignment) : _gridChildAlignment;
}
if (_fillImage != null)
{
_fillImage.fillOrigin = useRightToLeft ? GetRtoLFillOrigin(_fillImage.fillMethod, _fillOrigin) : _fillOrigin;
}
}
#endregion
#region Private Types & Data
private static bool s_useRightToLeft;
private bool _initialized;
private Text _text;
private TextAnchor _textAlignment;
private HorizontalLayoutGroup _horizontalLayout;
private TextAnchor _horLayoutAlignment;
private bool _horLayoutReversed;
private VerticalLayoutGroup _verticalLayout;
private TextAnchor _verLayoutAlignment;
private GridLayoutGroup _gridLayout;
private GridLayoutGroup.Corner _gridStartCorner;
private TextAnchor _gridChildAlignment;
private Image _fillImage;
private int _fillOrigin;
#if ULTIMATEXR_UNITY_TMPRO
private TextMeshProUGUI _textTMPro;
private TextAlignmentOptions _alignmentTMPro;
#endif
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0968a5f9d3d5caf4d91cd3e2011250d1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,227 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCanvas.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="Canvas" /> component, enables interaction
/// using <see cref="UxrFingerTip" /> components or <see cref="UxrLaserPointer" /> components.
/// </summary>
public class UxrCanvas : UxrComponent<UxrCanvas>
{
#region Inspector Properties/Serialized Fields
[SerializeField] protected UxrInteractionType _interactionType;
[SerializeField] protected float _fingerTipMinHoverDistance = UxrFingerTipRaycaster.FingerTipMinHoverDistanceDefault;
[SerializeField] protected bool _autoEnableLaserPointer;
[SerializeField] protected float _autoEnableDistance = 5.0f;
[SerializeField] protected bool _allowLeftHand = true;
[SerializeField] protected bool _allowRightHand = true;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the Unity <see cref="Canvas" /> component.
/// </summary>
public Canvas UnityCanvas => GetCachedComponent<Canvas>();
/// <summary>
/// Gets or sets whether the <see cref="UxrLaserPointer" /> components will automatically show their laser while
/// pointing towards the canvas.
/// </summary>
public bool AutoEnableLaserPointer
{
get => _autoEnableLaserPointer;
set => _autoEnableLaserPointer = value;
}
/// <summary>
/// Gets or sets the distance below which the <see cref="UxrLaserPointer" /> will automatically show the laser while
/// pointing towards the canvas.
/// </summary>
public float AutoEnableDistance
{
get => _autoEnableDistance;
set => _autoEnableDistance = value;
}
/// <summary>
/// Gets or sets the type of interaction with the UI components in the canvas.
/// </summary>
public UxrInteractionType CanvasInteractionType
{
get => _interactionType;
set
{
_interactionType = value;
if (_oldRaycaster != null)
{
DestroyVRCanvas();
CreateVRCanvas();
}
}
}
/// <summary>
/// Gets or sets the distance below which a <see cref="UxrFingerTip" /> component will generate hovering events.
/// </summary>
public float FingerTipMinHoverDistance
{
get => _fingerTipMinHoverDistance;
set => _fingerTipMinHoverDistance = value;
}
#endregion
#region Public Methods
/// <summary>
/// Checks if the canvas can be used with the given hand. This allows some canvases to work for the left or
/// right hand only.
/// </summary>
/// <param name="handSide">Which hand to check</param>
/// <returns>Boolean telling whether the given hand is compatible or not</returns>
public bool IsCompatible(UxrHandSide handSide)
{
return (handSide == UxrHandSide.Left && _allowLeftHand) || (handSide == UxrHandSide.Right && _allowRightHand);
}
/// <summary>
/// Sets up the canvas so that it can be used with <see cref="UxrPointerInputModule" />.
/// </summary>
/// <param name="inputModule">The input module</param>
public void SetupCanvas(UxrPointerInputModule inputModule)
{
CanvasInteractionType = inputModule.InteractionTypeOnAutoEnable;
if (_newRaycasterFingerTips != null)
{
_newRaycasterFingerTips.FingerTipMinHoverDistance = inputModule.FingerTipMinHoverDistance;
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
if (UxrPointerInputModule.Instance && UxrPointerInputModule.Instance.AutoAssignEventCamera && UnityCanvas && UxrAvatar.LocalAvatar)
{
UnityCanvas.worldCamera = UxrAvatar.LocalAvatar.CameraComponent;
}
if (_newRaycasterFingerTips == null && _newRaycasterLaserPointer == null)
{
CreateVRCanvas();
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates the raycaster required to use the <see cref="UxrCanvas" /> component with
/// <see cref="UxrPointerInputModule" />.
/// </summary>
private void CreateVRCanvas()
{
if (UnityCanvas == null)
{
return;
}
if (_oldRaycaster == null)
{
_oldRaycaster = UnityCanvas.gameObject.GetComponent<GraphicRaycaster>();
}
if (_interactionType == UxrInteractionType.FingerTips)
{
_newRaycasterFingerTips = GetOrAddRaycaster<UxrFingerTipRaycaster>(_oldRaycaster);
_newRaycasterFingerTips.FingerTipMinHoverDistance = _fingerTipMinHoverDistance;
}
else if (_interactionType == UxrInteractionType.LaserPointers)
{
_newRaycasterLaserPointer = GetOrAddRaycaster<UxrLaserPointerRaycaster>(_oldRaycaster);
}
}
/// <summary>
/// Sets up the new raycaster.
/// </summary>
/// <param name="oldRaycaster"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private T GetOrAddRaycaster<T>(GraphicRaycaster oldRaycaster) where T : GraphicRaycaster
{
bool copyParameters = UnityCanvas.GetComponent<T>() == null;
T rayCaster = UnityCanvas.GetOrAddComponent<T>();
rayCaster.enabled = true;
if (oldRaycaster && rayCaster)
{
if (copyParameters)
{
rayCaster.ignoreReversedGraphics = oldRaycaster.ignoreReversedGraphics;
rayCaster.blockingObjects = GraphicRaycaster.BlockingObjects.All;
rayCaster.blockingMask = oldRaycaster.blockingMask;
}
oldRaycaster.enabled = false;
}
return rayCaster;
}
/// <summary>
/// Destroys the new raycaster and restores the old one.
/// </summary>
private void DestroyVRCanvas()
{
if (_newRaycasterFingerTips)
{
Destroy(_newRaycasterFingerTips);
}
if (_newRaycasterLaserPointer)
{
Destroy(_newRaycasterLaserPointer);
}
if (_oldRaycaster && _oldRaycaster.enabled == false)
{
_oldRaycaster.enabled = true;
}
}
#endregion
#region Private Types & Data
private GraphicRaycaster _oldRaycaster;
private UxrFingerTipRaycaster _newRaycasterFingerTips;
private UxrLaserPointerRaycaster _newRaycasterLaserPointer;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 07521b0732d327543ad102e617a223c6
timeCreated: 1499253107
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,205 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFingerTipRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Raycaster compatible with Unity UI to use <see cref="UxrFingerTip" /> components on canvases, enabling touch
/// interaction.
/// </summary>
[RequireComponent(typeof(Canvas))]
public class UxrFingerTipRaycaster : UxrGraphicRaycaster
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _fingerTipMinHoverDistance = FingerTipMinHoverDistanceDefault;
[SerializeField] private float _fingerTipMaxAllowedAngle = FingerTipMaxAllowedTipAngle;
#endregion
#region Public Types & Data
/// <summary>
/// Default maximum distance a <see cref="UxrFingerTip" /> can have to a canvas in order to start generating hovering
/// events.
/// </summary>
public const float FingerTipMinHoverDistanceDefault = 0.05f;
/// <summary>
/// Default maximum angle between the finger and the canvas for a <see cref="UxrFingerTip" /> component to generate
/// input events.
/// </summary>
public const float FingerTipMaxAllowedTipAngle = 65.0f;
/// <summary>
/// Gets or sets the maximum distance a <see cref="UxrFingerTip" /> can have to a canvas in order to generate hovering
/// events.
/// </summary>
public float FingerTipMinHoverDistance
{
get => _fingerTipMinHoverDistance;
set => _fingerTipMinHoverDistance = value;
}
#endregion
#region Public Overrides GraphicRaycaster
/// <inheritdoc />
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
// Check if it should be ray-casted
UxrPointerEventData pointerEventData = eventData as UxrPointerEventData;
if (pointerEventData == null || pointerEventData.FingerTip == null)
{
return;
}
// Initialize if necessary
if (_canvas == null)
{
_canvas = gameObject.GetComponent<Canvas>();
_canvasGroup = gameObject.GetComponent<CanvasGroup>();
if (_canvas == null)
{
return;
}
}
if (_canvasGroup != null && _canvasGroup.interactable == false)
{
return;
}
// Raycast against the canvas, gather all results and append to the list
_raycastResults.Clear();
// First check finger angle. This helps avoiding unwanted clicks.
if (Vector3.Angle(pointerEventData.FingerTip.WorldDir, _canvas.transform.forward) > _fingerTipMaxAllowedAngle)
{
return;
}
// Raycast
var ray = new Ray(pointerEventData.FingerTip.WorldPos, pointerEventData.FingerTip.WorldDir);
Raycast(_canvas, eventCamera, ray, ref _raycastResults, ref resultAppendList);
// Assign correct indices and get closest raycast
RaycastResult? raycastNearest = null;
for (int i = 0; i < _raycastResults.Count; ++i)
{
RaycastResult rayCastResult = _raycastResults[i];
rayCastResult.index = resultAppendList.Count + i;
if (!raycastNearest.HasValue || rayCastResult.distance < raycastNearest.Value.distance)
{
raycastNearest = rayCastResult;
}
}
// If a closest raycast was found, use it to compute the event delta
if (raycastNearest.HasValue)
{
eventData.position = raycastNearest.Value.screenPosition;
eventData.delta = eventData.position - _lastPosition;
_lastPosition = eventData.position;
eventData.pointerCurrentRaycast = raycastNearest.Value;
}
}
#endregion
#region Private Methods
/// <summary>
/// Performs a raycast to check which elements in a canvas were potentially interacted with.
/// </summary>
/// <param name="canvas">Canvas to check</param>
/// <param name="cam">Camera from which to perform the checks</param>
/// <param name="ray">Ray to use for intersection detection</param>
/// <param name="results">Returns the list of results sorted by increasing distance</param>
/// <param name="resultAppendList">Returns a list where the results have been appended to the existing content</param>
private void Raycast(Canvas canvas, Camera cam, Ray ray, ref List<RaycastResult> results, ref List<RaycastResult> resultAppendList)
{
if (cam == null)
{
return;
}
// Iterate over all canvas graphics
IList<Graphic> listGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
for (int i = 0; i < listGraphics.Count; ++i)
{
if (listGraphics[i].depth == -1 || !listGraphics[i].raycastTarget)
{
continue;
}
float distance = Vector3.Dot(listGraphics[i].transform.forward, listGraphics[i].transform.position - ray.origin) / Vector3.Dot(listGraphics[i].transform.forward, ray.direction);
Vector3 position = ray.GetPoint(distance);
Vector2 pointerPosition = cam.WorldToScreenPoint(position);
if (distance > _fingerTipMinHoverDistance)
{
continue;
}
if (!RectTransformUtility.RectangleContainsScreenPoint(listGraphics[i].rectTransform, pointerPosition, cam))
{
continue;
}
if (listGraphics[i].Raycast(pointerPosition, cam))
{
var result = new RaycastResult
{
gameObject = listGraphics[i].gameObject,
module = this,
distance = distance,
screenPosition = pointerPosition,
worldPosition = position,
depth = listGraphics[i].depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
};
results.Add(result);
}
}
results.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
resultAppendList.AddRange(results);
}
#endregion
#region Private Types & Data
private Canvas _canvas;
private CanvasGroup _canvasGroup;
private List<RaycastResult> _raycastResults = new List<RaycastResult>();
private Vector2 _lastPosition;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 43772a8e9057f664b8d08fdebfb7a0f6
timeCreated: 1499253130
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGraphicRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Base Unity UI Graphic raycaster class.
/// </summary>
public abstract class UxrGraphicRaycaster : GraphicRaycaster
{
#region Public Methods
/// <summary>
/// Checks whether the raycast result describes a collision against a 2D or 3D object.
/// </summary>
/// <param name="result">Raycast result</param>
/// <returns>Whether collision is against a 2D or 3D object</returns>
public static bool Is2DOr3DRaycastResult(RaycastResult result)
{
return result.depth == UxrConstants.UI.Depth2DObject || result.depth == UxrConstants.UI.Depth3DObject;
}
/// <summary>
/// Compares two raycast results.
/// </summary>
/// <param name="a">Raycast result 1</param>
/// <param name="b">Raycast result 2</param>
/// <returns>Less than 0 if a is closer than b, 0 if they are at the same distance and greater than 0 if b is closer than a</returns>
public static int CompareDepth(RaycastResult a, RaycastResult b)
{
if (Is2DOr3DRaycastResult(a) || Is2DOr3DRaycastResult(b))
{
return a.distance.CompareTo(b.distance);
}
return a.depth.CompareTo(b.depth);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58546659bae6d204b9d8b76b60348c0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrIgnoreCanvas.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a Unity <see cref="Canvas" /> component, will ignore it
/// when the <see cref="UxrPointerInputModule" /> is set to automatically add <see cref="UxrCanvas" /> components that
/// enable user interaction with UltimateXR. See <see cref="UxrPointerInputModule.AutoEnableOnWorldCanvases" />.
/// </summary>
public class UxrIgnoreCanvas : UxrComponent
{
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 79994de9d5ee8844e811a98308b8a140
timeCreated: 1499253107
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInteractionType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Enumerates the types of interaction supported.
/// </summary>
public enum UxrInteractionType
{
/// <summary>
/// Interaction using <see cref="UxrFingerTip" /> components attached to the finger tips of an <see cref="UxrAvatar" />
/// . Enables touch interaction.
/// </summary>
FingerTips,
/// <summary>
/// Interaction using <see cref="UxrLaserPointer" /> components from a distance.
/// </summary>
LaserPointers
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 28efa05660db476d815944c84d9aec0a
timeCreated: 1648295346

View File

@@ -0,0 +1,249 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLaserPointerRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Raycaster compatible with Unity UI to use <see cref="UxrLaserPointer" /> components on canvases, enabling
/// interaction using laser pointers from a distance.
/// </summary>
[RequireComponent(typeof(Canvas))]
public class UxrLaserPointerRaycaster : UxrGraphicRaycaster
{
#region Public Overrides GraphicRaycaster
/// <inheritdoc />
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
// Check if it should be ray-casted
UxrPointerEventData pointerEventData = eventData as UxrPointerEventData;
if (pointerEventData == null || pointerEventData.LaserPointer == null)
{
return;
}
// Initialize if necessary
if (_canvas == null)
{
_canvas = gameObject.GetComponent<Canvas>();
_canvasGroup = gameObject.GetComponent<CanvasGroup>();
if (_canvas == null)
{
return;
}
}
if (_canvasGroup != null && _canvasGroup.interactable == false)
{
return;
}
// Raycast against the canvas, gather all results and append to the list
_raycastResults.Clear();
var ray = new Ray(pointerEventData.LaserPointer.LaserPos, pointerEventData.LaserPointer.LaserDir);
Raycast(_canvas, pointerEventData.LaserPointer, eventCamera, ray, ref _raycastResults, ref resultAppendList, out float _);
// Assign correct indices and get closest raycast
RaycastResult? raycastNearest = null;
for (int i = 0; i < _raycastResults.Count; ++i)
{
RaycastResult rayCastResult = _raycastResults[i];
rayCastResult.index = resultAppendList.Count + i;
if (!raycastNearest.HasValue || rayCastResult.distance < raycastNearest.Value.distance)
{
raycastNearest = rayCastResult;
}
}
// If a closest raycast was found, use it to compute the event delta
if (raycastNearest.HasValue)
{
eventData.position = raycastNearest.Value.screenPosition;
eventData.delta = eventData.position - _lastPosition;
_lastPosition = eventData.position;
eventData.pointerCurrentRaycast = raycastNearest.Value;
}
}
#endregion
#region Private Methods
/// <summary>
/// Performs a raycast to check which elements in a canvas were potentially interacted with.
/// </summary>
/// <param name="canvas">Canvas to check</param>
/// <param name="laserPointer">Source laser pointer or null if it could not be retrieved</param>
/// <param name="cam">Camera from which to perform the checks</param>
/// <param name="ray">Ray to use for intersection detection</param>
/// <param name="results">Returns the list of results sorted by increasing distance</param>
/// <param name="resultAppendList">Returns a list where the results have been appended to the existing content</param>
/// <param name="occluderDistance">Returns the occluder distance</param>
private void Raycast(Canvas canvas, UxrLaserPointer laserPointer, Camera cam, Ray ray, ref List<RaycastResult> results, ref List<RaycastResult> resultAppendList, out float occluderDistance)
{
occluderDistance = -1.0f;
if (cam == null)
{
return;
}
float hitDistance = float.MaxValue;
GameObject hitObject = null;
Vector3 hitPosition = Vector3.zero;
int hitDepth = 0;
// Check for objects in the path. Set hitDistance to the ray length to the closest hit.
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
{
RectTransform rectTransformCanvas = canvas.GetComponent<RectTransform>();
float maxDistance = Vector3.Distance(ray.origin, canvas.transform.position) +
Mathf.Max(rectTransformCanvas.localScale.x, rectTransformCanvas.localScale.y, rectTransformCanvas.localScale.z) *
Mathf.Max(rectTransformCanvas.rect.width, rectTransformCanvas.rect.height);
bool blocking3D = laserPointer != null ? laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.Colliders3D) : blockingObjects.HasFlag(BlockingObjects.ThreeD);
if (blocking3D)
{
if (Physics.Raycast(ray,
out RaycastHit hit,
maxDistance,
laserPointer != null ? laserPointer.BlockingMask : m_BlockingMask,
laserPointer != null ? laserPointer.TriggerCollidersInteraction : QueryTriggerInteraction.Ignore))
{
hitObject = hit.collider.gameObject;
hitPosition = hit.point;
hitDepth = UxrConstants.UI.Depth3DObject;
hitDistance = hit.distance;
occluderDistance = hitDistance;
}
}
bool blocking2D = laserPointer != null ? laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.Colliders2D) : blockingObjects.HasFlag(BlockingObjects.TwoD);
if (blocking2D)
{
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, maxDistance, laserPointer != null ? laserPointer.BlockingMask : m_BlockingMask);
if (hit.collider != null && hit.distance < hitDistance)
{
hitObject = hit.collider.gameObject;
hitPosition = hit.point;
hitDepth = UxrConstants.UI.Depth2DObject;
hitDistance = hit.fraction * maxDistance;
occluderDistance = hitDistance;
}
}
}
if (hitObject != null)
{
var result = new RaycastResult
{
gameObject = hitObject,
module = this,
distance = hitDistance,
screenPosition = cam.WorldToScreenPoint(hitPosition),
worldPosition = hitPosition,
depth = hitDepth,
sortingLayer = 0,
sortingOrder = 0,
};
results.Add(result);
}
// Iterate over all canvas graphics
bool processUI = laserPointer == null || laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.UI);
if (processUI)
{
IList<Graphic> listGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
for (int i = 0; i < listGraphics.Count; i++)
{
Graphic graphic = listGraphics[i];
if (graphic.depth == -1 || !graphic.raycastTarget)
{
continue;
}
float distance = Vector3.Dot(graphic.transform.forward, graphic.transform.position - ray.origin) / Vector3.Dot(graphic.transform.forward, ray.direction);
if (distance < 0.0f)
{
continue;
}
if (distance - HitDistanceThreshold > hitDistance)
{
continue;
}
Vector3 position = ray.GetPoint(distance);
Vector2 pointerPosition = cam.WorldToScreenPoint(position);
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, cam))
{
continue;
}
if (graphic.Raycast(pointerPosition, cam))
{
var result = new RaycastResult
{
gameObject = graphic.gameObject,
module = this,
distance = distance,
screenPosition = pointerPosition,
worldPosition = position,
depth = graphic.depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
};
results.Add(result);
}
}
}
results.Sort(CompareDepth);
resultAppendList.AddRange(results);
}
#endregion
#region Private Types & Data
private const float HitDistanceThreshold = 0.001f;
private Canvas _canvas;
private CanvasGroup _canvasGroup;
private List<RaycastResult> _raycastResults = new List<RaycastResult>();
private Vector2 _lastPosition;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8e1f143194d28eb40a9b20bbce7be961
timeCreated: 1499253130
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrNonDrawingGraphic.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Graphic component that can be used together with <see cref="UxrControlInput" /> on a UI element that has no
/// <see cref="Graphic" /> attached. It is useful to handle input on controls that need to graphic rendering, in order
/// to save performance.
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
public class UxrNonDrawingGraphic : Graphic
{
#region Public Overrides Graphic
/// <inheritdoc />
public override void SetMaterialDirty()
{
}
/// <inheritdoc />
public override void SetVerticesDirty()
{
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 18e00cfb3078b67429a699920e0ff930
timeCreated: 1471359907
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,137 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPointerEventData.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Event data class that adds information required by <see cref="UxrPointerInputModule" /> to facilitate the
/// processing of UI interaction events.
/// </summary>
public class UxrPointerEventData : PointerEventData
{
#region Public Types & Data
/// <summary>
/// Gets whether the event data contains valid information.
/// </summary>
public bool HasData => pointerCurrentRaycast.isValid || IgnoredGameObject != null || IsNonUI;
/// <summary>
/// Gets the <see cref="UxrAvatar" /> responsible for the interaction.
/// </summary>
public UxrAvatar Avatar { get; }
/// <summary>
/// Gets the hand responsible for the interaction.
/// </summary>
public UxrHandSide HandSide { get; }
/// <summary>
/// Gets the finger tip if this event is being processed by one. Null if not.
/// </summary>
public UxrFingerTip FingerTip { get; }
/// <summary>
/// Gets the laser pointer if this event is being processed by one. Null if not.
/// </summary>
public UxrLaserPointer LaserPointer { get; }
/// <summary>
/// Gets the current pointer world position.
/// </summary>
public Vector3 WorldPos { get; internal set; }
/// <summary>
/// Gets the pointer world position during the last frame.
/// </summary>
public Vector3 PreviousWorldPos { get; internal set; }
/// <summary>
/// Gets whether the world position has been initialized.
/// </summary>
public bool WorldPosInitialized { get; internal set; }
/// <summary>
/// Gets whether the pointer is pressing this frame.
/// </summary>
public bool PressedThisFrame { get; internal set; }
/// <summary>
/// Gets whether the pointer is pressing this frame.
/// </summary>
public bool ReleasedThisFrame { get; internal set; }
/// <summary>
/// Gets whether the current raycast UI element is interactive.
/// </summary>
public bool IsInteractive { get; internal set; }
/// <summary>
/// Gets whether the current raycast UI element is not a UI GameObject.
/// This happens when the raycast is valid and the object has either a 2D or 3D collider.
/// </summary>
public bool IsNonUI => GameObject2D != null || GameObject3D != null;
/// <summary>
/// Gets the UI gameObject that was ignored because it could not be interacted with.
/// </summary>
public GameObject IgnoredGameObject { get; internal set; }
/// <summary>
/// Gets the gameObject if the raycast element is an object with a 3D collider.
/// </summary>
public GameObject GameObject3D { get; internal set; }
/// <summary>
/// Gets the gameObject if the raycast element is an object with a 2D collider.
/// </summary>
public GameObject GameObject2D { get; internal set; }
/// <summary>
/// Gets the <see cref="GameObject" /> that was clicked, if there was one.
/// </summary>
public GameObject GameObjectClicked { get; internal set; }
/// <summary>
/// Gets the current cursor speed.
/// </summary>
public float Speed { get; internal set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="eventSystem">Event system</param>
/// <param name="fingerTip">Finger tip responsible for the event</param>
public UxrPointerEventData(EventSystem eventSystem, UxrFingerTip fingerTip) : base(eventSystem)
{
Avatar = fingerTip.Avatar;
HandSide = fingerTip.Side;
FingerTip = fingerTip;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="eventSystem">Event system</param>
/// <param name="laserPointer">Laser pointer responsible for the event</param>
public UxrPointerEventData(EventSystem eventSystem, UxrLaserPointer laserPointer) : base(eventSystem)
{
Avatar = laserPointer.Avatar;
HandSide = laserPointer.HandSide;
LaserPointer = laserPointer;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0dc0fbe190724434fb8cc6c933ff8a36
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 217d6da9f2110c642952a97bcb13abd4
timeCreated: 1499253133
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCameraPointer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Devices;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
namespace UltimateXR.UI
{
/// <summary>
/// Gaze pointer that can be added to a camera to enable gaze interaction with user interfaces.
/// </summary>
public class UxrCameraPointer : UxrLaserPointer
{
#region Inspector Properties/Serialized Fields
[SerializeField] private GameObject _crosshair;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
if (_crosshair != null)
{
// Disable crosshair colliders to avoid ray-casting
_crosshair.GetComponentsInChildren<Collider>().ForEach(c => c.enabled = false);
}
ClickInput = UxrInputButtons.Everything;
ShowLaserInput = UxrInputButtons.None;
IsInvisible = true;
// At the end so that the overriden parameters initialize the UxrLaserPointer component correctly.
base.Awake();
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: eba263f6ac85a8c4488cf1d4ae4eae98
timeCreated: 1521446197
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControlFeedback.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Haptics;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
namespace UltimateXR.UI
{
/// <summary>
/// Defines sound and haptic feedback for pressing events. Each <see cref="UxrControlInput" />, for instance, has a
/// <see cref="UxrControlFeedback" /> for each of its click/down/up events.
/// </summary>
[Serializable]
public class UxrControlFeedback
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrHapticClip _hapticClip = new UxrHapticClip();
[SerializeField] private AudioClip _audioClip;
[SerializeField] [Range(0, 1)] private float _audioVolume = 1.0f;
[SerializeField] private bool _useAudio3D = true;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the haptic clip.
/// </summary>
public UxrHapticClip HapticClip
{
get => _hapticClip;
set => _hapticClip = value;
}
/// <summary>
/// Gets or sets the audio clip.
/// </summary>
public AudioClip AudioClip
{
get => _audioClip;
set => _audioClip = value;
}
/// <summary>
/// Gets or sets the audio volume.
/// </summary>
public float AudioVolume
{
get => _audioVolume;
set => _audioVolume = value;
}
/// <summary>
/// Gets or sets whether to use 3D audio.
/// </summary>
public bool UseAudio3D
{
get => _useAudio3D;
set => _useAudio3D = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor.
/// </summary>
public UxrControlFeedback()
{
}
/// <summary>
/// Constructor allowing to define the haptic clip.
/// </summary>
/// <param name="hapticClip">Haptic clip to play on the event</param>
public UxrControlFeedback(UxrHapticClip hapticClip)
{
HapticClip = hapticClip;
}
#endregion
#region Public Types & Data
/// <summary>
/// Subtle click
/// </summary>
public static UxrControlFeedback FeedbackDown = new UxrControlFeedback(new UxrHapticClip(null, UxrHapticClipType.Click, UxrHapticMode.Mix, 1.0f, 0.2f));
/// <summary>
/// No feedback
/// </summary>
public static UxrControlFeedback FeedbackUp = new UxrControlFeedback(new UxrHapticClip());
/// <summary>
/// Regular click
/// </summary>
public static UxrControlFeedback FeedbackClick = new UxrControlFeedback(new UxrHapticClip(null, UxrHapticClipType.Click, UxrHapticMode.Mix, 1.0f, 0.6f));
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0fac381ae1f84c444b2037874281c890
timeCreated: 1457950127
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,147 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFingerTip.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.UI
{
/// <summary>
/// Component that, added to the tip of an <see cref="UxrAvatar" /> finger, allows it to interact with user interfaces.
/// It is normally added only to the index fingers so that other fingers don't generate unwanted interactions, but it
/// can be added to any finger.
/// </summary>
public class UxrFingerTip : UxrAvatarComponent<UxrFingerTip>
{
#region Public Types & Data
/// <summary>
/// Gets whether the <see cref="UxrGrabber" /> component belonging to the same hand the finger tip is, is currently
/// grabbing something.
/// </summary>
public bool IsHandGrabbing => HandGrabber && HandGrabber.GrabbedObject != null;
/// <summary>
/// Gets the current world position.
/// </summary>
public Vector3 WorldPos => transform.position;
/// <summary>
/// Gets the current world direction. The direction points in the direction the finger would be pointing. It is used to
/// filter out interactions where the forward vector is not perpendicular enough to the UI to interact with it.
/// </summary>
public Vector3 WorldDir => transform.forward;
/// <summary>
/// Gets the current world speed the finger tip is travelling at.
/// </summary>
public Vector3 WorldSpeed => _updateCount >= 2 ? (_currentWorldPos - _lastWorldPos) / Time.deltaTime : Vector3.zero;
/// <summary>
/// Gets the hand the finger tip belongs to.
/// </summary>
public UxrHandSide Side { get; private set; }
/// <summary>
/// Gets the <see cref="UxrGrabber" /> component of the hand the finger tip belongs to.
/// </summary>
public UxrGrabber HandGrabber { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Checks whether the finger tip is inside a box collider.
/// </summary>
/// <param name="box">Box collider</param>
/// <param name="allowWhileGrabbing">
/// Whether the operation that the check is for allows the hand to be grabbing something.
/// This means if the value is false and the hand is currently grabbing, it will always return false no matter the
/// finger tip is inside the box or not
/// </param>
/// <returns>Whether the finger tip is inside</returns>
public bool IsInside(BoxCollider box, bool allowWhileGrabbing = false)
{
if (!allowWhileGrabbing && IsHandGrabbing)
{
return false;
}
return box != null && transform.position.IsInsideBox(box);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrAvatarRig.GetHandSide(transform, out UxrHandSide handSide);
Side = handSide;
}
/// <summary>
/// Resets the internal state.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_updateCount = 0;
}
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
if (!(Avatar && Avatar.AvatarController))
{
return;
}
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(Avatar, true))
{
if (grabber.Side == Side)
{
HandGrabber = grabber;
break;
}
}
}
/// <summary>
/// Updates the component.
/// </summary>
private void Update()
{
_lastWorldPos = _currentWorldPos;
_currentWorldPos = transform.position;
_updateCount++;
}
#endregion
#region Private Types & Data
private int _updateCount;
private Vector3 _currentWorldPos;
private Vector3 _lastWorldPos;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1e65b7467b4c7ea44b54c3f014947fff
timeCreated: 1499257067
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,442 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLaserPointer.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Devices;
using UltimateXR.Devices.Visualization;
using UltimateXR.Extensions.Unity.Render;
using UltimateXR.UI.UnityInputModule;
using UnityEngine;
using UnityEngine.Rendering;
namespace UltimateXR.UI
{
/// <summary>
/// Component that, added to an object in an <see cref="UxrAvatar" /> , allows it to interact with user interfaces
/// using a laser pointer. It is normally added to the hand, so that it points in a forward direction from the hand,
/// but can also be added to inanimate objects.
/// </summary>
public class UxrLaserPointer : UxrAvatarComponent<UxrLaserPointer>
{
#region Inspector Properties/Serialized Fields
// General
[SerializeField] protected UxrHandSide _handSide = UxrHandSide.Left;
[SerializeField] protected bool _useControllerForward = true;
// Interaction
[SerializeField] protected UxrLaserPointerTargetTypes _targetTypes = UxrLaserPointerTargetTypes.UI | UxrLaserPointerTargetTypes.Colliders2D | UxrLaserPointerTargetTypes.Colliders3D;
[SerializeField] private QueryTriggerInteraction _triggerCollidersInteraction = QueryTriggerInteraction.Ignore;
[SerializeField] private LayerMask _blockingMask = ~0;
// Input
[SerializeField] protected UxrInputButtons _clickInput = UxrInputButtons.Trigger;
[SerializeField] protected UxrInputButtons _showLaserInput = UxrInputButtons.Joystick;
[SerializeField] protected UxrButtonEventType _showLaserButtonEvent = UxrButtonEventType.Touching;
// Appearance
[SerializeField] protected bool _invisible = false;
[SerializeField] protected float _rayLength = 100.0f;
[SerializeField] protected float _rayWidth = 0.003f;
[SerializeField] protected Color _rayColorInteractive = new Color(0.0f, 1.0f, 0.0f, 0.5f);
[SerializeField] protected Color _rayColorNonInteractive = new Color(1.0f, 0.0f, 0.0f, 0.5f);
[SerializeField] protected Material _rayHitMaterial = null;
[SerializeField] protected float _rayHitSize = 0.004f;
[SerializeField] protected GameObject _optionalEnableWhenLaserOn = null;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the laser is currently enabled.
/// </summary>
public bool IsLaserEnabled => gameObject.activeInHierarchy && enabled &&
(ForceLaserEnabled ||
IsAutoEnabled ||
(Avatar.ControllerInput.IsControllerEnabled(_handSide) && Avatar.ControllerInput.GetButtonsEvent(_handSide, ShowLaserInput, ShowLaserButtonEvent)));
/// <summary>
/// Gets the <see cref="Transform" /> that is used to compute the direction in which the laser points. The laser will
/// point in the <see cref="Transform.forward" /> direction.
/// </summary>
public Transform LaserTransform
{
get
{
if (UseControllerForward && !Avatar.HasDummyControllerInput)
{
UxrController3DModel model = Avatar.ControllerInput.GetController3DModel(_handSide);
if (model && model.gameObject.activeInHierarchy)
{
return model.Forward != null ? model.Forward : transform;
}
}
return transform;
}
}
/// <summary>
/// Gets the laser origin position.
/// </summary>
public Vector3 LaserPos => LaserTransform.position;
/// <summary>
/// Gets the laser direction.
/// </summary>
public Vector3 LaserDir => LaserTransform.forward;
/// <summary>
/// Gets the hand the laser pointer belongs to.
/// </summary>
public UxrHandSide HandSide => _handSide;
/// <summary>
/// Gets or sets whether the laser should be forcefully enabled. This is useful when
/// <see cref="UxrCanvas.AutoEnableLaserPointer" /> is used or a controller input is required to enable the laser
/// pointer.
/// </summary>
public bool ForceLaserEnabled { get; set; }
/// <summary>
/// Gets or sets whether the laser should ignore the <see cref="UxrCanvas.AutoEnableLaserPointer" />
/// property in canvases.
/// </summary>
public bool IgnoreAutoEnable { get; set; }
/// <summary>
/// Gets or sets whether to use the real controller forward instead of the component's forward.
/// </summary>
public bool UseControllerForward
{
get => _useControllerForward;
set => _useControllerForward = value;
}
/// <summary>
/// Gets or sets the elements the laser can interact with.
/// </summary>
public UxrLaserPointerTargetTypes TargetTypes
{
get => _targetTypes;
set => _targetTypes = value;
}
/// <summary>
/// Gets or sets how to treat collisions against trigger volumes.
/// By default the laser doesn't collide against trigger volumes.
/// </summary>
public QueryTriggerInteraction TriggerCollidersInteraction
{
get => _triggerCollidersInteraction;
set => _triggerCollidersInteraction = value;
}
/// <summary>
/// Gets or sets the which layers will block the laser for 3D objects.
/// </summary>
public LayerMask BlockingMask
{
get => _blockingMask;
set => _blockingMask = value;
}
/// <summary>
/// Gets or sets the input button(s) required for a click.
/// </summary>
public UxrInputButtons ClickInput
{
get => _clickInput;
set => _clickInput = value;
}
/// <summary>
/// Gets or sets the input button(s) required to show the laser. Use <see cref="UxrInputButtons.None" /> to have the
/// laser always enabled or <see cref="UxrInputButtons.Everything" /> to have it always disabled and let
/// <see cref="UxrCanvas.AutoEnableLaserPointer" /> handle the enabling/disabling.
/// </summary>
public UxrInputButtons ShowLaserInput
{
get => _showLaserInput;
set => _showLaserInput = value;
}
/// <summary>
/// Gets or sets the button event type required for <see cref="ShowLaserInput" />.
/// </summary>
public UxrButtonEventType ShowLaserButtonEvent
{
get => _showLaserButtonEvent;
set => _showLaserButtonEvent = value;
}
/// <summary>
/// Gets or sets whether to use an invisible laser ray.
/// </summary>
public bool IsInvisible
{
get => _invisible;
set => _invisible = value;
}
/// <summary>
/// Gets or sets the maximum laser length. This is the distance that the ray will travel if not occluded.
/// </summary>
public float MaxRayLength
{
get => _rayLength;
set => _rayLength = value;
}
/// <summary>
/// Gets the current laser ray length.
/// </summary>
public float CurrentRayLength { get; private set; }
/// <summary>
/// Gets or sets the laser ray width.
/// </summary>
public float RayWidth
{
get => _rayWidth;
set => _rayWidth = value;
}
/// <summary>
/// Gets or sets the ray color when it's pointing to an interactive element.
/// </summary>
public Color RayColorInteractive
{
get => _rayColorInteractive;
set => _rayColorInteractive = value;
}
/// <summary>
/// Gets or sets the ray color when it's not pointing to an interactive element.
/// </summary>
public Color RayColorNonInteractive
{
get => _rayColorNonInteractive;
set => _rayColorNonInteractive = value;
}
/// <summary>
/// Gets or sets the size of the ray hit quad..
/// </summary>
public float RayHitSize
{
get => _rayHitSize;
set => _rayHitSize = value;
}
/// <summary>
/// Gets or sets an optional GameObject that will be enabled or disabled along with the laser.
/// </summary>
public GameObject OptionalEnableWhenLaserOn
{
get => _optionalEnableWhenLaserOn;
set => _optionalEnableWhenLaserOn = value;
}
#endregion
#region Internal Types & Data
/// <summary>
/// Gets or sets whether the laser is enabled automatically due to pointing at a UI.
/// </summary>
internal bool IsAutoEnabled { get; set; }
#endregion
#region Public Methods
/// <summary>
/// Checks whether the user performed a click this frame (released the input button after pressing).
/// </summary>
/// <returns>Whether the user performed a click action</returns>
public bool IsClickedThisFrame()
{
return Avatar.ControllerInput.GetButtonsEvent(_handSide, ClickInput, UxrButtonEventType.PressDown);
}
/// <summary>
/// Checks whether the user performed a press this frame (pressed the input button).
/// </summary>
/// <returns>Whether the user performed a press action</returns>
public bool IsReleasedThisFrame()
{
return Avatar.ControllerInput.GetButtonsEvent(_handSide, ClickInput, UxrButtonEventType.PressUp);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (Avatar == null)
{
UxrManager.LogMissingAvatarInHierarchyError(this);
}
// Set up line renderer
_lineRenderer = gameObject.AddComponent<LineRenderer>();
_lineRenderer.useWorldSpace = false;
SetLineRendererMesh(MaxRayLength);
_lineRenderer.material = new Material(ShaderExt.UnlitTransparentColor);
_lineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 1;
// Set up raycast hit quad
_hitQuad = new GameObject("Laser Hit");
_hitQuad.transform.parent = transform;
MeshFilter laserHitMeshFilter = _hitQuad.AddComponent<MeshFilter>();
laserHitMeshFilter.sharedMesh = MeshExt.CreateQuad(1.0f);
_laserHitRenderer = _hitQuad.AddComponent<MeshRenderer>();
_laserHitRenderer.receiveShadows = false;
_laserHitRenderer.shadowCastingMode = ShadowCastingMode.Off;
_laserHitRenderer.sharedMaterial = _rayHitMaterial;
_hitQuad.SetActive(false);
}
/// <summary>
/// Updates the laser pointer.
/// </summary>
private void LateUpdate()
{
if (OptionalEnableWhenLaserOn != null)
{
OptionalEnableWhenLaserOn.SetActive(IsLaserEnabled);
}
// TODO: In order to use UxrLaserPointer for other than Unity UI, the following part should be extracted.
UxrPointerEventData laserPointerEventData = UxrPointerInputModule.Instance != null ? UxrPointerInputModule.Instance.GetPointerEventData(this) : null;
if (_lineRenderer)
{
_lineRenderer.enabled = IsLaserEnabled && !IsInvisible;
_lineRenderer.material.color = laserPointerEventData != null && laserPointerEventData.IsInteractive ? RayColorInteractive : RayColorNonInteractive;
if (_laserHitRenderer)
{
_laserHitRenderer.enabled = !IsInvisible;
_laserHitRenderer.material.color = _lineRenderer.material.color;
}
}
CurrentRayLength = MaxRayLength;
if (laserPointerEventData != null && laserPointerEventData.HasData && IsLaserEnabled)
{
CurrentRayLength = laserPointerEventData.pointerCurrentRaycast.distance;
if (Avatar.CameraComponent && _hitQuad)
{
_hitQuad.SetActive(true);
_hitQuad.transform.position = LaserTransform.TransformPoint(Vector3.forward * CurrentRayLength);
_hitQuad.transform.LookAt(Avatar.CameraPosition);
Plane plane = new Plane(Avatar.CameraForward, Avatar.CameraPosition);
float dist = plane.GetDistanceToPoint(_hitQuad.transform.position);
_hitQuad.transform.localScale = RayHitSize * Mathf.Max(2.0f, dist) * Vector3.one;
}
}
else
{
_hitQuad.SetActive(false);
}
if (_lineRenderer && _lineRenderer.enabled)
{
SetLineRendererMesh(CurrentRayLength);
}
}
#endregion
#region Private Methods
/// <summary>
/// Updates the line renderer mesh.
/// </summary>
/// <param name="rayLength">New ray length</param>
private void SetLineRendererMesh(float rayLength)
{
_lineRenderer.startWidth = RayWidth;
_lineRenderer.endWidth = RayWidth;
float t1 = Mathf.Min(rayLength * 0.33f, GradientLength);
float t2 = Mathf.Max(rayLength * 0.66f, rayLength - GradientLength);
Vector3[] positions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, t1),
new Vector3(0.0f, 0.0f, t2),
new Vector3(0.0f, 0.0f, rayLength)
};
for (int i = 0; i < positions.Length; ++i)
{
positions[i] = _lineRenderer.transform.InverseTransformPoint(LaserTransform.TransformPoint(positions[i]));
}
_lineRenderer.SetPositions(positions);
Gradient colorGradient = new Gradient();
colorGradient.colorKeys = new[]
{
new GradientColorKey(Color.white, 0.0f),
new GradientColorKey(Color.white, t1 / rayLength),
new GradientColorKey(Color.white, t2 / rayLength),
new GradientColorKey(Color.white, 1.0f)
};
colorGradient.alphaKeys = new[]
{
new GradientAlphaKey(0.0f, 0.0f),
new GradientAlphaKey(1.0f, t1 / rayLength),
new GradientAlphaKey(1.0f, t2 / rayLength),
new GradientAlphaKey(0.0f, 1.0f)
};
_lineRenderer.colorGradient = colorGradient;
_lineRenderer.positionCount = 4;
}
#endregion
#region Private Types & Data
private const float GradientLength = 0.4f;
private LineRenderer _lineRenderer;
private Renderer _laserHitRenderer;
private bool _isAutoEnabled;
private GameObject _hitQuad;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3a9dec1202eb387459d7c753899b8af6
timeCreated: 1499257067
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLaserPointerTargetTypes.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.UI
{
/// <summary>
/// Enumerates the different elements that a <see cref="UxrLaserPointer"/> can interact with.
/// </summary>
[Flags]
public enum UxrLaserPointerTargetTypes
{
UI = 1 << 0,
Colliders2D = 1 << 1,
Colliders3D = 1 << 2
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21884ee87bad79f4ba0197f4e754ac02
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: