235 lines
10 KiB
C#
235 lines
10 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||
|
||
#if UNITY_EDITOR && UNITY_IMGUI
|
||
|
||
using Animancer.Editor;
|
||
using Animancer.Editor.Previews;
|
||
using System;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace Animancer.Units.Editor
|
||
{
|
||
/// <summary>[Editor-Only]
|
||
/// A <see cref="PropertyDrawer"/> for <see cref="float"/> fields with a <see cref="UnitsAttribute"/>
|
||
/// which displays them using 3 fields: Normalized, Seconds, and Frames.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <strong>Documentation:</strong>
|
||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#time-fields">
|
||
/// Time Fields</see>
|
||
/// </remarks>
|
||
/// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/AnimationTimeAttributeDrawer
|
||
[CustomPropertyDrawer(typeof(AnimationTimeAttribute), true)]
|
||
public class AnimationTimeAttributeDrawer : UnitsAttributeDrawer
|
||
{
|
||
/************************************************************************************************************************/
|
||
|
||
/// <summary>The default value to be used for the next field drawn by this attribute.</summary>
|
||
public static float NextDefaultValue { get; set; } = float.NaN;
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
/// <inheritdoc/>
|
||
protected override int GetLineCount(SerializedProperty property, GUIContent label)
|
||
=> EditorGUIUtility.wideMode || TransitionDrawer.Context.Property == null
|
||
? 1
|
||
: 2;
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
/// <inheritdoc/>
|
||
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
|
||
{
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
var nextDefaultValue = NextDefaultValue;
|
||
|
||
BeginProperty(area, property, ref label, out var value);
|
||
OnGUI(area, label, ref value);
|
||
EndProperty(area, property, ref value);
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
var index = (int)AnimationTimeAttribute.Units.Normalized;
|
||
TransitionPreviewWindow.PreviewNormalizedTime =
|
||
GetDisplayValue(value, nextDefaultValue) * Attribute.Multipliers[index];
|
||
}
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
/// <summary>Draws the GUI for this attribute.</summary>
|
||
public void OnGUI(Rect area, GUIContent label, ref float value)
|
||
{
|
||
Initialize();
|
||
|
||
var context = TransitionDrawer.Context;
|
||
if (context.Property == null)
|
||
{
|
||
value = DoSpecialFloatField(area, label, value, DisplayConverters[Attribute.UnitIndex]);
|
||
goto Return;
|
||
}
|
||
|
||
var length = context.MaximumDuration;
|
||
if (length <= 0)
|
||
length = float.NaN;
|
||
|
||
AnimancerUtilities.TryGetFrameRate(context.Transition, out var frameRate);
|
||
|
||
var multipliers = CalculateMultipliers(length, frameRate);
|
||
if (multipliers == null)
|
||
{
|
||
EditorGUI.LabelField(area, label.text, $"Invalid {nameof(Validate)}.{nameof(Validate.Value)}");
|
||
goto Return;
|
||
}
|
||
|
||
DoPreviewTimeButton(ref area, ref value, multipliers);
|
||
|
||
Attribute.IsOptional = !float.IsNaN(NextDefaultValue);
|
||
Attribute.DefaultValue = NextDefaultValue;
|
||
DoFieldGUI(area, label, ref value);
|
||
|
||
Return:
|
||
NextDefaultValue = float.NaN;
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
private float[] CalculateMultipliers(float length, float frameRate)
|
||
{
|
||
switch ((AnimationTimeAttribute.Units)Attribute.UnitIndex)
|
||
{
|
||
case AnimationTimeAttribute.Units.Normalized:
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Normalized] = 1;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Seconds] = length;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Frames] = length * frameRate;
|
||
break;
|
||
|
||
case AnimationTimeAttribute.Units.Seconds:
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Normalized] = 1f / length;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Seconds] = 1;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Frames] = frameRate;
|
||
break;
|
||
|
||
case AnimationTimeAttribute.Units.Frames:
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Normalized] = 1f / length / frameRate;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Seconds] = 1f / frameRate;
|
||
Attribute.Multipliers[(int)AnimationTimeAttribute.Units.Frames] = 1;
|
||
break;
|
||
|
||
default:
|
||
return null;
|
||
}
|
||
|
||
var settings = AnimancerSettingsGroup<AnimationTimeAttributeSettings>.Instance;
|
||
ApplyVisibilitySetting(settings.showNormalized, AnimationTimeAttribute.Units.Normalized);
|
||
ApplyVisibilitySetting(settings.showSeconds, AnimationTimeAttribute.Units.Seconds);
|
||
ApplyVisibilitySetting(settings.showFrames, AnimationTimeAttribute.Units.Frames);
|
||
|
||
void ApplyVisibilitySetting(bool show, AnimationTimeAttribute.Units setting)
|
||
{
|
||
if (show)
|
||
return;
|
||
|
||
var index = (int)setting;
|
||
if (Attribute.UnitIndex != index)
|
||
Attribute.Multipliers[index] = float.NaN;
|
||
}
|
||
|
||
return Attribute.Multipliers;
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
private void DoPreviewTimeButton(
|
||
ref Rect area,
|
||
ref float value,
|
||
float[] multipliers)
|
||
{
|
||
if (!TransitionPreviewWindow.IsPreviewingCurrentProperty())
|
||
return;
|
||
|
||
var previewTime = TransitionPreviewWindow.PreviewNormalizedTime;
|
||
|
||
const string Tooltip =
|
||
"<22> Left Click = preview the current value of this field." +
|
||
"\n<> Right Click = set this field to use the current preview time.";
|
||
|
||
var displayValue = GetDisplayValue(value, NextDefaultValue);
|
||
|
||
var multiplier = multipliers[(int)AnimationTimeAttribute.Units.Normalized];
|
||
displayValue *= multiplier;
|
||
|
||
var isCurrent = Mathf.Approximately(displayValue, previewTime);
|
||
|
||
var buttonArea = area;
|
||
if (TransitionDrawer.DoPreviewButtonGUI(ref buttonArea, isCurrent, Tooltip))
|
||
{
|
||
if (Event.current.button != 1)
|
||
TransitionPreviewWindow.PreviewNormalizedTime = displayValue;
|
||
else
|
||
value = previewTime / multiplier;
|
||
}
|
||
|
||
// Only steal the button area for single line fields.
|
||
if (area.height <= AnimancerGUI.LineHeight)
|
||
area = buttonArea;
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
#region Settings
|
||
/************************************************************************************************************************/
|
||
|
||
/// <summary>[Editor-Only] Options to determine how <see cref="AnimationTimeAttribute"/> displays.</summary>
|
||
/// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/AnimationTimeAttributeSettings
|
||
[Serializable, InternalSerializableType]
|
||
public class AnimationTimeAttributeSettings : AnimancerSettingsGroup
|
||
{
|
||
/************************************************************************************************************************/
|
||
|
||
/// <inheritdoc/>
|
||
public override string DisplayName
|
||
=> "Time Fields";
|
||
|
||
/// <inheritdoc/>
|
||
public override int Index
|
||
=> 5;
|
||
|
||
/************************************************************************************************************************/
|
||
|
||
/// <summary>Should time fields show approximations if the value is too long for the GUI?</summary>
|
||
/// <remarks>This setting is used by <see cref="CompactUnitConversionCache"/>.</remarks>
|
||
[Tooltip("Should time fields show approximations if the value is too long for the GUI?" +
|
||
" For example, '1.111111' could instead show '1.111~'.")]
|
||
public bool showApproximations = true;
|
||
|
||
/// <summary>Should the <see cref="AnimationTimeAttribute.Units.Normalized"/> field be shown?</summary>
|
||
/// <remarks>This setting is ignored for fields which directly store the normalized value.</remarks>
|
||
[Tooltip("Should the " + nameof(AnimationTimeAttribute.Units.Normalized) + " field be shown?")]
|
||
public bool showNormalized = true;
|
||
|
||
/// <summary>Should the <see cref="AnimationTimeAttribute.Units.Seconds"/> field be shown?</summary>
|
||
/// <remarks>This setting is ignored for fields which directly store the seconds value.</remarks>
|
||
[Tooltip("Should the " + nameof(AnimationTimeAttribute.Units.Seconds) + " field be shown?")]
|
||
public bool showSeconds = true;
|
||
|
||
/// <summary>Should the <see cref="AnimationTimeAttribute.Units.Frames"/> field be shown?</summary>
|
||
/// <remarks>This setting is ignored for fields which directly store the frame value.</remarks>
|
||
[Tooltip("Should the " + nameof(AnimationTimeAttribute.Units.Frames) + " field be shown?")]
|
||
public bool showFrames = true;
|
||
|
||
/************************************************************************************************************************/
|
||
}
|
||
|
||
/************************************************************************************************************************/
|
||
#endregion
|
||
/************************************************************************************************************************/
|
||
}
|
||
|
||
#endif
|
||
|