| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 | // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //#if UNITY_EDITOR && UNITY_IMGUIusing 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.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
 |