chore: initial commit
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A dummy object for tracking the selection within the <see cref="TransitionLibraryWindow"/>
|
||||
/// and showing its details in the Inspector.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelection
|
||||
[AnimancerHelpUrl(typeof(TransitionLibrarySelection))]
|
||||
public class TransitionLibrarySelection : ScriptableObject
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Types of objects can be selected.</summary>
|
||||
public enum SelectionType
|
||||
{
|
||||
/// <summary>Nothing selected.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>The main library.</summary>
|
||||
Library,
|
||||
|
||||
/// <summary>A from-transition.</summary>
|
||||
FromTransition,
|
||||
|
||||
/// <summary>A to-transition.</summary>
|
||||
ToTransition,
|
||||
|
||||
/// <summary>A modifier for a particular from-to transition combination.</summary>
|
||||
Modifier,
|
||||
|
||||
/// <summary>A <see cref="TransitionGroup"/>.</summary>
|
||||
Group,
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private TransitionLibraryWindow _Window;
|
||||
[SerializeField] private SelectionType _Type;
|
||||
[SerializeField] private int _FromIndex = -1;
|
||||
[SerializeField] private int _ToIndex = -1;
|
||||
[SerializeField] private int _Version;
|
||||
|
||||
/// <summary>The window this selection is associated with.</summary>
|
||||
public TransitionLibraryWindow Window
|
||||
=> _Window;
|
||||
|
||||
/// <summary>The type of selected object.</summary>
|
||||
public SelectionType Type
|
||||
=> _Type;
|
||||
|
||||
/// <summary>The index of the <see cref="FromTransition"/>.</summary>
|
||||
public int FromIndex
|
||||
=> _FromIndex;
|
||||
|
||||
/// <summary>The index of the <see cref="ToTransition"/>.</summary>
|
||||
public int ToIndex
|
||||
=> _ToIndex;
|
||||
|
||||
/// <summary>The number of times this selection has been changed.</summary>
|
||||
public int Version
|
||||
=> _Version;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The transition the current selection is coming from.</summary>
|
||||
public TransitionAssetBase FromTransition { get; private set; }
|
||||
|
||||
/// <summary>The transition the current selection is going to.</summary>
|
||||
public TransitionAssetBase ToTransition { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="ITransition.FadeDuration"/> of the current selection.</summary>
|
||||
public float FadeDuration { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="ITransition.NormalizedStartTime"/> of the current selection.</summary>
|
||||
public float NormalizedStartTime { get; private set; }
|
||||
|
||||
/// <summary>Does the current selection have a modified <see cref="FadeDuration"/>?</summary>
|
||||
public bool HasModifier { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private object _Selected;
|
||||
|
||||
/// <summary>The currently selected object.</summary>
|
||||
public object Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
Validate();
|
||||
return _Selected;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Deselects the current object if it isn't valid.</summary>
|
||||
public bool Validate()
|
||||
{
|
||||
if (IsValid())
|
||||
return true;
|
||||
|
||||
Deselect();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Is the current selection valid?</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
if (this == null ||
|
||||
_Window == null ||
|
||||
Selection.activeObject != this)
|
||||
return false;
|
||||
|
||||
var library = _Window.SourceObject;
|
||||
if (library == null)
|
||||
return false;
|
||||
|
||||
FromTransition = null;
|
||||
ToTransition = null;
|
||||
FadeDuration = float.NaN;
|
||||
NormalizedStartTime = float.NaN;
|
||||
HasModifier = false;
|
||||
|
||||
switch (_Type)
|
||||
{
|
||||
case SelectionType.Library:
|
||||
name = "Transition Library";
|
||||
_Selected = library;
|
||||
return library != null;
|
||||
|
||||
case SelectionType.FromTransition:
|
||||
name = "From Transition";
|
||||
if (!_Window.Data.Transitions.TryGet(_FromIndex, out var transition))
|
||||
return false;
|
||||
|
||||
FromTransition = transition;
|
||||
FadeDuration = transition.TryGetFadeDuration();
|
||||
NormalizedStartTime = transition.TryGetNormalizedStartTime();
|
||||
_Selected = transition;
|
||||
return true;
|
||||
|
||||
case SelectionType.ToTransition:
|
||||
name = "To Transition";
|
||||
if (!_Window.Data.Transitions.TryGet(_ToIndex, out transition))
|
||||
return false;
|
||||
|
||||
ToTransition = transition;
|
||||
FadeDuration = transition.TryGetFadeDuration();
|
||||
NormalizedStartTime = transition.TryGetNormalizedStartTime();
|
||||
_Selected = transition;
|
||||
return true;
|
||||
|
||||
case SelectionType.Modifier:
|
||||
name = "Transition Modifier";
|
||||
|
||||
var hasTransitions = _Window.Data.TryGetTransition(_FromIndex, out transition);
|
||||
FromTransition = transition;
|
||||
|
||||
hasTransitions |= _Window.Data.TryGetTransition(_ToIndex, out transition);
|
||||
ToTransition = transition;
|
||||
|
||||
if (_Window.Data.TryGetModifier(_FromIndex, _ToIndex, out var modifier))
|
||||
{
|
||||
HasModifier = true;
|
||||
}
|
||||
else if (!hasTransitions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FadeDuration = modifier.FadeDuration;
|
||||
NormalizedStartTime = modifier.NormalizedStartTime;
|
||||
_Selected = modifier;
|
||||
return true;
|
||||
|
||||
case SelectionType.Group:
|
||||
name = "Transition Group";
|
||||
return _Selected is TransitionGroup;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the <see cref="Selected"/> object.</summary>
|
||||
/// <remarks>
|
||||
/// We can't simply set the <see cref="Selection.activeObject"/>
|
||||
/// because it might not be a <see cref="UnityEngine.Object"/>
|
||||
/// and if it is then we don't want the Project window to move to it.
|
||||
/// <para></para>
|
||||
/// So instead, we select this dummy object and <see cref="TransitionLibrarySelectionEditor"/>
|
||||
/// draws a custom Inspector for the target object.
|
||||
/// </remarks>
|
||||
public void Select(
|
||||
TransitionLibraryWindow window,
|
||||
object select,
|
||||
int index,
|
||||
SelectionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SelectionType.Library:
|
||||
_FromIndex = -1;
|
||||
_ToIndex = -1;
|
||||
break;
|
||||
|
||||
case SelectionType.FromTransition:
|
||||
_FromIndex = index;
|
||||
_ToIndex = -1;
|
||||
break;
|
||||
|
||||
case SelectionType.ToTransition:
|
||||
_FromIndex = -1;
|
||||
_ToIndex = index;
|
||||
break;
|
||||
|
||||
case SelectionType.Modifier:
|
||||
if (select is TransitionModifierDefinition modifier)
|
||||
{
|
||||
_FromIndex = modifier.FromIndex;
|
||||
_ToIndex = modifier.ToIndex;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
case SelectionType.Group:
|
||||
if (select is TransitionGroup group)
|
||||
{
|
||||
_FromIndex = index;
|
||||
_ToIndex = index;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
Deselect();
|
||||
throw new ArgumentException($"Unhandled {nameof(SelectionType)}", nameof(type));
|
||||
}
|
||||
|
||||
_Window = window;
|
||||
_Type = type;
|
||||
_Selected = select;
|
||||
_Version++;
|
||||
|
||||
Selection.activeObject = this;
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears the <see cref="Selected"/> object.</summary>
|
||||
public void Deselect()
|
||||
{
|
||||
_Window = null;
|
||||
_Type = default;
|
||||
_FromIndex = -1;
|
||||
_ToIndex = -1;
|
||||
_Selected = null;
|
||||
_Version++;
|
||||
|
||||
if (Selection.activeObject == this)
|
||||
Selection.activeObject = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles selection changes.</summary>
|
||||
public void OnSelectionChange()
|
||||
{
|
||||
if (Selection.activeObject == this)
|
||||
return;
|
||||
|
||||
Deselect();
|
||||
|
||||
if (_Window != null)
|
||||
_Window.Repaint();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Selects this object if it contains a valid selection.</summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (Selected != null)
|
||||
Selection.activeObject = this;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d5cccea07064a74a91890855fd2aaa4
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,273 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A custom Inspector for <see cref="TransitionLibrarySelection"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionEditor
|
||||
[CustomEditor(typeof(TransitionLibrarySelection), true)]
|
||||
public class TransitionLibrarySelectionEditor : UnityEditor.Editor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Casts the <see cref="UnityEditor.Editor.target"/>.</summary>
|
||||
public TransitionLibrarySelection Target
|
||||
=> target as TransitionLibrarySelection;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var target = Target;
|
||||
if (target == null || !target.Validate())
|
||||
return;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
switch (target.Type)
|
||||
{
|
||||
case TransitionLibrarySelection.SelectionType.Library:
|
||||
DoNestedEditorGUI(target.Selected as TransitionLibraryAsset, "Transition Library");
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
DoTransitionGUI(target.Selected as TransitionAssetBase);
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
DoModifierGUI(target, (TransitionModifierDefinition)target.Selected);
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.Group:
|
||||
DoGroupGUI(target, (TransitionGroup)target.Selected);
|
||||
break;
|
||||
|
||||
default:
|
||||
target.Deselect();
|
||||
break;
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
target.Window.Repaint();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Nested Editor
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private readonly CachedEditor NestedEditor = new();
|
||||
[NonSerialized] private readonly CachedEditor NestedEditor2 = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the <see cref="UnityEditor.Editor"/> for the `target`.</summary>
|
||||
private void DoNestedEditorGUI<T>(T target, string referenceLabel)
|
||||
where T : Object
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
AnimancerGUI.DoObjectFieldGUI(referenceLabel, target, false);
|
||||
|
||||
var editor = NestedEditor.GetEditor(target);
|
||||
if (editor != null)
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cleans up any nested editors.</summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
NestedEditor.Dispose();
|
||||
NestedEditor2.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Transitions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for the `transition`.</summary>
|
||||
private void DoTransitionGUI(
|
||||
TransitionAssetBase transition)
|
||||
{
|
||||
DoTransitionNameGUI(transition);
|
||||
DoNestedEditorGUI(transition, "Transition Asset");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field for editing the name of the `transition`.</summary>
|
||||
private void DoTransitionNameGUI(
|
||||
TransitionAssetBase transition)
|
||||
{
|
||||
var isSubAsset = AssetDatabase.IsSubAsset(transition);
|
||||
var isMainAsset = !isSubAsset && AssetDatabase.IsMainAsset(transition);
|
||||
var label = isSubAsset
|
||||
? "Sub-Asset Name"
|
||||
: isMainAsset
|
||||
? "File Name"
|
||||
: "Name";
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var name = TransitionModifierTableGUI.GetTransitionName(transition);
|
||||
name = EditorGUILayout.DelayedTextField(label, name);
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && transition != null)
|
||||
{
|
||||
transition.SetName(name);
|
||||
|
||||
if (isSubAsset)
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
else if (isMainAsset)
|
||||
{
|
||||
AssetDatabase.RenameAsset(
|
||||
AssetDatabase.GetAssetPath(transition),
|
||||
name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Modifiers
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly BoolPref
|
||||
IsFromExpanded = new($"{nameof(TransitionLibrarySelectionEditor)}.{nameof(IsFromExpanded)}"),
|
||||
IsToExpanded = new($"{nameof(TransitionLibrarySelectionEditor)}.{nameof(IsToExpanded)}");
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for the `modifier`.</summary>
|
||||
private void DoModifierGUI(
|
||||
TransitionLibrarySelection selection,
|
||||
TransitionModifierDefinition modifier)
|
||||
{
|
||||
var library = selection.Window.Data;
|
||||
DoTransitionField(library, NestedEditor, IsFromExpanded, modifier.FromIndex, "From");
|
||||
DoTransitionField(library, NestedEditor2, IsToExpanded, modifier.ToIndex, "To");
|
||||
|
||||
if (selection.Window.TryGetPage<TransitionLibraryFadeDurationsPage>(out var fadeDurations))
|
||||
{
|
||||
var area = AnimancerGUI.LayoutSingleLineRect();
|
||||
TransitionModifierTableGUI.DoModifierValueGUI(
|
||||
area,
|
||||
selection.Window,
|
||||
fadeDurations,
|
||||
modifier.FromIndex,
|
||||
modifier.ToIndex,
|
||||
"Fade Duration",
|
||||
false);
|
||||
}
|
||||
|
||||
if (selection.Window.TryGetPage<TransitionLibraryStartTimesPage>(out var startTimes))
|
||||
{
|
||||
var area = AnimancerGUI.LayoutSingleLineRect();
|
||||
TransitionModifierTableGUI.DoModifierValueGUI(
|
||||
area,
|
||||
selection.Window,
|
||||
startTimes,
|
||||
modifier.FromIndex,
|
||||
modifier.ToIndex,
|
||||
"Start Time",
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for a transition.</summary>
|
||||
private TransitionAssetBase DoTransitionField(
|
||||
TransitionLibraryDefinition library,
|
||||
CachedEditor cachedEditor,
|
||||
BoolPref isExpanded,
|
||||
int transitionIndex,
|
||||
string label)
|
||||
{
|
||||
library.TryGetTransition(transitionIndex, out var transition);
|
||||
|
||||
var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.After);
|
||||
var labelArea = area;
|
||||
labelArea.width = EditorGUIUtility.labelWidth;
|
||||
|
||||
isExpanded.Value = EditorGUI.Foldout(labelArea, isExpanded, GUIContent.none, true);
|
||||
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
|
||||
AnimancerGUI.DoObjectFieldGUI(area, label, transition, false);
|
||||
|
||||
GUI.enabled = enabled;
|
||||
|
||||
if (isExpanded)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
|
||||
var editor = cachedEditor.GetEditor(transition);
|
||||
editor.OnInspectorGUI();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Groups
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for the `group`.</summary>
|
||||
private void DoGroupGUI(
|
||||
TransitionLibrarySelection selection,
|
||||
TransitionGroup group)
|
||||
{
|
||||
group.Name = EditorGUILayout.TextField("Group Name", group.Name);
|
||||
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
|
||||
EditorGUILayout.LabelField("Transition Count", group.TransitionIndices.Count.ToStringCached());
|
||||
|
||||
var transitions = selection.Window.Data.Transitions;
|
||||
for (int i = 0; i < group.TransitionIndices.Count; i++)
|
||||
{
|
||||
var index = group.TransitionIndices[i];
|
||||
if (!transitions.TryGetObject(index, out var transition))
|
||||
continue;
|
||||
|
||||
EditorGUILayout.ObjectField(
|
||||
$"Transition {i.ToStringCached()}",
|
||||
transition,
|
||||
typeof(TransitionAssetBase),
|
||||
false);
|
||||
}
|
||||
|
||||
GUI.enabled = enabled;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cfcfbc7bdddf4d4fabcf7aa15635df6
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,524 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using Animancer.Editor.Previews;
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only] Custom preview for <see cref="TransitionLibrarySelection"/>.</summary>
|
||||
/// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionPreview
|
||||
[CustomPreview(typeof(TransitionLibrarySelection))]
|
||||
public class TransitionLibrarySelectionPreview : ObjectPreview
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private AnimancerPreviewRenderer _PreviewRenderer;
|
||||
[SerializeField] private TransitionPreviewPlayer _PreviewPlayer;
|
||||
|
||||
[NonSerialized] private TransitionLibrarySelection _Target;
|
||||
[NonSerialized] private int _TargetVersion = -1;
|
||||
|
||||
[NonSerialized] private readonly TransitionLibrarySelectionPreviewSpeed Speed = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize(Object[] targets)
|
||||
{
|
||||
_PreviewRenderer ??= new();
|
||||
_PreviewPlayer ??= new();
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
_Target = targets[0] as TransitionLibrarySelection;
|
||||
if (_Target != null)
|
||||
{
|
||||
_TargetVersion = _Target.Version - 1;
|
||||
if (_Target.Window != null)
|
||||
_PreviewRenderer.PreviewObject.TrySelectBestModel(_Target.Window.Data);
|
||||
CheckTarget();
|
||||
}
|
||||
}
|
||||
|
||||
base.Initialize(targets);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Cleanup()
|
||||
{
|
||||
base.Cleanup();
|
||||
_PreviewPlayer?.Dispose();
|
||||
_PreviewPlayer = null;
|
||||
_PreviewRenderer?.Dispose();
|
||||
_PreviewRenderer = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles changes to the target object.</summary>
|
||||
private void CheckTarget()
|
||||
{
|
||||
if (_TargetVersion == _Target.Version)
|
||||
return;
|
||||
|
||||
_TargetVersion = _Target.Version;
|
||||
_PreviewPlayer.IsPlaying = false;
|
||||
|
||||
switch (_Target.Type)
|
||||
{
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
_PreviewPlayer.FromTransition = _Target.FromTransition;
|
||||
_PreviewPlayer.ToTransition = null;
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
_PreviewPlayer.FromTransition = null;
|
||||
_PreviewPlayer.ToTransition = _Target.ToTransition;
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
_PreviewPlayer.FromTransition = _Target.FromTransition;
|
||||
_PreviewPlayer.ToTransition = _Target.ToTransition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Updates the settings of the <see cref="TransitionPreviewPlayer"/>.</summary>
|
||||
private void UpdatePlayerSettings()
|
||||
{
|
||||
_PreviewPlayer.Graph = _PreviewRenderer.PreviewObject.Graph;
|
||||
|
||||
_PreviewPlayer.FadeDuration = _Target.FadeDuration;
|
||||
_PreviewPlayer.NormalizedStartTime = _Target.NormalizedStartTime;
|
||||
_PreviewPlayer.Speed = Speed.Speed;
|
||||
_PreviewPlayer.RecalculateTimeBounds();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly GUIContent
|
||||
Title = new("Preview");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override GUIContent GetPreviewTitle()
|
||||
=> Title;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasPreviewGUI()
|
||||
=> _Target != null
|
||||
&& _Target.Type switch
|
||||
{
|
||||
TransitionLibrarySelection.SelectionType.FromTransition or
|
||||
TransitionLibrarySelection.SelectionType.ToTransition or
|
||||
TransitionLibrarySelection.SelectionType.Modifier
|
||||
=> true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Header Settings
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _ToolbarButtonStyle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnPreviewSettings()
|
||||
{
|
||||
CheckTarget();
|
||||
|
||||
_ToolbarButtonStyle ??= new(EditorStyles.toolbarButton)
|
||||
{
|
||||
padding = new(),
|
||||
};
|
||||
|
||||
var area = GUILayoutUtility.GetRect(LineHeight * 1.5f, LineHeight);
|
||||
DoPlayPauseToggle(area, _ToolbarButtonStyle);
|
||||
|
||||
area = GUILayoutUtility.GetRect(LineHeight * 2f, LineHeight);
|
||||
Speed.DoToggleGUI(area, _ToolbarButtonStyle);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a toggle to play and pause the preview.</summary>
|
||||
private void DoPlayPauseToggle(Rect area, GUIStyle style)
|
||||
{
|
||||
if (TryUseClickEvent(area, 1) || TryUseClickEvent(area, 2))
|
||||
_PreviewPlayer.CurrentTime = _PreviewPlayer.MinTime;
|
||||
|
||||
_PreviewPlayer.IsPlaying = AnimancerGUI.DoPlayPauseToggle(
|
||||
area,
|
||||
_PreviewPlayer.IsPlaying,
|
||||
style,
|
||||
"Left Click = Play/Pause\nRight Click = Reset Time");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInteractivePreviewGUI(Rect area, GUIStyle background)
|
||||
{
|
||||
if (_Target == null)
|
||||
return;
|
||||
|
||||
CheckTarget();
|
||||
UpdatePlayerSettings();
|
||||
|
||||
DoSettingsGUI(ref area);
|
||||
|
||||
DoTimelineGUI(ref area);
|
||||
|
||||
_PreviewRenderer.DoGUI(area, background);
|
||||
|
||||
AnimancerPreviewObjectGUI.HandleDragAndDrop(area, _PreviewRenderer.PreviewObject);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws settings for modifying the preview.</summary>
|
||||
private void DoSettingsGUI(ref Rect area)
|
||||
{
|
||||
if (!Speed.IsOn)
|
||||
return;
|
||||
|
||||
area.yMin += StandardSpacing;
|
||||
|
||||
Speed.DoSpeedSlider(ref area, EditorStyles.toolbar);
|
||||
|
||||
var preview = _PreviewRenderer.PreviewObject;
|
||||
var height = AnimancerPreviewObjectGUI.CalculateHeight(preview);
|
||||
var settingsArea = StealFromTop(ref area, height, StandardSpacing);
|
||||
settingsArea = settingsArea.Expand(-StandardSpacing, 0);
|
||||
|
||||
GUI.Label(settingsArea, GUIContent.none, EditorStyles.toolbar);
|
||||
|
||||
AnimancerPreviewObjectGUI.DoModelGUI(settingsArea, preview);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Timeline
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the preview timeline.</summary>
|
||||
private void DoTimelineGUI(ref Rect area)
|
||||
{
|
||||
var timelineArea = StealFromTop(ref area, EditorStyles.toolbar.fixedHeight, StandardSpacing);
|
||||
|
||||
EditorGUI.DrawRect(timelineArea, Grey(0.25f, 0.3f));
|
||||
EditorGUI.DrawRect(new(timelineArea.x, timelineArea.yMax - 1, timelineArea.width, 1), Grey(0, 0.5f));
|
||||
|
||||
DoFadeDurationSliderGUI(timelineArea);
|
||||
DoTimeSliderGUI(timelineArea);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int SliderHash = "Slider".GetHashCode();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the fade duration slider.</summary>
|
||||
private void DoFadeDurationSliderGUI(Rect area)
|
||||
{
|
||||
if (!CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
|
||||
return;
|
||||
|
||||
switch (_Target.Type)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
break;
|
||||
}
|
||||
|
||||
var sliderArea = area;
|
||||
sliderArea.width = LineHeight * 0.5f;
|
||||
sliderArea.x = endFadeX - sliderArea.width * 0.5f;
|
||||
|
||||
var control = new GUIControl(sliderArea, SliderHash);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (control.TryUseMouseDown())
|
||||
_PreviewPlayer.IsPlaying = false;
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
control.TryUseMouseUp();
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
{
|
||||
var x = Math.Max(startFadeX, control.Event.mousePosition.x);
|
||||
var normalizedTime = area.InverseLerpUnclampedX(x);
|
||||
var normalizedStartFade = area.InverseLerpUnclampedX(startFadeX);
|
||||
|
||||
_PreviewPlayer.NormalizedTime = normalizedTime;
|
||||
var fadeDuration =
|
||||
_PreviewPlayer.LerpTimeUnclamped(normalizedTime) -
|
||||
_PreviewPlayer.LerpTimeUnclamped(normalizedStartFade);
|
||||
|
||||
var selected = _Target.Selected;
|
||||
if (selected is TransitionModifierDefinition modifier)
|
||||
{
|
||||
_Target.Window.RecordUndo()
|
||||
.SetModifier(modifier.WithFadeDuration(fadeDuration));
|
||||
}
|
||||
else if (selected is TransitionAssetBase transitionAsset)
|
||||
{
|
||||
if (fadeDuration < 0)
|
||||
fadeDuration = 0;
|
||||
|
||||
using var serializedObject = new SerializedObject(transitionAsset);
|
||||
var property = serializedObject.FindProperty(TransitionAssetBase.TransitionField);
|
||||
property = property.FindPropertyRelative("_" + nameof(ITransition.FadeDuration));
|
||||
property.floatValue = fadeDuration;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
_Target.Window.Repaint();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
|
||||
var color = AnimancerStateDrawerColors.FadeLineColor;
|
||||
|
||||
var showCursor = GUIUtility.hotControl == 0 || GUIUtility.hotControl == control.ID;
|
||||
if (showCursor)
|
||||
EditorGUIUtility.AddCursorRect(sliderArea, MouseCursor.ResizeHorizontal);
|
||||
|
||||
if (!showCursor || !sliderArea.Contains(control.Event.mousePosition))
|
||||
color.a *= 0.5f;
|
||||
|
||||
EditorGUI.DrawRect(
|
||||
new(endFadeX, sliderArea.y, 1, sliderArea.height - 1),
|
||||
color);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the preview time slider.</summary>
|
||||
private void DoTimeSliderGUI(Rect area)
|
||||
{
|
||||
var control = new GUIControl(area, SliderHash);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (control.TryUseMouseDown())
|
||||
{
|
||||
_ForceClampTime = true;
|
||||
_DidWrapTime = false;
|
||||
HandleDragTime(area, control.Event);
|
||||
|
||||
_ForceClampTime = control.Event.control;
|
||||
if (!_ForceClampTime)
|
||||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||||
|
||||
_PreviewPlayer.IsPlaying = control.Event.clickCount > 1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (control.TryUseMouseUp())
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
HandleDragTime(area, control.Event);
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
|
||||
BeginTriangles(AnimancerStateDrawerColors.FadeLineColor);
|
||||
|
||||
if (CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
|
||||
{
|
||||
// Fade.
|
||||
DrawLineBatched(
|
||||
new(startFadeX, area.yMin + 1),
|
||||
new(endFadeX, area.yMax - 1),
|
||||
1);
|
||||
|
||||
// To.
|
||||
if (endFadeX < area.xMax)
|
||||
DrawLineBatched(
|
||||
new(endFadeX, area.yMax - 1),
|
||||
new(area.xMax, area.yMax - 1),
|
||||
1);
|
||||
}
|
||||
|
||||
// From.
|
||||
if (area.xMin < startFadeX)
|
||||
DrawLineBatched(
|
||||
new(area.xMin, area.yMin + 1),
|
||||
new(startFadeX, area.yMin + 1),
|
||||
1);
|
||||
|
||||
var color = _PreviewPlayer.IsPlaying
|
||||
? AnimancerStateDrawerColors.PlayingBarColor
|
||||
: AnimancerStateDrawerColors.PausedBarColor;
|
||||
color.a = 1;
|
||||
|
||||
var timeX = area.LerpUnclampedX(_PreviewPlayer.NormalizedTime);
|
||||
|
||||
GL.Color(color);
|
||||
DrawLineBatched(new(timeX, area.yMin), new(timeX, area.yMax), 2);
|
||||
|
||||
EndTriangles();
|
||||
|
||||
DoTransitionLabels(area);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool _ForceClampTime;
|
||||
private bool _DidWrapTime;
|
||||
|
||||
/// <summary>Draws handles drag events to control the preview time.</summary>
|
||||
private void HandleDragTime(Rect area, Event currentEvent)
|
||||
{
|
||||
if (_ForceClampTime)
|
||||
{
|
||||
_PreviewPlayer.NormalizedTime = area.InverseLerpUnclampedX(currentEvent.mousePosition.x);
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = currentEvent.delta.x;
|
||||
|
||||
var normalizedTime = _PreviewPlayer.NormalizedTime;
|
||||
if (normalizedTime == 0 && !_DidWrapTime && delta > 0)
|
||||
{
|
||||
var x = currentEvent.mousePosition.x;
|
||||
if (area.xMin > x || area.xMax < x)
|
||||
return;
|
||||
}
|
||||
|
||||
normalizedTime += delta / area.width;
|
||||
if (normalizedTime >= 0 || _DidWrapTime)
|
||||
{
|
||||
|
||||
if (normalizedTime > 1)
|
||||
_DidWrapTime = true;
|
||||
|
||||
normalizedTime = AnimancerUtilities.Wrap01(normalizedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedTime = 0;
|
||||
}
|
||||
|
||||
_PreviewPlayer.NormalizedTime = normalizedTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the start and end pixels of the fade.</summary>
|
||||
private bool CalculateFadeBounds(
|
||||
Rect area,
|
||||
out float startFadeX,
|
||||
out float endFadeX)
|
||||
{
|
||||
var fadeDuration = _Target.FadeDuration;
|
||||
if (!float.IsNaN(fadeDuration))
|
||||
{
|
||||
startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
|
||||
|
||||
endFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(fadeDuration));
|
||||
|
||||
if (_Target.FromTransition.IsValid())
|
||||
{
|
||||
if (!_Target.ToTransition.IsValid())
|
||||
{
|
||||
endFadeX -= startFadeX;
|
||||
startFadeX = area.xMin;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_Target.ToTransition.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
|
||||
endFadeX = startFadeX;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws labels for the selected transitions.</summary>
|
||||
private void DoTransitionLabels(Rect area)
|
||||
{
|
||||
area.xMin += 1;
|
||||
area.xMax -= 2;
|
||||
|
||||
var mid = area.width * 0.5f;
|
||||
var leftArea = area;
|
||||
var rightArea = area;
|
||||
|
||||
var fromTransition = _Target.FromTransition;
|
||||
var toTransition = _Target.ToTransition;
|
||||
|
||||
var hasFrom = fromTransition.IsValid();
|
||||
var hasTo = toTransition.IsValid();
|
||||
|
||||
if (hasFrom && hasTo)
|
||||
{
|
||||
leftArea.width = mid - StandardSpacing * 0.5f;
|
||||
|
||||
rightArea.x = area.xMax - leftArea.width;
|
||||
rightArea.width = leftArea.width;
|
||||
}
|
||||
|
||||
if (hasFrom)
|
||||
GUI.Label(leftArea, _Target.FromTransition.GetCachedName());
|
||||
|
||||
if (hasTo)
|
||||
GUI.Label(rightArea, _Target.ToTransition.GetCachedName(), RightLabelStyle);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7de29fe26fe25b540987aef2f28c1ee0
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// <see cref="ToggledSpeedSlider"/> for <see cref="TransitionLibrarySelectionPreview"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionPreviewSpeed
|
||||
public class TransitionLibrarySelectionPreviewSpeed : ToggledSpeedSlider
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private const string
|
||||
SpeedPrefKey = nameof(TransitionLibrarySelectionPreviewSpeed) + "." + nameof(Speed);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionLibrarySelectionPreviewSpeed"/>.</summary>
|
||||
public TransitionLibrarySelectionPreviewSpeed()
|
||||
: base(nameof(TransitionLibrarySelectionPreviewSpeed) + ".Show")
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSetSpeed(float speed)
|
||||
{
|
||||
EditorPrefs.SetFloat(SpeedPrefKey, speed);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool DoToggleGUI(Rect area, GUIStyle style)
|
||||
{
|
||||
if (float.IsNaN(Speed))
|
||||
Speed = EditorPrefs.GetFloat(SpeedPrefKey, 1);
|
||||
|
||||
return base.DoToggleGUI(area, style);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29d5d1e026917374788c9f8258fc1fca
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user