Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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