123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR && UNITY_IMGUI
- using System;
- using UnityEditor;
- using UnityEngine;
- using Object = UnityEngine.Object;
- namespace Animancer.Editor.Previews
- {
- /// <summary>[Editor-Only]
- /// An <see cref="EditorWindow"/> which allows the user to preview animation transitions separately from the rest
- /// of the scene in Edit Mode or Play Mode.
- /// </summary>
- /// <remarks>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
- /// Previews</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
- ///
- [HelpURL(Strings.DocsURLs.TransitionPreviews)]
- [EditorWindowTitle]// Prevent the base SceneView from trying to use this type name to find the icon.
- public partial class TransitionPreviewWindow : SceneView
- {
- /************************************************************************************************************************/
- #region Public API
- /************************************************************************************************************************/
- private static Texture _Icon;
- /// <summary>The icon image used by this window.</summary>
- public static Texture Icon
- {
- get
- {
- if (_Icon == null)
- {
- // Possible icons: "UnityEditor.LookDevView", "SoftlockInline", "ViewToolOrbit", "ClothInspector.ViewValue".
- var name = EditorGUIUtility.isProSkin ? "ViewToolOrbit On" : "ViewToolOrbit";
- _Icon = AnimancerIcons.Load(name);
- if (_Icon == null)
- _Icon = EditorGUIUtility.whiteTexture;
- }
- return _Icon;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Focusses the <see cref="TransitionPreviewWindow"/> or creates one if none exists.
- /// Or closes the existing window if it was already previewing the `transitionProperty`.
- /// </summary>
- 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<TransitionPreviewWindow>(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();
- }
- };
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The <see cref="AnimancerState.NormalizedTime"/> of the current transition. Can only be set if the property
- /// being previewed matches the current <see cref="TransitionDrawer.Context"/>.
- /// </summary>
- public static float PreviewNormalizedTime
- {
- get => _Instance._Animations.NormalizedTime;
- set
- {
- if (value.IsFinite() &&
- IsPreviewingCurrentProperty())
- _Instance._Animations.NormalizedTime = value;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns the <see cref="AnimancerState"/> of the current transition if the property being previewed matches
- /// the <see cref="TransitionDrawer.Context"/>. Otherwise returns null.
- /// </summary>
- public static AnimancerState GetCurrentState()
- {
- if (!IsPreviewingCurrentProperty())
- return null;
- var previewObject = _Instance._Scene.PreviewObject;
- if (previewObject == null || previewObject.Graph == null)
- return null;
- previewObject.Graph.States.TryGet(Transition, out var state);
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Is the current <see cref="TransitionDrawer.DrawerContext.Property"/> being previewed at the moment?
- /// </summary>
- public static bool IsPreviewingCurrentProperty()
- => IsPreviewing(TransitionDrawer.Context.Property);
- /// <summary>Is the `property` being previewed at the moment?</summary>
- public static bool IsPreviewing(SerializedProperty property)
- => property != null
- && _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;
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void OnEnable()
- {
- _Instance = this;
- base.OnEnable();
- name = "Transition Preview Window";
- titleContent = new("Transition Preview", Icon);
- autoRepaintOnSceneChange = true;
- sceneViewState.showSkybox = TransitionPreviewSettings.ShowSkybox;
- sceneLighting = TransitionPreviewSettings.SceneLighting;
- _Scene ??= new();
- _Animations ??= new();
- 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;
- }
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void OnDisable()
- {
- base.OnDisable();
- _Scene.OnDisable();
- _Instance = null;
- Selection.selectionChanged -= OnSelectionChanged;
- AssemblyReloadEvents.beforeAssemblyReload -= DeselectPreviewSceneObjects;
- }
- /************************************************************************************************************************/
- /// <summary>Cleans up this window.</summary>
- protected virtual new void OnDestroy()
- {
- base.OnDestroy();
- _Scene.OnDestroy();
- DestroyTransitionProperty();
- using (ListPool<Object>.Instance.Acquire(out var objects))
- {
- for (int i = 0; i < _PreviousSelection.Length; i++)
- {
- var obj = _PreviousSelection[i];
- if (obj != null)
- objects.Add(obj);
- }
- Selection.objects = objects.ToArray();
- }
- _TransitionProperty = null;
- AnimancerGUI.RepaintEverything();
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override void OnSceneGUI()
- {
- _Instance = this;
- base.OnSceneGUI();
- _Scene.OnGUI();
- TransitionPreviewSettings.ShowSkybox = sceneViewState.showSkybox;
- TransitionPreviewSettings.SceneLighting = sceneLighting;
- }
- /************************************************************************************************************************/
- /// <summary>Called multiple times per second while this window is visible.</summary>
- private void Update()
- {
- if (Selection.activeObject == null)
- Selection.activeObject = this;
- if (TransitionPreviewSettings.AutoClose &&
- !_TransitionProperty.IsValid())
- {
- Close();
- return;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Returns false.</summary>
- /// <remarks>Returning true makes it draw the main scene instead of the custom scene in Unity 2020.</remarks>
- protected override bool SupportsStageHandling() => false;
- /************************************************************************************************************************/
- private void OnSelectionChanged()
- {
- if (Selection.activeObject == null)
- EditorApplication.delayCall += () => Selection.activeObject = this;
- }
- /************************************************************************************************************************/
- private void DeselectPreviewSceneObjects()
- {
- using (ListPool<Object>.Instance.Acquire(out var objects))
- {
- var selection = Selection.objects;
- for (int i = 0; i < selection.Length; i++)
- {
- var obj = selection[i];
- if (!_Scene.IsSceneObject(obj))
- objects.Add(obj);
- }
- Selection.objects = objects.ToArray();
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Transition Property
- /************************************************************************************************************************/
- [SerializeField]
- private Serialization.PropertyReference _TransitionProperty;
- /// <summary>The <see cref="SerializedProperty"/> currently being previewed.</summary>
- public static SerializedProperty TransitionProperty => _Instance._TransitionProperty;
- /************************************************************************************************************************/
- /// <summary>The <see cref="ITransitionDetailed"/> currently being previewed.</summary>
- public static ITransitionDetailed Transition
- {
- get
- {
- var property = _Instance._TransitionProperty;
- if (!property.IsValid())
- return null;
- return property.Property.GetValue<ITransitionDetailed>();
- }
- }
- /************************************************************************************************************************/
- /// <summary>Indicates whether the `property` is able to be previewed by this system.</summary>
- public static bool CanBePreviewed(SerializedProperty property)
- {
- var accessor = property.GetAccessor();
- if (accessor == null)
- return false;
- var type = accessor.GetFieldElementType(property);
- if (typeof(ITransitionDetailed).IsAssignableFrom(type))
- return true;
- var value = accessor.GetValue(property);
- return
- value != null &&
- typeof(ITransitionDetailed).IsAssignableFrom(value.GetType());
- }
- /************************************************************************************************************************/
- private void SetTargetProperty(SerializedProperty property)
- {
- if (property.serializedObject.targetObjects.Length != 1)
- {
- Close();
- throw new ArgumentException($"{nameof(TransitionPreviewWindow)} does not support multi-object selection.");
- }
- if (!CanBePreviewed(property))
- {
- Close();
- throw new ArgumentException($"The specified property does not implement {nameof(ITransitionDetailed)}.");
- }
- if (!_TransitionProperty.IsValid())
- _PreviousSelection = Selection.objects;
- Selection.activeObject = this;
- DestroyTransitionProperty();
- _TransitionProperty = property;
- _Scene.OnTargetPropertyChanged();
- }
- /************************************************************************************************************************/
- private void DestroyTransitionProperty()
- {
- if (_TransitionProperty == null)
- return;
- _Scene.PreviewObject.DestroyInstanceObject();
- _TransitionProperty.Dispose();
- _TransitionProperty = null;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
- #endif
|