mixedreality/com.microsoft.mixedreality..../SDK/Features/UX/Scripts/AppBar/AppBar.cs

637 lines
19 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.UI
{
/// <summary>
/// Logic for the App Bar. Generates buttons, manages states.
/// </summary>
[AddComponentMenu("Scripts/MRTK/SDK/AppBar")]
public class AppBar : MonoBehaviour
{
private const float backgroundBarMoveSpeed = 5;
#region Enum Definitions
[Flags]
public enum ButtonTypeEnum
{
Custom = 0,
Remove = 1,
Adjust = 2,
Hide = 4,
Show = 8,
Done = 16
}
public enum AppBarDisplayTypeEnum
{
Manipulation,
Standalone
}
public enum AppBarStateEnum
{
Default,
Manipulation,
Hidden
}
#endregion
#region Private Serialized Fields with Public Properties
[Header("Target Bounding Box")]
[Tooltip("Object the app bar is controlling - This object must implement the IBoundsTargetProvider.")]
[SerializeField]
private MonoBehaviour boundingBox = null;
/// <summary>
/// Object the app bar is controlling - This object must implement the IBoundsTargetProvider.
/// </summary>
public MonoBehaviour Target
{
get { return boundingBox; }
set { boundingBox = value; }
}
[Tooltip("The parent game object for the renderable objects in the app bar")]
[SerializeField]
private GameObject baseRenderer = null;
/// <summary>
/// The parent game object for the renderable objects in the AppBar
/// </summary>
public GameObject BaseRenderer
{
get => baseRenderer;
set => baseRenderer = value;
}
[Tooltip("The parent transform for the button collection")]
[SerializeField]
private Transform buttonParent = null;
/// <summary>
/// The parent transform for the button collection
/// </summary>
public Transform ButtonParent
{
get => buttonParent;
set => buttonParent = value;
}
[Tooltip("The background gameobject, scales to fill area behind buttons")]
[SerializeField]
private GameObject backgroundBar = null;
/// <summary>
/// The background gameobject, scales to fill area behind buttons
/// </summary>
public GameObject BackgroundBar
{
get => backgroundBar;
set => backgroundBar = value;
}
[Header("States")]
[Tooltip("The AppBar's display type; default is Manipulation")]
[SerializeField]
private AppBarDisplayTypeEnum displayType = AppBarDisplayTypeEnum.Manipulation;
/// <summary>
/// The AppBar's display type; default is Manipulation
/// </summary>
public AppBarDisplayTypeEnum DisplayType
{
get { return displayType; }
set { displayType = value; }
}
[Tooltip("The AppBar's current state")]
[SerializeField]
private AppBarStateEnum state = AppBarStateEnum.Default;
/// <summary>
/// The AppBar's current state
/// </summary>
public AppBarStateEnum State
{
get { return state; }
set { state = value; }
}
[Header("Default Button Options")]
[Tooltip("Should the AppBar have a remove button")]
[SerializeField]
private bool useRemove = true;
/// <summary>
/// Should the AppBar have a remove button
/// </summary>
public bool UseRemove
{
get { return useRemove; }
set { useRemove = value; }
}
[Tooltip("Should the AppBar have an adjust button")]
[SerializeField]
private bool useAdjust = true;
/// <summary>
/// Should the AppBar have an adjust button
/// </summary>
public bool UseAdjust
{
get { return useAdjust; }
set { useAdjust = value; }
}
[Tooltip("Should the AppBar have a hide button")]
[SerializeField]
private bool useHide = true;
/// <summary>
/// Should the AppBar have a hide button
/// </summary>
public bool UseHide
{
get { return useHide; }
set { useHide = value; }
}
[Header("Default Button Icons")]
[Tooltip("The adjust button texture")]
[SerializeField]
private Texture adjustIcon = null;
/// <summary>
/// The adjust button texture
/// </summary>
public Texture AdjustIcon
{
get => adjustIcon;
set => adjustIcon = value;
}
[Tooltip("The done button texture")]
[SerializeField]
private Texture doneIcon = null;
/// <summary>
/// The done button texture
/// </summary>
public Texture DoneIcon
{
get => doneIcon;
set => doneIcon = value;
}
[Tooltip("The hide button texture")]
[SerializeField]
private Texture hideIcon = null;
/// <summary>
/// The hide button texture
/// </summary>
public Texture HideIcon
{
get => hideIcon;
set => hideIcon = value;
}
[Tooltip("The Remove button texture")]
[SerializeField]
private Texture removeIcon = null;
/// <summary>
/// The remove button texture
/// </summary>
public Texture RemoveIcon
{
get => removeIcon;
set => removeIcon = value;
}
[Tooltip("The show button texture")]
[SerializeField]
private Texture showIcon = null;
/// <summary>
/// The show button texture
/// </summary>
public Texture ShowIcon
{
get => showIcon;
set => showIcon = value;
}
[Header("Scale & Position Options")]
[SerializeField]
[Tooltip("Uses an alternate follow style that works better for very oblong objects.")]
private bool useTightFollow = false;
/// <summary>
/// Uses an alternate follow style that works better for very oblong objects
/// </summary>
public bool UseTightFollow
{
get { return useTightFollow; }
set { useTightFollow = value; }
}
[SerializeField]
[Tooltip("Where to display the app bar on the y axis. This can be set to negative values to force the app bar to appear below the object.")]
private float hoverOffsetYScale = 0.25f;
/// <summary>
/// Where to display the app bar on the y axis
/// This can be set to negative values
/// to force the app bar to appear below the object
/// </summary>
public float HoverOffsetYScale
{
get { return hoverOffsetYScale; }
set { hoverOffsetYScale = value; }
}
[SerializeField]
[Tooltip("Pushes the app bar away from the object.")]
private float hoverOffsetZ = 0f;
/// <summary>
/// Pushes the app bar away from the object
/// </summary>
public float HoverOffsetZ
{
get { return hoverOffsetZ; }
set { hoverOffsetZ = value; }
}
[Tooltip("The button width for each button")]
[SerializeField]
private float buttonWidth = 0.032f;
/// <summary>
/// The button width for each button
/// </summary>
public float ButtonWidth
{
get => buttonWidth;
set => buttonWidth = value;
}
[Tooltip("The button depth for each button")]
[SerializeField]
private float buttonDepth = 0.016f;
/// <summary>
/// The button depth for each button
/// </summary>
public float ButtonDepth
{
get => buttonDepth;
set => buttonDepth = value;
}
#endregion
private List<AppBarButton> buttons = new List<AppBarButton>();
private Vector3 targetBarSize = Vector3.one;
private float lastTimeTapped = 0f;
private float coolDownTime = 0.5f;
private BoundingBoxHelper helper = new BoundingBoxHelper();
private List<Vector3> boundsPoints = new List<Vector3>();
#region MonoBehaviour Functions
private void OnEnable()
{
InitializeButtons();
}
private void LateUpdate()
{
UpdateAppBar();
}
#endregion
public void Reset()
{
State = AppBarStateEnum.Default;
FollowTargetObject(false);
lastTimeTapped = Time.time + coolDownTime;
}
public void OnButtonPressed(AppBarButton button)
{
if (Time.time < lastTimeTapped + coolDownTime)
return;
lastTimeTapped = Time.time;
switch (button.ButtonType)
{
case ButtonTypeEnum.Remove:
OnClickRemove();
break;
case ButtonTypeEnum.Adjust:
State = AppBarStateEnum.Manipulation;
break;
case ButtonTypeEnum.Hide:
State = AppBarStateEnum.Hidden;
break;
case ButtonTypeEnum.Show:
State = AppBarStateEnum.Default;
break;
case ButtonTypeEnum.Done:
State = AppBarStateEnum.Default;
break;
default:
break;
}
}
protected virtual void OnClickRemove()
{
// Set the app bar and bounding box to inactive
if (Target is IBoundsTargetProvider boundsProvider && !boundsProvider.IsNull())
{
boundsProvider.Target.SetActive(false);
}
Target.gameObject.SetActive(false);
gameObject.SetActive(false);
}
private void InitializeButtons()
{
buttons.Clear();
foreach (Transform child in ButtonParent)
{
AppBarButton appBarButton = child.GetComponent<AppBarButton>();
if (appBarButton == null)
throw new Exception("Found a transform without an AppBarButton component under buttonTransforms!");
appBarButton.InitializeButtonContent(this);
// Set to invisible initially if not custom
switch (appBarButton.ButtonType)
{
case ButtonTypeEnum.Custom:
break;
default:
appBarButton.SetVisible(false);
break;
}
buttons.Add(appBarButton);
}
}
private void UpdateAppBar()
{
UpdateButtons();
UpdateTargetObject();
FollowTargetObject(true);
}
private void UpdateButtons()
{
// First just count how many buttons are visible
int activeButtonNum = 0;
for (int i = 0; i < buttons.Count; i++)
{
AppBarButton button = buttons[i];
button.SetVisible(GetButtonVisible(button.ButtonType));
if (!buttons[i].Visible)
{
continue;
}
activeButtonNum++;
}
// Sort the buttons by display order
buttons.Sort(delegate (AppBarButton b1, AppBarButton b2) { return b2.DisplayOrder.CompareTo(b1.DisplayOrder); });
// Use active button number to determine background size and offset
float backgroundBarSize = ButtonWidth * activeButtonNum;
Vector3 positionOffset = Vector3.right * ((backgroundBarSize / 2) - (ButtonWidth / 2));
// Go through them again, setting active as
activeButtonNum = 0;
for (int i = 0; i < buttons.Count; i++)
{
// Set the sibling index and target position so the button will behave predictably when set visible
buttons[i].transform.SetSiblingIndex(i);
buttons[i].SetTargetPosition((Vector3.left * ButtonWidth * activeButtonNum) + positionOffset);
if (!buttons[i].Visible)
continue;
activeButtonNum++;
}
targetBarSize.x = backgroundBarSize;
BackgroundBar.transform.localScale = Vector3.Lerp(BackgroundBar.transform.localScale, targetBarSize, Time.deltaTime * backgroundBarMoveSpeed);
BackgroundBar.transform.localPosition = Vector3.forward * ButtonDepth / 2;
}
private void UpdateTargetObject()
{
if (!(Target is IBoundsTargetProvider boundsProvider) || boundsProvider.IsNull() || boundsProvider.Target == null)
{
bool isDisplayTypeNotManipulation = DisplayType != AppBarDisplayTypeEnum.Manipulation;
if (BaseRenderer.activeSelf != isDisplayTypeNotManipulation)
{
BaseRenderer.SetActive(isDisplayTypeNotManipulation);
}
return;
}
// Target can't be updated in editor mode
if (!Application.isPlaying)
return;
if (boundsProvider == null)
return;
switch (State)
{
case AppBarStateEnum.Manipulation:
boundsProvider.Active = true;
break;
default:
boundsProvider.Active = false;
break;
}
}
private void FollowTargetObject(bool smooth)
{
if (!(Target is IBoundsTargetProvider boundsProvider) || boundsProvider.IsNull())
{
return;
}
// Calculate the best follow position
Vector3 finalPosition = Vector3.zero;
Vector3 headPosition = CameraCache.Main.transform.position;
boundsPoints.Clear();
helper.UpdateNonAABoundsCornerPositions(boundsProvider.TargetBounds, boundsPoints);
int followingFaceIndex = helper.GetIndexOfForwardFace(headPosition);
Vector3 faceNormal = helper.GetFaceNormal(followingFaceIndex);
// Finalize the new position
finalPosition = helper.GetFaceBottomCentroid(followingFaceIndex) + (faceNormal * HoverOffsetZ);
// Follow our bounding box
transform.position = smooth ? Vector3.Lerp(transform.position, finalPosition, Time.deltaTime * backgroundBarMoveSpeed) : finalPosition;
// Rotate on the y axis
Vector3 direction = (boundsProvider.TargetBounds.bounds.center - finalPosition).normalized;
if (direction != Vector3.zero)
{
Vector3 eulerAngles = Quaternion.LookRotation(direction, Vector3.up).eulerAngles;
eulerAngles.x = 0f;
eulerAngles.z = 0f;
transform.eulerAngles = eulerAngles;
}
else
{
transform.eulerAngles = Vector3.zero;
}
}
private bool GetButtonVisible(ButtonTypeEnum buttonType)
{
// Set visibility based on button type / options
switch (buttonType)
{
default:
break;
case ButtonTypeEnum.Remove:
if (!UseRemove)
return false;
break;
case ButtonTypeEnum.Hide:
if (!UseHide)
return false;
break;
case ButtonTypeEnum.Adjust:
if (!UseAdjust)
return false;
break;
}
switch (State)
{
case AppBarStateEnum.Default:
default:
switch (buttonType)
{
// Show hide, adjust, remove buttons
// The rest are hidden
case ButtonTypeEnum.Hide:
case ButtonTypeEnum.Remove:
case ButtonTypeEnum.Adjust:
case ButtonTypeEnum.Custom:
return true;
default:
return false;
}
case AppBarStateEnum.Hidden:
switch (buttonType)
{
// Show the show button
// The rest are hidden
case ButtonTypeEnum.Show:
return true;
default:
return false;
}
case AppBarStateEnum.Manipulation:
switch (buttonType)
{
// Show done button
// The rest are hidden
case ButtonTypeEnum.Done:
return true;
default:
return false;
}
}
}
public void GetButtonTextAndIconFromType(ButtonTypeEnum type, out string buttonText, out Texture buttonIcon, out int displayOrder)
{
switch (type)
{
case ButtonTypeEnum.Show:
buttonText = "Show";
buttonIcon = ShowIcon;
displayOrder = 0;
break;
case ButtonTypeEnum.Hide:
buttonText = "Hide";
buttonIcon = HideIcon;
displayOrder = 1;
break;
case ButtonTypeEnum.Adjust:
buttonText = "Adjust";
buttonIcon = AdjustIcon;
displayOrder = 2;
break;
case ButtonTypeEnum.Remove:
buttonText = "Remove";
buttonIcon = RemoveIcon;
displayOrder = 3;
break;
case ButtonTypeEnum.Done:
buttonText = "Done";
buttonIcon = DoneIcon;
displayOrder = 4;
break;
default:
throw new ArgumentOutOfRangeException("type", type, null);
}
}
}
}