// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// [Editor-Only]
/// An which allows the user to preview animation transitions separately from the rest
/// of the scene in Edit Mode or Play Mode.
///
///
/// Documentation: Previews
///
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionPreviewWindow
///
[HelpURL(Strings.DocsURLs.TransitionPreviews)]
#if UNITY_2020_1_OR_NEWER
[EditorWindowTitle]// Prevent the base SceneView from trying to use this type name to find the icon.
#endif
public partial class TransitionPreviewWindow : SceneView
{
/************************************************************************************************************************/
#region Public API
/************************************************************************************************************************/
private static Texture _Icon;
/// The icon image used by this window.
public static Texture Icon
{
get
{
if (_Icon == null)
{
// Possible icons: "UnityEditor.LookDevView", "SoftlockInline", "ViewToolOrbit", "ClothInspector.ViewValue".
var name = EditorGUIUtility.isProSkin ? "ViewToolOrbit On" : "ViewToolOrbit";
_Icon = AnimancerGUI.LoadIcon(name);
if (_Icon == null)
_Icon = EditorGUIUtility.whiteTexture;
}
return _Icon;
}
}
/************************************************************************************************************************/
///
/// Focusses the or creates one if none exists.
/// Or closes the existing window if it was already previewing the `transitionProperty`.
///
public static void OpenOrClose(SerializedProperty transitionProperty)
{
transitionProperty = transitionProperty.Copy();
EditorApplication.delayCall += () =>
{
if (!IsPreviewing(transitionProperty))
{
// To avoid Unity giving a warning about camera rotation in 2D Mode:
// Set all scene views to not 2D mode and store their previous state.
var sceneViews = SceneView.sceneViews.ToArray();
var was2D = new bool[sceneViews.Length];
for (int i = 0; i < sceneViews.Length; i++)
{
var sceneView = (SceneView)sceneViews[i];
was2D[i] = sceneView.in2DMode;
sceneView.in2DMode = false;
}
GetWindow(typeof(SceneView))
.SetTargetProperty(transitionProperty);
// Then after opening the window immediately return each scene view back to its previous state.
for (int i = 0; i < sceneViews.Length; i++)
{
var sceneView = (SceneView)sceneViews[i];
sceneView.in2DMode = was2D[i];
}
}
else
{
_Instance.Close();
}
};
}
/************************************************************************************************************************/
///
/// The of the current transition. Can only be set if the property
/// being previewed matches the current .
///
public static float PreviewNormalizedTime
{
get => _Instance._Animations.NormalizedTime;
set
{
if (value.IsFinite() &&
IsPreviewingCurrentProperty())
_Instance._Animations.NormalizedTime = value;
}
}
/************************************************************************************************************************/
///
/// Returns the of the current transition if the property being previewed matches
/// the . Otherwise returns null.
///
public static AnimancerState GetCurrentState()
{
if (!IsPreviewingCurrentProperty() ||
_Instance._Scene.Animancer == null)
return null;
_Instance._Scene.Animancer.States.TryGet(Transition, out var state);
return state;
}
/************************************************************************************************************************/
///
/// Is the current being previewed at the moment?
///
public static bool IsPreviewingCurrentProperty()
{
return
TransitionDrawer.Context != null &&
IsPreviewing(TransitionDrawer.Context.Property);
}
/// Is the `property` being previewed at the moment?
public static bool IsPreviewing(SerializedProperty property)
{
return
_Instance != null &&
_Instance._TransitionProperty.IsValid() &&
Serialization.AreSameProperty(property, _Instance._TransitionProperty);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Messages
/************************************************************************************************************************/
private static TransitionPreviewWindow _Instance;
[SerializeField] private Object[] _PreviousSelection;
[SerializeField] private Animations _Animations;
[SerializeField] private Scene _Scene;
/************************************************************************************************************************/
///
public override void OnEnable()
{
_Instance = this;
#if ! UNITY_2020_1_OR_NEWER
// Unity 2019 logs an error message when opening this window.
// This is because the base SceneView has a [EditorWindowTitle] attribute which looks for the icon by
// name, but since it's internal before Unity 2020 we can't replace it to prevent it from doing so.
// Error: Unable to load the icon: 'Animancer.Editor.TransitionPreviewWindow'.
using (BlockAllLogs.Activate())
#endif
{
base.OnEnable();
}
name = "Transition Preview Window";
titleContent = new GUIContent("Transition Preview", Icon);
autoRepaintOnSceneChange = true;
sceneViewState.showSkybox = Settings.ShowSkybox;
sceneLighting = Settings.SceneLighting;
if (_Scene == null)
_Scene = new Scene();
if (_Animations == null)
_Animations = new Animations();
if (_TransitionProperty.IsValid() &&
!CanBePreviewed(_TransitionProperty))
{
DestroyTransitionProperty();
}
_Scene.OnEnable();
Selection.selectionChanged += OnSelectionChanged;
AssemblyReloadEvents.beforeAssemblyReload += DeselectPreviewSceneObjects;
// Re-select next frame.
// This fixes an issue where the Inspector header displays differently after a domain reload.
if (Selection.activeObject == this)
{
Selection.activeObject = null;
EditorApplication.delayCall += () => Selection.activeObject = this;
}
}
/************************************************************************************************************************/
///
public override void OnDisable()
{
base.OnDisable();
_Scene.OnDisable();
_Instance = null;
Selection.selectionChanged -= OnSelectionChanged;
AssemblyReloadEvents.beforeAssemblyReload -= DeselectPreviewSceneObjects;
}
/************************************************************************************************************************/
/// Cleans up this window.
protected virtual new void OnDestroy()
{
base.OnDestroy();
_Scene.OnDestroy();
DestroyTransitionProperty();
using (ObjectPool.Disposable.AcquireList