// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- 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 { /// /// Component that, added to a with a 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 (). /// [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 /// /// Initializes the component. /// protected override void Awake() { base.Awake(); _image = GetComponent(); _controlInput = GetComponent(); _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)."); } } } /// /// Subscribes to events. /// protected override void OnEnable() { base.OnEnable(); _controlInput.CursorEntered += Input_Entered; _controlInput.CursorExited += Input_Exited; } /// /// Unsubscribes from events. /// protected override void OnDisable() { base.OnDisable(); _controlInput.CursorEntered -= Input_Entered; _controlInput.CursorExited -= Input_Exited; } /// /// Updates the progress and checks whether to generate the Click event. /// 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 /// /// Called whenever the pointer entered the control's Rect. /// /// Control input /// Event data private void Input_Entered(UxrControlInput controlInput, PointerEventData eventData) { _timer = _lookAtSecondsToClick; } /// /// Called whenever the pointer exited the control's Rect. /// /// Control input /// Event data 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 } }