mixedreality/com.microsoft.mixedreality..../SDK/Features/UX/Scripts/Buttons/ButtonConfigHelper.cs

606 lines
24 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
using Microsoft.MixedReality.Toolkit.Utilities.Editor;
#endif
namespace Microsoft.MixedReality.Toolkit.UI
{
/// <summary>
/// Helper component that gathers the most commonly modified button elements in one place.
/// </summary>
[ExecuteAlways]
[HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/ux-building-blocks/button#how-to-change-the-icon-and-text")]
public partial class ButtonConfigHelper : MonoBehaviour
{
/// <summary>
/// Modifies the main label text.
/// </summary>
public string MainLabelText
{
get { return mainLabelText != null ? mainLabelText.text : null; }
set
{
if (mainLabelText == null)
{
Debug.LogWarning("No main label set in " + name + " - not setting main label text.");
return;
}
mainLabelText.text = value;
}
}
/// <summary>
/// Modifies the 'See it / Say it' label text.
/// </summary>
public string SeeItSayItLabelText
{
get { return seeItSayItLabelText != null ? seeItSayItLabelText.text : null; }
set
{
if (seeItSayItLabelText == null)
{
Debug.LogWarning("No see it / say it label set in " + name + " - not setting see it / say it label text.");
return;
}
seeItSayItLabelText.text = value;
}
}
/// <summary>
/// Turns the see it / say it label object on / off.
/// </summary>
public bool SeeItSayItLabelEnabled
{
get
{
if (seeItSayItLabel == null)
{
return false;
}
return seeItSayItLabel.activeSelf;
}
set
{
if (seeItSayItLabel == null)
{
Debug.LogWarning("No see it / say it label set in " + name + " - not setting see it / say it label enabled.");
return;
}
seeItSayItLabel.SetActive(value);
}
}
/// <summary>
/// Returns the Interactable's OnClick event.
/// </summary>
public UnityEvent OnClick
{
get
{
if (interactable == null)
{
Debug.LogWarning("No interactable set in " + name + " - returning an empty OnClick event.");
return emptyOnClickEvent;
}
return interactable.OnClick;
}
}
/// <summary>
/// Modifies the button's icon rendering style.
/// </summary>
public ButtonIconStyle IconStyle
{
get { return iconStyle; }
set
{
if (iconStyle != value)
{
SetIconStyle(value);
}
}
}
/// <summary>
/// The button's icon set. Note that setting this will not automatically assign an icon from the new set.
/// </summary>
public ButtonIconSet IconSet
{
get => iconSet;
set => iconSet = value;
}
private readonly static UnityEvent emptyOnClickEvent = new UnityEvent();
private readonly static uint defaultIconChar = ButtonIconSet.ConvertCharStringToUInt32("\uEBD2");
private const string defaultIconTextureNameID = "_MainTex";
[SerializeField, Tooltip("Optional main label used by the button.")]
private TextMeshPro mainLabelText = null;
[SerializeField, Tooltip("Optional interactable component used by the button. Used for its OnClick event.")]
private Interactable interactable = null;
[SerializeField, Tooltip("Optional see it / say it object.")]
private GameObject seeItSayItLabel = null;
[SerializeField, Tooltip("Optional see it / say it label used by the button. Should be subsumed under the seeItSayItLabel object."), FormerlySerializedAs("seeItSatItLabelText")]
private TextMeshPro seeItSayItLabelText = null;
[SerializeField, Tooltip("How the button icon should be rendered.")]
private ButtonIconStyle iconStyle = ButtonIconStyle.Quad;
[Header("Font Icon")]
[SerializeField, Tooltip("Optional label used for font icon.")]
private TextMeshPro iconCharLabel = null;
[SerializeField, Tooltip("Optional font used for font icon. This will be set by configuration actions using the icon set.")]
private TMP_FontAsset iconCharFont = null;
[SerializeField, Tooltip("Optional unicode code for font icon. See Text Mesh Pro font asset for available unicode characters. This will be set by configuration actions.")]
private uint iconChar = 0;
[Header("Sprite Icon")]
[SerializeField, Tooltip("Optional sprite renderer used for sprite icon.")]
private SpriteRenderer iconSpriteRenderer = null;
[SerializeField, Tooltip("Optional sprite used for sprite icon. This will be set by configuration actions.")]
private Sprite iconSprite = null;
[Header("Quad Icon")]
[SerializeField, Tooltip("Optional quad renderer used for texture icon.")]
private MeshRenderer iconQuadRenderer = null;
[SerializeField, Tooltip("The texture name ID. Set to " + defaultIconTextureNameID + " by default.")]
private string iconQuadTextureNameID = defaultIconTextureNameID;
[SerializeField, Tooltip("Optional texture used for texture icon. This will be set by configuration actions.")]
private Texture iconQuadTexture = null;
// Disable 'assigned but never used' errors to avoid errors related to editor-only fields.
#pragma warning disable CS0414
[SerializeField, Tooltip("The default material used by quad button icons. Used to detect legacy custom buttons.")]
private Material defaultButtonQuadMaterial = null;
[Header("Icon Set")]
[SerializeField, Tooltip("Optional icon set used to configure icon objects.")]
private ButtonIconSet iconSet = null;
[SerializeField, Tooltip("The default icon set.")]
private ButtonIconSet defaultIconSet = null;
#pragma warning restore CS0414
private MaterialPropertyBlock iconTexturePropertyBlock;
/// <summary>
/// Searches the icon set for a character matching newIconCharName.
/// If no icon set is available, or if no texture with that name is found, no action is taken.
/// </summary>
/// <param name="newIconCharName">Name of the new icon character as defined in the IconSet.</param>
public void SetCharIconByName(string newIconCharName)
{
if (string.IsNullOrEmpty(newIconCharName))
{
Debug.LogError("Icon character name cannot be null.");
return;
}
if (iconSet == null)
{
Debug.LogWarning("No icon set in " + name + " - taking no action..");
return;
}
uint charIcon = 0;
if (!iconSet.TryGetCharIcon(newIconCharName, out charIcon))
{
Debug.LogWarning("Couldn't find icon character with name " + newIconCharName + " in " + name + " - taking no action..");
return;
}
SetCharIcon(charIcon);
}
/// <summary>
/// Searches the icon set for a texture matching newIconTextureName.
/// If no icon set is available, or if no texture with that name is found, no action is taken.
/// </summary>
/// <param name="newIconTextureName">Name of the new icon texture asset.</param>
public void SetQuadIconByName(string newIconTextureName)
{
if (string.IsNullOrEmpty(newIconTextureName))
{
Debug.LogError("Icon texture name cannot be null.");
return;
}
if (iconSet == null)
{
Debug.LogWarning("No icon set in " + name + " - taking no action..");
return;
}
Texture2D quadIcon = null;
if (!iconSet.TryGetQuadIcon(newIconTextureName, out quadIcon))
{
Debug.LogWarning("Couldn't find icon texture with name " + newIconTextureName + " in " + name + " - taking no action..");
return;
}
SetQuadIcon(quadIcon);
}
/// <summary>
/// Searches the icon set for a texture matching newIconSpriteName.
/// If no icon set is available, or if no texture with that name is found, no action is taken.
/// </summary>
/// <param name="newIconTextureName">Name of the new icon texture asset.</param>
public void SetSpriteIconByName(string newIconSpriteName)
{
if (string.IsNullOrEmpty(newIconSpriteName))
{
Debug.LogError("Icon sprite name cannot be null.");
return;
}
if (iconSet == null)
{
Debug.LogWarning("No icon set in " + name + " - taking no action..");
return;
}
Sprite spriteIcon = null;
if (!iconSet.TryGetSpriteIcon(newIconSpriteName, out spriteIcon))
{
Debug.LogWarning("Couldn't find icon sprite with name " + newIconSpriteName + " in " + name + " - taking no action..");
return;
}
SetSpriteIcon(spriteIcon);
}
/// <summary>
/// Sets the character for the button. This automatically sets the button icon style to Char.
/// </summary>
/// <param name="newIconChar">Unicode string for new icon character.</param>
/// <param name="newIconCharFont">Optional TMPro font asset. If null, the existing font asset will be used.</param>
public void SetCharIcon(uint newIconChar, UnityEngine.Object newIconCharFont = null)
{
if (newIconChar <= 0)
{
return;
}
if (newIconCharFont != null && newIconCharFont != iconCharFont)
{
iconCharFont = (TMP_FontAsset)newIconCharFont;
}
if (iconCharLabel == null)
{
Debug.LogWarning("No icon char label in " + name + " - not setting custom icon char.");
return;
}
iconChar = newIconChar;
if (iconCharFont != null)
{
iconCharLabel.font = iconCharFont;
}
uint labelChar = ButtonIconSet.ConvertCharStringToUInt32(iconCharLabel.text);
if (labelChar != iconChar || iconCharLabel.font != iconCharFont)
{
iconCharLabel.text = ButtonIconSet.ConvertUInt32ToUnicodeCharString(newIconChar);
}
SetIconStyle(ButtonIconStyle.Char);
}
/// <summary>
/// Sets the sprite for the button. This automatically sets the button icon style to Sprite.
/// </summary>
public void SetSpriteIcon(Sprite newIconSprite)
{
if (newIconSprite == null)
{
return;
}
if (iconSpriteRenderer == null)
{
Debug.LogWarning("No icon sprite renderer in " + name + " - not setting custom icon sprite.");
return;
}
iconSprite = newIconSprite;
if (iconSpriteRenderer.sprite != iconSprite)
{
iconSpriteRenderer.sprite = newIconSprite;
}
SetIconStyle(ButtonIconStyle.Sprite);
}
/// <summary>
/// Sets the quad texture for the button. This automatically sets the button icon style to Quad.
/// </summary>
public void SetQuadIcon(Texture newIconTexture)
{
if (newIconTexture == null)
{
return;
}
if (iconQuadRenderer == null)
{
Debug.LogWarning("No icon quad renderer in " + name + " - not setting custom icon texture.");
return;
}
iconQuadTexture = newIconTexture;
if (iconTexturePropertyBlock == null)
{
iconTexturePropertyBlock = new MaterialPropertyBlock();
}
iconQuadRenderer.GetPropertyBlock(iconTexturePropertyBlock);
iconTexturePropertyBlock.SetTexture(iconQuadTextureNameID, newIconTexture);
iconQuadRenderer.SetPropertyBlock(iconTexturePropertyBlock);
SetIconStyle(ButtonIconStyle.Quad);
}
/// <summary>
/// Sets the icon style for the button. Relevant components will be turned on / off based on style.
/// </summary>
private void SetIconStyle(ButtonIconStyle newStyle)
{
iconStyle = newStyle;
switch (iconStyle)
{
case ButtonIconStyle.Char:
if (iconCharLabel != null) { iconCharLabel.gameObject.SetActive(true); }
if (iconSpriteRenderer != null) { iconSpriteRenderer.gameObject.SetActive(false); }
if (iconQuadRenderer != null) { iconQuadRenderer.gameObject.SetActive(false); }
break;
case ButtonIconStyle.Sprite:
if (iconCharLabel != null) { iconCharLabel.gameObject.SetActive(false); }
if (iconSpriteRenderer != null) { iconSpriteRenderer.gameObject.SetActive(true); }
if (iconQuadRenderer != null) { iconQuadRenderer.gameObject.SetActive(false); }
break;
case ButtonIconStyle.Quad:
if (iconCharLabel != null) { iconCharLabel.gameObject.SetActive(false); }
if (iconSpriteRenderer != null) { iconSpriteRenderer.gameObject.SetActive(false); }
if (iconQuadRenderer != null) { iconQuadRenderer.gameObject.SetActive(true); }
break;
case ButtonIconStyle.None:
if (iconCharLabel != null) { iconCharLabel.gameObject.SetActive(false); }
if (iconSpriteRenderer != null) { iconSpriteRenderer.gameObject.SetActive(false); }
if (iconQuadRenderer != null) { iconQuadRenderer.gameObject.SetActive(false); }
break;
}
}
/// <summary>
/// Forces the config helper to apply its internal settings.
/// </summary>
public void ForceRefresh()
{
switch (iconStyle)
{
case ButtonIconStyle.Quad:
SetQuadIcon(iconQuadTexture);
break;
case ButtonIconStyle.Char:
SetCharIcon(iconChar, iconCharFont);
break;
case ButtonIconStyle.Sprite:
SetSpriteIcon(iconSprite);
break;
case ButtonIconStyle.None:
SetIconStyle(ButtonIconStyle.None);
break;
}
}
private void OnEnable()
{
#if UNITY_EDITOR
if (EditorCheckForCustomIcon())
{ // If we're using a custom icon, preserve it so it doesn't vanish
EditorPreserveCustomIcon();
}
#else
// Set these to null to avoid build errors.
defaultIconSet = null;
defaultButtonQuadMaterial = null;
#endif
ForceRefresh();
}
#if UNITY_EDITOR
private static readonly string generatedIconSetName = "CustomIconSet";
private static readonly string customIconSetsFolderName = "CustomIconSets";
private static readonly string customIconSetCreatedMessage = "A new icon set has been created to hold your button's custom icons. This icon set will be used by your button's ButtonConfigHelper component. It has been saved to:\n\n{0}";
/// <summary>
/// Returns true if the button is using a custom icon material.
/// </summary>
public bool EditorCheckForCustomIcon()
{
if (iconSet == null || iconQuadRenderer == null || iconStyle != ButtonIconStyle.Quad)
{ // Nothing to do here.
return false;
}
if (iconSet != defaultIconSet)
{ // This button is using a custom icon set, so we can't assume material differences mean it needs an upgrade.
return false;
}
if (iconQuadRenderer.sharedMaterial == defaultButtonQuadMaterial)
{ // This button is using the default material, so it's not a customized button.
return false;
}
string assetPath = AssetDatabase.GetAssetPath(iconQuadRenderer.sharedMaterial);
if (string.IsNullOrEmpty(assetPath))
{ // If the asset path is null, this material instance exists only in memory.
return false;
}
return true;
}
/// <summary>
/// Upgrades a button using a custom icon material.
/// </summary>
public void EditorUpgradeCustomIcon(ButtonIconSet defaultIconSet = null, string customIconsFolder = null, bool hideAlert = false)
{
if (string.IsNullOrEmpty(customIconsFolder))
{
customIconsFolder = MixedRealityToolkitFiles.GetGeneratedFolder;
}
if (defaultIconSet == null)
{
defaultIconSet = this.defaultIconSet;
}
SerializedObject configObject = new SerializedObject(this);
SerializedProperty iconStyleProp = configObject.FindProperty("iconStyle");
SerializedProperty iconSetProp = configObject.FindProperty("iconSet");
SerializedProperty iconQuadTextureProp = configObject.FindProperty("iconQuadTexture");
if (iconQuadRenderer.gameObject.activeSelf && !iconQuadRenderer.enabled)
{ // If the quad renderer is disabled, enable it and disable the quad renderer game object instead.
// Disabling the quad renderer used to be the preferred way to disable icons but it's no longer consistent with our icon style.
iconQuadRenderer.enabled = true;
iconQuadRenderer.gameObject.SetActive(false);
EditorUtility.SetDirty(gameObject);
}
if (!iconQuadRenderer.gameObject.activeSelf && !iconSpriteRenderer.gameObject.activeSelf && !iconCharLabel.gameObject.activeSelf)
{ // If all the icon objects are disabled, set the icon style to none and do nothing else.
iconStyleProp.intValue = (int)ButtonIconStyle.None;
configObject.ApplyModifiedProperties();
EditorUtility.SetDirty(gameObject);
return;
}
string assetPath = AssetDatabase.GetAssetPath(iconQuadRenderer.sharedMaterial);
if (string.IsNullOrEmpty(assetPath))
{ // If the asset path is null, this material instance exists only in memory.
return;
}
Material targetQuadMaterial = iconQuadRenderer.sharedMaterial;
Texture targetQuadIcon = targetQuadMaterial.mainTexture;
if (targetQuadIcon == null)
{ // There is no icon to copy.
return;
}
ButtonIconSet targetIconSet = iconSet;
bool createdIconSet = false;
string generatedIconSetFolder = System.IO.Path.Combine(customIconsFolder, customIconSetsFolderName);
// If this icon set doesn't have our icon in it, we need to either add it or create a new icon set
if (!iconSet.TryGetQuadIcon(targetQuadIcon.name, out Texture2D quadIcon) && iconSet == defaultIconSet)
{ // If we're using the default icon set, we have to create a new set to add the icon
if (!AssetDatabase.IsValidFolder(generatedIconSetFolder))
{ // Create the folder if it doesn't exist
AssetDatabase.CreateFolder(customIconsFolder, customIconSetsFolderName);
}
string generatedIconSetPath = System.IO.Path.Combine(generatedIconSetFolder, generatedIconSetName + ".asset");
targetIconSet = (ButtonIconSet)AssetDatabase.LoadAssetAtPath(generatedIconSetPath, typeof(ButtonIconSet));
if (targetIconSet == null)
{ // If the icon set doesn't already exist, duplicate the default
ScriptableObject duplicateIconSet = Instantiate<ButtonIconSet>(defaultIconSet);
duplicateIconSet.name = generatedIconSetName;
AssetDatabase.CreateAsset(duplicateIconSet, generatedIconSetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
targetIconSet = (ButtonIconSet)AssetDatabase.LoadAssetAtPath(generatedIconSetPath, typeof(ButtonIconSet));
createdIconSet = true;
}
}
bool selectIconSet = false;
if (createdIconSet && !hideAlert)
{
selectIconSet = EditorUtility.DisplayDialog("Custom Icon Set Created", string.Format(customIconSetCreatedMessage, generatedIconSetFolder), "View Asset", "OK");
}
// Set the icon set to the custom generated icon set
iconSetProp.objectReferenceValue = targetIconSet;
// Add the custom icon to the custom set
targetIconSet.EditorAddCustomQuadIcon(targetQuadIcon);
// Reset changes to the quad renderer
iconQuadTextureProp.objectReferenceValue = targetQuadIcon;
configObject.ApplyModifiedProperties();
// If the custom material shader is different from the default material, don't alter the material
if (targetQuadMaterial.shader.name == defaultButtonQuadMaterial.shader.name && PrefabUtility.IsPartOfPrefabInstance(iconQuadRenderer))
{ // If the custom material shader is the same, revert any prefab overrides
SerializedObject iconQuadRendererObject = new SerializedObject(iconQuadRenderer);
SerializedProperty materialsProp = iconQuadRendererObject.FindProperty("m_Materials");
PrefabUtility.RevertPropertyOverride(materialsProp, InteractionMode.AutomatedAction);
}
EditorUtility.SetDirty(gameObject);
ForceRefresh();
if (selectIconSet)
{
Selection.activeObject = targetIconSet;
EditorGUIUtility.PingObject(targetIconSet);
}
}
private void EditorPreserveCustomIcon()
{
SerializedObject configObject = new SerializedObject(this);
SerializedProperty iconQuadTextureProp = configObject.FindProperty("iconQuadTexture");
iconQuadTextureProp.objectReferenceValue = iconQuadRenderer.sharedMaterial.mainTexture;
configObject.ApplyModifiedProperties();
}
public void EditorSetDefaultIconSet(ButtonIconSet iconSet)
{
defaultIconSet = iconSet;
}
public void EditorSetDefaultQuadMaterial(Material mat)
{
defaultButtonQuadMaterial = mat;
}
public void EditorSetIconQuadRenderer(MeshRenderer renderer)
{
iconQuadRenderer = renderer;
}
#endif
}
}