// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
using System;
using UnityEngine;
#if UNITY_EDITOR
using Animancer.Editor;
using UnityEditor;
using UnityEditorInternal;
#endif
namespace Animancer
{
///
/// https://kybernetik.com.au/animancer/api/Animancer/MixerTransition_2
[Serializable]
#if ! UNITY_EDITOR
[System.Obsolete(Validate.ProOnlyMessage)]
#endif
public abstract class MixerTransition : ManualMixerTransition,
ICopyable>
where TMixer : MixerState
{
/************************************************************************************************************************/
[SerializeField]
private TParameter[] _Thresholds;
/// []
/// The parameter values at which each of the states are used and blended.
///
public ref TParameter[] Thresholds => ref _Thresholds;
/// The name of the serialized backing field of .
public const string ThresholdsField = nameof(_Thresholds);
/************************************************************************************************************************/
[SerializeField]
private TParameter _DefaultParameter;
/// []
/// The initial parameter value to give the mixer when it is first created.
///
public ref TParameter DefaultParameter => ref _DefaultParameter;
/// The name of the serialized backing field of .
public const string DefaultParameterField = nameof(_DefaultParameter);
/************************************************************************************************************************/
///
public override void InitializeState()
{
base.InitializeState();
State.SetThresholds(_Thresholds);
State.Parameter = _DefaultParameter;
}
/************************************************************************************************************************/
///
public virtual void CopyFrom(MixerTransition copyFrom)
{
CopyFrom((ManualMixerTransition)copyFrom);
if (copyFrom == null)
{
_DefaultParameter = default;
_Thresholds = default;
return;
}
_DefaultParameter = copyFrom._DefaultParameter;
AnimancerUtilities.CopyExactArray(copyFrom._Thresholds, ref _Thresholds);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/// [Editor-Only] Draws the Inspector GUI for a .
///
/// Documentation: Transitions
/// and Mixers
///
/// https://kybernetik.com.au/animancer/api/Animancer/MixerTransitionDrawer
///
public class MixerTransitionDrawer : ManualMixerTransition.Drawer
{
/************************************************************************************************************************/
/// The number of horizontal pixels the "Threshold" label occupies.
private readonly float ThresholdWidth;
/************************************************************************************************************************/
private static float _StandardThresholdWidth;
///
/// The number of horizontal pixels the word "Threshold" occupies when drawn with the
/// style.
///
protected static float StandardThresholdWidth
{
get
{
if (_StandardThresholdWidth == 0)
_StandardThresholdWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Threshold");
return _StandardThresholdWidth;
}
}
/************************************************************************************************************************/
///
/// Creates a new using the default .
///
public MixerTransitionDrawer()
: this(StandardThresholdWidth)
{ }
///
/// Creates a new using a custom width for its threshold labels.
///
protected MixerTransitionDrawer(float thresholdWidth)
=> ThresholdWidth = thresholdWidth;
/************************************************************************************************************************/
///
/// The serialized of the
/// .
///
protected static SerializedProperty CurrentThresholds { get; private set; }
/************************************************************************************************************************/
///
protected override void GatherSubProperties(SerializedProperty property)
{
base.GatherSubProperties(property);
CurrentThresholds = property.FindPropertyRelative(MixerTransition2D.ThresholdsField);
if (CurrentAnimations == null ||
CurrentThresholds == null ||
property.hasMultipleDifferentValues)
return;
var count = Math.Max(CurrentAnimations.arraySize, CurrentThresholds.arraySize);
CurrentAnimations.arraySize = count;
CurrentThresholds.arraySize = count;
if (CurrentSpeeds != null &&
CurrentSpeeds.arraySize != 0)
CurrentSpeeds.arraySize = count;
}
/************************************************************************************************************************/
///
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var height = base.GetPropertyHeight(property, label);
if (property.isExpanded)
{
if (CurrentThresholds != null)
{
height -= AnimancerGUI.StandardSpacing +
EditorGUI.GetPropertyHeight(CurrentThresholds, label);
}
}
return height;
}
/************************************************************************************************************************/
///
protected override void DoChildPropertyGUI(ref Rect area, SerializedProperty rootProperty, SerializedProperty property, GUIContent label)
{
if (property.propertyPath.EndsWith($".{MixerTransition2D.ThresholdsField}"))
return;
base.DoChildPropertyGUI(ref area, rootProperty, property, label);
}
/************************************************************************************************************************/
/// Splits the specified `area` into separate sections.
protected void SplitListRect(Rect area, bool isHeader,
out Rect animation, out Rect threshold, out Rect speed, out Rect sync)
{
SplitListRect(area, isHeader, out animation, out speed, out sync);
if (TwoLineMode && !isHeader)
{
threshold = AnimancerGUI.StealFromLeft(ref speed, ThresholdWidth, AnimancerGUI.StandardSpacing);
}
else
{
threshold = animation;
var xMin = threshold.xMin = EditorGUIUtility.labelWidth + AnimancerGUI.IndentSize;
animation.xMax = xMin - AnimancerGUI.StandardSpacing;
}
}
/************************************************************************************************************************/
///
protected override void DoChildListHeaderGUI(Rect area)
{
SplitListRect(area, true, out var animationArea, out var thresholdArea, out var speedArea, out var syncArea);
DoAnimationHeaderGUI(animationArea);
var attribute = AttributeCache.FindAttribute(CurrentThresholds);
var text = attribute != null
? attribute.Label
: "Threshold";
using (ObjectPool.Disposable.AcquireContent(out var label, text,
"The parameter values at which each child state will be fully active"))
DoHeaderDropdownGUI(thresholdArea, CurrentThresholds, label, AddThresholdFunctionsToMenu);
DoSpeedHeaderGUI(speedArea);
DoSyncHeaderGUI(syncArea);
}
/************************************************************************************************************************/
///
protected override void DoElementGUI(Rect area, int index,
SerializedProperty animation, SerializedProperty speed)
{
SplitListRect(area, false,
out var animationArea, out var thresholdArea, out var speedArea, out var syncArea);
DoAnimationField(animationArea, animation);
DoThresholdGUI(thresholdArea, index);
DoSpeedFieldGUI(speedArea, speed, index);
DoSyncToggleGUI(syncArea, index);
}
/************************************************************************************************************************/
/// Draws the GUI of the threshold at the specified `index`.
protected virtual void DoThresholdGUI(Rect area, int index)
{
var threshold = CurrentThresholds.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(area, threshold, GUIContent.none);
}
/************************************************************************************************************************/
///
protected override void OnAddElement(int index)
{
base.OnAddElement(index);
if (CurrentThresholds.arraySize > 0)
CurrentThresholds.InsertArrayElementAtIndex(index);
}
/************************************************************************************************************************/
///
protected override void OnRemoveElement(ReorderableList list)
{
base.OnRemoveElement(list);
Serialization.RemoveArrayElement(CurrentThresholds, list.index);
}
/************************************************************************************************************************/
///
protected override void ResizeList(int size)
{
base.ResizeList(size);
CurrentThresholds.arraySize = size;
}
/************************************************************************************************************************/
///
protected override void OnReorderList(ReorderableList list, int oldIndex, int newIndex)
{
base.OnReorderList(list, oldIndex, newIndex);
CurrentThresholds.MoveArrayElement(oldIndex, newIndex);
}
/************************************************************************************************************************/
/// Adds functions to the `menu` relating to the thresholds.
protected virtual void AddThresholdFunctionsToMenu(GenericMenu menu) { }
/************************************************************************************************************************/
}
#endif
}