mixedreality/com.microsoft.mixedreality..../SDK/Features/UX/Scripts/VisualThemes/ThemeEngines/InteractableThemeBase.cs

267 lines
11 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>
/// Base abstract class for all Theme Engines. Extend to create custom Theme logic
/// </summary>
public abstract class InteractableThemeBase
{
/// <summary>
/// Types of component this Theme Engine will target on the initialized GameObject or related GameObjects
/// </summary>
public Type[] Types { get; protected set; } = Array.Empty<Type>();
/// <summary>
/// Name of Theme Engine
/// </summary>
public string Name { get; protected set; } = "Base Theme";
/// <summary>
/// List of Properties with values per state
/// </summary>
public List<ThemeStateProperty> StateProperties { get; set; } = new List<ThemeStateProperty>();
/// <summary>
/// List of global Theme Engine properties
/// </summary>
public List<ThemeProperty> Properties { get; set; } = new List<ThemeProperty>();
/// <summary>
/// GameObject initialized with this ThemeEngine and being targeted based on state changes
/// </summary>
public GameObject Host { get; set; }
/// <summary>
/// Defines how to ease between values during state changes
/// </summary>
public Easing Ease { get; set; } = new Easing();
/// <summary>
/// True if Theme Engine has been initialized, false otherwise
/// </summary>
public bool Loaded { get; protected set; } = false;
/// <summary>
/// Indicates whether the current Theme engine implementation supports easing between state values
/// </summary>
public virtual bool IsEasingSupported => true;
/// <summary>
/// Indicates whether the current Theme engine implementation supports shader targeting on state properties
/// </summary>
public virtual bool AreShadersSupported => false;
/// <summary>
/// Instruct theme to set value for current property with given index state and at given lerp percentage
/// </summary>
/// <param name="property">property to update value</param>
/// <param name="index">index of state to access array of values</param>
/// <param name="percentage">percentage transition between values</param>
public abstract void SetValue(ThemeStateProperty property, int index, float percentage);
/// <summary>
/// Get the current property value for the provided state property
/// </summary>
/// <param name="property">state property to access</param>
/// <returns>Value currently for given state property</returns>
public abstract ThemePropertyValue GetProperty(ThemeStateProperty property);
/// <summary>
/// Generates the default theme definition configuration for the current theme implementation
/// </summary>
/// <returns>Default ThemeDefinition to initialize with the current theme engine implementation</returns>
public abstract ThemeDefinition GetDefaultThemeDefinition();
/// <summary>
/// Instruct theme to set value for current property with ThemePropertyValue value provided
/// </summary>
/// <param name="property">property to update value</param>
/// <param name="value">Value for theme to set</param>
protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value);
protected Dictionary<ThemeStateProperty, ThemePropertyValue> originalStateValues = new Dictionary<ThemeStateProperty, ThemePropertyValue>();
private bool hasFirstState = false;
private int lastState = -1;
/// <summary>
/// Helper method to instantiate a Theme Engine of provided type. Type must extend InteractableThemeBase
/// </summary>
/// <param name="themeType">Type of ThemeEngine to create</param>
/// <returns>Instance of ThemeEngine of given type</returns>
public static InteractableThemeBase CreateTheme(Type themeType)
{
if (!themeType.IsSubclassOf(typeof(InteractableThemeBase)))
{
Debug.LogError($"Trying to initialize theme of type {themeType} but type does not extend {typeof(InteractableThemeBase)}");
return null;
}
return (InteractableThemeBase)Activator.CreateInstance(themeType);
}
/// <summary>
/// Helper method to create and initialize a Theme Engine for given configuration and targeted GameObject
/// </summary>
/// <param name="definition">Theme configuration with type information and properties to initialize ThemeEngine with</param>
/// <param name="host">GameObject for Theme Engine to target</param>
/// <returns>Instance of Theme Engine initialized</returns>
public static InteractableThemeBase CreateAndInitTheme(ThemeDefinition definition, GameObject host = null)
{
var theme = CreateTheme(definition.ThemeType);
theme.Init(host, definition);
return theme;
}
/// <summary>
/// Initialize current Theme Engine with given configuration and target the provided GameObject
/// </summary>
/// <param name="host">GameObject to target changes against</param>
/// <param name="definition">Configuration information to initialize Theme Engine</param>
public virtual void Init(GameObject host, ThemeDefinition definition)
{
Host = host;
StateProperties = new List<ThemeStateProperty>();
foreach (ThemeStateProperty stateProp in definition.StateProperties)
{
// This is a temporary workaround to support backward compatible themes
// If the current state properties is one we know supports shaders, try to migrate data
// See ThemeStateProperty class for more details
if (ThemeStateProperty.IsShaderPropertyType(stateProp.Type))
{
stateProp.MigrateShaderData();
}
StateProperties.Add(new ThemeStateProperty()
{
Name = stateProp.Name,
Type = stateProp.Type,
Values = stateProp.Values,
Default = stateProp.Default,
TargetShader = stateProp.TargetShader,
ShaderPropertyName = stateProp.ShaderPropertyName,
});
originalStateValues.Add(stateProp, GetProperty(stateProp));
}
Properties = new List<ThemeProperty>();
foreach (ThemeProperty prop in definition.CustomProperties)
{
Properties.Add(new ThemeProperty()
{
Name = prop.Name,
Tooltip = prop.Tooltip,
Type = prop.Type,
Value = prop.Value,
});
}
Debug.Assert(GetDefaultThemeDefinition().StateProperties.Count == StateProperties.Count, $"{Name} state properties inconsistency with default theme definition, consider reserializing.");
Debug.Assert(GetDefaultThemeDefinition().CustomProperties.Count == Properties.Count, $"{Name} custom properties inconsistency with default theme definition, consider reserializing.");
if (definition.Easing != null)
{
Ease = definition.Easing.Copy();
Ease.Stop();
}
Loaded = true;
}
/// <summary>
/// Resets properties on Host GameObject to their original values when Init() was called for this theme engine.
/// Useful for reverting changes done by this theme engine.
/// </summary>
public virtual void Reset()
{
foreach (var originalState in originalStateValues)
{
SetValue(originalState.Key, originalState.Value);
}
}
/// <summary>
/// Update ThemeEngine for given state based on Theme logic. Check, sets, and possibly eases values based on given state
/// </summary>
/// <param name="state">current state to target</param>
/// <param name="force">force update call even if state is not new</param>
public virtual void OnUpdate(int state, bool force = false)
{
if (state != lastState || force)
{
int themePropCount = StateProperties.Count;
for (int i = 0; i < themePropCount; i++)
{
ThemeStateProperty current = StateProperties[i];
current.StartValue = GetProperty(current);
if (hasFirstState || force)
{
Ease.Start();
SetValue(current, state, Ease.GetCurved());
hasFirstState = true;
}
else
{
SetValue(current, state, 1);
if (i >= themePropCount - 1)
{
hasFirstState = true;
}
}
StateProperties[i] = current;
}
lastState = state;
}
else if (Ease.Enabled && Ease.IsPlaying())
{
Ease.OnUpdate();
int themePropCount = StateProperties.Count;
for (int i = 0; i < themePropCount; i++)
{
ThemeStateProperty current = StateProperties[i];
SetValue(current, state, Ease.GetCurved());
}
}
lastState = state;
}
protected float LerpFloat(float s, float e, float t)
{
return (e - s) * t + s;
}
protected int LerpInt(int s, int e, float t)
{
return Mathf.RoundToInt((e - s) * t) + s;
}
protected ThemeProperty GetThemeProperty(int index)
{
if (index >= 0)
{
if (Properties != null && Properties.Count > index)
{
return Properties[index];
}
// If Theme's data does not have desired property, return defaults.
// If index is not in range, throw exception to fail fast.
return GetDefaultThemeDefinition().CustomProperties[index];
}
return null;
}
}
}