// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Serialization; namespace Microsoft.MixedReality.Toolkit.UI { /// /// Add to any Object to spawn a prefab to it, according to preference /// [AddComponentMenu("Scripts/MRTK/SDK/PrefabSpawner")] public class PrefabSpawner : BaseFocusHandler, IMixedRealityInputHandler, IMixedRealityInputHandler { private enum VanishType { /// /// Causes the tooltip to vanish immediately after disappearing. Ignores vanishDelay. /// VanishOnFocusExit = 0, VanishOnTap, /// /// Equivalent to VanishOnFocusExit. but honors vanishDelay and will only disappear /// after that many seconds elapses after focus loss. /// VanishOnFocusExitWithDelay, } private enum AppearType { AppearOnFocusEnter = 0, AppearOnTap, } public enum RemainType { Indefinite = 0, Timeout, } [SerializeField, FormerlySerializedAs("toolTipPrefab")] protected GameObject prefab = null; [Header("Input Settings")] [SerializeField] [Tooltip("The action that will be used for when to spawn or toggle the tooltip.")] private MixedRealityInputAction tooltipToggleAction = MixedRealityInputAction.None; [Header("Appear / Vanish Behavior Settings")] [SerializeField] private AppearType appearType = AppearType.AppearOnFocusEnter; [SerializeField] private VanishType vanishType = VanishType.VanishOnFocusExit; [SerializeField] private RemainType remainType = RemainType.Timeout; [Header("Timing")] [SerializeField] [Range(0f, 5f)] private float appearDelay = 0.0f; [SerializeField] [Range(0f, 5f)] [Tooltip("The number of seconds that must elapse before the tooltip will disappear. Only used when vanishType is VanishOnFocusExitWithDelay.")] private float vanishDelay = 2.0f; [SerializeField] [Range(0.5f, 10.0f)] private float lifetime = 1.0f; [Header("Orientation")] [SerializeField] private bool keepWorldRotation = true; private float focusEnterTime = 0f; private float focusExitTime = 0f; private float tappedTime = 0f; private GameObject spawnable; private async void ShowSpawnable() { await UpdateSpawnable(focusEnterTime, tappedTime); } private async Task UpdateSpawnable(float focusEnterTimeOnStart, float tappedTimeOnStart) { if (spawnable == null) { spawnable = Instantiate(prefab); spawnable.transform.parent = transform; spawnable.transform.localPosition = Vector3.zero; if (!keepWorldRotation) { spawnable.transform.localRotation = Quaternion.identity; } spawnable.SetActive(false); } if (appearType == AppearType.AppearOnFocusEnter) { // Wait for the appear delay await new WaitForSeconds(appearDelay); // If we don't have focus any more, get out of here if (!HasFocus) { return; } } spawnable.SetActive(true); SpawnableActivated(spawnable); while (spawnable.activeSelf) { if (remainType == RemainType.Timeout) { switch (appearType) { case AppearType.AppearOnTap: if (Time.unscaledTime - tappedTime >= lifetime) { spawnable.SetActive(false); return; } break; case AppearType.AppearOnFocusEnter: if (Time.unscaledTime - focusEnterTime >= lifetime) { spawnable.SetActive(false); return; } break; } } // Check whether we're supposed to disappear switch (vanishType) { case VanishType.VanishOnFocusExit: if (!HasFocus) { spawnable.SetActive(false); } break; case VanishType.VanishOnTap: if (!tappedTime.Equals(tappedTimeOnStart)) { spawnable.SetActive(false); } break; case VanishType.VanishOnFocusExitWithDelay: default: if (!HasFocus && HasVanishDelayElapsed()) { spawnable.SetActive(false); } break; } await new WaitForUpdate(); } } protected virtual void SpawnableActivated(GameObject spawnable) { } /// public override void OnFocusEnter(FocusEventData eventData) { base.OnFocusEnter(eventData); HandleFocusEnter(); } /// public override void OnFocusExit(FocusEventData eventData) { base.OnFocusExit(eventData); HandleFocusExit(); } /// void IMixedRealityInputHandler.OnInputChanged(InputEventData eventData) { if (eventData.InputData > .95f) { HandleTap(); } } /// void IMixedRealityInputHandler.OnInputDown(InputEventData eventData) { if (tooltipToggleAction.Id == eventData.MixedRealityInputAction.Id) { HandleTap(); } } /// void IMixedRealityInputHandler.OnInputUp(InputEventData eventData) { } protected virtual void HandleTap() { tappedTime = Time.unscaledTime; if (spawnable == null || !spawnable.activeSelf) { switch (appearType) { case AppearType.AppearOnTap: ShowSpawnable(); break; } } else { switch (vanishType) { case VanishType.VanishOnTap: spawnable.SetActive(false); break; } } } private void HandleFocusEnter() { focusEnterTime = Time.unscaledTime; if (spawnable == null || !spawnable.activeSelf) { switch (appearType) { case AppearType.AppearOnFocusEnter: ShowSpawnable(); break; } } } private void HandleFocusExit() { focusExitTime = Time.unscaledTime; } private Boolean HasVanishDelayElapsed() { return Time.unscaledTime - focusExitTime > vanishDelay; } } }