// 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