368 lines
14 KiB
C#
368 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor.Timeline;
|
|
using UnityEngine;
|
|
using UnityEngine.Timeline;
|
|
|
|
namespace UnityEditor
|
|
{
|
|
class ClipCurveEditor
|
|
{
|
|
static readonly GUIContent s_RemoveCurveContent = new GUIContent(L10n.Tr("Remove Curve"));
|
|
static readonly GUIContent s_RemoveCurvesContent = new GUIContent(L10n.Tr("Remove Curves"));
|
|
|
|
internal readonly CurveEditor m_CurveEditor;
|
|
static readonly CurveEditorSettings s_CurveEditorSettings = new CurveEditorSettings
|
|
{
|
|
hSlider = false,
|
|
vSlider = false,
|
|
hRangeLocked = false,
|
|
vRangeLocked = false,
|
|
scaleWithWindow = true,
|
|
hRangeMin = 0.0f,
|
|
showAxisLabels = true,
|
|
allowDeleteLastKeyInCurve = true,
|
|
rectangleToolFlags = CurveEditorSettings.RectangleToolFlags.MiniRectangleTool
|
|
};
|
|
|
|
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;
|
|
|
|
UInt64 m_LastClipVersion = UInt64.MaxValue;
|
|
|
|
TrackViewModelData m_ViewModel;
|
|
bool m_ShouldRestoreShownArea;
|
|
|
|
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.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);
|
|
|
|
m_ShouldRestoreShownArea = true;
|
|
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 CurveDataSource dataSource
|
|
{
|
|
get { return m_DataSource; }
|
|
}
|
|
|
|
// called when curves are edited
|
|
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;
|
|
|
|
// something changed, manage the undo properly.
|
|
m_DataSource.ApplyCurveChanges(curvesToUpdate);
|
|
m_LastClipVersion = m_DataSource.GetClipVersion();
|
|
}
|
|
|
|
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));
|
|
if (m_BindingHierarchy.treeViewController != null)
|
|
m_BindingHierarchy.treeViewController.contextClickItemCallback = ContextClickItemCallback;
|
|
GUILayout.EndScrollView();
|
|
GUILayout.EndArea();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogException(e);
|
|
}
|
|
}
|
|
|
|
void ContextClickItemCallback(int obj)
|
|
{
|
|
GenerateContextMenu(obj);
|
|
}
|
|
|
|
void GenerateContextMenu(int obj = -1)
|
|
{
|
|
if (Event.current.type != EventType.ContextClick)
|
|
return;
|
|
|
|
var selectedCurves = GetSelectedProperties().ToArray();
|
|
if (selectedCurves.Length > 0)
|
|
{
|
|
var menu = new GenericMenu();
|
|
var content = selectedCurves.Length == 1 ? s_RemoveCurveContent : s_RemoveCurvesContent;
|
|
menu.AddItem(content,
|
|
false,
|
|
() => RemoveCurves(selectedCurves)
|
|
);
|
|
menu.ShowAsContext();
|
|
}
|
|
}
|
|
|
|
public IEnumerable<EditorCurveBinding> GetSelectedProperties(bool useForcedGroups = false)
|
|
{
|
|
var bindings = new HashSet<EditorCurveBinding>();
|
|
var bindingTree = m_BindingHierarchy.treeViewController.data as BindingTreeViewDataSource;
|
|
foreach (var selectedId in m_BindingHierarchy.treeViewController.GetSelection())
|
|
{
|
|
var node = bindingTree.FindItem(selectedId) as CurveTreeViewNode;
|
|
if (node == null)
|
|
continue;
|
|
|
|
var curveNodeParent = node.parent as CurveTreeViewNode;
|
|
if (useForcedGroups && node.forceGroup && curveNodeParent != null)
|
|
bindings.UnionWith(curveNodeParent.bindings);
|
|
else
|
|
bindings.UnionWith(node.bindings);
|
|
}
|
|
return bindings;
|
|
}
|
|
|
|
public void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
|
{
|
|
m_DataSource.RemoveCurves(bindings);
|
|
m_BindingHierarchy.RefreshTree();
|
|
TimelineWindow.instance.state.CalculateRowRects();
|
|
m_LastClipVersion = m_DataSource.GetClipVersion();
|
|
}
|
|
|
|
class CurveEditorState : ICurveEditorState
|
|
{
|
|
public TimeArea.TimeFormat timeFormat { get; set; }
|
|
public Vector2 timeRange => new Vector2(0, 1);
|
|
public bool rippleTime => false;
|
|
}
|
|
|
|
void UpdateCurveEditorIfNeeded(WindowState state)
|
|
{
|
|
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null))
|
|
return;
|
|
|
|
// check if the curves have changed externally
|
|
var curveChange = m_DataSource.UpdateExternalChanges(ref m_LastClipVersion);
|
|
if (curveChange == CurveChangeType.None)
|
|
return;
|
|
|
|
if (curveChange == CurveChangeType.CurveAddedOrRemoved)
|
|
m_BindingHierarchy.RefreshTree();
|
|
else // curve modified
|
|
m_BindingHierarchy.RefreshCurves();
|
|
|
|
m_CurveEditor.InvalidateSelectionBounds();
|
|
|
|
m_CurveEditor.state = new CurveEditorState() { timeFormat = state.timeFormat.ToTimeAreaFormat() };
|
|
m_CurveEditor.invSnap = (float)state.referenceSequence.frameRate;
|
|
}
|
|
|
|
public void DrawCurveEditor(Rect rect, WindowState state, Vector2 clipRange, bool loop, bool selected)
|
|
{
|
|
SetupMarginsAndRect(rect, state);
|
|
UpdateCurveEditorIfNeeded(state);
|
|
|
|
if (m_ShouldRestoreShownArea)
|
|
RestoreShownArea();
|
|
|
|
var curveVisibleTimeRange = CalculateCurveVisibleTimeRange(state.timeAreaShownRange, m_DataSource);
|
|
m_CurveEditor.SetShownHRangeInsideMargins(curveVisibleTimeRange.x, curveVisibleTimeRange.y); //align the curve with the clip.
|
|
|
|
if (m_LastFrameRate != state.referenceSequence.frameRate)
|
|
{
|
|
m_CurveEditor.hTicks.SetTickModulosForFrameRate((float)state.referenceSequence.frameRate);
|
|
m_LastFrameRate = (float)state.referenceSequence.frameRate;
|
|
}
|
|
|
|
foreach (var cw in m_CurveEditor.animationCurves)
|
|
cw.renderer.SetWrap(WrapMode.Default, loop ? WrapMode.Loop : WrapMode.Default);
|
|
|
|
using (new GUIGroupScope(rect))
|
|
{
|
|
var localRect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
|
var localClipRange = new Vector2(Mathf.Floor(clipRange.x - rect.xMin), Mathf.Ceil(clipRange.y - rect.xMin));
|
|
var curveStartPosX = Mathf.Floor(state.TimeToPixel(m_DataSource.start) - rect.xMin);
|
|
|
|
EditorGUI.DrawRect(new Rect(curveStartPosX, 0.0f, 1.0f, rect.height), new Color(1.0f, 1.0f, 1.0f, 0.5f));
|
|
DrawCurveEditorBackground(localRect);
|
|
|
|
if (selected)
|
|
{
|
|
var selectionRect = new Rect(localClipRange.x, 0.0f, localClipRange.y - localClipRange.x, localRect.height);
|
|
DrawOutline(selectionRect);
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
{
|
|
var evt = Event.current;
|
|
if (evt.type == EventType.Layout || evt.type == EventType.Repaint || selected)
|
|
m_CurveEditor.CurveGUI();
|
|
}
|
|
if (EditorGUI.EndChangeCheck())
|
|
OnCurvesUpdated();
|
|
|
|
DrawOverlay(localRect, localClipRange, DirectorStyles.Instance.customSkin.colorInlineCurveOutOfRangeOverlay);
|
|
DrawGrid(localRect, curveStartPosX);
|
|
}
|
|
}
|
|
|
|
static Vector2 CalculateCurveVisibleTimeRange(Vector2 timeAreaShownRange, CurveDataSource curve)
|
|
{
|
|
var curveVisibleTimeRange = new Vector2
|
|
{
|
|
x = Math.Max(0.0f, timeAreaShownRange.x - curve.start),
|
|
y = timeAreaShownRange.y - curve.start
|
|
};
|
|
return curveVisibleTimeRange * curve.timeScale;
|
|
}
|
|
|
|
void SetupMarginsAndRect(Rect rect, WindowState state)
|
|
{
|
|
var startX = state.TimeToPixel(m_DataSource.start) - rect.x;
|
|
var timelineWidth = state.timeAreaRect.width;
|
|
m_CurveEditor.rect = new Rect(0.0f, 0.0f, timelineWidth, rect.height);
|
|
m_CurveEditor.leftmargin = Math.Max(startX, 0.0f);
|
|
m_CurveEditor.rightmargin = 0.0f;
|
|
m_CurveEditor.topmargin = m_CurveEditor.bottommargin = CalculateTopMargin(rect.height);
|
|
}
|
|
|
|
void RestoreShownArea()
|
|
{
|
|
if (isNewSelection)
|
|
FrameClip();
|
|
else
|
|
m_CurveEditor.shownAreaInsideMargins = m_ViewModel.inlineCurvesShownAreaInsideMargins;
|
|
m_ShouldRestoreShownArea = false;
|
|
}
|
|
|
|
static void DrawCurveEditorBackground(Rect rect)
|
|
{
|
|
if (EditorGUIUtility.isProSkin)
|
|
return;
|
|
|
|
var animEditorBackgroundRect = Rect.MinMaxRect(0.0f, rect.yMin, rect.xMax, rect.yMax);
|
|
|
|
// Curves are not legible in Personal Skin so we need to darken the background a bit.
|
|
EditorGUI.DrawRect(animEditorBackgroundRect, DirectorStyles.Instance.customSkin.colorInlineCurvesBackground);
|
|
}
|
|
|
|
static float CalculateTopMargin(float height)
|
|
{
|
|
return Mathf.Clamp(0.15f * height, 10.0f, 40.0f);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static void DrawOverlay(Rect rect, Vector2 clipRange, Color color)
|
|
{
|
|
var leftSide = new Rect(rect.xMin, rect.yMin, clipRange.x - rect.xMin, rect.height);
|
|
EditorGUI.DrawRect(leftSide, color);
|
|
|
|
var rightSide = new Rect(Mathf.Max(0.0f, clipRange.y), rect.yMin, rect.xMax, rect.height);
|
|
EditorGUI.DrawRect(rightSide, color);
|
|
}
|
|
|
|
void DrawGrid(Rect rect, float curveXPosition)
|
|
{
|
|
var gridXPos = Mathf.Max(curveXPosition - s_GridLabelWidth, rect.xMin);
|
|
var gridRect = new Rect(gridXPos, rect.y, s_GridLabelWidth, rect.height);
|
|
var originalRect = m_CurveEditor.rect;
|
|
|
|
m_CurveEditor.rect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
|
using (new GUIGroupScope(gridRect))
|
|
m_CurveEditor.GridGUI();
|
|
m_CurveEditor.rect = originalRect;
|
|
}
|
|
}
|
|
}
|