Add ultimate xr
This commit is contained in:
8
Assets/UltimateXR/Runtime/Scripts/UI/Helpers.meta
Normal file
8
Assets/UltimateXR/Runtime/Scripts/UI/Helpers.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96fc6bb91a5f4cbc9dfa3d80488cb691
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7f841a5ab5c49e1b8882865b2d33115
|
||||
timeCreated: 1643966683
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9d54601c17c87e44ae4402dd733147c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 263bfbe786344a545b0b5cc503acca73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57e9e370504f517459fa8c9ffc9b37b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8266e8fe80b14fe1a6ee770a8d369419
|
||||
timeCreated: 1643968098
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 171929de408c4a0aa3522c0c969b3408
|
||||
timeCreated: 1643212986
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91d401316c344f7d873da24bb2bef16b
|
||||
timeCreated: 1643213319
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02c109b87bfd46afb1d1aa7614d12f47
|
||||
timeCreated: 1632987224
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e13b8d6b491b4f19b22ae91cc599f1c5
|
||||
timeCreated: 1643213310
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a0b5de2afed4b00902ebabef5a4465d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da3d1ea9989c4cab9f15910108b907a2
|
||||
timeCreated: 1648244279
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88631d58f15603847b5bde7ae5865d1f
|
||||
timeCreated: 1457945442
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bded173e3f90483c826317e3b6fe50ed
|
||||
timeCreated: 1686586842
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a26af86e3fe64c0fbc1ce1f0fb47d9cd
|
||||
timeCreated: 1643734079
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bccdfcd78e8283419cf8f9d4214cd49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 126143e2841b48b89285dfbf55f46861
|
||||
timeCreated: 1686581341
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f30cc83c3d143fc965842a9f4269e94
|
||||
timeCreated: 1710686450
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91a12d6f9e0c02441a4a3316c35c956d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ab744bd16f548f987238abb3edb36f6
|
||||
timeCreated: 1643966807
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb3459a5e0294a478dcf6712b27704b1
|
||||
timeCreated: 1640709321
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eea1951b7bec08642a2234cbfa8dc391
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96e14cd674acb2e458e6fcb725350787
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0968a5f9d3d5caf4d91cd3e2011250d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07521b0732d327543ad102e617a223c6
|
||||
timeCreated: 1499253107
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43772a8e9057f664b8d08fdebfb7a0f6
|
||||
timeCreated: 1499253130
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58546659bae6d204b9d8b76b60348c0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79994de9d5ee8844e811a98308b8a140
|
||||
timeCreated: 1499253107
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28efa05660db476d815944c84d9aec0a
|
||||
timeCreated: 1648295346
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e1f143194d28eb40a9b20bbce7be961
|
||||
timeCreated: 1499253130
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 217d6da9f2110c642952a97bcb13abd4
|
||||
timeCreated: 1499253133
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
47
Assets/UltimateXR/Runtime/Scripts/UI/UxrCameraPointer.cs
Normal file
47
Assets/UltimateXR/Runtime/Scripts/UI/UxrCameraPointer.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
108
Assets/UltimateXR/Runtime/Scripts/UI/UxrControlFeedback.cs
Normal file
108
Assets/UltimateXR/Runtime/Scripts/UI/UxrControlFeedback.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
147
Assets/UltimateXR/Runtime/Scripts/UI/UxrFingerTip.cs
Normal file
147
Assets/UltimateXR/Runtime/Scripts/UI/UxrFingerTip.cs
Normal 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
|
||||
}
|
||||
}
|
||||
12
Assets/UltimateXR/Runtime/Scripts/UI/UxrFingerTip.cs.meta
Normal file
12
Assets/UltimateXR/Runtime/Scripts/UI/UxrFingerTip.cs.meta
Normal 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:
|
||||
442
Assets/UltimateXR/Runtime/Scripts/UI/UxrLaserPointer.cs
Normal file
442
Assets/UltimateXR/Runtime/Scripts/UI/UxrLaserPointer.cs
Normal 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
|
||||
}
|
||||
}
|
||||
12
Assets/UltimateXR/Runtime/Scripts/UI/UxrLaserPointer.cs.meta
Normal file
12
Assets/UltimateXR/Runtime/Scripts/UI/UxrLaserPointer.cs.meta
Normal 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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21884ee87bad79f4ba0197f4e754ac02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user