// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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 { /// [Editor-Only] Custom preview for . /// Parts of this class are based on Unity's . /// 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(); /************************************************************************************************************************/ /// 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); } /************************************************************************************************************************/ /// public override void Cleanup() { base.Cleanup(); _PreviewPlayer?.Dispose(); _PreviewPlayer = null; _PreviewRenderer?.Dispose(); _PreviewRenderer = null; } /************************************************************************************************************************/ /// Handles changes to the target object. 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; } } /************************************************************************************************************************/ /// Updates the settings of the . 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"); /// public override GUIContent GetPreviewTitle() => Title; /************************************************************************************************************************/ /// 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; /// 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); } /************************************************************************************************************************/ /// Draws a toggle to play and pause the preview. 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 /************************************************************************************************************************/ /// 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); } /************************************************************************************************************************/ /// Draws settings for modifying the preview. 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 /************************************************************************************************************************/ /// Draws the preview timeline. 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(); /************************************************************************************************************************/ /// Draws the fade duration slider. 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; } } /************************************************************************************************************************/ /// Draws the preview time slider. 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; /// Draws handles drag events to control the preview time. 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; } /************************************************************************************************************************/ /// Calculates the start and end pixels of the fade. 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; } /************************************************************************************************************************/ /// Draws labels for the selected transitions. 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