chore: initial commit
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Units.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A <see cref="PropertyDrawer"/> for fields with an <see cref="AnimationSpeedAttributeDrawer"/>
|
||||
/// which displays them using an 'x' suffix.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/AnimationSpeedAttributeDrawer
|
||||
[CustomPropertyDrawer(typeof(AnimationSpeedAttribute), true)]
|
||||
public class AnimationSpeedAttributeDrawer : UnitsAttributeDrawer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override int GetLineCount(SerializedProperty property, GUIContent label)
|
||||
=> 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b7804967cafb14c9bfc15e59c2338e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,281 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 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>
|
||||
/// Should the <see cref="NextDefaultValue"/> and <see cref="NextValueIsOptional"/>
|
||||
/// be used for the next field to be drawn?
|
||||
/// </summary>
|
||||
public static bool HasNextDefaultValue { get; private set; }
|
||||
|
||||
/// <summary>The default value to be used for the next field drawn by this attribute.</summary>
|
||||
public static float NextDefaultValue { get; private set; } = float.NaN;
|
||||
|
||||
/// <summary>The default value to be used for the next field drawn by this attribute.</summary>
|
||||
public static bool NextValueIsOptional { get; private set; }
|
||||
|
||||
/// <summary>Sets the <see cref="NextDefaultValue"/> and <see cref="NextValueIsOptional"/>.</summary>
|
||||
public static void SetNextDefaultValue(float defaultValue, bool isOptional)
|
||||
{
|
||||
HasNextDefaultValue = true;
|
||||
NextDefaultValue = defaultValue;
|
||||
NextValueIsOptional = isOptional;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="NextDefaultValue"/>
|
||||
/// and the <see cref="NextValueIsOptional"/> is true if the value is not <see cref="float.NaN"/>.
|
||||
/// </summary>
|
||||
public static void SetNextDefaultValue(float defaultValue)
|
||||
{
|
||||
HasNextDefaultValue = true;
|
||||
NextDefaultValue = defaultValue;
|
||||
NextValueIsOptional = !float.IsNaN(defaultValue);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
Initialize();
|
||||
|
||||
var isOptional = Attribute.IsOptional;
|
||||
var defaultValue = Attribute.DefaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
if (HasNextDefaultValue)
|
||||
{
|
||||
Attribute.IsOptional = NextValueIsOptional;
|
||||
Attribute.DefaultValue = NextDefaultValue;
|
||||
}
|
||||
|
||||
var context = TransitionDrawer.Context;
|
||||
if (context.Transition == null)
|
||||
{
|
||||
value = DoSpecialFloatField(area, label, value, DisplayConverters[Attribute.UnitIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
var length = context.MaximumLength;
|
||||
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)}");
|
||||
return;
|
||||
}
|
||||
|
||||
DoPreviewTimeButton(ref area, ref value, multipliers);
|
||||
|
||||
DoFieldGUI(area, label, ref value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Attribute.IsOptional = isOptional;
|
||||
Attribute.DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
HasNextDefaultValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a04888b1c18805741926a04973166d09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,380 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using Animancer.Editor;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
|
||||
namespace Animancer.Units.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only] A <see cref="PropertyDrawer"/> for fields with a <see cref="UnitsAttribute"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/UnitsAttributeDrawer
|
||||
[CustomPropertyDrawer(typeof(UnitsAttribute), true)]
|
||||
public class UnitsAttributeDrawer : PropertyDrawer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The attribute on the field being drawn.</summary>
|
||||
public UnitsAttribute Attribute { get; private set; }
|
||||
|
||||
/// <summary>The converters used to generate display strings for each of the fields.</summary>
|
||||
public CompactUnitConversionCache[] DisplayConverters { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers the <see cref="Attribute"/> and sets up the <see cref="DisplayConverters"/>.</summary>
|
||||
public void Initialize()
|
||||
=> Initialize(attribute);
|
||||
|
||||
/// <summary>Gathers the <see cref="Attribute"/> and sets up the <see cref="DisplayConverters"/>.</summary>
|
||||
public void Initialize(Attribute attribute)
|
||||
{
|
||||
if (Attribute != null)
|
||||
return;
|
||||
|
||||
Attribute = (UnitsAttribute)attribute;
|
||||
|
||||
var suffixes = Attribute.Suffixes;
|
||||
DisplayConverters = new CompactUnitConversionCache[suffixes.Length];
|
||||
for (int i = 0; i < suffixes.Length; i++)
|
||||
DisplayConverters[i] = new(suffixes[i]);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var lineCount = GetLineCount(property, label);
|
||||
return LineHeight * lineCount + StandardSpacing * (lineCount - 1);
|
||||
}
|
||||
|
||||
/// <summary>Determines how many lines tall the `property` should be.</summary>
|
||||
protected virtual int GetLineCount(SerializedProperty property, GUIContent label)
|
||||
=> EditorGUIUtility.wideMode
|
||||
? 1
|
||||
: 2;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Begins a GUI property block to be ended by <see cref="EndProperty"/>.</summary>
|
||||
protected static void BeginProperty(
|
||||
Rect area,
|
||||
SerializedProperty property,
|
||||
ref GUIContent label,
|
||||
out float value)
|
||||
{
|
||||
label = EditorGUI.BeginProperty(area, label, property);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
value = property.floatValue;
|
||||
}
|
||||
|
||||
/// <summary>Ends a GUI property block started by <see cref="BeginProperty"/>.</summary>
|
||||
protected static void EndProperty(
|
||||
Rect area,
|
||||
SerializedProperty property,
|
||||
ref float value)
|
||||
{
|
||||
if (TryUseClickEvent(area, 2))
|
||||
DefaultValues.SetToDefault(ref value, property);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.floatValue = value;
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws this attribute's fields for the `property`.</summary>
|
||||
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
Initialize();
|
||||
BeginProperty(area, property, ref label, out var value);
|
||||
DoFieldGUI(area, label, ref value);
|
||||
EndProperty(area, property, ref value);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int TextFieldHash = "EditorTextField".GetHashCode();
|
||||
|
||||
/// <summary>Draws this attribute's fields.</summary>
|
||||
public void DoFieldGUI(Rect area, GUIContent label, ref float value)
|
||||
{
|
||||
var isMultiLine = area.height >= LineHeight * 2;
|
||||
area.height = LineHeight;
|
||||
|
||||
DoOptionalBeforeGUI(
|
||||
Attribute.IsOptional,
|
||||
area,
|
||||
out var toggleArea,
|
||||
out var guiWasEnabled,
|
||||
out var previousLabelWidth);
|
||||
|
||||
var hasLabel = label != null && !string.IsNullOrEmpty(label.text);
|
||||
Rect allFieldArea;
|
||||
|
||||
if (isMultiLine)
|
||||
{
|
||||
EditorGUI.LabelField(area, label);
|
||||
label = null;
|
||||
NextVerticalArea(ref area);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
allFieldArea = EditorGUI.IndentedRect(area);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
else if (hasLabel)
|
||||
{
|
||||
var labelXMax = area.x + EditorGUIUtility.labelWidth;
|
||||
allFieldArea = new(labelXMax, area.y, area.xMax - labelXMax, area.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
allFieldArea = area;
|
||||
}
|
||||
|
||||
CountActiveFields(out var count, out var last);
|
||||
|
||||
var currentEvent = Event.current;
|
||||
|
||||
var beforeControlID = GUIUtility.GetControlID(TextFieldHash, FocusType.Passive, area);
|
||||
|
||||
if (float.IsNaN(value) &&
|
||||
Attribute.DisabledText != null &&
|
||||
currentEvent.type == EventType.Repaint &&
|
||||
!area.Contains(currentEvent.mousePosition) &&
|
||||
!HasKeyboardControl(beforeControlID, beforeControlID + count))
|
||||
{
|
||||
var dragArea = area;
|
||||
dragArea.width = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.AddCursorRect(dragArea, MouseCursor.SlideArrow);
|
||||
|
||||
label ??= GUIContent.none;
|
||||
|
||||
EditorGUI.TextField(area, label, Attribute.DisabledText);
|
||||
|
||||
for (int i = 1; i < count; i++)
|
||||
GUIUtility.GetControlID(TextFieldHash, FocusType.Keyboard, area);
|
||||
}
|
||||
else
|
||||
{
|
||||
var width = (allFieldArea.width - StandardSpacing * (count - 1)) / count;
|
||||
var fieldArea = new Rect(allFieldArea.x, allFieldArea.y, width, allFieldArea.height);
|
||||
|
||||
var displayValue = GetDisplayValue(value, Attribute.DefaultValue);
|
||||
|
||||
// Draw the active fields.
|
||||
for (int i = 0; i < Attribute.Multipliers.Length; i++)
|
||||
{
|
||||
var multiplier = Attribute.Multipliers[i];
|
||||
if (float.IsNaN(multiplier))
|
||||
continue;
|
||||
|
||||
if (hasLabel)
|
||||
{
|
||||
fieldArea.xMin = area.xMin;
|
||||
}
|
||||
else if (i < last)
|
||||
{
|
||||
fieldArea.width = width;
|
||||
fieldArea.xMax = AnimancerUtilities.Round(fieldArea.xMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldArea.xMax = area.xMax;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var fieldValue = displayValue * multiplier;
|
||||
fieldValue = DoSpecialFloatField(fieldArea, label, fieldValue, DisplayConverters[i]);
|
||||
label = null;
|
||||
hasLabel = false;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
value = fieldValue / multiplier;
|
||||
|
||||
fieldArea.x += fieldArea.width + StandardSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
DoOptionalAfterGUI(
|
||||
Attribute.IsOptional,
|
||||
toggleArea,
|
||||
ref value,
|
||||
Attribute.DefaultValue,
|
||||
guiWasEnabled,
|
||||
previousLabelWidth);
|
||||
|
||||
Validate.ValueRule(ref value, Attribute.Rule);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Counts the number of active <see cref="UnitsAttribute.Multipliers"/>.</summary>
|
||||
private void CountActiveFields(out int count, out int last)
|
||||
{
|
||||
count = 0;
|
||||
last = 0;
|
||||
|
||||
for (int i = 0; i < Attribute.Multipliers.Length; i++)
|
||||
{
|
||||
if (!float.IsNaN(Attribute.Multipliers[i]))
|
||||
{
|
||||
count++;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is the <see cref="GUIUtility.keyboardControl"/> in the specified range (inclusive)?</summary>
|
||||
private static bool HasKeyboardControl(int minControlID, int maxControlID)
|
||||
{
|
||||
var keyboardControl = GUIUtility.keyboardControl;
|
||||
return keyboardControl >= minControlID && keyboardControl <= maxControlID;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Draws a <see cref="EditorGUI.FloatField(Rect, GUIContent, float)"/> with an alternate string
|
||||
/// when it's not selected (for example, "1" might display as "1s" to indicate "seconds").
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method treats most <see cref="EventType"/>s normally,
|
||||
/// but for <see cref="EventType.Repaint"/> it instead draws a text field with the converted string.
|
||||
/// </remarks>
|
||||
public static float DoSpecialFloatField(
|
||||
Rect area,
|
||||
GUIContent label,
|
||||
float value,
|
||||
CompactUnitConversionCache toString)
|
||||
{
|
||||
if (label != null && !string.IsNullOrEmpty(label.text))
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return EditorGUI.FloatField(area, label, value);
|
||||
|
||||
var dragArea = new Rect(area.x, area.y, EditorGUIUtility.labelWidth, area.height);
|
||||
EditorGUIUtility.AddCursorRect(dragArea, MouseCursor.SlideArrow);
|
||||
|
||||
var text = toString.Convert(value, area.width - EditorGUIUtility.labelWidth);
|
||||
EditorGUI.TextField(area, label, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
var indentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
value = EditorGUI.FloatField(area, value);
|
||||
else
|
||||
EditorGUI.TextField(area, toString.Convert(value, area.width));
|
||||
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Prepares the details for drawing a toggle to set the field to <see cref="float.NaN"/>.</summary>
|
||||
/// <remarks>Call this before drawing the field then call <see cref="DoOptionalAfterGUI"/> after it.</remarks>
|
||||
public void DoOptionalBeforeGUI(
|
||||
bool isOptional,
|
||||
Rect area,
|
||||
out Rect toggleArea,
|
||||
out bool guiWasEnabled,
|
||||
out float previousLabelWidth)
|
||||
{
|
||||
toggleArea = area;
|
||||
guiWasEnabled = GUI.enabled;
|
||||
previousLabelWidth = EditorGUIUtility.labelWidth;
|
||||
if (!isOptional)
|
||||
return;
|
||||
|
||||
toggleArea.x += previousLabelWidth;
|
||||
|
||||
toggleArea.width = ToggleWidth;
|
||||
EditorGUIUtility.labelWidth += toggleArea.width;
|
||||
|
||||
EditorGUIUtility.AddCursorRect(toggleArea, MouseCursor.Arrow);
|
||||
|
||||
// We need to draw the toggle after everything else to it goes on top of the label. But we want it to
|
||||
// get priority for input events, so we disable the other controls during those events in its area.
|
||||
var currentEvent = Event.current;
|
||||
if (guiWasEnabled && toggleArea.Contains(currentEvent.mousePosition))
|
||||
{
|
||||
switch (currentEvent.type)
|
||||
{
|
||||
case EventType.Repaint:
|
||||
case EventType.Layout:
|
||||
break;
|
||||
|
||||
default:
|
||||
GUI.enabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a toggle to set the `value` to <see cref="float.NaN"/> when disabled.</summary>
|
||||
public void DoOptionalAfterGUI(
|
||||
bool isOptional,
|
||||
Rect area,
|
||||
ref float value,
|
||||
float defaultValue,
|
||||
bool guiWasEnabled,
|
||||
float previousLabelWidth)
|
||||
{
|
||||
GUI.enabled = guiWasEnabled;
|
||||
EditorGUIUtility.labelWidth = previousLabelWidth;
|
||||
|
||||
if (!isOptional)
|
||||
return;
|
||||
|
||||
area.x += StandardSpacing;
|
||||
|
||||
var wasEnabled = !float.IsNaN(value);
|
||||
|
||||
// Use the EditorGUI method instead to properly handle EditorGUI.showMixedValue.
|
||||
//var isEnabled = GUI.Toggle(area, wasEnabled, GUIContent.none);
|
||||
|
||||
var indentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
var isEnabled = EditorGUI.Toggle(area, wasEnabled);
|
||||
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
|
||||
if (isEnabled != wasEnabled)
|
||||
{
|
||||
value = isEnabled ? defaultValue : float.NaN;
|
||||
Deselect();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the value that should be displayed for a given field.</summary>
|
||||
public static float GetDisplayValue(float value, float defaultValue)
|
||||
=> float.IsNaN(value) ? defaultValue : value;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee62458994789464d9e1c9d6b5801083
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user