351 lines
12 KiB
C#
351 lines
12 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEditor.Timeline;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Timeline;
|
||
|
using Graphics = UnityEditor.Timeline.Graphics;
|
||
|
|
||
|
namespace UnityEditor
|
||
|
{
|
||
|
class ClipCurveEditor
|
||
|
{
|
||
|
internal readonly CurveEditor m_CurveEditor;
|
||
|
static readonly CurveEditorSettings s_CurveEditorSettings = new CurveEditorSettings();
|
||
|
|
||
|
static readonly float s_GridLabelWidth = 40.0f;
|
||
|
|
||
|
readonly BindingSelector m_BindingHierarchy;
|
||
|
public BindingSelector bindingHierarchy
|
||
|
{
|
||
|
get { return m_BindingHierarchy; }
|
||
|
}
|
||
|
|
||
|
public Rect shownAreaInsideMargins
|
||
|
{
|
||
|
get { return m_CurveEditor != null ? m_CurveEditor.shownAreaInsideMargins : new Rect(1, 1, 1, 1); }
|
||
|
}
|
||
|
|
||
|
Vector2 m_ScrollPosition = Vector2.zero;
|
||
|
|
||
|
readonly CurveDataSource m_DataSource;
|
||
|
|
||
|
float m_LastFrameRate = 30.0f;
|
||
|
int m_LastClipVersion = -1;
|
||
|
int m_LastCurveCount = -1;
|
||
|
TrackViewModelData m_ViewModel;
|
||
|
|
||
|
bool isNewSelection
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_ViewModel == null || m_DataSource == null)
|
||
|
return true;
|
||
|
|
||
|
return m_ViewModel.lastInlineCurveDataID != m_DataSource.id;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal CurveEditor curveEditor
|
||
|
{
|
||
|
get { return m_CurveEditor; }
|
||
|
}
|
||
|
|
||
|
public ClipCurveEditor(CurveDataSource dataSource, TimelineWindow parentWindow, TrackAsset hostTrack)
|
||
|
{
|
||
|
m_DataSource = dataSource;
|
||
|
|
||
|
m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], false);
|
||
|
|
||
|
s_CurveEditorSettings.hSlider = false;
|
||
|
s_CurveEditorSettings.vSlider = false;
|
||
|
s_CurveEditorSettings.hRangeLocked = false;
|
||
|
s_CurveEditorSettings.vRangeLocked = false;
|
||
|
s_CurveEditorSettings.scaleWithWindow = true;
|
||
|
s_CurveEditorSettings.hRangeMin = 0.0f;
|
||
|
s_CurveEditorSettings.showAxisLabels = true;
|
||
|
s_CurveEditorSettings.allowDeleteLastKeyInCurve = true;
|
||
|
s_CurveEditorSettings.rectangleToolFlags = CurveEditorSettings.RectangleToolFlags.MiniRectangleTool;
|
||
|
|
||
|
s_CurveEditorSettings.vTickStyle = new TickStyle
|
||
|
{
|
||
|
tickColor = { color = DirectorStyles.Instance.customSkin.colorInlineCurveVerticalLines },
|
||
|
distLabel = 20,
|
||
|
stubs = true
|
||
|
};
|
||
|
|
||
|
s_CurveEditorSettings.hTickStyle = new TickStyle
|
||
|
{
|
||
|
// hide horizontal lines by giving them a transparent color
|
||
|
tickColor = { color = new Color(0.0f, 0.0f, 0.0f, 0.0f) },
|
||
|
distLabel = 0
|
||
|
};
|
||
|
|
||
|
m_CurveEditor.settings = s_CurveEditorSettings;
|
||
|
|
||
|
m_ViewModel = TimelineWindowViewPrefs.GetTrackViewModelData(hostTrack);
|
||
|
|
||
|
if (isNewSelection)
|
||
|
m_CurveEditor.shownArea = new Rect(1, 1, 1, 1);
|
||
|
else
|
||
|
m_CurveEditor.shownAreaInsideMargins = m_ViewModel.inlineCurvesShownAreaInsideMargins;
|
||
|
|
||
|
m_CurveEditor.ignoreScrollWheelUntilClicked = true;
|
||
|
m_CurveEditor.curvesUpdated = OnCurvesUpdated;
|
||
|
|
||
|
m_BindingHierarchy = new BindingSelector(parentWindow, m_CurveEditor, m_ViewModel.inlineCurvesState);
|
||
|
}
|
||
|
|
||
|
public void SelectAllKeys()
|
||
|
{
|
||
|
m_CurveEditor.SelectAll();
|
||
|
}
|
||
|
|
||
|
public void FrameClip()
|
||
|
{
|
||
|
m_CurveEditor.InvalidateBounds();
|
||
|
m_CurveEditor.FrameClip(false, true);
|
||
|
}
|
||
|
|
||
|
public bool HasSelection()
|
||
|
{
|
||
|
return m_CurveEditor.hasSelection;
|
||
|
}
|
||
|
|
||
|
public Vector2 GetSelectionRange()
|
||
|
{
|
||
|
Bounds b = m_CurveEditor.selectionBounds;
|
||
|
return new Vector2(b.min.x, b.max.x);
|
||
|
}
|
||
|
|
||
|
public CurveDataSource dataSource
|
||
|
{
|
||
|
get { return m_DataSource; }
|
||
|
}
|
||
|
|
||
|
internal void OnCurvesUpdated()
|
||
|
{
|
||
|
if (m_DataSource == null)
|
||
|
return;
|
||
|
|
||
|
if (m_CurveEditor == null)
|
||
|
return;
|
||
|
|
||
|
if (m_CurveEditor.animationCurves.Length == 0)
|
||
|
return;
|
||
|
|
||
|
List<CurveWrapper> curvesToUpdate = m_CurveEditor.animationCurves.Where(c => c.changed).ToList();
|
||
|
|
||
|
// nothing changed, return.
|
||
|
if (curvesToUpdate.Count == 0)
|
||
|
return;
|
||
|
|
||
|
AnimationClip clip = m_DataSource.animationClip;
|
||
|
|
||
|
// something changed, manage the undo properly.
|
||
|
Undo.RegisterCompleteObjectUndo(clip, "Edit Clip Curve");
|
||
|
|
||
|
foreach (CurveWrapper c in curvesToUpdate)
|
||
|
{
|
||
|
AnimationUtility.SetEditorCurve(clip, c.binding, c.curve);
|
||
|
c.changed = false;
|
||
|
}
|
||
|
|
||
|
m_DataSource.UpdateCurves(curvesToUpdate);
|
||
|
}
|
||
|
|
||
|
public void DrawHeader(Rect headerRect)
|
||
|
{
|
||
|
m_BindingHierarchy.InitIfNeeded(headerRect, m_DataSource, isNewSelection);
|
||
|
|
||
|
try
|
||
|
{
|
||
|
GUILayout.BeginArea(headerRect);
|
||
|
m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
|
||
|
m_BindingHierarchy.OnGUI(new Rect(0, 0, headerRect.width, headerRect.height));
|
||
|
GUILayout.EndScrollView();
|
||
|
GUILayout.EndArea();
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Debug.LogException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class FrameFormatCurveEditorState : ICurveEditorState
|
||
|
{
|
||
|
public TimeArea.TimeFormat timeFormat
|
||
|
{
|
||
|
get { return TimeArea.TimeFormat.Frame; }
|
||
|
}
|
||
|
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||
|
public bool rippleTime { get { return false; } }
|
||
|
}
|
||
|
|
||
|
class UnformattedCurveEditorState : ICurveEditorState
|
||
|
{
|
||
|
public TimeArea.TimeFormat timeFormat
|
||
|
{
|
||
|
get { return TimeArea.TimeFormat.None; }
|
||
|
}
|
||
|
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||
|
public bool rippleTime { get { return false; } }
|
||
|
}
|
||
|
|
||
|
void UpdateCurveEditorIfNeeded(WindowState state)
|
||
|
{
|
||
|
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null) || (m_DataSource.animationClip == null))
|
||
|
return;
|
||
|
|
||
|
AnimationClipCurveInfo curveInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_DataSource.animationClip);
|
||
|
int version = curveInfo.version;
|
||
|
if (version != m_LastClipVersion)
|
||
|
{
|
||
|
// tree has changed
|
||
|
if (m_LastCurveCount != curveInfo.curves.Length)
|
||
|
{
|
||
|
m_BindingHierarchy.RefreshTree();
|
||
|
m_LastCurveCount = curveInfo.curves.Length;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// update just the curves
|
||
|
m_BindingHierarchy.RefreshCurves();
|
||
|
}
|
||
|
|
||
|
if (isNewSelection)
|
||
|
FrameClip();
|
||
|
|
||
|
m_LastClipVersion = version;
|
||
|
}
|
||
|
|
||
|
if (state.timeInFrames)
|
||
|
m_CurveEditor.state = new FrameFormatCurveEditorState();
|
||
|
else
|
||
|
m_CurveEditor.state = new UnformattedCurveEditorState();
|
||
|
|
||
|
m_CurveEditor.invSnap = state.referenceSequence.frameRate;
|
||
|
}
|
||
|
|
||
|
public void DrawCurveEditor(Rect animEditorRect, WindowState state, Vector2 activeRange, bool loop, bool selected)
|
||
|
{
|
||
|
var curveStart = state.TimeToPixel(m_DataSource.start);
|
||
|
var minCurveStart = animEditorRect.xMax - 1.0f;
|
||
|
|
||
|
if (curveStart > minCurveStart) // Prevent the curve from drawing inside small rect
|
||
|
animEditorRect.xMax += curveStart - minCurveStart;
|
||
|
|
||
|
UpdateCurveEditorIfNeeded(state);
|
||
|
|
||
|
DrawCurveEditorBackground(animEditorRect);
|
||
|
|
||
|
// adjust the top margin so smaller rectangle have smaller top / bottom margins.
|
||
|
m_CurveEditor.topmargin = m_CurveEditor.bottommargin = CalculateTopMargin(animEditorRect.height);
|
||
|
|
||
|
//align the curve with the clip.
|
||
|
var localCurveStart = curveStart - animEditorRect.xMin;
|
||
|
m_CurveEditor.leftmargin = 0.0f;
|
||
|
m_CurveEditor.rect = new Rect(localCurveStart, 0.0f, animEditorRect.width - localCurveStart, animEditorRect.height);
|
||
|
m_CurveEditor.SetShownHRangeInsideMargins(0.0f, (state.PixelToTime(animEditorRect.xMax) - m_DataSource.start) * m_DataSource.timeScale);
|
||
|
|
||
|
if (m_LastFrameRate != state.referenceSequence.frameRate)
|
||
|
{
|
||
|
m_CurveEditor.hTicks.SetTickModulosForFrameRate(state.referenceSequence.frameRate);
|
||
|
m_LastFrameRate = state.referenceSequence.frameRate;
|
||
|
}
|
||
|
|
||
|
foreach (CurveWrapper cw in m_CurveEditor.animationCurves)
|
||
|
cw.renderer.SetWrap(WrapMode.Default, loop ? WrapMode.Loop : WrapMode.Default);
|
||
|
|
||
|
m_CurveEditor.BeginViewGUI();
|
||
|
|
||
|
Color oldColor = GUI.color;
|
||
|
GUI.color = Color.white;
|
||
|
|
||
|
GUI.BeginGroup(animEditorRect);
|
||
|
|
||
|
// Draw a line at 0
|
||
|
Graphics.DrawLine(new Vector2(localCurveStart, 0.0f), new Vector2(localCurveStart, animEditorRect.height), new Color(1.0f, 1.0f, 1.0f, 0.5f));
|
||
|
|
||
|
float rangeStart = activeRange.x - animEditorRect.x;
|
||
|
float rangeWidth = activeRange.y - activeRange.x;
|
||
|
|
||
|
// draw selection outline underneath the curves.
|
||
|
if (selected)
|
||
|
{
|
||
|
var selectionRect = new Rect(rangeStart, 0.0f, rangeWidth, animEditorRect.height);
|
||
|
DrawOutline(selectionRect);
|
||
|
}
|
||
|
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
|
||
|
Event evt = Event.current;
|
||
|
if ((evt.type == EventType.Layout) || (evt.type == EventType.Repaint) || selected)
|
||
|
m_CurveEditor.CurveGUI();
|
||
|
|
||
|
m_CurveEditor.EndViewGUI();
|
||
|
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
OnCurvesUpdated();
|
||
|
|
||
|
// draw overlays on top of curves
|
||
|
var overlayColor = DirectorStyles.Instance.customSkin.colorInlineCurveOutOfRangeOverlay;
|
||
|
|
||
|
var leftSide = new Rect(localCurveStart, 0.0f, rangeStart - localCurveStart, animEditorRect.height);
|
||
|
EditorGUI.DrawRect(leftSide, overlayColor);
|
||
|
|
||
|
var rightSide = new Rect(rangeStart + rangeWidth, 0.0f, animEditorRect.width - rangeStart - rangeWidth, animEditorRect.height);
|
||
|
EditorGUI.DrawRect(rightSide, overlayColor);
|
||
|
|
||
|
GUI.color = oldColor;
|
||
|
|
||
|
GUI.EndGroup();
|
||
|
|
||
|
// draw the grid labels last
|
||
|
var gridRect = animEditorRect;
|
||
|
gridRect.width = s_GridLabelWidth;
|
||
|
var offset = localCurveStart - s_GridLabelWidth;
|
||
|
if (offset > 0.0f)
|
||
|
gridRect.x = animEditorRect.x + offset;
|
||
|
m_CurveEditor.rect = new Rect(0.0f, 0.0f, animEditorRect.width, animEditorRect.height);
|
||
|
|
||
|
GUI.BeginGroup(gridRect);
|
||
|
m_CurveEditor.GridGUI();
|
||
|
GUI.EndGroup();
|
||
|
}
|
||
|
|
||
|
static void DrawCurveEditorBackground(Rect animEditorRect)
|
||
|
{
|
||
|
if (EditorGUIUtility.isProSkin)
|
||
|
return;
|
||
|
|
||
|
var animEditorBackgroundRect = Rect.MinMaxRect(
|
||
|
0.0f, animEditorRect.yMin, animEditorRect.xMax, animEditorRect.yMax);
|
||
|
|
||
|
// Curves are not legible in Personal Skin so we need to darken the background a bit.
|
||
|
EditorGUI.DrawRect(animEditorBackgroundRect, DirectorStyles.Instance.customSkin.colorInlineCurvesBackground);
|
||
|
}
|
||
|
|
||
|
float CalculateTopMargin(float height)
|
||
|
{
|
||
|
return Mathf.Clamp(0.15f * height, 10.0f, 40.0f);
|
||
|
}
|
||
|
|
||
|
// todo move this in a utility class?
|
||
|
static void DrawOutline(Rect rect, float thickness = 2.0f)
|
||
|
{
|
||
|
// Draw top selected lines.
|
||
|
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), Color.white);
|
||
|
|
||
|
// Draw bottom selected lines.
|
||
|
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), Color.white);
|
||
|
|
||
|
// Draw Left Selected Lines
|
||
|
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), Color.white);
|
||
|
|
||
|
// Draw Right Selected Lines
|
||
|
EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), Color.white);
|
||
|
}
|
||
|
}
|
||
|
}
|