Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -0,0 +1,25 @@
using UnityEngine;
using UnityEngine.Timeline;
// Data sources for key overlays
namespace UnityEditor.Timeline
{
// Used for key overlays manipulators
class AnimationTrackKeyDataSource : BasePropertyKeyDataSource
{
readonly float m_TrackOffset;
protected override AnimationClip animationClip { get; }
public AnimationTrackKeyDataSource(AnimationTrack track)
{
animationClip = track != null ? track.infiniteClip : null;
m_TrackOffset = track != null ? (float)track.infiniteClipTimeOffset : 0.0f;
}
protected override float TransformKeyTime(float keyTime)
{
return keyTime + m_TrackOffset;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0b56195e00682b4594dfaeef9d5fa78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
class Control
{
readonly List<Manipulator> m_Manipulators = new List<Manipulator>();
public bool HandleManipulatorsEvents(WindowState state)
{
var isHandled = false;
foreach (var manipulator in m_Manipulators)
{
isHandled = manipulator.HandleEvent(state);
if (isHandled)
break;
}
return isHandled;
}
public void AddManipulator(Manipulator m)
{
m_Manipulators.Add(m);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 077690d334440b044bdd51b26b3e9413
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d40a0edbdcdcf9747a420f3bbe0f18db
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,89 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
[CustomTrackDrawer(typeof(AnimationTrack)), UsedImplicitly]
class AnimationTrackDrawer : TrackDrawer
{
static class Styles
{
public static readonly GUIContent AvatarMaskActiveTooltip = L10n.TextContent(string.Empty, "Enable Avatar Mask");
public static readonly GUIContent AvatarMaskInactiveTooltip = L10n.TextContent(string.Empty, "Disable Avatar Mask");
}
public override void DrawTrackHeaderButton(Rect rect, WindowState state)
{
var animTrack = track as AnimationTrack;
if (animTrack == null) return;
var style = DirectorStyles.Instance.trackAvatarMaskButton;
var tooltip = animTrack.applyAvatarMask ? Styles.AvatarMaskInactiveTooltip : Styles.AvatarMaskActiveTooltip;
using (var check = new EditorGUI.ChangeCheckScope())
{
var toggle = GUI.Toggle(rect, animTrack.applyAvatarMask, tooltip, style);
if (check.changed)
{
animTrack.applyAvatarMask = toggle;
if (state != null)
state.rebuildGraph = true;
}
}
}
public override void DrawRecordingBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
base.DrawRecordingBackground(trackRect, trackAsset, visibleTime, state);
DrawBorderOfAddedRecordingClip(trackRect, trackAsset, visibleTime, (WindowState)state);
}
static void DrawBorderOfAddedRecordingClip(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
if (!state.IsArmedForRecord(trackAsset))
return;
AnimationTrack animTrack = trackAsset as AnimationTrack;
if (animTrack == null || !animTrack.inClipMode)
return;
// make sure there is no clip but we can add one
TimelineClip clip = null;
if (trackAsset.FindRecordingClipAtTime(state.editSequence.time, out clip) || clip != null)
return;
float yMax = trackRect.yMax;
float yMin = trackRect.yMin;
double startGap = 0;
double endGap = 0;
trackAsset.GetGapAtTime(state.editSequence.time, out startGap, out endGap);
if (double.IsInfinity(endGap))
endGap = visibleTime.y;
if (startGap > visibleTime.y || endGap < visibleTime.x)
return;
startGap = Math.Max(startGap, visibleTime.x);
endGap = Math.Min(endGap, visibleTime.y);
float xMin = state.TimeToPixel(startGap);
float xMax = state.TimeToPixel(endGap);
var r = Rect.MinMaxRect(xMin, yMin, xMax, yMax);
ClipDrawer.DrawClipSelectionBorder(r, ClipBorder.Recording(), ClipBlends.kNone);
}
public override bool HasCustomTrackHeaderButton()
{
var animTrack = track as AnimationTrack;
if (animTrack == null) return false;
return animTrack != null && animTrack.avatarMask != null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb281723220c9964094e6c52e0ece792
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,682 @@
using System;
using UnityEngine.Timeline;
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
enum BlendKind
{
None,
Ease,
Mix
}
enum BlendAngle
{
Descending,
Ascending
}
struct IconData
{
public enum Side { Left = -1, Right = 1 }
public Texture2D icon;
public Color tint;
public float width { get { return 16; } }
public float height { get { return 16; } }
public IconData(Texture2D icon)
{
this.icon = icon;
tint = Color.white;
}
}
struct ClipBorder
{
public readonly Color color;
public readonly float thickness;
ClipBorder(Color color, float thickness)
{
this.color = color;
this.thickness = thickness;
}
const float k_ClipSelectionBorder = 1.0f;
const float k_ClipRecordingBorder = 2.0f;
public static ClipBorder Recording()
{
return new ClipBorder(DirectorStyles.Instance.customSkin.colorRecordingClipOutline, k_ClipRecordingBorder);
}
public static ClipBorder Selection()
{
return new ClipBorder(Color.white, k_ClipSelectionBorder);
}
public static ClipBorder Default()
{
return new ClipBorder(DirectorStyles.Instance.customSkin.clipBorderColor, k_ClipSelectionBorder);
}
}
struct ClipBlends
{
public readonly BlendKind inKind;
public readonly Rect inRect;
public readonly BlendKind outKind;
public readonly Rect outRect;
public ClipBlends(BlendKind inKind, Rect inRect, BlendKind outKind, Rect outRect)
{
this.inKind = inKind;
this.inRect = inRect;
this.outKind = outKind;
this.outRect = outRect;
}
public static readonly ClipBlends kNone = new ClipBlends(BlendKind.None, Rect.zero, BlendKind.None, Rect.zero);
}
struct ClipDrawData
{
public TimelineClip clip; // clip being drawn
public Rect targetRect; // rectangle to draw to
public Rect unclippedRect; // the clip's unclipped rect
public Rect clippedRect; // the clip's clipped rect to the visible time area
public Rect clipCenterSection; // clip center section
public string title; // clip title
public bool selected; // is the clip selected
public bool inlineCurvesSelected; // is the inline curve of the clip selected
public double localVisibleStartTime;
public double localVisibleEndTime;
public IconData[] leftIcons;
public IconData[] rightIcons;
public TimelineClip previousClip;
public bool previousClipSelected;
public bool supportsLooping;
public int minLoopIndex;
public List<Rect> loopRects;
public ClipBlends clipBlends;
public ClipDrawOptions ClipDrawOptions;
public ClipEditor clipEditor;
}
static class ClipDrawer
{
public static class Styles
{
public static readonly Texture2D iconWarn = EditorGUIUtility.LoadIconRequired("console.warnicon.inactive.sml");
public static readonly string HoldText = L10n.Tr("HOLD");
public static readonly Texture2D s_IconNoRecord = EditorGUIUtility.LoadIcon("console.erroricon.sml");
public static readonly GUIContent s_ClipNotRecorable = L10n.TextContent("", "This clip is not recordable");
public static readonly GUIContent s_ClipNoRecordInBlend = L10n.TextContent("", "Recording in blends in prohibited");
}
const float k_ClipSwatchLineThickness = 4.0f;
const float k_MinClipWidth = 7.0f;
const float k_ClipInOutMinWidth = 15.0f;
const float k_ClipLoopsMinWidth = 20.0f;
const float k_ClipLabelPadding = 6.0f;
const float k_ClipLabelMinWidth = 10.0f;
const float k_IconsPadding = 1.0f;
const float k_ClipInlineWidth = 1.0f;
static readonly GUIContent s_TitleContent = new GUIContent();
static readonly IconData[] k_ClipErrorIcons = { new IconData {icon = Styles.iconWarn, tint = DirectorStyles.kClipErrorColor} };
static readonly Dictionary<int, string> s_LoopStringCache = new Dictionary<int, string>(100);
// caches the loopstring to avoid allocation from string concats
static string GetLoopString(int loopIndex)
{
string loopString = null;
if (!s_LoopStringCache.TryGetValue(loopIndex, out loopString))
{
loopString = "L" + loopIndex;
s_LoopStringCache[loopIndex] = loopString;
}
return loopString;
}
static void DrawLoops(ClipDrawData drawData)
{
if (drawData.loopRects == null || drawData.loopRects.Count == 0)
return;
var oldColor = GUI.color;
int loopIndex = drawData.minLoopIndex;
for (int l = 0; l < drawData.loopRects.Count; l++)
{
Rect theRect = drawData.loopRects[l];
theRect.x -= drawData.unclippedRect.x;
theRect.x += 1;
theRect.width -= 2.0f;
theRect.y = 5.0f;
theRect.height -= 4.0f;
theRect.xMin -= 4f;
if (theRect.width >= 5f)
{
using (new GUIViewportScope(drawData.clipCenterSection))
{
GUI.color = new Color(0.0f, 0.0f, 0.0f, 0.2f);
GUI.Label(theRect, GUIContent.none, DirectorStyles.Instance.displayBackground);
if (theRect.width > 36.0f)
{
var style = DirectorStyles.Instance.fontClipLoop;
GUI.color = new Color(0.0f, 0.0f, 0.0f, 0.3f);
var loopContent = new GUIContent(drawData.supportsLooping ? GetLoopString(loopIndex) : Styles.HoldText);
GUI.Label(theRect, loopContent, style);
}
}
}
loopIndex++;
if (!drawData.supportsLooping)
break;
}
GUI.color = oldColor;
}
static void DrawClipBorder(ClipDrawData drawData)
{
var animTrack = drawData.clip.GetParentTrack() as AnimationTrack;
var selectionBorder = ClipBorder.Selection();
if (TimelineWindow.instance.state.recording && animTrack == null && drawData.clip.GetParentTrack().IsRecordingToClip(drawData.clip))
{
DrawClipSelectionBorder(drawData.clipCenterSection, selectionBorder, drawData.clipBlends);
return;
}
DrawClipDefaultBorder(drawData.clipCenterSection, ClipBorder.Default(), drawData.clipBlends);
if (drawData.selected)
DrawClipSelectionBorder(drawData.clipCenterSection, selectionBorder, drawData.clipBlends);
if (drawData.previousClip != null && drawData.previousClipSelected)
{
bool shouldDrawLeftLine = Math.Abs(drawData.previousClip.start - drawData.clip.start) < double.Epsilon;
DrawClipBlendSelectionBorder(drawData.clipCenterSection, selectionBorder, drawData.clipBlends, shouldDrawLeftLine);
}
}
public static void DrawClipSelectionBorder(Rect clipRect, ClipBorder border, ClipBlends blends)
{
var thickness = border.thickness;
var color = border.color;
var min = clipRect.min;
var max = clipRect.max;
//Left line
if (blends.inKind == BlendKind.None)
EditorGUI.DrawRect(new Rect(min.x, min.y, thickness, max.y - min.y), color);
else
DrawBlendLine(blends.inRect, blends.inKind == BlendKind.Mix ? BlendAngle.Descending : BlendAngle.Ascending, thickness, color);
//Right line
if (blends.outKind == BlendKind.None)
EditorGUI.DrawRect(new Rect(max.x - thickness, min.y, thickness, max.y - min.y), color);
else
DrawBlendLine(blends.outRect, BlendAngle.Descending, thickness, color);
//Top line
var xTop1 = blends.inKind == BlendKind.Mix ? blends.inRect.xMin : min.x;
var xTop2 = max.x;
EditorGUI.DrawRect(new Rect(xTop1, min.y, xTop2 - xTop1, thickness), color);
//Bottom line
var xBottom1 = blends.inKind == BlendKind.Ease ? blends.inRect.xMin : min.x;
var xBottom2 = blends.outKind == BlendKind.None ? max.x : blends.outRect.xMax;
EditorGUI.DrawRect(new Rect(xBottom1, max.y - thickness, xBottom2 - xBottom1, thickness), color);
}
static Vector3[] s_BlendLines = new Vector3[4];
static void DrawBlendLine(Rect rect, BlendAngle blendAngle, float width, Color color)
{
var halfWidth = width / 2.0f;
Vector2 p0, p1;
var inverse = 1.0f;
if (blendAngle == BlendAngle.Descending)
{
p0 = rect.min;
p1 = rect.max;
}
else
{
p0 = new Vector2(rect.xMax, rect.yMin);
p1 = new Vector2(rect.xMin, rect.yMax);
inverse = -1.0f;
}
s_BlendLines[0] = new Vector3(p0.x - halfWidth, p0.y + halfWidth * inverse);
s_BlendLines[1] = new Vector3(p1.x - halfWidth, p1.y + halfWidth * inverse);
s_BlendLines[2] = new Vector3(p1.x + halfWidth, p1.y - halfWidth * inverse);
s_BlendLines[3] = new Vector3(p0.x + halfWidth, p0.y - halfWidth * inverse);
Graphics.DrawPolygonAA(color, s_BlendLines);
}
static void DrawClipBlendSelectionBorder(Rect clipRect, ClipBorder border, ClipBlends blends, bool shouldLeftLine = false)
{
var color = border.color;
var thickness = border.thickness;
if (blends.inKind == BlendKind.Mix)
{
DrawBlendLine(blends.inRect, BlendAngle.Descending, thickness, color);
var xBottom1 = blends.inRect.xMin;
var xBottom2 = blends.inRect.xMax;
EditorGUI.DrawRect(new Rect(xBottom1, clipRect.max.y - thickness, xBottom2 - xBottom1, thickness), color);
if (shouldLeftLine)
EditorGUI.DrawRect(new Rect(xBottom1, clipRect.min.y, thickness, clipRect.max.y - clipRect.min.y), color);
}
}
static void DrawClipDefaultBorder(Rect clipRect, ClipBorder border, ClipBlends blends)
{
var color = border.color;
var thickness = border.thickness;
// Draw vertical lines at the edges of the clip
EditorGUI.DrawRect(new Rect(clipRect.xMin, clipRect.y, thickness, clipRect.height), color); //left
//only draw the right one when no out mix blend
if (blends.outKind != BlendKind.Mix)
EditorGUI.DrawRect(new Rect(clipRect.xMax - thickness, clipRect.y, thickness, clipRect.height), color); //right
//draw a vertical line for the previous clip
if (blends.inKind == BlendKind.Mix)
EditorGUI.DrawRect(new Rect(blends.inRect.xMin, blends.inRect.y, thickness, blends.inRect.height), color); //left
//Draw blend line
if (blends.inKind == BlendKind.Mix)
DrawBlendLine(blends.inRect, BlendAngle.Descending, thickness, color);
}
static void DrawClipTimescale(Rect targetRect, Rect clippedRect, double timeScale)
{
if (timeScale != 1.0)
{
const float xOffset = 4.0f;
const float yOffset = 6.0f;
var segmentLength = timeScale > 1.0f ? 5.0f : 15.0f;
// clamp to the visible region to reduce the line count (case 1213189), but adject the start segment to match the visuals of drawing targetRect
var startX = clippedRect.min.x - ((clippedRect.min.x - targetRect.min.x) % (segmentLength * 2));
var endX = clippedRect.max.x;
var start = new Vector3(startX + xOffset, targetRect.min.y + yOffset, 0.0f);
var end = new Vector3(endX - xOffset, targetRect.min.y + yOffset, 0.0f);
Graphics.DrawDottedLine(start, end, segmentLength, DirectorStyles.Instance.customSkin.colorClipFont);
Graphics.DrawDottedLine(start + new Vector3(0.0f, 1.0f, 0.0f), end + new Vector3(0.0f, 1.0f, 0.0f), segmentLength, DirectorStyles.Instance.customSkin.colorClipFont);
}
}
static void DrawClipInOut(Rect targetRect, TimelineClip clip)
{
var assetDuration = TimelineHelpers.GetClipAssetEndTime(clip);
bool drawClipOut = assetDuration<double.MaxValue &&
assetDuration - clip.end> TimeUtility.kTimeEpsilon;
bool drawClipIn = clip.clipIn > 0.0;
if (!drawClipIn && !drawClipOut)
return;
var rect = targetRect;
if (drawClipOut)
{
var icon = DirectorStyles.Instance.clipOut;
var iconRect = new Rect(rect.xMax - icon.fixedWidth - 2.0f,
rect.yMin + (rect.height - icon.fixedHeight) * 0.5f,
icon.fixedWidth, icon.fixedHeight);
GUI.Label(iconRect, GUIContent.none, icon);
}
if (drawClipIn)
{
var icon = DirectorStyles.Instance.clipIn;
var iconRect = new Rect(2.0f + rect.xMin,
rect.yMin + (rect.height - icon.fixedHeight) * 0.5f,
icon.fixedWidth, icon.fixedHeight);
GUI.Label(iconRect, GUIContent.none, icon);
}
}
static void DrawClipLabel(ClipDrawData data, Rect availableRect, Color color)
{
var errorText = data.ClipDrawOptions.errorText;
var hasError = !string.IsNullOrEmpty(errorText);
var textColor = hasError ? DirectorStyles.kClipErrorColor : color;
var tooltip = hasError ? errorText : data.ClipDrawOptions.tooltip;
if (hasError)
DrawClipLabel(data.title, availableRect, textColor, k_ClipErrorIcons, null, tooltip);
else
DrawClipLabel(data.title, availableRect, textColor, data.leftIcons, data.rightIcons, tooltip);
}
static void DrawClipLabel(string title, Rect availableRect, Color color, string errorText = "")
{
var hasError = !string.IsNullOrEmpty(errorText);
var textColor = hasError ? DirectorStyles.kClipErrorColor : color;
if (hasError)
DrawClipLabel(title, availableRect, textColor, k_ClipErrorIcons, null, errorText);
else
DrawClipLabel(title, availableRect, textColor, null, null, errorText);
}
static void DrawClipLabel(string title, Rect availableRect, Color textColor, IconData[] leftIcons, IconData[] rightIcons, string tooltipMessage = "")
{
s_TitleContent.text = title;
var neededTextWidth = DirectorStyles.Instance.fontClip.CalcSize(s_TitleContent).x;
var neededIconWidthLeft = 0.0f;
var neededIconWidthRight = 0.0f;
if (leftIcons != null)
for (int i = 0, n = leftIcons.Length; i < n; ++i)
neededIconWidthLeft += leftIcons[i].width + k_IconsPadding;
if (rightIcons != null)
for (int i = 0, n = rightIcons.Length; i < n; ++i)
neededIconWidthRight += rightIcons[i].width + k_IconsPadding;
var neededIconWidth = Mathf.Max(neededIconWidthLeft, neededIconWidthRight);
// Atomic operation: We either show all icons or no icons at all
var showIcons = neededTextWidth / 2.0f + neededIconWidth < availableRect.width / 2.0f;
if (showIcons)
{
if (leftIcons != null)
DrawClipIcons(leftIcons, IconData.Side.Left, neededTextWidth, availableRect);
if (rightIcons != null)
DrawClipIcons(rightIcons, IconData.Side.Right, neededTextWidth, availableRect);
}
if (neededTextWidth > availableRect.width)
s_TitleContent.text = DirectorStyles.Elipsify(title, availableRect.width, neededTextWidth);
s_TitleContent.tooltip = tooltipMessage;
DrawClipName(availableRect, s_TitleContent, textColor);
}
static void DrawClipIcons(IconData[] icons, IconData.Side side, float labelWidth, Rect availableRect)
{
var halfText = labelWidth / 2.0f;
var offset = halfText + k_IconsPadding;
foreach (var iconData in icons)
{
offset += iconData.width / 2.0f + k_IconsPadding;
var iconRect =
new Rect(0.0f, 0.0f, iconData.width, iconData.height)
{
center = new Vector2(availableRect.center.x + offset * (int)side, availableRect.center.y)
};
DrawIcon(iconRect, iconData.tint, iconData.icon);
offset += iconData.width / 2.0f;
}
}
static void DrawClipName(Rect rect, GUIContent content, Color textColor)
{
Graphics.ShadowLabel(rect, content, DirectorStyles.Instance.fontClip, textColor, Color.black);
}
static void DrawIcon(Rect imageRect, Color color, Texture2D icon)
{
GUI.DrawTexture(imageRect, icon, ScaleMode.ScaleAndCrop, true, 0, color, 0, 0);
}
static void DrawClipBackground(Rect clipCenterSection, bool selected)
{
if (Event.current.type != EventType.Repaint)
return;
var color = selected ? DirectorStyles.Instance.customSkin.clipSelectedBckg : DirectorStyles.Instance.customSkin.clipBckg;
EditorGUI.DrawRect(clipCenterSection, color);
}
static Vector3[] s_BlendVertices = new Vector3[3];
static void DrawClipBlends(ClipBlends blends, Color inColor, Color outColor, Color backgroundColor)
{
switch (blends.inKind)
{
case BlendKind.Ease:
// 2
// / |
// / |
// 0---1
EditorGUI.DrawRect(blends.inRect, backgroundColor);
s_BlendVertices[0] = new Vector3(blends.inRect.xMin, blends.inRect.yMax);
s_BlendVertices[1] = new Vector3(blends.inRect.xMax, blends.inRect.yMax);
s_BlendVertices[2] = new Vector3(blends.inRect.xMax, blends.inRect.yMin);
Graphics.DrawPolygonAA(inColor, s_BlendVertices);
break;
case BlendKind.Mix:
// 0---2
// \ |
// \ |
// 1
s_BlendVertices[0] = new Vector3(blends.inRect.xMin, blends.inRect.yMin);
s_BlendVertices[1] = new Vector3(blends.inRect.xMax, blends.inRect.yMax);
s_BlendVertices[2] = new Vector3(blends.inRect.xMax, blends.inRect.yMin);
Graphics.DrawPolygonAA(inColor, s_BlendVertices);
break;
}
if (blends.outKind != BlendKind.None)
{
if (blends.outKind == BlendKind.Ease)
EditorGUI.DrawRect(blends.outRect, backgroundColor);
// 0
// | \
// | \
// 1---2
s_BlendVertices[0] = new Vector3(blends.outRect.xMin, blends.outRect.yMin);
s_BlendVertices[1] = new Vector3(blends.outRect.xMin, blends.outRect.yMax);
s_BlendVertices[2] = new Vector3(blends.outRect.xMax, blends.outRect.yMax);
Graphics.DrawPolygonAA(outColor, s_BlendVertices);
}
}
static void DrawClipSwatch(Rect targetRect, Color swatchColor)
{
// Draw Colored Line at the bottom.
var colorRect = targetRect;
colorRect.yMin = colorRect.yMax - k_ClipSwatchLineThickness;
EditorGUI.DrawRect(colorRect, swatchColor);
}
public static void DrawSimpleClip(TimelineClip clip, Rect targetRect, ClipBorder border, Color overlay, ClipDrawOptions drawOptions)
{
GUI.BeginClip(targetRect);
var clipRect = new Rect(0.0f, 0.0f, targetRect.width, targetRect.height);
var orgColor = GUI.color;
GUI.color = overlay;
DrawClipBackground(clipRect, false);
GUI.color = orgColor;
if (clipRect.width <= k_MinClipWidth)
{
clipRect.width = k_MinClipWidth;
}
DrawClipSwatch(targetRect, drawOptions.highlightColor * overlay);
if (targetRect.width >= k_ClipInOutMinWidth)
DrawClipInOut(clipRect, clip);
var textRect = clipRect;
textRect.xMin += k_ClipLabelPadding;
textRect.xMax -= k_ClipLabelPadding;
if (textRect.width > k_ClipLabelMinWidth)
DrawClipLabel(clip.displayName, textRect, Color.white, drawOptions.errorText);
DrawClipSelectionBorder(clipRect, border, ClipBlends.kNone);
GUI.EndClip();
}
public static void DrawDefaultClip(ClipDrawData drawData)
{
var customSkin = DirectorStyles.Instance.customSkin;
var blendInColor = drawData.selected ? customSkin.clipBlendInSelected : customSkin.clipBlendIn;
var blendOutColor = drawData.selected ? customSkin.clipBlendOutSelected : customSkin.clipBlendOut;
var easeBackgroundColor = customSkin.clipEaseBckgColor;
DrawClipBlends(drawData.clipBlends, blendInColor, blendOutColor, easeBackgroundColor);
DrawClipBackground(drawData.clipCenterSection, drawData.selected);
if (drawData.targetRect.width > k_MinClipWidth)
{
DrawClipEditorBackground(drawData);
}
else
{
drawData.targetRect.width = k_MinClipWidth;
drawData.clipCenterSection.width = k_MinClipWidth;
}
DrawClipTimescale(drawData.targetRect, drawData.clippedRect, drawData.clip.timeScale);
if (drawData.targetRect.width >= k_ClipInOutMinWidth)
DrawClipInOut(drawData.targetRect, drawData.clip);
var labelRect = drawData.clipCenterSection;
if (drawData.targetRect.width >= k_ClipLoopsMinWidth)
{
bool selected = drawData.selected || drawData.inlineCurvesSelected;
if (selected)
{
if (drawData.loopRects != null && drawData.loopRects.Any())
{
DrawLoops(drawData);
var l = drawData.loopRects[0];
labelRect.xMax = Math.Min(labelRect.xMax, l.x - drawData.unclippedRect.x);
}
}
}
labelRect.xMin += k_ClipLabelPadding;
labelRect.xMax -= k_ClipLabelPadding;
if (labelRect.width > k_ClipLabelMinWidth)
{
DrawClipLabel(drawData, labelRect, Color.white);
}
DrawClipSwatch(drawData.targetRect, drawData.ClipDrawOptions.highlightColor);
DrawClipBorder(drawData);
}
static void DrawClipEditorBackground(ClipDrawData drawData)
{
var isRepaint = (Event.current.type == EventType.Repaint);
if (isRepaint && drawData.clipEditor != null)
{
var customBodyRect = drawData.clippedRect;
customBodyRect.yMin += k_ClipInlineWidth;
customBodyRect.yMax -= k_ClipSwatchLineThickness;
var region = new ClipBackgroundRegion(customBodyRect, drawData.localVisibleStartTime, drawData.localVisibleEndTime);
try
{
drawData.clipEditor.DrawBackground(drawData.clip, region);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
public static void DrawAnimationRecordBorder(ClipDrawData drawData)
{
if (!drawData.clip.GetParentTrack().IsRecordingToClip(drawData.clip))
return;
var time = new DiscreteTime(TimelineWindow.instance.state.editSequence.time);
var start = new DiscreteTime(drawData.clip.start + drawData.clip.mixInDuration);
var end = new DiscreteTime(drawData.clip.end - drawData.clip.mixOutDuration);
if (time < start || time >= end)
return;
DrawClipSelectionBorder(drawData.clipCenterSection, ClipBorder.Recording(), ClipBlends.kNone);
}
public static void DrawRecordProhibited(ClipDrawData drawData)
{
DrawRecordInvalidClip(drawData);
DrawRecordOnBlend(drawData);
}
public static void DrawRecordOnBlend(ClipDrawData drawData)
{
double time = TimelineWindow.instance.state.editSequence.time;
if (time >= drawData.clip.start && time < drawData.clip.start + drawData.clip.mixInDuration)
{
Rect r = Rect.MinMaxRect(drawData.clippedRect.xMin, drawData.clippedRect.yMin, drawData.clipCenterSection.xMin, drawData.clippedRect.yMax);
DrawInvalidRecordIcon(r, Styles.s_ClipNoRecordInBlend);
}
if (time <= drawData.clip.end && time > drawData.clip.end - drawData.clip.mixOutDuration)
{
Rect r = Rect.MinMaxRect(drawData.clipCenterSection.xMax, drawData.clippedRect.yMin, drawData.clippedRect.xMax, drawData.clippedRect.yMax);
DrawInvalidRecordIcon(r, Styles.s_ClipNoRecordInBlend);
}
}
public static void DrawRecordInvalidClip(ClipDrawData drawData)
{
if (drawData.clip.recordable)
return;
double time = TimelineWindow.instance.state.editSequence.time;
if (time < drawData.clip.start + drawData.clip.mixInDuration || time > drawData.clip.end - drawData.clip.mixOutDuration)
return;
DrawInvalidRecordIcon(drawData.clipCenterSection, Styles.s_ClipNotRecorable);
}
public static void DrawInvalidRecordIcon(Rect rect, GUIContent helpText)
{
EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.30f));
var icon = Styles.s_IconNoRecord;
if (rect.width < icon.width || rect.height < icon.height)
return;
float x = rect.x + (rect.width - icon.width) * 0.5f;
float y = rect.y + (rect.height - icon.height) * 0.5f;
Rect r = new Rect(x, y, icon.width, icon.height);
GUI.Label(r, helpText);
GUI.DrawTexture(r, icon, ScaleMode.ScaleAndCrop, true, 0, Color.white, 0, 0);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63118a0c9ee42ac46b7f30e793177a76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,87 @@
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class InfiniteTrackDrawer : TrackDrawer
{
readonly IPropertyKeyDataSource m_DataSource;
Rect m_TrackRect;
public InfiniteTrackDrawer(IPropertyKeyDataSource dataSource)
{
m_DataSource = dataSource;
}
public bool CanDraw(TrackAsset track, WindowState state)
{
var keys = m_DataSource.GetKeys();
var isTrackEmpty = track.clips.Length == 0;
return keys != null || (state.IsArmedForRecord(track) && isTrackEmpty);
}
static void DrawRecordBackground(Rect trackRect)
{
var styles = DirectorStyles.Instance;
EditorGUI.DrawRect(trackRect, styles.customSkin.colorInfiniteTrackBackgroundRecording);
Graphics.ShadowLabel(trackRect,
DirectorStyles.Elipsify(DirectorStyles.recordingLabel.text, trackRect, styles.fontClip),
styles.fontClip, Color.white, Color.black);
}
public override bool DrawTrack(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
m_TrackRect = trackRect;
if (!CanDraw(trackAsset, state))
return true;
if (state.recording && state.IsArmedForRecord(trackAsset))
DrawRecordBackground(trackRect);
if (m_DataSource.GetKeys() != null && m_DataSource.GetKeys().Length > 0 || state.recording)
GUI.Box(trackRect, GUIContent.none, DirectorStyles.Instance.infiniteTrack);
var shadowRect = trackRect;
shadowRect.yMin = shadowRect.yMax;
shadowRect.height = 15.0f;
if (Event.current.type == EventType.Repaint)
DirectorStyles.Instance.bottomShadow.Draw(shadowRect, false, false, false, false);
var keys = m_DataSource.GetKeys();
if (keys != null && keys.Length > 0)
{
foreach (var k in keys)
DrawKeyFrame(k, state);
}
return true;
}
void DrawKeyFrame(float key, WindowState state)
{
var x = state.TimeToPixel(key);
var bounds = new Rect(x, m_TrackRect.yMin + 3.0f, 1.0f, m_TrackRect.height - 6.0f);
if (!m_TrackRect.Overlaps(bounds))
return;
var iconWidth = DirectorStyles.Instance.keyframe.fixedWidth;
var iconHeight = DirectorStyles.Instance.keyframe.fixedHeight;
var keyframeRect = bounds;
keyframeRect.width = iconWidth;
keyframeRect.height = iconHeight;
keyframeRect.xMin -= iconWidth / 2.0f;
keyframeRect.yMin = m_TrackRect.yMin + ((m_TrackRect.height - iconHeight) / 2.0f);
// case 890650 : Make sure to use GUI.Label and not GUI.Box since the number of key frames can vary while dragging keys in the inline curves causing hotControls to be desynchronized
GUI.Label(keyframeRect, GUIContent.none, DirectorStyles.Instance.keyframe);
EditorGUI.DrawRect(bounds, DirectorStyles.Instance.customSkin.colorInfiniteClipLine);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ea6a8a826704f743b3b0ce3e9d3c9a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 16388ae022a89264b84107f0c1b44680
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,63 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class ClipsLayer : ItemsLayer<TimelineClipGUI>
{
static readonly GUIStyle k_ConnectorIcon = DirectorStyles.Instance.connector;
public ClipsLayer(Layer layerOrder, IRowGUI parent) : base(layerOrder)
{
var track = parent.asset;
track.SortClips();
TimelineClipGUI previousClipGUI = null;
foreach (var clip in track.clips)
{
var oldClipGUI = ItemToItemGui.GetGuiForClip(clip);
var isInvalid = oldClipGUI != null && oldClipGUI.isInvalid; // HACK Make sure to carry invalidy state when refereshing the cache.
var currentClipGUI = new TimelineClipGUI(clip, parent, this) {isInvalid = isInvalid};
if (previousClipGUI != null) previousClipGUI.nextClip = currentClipGUI;
currentClipGUI.previousClip = previousClipGUI;
AddItem(currentClipGUI);
previousClipGUI = currentClipGUI;
}
//adjust zOrder based on current clip selection
foreach (var clipGUI in items)
{
if(clipGUI.IsSelected())
clipGUI.MoveToTop();
}
}
public override void Draw(Rect rect, WindowState state)
{
base.Draw(rect, state); //draw clips
DrawConnector(items);
}
static void DrawConnector(List<TimelineClipGUI> clips)
{
if (Event.current.type != EventType.Repaint)
return;
foreach (var clip in clips)
{
if (clip.previousClip != null && clip.visible && clip.treeViewRect.width > 14 &&
(DiscreteTime)clip.start == (DiscreteTime)clip.previousClip.end)
{
// draw little connector widget
var localRect = clip.treeViewRect;
localRect.x -= Mathf.Floor(k_ConnectorIcon.fixedWidth / 2.0f);
localRect.width = k_ConnectorIcon.fixedWidth;
localRect.height = k_ConnectorIcon.fixedHeight;
GUI.Label(localRect, GUIContent.none, k_ConnectorIcon);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a809a4b50addbf44b9023b5e7f9fd4d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
enum Layer : byte
{
Clips,
ClipHandles,
Markers,
MarkerHeaderTrack,
MarkersOnHeader
}
struct LayerZOrder : IComparable<LayerZOrder>
{
Layer m_Layer;
int m_ZOrder;
public LayerZOrder(Layer layer, int zOrder)
{
m_Layer = layer;
m_ZOrder = zOrder;
}
public int CompareTo(LayerZOrder other)
{
if (m_Layer == other.m_Layer)
return m_ZOrder.CompareTo(other.m_ZOrder);
return m_Layer.CompareTo(other.m_Layer);
}
public static LayerZOrder operator++(LayerZOrder x)
{
return new LayerZOrder(x.m_Layer, x.m_ZOrder + 1);
}
public LayerZOrder ChangeLayer(Layer layer)
{
return new LayerZOrder(layer, m_ZOrder);
}
}
interface ILayerable
{
LayerZOrder zOrder { get; }
}
interface IZOrderProvider
{
LayerZOrder Next();
}
interface ILayer : IZOrderProvider
{
void Draw(Rect rect, WindowState state);
}
abstract class ItemsLayer<T> : ILayer where T : TimelineItemGUI
{
// provide a buffer for time-based culling to allow for UI that extends slightly beyond the time (e.g. markers)
// prevents popping of marker visibility.
const int kVisibilityBufferInPixels = 10;
int m_PreviousLayerStateHash = -1;
LayerZOrder m_LastZOrder;
public LayerZOrder Next()
{
m_NeedSort = true;
return m_LastZOrder++;
}
readonly List<T> m_Items = new List<T>();
bool m_NeedSort = true;
public virtual void Draw(Rect rect, WindowState state)
{
if (m_Items.Count <= 0) return;
Sort();
// buffer to prevent flickering of markers at boundaries
var onePixelTime = state.PixelDeltaToDeltaTime(kVisibilityBufferInPixels);
var visibleTime = state.timeAreaShownRange + new Vector2(-onePixelTime, onePixelTime);
var layerViewStateHasChanged = GetLayerViewStateChanged(rect, state);
foreach (var item in m_Items)
{
item.visible = item.end > visibleTime.x && item.start < visibleTime.y;
if (!item.visible)
continue;
item.Draw(rect, layerViewStateHasChanged, state);
}
}
public List<T> items => m_Items;
protected void AddItem(T item)
{
m_Items.Add(item);
m_NeedSort = true;
}
protected ItemsLayer(Layer layerOrder)
{
m_LastZOrder = new LayerZOrder(layerOrder, 0);
}
void Sort()
{
if (!m_NeedSort)
return;
m_Items.Sort((a, b) => a.zOrder.CompareTo(b.zOrder));
m_NeedSort = false;
}
bool GetLayerViewStateChanged(Rect rect, WindowState state)
{
var layerStateHash = rect.GetHashCode().CombineHash(state.viewStateHash);
var layerViewStateHasChanged = layerStateHash != m_PreviousLayerStateHash;
if (Event.current.type == EventType.Layout && layerViewStateHasChanged)
m_PreviousLayerStateHash = layerStateHash;
return layerViewStateHasChanged;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef97f39912c138b4cabdccedfb24093b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class MarkersLayer : ItemsLayer<TimelineItemGUI>
{
public MarkersLayer(Layer layerOrder, IRowGUI parent) : base(layerOrder)
{
CreateLists(parent);
}
void CreateLists(IRowGUI parent)
{
var markerCount = parent.asset.GetMarkerCount();
if (markerCount == 0) return;
var accumulator = new List<IMarker>();
var sortedMarkers = new List<IMarker>(parent.asset.GetMarkers());
var vm = TimelineWindowViewPrefs.GetTrackViewModelData(parent.asset);
sortedMarkers.Sort((lhs, rhs) =>
{
// Sort by time first
var timeComparison = lhs.time.CompareTo(rhs.time);
if (timeComparison != 0)
return timeComparison;
// If there's a collision, sort by edit timestamp
var lhsObject = lhs as object;
var rhsObject = rhs as object;
if (lhsObject.Equals(null) || rhsObject.Equals(null))
return 0;
var lhsHash = lhsObject.GetHashCode();
var rhsHash = rhsObject.GetHashCode();
if (vm.markerTimeStamps.ContainsKey(lhsHash) && vm.markerTimeStamps.ContainsKey(rhsHash))
return vm.markerTimeStamps[lhsHash].CompareTo(vm.markerTimeStamps[rhsHash]);
return 0;
});
foreach (var current in sortedMarkers)
{
// TODO: Take zoom factor into account?
if (accumulator.Count > 0 && Math.Abs(current.time - accumulator[accumulator.Count - 1].time) > TimeUtility.kTimeEpsilon)
ProcessAccumulator(accumulator, parent);
accumulator.Add(current);
}
ProcessAccumulator(accumulator, parent);
}
void ProcessAccumulator(List<IMarker> accumulator, IRowGUI parent)
{
if (accumulator.Count == 0) return;
if (accumulator.Count == 1)
{
AddItem(new TimelineMarkerGUI(accumulator[0], parent, this));
}
else
{
// Ensure that the cluster is always considered *below* the markers it contains.
var clusterZOrder = Next();
AddItem(
new TimelineMarkerClusterGUI(
accumulator.Select(m => new TimelineMarkerGUI(m, parent, this)).ToList(),
parent, this, clusterZOrder));
}
accumulator.Clear();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bea62e1faac8f9a48a4cb919ea05cb6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,50 @@
using System;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrackDrawer : GUIDrawer
{
internal WindowState sequencerState { get; set; }
public static TrackDrawer CreateInstance(TrackAsset trackAsset)
{
if (trackAsset == null)
return Activator.CreateInstance<TrackDrawer>();
TrackDrawer drawer;
try
{
drawer = (TrackDrawer)Activator.CreateInstance(TimelineHelpers.GetCustomDrawer(trackAsset.GetType()));
}
catch (Exception)
{
drawer = Activator.CreateInstance<TrackDrawer>();
}
drawer.track = trackAsset;
return drawer;
}
protected TrackAsset track { get; private set; }
public virtual bool HasCustomTrackHeaderButton()
{
return false;
}
public virtual void DrawTrackHeaderButton(Rect rect, WindowState state) {}
public virtual bool DrawTrack(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
return false;
}
public virtual void DrawRecordingBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
EditorGUI.DrawRect(trackRect, DirectorStyles.Instance.customSkin.colorTrackBackgroundRecording);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a0f991b6c2f45b44b92e163f9969e8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,38 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Timeline
{
struct TrackItemsDrawer
{
List<ILayer> m_Layers;
ClipsLayer m_ClipsLayer;
public List<TimelineClipGUI> clips => m_ClipsLayer.items;
public TrackItemsDrawer(IRowGUI parent)
{
m_Layers = null;
m_ClipsLayer = null;
BuildGUICache(parent);
}
void BuildGUICache(IRowGUI parent)
{
m_ClipsLayer = new ClipsLayer(Layer.Clips, parent);
m_Layers = new List<ILayer>
{
m_ClipsLayer,
new MarkersLayer(Layer.Markers, parent)
};
}
public void Draw(Rect rect, WindowState state)
{
foreach (var layer in m_Layers)
{
layer.Draw(rect, state);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7137daaeb11e8647bf1ade9b7e9aa97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditorInternal;
using UnityEngine;
namespace UnityEditor.Timeline
{
interface IPropertyKeyDataSource
{
float[] GetKeys(); // Get the keys
Dictionary<float, string> GetDescriptions(); // Caches for descriptions
}
abstract class BasePropertyKeyDataSource : IPropertyKeyDataSource
{
static readonly StringBuilder k_StringBuilder = new StringBuilder();
protected abstract AnimationClip animationClip { get; }
public virtual float[] GetKeys()
{
if (animationClip == null)
return null;
var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
return info.keyTimes.Select(TransformKeyTime).ToArray();
}
public virtual Dictionary<float, string> GetDescriptions()
{
var map = new Dictionary<float, string>();
var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
var processed = new HashSet<string>();
foreach (var b in info.bindings)
{
var groupID = b.GetGroupID();
if (processed.Contains(groupID))
continue;
var group = info.GetGroupBinding(groupID);
var prefix = AnimationWindowUtility.GetNicePropertyGroupDisplayName(b.type, b.propertyName);
foreach (var t in info.keyTimes)
{
k_StringBuilder.Length = 0;
var key = TransformKeyTime(t);
if (map.ContainsKey(key))
k_StringBuilder.Append(map[key])
.Append('\n');
k_StringBuilder.Append(prefix)
.Append(" : ")
.Append(group.GetDescription(key));
map[key] = k_StringBuilder.ToString();
}
processed.Add(groupID);
}
return map;
}
protected virtual float TransformKeyTime(float keyTime)
{
return keyTime;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee83d5e1ad2ac4c9aa39e3a6f256062e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
interface IRowGUI
{
TrackAsset asset { get; }
Rect boundingRect { get; }
bool locked { get; }
bool showMarkers { get; }
bool muted { get; }
Rect ToWindowSpace(Rect treeViewRect);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ecec8cdc3ff99e4a9c65635d7e6b043
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9d816a6ab06c6834480f5f45f440e287
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using System;
namespace UnityEditor.Timeline
{
interface ISelectable : ILayerable
{
void Select();
bool IsSelected();
void Deselect();
bool CanSelect(UnityEngine.Event evt);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4acbfc0398bab674f922f693e58f4afc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,796 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineClipGUI : TimelineItemGUI, IClipCurveEditorOwner, ISnappable, IAttractable
{
EditorClip m_EditorItem;
Rect m_ClipCenterSection;
readonly List<Rect> m_LoopRects = new List<Rect>();
ClipDrawData m_ClipDrawData;
Rect m_MixOutRect;
Rect m_MixInRect;
int m_MinLoopIndex = 1;
// clip dirty detection
int m_LastDirtyIndex = Int32.MinValue;
bool m_ClipViewDirty = true;
bool supportResize { get; }
public ClipCurveEditor clipCurveEditor { get; set; }
public TimelineClipGUI previousClip { get; set; }
public TimelineClipGUI nextClip { get; set; }
static readonly float k_MinMixWidth = 2;
static readonly float k_MaxHandleWidth = 10f;
static readonly float k_MinHandleWidth = 1f;
bool? m_ShowDrillIcon;
ClipEditor m_ClipEditor;
static List<PlayableDirector> s_TempSubDirectors = new List<PlayableDirector>();
static readonly IconData k_DiggableClipIcon = new IconData(DirectorStyles.LoadIcon("TimelineDigIn"));
string name
{
get
{
if (string.IsNullOrEmpty(clip.displayName))
return "(Empty)";
return clip.displayName;
}
}
public bool inlineCurvesSelected => SelectionManager.IsCurveEditorFocused(this);
public Rect mixOutRect
{
get
{
var percent = clip.mixOutPercentage;
var x = Mathf.Round(treeViewRect.width * (1 - percent));
var width = Mathf.Round(treeViewRect.width * percent);
m_MixOutRect.Set(x, 0.0f, width, treeViewRect.height);
return m_MixOutRect;
}
}
public Rect mixInRect
{
get
{
var width = Mathf.Round(treeViewRect.width * clip.mixInPercentage);
m_MixInRect.Set(0.0f, 0.0f, width, treeViewRect.height);
return m_MixInRect;
}
}
public ClipBlends GetClipBlends()
{
var _mixInRect = mixInRect;
var _mixOutRect = mixOutRect;
var blendInKind = BlendKind.None;
if (_mixInRect.width > k_MinMixWidth && clip.hasBlendIn)
blendInKind = BlendKind.Mix;
else if (_mixInRect.width > k_MinMixWidth)
blendInKind = BlendKind.Ease;
var blendOutKind = BlendKind.None;
if (_mixOutRect.width > k_MinMixWidth && clip.hasBlendOut)
blendOutKind = BlendKind.Mix;
else if (_mixOutRect.width > k_MinMixWidth)
blendOutKind = BlendKind.Ease;
return new ClipBlends(blendInKind, _mixInRect, blendOutKind, _mixOutRect);
}
public override double start
{
get { return clip.start; }
}
public override double end
{
get { return clip.end; }
}
public bool supportsLooping
{
get { return clip.SupportsLooping(); }
}
// for the inline curve editor, only show loops if we recorded the asset
bool IClipCurveEditorOwner.showLoops
{
get { return clip.SupportsLooping() && (clip.asset is AnimationPlayableAsset); }
}
TrackAsset IClipCurveEditorOwner.owner
{
get { return clip.GetParentTrack(); }
}
public bool supportsSubTimelines
{
get { return m_ClipEditor.supportsSubTimelines; }
}
public int minLoopIndex
{
get { return m_MinLoopIndex; }
}
public Rect clippedRect { get; private set; }
public override void Select()
{
MoveToTop();
SelectionManager.Add(clip);
if (clipCurveEditor != null && SelectionManager.Count() == 1)
SelectionManager.SelectInlineCurveEditor(this);
}
public override bool IsSelected()
{
return SelectionManager.Contains(clip);
}
public override void Deselect()
{
SelectionManager.Remove(clip);
if (inlineCurvesSelected)
SelectionManager.SelectInlineCurveEditor(null);
}
public override bool CanSelect(Event evt)
{
ClipBlends clipBlends = GetClipBlends();
Vector2 mousePos = evt.mousePosition - rect.position;
return m_ClipCenterSection.Contains(mousePos) || IsPointLocatedInClipBlend(mousePos, clipBlends);
}
static bool IsPointLocatedInClipBlend(Vector2 pt, ClipBlends blends)
{
if (blends.inRect.Contains(pt))
{
if (blends.inKind == BlendKind.Mix)
return Sign(pt, blends.inRect.min, blends.inRect.max) < 0;
return true;
}
if (blends.outRect.Contains(pt))
{
if (blends.outKind == BlendKind.Mix)
return Sign(pt, blends.outRect.min, blends.outRect.max) >= 0;
return true;
}
return false;
}
static float Sign(Vector2 point, Vector2 linePoint1, Vector2 linePoint2)
{
return (point.x - linePoint2.x) * (linePoint1.y - linePoint2.y) - (linePoint1.x - linePoint2.x) * (point.y - linePoint2.y);
}
public override ITimelineItem item
{
get { return ItemsUtils.ToItem(clip); }
}
IZOrderProvider zOrderProvider { get; }
public TimelineClipHandle leftHandle { get; }
public TimelineClipHandle rightHandle { get; }
public TimelineClipGUI(TimelineClip clip, IRowGUI parent, IZOrderProvider provider) : base(parent)
{
zOrderProvider = provider;
zOrder = provider.Next();
m_EditorItem = EditorClipFactory.GetEditorClip(clip);
m_ClipEditor = CustomTimelineEditorCache.GetClipEditor(clip);
supportResize = true;
leftHandle = new TimelineClipHandle(this, TrimEdge.Start);
rightHandle = new TimelineClipHandle(this, TrimEdge.End);
ItemToItemGui.Add(clip, this);
}
void CreateInlineCurveEditor(WindowState state)
{
if (clipCurveEditor != null)
return;
var animationClip = clip.animationClip;
if (animationClip != null && animationClip.empty)
animationClip = null;
// prune out clips coming from FBX
if (animationClip != null && !clip.recordable)
return; // don't show, even if there are curves
if (animationClip == null && !clip.HasAnyAnimatableParameters())
return; // nothing to show
state.AddEndFrameDelegate((istate, currentEvent) =>
{
clipCurveEditor = new ClipCurveEditor(CurveDataSource.Create(this), TimelineWindow.instance, clip.GetParentTrack());
return true;
});
}
public TimelineClip clip
{
get { return m_EditorItem.clip; }
}
// Draw the actual clip. Defers to the track drawer for customization
void UpdateDrawData(WindowState state, Rect drawRect, string title, bool selected, bool previousClipSelected, float rectXOffset)
{
m_ClipDrawData.clip = clip;
m_ClipDrawData.targetRect = drawRect;
m_ClipDrawData.clipCenterSection = m_ClipCenterSection;
m_ClipDrawData.unclippedRect = treeViewRect;
m_ClipDrawData.title = title;
m_ClipDrawData.selected = selected;
m_ClipDrawData.inlineCurvesSelected = inlineCurvesSelected;
m_ClipDrawData.previousClip = previousClip != null ? previousClip.clip : null;
m_ClipDrawData.previousClipSelected = previousClipSelected;
Vector3 shownAreaTime = state.timeAreaShownRange;
m_ClipDrawData.localVisibleStartTime = clip.ToLocalTimeUnbound(Math.Max(clip.start, shownAreaTime.x));
m_ClipDrawData.localVisibleEndTime = clip.ToLocalTimeUnbound(Math.Min(clip.end, shownAreaTime.y));
m_ClipDrawData.clippedRect = new Rect(clippedRect.x - rectXOffset, 0.0f, clippedRect.width, clippedRect.height);
m_ClipDrawData.minLoopIndex = minLoopIndex;
m_ClipDrawData.loopRects = m_LoopRects;
m_ClipDrawData.supportsLooping = supportsLooping;
m_ClipDrawData.clipBlends = GetClipBlends();
m_ClipDrawData.clipEditor = m_ClipEditor;
m_ClipDrawData.ClipDrawOptions = UpdateClipDrawOptions(m_ClipEditor, clip);
UpdateClipIcons(state);
}
void UpdateClipIcons(WindowState state)
{
// Pass 1 - gather size
int required = 0;
bool requiresDigIn = ShowDrillIcon(state.editSequence.director);
if (requiresDigIn)
required++;
var icons = m_ClipDrawData.ClipDrawOptions.icons;
foreach (var icon in icons)
{
if (icon != null)
required++;
}
// Pass 2 - copy icon data
if (required == 0)
{
m_ClipDrawData.rightIcons = null;
return;
}
if (m_ClipDrawData.rightIcons == null || m_ClipDrawData.rightIcons.Length != required)
m_ClipDrawData.rightIcons = new IconData[required];
int index = 0;
if (requiresDigIn)
m_ClipDrawData.rightIcons[index++] = k_DiggableClipIcon;
foreach (var icon in icons)
{
if (icon != null)
m_ClipDrawData.rightIcons[index++] = new IconData(icon);
}
}
static ClipDrawOptions UpdateClipDrawOptions(ClipEditor clipEditor, TimelineClip clip)
{
try
{
return clipEditor.GetClipOptions(clip);
}
catch (Exception e)
{
Debug.LogException(e);
}
return CustomTimelineEditorCache.GetDefaultClipEditor().GetClipOptions(clip);
}
static void DrawClip(ClipDrawData drawData)
{
ClipDrawer.DrawDefaultClip(drawData);
if (drawData.clip.asset is AnimationPlayableAsset)
{
var state = TimelineWindow.instance.state;
if (state.recording && state.IsArmedForRecord(drawData.clip.GetParentTrack()))
{
ClipDrawer.DrawAnimationRecordBorder(drawData);
ClipDrawer.DrawRecordProhibited(drawData);
}
}
}
public void DrawGhostClip(Rect targetRect)
{
DrawSimpleClip(targetRect, ClipBorder.Selection(), new Color(1.0f, 1.0f, 1.0f, 0.5f));
}
public void DrawInvalidClip(Rect targetRect)
{
DrawSimpleClip(targetRect, ClipBorder.Selection(), DirectorStyles.Instance.customSkin.colorInvalidClipOverlay);
}
void DrawSimpleClip(Rect targetRect, ClipBorder border, Color overlay)
{
var drawOptions = UpdateClipDrawOptions(CustomTimelineEditorCache.GetClipEditor(clip), clip);
ClipDrawer.DrawSimpleClip(clip, targetRect, border, overlay, drawOptions);
}
void DrawInto(Rect drawRect, WindowState state)
{
if (Event.current.type != EventType.Repaint)
return;
// create the inline curve editor if not already created
CreateInlineCurveEditor(state);
// @todo optimization, most of the calculations (rect, offsets, colors, etc.) could be cached
// and rebuilt when the hash of the clip changes.
if (isInvalid)
{
DrawInvalidClip(treeViewRect);
return;
}
GUI.BeginClip(drawRect);
var originRect = new Rect(0.0f, 0.0f, drawRect.width, drawRect.height);
string clipLabel = name;
var selected = SelectionManager.Contains(clip);
var previousClipSelected = previousClip != null && SelectionManager.Contains(previousClip.clip);
if (selected && 1.0 != clip.timeScale)
clipLabel += " " + clip.timeScale.ToString("F2") + "x";
UpdateDrawData(state, originRect, clipLabel, selected, previousClipSelected, drawRect.x);
DrawClip(m_ClipDrawData);
GUI.EndClip();
if (clip.GetParentTrack() != null && !clip.GetParentTrack().lockedInHierarchy)
{
if (selected && supportResize)
{
var cursorRect = rect;
cursorRect.xMin += leftHandle.boundingRect.width;
cursorRect.xMax -= rightHandle.boundingRect.width;
EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.MoveArrow);
}
if (supportResize)
{
var handleWidth = Mathf.Clamp(drawRect.width * 0.3f, k_MinHandleWidth, k_MaxHandleWidth);
leftHandle.Draw(drawRect, handleWidth, state);
rightHandle.Draw(drawRect, handleWidth, state);
}
}
}
void CalculateClipRectangle(Rect trackRect, WindowState state)
{
if (m_ClipViewDirty)
{
var clipRect = RectToTimeline(trackRect, state);
treeViewRect = clipRect;
// calculate clipped rect
clipRect.xMin = Mathf.Max(clipRect.xMin, trackRect.xMin);
clipRect.xMax = Mathf.Min(clipRect.xMax, trackRect.xMax);
if (clipRect.width > 0 && clipRect.width < 2)
{
clipRect.width = 5.0f;
}
clippedRect = clipRect;
}
}
void AddToSpacePartitioner(WindowState state)
{
if (Event.current.type == EventType.Repaint && !parent.locked)
state.spacePartitioner.AddBounds(this, rect);
}
void CalculateBlendRect()
{
m_ClipCenterSection = treeViewRect;
m_ClipCenterSection.x = 0;
m_ClipCenterSection.y = 0;
m_ClipCenterSection.xMin = mixInRect.xMax;
m_ClipCenterSection.width = Mathf.Round(treeViewRect.width - mixInRect.width - mixOutRect.width);
m_ClipCenterSection.xMax = m_ClipCenterSection.xMin + m_ClipCenterSection.width;
}
// Entry point to the Clip Drawing...
public override void Draw(Rect trackRect, bool trackRectChanged, WindowState state)
{
// if the clip has changed, fire the appropriate callback
DetectClipChanged(trackRectChanged);
// update the clip projected rectangle on the timeline
CalculateClipRectangle(trackRect, state);
AddToSpacePartitioner(state);
// update the blend rects (when clip overlaps with others)
CalculateBlendRect();
// update the loop rects (when clip loops)
CalculateLoopRects(trackRect, state);
DrawExtrapolation(trackRect, treeViewRect);
DrawInto(treeViewRect, state);
ResetClipChanged();
}
void DetectClipChanged(bool trackRectChanged)
{
if (Event.current.type == EventType.Layout)
{
if (clip.DirtyIndex != m_LastDirtyIndex)
{
m_ClipViewDirty = true;
try
{
m_ClipEditor.OnClipChanged(clip);
}
catch (Exception e)
{
Debug.LogException(e);
}
m_LastDirtyIndex = clip.DirtyIndex;
}
m_ClipViewDirty |= trackRectChanged;
}
}
void ResetClipChanged()
{
if (Event.current.type == EventType.Repaint)
m_ClipViewDirty = false;
}
internal void MoveToTop()
{
zOrder = zOrderProvider.Next();
}
GUIStyle GetExtrapolationIcon(TimelineClip.ClipExtrapolation mode)
{
GUIStyle extrapolationIcon = null;
switch (mode)
{
case TimelineClip.ClipExtrapolation.None: return null;
case TimelineClip.ClipExtrapolation.Hold: extrapolationIcon = m_Styles.extrapolationHold; break;
case TimelineClip.ClipExtrapolation.Loop: extrapolationIcon = m_Styles.extrapolationLoop; break;
case TimelineClip.ClipExtrapolation.PingPong: extrapolationIcon = m_Styles.extrapolationPingPong; break;
case TimelineClip.ClipExtrapolation.Continue: extrapolationIcon = m_Styles.extrapolationContinue; break;
}
return extrapolationIcon;
}
Rect GetPreExtrapolationBounds(Rect trackRect, Rect clipRect, GUIStyle icon)
{
float x = clipRect.xMin - (icon.fixedWidth + 10.0f);
float y = trackRect.yMin + (trackRect.height - icon.fixedHeight) / 2.0f;
if (previousClip != null)
{
float distance = Mathf.Abs(treeViewRect.xMin - previousClip.treeViewRect.xMax);
if (distance < icon.fixedWidth)
return new Rect(0.0f, 0.0f, 0.0f, 0.0f);
if (distance < icon.fixedWidth + 20.0f)
{
float delta = (distance - icon.fixedWidth) / 2.0f;
x = clipRect.xMin - (icon.fixedWidth + delta);
}
}
return new Rect(x, y, icon.fixedWidth, icon.fixedHeight);
}
Rect GetPostExtrapolationBounds(Rect trackRect, Rect clipRect, GUIStyle icon)
{
float x = clipRect.xMax + 10.0f;
float y = trackRect.yMin + (trackRect.height - icon.fixedHeight) / 2.0f;
if (nextClip != null)
{
float distance = Mathf.Abs(nextClip.treeViewRect.xMin - treeViewRect.xMax);
if (distance < icon.fixedWidth)
return new Rect(0.0f, 0.0f, 0.0f, 0.0f);
if (distance < icon.fixedWidth + 20.0f)
{
float delta = (distance - icon.fixedWidth) / 2.0f;
x = clipRect.xMax + delta;
}
}
return new Rect(x, y, icon.fixedWidth, icon.fixedHeight);
}
static void DrawExtrapolationIcon(Rect rect, GUIStyle icon)
{
GUI.Label(rect, GUIContent.none, icon);
}
void DrawExtrapolation(Rect trackRect, Rect clipRect)
{
if (clip.hasPreExtrapolation)
{
GUIStyle icon = GetExtrapolationIcon(clip.preExtrapolationMode);
if (icon != null)
{
Rect iconBounds = GetPreExtrapolationBounds(trackRect, clipRect, icon);
if (iconBounds.width > 1 && iconBounds.height > 1)
DrawExtrapolationIcon(iconBounds, icon);
}
}
if (clip.hasPostExtrapolation)
{
GUIStyle icon = GetExtrapolationIcon(clip.postExtrapolationMode);
if (icon != null)
{
Rect iconBounds = GetPostExtrapolationBounds(trackRect, clipRect, icon);
if (iconBounds.width > 1 && iconBounds.height > 1)
DrawExtrapolationIcon(iconBounds, icon);
}
}
}
static Rect ProjectRectOnTimeline(Rect rect, Rect trackRect, WindowState state)
{
Rect newRect = rect;
// transform clipRect into pixel-space
newRect.x *= state.timeAreaScale.x;
newRect.width *= state.timeAreaScale.x;
newRect.x += state.timeAreaTranslation.x + trackRect.xMin;
// adjust clipRect height and vertical centering
const int clipPadding = 2;
newRect.y = trackRect.y + clipPadding;
newRect.height = trackRect.height - (2 * clipPadding);
return newRect;
}
void CalculateLoopRects(Rect trackRect, WindowState state)
{
if (!m_ClipViewDirty)
return;
m_LoopRects.Clear();
if (clip.duration < WindowState.kTimeEpsilon)
return;
var times = TimelineHelpers.GetLoopTimes(clip);
var loopDuration = TimelineHelpers.GetLoopDuration(clip);
m_MinLoopIndex = -1;
// we have a hold, no need to compute all loops
if (!supportsLooping)
{
if (times.Length > 1)
{
var t = times[1];
float loopTime = (float)(clip.duration - t);
m_LoopRects.Add(ProjectRectOnTimeline(new Rect((float)(t + clip.start), 0, loopTime, 0), trackRect, state));
}
return;
}
var range = state.timeAreaShownRange;
var visibleStartTime = range.x - clip.start;
var visibleEndTime = range.y - clip.start;
for (int i = 1; i < times.Length; i++)
{
var t = times[i];
// don't draw off screen loops
if (t > visibleEndTime)
break;
float loopTime = Mathf.Min((float)(clip.duration - t), (float)loopDuration);
var loopEnd = t + loopTime;
if (loopEnd < visibleStartTime)
continue;
m_LoopRects.Add(ProjectRectOnTimeline(new Rect((float)(t + clip.start), 0, loopTime, 0), trackRect, state));
if (m_MinLoopIndex == -1)
m_MinLoopIndex = i;
}
}
public override Rect RectToTimeline(Rect trackRect, WindowState state)
{
var offsetFromTimeSpaceToPixelSpace = state.timeAreaTranslation.x + trackRect.xMin;
var start = (float)(DiscreteTime)clip.start;
var end = (float)(DiscreteTime)clip.end;
return Rect.MinMaxRect(
Mathf.Round(start * state.timeAreaScale.x + offsetFromTimeSpaceToPixelSpace), Mathf.Round(trackRect.yMin),
Mathf.Round(end * state.timeAreaScale.x + offsetFromTimeSpaceToPixelSpace), Mathf.Round(trackRect.yMax)
);
}
public IEnumerable<Edge> SnappableEdgesFor(IAttractable attractable, ManipulateEdges manipulateEdges)
{
var edges = new List<Edge>();
bool canAddEdges = !parent.muted;
if (canAddEdges)
{
// Hack: Trim Start in Ripple mode should not have any snap point added
if (EditMode.editType == EditMode.EditType.Ripple && manipulateEdges == ManipulateEdges.Left)
return edges;
if (attractable != this)
{
if (EditMode.editType == EditMode.EditType.Ripple)
{
bool skip = false;
// Hack: Since Trim End and Move in Ripple mode causes other snap point to move on the same track (which is not supported), disable snapping for this special cases...
// TODO Find a proper way to have different snap edges for each edit mode.
if (manipulateEdges == ManipulateEdges.Right)
{
var otherClipGUI = attractable as TimelineClipGUI;
skip = otherClipGUI != null && otherClipGUI.parent == parent;
}
else if (manipulateEdges == ManipulateEdges.Both)
{
var moveHandler = attractable as MoveItemHandler;
skip = moveHandler != null && moveHandler.movingItems.Any(clips => clips.targetTrack == clip.GetParentTrack() && clip.start >= clips.start);
}
if (skip)
return edges;
}
AddEdge(edges, clip.start);
AddEdge(edges, clip.end);
}
else
{
if (manipulateEdges == ManipulateEdges.Right)
{
var d = TimelineHelpers.GetClipAssetEndTime(clip);
if (d < double.MaxValue)
{
if (clip.SupportsLooping())
{
var l = TimelineHelpers.GetLoopDuration(clip);
var shownTime = TimelineWindow.instance.state.timeAreaShownRange;
do
{
AddEdge(edges, d, false);
d += l;
}
while (d < shownTime.y);
}
else
{
AddEdge(edges, d, false);
}
}
}
if (manipulateEdges == ManipulateEdges.Left)
{
var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip);
if (clipInfo != null && clipInfo.keyTimes.Any())
AddEdge(edges, clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()), false);
}
}
}
return edges;
}
public bool ShouldSnapTo(ISnappable snappable)
{
return true;
}
bool ShowDrillIcon(PlayableDirector resolver)
{
if (!m_ShowDrillIcon.HasValue || TimelineWindow.instance.hierarchyChangedThisFrame)
{
var nestable = m_ClipEditor.supportsSubTimelines;
m_ShowDrillIcon = nestable && resolver != null;
if (m_ShowDrillIcon.Value)
{
s_TempSubDirectors.Clear();
try
{
m_ClipEditor.GetSubTimelines(clip, resolver, s_TempSubDirectors);
}
catch (Exception e)
{
Debug.LogException(e);
}
m_ShowDrillIcon &= s_TempSubDirectors.Count > 0;
}
}
return m_ShowDrillIcon.Value;
}
static void AddEdge(List<Edge> edges, double time, bool showEdgeHint = true)
{
var shownTime = TimelineWindow.instance.state.timeAreaShownRange;
if (time >= shownTime.x && time <= shownTime.y)
edges.Add(new Edge(time, showEdgeHint));
}
public void SelectCurves()
{
SelectionManager.SelectOnly(clip);
SelectionManager.SelectInlineCurveEditor(this);
}
public void ValidateCurvesSelection()
{
if (!IsSelected()) //if clip is not selected, deselect the inline curve
SelectionManager.SelectInlineCurveEditor(null);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c01b61b3a6887c49a15276fd38be918
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using UnityEngine.Timeline;
using UnityEngine;
namespace UnityEditor.Timeline
{
static class ItemToItemGui
{
static Dictionary<object, TimelineItemGUI> s_ItemToItemGUI =
new Dictionary<object, TimelineItemGUI>();
public static void Add(TimelineClip clip, TimelineItemGUI gui)
{
s_ItemToItemGUI[clip] = gui;
}
public static void Add(IMarker marker, TimelineItemGUI gui)
{
s_ItemToItemGUI[marker] = gui;
}
public static TimelineClipGUI GetGuiForClip(TimelineClip clip)
{
return GetGuiForItem(clip) as TimelineClipGUI;
}
public static TimelineMarkerGUI GetGuiForMarker(IMarker marker)
{
return GetGuiForItem(marker) as TimelineMarkerGUI;
}
static TimelineItemGUI GetGuiForItem(object item)
{
if (item == null)
return null;
TimelineItemGUI gui;
s_ItemToItemGUI.TryGetValue(item, out gui);
return gui;
}
}
abstract class TimelineItemGUI : ISelectable
{
protected readonly DirectorStyles m_Styles;
public abstract ITimelineItem item { get; }
public abstract double start { get; }
public abstract double end { get; }
public abstract void Draw(Rect rect, bool rectChanged, WindowState state);
public abstract Rect RectToTimeline(Rect trackRect, WindowState state);
public virtual void Select() {}
public virtual bool IsSelected() { return false; }
public virtual void Deselect() {}
public virtual bool CanSelect(Event evt) { return true; }
public virtual void StartDrag() {}
public virtual void StopDrag() {}
public LayerZOrder zOrder { get; set; }
public bool visible { get; set; }
public bool isInvalid { get; set; }
public IRowGUI parent { get; }
public Rect rect
{
get { return parent.ToWindowSpace(treeViewRect); }
}
public Rect treeViewRect
{
get { return m_TreeViewRect; }
protected set
{
m_TreeViewRect = value;
if (value.width < 0.0f)
m_TreeViewRect.width = 1.0f;
}
}
Rect m_TreeViewRect;
protected TimelineItemGUI(IRowGUI parent)
{
this.parent = parent;
m_Styles = DirectorStyles.Instance;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f515f8ecd3b6a546b90abaae2553f99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Timeline
{
class TimelineMarkerClusterGUI : TimelineItemGUI
{
readonly List<TimelineMarkerGUI> m_MarkerGUIs;
readonly IZOrderProvider m_ZOrderProvider;
public TimelineMarkerGUI topMarker
{
get { return m_MarkerGUIs.LastOrDefault(); }
}
TimelineMarkerGUI m_ManipulatedMarker;
public TimelineMarkerClusterGUI(List<TimelineMarkerGUI> guis, IRowGUI parent,
IZOrderProvider zOrderProvider, LayerZOrder layerZOrder)
: base(parent)
{
m_MarkerGUIs = guis;
m_ZOrderProvider = zOrderProvider;
zOrder = layerZOrder;
SortMarkers();
topMarker.onStartDrag += OnDragTopMarker;
}
public override double start
{
get { return topMarker.start; }
}
public override double end
{
get { return topMarker.end; }
}
public override ITimelineItem item
{
get { return topMarker.item; }
}
public override void Select()
{
foreach (var marker in m_MarkerGUIs)
{
if (!marker.IsSelected())
marker.Select();
}
}
public override void Deselect()
{
foreach (var marker in m_MarkerGUIs)
{
if (marker.IsSelected())
marker.Deselect();
}
}
public override void Draw(Rect trackRect, bool trackRectChanged, WindowState state)
{
RegisterRect(state);
topMarker.Draw(trackRect, trackRectChanged, state);
if (m_MarkerGUIs.Count > 1)
GUI.Box(treeViewRect, String.Empty, DirectorStyles.Instance.markerMultiOverlay);
if (m_ManipulatedMarker != null)
m_ManipulatedMarker.Draw(trackRect, trackRectChanged, state);
}
public override Rect RectToTimeline(Rect trackRect, WindowState state)
{
return topMarker.RectToTimeline(trackRect, state);
}
public void CycleTop()
{
if (m_MarkerGUIs.Count < 2)
return;
topMarker.onStartDrag -= OnDragTopMarker;
var last = topMarker;
for (int i = 0; i < m_MarkerGUIs.Count; ++i)
{
var next = m_MarkerGUIs[i];
m_MarkerGUIs[i] = last;
last = next;
}
topMarker.zOrder = m_ZOrderProvider.Next();
topMarker.onStartDrag += OnDragTopMarker;
}
void OnDragTopMarker()
{
m_ManipulatedMarker = topMarker;
m_ManipulatedMarker.onStartDrag -= OnDragTopMarker;
m_MarkerGUIs.RemoveAt(m_MarkerGUIs.Count - 1);
}
void SortMarkers()
{
m_MarkerGUIs.Sort((lhs, rhs) => lhs.zOrder.CompareTo(rhs.zOrder));
}
void RegisterRect(WindowState state)
{
treeViewRect = topMarker.treeViewRect;
if (Event.current.type == EventType.Repaint && !parent.locked)
state.spacePartitioner.AddBounds(this, rect);
}
public static bool CanCycleMarkers()
{
if (!SelectionManager.SelectedMarkers().Any())
return false;
var cluster = PickerUtils.TopmostPickedItemOfType<TimelineMarkerClusterGUI>();
if (cluster == null)
return false;
// Only cycle if the marker is selected and nothing else is selected
return cluster.topMarker.IsSelected() && SelectionManager.Count() == 1;
}
public static void CycleMarkers()
{
var cluster = PickerUtils.TopmostPickedItemOfType<TimelineMarkerClusterGUI>();
if (cluster == null)
return;
cluster.topMarker.Deselect();
cluster.CycleTop();
cluster.topMarker.Select();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c03ae9aa36a4fd44a983831f44654be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Timeline
{
class TimelineMarkerGUI : TimelineItemGUI, ISnappable, IAttractable
{
public event System.Action onStartDrag;
int m_ProjectedClipHash;
int m_MarkerHash;
bool m_Selectable;
MarkerDrawOptions m_MarkerDrawOptions;
MarkerEditor m_Editor;
IMarker marker { get; }
bool selectable
{
get { return m_Selectable; }
}
public double time
{
get { return marker.time; }
}
public override double start
{
get { return time; }
}
public override double end
{
get { return time; }
}
public override void Select()
{
zOrder = zOrderProvider.Next();
SelectionManager.Add(marker);
TimelineWindowViewPrefs.GetTrackViewModelData(parent.asset).markerTimeStamps[m_MarkerHash] = DateTime.UtcNow.Ticks;
}
public override bool IsSelected()
{
return SelectionManager.Contains(marker);
}
public override void Deselect()
{
SelectionManager.Remove(marker);
}
public override ITimelineItem item
{
get { return ItemsUtils.ToItem(marker); }
}
IZOrderProvider zOrderProvider { get; }
public TimelineMarkerGUI(IMarker theMarker, IRowGUI parent, IZOrderProvider provider) : base(parent)
{
marker = theMarker;
m_Selectable = marker.GetType().IsSubclassOf(typeof(UnityObject));
m_MarkerHash = 0;
var o = marker as object;
if (!o.Equals(null))
m_MarkerHash = o.GetHashCode();
zOrderProvider = provider;
zOrder = zOrderProvider.Next();
ItemToItemGui.Add(marker, this);
m_Editor = CustomTimelineEditorCache.GetMarkerEditor(theMarker);
}
int ComputeDirtyHash()
{
return time.GetHashCode();
}
static void DrawMarker(Rect drawRect, Type type, bool isSelected, bool isCollapsed, MarkerDrawOptions options)
{
if (Event.current.type == EventType.Repaint)
{
bool hasError = !string.IsNullOrEmpty(options.errorText);
var style = StyleManager.UssStyleForType(type);
style.Draw(drawRect, GUIContent.none, false, false, !isCollapsed, isSelected);
// case1141836: Use of GUI.Box instead of GUI.Label causes desync in UI controlID
if (hasError)
GUI.Label(drawRect, String.Empty, DirectorStyles.Instance.markerWarning);
var tooltip = hasError ? options.errorText : options.tooltip;
if (!string.IsNullOrEmpty(tooltip) && drawRect.Contains(Event.current.mousePosition))
{
GUIStyle.SetMouseTooltip(tooltip, drawRect);
}
}
}
void UpdateDrawData()
{
if (Event.current.type == EventType.Layout)
{
try
{
m_MarkerDrawOptions = m_Editor.GetMarkerOptions(marker);
}
catch (Exception e)
{
Debug.LogException(e);
m_MarkerDrawOptions = CustomTimelineEditorCache.GetDefaultMarkerEditor().GetMarkerOptions(marker);
}
}
}
public override void Draw(Rect trackRect, bool trackRectChanged, WindowState state)
{
UpdateDrawData();
// compute marker hash
var currentMarkerHash = ComputeDirtyHash();
// update the clip projected rectangle on the timeline
CalculateClipRectangle(trackRect, state, currentMarkerHash, trackRectChanged);
var isSelected = selectable && SelectionManager.Contains(marker);
var showMarkers = parent.showMarkers;
QueueOverlay(treeViewRect, isSelected, !showMarkers);
DrawMarker(treeViewRect, marker.GetType(), isSelected, !showMarkers, m_MarkerDrawOptions);
if (Event.current.type == EventType.Repaint && showMarkers && !parent.locked)
state.spacePartitioner.AddBounds(this, rect);
}
public void QueueOverlay(Rect rect, bool isSelected, bool isCollapsed)
{
if (Event.current.type == EventType.Repaint && m_Editor.supportsDrawOverlay)
{
rect = GUIClip.Unclip(rect);
TimelineWindow.instance.AddUserOverlay(marker, rect, m_Editor, isCollapsed, isSelected);
}
}
public override void StartDrag()
{
if (onStartDrag != null)
onStartDrag.Invoke();
}
void CalculateClipRectangle(Rect trackRect, WindowState state, int projectedClipHash, bool trackRectChanged)
{
if (m_ProjectedClipHash == projectedClipHash && !trackRectChanged)
return;
m_ProjectedClipHash = projectedClipHash;
treeViewRect = RectToTimeline(trackRect, state);
}
public override Rect RectToTimeline(Rect trackRect, WindowState state)
{
var style = StyleManager.UssStyleForType(marker.GetType());
var width = style.fixedWidth;
var height = style.fixedHeight;
var x = ((float)marker.time * state.timeAreaScale.x) + state.timeAreaTranslation.x + trackRect.xMin;
x -= 0.5f * width;
return new Rect(x, trackRect.y, width, height);
}
public IEnumerable<Edge> SnappableEdgesFor(IAttractable attractable, ManipulateEdges manipulateEdges)
{
var edges = new List<Edge>();
var attractableGUI = attractable as TimelineMarkerGUI;
var canAddEdges = !(attractableGUI != null && attractableGUI.parent == parent);
if (canAddEdges)
edges.Add(new Edge(time));
return edges;
}
public bool ShouldSnapTo(ISnappable snappable)
{
return snappable != this;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d34348f8b97a334291f5cf31adc5d67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,54 @@
using UnityEditor.Timeline.Actions;
using UnityEngine;
namespace UnityEditor.Timeline
{
class DrillIntoClip : Manipulator
{
protected override bool DoubleClick(Event evt, WindowState state)
{
if (evt.button != 0)
return false;
var guiClip = PickerUtils.TopmostPickedItem() as TimelineClipGUI;
if (guiClip == null)
return false;
if (!TimelineWindow.instance.state.editSequence.isReadOnly && (guiClip.clip.curves != null || guiClip.clip.animationClip != null))
Invoker.Invoke<EditClipInAnimationWindow>(new[] {guiClip.clip});
if (guiClip.supportsSubTimelines)
Invoker.Invoke<EditSubTimeline>(new[] {guiClip.clip});
return true;
}
}
class ContextMenuManipulator : Manipulator
{
protected override bool MouseDown(Event evt, WindowState state)
{
if (evt.button == 1)
ItemSelection.HandleSingleSelection(evt);
return false;
}
protected override bool ContextClick(Event evt, WindowState state)
{
if (evt.alt)
return false;
var selectable = PickerUtils.TopmostPickedItem() as ISelectable;
if (selectable != null && selectable.IsSelected())
{
SequencerContextMenu.ShowItemContextMenu(evt.mousePosition);
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b222f61a1f7253e4d8e8cc82bfde9e42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,274 @@
using System;
using System.Linq;
using UnityEditor.ShortcutManagement;
using UnityEditor.Timeline.Actions;
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimelinePanManipulator : Manipulator
{
const float k_MaxPanSpeed = 50.0f;
bool m_Active;
protected override bool MouseDown(Event evt, WindowState state)
{
if ((evt.button == 2 && evt.modifiers == EventModifiers.None) ||
(evt.button == 0 && evt.modifiers == EventModifiers.Alt))
{
TimelineCursors.SetCursor(TimelineCursors.CursorType.Pan);
m_Active = true;
return true;
}
return false;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (m_Active)
{
TimelineCursors.ClearCursor();
state.editorWindow.Repaint();
}
return false;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
// Note: Do not rely on evt.button here as some 3rd party automation
// software does not properly set the button data during drag.
if (!m_Active)
return false;
return Pan(evt, state);
}
protected override bool MouseWheel(Event evt, WindowState state)
{
if (Math.Abs(evt.delta.x) < 1e-5 || Math.Abs(evt.delta.x) <= Math.Abs(evt.delta.y))
return false;
TimelineZoomManipulator.InvalidateWheelZoom();
var panEvent = new Event(evt);
panEvent.delta = new Vector2(panEvent.delta.x * k_MaxPanSpeed * -1.0f, 0.0f);
return Pan(panEvent, state);
}
static bool Pan(Event evt, WindowState state)
{
var cursorRect = TimelineWindow.instance.sequenceContentRect;
cursorRect.xMax = TimelineWindow.instance.position.xMax;
cursorRect.yMax = TimelineWindow.instance.position.yMax;
if (state.GetWindow() != null && state.GetWindow().treeView != null)
{
var scroll = state.GetWindow().treeView.scrollPosition;
scroll.y -= evt.delta.y;
state.GetWindow().treeView.scrollPosition = scroll;
state.OffsetTimeArea((int)evt.delta.x);
return true;
}
return false;
}
}
class TimelineZoomManipulator : Manipulator
{
Vector2 m_MouseDownPos = Vector2.zero;
float m_FocalTime;
float m_LastMouseMoveX = -1;
bool m_WheelUsedLast;
TimelineZoomManipulator() {}
public static readonly TimelineZoomManipulator Instance = new TimelineZoomManipulator();
internal void DoZoom(float zoomFactor)
{
var refRange = TimelineEditor.visibleTimeRange;
DoZoom(zoomFactor, refRange, (refRange.x + refRange.y) / 2);
// Force resetting the reference zoom after a Framing operation
InvalidateWheelZoom();
}
static void DoZoom(float zoomFactor, Vector2 refRange, float focalTime)
{
const float kMinRange = 0.05f; // matches zoomable area.
if (zoomFactor <= 0)
return;
var t = Mathf.Max(focalTime, refRange.x);
var x =(refRange.x + t * (zoomFactor - 1)) / zoomFactor;
var y = (refRange.y + t * (zoomFactor - 1)) / zoomFactor;
var newRange = Mathf.Abs(x - y) < kMinRange? refRange:new Vector2(
Mathf.Max(x, -WindowConstants.timeAreaShownRangePadding),
Mathf.Min(y, WindowState.kMaxShownTime));
if(newRange != refRange)
// Zoomable area does not protect 100% against crazy values
TimelineEditor.visibleTimeRange = newRange;
}
internal static void InvalidateWheelZoom()
{
Instance.m_WheelUsedLast = false;
}
protected override bool MouseDown(Event evt, WindowState state)
{
m_MouseDownPos = evt.mousePosition;
m_FocalTime = state.PixelToTime(m_MouseDownPos.x);
return false;
}
protected override bool MouseWheel(Event evt, WindowState state)
{
if (Math.Abs(evt.delta.y) < 1e-5)
return false;
var zoomRect = TimelineWindow.instance.sequenceContentRect;
zoomRect.yMax += TimelineWindow.instance.horizontalScrollbarHeight;
if (!zoomRect.Contains(evt.mousePosition))
return false;
if (!m_WheelUsedLast || Mathf.Abs(m_LastMouseMoveX - evt.mousePosition.x) > 1.0f)
{
m_LastMouseMoveX = evt.mousePosition.x;
m_FocalTime = state.PixelToTime(m_LastMouseMoveX);
}
var zoomFactor = -evt.delta.y * 0.02f + 1;
DoZoom(zoomFactor, state.timeAreaShownRange, m_FocalTime);
m_WheelUsedLast = true;
return true;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
// Fast zoom...
if (evt.modifiers != EventModifiers.Alt || evt.button != 1) return false;
var mouseMoveLength = Event.current.mousePosition - m_MouseDownPos;
var delta = Math.Abs(mouseMoveLength.x) > Math.Abs(mouseMoveLength.y)
? mouseMoveLength.x
: -mouseMoveLength.y;
var zoomFactor = PixelToZoom(delta);
DoZoom(zoomFactor, state.timeAreaShownRange, m_FocalTime);
m_WheelUsedLast = false;
return true;
}
static float PixelToZoom(float x)
{
const float pixel2Zoom = 1 / 300.0f;
x *= pixel2Zoom;
if (x < -0.75)
{
// Rational function that behaves like 1+x on [-0.75,inf) and asymptotically reaches zero on (-inf,-0.75]
// The coefficients were obtained by the following constraints:
//1) f(-0.75) = 0.25
//2) f'(-0.75) = 1 C1 continuity
//3) f(-3) = 0.001 (asymptotically zero)
return 1 / (98.6667f + 268.444f * x + 189.63f * x * x);
}
return 1 + x;
}
}
class TimelineShortcutManipulator : Manipulator
{
protected override bool ValidateCommand(Event evt, WindowState state)
{
return evt.commandName == EventCommandNames.Copy ||
evt.commandName == EventCommandNames.Paste ||
evt.commandName == EventCommandNames.Duplicate ||
evt.commandName == EventCommandNames.SelectAll ||
evt.commandName == EventCommandNames.Delete ||
evt.commandName == EventCommandNames.SoftDelete ||
evt.commandName == EventCommandNames.FrameSelected;
}
protected override bool ExecuteCommand(Event evt, WindowState state)
{
if (state.IsCurrentEditingASequencerTextField())
return false;
if (evt.commandName == EventCommandNames.SelectAll)
{
Invoker.InvokeWithSelected<SelectAllAction>();
return true;
}
if (evt.commandName == EventCommandNames.SoftDelete)
{
Invoker.InvokeWithSelected<DeleteAction>();
return true;
}
if (evt.commandName == EventCommandNames.FrameSelected)
{
Invoker.InvokeWithSelected<FrameSelectedAction>();
return true;
}
return ActionManager.HandleShortcut(evt);
}
}
class InlineCurvesShortcutManipulator : Manipulator
{
protected override bool ExecuteCommand(Event evt, WindowState state)
{
if (state.IsCurrentEditingASequencerTextField())
return false;
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
if (inlineCurveEditor == null || !inlineCurveEditor.inlineCurvesSelected)
return false;
if (evt.commandName != EventCommandNames.FrameSelected)
return false;
Invoker.InvokeWithSelected<FrameSelectedAction>();
return true;
}
// CurveEditor uses an hardcoded shortcut to execute the FrameAll action, preventing the ShortcutManager from
// ever picking it up. We have to hijack it to ensure our code is being run when framing inline curves.
protected override bool KeyDown(Event evt, WindowState state)
{
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
if (inlineCurveEditor == null || !inlineCurveEditor.inlineCurvesSelected)
return false;
// Not conflicting with the hardcoded value
if (evt.keyCode != KeyCode.A)
return false;
var combination = ShortcutManager.instance.GetShortcutBinding(Shortcuts.Timeline.frameAll)
.keyCombinationSequence.ToList();
var shortcutCombination = combination.First();
var currentCombination = KeyCombination.FromKeyboardInput(evt);
// User is not actually pressing the correct key combination for FrameAll
if (combination.Count == 1 && shortcutCombination.Equals(currentCombination))
Invoker.InvokeWithSelected<FrameAllAction>();
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d029640460cf8ff47bbbfe69f49ddf29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,196 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Timeline.Actions;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class InlineCurveResize : Manipulator
{
bool m_Captured;
float m_CapturedHeight;
float m_CaptureMouseYPos;
InlineCurveResizeHandle m_Target;
protected override bool MouseDown(Event evt, WindowState state)
{
m_Target = PickerUtils.FirstPickedElementOfType<InlineCurveResizeHandle>();
if (m_Target == null)
return false;
m_Captured = true;
m_CapturedHeight = TimelineWindowViewPrefs.GetInlineCurveHeight(m_Target.trackGUI.track);
m_CaptureMouseYPos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition).y;
state.AddCaptured(this);
return true;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (!m_Captured || m_Target == null)
return false;
var trackGUI = m_Target.trackGUI;
float inlineTrackHeight = m_CapturedHeight +
(GUIUtility.GUIToScreenPoint(Event.current.mousePosition).y - m_CaptureMouseYPos);
TimelineWindowViewPrefs.SetInlineCurveHeight(trackGUI.track, Mathf.Max(inlineTrackHeight, 60.0f));
state.GetWindow().treeView.CalculateRowRects();
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_Captured)
return false;
state.RemoveCaptured(this);
m_Captured = false;
return true;
}
public override void Overlay(Event evt, WindowState state)
{
var rect = state.GetWindow().sequenceRect;
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeUpDown);
}
}
class TrackResize : Manipulator
{
bool m_Captured;
int m_NumberOfContributingTracks;
TrackResizeHandle m_Target;
List<TimelineTrackGUI> m_TracksToResize;
protected override bool MouseDown(Event evt, WindowState state)
{
m_Target = PickerUtils.FirstPickedElementOfType<TrackResizeHandle>();
if (m_Target == null)
return false;
m_NumberOfContributingTracks = 1;
var selectedTracks = SelectionManager.SelectedTrackGUI().ToList();
if (selectedTracks.Any() && selectedTracks.Contains(m_Target.trackGUI)) //resize all selected tracks
{
var allTrackGui = state.GetWindow().treeView.allTrackGuis;
m_TracksToResize = allTrackGui.OfType<TimelineTrackGUI>().Where(i => SelectionManager.Contains(i.track)).ToList();
m_NumberOfContributingTracks += m_TracksToResize.IndexOf(m_Target.trackGUI);
}
else
m_TracksToResize = new List<TimelineTrackGUI> { m_Target.trackGUI };
m_Captured = true;
state.AddCaptured(this);
return true;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (!m_Captured || m_Target == null)
return false;
var delta = evt.mousePosition.y - m_Target.boundingRect.center.y;
var extension = Mathf.RoundToInt(delta / m_NumberOfContributingTracks / state.trackScale);
foreach (var track in m_TracksToResize)
track.heightExtension += extension;
state.GetWindow().treeView.CalculateRowRects();
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_Captured)
return false;
foreach (var track in m_TracksToResize)
CommitExtension(track);
state.GetWindow().treeView.CalculateRowRects();
state.RemoveCaptured(this);
m_Captured = false;
return true;
}
public override void Overlay(Event evt, WindowState state)
{
var rect = state.GetWindow().sequenceRect;
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeUpDown);
}
static void CommitExtension(TimelineTrackGUI trackGUI)
{
if (trackGUI != null)
TimelineWindowViewPrefs.SetTrackHeightExtension(trackGUI.track, trackGUI.heightExtension);
}
}
class TrackDoubleClick : Manipulator
{
protected override bool DoubleClick(Event evt, WindowState state)
{
if (evt.button != 0)
return false;
var trackGUI = PickerUtils.FirstPickedElementOfType<TimelineTrackBaseGUI>();
if (trackGUI == null)
return false;
// Double-click is only available for AnimationTracks: it conflicts with selection mechanics on other tracks
if ((trackGUI.track as AnimationTrack) == null)
return false;
return EditTrackInAnimationWindow.Do(trackGUI.track);
}
}
class TrackShortcutManipulator : Manipulator
{
protected override bool KeyDown(Event evt, WindowState state)
{
return InternalExecute(evt, state);
}
protected override bool ExecuteCommand(Event evt, WindowState state)
{
return InternalExecute(evt, state);
}
static bool InternalExecute(Event evt, WindowState state)
{
if (state.IsCurrentEditingASequencerTextField())
return false;
var tracks = SelectionManager.SelectedTracks().ToList();
var items = SelectionManager.SelectedClipGUI();
foreach (var item in items)
{
var trackGUI = item.parent as TimelineTrackBaseGUI;
if (trackGUI == null)
continue;
if (!tracks.Contains(trackGUI.track))
tracks.Add(trackGUI.track);
}
return ActionManager.HandleShortcut(evt,
ActionManager.TrackActions,
x => ActionManager.ExecuteTrackAction(x, tracks));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5151708d47edd4344ba8e7bd469d966e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,103 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
abstract class Manipulator
{
int m_Id;
protected virtual bool MouseDown(Event evt, WindowState state) { return false; }
protected virtual bool MouseDrag(Event evt, WindowState state) { return false; }
protected virtual bool MouseWheel(Event evt, WindowState state) { return false; }
protected virtual bool MouseUp(Event evt, WindowState state) { return false; }
protected virtual bool DoubleClick(Event evt, WindowState state) { return false; }
protected virtual bool KeyDown(Event evt, WindowState state) { return false; }
protected virtual bool KeyUp(Event evt, WindowState state) { return false; }
protected virtual bool ContextClick(Event evt, WindowState state) { return false; }
protected virtual bool ValidateCommand(Event evt, WindowState state) { return false; }
protected virtual bool ExecuteCommand(Event evt, WindowState state) { return false; }
public virtual void Overlay(Event evt, WindowState state) {}
public bool HandleEvent(WindowState state)
{
Event currentEvent = Event.current;
var type = currentEvent.GetTypeForControl(m_Id);
return HandleEvent(type, currentEvent, state);
}
public bool HandleEvent(EventType type, WindowState state)
{
Event currentEvent = Event.current;
return HandleEvent(type, currentEvent, state);
}
bool HandleEvent(EventType type, Event evt, WindowState state)
{
if (m_Id == 0)
m_Id = GUIUtility.GetPermanentControlID();
bool isHandled = false;
switch (type)
{
case EventType.ScrollWheel:
isHandled = MouseWheel(evt, state);
break;
case EventType.MouseUp:
{
if (GUIUtility.hotControl == m_Id)
{
isHandled = MouseUp(evt, state);
GUIUtility.hotControl = 0;
evt.Use();
}
}
break;
case EventType.MouseDown:
{
isHandled = evt.clickCount < 2 ? MouseDown(evt, state) : DoubleClick(evt, state);
if (isHandled)
GUIUtility.hotControl = m_Id;
}
break;
case EventType.MouseDrag:
{
if (GUIUtility.hotControl == m_Id)
isHandled = MouseDrag(evt, state);
}
break;
case EventType.KeyDown:
isHandled = KeyDown(evt, state);
break;
case EventType.KeyUp:
isHandled = KeyUp(evt, state);
break;
case EventType.ContextClick:
isHandled = ContextClick(evt, state);
break;
case EventType.ValidateCommand:
isHandled = ValidateCommand(evt, state);
break;
case EventType.ExecuteCommand:
isHandled = ExecuteCommand(evt, state);
break;
}
if (isHandled)
evt.Use();
return isHandled;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bf68308f6cdff141ab98cd34174e4f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.Timeline
{
static class PickerUtils
{
public static List<object> pickedElements { get; private set; }
public static void DoPick(WindowState state, Vector2 mousePosition)
{
if (state.GetWindow().sequenceHeaderRect.Contains(mousePosition))
{
pickedElements = state.headerSpacePartitioner.GetItemsAtPosition<object>(mousePosition).ToList();
}
else if (state.GetWindow().sequenceContentRect.Contains(mousePosition))
{
pickedElements = state.spacePartitioner.GetItemsAtPosition<object>(mousePosition).ToList();
}
else
{
if (pickedElements != null)
pickedElements.Clear();
else
pickedElements = new List<object>();
}
}
public static ILayerable TopmostPickedItem()
{
return PickedItemsSortedByZOrderOfType<ILayerable>().FirstOrDefault();
}
public static T TopmostPickedItemOfType<T>() where T : class, ILayerable
{
return PickedItemsSortedByZOrderOfType<T>().FirstOrDefault();
}
public static T TopmostPickedItemOfType<T>(Func<T, bool> predicate) where T : class, ILayerable
{
return PickedItemsSortedByZOrderOfType<T>().FirstOrDefault(predicate);
}
static IEnumerable<T> PickedItemsSortedByZOrderOfType<T>() where T: class, ILayerable
{
return pickedElements.OfType<T>().OrderByDescending(x => x.zOrder);
}
public static T FirstPickedElementOfType<T>() where T : class, IBounds
{
return pickedElements.FirstOrDefault(e => e is T) as T;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16950289b516d6747868e0f7bf7b37a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 06ae1baf5524b314fa65b173b9eca869
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,21 @@
namespace UnityEditor.Timeline
{
enum AttractedEdge
{
None,
Left,
Right
}
interface IAttractable
{
bool ShouldSnapTo(ISnappable snappable);
double start { get; }
double end { get; }
}
interface IAttractionHandler
{
void OnAttractedEdge(IAttractable attractable, ManipulateEdges manipulateEdges, AttractedEdge edge, double time);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bfec54ce89b0b642a65d44def023b99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
struct Edge
{
public double time { get; set; }
public bool showSnapHint { get; set; }
public Edge(double edgeTime, bool snapHint = true) : this()
{
time = edgeTime;
showSnapHint = snapHint;
}
}
interface ISnappable
{
IEnumerable<Edge> SnappableEdgesFor(IAttractable attractable, ManipulateEdges manipulateEdges);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3664a2ade19bb7848a4d2c96ac9148e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
enum ManipulateEdges
{
Left,
Right,
Both
}
class SnapEngine
{
static readonly float k_MagnetInfluenceInPixels = 10.0f;
class SnapInfo
{
public double time { get; set; }
public bool showSnapHint { get; set; }
public bool IsInInfluenceZone(double currentTime, WindowState state)
{
var pos = state.TimeToPixel(currentTime);
var magnetPos = state.TimeToPixel(time);
return Math.Abs(pos - magnetPos) < k_MagnetInfluenceInPixels;
}
}
struct TimeBoundaries
{
public TimeBoundaries(double l, double r)
{
left = l;
right = r;
}
public readonly double left;
public readonly double right;
public TimeBoundaries Translate(double d)
{
return new TimeBoundaries(left + d, right + d);
}
}
public static bool displayDebugLayout;
readonly IAttractable m_Attractable;
readonly IAttractionHandler m_AttractionHandler;
readonly ManipulateEdges m_ManipulateEdges;
readonly WindowState m_State;
double m_GrabbedTime;
TimeBoundaries m_GrabbedTimes;
TimeBoundaries m_CurrentTimes;
readonly List<SnapInfo> m_Magnets = new List<SnapInfo>();
bool m_SnapEnabled;
public SnapEngine(IAttractable attractable, IAttractionHandler attractionHandler, ManipulateEdges manipulateEdges, WindowState state,
Vector2 mousePosition, IEnumerable<ISnappable> snappables = null)
{
m_Attractable = attractable;
m_ManipulateEdges = manipulateEdges;
m_AttractionHandler = attractionHandler;
m_State = state;
m_CurrentTimes = m_GrabbedTimes = new TimeBoundaries(m_Attractable.start, m_Attractable.end);
m_GrabbedTime = m_State.PixelToTime(mousePosition.x);
// Add Time zero as Magnet
AddMagnet(0.0, true, state);
// Add current Time as Magnet
// case1157280 only add current time as magnet if visible
if (TimelineWindow.instance.currentMode.ShouldShowTimeCursor(m_State))
AddMagnet(state.editSequence.time, true, state);
if (state.IsEditingASubTimeline())
{
// Add start and end of evaluable range as Magnets
// This includes the case where the master timeline has a fixed length
var range = state.editSequence.GetEvaluableRange();
AddMagnet(range.start, true, state);
AddMagnet(range.end, true, state);
}
else if (state.masterSequence.asset.durationMode == TimelineAsset.DurationMode.FixedLength)
{
// Add end sequence Time as Magnet
AddMagnet(state.masterSequence.asset.duration, true, state);
}
if (snappables == null)
snappables = GetVisibleSnappables(m_State);
foreach (var snappable in snappables)
{
if (!attractable.ShouldSnapTo(snappable))
continue;
var edges = snappable.SnappableEdgesFor(attractable, manipulateEdges);
foreach (var edge in edges)
AddMagnet(edge.time, edge.showSnapHint, state);
}
}
public static IEnumerable<ISnappable> GetVisibleSnappables(WindowState state)
{
Rect rect = TimelineWindow.instance.state.timeAreaRect;
rect.height = float.MaxValue;
return state.spacePartitioner.GetItemsInArea<ISnappable>(rect).ToArray();
}
void AddMagnet(double magnetTime, bool showSnapHint, WindowState state)
{
var magnet = m_Magnets.FirstOrDefault(m => m.time.Equals(magnetTime));
if (magnet == null)
{
if (IsMagnetInShownArea(magnetTime, state))
m_Magnets.Add(new SnapInfo { time = magnetTime, showSnapHint = showSnapHint });
}
else
{
magnet.showSnapHint |= showSnapHint;
}
}
static bool IsMagnetInShownArea(double time, WindowState state)
{
var shownArea = state.timeAreaShownRange;
return time >= shownArea.x && time <= shownArea.y;
}
SnapInfo GetMagnetAt(double time)
{
return m_Magnets.FirstOrDefault(m => m.time.Equals(time));
}
SnapInfo ClosestMagnet(double time)
{
SnapInfo candidate = null;
var min = double.MaxValue;
foreach (var magnetInfo in m_Magnets)
{
var m = Math.Abs(magnetInfo.time - time);
if (m < min)
{
candidate = magnetInfo;
min = m;
}
}
if (candidate != null && candidate.IsInInfluenceZone(time, m_State))
return candidate;
return null;
}
public void Snap(Vector2 currentMousePosition, EventModifiers modifiers)
{
var d = m_State.PixelToTime(currentMousePosition.x) - m_GrabbedTime;
m_CurrentTimes = m_GrabbedTimes.Translate(d);
bool isLeft = m_ManipulateEdges == ManipulateEdges.Left || m_ManipulateEdges == ManipulateEdges.Both;
bool isRight = m_ManipulateEdges == ManipulateEdges.Right || m_ManipulateEdges == ManipulateEdges.Both;
bool attracted = false;
m_SnapEnabled = modifiers == ManipulatorsUtils.actionModifier ? !m_State.edgeSnaps : m_State.edgeSnaps;
if (m_SnapEnabled)
{
SnapInfo leftActiveMagnet = null;
SnapInfo rightActiveMagnet = null;
if (isLeft)
leftActiveMagnet = ClosestMagnet(m_CurrentTimes.left);
if (isRight)
rightActiveMagnet = ClosestMagnet(m_CurrentTimes.right);
if (leftActiveMagnet != null || rightActiveMagnet != null)
{
attracted = true;
bool leftAttraction = false;
if (rightActiveMagnet == null)
{
// Attracted by a left magnet only.
leftAttraction = true;
}
else
{
if (leftActiveMagnet != null)
{
// Attracted by both magnets, choose the closest one.
var leftDistance = Math.Abs(leftActiveMagnet.time - m_CurrentTimes.left);
var rightDistance = Math.Abs(rightActiveMagnet.time - m_CurrentTimes.right);
leftAttraction = leftDistance <= rightDistance;
}
// else, Attracted by right magnet only
}
if (leftAttraction)
{
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Left, leftActiveMagnet.time);
}
else
{
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Right, rightActiveMagnet.time);
}
}
}
if (!attracted)
{
var time = isLeft ? m_CurrentTimes.left : m_CurrentTimes.right;
time = TimeReferenceUtility.SnapToFrameIfRequired(time);
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.None, time);
}
}
public void OnGUI(bool showLeft = true, bool showRight = true)
{
if (displayDebugLayout)
{
// Display Magnet influence zone
foreach (var m in m_Magnets)
{
var window = TimelineWindow.instance;
var rect = new Rect(m_State.TimeToPixel(m.time) - k_MagnetInfluenceInPixels, window.state.timeAreaRect.yMax, 2f * k_MagnetInfluenceInPixels, m_State.windowHeight);
EditorGUI.DrawRect(rect, new Color(1f, 0f, 0f, 0.4f));
}
// Display Cursor position
var mousePos = Event.current.mousePosition;
var time = m_State.PixelToTime(mousePos.x);
var p = new Vector2(m_State.TimeToPixel(time), TimelineWindow.instance.state.timeAreaRect.yMax);
var s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.blue);
p = new Vector2(m_State.TimeToPixel(m_GrabbedTime), TimelineWindow.instance.state.timeAreaRect.yMax);
s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.red);
p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.left), TimelineWindow.instance.state.timeAreaRect.yMax);
s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.right), TimelineWindow.instance.state.timeAreaRect.yMax);
EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
}
if (m_SnapEnabled)
{
if (showLeft)
DrawMagnetLineAt(m_Attractable.start);
if (showRight)
DrawMagnetLineAt(m_Attractable.end);
}
}
void DrawMagnetLineAt(double time)
{
var magnet = GetMagnetAt(time);
if (magnet != null && magnet.showSnapHint)
Graphics.DrawLineAtTime(m_State, magnet.time, Color.white);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 48967a2d5427ac2489cc0ea61da5f1a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimelineClipHandle : ILayerable
{
Rect m_Rect;
readonly TimelineClipGUI m_ClipGUI;
readonly TrimEdge m_TrimDirection;
readonly LayerZOrder m_ZOrder;
public Rect boundingRect
{
get { return m_ClipGUI.parent.ToWindowSpace(m_Rect); }
}
public TrimEdge trimDirection
{
get { return m_TrimDirection; }
}
public TimelineClipGUI clipGUI
{
get { return m_ClipGUI; }
}
public LayerZOrder zOrder
{
get { return m_ZOrder; }
}
public TimelineClipHandle(TimelineClipGUI theClipGUI, TrimEdge trimDirection)
{
m_TrimDirection = trimDirection;
m_ClipGUI = theClipGUI;
m_ZOrder = theClipGUI.zOrder.ChangeLayer(Layer.ClipHandles);
}
public void Draw(Rect clientRect, float width, WindowState state)
{
var handleRect = clientRect;
handleRect.width = width;
if (m_TrimDirection == TrimEdge.End)
handleRect.x = clientRect.xMax - width;
m_Rect = handleRect;
if (!TimelineWindow.instance.state.editSequence.isReadOnly)
EditorGUIUtility.AddCursorRect(handleRect, MouseCursor.SplitResizeLeftRight);
state.spacePartitioner.AddBounds(this, boundingRect);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e29cdd1646803545b8fb18908666ec4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditorInternal;
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimelineClipUnion
{
List<TimelineClipGUI> m_Members = new List<TimelineClipGUI>();
Rect m_BoundingRect;
Rect m_Union;
double m_Start;
double m_Duration;
bool m_InitUnionRect = true;
void Add(TimelineClipGUI clip)
{
m_Members.Add(clip);
if (m_Members.Count == 1)
{
m_BoundingRect = clip.clippedRect;
}
else
{
m_BoundingRect = Encompass(m_BoundingRect, clip.rect);
}
}
public void Draw(Rect parentRect, WindowState state)
{
if (m_InitUnionRect)
{
m_Start = m_Members.OrderBy(c => c.clip.start).First().clip.start;
m_Duration = m_Members.Sum(c => c.clip.duration);
m_InitUnionRect = false;
}
m_Union = new Rect((float)(m_Start) * state.timeAreaScale.x, 0, (float)m_Duration * state.timeAreaScale.x, 0);
// transform clipRect into pixel-space
m_Union.xMin += state.timeAreaTranslation.x + parentRect.x;
m_Union.xMax += state.timeAreaTranslation.x + parentRect.x;
m_Union.y = parentRect.y + 4.0f;
m_Union.height = parentRect.height - 8.0f;
// calculate clipped rect
if (m_Union.x < parentRect.xMin)
{
var overflow = parentRect.xMin - m_Union.x;
m_Union.x = parentRect.xMin;
m_Union.width -= overflow;
}
// bail out if completely clipped
if (m_Union.xMax < parentRect.xMin)
return;
if (m_Union.xMin > parentRect.xMax)
return;
EditorGUI.DrawRect(m_Union, DirectorStyles.Instance.customSkin.colorClipUnion);
}
public static List<TimelineClipUnion> Build(List<TimelineClipGUI> clips)
{
var unions = new List<TimelineClipUnion>();
if (clips == null)
return unions;
TimelineClipUnion currentUnion = null;
foreach (var c in clips)
{
if (currentUnion == null)
{
currentUnion = new TimelineClipUnion();
currentUnion.Add(c);
unions.Add(currentUnion);
}
else
{
Rect result;
if (Intersection(c.rect, currentUnion.m_BoundingRect, out result))
{
currentUnion.Add(c);
}
else
{
currentUnion = new TimelineClipUnion();
currentUnion.Add(c);
unions.Add(currentUnion);
}
}
}
return unions;
}
public static Rect Encompass(Rect a, Rect b)
{
Rect newRect = a;
newRect.xMin = Mathf.Min(a.xMin, b.xMin);
newRect.yMin = Mathf.Min(a.yMin, b.yMin);
newRect.xMax = Mathf.Max(a.xMax, b.xMax);
newRect.yMax = Mathf.Max(a.yMax, b.yMax);
return newRect;
}
public static bool Intersection(Rect r1, Rect r2, out Rect intersection)
{
if (!r1.Overlaps(r2) && !r2.Overlaps(r1))
{
intersection = new Rect(0, 0, 0, 0);
return false;
}
float left = Mathf.Max(r1.xMin, r2.xMin);
float top = Mathf.Max(r1.yMin, r2.yMin);
float right = Mathf.Min(r1.xMax, r2.xMax);
float bottom = Mathf.Min(r1.yMax, r2.yMax);
intersection = new Rect(left, top, right - left, bottom - top);
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc99dfeec967f2644b25dd6bace59b7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineDataSource : TreeViewDataSource
{
readonly TimelineWindow m_TimelineWindow;
readonly TimelineTreeViewGUI m_ParentGUI;
public List<TimelineTrackBaseGUI> allTrackGuis { get; private set; }
TreeViewItem treeroot
{
get { return m_RootItem; }
}
public TimelineDataSource(TimelineTreeViewGUI parentGUI, TreeViewController treeView, TimelineWindow sequencerWindow)
: base(treeView)
{
m_TreeView.useExpansionAnimation = false;
m_TimelineWindow = sequencerWindow;
m_ParentGUI = parentGUI;
FetchData();
}
public override bool IsExpanded(TreeViewItem item)
{
if (!IsExpandable(item))
return true;
return IsExpanded(item.id);
}
public override bool IsExpandable(TreeViewItem item)
{
var expandable = false;
var track = item as TimelineTrackBaseGUI;
if (track != null)
expandable = track.expandable;
return expandable && item.hasChildren;
}
public sealed override void FetchData()
{
// create root item
m_RootItem = new TimelineGroupGUI(m_TreeView, m_ParentGUI, 1, 0, null, "root", null, true);
var tree = new Dictionary<TrackAsset, TimelineTrackBaseGUI>();
var filteredView = m_TimelineWindow.state.editSequence.asset.trackObjects;
allTrackGuis = new List<TimelineTrackBaseGUI>(filteredView.Count());
foreach (var t in filteredView)
{
CreateItem(t, ref tree, filteredView.OfType<TrackAsset>(), m_RootItem);
}
m_NeedRefreshRows = true;
SetExpanded(m_RootItem, true);
}
TimelineTrackBaseGUI CreateItem(ScriptableObject scriptableObject, ref Dictionary<TrackAsset, TimelineTrackBaseGUI> tree, IEnumerable<TrackAsset> selectedRows, TreeViewItem parentTreeViewItem)
{
// if a script doesn't load correctly, the trackAsset will be NULL, but the scriptableObject __should_ be intact (but == null will be true)
var trackAsset = scriptableObject as TrackAsset;
if (tree == null)
throw new ArgumentNullException("tree");
if (selectedRows == null)
throw new ArgumentNullException("selectedRows");
if (trackAsset != null && tree.ContainsKey(trackAsset))
return tree[trackAsset];
TimelineTrackBaseGUI parentItem = parentTreeViewItem as TimelineTrackBaseGUI;
// should we create the parent?
TrackAsset parentTrack = trackAsset != null ? (trackAsset.parent as TrackAsset) : null;
if (trackAsset != null && parentTrack != null && selectedRows.Contains(parentTrack))
{
parentItem = CreateItem(parentTrack, ref tree, selectedRows, parentTreeViewItem);
}
int theDepth = -1;
if (parentItem != null)
theDepth = parentItem.depth;
theDepth++;
TimelineTrackBaseGUI newItem;
if (trackAsset == null)
{
PlayableAsset parent = m_TimelineWindow.state.editSequence.asset;
if (parentItem != null && parentItem.track != null)
parent = parentItem.track;
newItem = new TimelineTrackErrorGUI(m_TreeView, m_ParentGUI, 0, theDepth, parentItem, "ERROR", scriptableObject, parent);
}
else if (trackAsset.GetType() != typeof(GroupTrack))
{
newItem = new TimelineTrackGUI(m_TreeView, m_ParentGUI, trackAsset.GetInstanceID(), theDepth, parentItem, trackAsset.name, trackAsset);
}
else
{
newItem = new TimelineGroupGUI(m_TreeView, m_ParentGUI, trackAsset.GetInstanceID(), theDepth, parentItem, trackAsset.name, trackAsset, false);
}
allTrackGuis.Add(newItem);
if (parentItem != null)
{
if (parentItem.children == null)
parentItem.children = new List<TreeViewItem>();
parentItem.children.Add(newItem);
SetExpanded(newItem, trackAsset.IsCollapsed());
}
else
{
m_RootItem = newItem;
SetExpanded(m_RootItem, true);
}
if (trackAsset != null)
tree[trackAsset] = newItem;
var actorAsAnimTrack = newItem.track as AnimationTrack;
bool isEditableInfiniteClip = actorAsAnimTrack != null && actorAsAnimTrack.ShouldShowInfiniteClipEditor();
if (isEditableInfiniteClip)
{
if (newItem.children == null)
newItem.children = new List<TreeViewItem>();
}
else if (trackAsset != null)
{
// check if clips on this track have animation, if so we inline a animationEditorTrack
bool clipHasAnimatableAnimationCurves = false;
for (var i = 0; i != newItem.track.clips.Length; ++i)
{
var curveClip = newItem.track.clips[i].curves;
var animationClip = newItem.track.clips[i].animationClip;
// prune out clip with zero curves
if (curveClip != null && curveClip.empty)
curveClip = null;
if (animationClip != null && animationClip.empty)
animationClip = null;
// prune out clips coming from FBX
if (animationClip != null && ((animationClip.hideFlags & HideFlags.NotEditable) != 0))
animationClip = null;
if (!newItem.track.clips[i].recordable)
animationClip = null;
clipHasAnimatableAnimationCurves = (curveClip != null) || (animationClip != null);
if (clipHasAnimatableAnimationCurves)
break;
}
if (clipHasAnimatableAnimationCurves)
{
if (newItem.children == null)
newItem.children = new List<TreeViewItem>();
}
}
if (trackAsset != null)
{
// Here we are using the internal subTrackObject so we can properly handle tracks whose script
// can't load (via ScriptableObject)
foreach (var subTrack in trackAsset.subTracksObjects)
{
CreateItem(subTrack, ref tree, selectedRows, newItem);
}
}
return newItem;
}
public override bool CanBeParent(TreeViewItem item)
{
// will prevent track becoming subtracks via dragging
TimelineTrackGUI track = item as TimelineTrackGUI;
if (track != null)
return false;
return true;
}
public void ExpandItems(TreeViewItem item)
{
if (treeroot == item)
{
SetExpanded(treeroot, true);
}
TimelineGroupGUI gui = item as TimelineGroupGUI;
if (gui != null && gui.track != null)
{
SetExpanded(item, !gui.track.IsCollapsed());
}
if (item.children != null)
{
for (int c = 0; c < item.children.Count; c++)
{
ExpandItems(item.children[c]);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8c30efa89029e447b0dc0efc75b294c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,644 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEditor.Timeline;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using UnityObject = UnityEngine.Object;
namespace UnityEditor
{
class TimelineDragging : TreeViewDragging
{
public delegate bool TypeResolver(IEnumerable<Type> types, Action<Type> onComplete, string format);
private static readonly string k_SelectTrackWithBinding = L10n.Tr("Add {0}");
private static readonly string k_SelectTrackWithClip = L10n.Tr("Add Clip With {0}");
private static readonly string k_SelectClip = L10n.Tr("Add {0}");
const string k_GenericDragId = "TimelineDragging";
readonly int kDragSensitivity = 2;
readonly TimelineAsset m_Timeline;
readonly TimelineWindow m_Window;
class TimelineDragData
{
public TimelineDragData(List<TreeViewItem> draggedItems)
{
this.draggedItems = draggedItems;
}
public readonly List<TreeViewItem> draggedItems;
}
public TimelineDragging(TreeViewController treeView, TimelineWindow window, TimelineAsset data)
: base(treeView)
{
m_Timeline = data;
m_Window = window;
}
public override bool CanStartDrag(TreeViewItem targetItem, List<int> draggedItemIDs, Vector2 mouseDownPosition)
{
if (Event.current.modifiers != EventModifiers.None)
return false;
// Can only drag when starting in the track header area
if (mouseDownPosition.x > m_Window.sequenceHeaderRect.xMax)
return false;
var trackBaseGUI = targetItem as TimelineTrackBaseGUI;
if (trackBaseGUI == null || trackBaseGUI.track == null)
return false;
if (trackBaseGUI.track.lockedInHierarchy)
return false;
if (Event.current.type == EventType.MouseDrag && Mathf.Abs(Event.current.delta.y) < kDragSensitivity)
return false;
// Make sure dragged items are selected
// TODO Use similar system than the SceneHierarchyWindow in order to handle selection between treeView and tracks.
SelectionManager.Clear();
var draggedTrackGUIs = m_Window.allTracks.Where(t => draggedItemIDs.Contains(t.id));
foreach (var trackGUI in draggedTrackGUIs)
SelectionManager.Add(trackGUI.track);
return true;
}
public override void StartDrag(TreeViewItem draggedNode, List<int> draggedItemIDs)
{
DragAndDrop.PrepareStartDrag();
var tvItems = SelectionManager.SelectedTrackGUI().Cast<TreeViewItem>().ToList();
DragAndDrop.SetGenericData(k_GenericDragId, new TimelineDragData(tvItems));
DragAndDrop.objectReferences = new UnityObject[] {}; // this IS required for dragging to work
string title = draggedItemIDs.Count + (draggedItemIDs.Count > 1 ? "s" : ""); // title is only shown on OSX (at the cursor)
TimelineGroupGUI groupGui = draggedNode as TimelineGroupGUI;
if (groupGui != null)
{
title = groupGui.displayName;
}
DragAndDrop.StartDrag(title);
}
public static bool ResolveType(IEnumerable<System.Type> types, Action<Type> onComplete, string formatString)
{
if (!types.Any() || onComplete == null)
return false;
if (types.Count() == 1)
{
onComplete(types.First());
return true;
}
var menu = new GenericMenu();
var builtInTypes = types.Where(TypeUtility.IsBuiltIn).OrderBy(TypeUtility.GetDisplayName).ToArray();
var customTypes = types.Where(x => !TypeUtility.IsBuiltIn(x)).OrderBy(TypeUtility.GetDisplayName).ToArray();
foreach (var t in builtInTypes)
{
menu.AddItem(new GUIContent(string.Format(formatString, TypeUtility.GetDisplayName(t))), false, s => onComplete((System.Type)s), t);
}
if (builtInTypes.Length != 0 && customTypes.Length != 0)
menu.AddSeparator(string.Empty);
foreach (var t in customTypes)
{
menu.AddItem(new GUIContent(string.Format(formatString, TypeUtility.GetDisplayName(t))), false, s => onComplete((System.Type)s), t);
}
menu.ShowAsContext();
return true;
}
public override bool DragElement(TreeViewItem targetItem, Rect targetItemRect, int row)
{
if (TimelineWindow.instance.state.editSequence.isReadOnly)
return false;
// the drop rect contains the row rect plus additional spacing. The base drag element overlaps 1/2 the height of the next track
// which interferes with track bindings
var targetTrack = targetItem as TimelineGroupGUI;
if (row > 0 && targetTrack != null && !targetTrack.dropRect.Contains(Event.current.mousePosition))
return false;
return base.DragElement(targetItem, targetItemRect, row);
}
TreeViewItem GetNextItem(TreeViewItem item)
{
if (item == null)
return null;
if (item.parent == null)
{
int row = m_Window.treeView.data.GetRow(item.id);
var items = m_Window.treeView.data.GetRows();
if (items.Count > row + 1)
return items[row + 1];
return null;
}
var children = item.parent.children;
if (children == null)
return null;
for (int i = 0; i < children.Count - 1; i++)
{
if (children[i] == item)
return children[i + 1];
}
return null;
}
private static TrackAsset GetTrack(TreeViewItem item)
{
TimelineTrackBaseGUI baseGui = item as TimelineTrackBaseGUI;
if (baseGui == null)
return null;
return baseGui.track;
}
// The drag and drop may be over an expanded group but might be between tracks
private void HandleNestedItemGUI(ref TreeViewItem parentItem, ref TreeViewItem targetItem, ref TreeViewItem insertBefore)
{
const float kTopPad = 5;
const float kBottomPad = 5;
insertBefore = null;
if (!ShouldUseHierarchyDragAndDrop())
return;
var targetTrack = targetItem as TimelineGroupGUI;
if (targetTrack == null)
return;
var mousePosition = Event.current.mousePosition;
var dropBefore = targetTrack.rowRect.yMin + kTopPad > mousePosition.y;
var dropAfter = !(targetTrack.track is GroupTrack) && (targetTrack.rowRect.yMax - kBottomPad < mousePosition.y);
targetTrack.drawInsertionMarkerBefore = dropBefore;
targetTrack.drawInsertionMarkerAfter = dropAfter;
if (dropBefore)
{
targetItem = parentItem;
parentItem = targetItem != null ? targetItem.parent : null;
insertBefore = targetTrack;
}
else if (dropAfter)
{
targetItem = parentItem;
parentItem = targetItem != null ? targetItem.parent : null;
insertBefore = GetNextItem(targetTrack);
}
else if (targetTrack.track is GroupTrack)
{
targetTrack.isDropTarget = true;
}
}
public override DragAndDropVisualMode DoDrag(TreeViewItem parentItem, TreeViewItem targetItem, bool perform, DropPosition dropPos)
{
m_Window.isDragging = false;
var retMode = DragAndDropVisualMode.None;
var trackDragData = DragAndDrop.GetGenericData(k_GenericDragId) as TimelineDragData;
if (trackDragData != null)
{
retMode = HandleTrackDrop(parentItem, targetItem, perform, dropPos);
if (retMode == DragAndDropVisualMode.Copy && targetItem != null && Event.current.type == EventType.DragUpdated)
{
var targetActor = targetItem as TimelineGroupGUI;
if (targetActor != null)
targetActor.isDropTarget = true;
}
}
else if (DragAndDrop.objectReferences.Any())
{
var objectsBeingDropped = DragAndDrop.objectReferences.OfType<UnityObject>();
var director = m_Window.state.editSequence.director;
if (ShouldUseHierarchyDragAndDrop())
{
// for object drawing
var originalTarget = targetItem;
TreeViewItem insertBeforeItem = null;
HandleNestedItemGUI(ref parentItem, ref targetItem, ref insertBeforeItem);
var track = GetTrack(targetItem);
var parent = GetTrack(parentItem);
var insertBefore = GetTrack(insertBeforeItem);
retMode = HandleHierarchyPaneDragAndDrop(objectsBeingDropped, track, perform, m_Timeline, director, ResolveType, insertBefore);
// fallback to old clip behaviour
if (retMode == DragAndDropVisualMode.None)
{
retMode = HandleClipPaneObjectDragAndDrop(objectsBeingDropped, track, perform, m_Timeline, parent, director, m_Window.state.timeAreaShownRange.x, ResolveType, insertBefore);
}
// if we are rejected, clear any drop markers
if (retMode == DragAndDropVisualMode.Rejected && targetItem != null)
{
ClearInsertionMarkers(originalTarget);
ClearInsertionMarkers(targetItem);
ClearInsertionMarkers(parentItem);
ClearInsertionMarkers(insertBeforeItem);
}
}
else
{
var candidateTime = TimelineHelpers.GetCandidateTime(Event.current.mousePosition);
retMode = HandleClipPaneObjectDragAndDrop(objectsBeingDropped, GetTrack(targetItem), perform, m_Timeline, GetTrack(parentItem), director, candidateTime, ResolveType);
}
}
m_Window.isDragging = false;
return retMode;
}
void ClearInsertionMarkers(TreeViewItem item)
{
var trackGUI = item as TimelineTrackBaseGUI;
if (trackGUI != null)
{
trackGUI.drawInsertionMarkerAfter = false;
trackGUI.drawInsertionMarkerBefore = false;
trackGUI.isDropTarget = false;
}
}
bool ShouldUseHierarchyDragAndDrop()
{
return m_Window.state.IsEditingAnEmptyTimeline() || m_Window.state.sequencerHeaderWidth > Event.current.mousePosition.x;
}
public static DragAndDropVisualMode HandleHierarchyPaneDragAndDrop(IEnumerable<UnityObject> objectsBeingDropped, TrackAsset targetTrack, bool perform, TimelineAsset timeline, PlayableDirector director, TypeResolver typeResolver, TrackAsset insertBefore = null)
{
if (timeline == null)
return DragAndDropVisualMode.Rejected;
// if we are over a target track, defer to track binding system (implemented in TrackGUIs), unless we are a groupTrack
if (targetTrack != null && (targetTrack as GroupTrack) == null)
return DragAndDropVisualMode.Rejected;
if (targetTrack != null && targetTrack.lockedInHierarchy)
return DragAndDropVisualMode.Rejected;
var tracksWithBinding = objectsBeingDropped.SelectMany(TypeUtility.GetTracksCreatableFromObject).Distinct();
if (!tracksWithBinding.Any())
return DragAndDropVisualMode.None;
if (perform)
{
Action<Type> onResolve = trackType =>
{
foreach (var obj in objectsBeingDropped)
{
if (!obj.IsPrefab() && TypeUtility.IsTrackCreatableFromObject(obj, trackType))
{
var newTrack = TimelineHelpers.CreateTrack(timeline, trackType, targetTrack, string.Empty);
if (insertBefore != null)
{
if (targetTrack != null)
targetTrack.MoveLastTrackBefore(insertBefore);
else
timeline.MoveLastTrackBefore(insertBefore);
}
BindingUtility.BindWithEditorValidation(director, newTrack, obj);
}
}
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
};
typeResolver(tracksWithBinding, onResolve, k_SelectTrackWithBinding);
}
return DragAndDropVisualMode.Copy;
}
public static DragAndDropVisualMode HandleClipPaneObjectDragAndDrop(IEnumerable<UnityObject> objectsBeingDropped, TrackAsset targetTrack, bool perform, TimelineAsset timeline, TrackAsset parent, PlayableDirector director, double candidateTime, TypeResolver typeResolver, TrackAsset insertBefore = null)
{
if (timeline == null)
return DragAndDropVisualMode.Rejected;
// locked tracks always reject
if (targetTrack != null && targetTrack.lockedInHierarchy)
return DragAndDropVisualMode.Rejected;
// treat group tracks as having no track
if (targetTrack is GroupTrack)
{
parent = targetTrack;
targetTrack = null;
}
// Special case for monoscripts, since they describe the type
if (objectsBeingDropped.Any(o => o is MonoScript))
return HandleClipPaneMonoScriptDragAndDrop(objectsBeingDropped.OfType<MonoScript>(), targetTrack, perform, timeline, parent, director, candidateTime);
// no unity objects, or explicit exceptions
if (!objectsBeingDropped.Any() || objectsBeingDropped.Any(o => !ValidateObjectDrop(o)))
return DragAndDropVisualMode.Rejected;
// reject scene references if we have no context
if (director == null && objectsBeingDropped.Any(o => o.IsSceneObject()))
return DragAndDropVisualMode.Rejected;
var validTrackTypes = objectsBeingDropped.SelectMany(o => TypeUtility.GetTrackTypesForObject(o)).Distinct().ToList();
// special case for playable assets
if (objectsBeingDropped.Any(o => TypeUtility.IsConcretePlayableAsset(o.GetType())))
{
var playableAssets = objectsBeingDropped.OfType<IPlayableAsset>().Where(o => TypeUtility.IsConcretePlayableAsset(o.GetType()));
return HandleClipPanePlayableAssetDragAndDrop(playableAssets, targetTrack, perform, timeline, parent, director, candidateTime, typeResolver);
}
var markerTypes = objectsBeingDropped.SelectMany(o => TypeUtility.MarkerTypesWithFieldForObject(o)).Distinct();
// No tracks or markers support this object
if (!(markerTypes.Any() || validTrackTypes.Any()))
{
return DragAndDropVisualMode.Rejected;
}
// track is not compatible with marker
if (targetTrack != null && markerTypes.Any(o => !TypeUtility.DoesTrackSupportMarkerType(targetTrack, o)))
{
// track is not compatible with object
if (!validTrackTypes.Contains(targetTrack.GetType()))
return DragAndDropVisualMode.Rejected;
}
// there is no target track, dropping to empty space, or onto a group
if (perform)
{
// choose track and then clip
if (targetTrack == null)
{
var createdTrack = HandleTrackAndItemCreation(objectsBeingDropped, candidateTime, typeResolver, timeline, parent, validTrackTypes, insertBefore);
if (!createdTrack)
{
timeline.CreateMarkerTrack();
HandleItemCreation(objectsBeingDropped, timeline.markerTrack, candidateTime, typeResolver, true); // menu is always popped if ambiguous choice
}
}
// just choose clip/marker
else
{
HandleItemCreation(objectsBeingDropped, targetTrack, candidateTime, typeResolver, true); // menu is always popped if ambiguous choice
}
}
return DragAndDropVisualMode.Copy;
}
static bool HandleTrackAndItemCreation(IEnumerable<UnityEngine.Object> objectsBeingDropped, double candidateTime, TypeResolver typeResolver, TimelineAsset timeline, TrackAsset parent, IEnumerable<Type> validTrackTypes, TrackAsset insertBefore = null)
{
Action<Type> onResolved = t =>
{
var newTrack = TimelineHelpers.CreateTrack(timeline, t, parent, string.Empty);
if (insertBefore != null)
{
if (parent != null)
parent.MoveLastTrackBefore(insertBefore);
else
timeline.MoveLastTrackBefore(insertBefore);
}
HandleItemCreation(objectsBeingDropped, newTrack, candidateTime, typeResolver, validTrackTypes.Count() == 1); // menu is popped if ambiguous clip choice and unambiguous track choice
};
return typeResolver(validTrackTypes, t => onResolved(t), k_SelectTrackWithClip); // Did it create a track
}
static void HandleItemCreation(IEnumerable<UnityEngine.Object> objectsBeingDropped, TrackAsset targetTrack, double candidateTime, TypeResolver typeResolver, bool allowMenu)
{
var assetTypes = objectsBeingDropped.Select(o =>
TypeUtility.GetAssetTypesForObject(targetTrack.GetType(), o)
.Union(TypeUtility.MarkerTypesWithFieldForObject(o))).ToList();
Action<Type> onCreateItem = assetType =>
{
if (typeof(PlayableAsset).IsAssignableFrom(assetType))
{
TimelineHelpers.CreateClipsFromObjects(assetType, targetTrack, candidateTime,
objectsBeingDropped);
}
else
{
TimelineHelpers.CreateMarkersFromObjects(assetType, targetTrack, candidateTime, objectsBeingDropped);
}
};
var flatAssetTypes = assetTypes.SelectMany(x => x).Distinct();
// If there is a one to one mapping between assets and timeline types, no need to go through the type resolution, not ambiguous.
if (assetTypes.All(x => x.Count() <= 1))
{
foreach (var type in flatAssetTypes)
{
onCreateItem(type);
}
}
else
{
if (!allowMenu) // If we already popped a menu, and are presented with an ambiguous choice, take the first entry
{
flatAssetTypes = new[] {flatAssetTypes.First()};
}
typeResolver(flatAssetTypes, onCreateItem, k_SelectClip);
}
}
/// Handles drag and drop of a mono script.
public static DragAndDropVisualMode HandleClipPaneMonoScriptDragAndDrop(IEnumerable<MonoScript> scriptsBeingDropped, TrackAsset targetTrack, bool perform, TimelineAsset timeline, TrackAsset parent, PlayableDirector director, double candidateTime)
{
var playableAssetTypes = scriptsBeingDropped.Select(s => s.GetClass()).Where(TypeUtility.IsConcretePlayableAsset).Distinct();
if (!playableAssetTypes.Any())
return DragAndDropVisualMode.Rejected;
var targetTrackType = typeof(PlayableTrack);
if (targetTrack != null)
targetTrackType = targetTrack.GetType();
var trackAssetsTypes = TypeUtility.GetPlayableAssetsHandledByTrack(targetTrackType);
var supportedTypes = trackAssetsTypes.Intersect(playableAssetTypes);
if (!supportedTypes.Any())
return DragAndDropVisualMode.Rejected;
if (perform)
{
if (targetTrack == null)
targetTrack = TimelineHelpers.CreateTrack(timeline, targetTrackType, parent, string.Empty);
TimelineHelpers.CreateClipsFromTypes(supportedTypes, targetTrack, candidateTime);
}
return DragAndDropVisualMode.Copy;
}
public static DragAndDropVisualMode HandleClipPanePlayableAssetDragAndDrop(IEnumerable<IPlayableAsset> assetsBeingDropped, TrackAsset targetTrack, bool perform, TimelineAsset timeline, TrackAsset parent, PlayableDirector director, double candidateTime, TypeResolver typeResolver)
{
// get the list of supported track types
var assetTypes = assetsBeingDropped.Select(x => x.GetType()).Distinct();
IEnumerable<Type> supportedTypes = null;
if (targetTrack == null)
{
supportedTypes = TypeUtility.AllTrackTypes().Where(t => TypeUtility.GetPlayableAssetsHandledByTrack(t).Intersect(assetTypes).Any()).ToList();
}
else
{
supportedTypes = Enumerable.Empty<Type>();
var trackAssetTypes = TypeUtility.GetPlayableAssetsHandledByTrack(targetTrack.GetType());
if (trackAssetTypes.Intersect(assetTypes).Any())
supportedTypes = new[] {targetTrack.GetType()};
}
if (!supportedTypes.Any())
return DragAndDropVisualMode.Rejected;
if (perform)
{
Action<Type> onResolved = (t) =>
{
if (targetTrack == null)
targetTrack = TimelineHelpers.CreateTrack(timeline, t, parent, string.Empty);
var clipTypes = TypeUtility.GetPlayableAssetsHandledByTrack(targetTrack.GetType());
foreach (var asset in assetsBeingDropped)
{
if (clipTypes.Contains(asset.GetType()))
TimelineHelpers.CreateClipOnTrackFromPlayableAsset(asset, targetTrack, candidateTime);
}
};
typeResolver(supportedTypes, onResolved, k_SelectTrackWithClip);
}
return DragAndDropVisualMode.Copy;
}
static bool ValidateObjectDrop(UnityObject obj)
{
// legacy animation clips are not supported at all
AnimationClip clip = obj as AnimationClip;
if (clip != null && clip.legacy)
return false;
return !(obj is TimelineAsset);
}
public DragAndDropVisualMode HandleTrackDrop(TreeViewItem parentItem, TreeViewItem targetItem, bool perform, DropPosition dropPos)
{
((TimelineTreeView)m_Window.treeView.gui).showInsertionMarker = false;
var trackDragData = (TimelineDragData)DragAndDrop.GetGenericData(k_GenericDragId);
bool validDrag = ValidDrag(targetItem, trackDragData.draggedItems);
if (!validDrag)
return DragAndDropVisualMode.None;
var draggedTracks = trackDragData.draggedItems.OfType<TimelineGroupGUI>().Select(x => x.track).ToList();
if (draggedTracks.Count == 0)
return DragAndDropVisualMode.None;
if (parentItem != null)
{
var parentActor = parentItem as TimelineGroupGUI;
if (parentActor != null && parentActor.track != null)
{
if (parentActor.track.lockedInHierarchy)
return DragAndDropVisualMode.Rejected;
if (draggedTracks.Any(x => !TimelineCreateUtilities.ValidateParentTrack(parentActor.track, x.GetType())))
return DragAndDropVisualMode.Rejected;
}
}
var insertAfterItem = targetItem as TimelineGroupGUI;
if (insertAfterItem != null && insertAfterItem.track != null)
{
((TimelineTreeView)m_Window.treeView.gui).showInsertionMarker = true;
}
if (dropPos == DropPosition.Upon)
{
var groupGUI = targetItem as TimelineGroupGUI;
if (groupGUI != null)
groupGUI.isDropTarget = true;
}
if (perform)
{
PlayableAsset targetParent = m_Timeline;
var parentActor = parentItem as TimelineGroupGUI;
if (parentActor != null && parentActor.track != null)
targetParent = parentActor.track;
TrackAsset siblingTrack = insertAfterItem != null ? insertAfterItem.track : null;
// where the user drops after the last track, make sure to place it after all the tracks
if (targetParent == m_Timeline && dropPos == DropPosition.Below && siblingTrack == null)
{
siblingTrack = m_Timeline.GetRootTracks().LastOrDefault(x => !draggedTracks.Contains(x));
}
if (TrackExtensions.ReparentTracks(TrackExtensions.FilterTracks(draggedTracks).ToList(), targetParent, siblingTrack, dropPos == DropPosition.Above))
{
m_Window.state.Refresh();
}
}
return DragAndDropVisualMode.Move;
}
public static void OnTrackBindingDragUpdate(TrackAsset dropTarget)
{
if (DragAndDrop.objectReferences.Length == 0)
{
OnRejectTrackBindingDragUpdate();
return;
}
var trackEditor = CustomTimelineEditorCache.GetTrackEditor(dropTarget);
var isDragValid = trackEditor.IsBindingAssignableFrom_Safe(DragAndDrop.objectReferences[0], dropTarget);
if (isDragValid)
OnAcceptTrackBindingDragUpdate();
else
OnRejectTrackBindingDragUpdate();
}
static void OnAcceptTrackBindingDragUpdate()
{
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
}
static void OnRejectTrackBindingDragUpdate()
{
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
DragAndDrop.activeControlID = 0;
}
static bool ValidDrag(TreeViewItem target, List<TreeViewItem> draggedItems)
{
TreeViewItem currentParent = target;
while (currentParent != null)
{
if (draggedItems.Contains(currentParent))
return false;
currentParent = currentParent.parent;
}
// dragging into the sequence itself
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fcd66bf74cbeb264aa99679c7df84427
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,439 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimelineTreeView : ITreeViewGUI
{
float m_FoldoutWidth;
Rect m_DraggingInsertionMarkerRect;
readonly TreeViewController m_TreeView;
List<Rect> m_RowRects = new List<Rect>();
List<Rect> m_ExpandedRowRects = new List<Rect>();
float m_MaxWidthOfRows;
readonly WindowState m_State;
static readonly float kMinTrackHeight = 25.0f;
static readonly float kFoldOutOffset = 14.0f;
static DirectorStyles m_Styles;
public bool showInsertionMarker { get; set; }
public virtual float topRowMargin { get; private set; }
public virtual float bottomRowMargin { get; private set; }
public TimelineTreeView(TimelineWindow sequencerWindow, TreeViewController treeView)
{
m_TreeView = treeView;
m_TreeView.useExpansionAnimation = true;
m_TreeView.selectionChangedCallback += SelectionChangedCallback;
m_TreeView.contextClickOutsideItemsCallback += ContextClickOutsideItemsCallback;
m_TreeView.itemDoubleClickedCallback += ItemDoubleClickedCallback;
m_TreeView.contextClickItemCallback += ContextClickItemCallback;
m_TreeView.SetConsumeKeyDownEvents(false);
m_Styles = DirectorStyles.Instance;
m_State = sequencerWindow.state;
m_FoldoutWidth = DirectorStyles.Instance.foldout.fixedWidth;
}
void ItemDoubleClickedCallback(int id)
{
var trackGUI = m_TreeView.FindItem(id) as TimelineTrackGUI;
if (trackGUI == null)
return;
if (trackGUI.track == null || trackGUI.track.lockedInHierarchy)
return;
var selection = SelectionManager.SelectedItems().ToList();
var items = ItemsUtils.GetItems(trackGUI.track).ToList();
var addToSelection = !selection.SequenceEqual(items);
foreach (var i in items)
{
if (addToSelection)
SelectionManager.Add(i);
else
SelectionManager.Remove(i);
}
}
void ContextClickOutsideItemsCallback()
{
SequencerContextMenu.ShowNewTracksContextMenu(null, m_State);
Event.current.Use();
}
void ContextClickItemCallback(int id)
{
// may not occur if another menu is active
if (!m_TreeView.IsSelected(id))
SelectionChangedCallback(new[] {id});
SequencerContextMenu.ShowTrackContextMenu(Event.current.mousePosition);
Event.current.Use();
}
void SelectionChangedCallback(int[] ids)
{
if (Event.current.button == 1 && PickerUtils.TopmostPickedItem() is ISelectable)
return;
if (Event.current.command || Event.current.control || Event.current.shift)
SelectionManager.UnSelectTracks();
else
SelectionManager.Clear();
foreach (var id in ids)
{
var trackGUI = (TimelineTrackBaseGUI)m_TreeView.FindItem(id);
SelectionManager.Add(trackGUI.track);
}
m_State.GetWindow().Repaint();
}
public void OnInitialize() {}
public Rect GetRectForFraming(int row)
{
return GetRowRect(row, 1); // We ignore width by default when framing (only y scroll is affected)
}
protected virtual Vector2 GetSizeOfRow(TreeViewItem item)
{
if (item.displayName == "root")
return new Vector2(m_TreeView.GetTotalRect().width, 0.0f);
var trackGroupGui = item as TimelineGroupGUI;
if (trackGroupGui != null)
{
return new Vector2(m_TreeView.GetTotalRect().width, trackGroupGui.GetHeight(m_State));
}
float height = TrackEditor.DefaultTrackHeight;
if (item.hasChildren && m_TreeView.data.IsExpanded(item))
{
height = Mathf.Min(TrackEditor.DefaultTrackHeight, kMinTrackHeight);
}
return new Vector2(m_TreeView.GetTotalRect().width, height);
}
public virtual void BeginRowGUI()
{
if (m_TreeView.GetTotalRect().width != GetRowRect(0).width)
{
CalculateRowRects();
}
m_DraggingInsertionMarkerRect.x = -1;
m_TreeView.SetSelection(SelectionManager.SelectedTrackGUI().Select(t => t.id).ToArray(), false);
}
public virtual void EndRowGUI()
{
// Draw row marker when dragging
if (m_DraggingInsertionMarkerRect.x >= 0 && Event.current.type == EventType.Repaint)
{
Rect insertionRect = m_DraggingInsertionMarkerRect;
const float insertionHeight = 1.0f;
insertionRect.height = insertionHeight;
if (m_TreeView.dragging.drawRowMarkerAbove)
insertionRect.y -= insertionHeight * 0.5f + 2.0f;
else
insertionRect.y += m_DraggingInsertionMarkerRect.height - insertionHeight * 0.5f + 1.0f;
EditorGUI.DrawRect(insertionRect, Color.white);
}
}
public virtual void OnRowGUI(Rect rowRect, TreeViewItem item, int row, bool selected, bool focused)
{
using (new EditorGUI.DisabledScope(TimelineWindow.instance.currentMode.TrackState(TimelineWindow.instance.state) == TimelineModeGUIState.Disabled))
{
var sqvi = (TimelineTrackBaseGUI)item;
sqvi.treeViewToWindowTransformation = m_TreeView.GetTotalRect().position - m_TreeView.state.scrollPos;
// this may be called because an encompassing parent is visible
if (!sqvi.visibleExpanded)
return;
Rect headerRect = rowRect;
Rect contentRect = rowRect;
headerRect.width = m_State.sequencerHeaderWidth - 2.0f;
contentRect.xMin += m_State.sequencerHeaderWidth;
contentRect.width = rowRect.width - m_State.sequencerHeaderWidth - 1.0f;
Rect foldoutRect = rowRect;
var indent = GetFoldoutIndent(item);
var headerRectWithIndent = headerRect;
headerRectWithIndent.xMin = indent;
var rowRectWithIndent = new Rect(rowRect.x + indent, rowRect.y, rowRect.width - indent, rowRect.height);
sqvi.Draw(headerRectWithIndent, contentRect, m_State);
sqvi.DrawInsertionMarkers(rowRectWithIndent);
if (Event.current.type == EventType.Repaint)
{
m_State.spacePartitioner.AddBounds(sqvi);
// Show marker below this Item
if (showInsertionMarker)
{
if (m_TreeView.dragging != null && m_TreeView.dragging.GetRowMarkerControlID() == TreeViewController.GetItemControlID(item))
m_DraggingInsertionMarkerRect = rowRectWithIndent;
}
}
// Draw foldout (after text content above to ensure drop down icon is rendered above selection highlight)
DrawFoldout(item, foldoutRect, indent);
sqvi.ClearDrawFlags();
}
}
void DrawFoldout(TreeViewItem item, Rect foldoutRect, float indent)
{
var showFoldout = m_TreeView.data.IsExpandable(item);
if (showFoldout)
{
foldoutRect.x = indent - kFoldOutOffset;
foldoutRect.width = m_FoldoutWidth;
EditorGUI.BeginChangeCheck();
float foldoutIconHeight = DirectorStyles.Instance.foldout.fixedHeight;
foldoutRect.y += foldoutIconHeight / 2.0f;
foldoutRect.height = foldoutIconHeight;
if (foldoutRect.xMax > m_State.sequencerHeaderWidth)
return;
//Override Disable state for TrakGroup toggle button to expand/collapse group.
bool previousEnableState = GUI.enabled;
GUI.enabled = true;
bool newExpandedValue = GUI.Toggle(foldoutRect, m_TreeView.data.IsExpanded(item), GUIContent.none, m_Styles.foldout);
GUI.enabled = previousEnableState;
if (EditorGUI.EndChangeCheck())
{
if (Event.current.alt)
m_TreeView.data.SetExpandedWithChildren(item, newExpandedValue);
else
m_TreeView.data.SetExpanded(item, newExpandedValue);
}
}
}
public Rect GetRenameRect(Rect rowRect, int row, TreeViewItem item)
{
return rowRect;
}
public void BeginPingItem(TreeViewItem item, float topPixelOfRow, float availableWidth) {}
public void EndPingItem() {}
public Rect GetRowRect(int row, float rowWidth)
{
return GetRowRect(row);
}
public Rect GetRowRect(int row)
{
if (m_RowRects.Count == 0)
return new Rect();
if (row >= m_RowRects.Count)
return new Rect();
return m_RowRects[row];
}
static float GetSpacing(TreeViewItem item)
{
var trackBase = item as TimelineTrackBaseGUI;
if (trackBase != null)
return trackBase.GetVerticalSpacingBetweenTracks();
return 3.0f;
}
public void CalculateRowRects()
{
if (m_TreeView.isSearching)
return;
const float startY = 6.0f;
IList<TreeViewItem> rows = m_TreeView.data.GetRows();
m_RowRects = new List<Rect>(rows.Count);
m_ExpandedRowRects = new List<Rect>(rows.Count);
float curY = startY;
m_MaxWidthOfRows = 1f;
// first pass compute the row rects
for (int i = 0; i < rows.Count; ++i)
{
var item = rows[i];
if (i != 0)
curY += GetSpacing(item);
Vector2 rowSize = GetSizeOfRow(item);
m_RowRects.Add(new Rect(0, curY, rowSize.x, rowSize.y));
m_ExpandedRowRects.Add(m_RowRects[i]);
curY += rowSize.y;
if (rowSize.x > m_MaxWidthOfRows)
m_MaxWidthOfRows = rowSize.x;
// updated the expanded state
var groupGUI = item as TimelineGroupGUI;
if (groupGUI != null)
groupGUI.SetExpanded(m_TreeView.data.IsExpanded(item));
}
float halfHeight = halfDropBetweenHeight;
const float kGroupPad = 1.0f;
const float kSkinPadding = 5.0f * 0.6f;
// work bottom up and compute visible regions for groups
for (int i = rows.Count - 1; i > 0; i--)
{
float height = 0;
TimelineTrackBaseGUI item = (TimelineTrackBaseGUI)rows[i];
if (item.isExpanded && item.children != null && item.children.Count > 0)
{
for (var j = 0; j < item.children.Count; j++)
{
var child = item.children[j];
int index = rows.IndexOf(child);
if (index > i)
height += m_ExpandedRowRects[index].height + kSkinPadding;
}
height += kGroupPad;
}
m_ExpandedRowRects[i] = new Rect(m_RowRects[i].x, m_RowRects[i].y, m_RowRects[i].width, m_RowRects[i].height + height);
var groupGUI = item as TimelineGroupGUI;
if (groupGUI != null)
{
var spacing = GetSpacing(item) + 1;
groupGUI.expandedRect = m_ExpandedRowRects[i];
groupGUI.rowRect = m_RowRects[i];
groupGUI.dropRect = new Rect(m_RowRects[i].x, m_RowRects[i].y - spacing, m_RowRects[i].width, m_RowRects[i].height + Mathf.Max(halfHeight, spacing));
}
}
}
public virtual bool BeginRename(TreeViewItem item, float delay)
{
return false;
}
public virtual void EndRename() {}
protected virtual float GetFoldoutIndent(TreeViewItem item)
{
// Ignore depth when showing search results
if (item.depth <= 1 || m_TreeView.isSearching)
return DirectorStyles.kBaseIndent;
int depth = item.depth;
var trackGUI = item as TimelineTrackGUI;
// first level subtracks are not indented
if (trackGUI != null && trackGUI.track != null && trackGUI.track.isSubTrack)
depth--;
return depth * DirectorStyles.kBaseIndent;
}
public virtual float GetContentIndent(TreeViewItem item)
{
return GetFoldoutIndent(item);
}
public int GetNumRowsOnPageUpDown(TreeViewItem fromItem, bool pageUp, float heightOfTreeView)
{
return (int)Mathf.Floor(heightOfTreeView / 30); // return something
}
// Should return the row number of the first and last row thats fits in the pixel rect defined by top and height
public void GetFirstAndLastRowVisible(out int firstRowVisible, out int lastRowVisible)
{
int rowCount = m_TreeView.data.rowCount;
if (rowCount == 0)
{
firstRowVisible = lastRowVisible = -1;
return;
}
if (rowCount != m_ExpandedRowRects.Count)
{
Debug.LogError("Mismatch in state: rows vs cached rects. Did you remember to hook up: dataSource.onVisibleRowsChanged += gui.CalculateRowRects ?");
CalculateRowRects();
}
float topPixel = m_TreeView.state.scrollPos.y;
float heightInPixels = m_TreeView.GetTotalRect().height;
int firstVisible = -1;
int lastVisible = -1;
Rect visibleRect = new Rect(0, topPixel, m_ExpandedRowRects[0].width, heightInPixels);
for (int i = 0; i < m_ExpandedRowRects.Count; ++i)
{
bool visible = visibleRect.Overlaps(m_ExpandedRowRects[i]);
if (visible)
{
if (firstVisible == -1)
firstVisible = i;
lastVisible = i;
}
TimelineTrackBaseGUI gui = m_TreeView.data.GetItem(i) as TimelineTrackBaseGUI;
if (gui != null)
{
gui.visibleExpanded = visible;
gui.visibleRow = visibleRect.Overlaps(m_RowRects[i]);
}
}
if (firstVisible != -1 && lastVisible != -1)
{
firstRowVisible = firstVisible;
lastRowVisible = lastVisible;
}
else
{
firstRowVisible = 0;
lastRowVisible = rowCount - 1;
}
}
public Vector2 GetTotalSize()
{
if (m_RowRects.Count == 0)
return new Vector2(0, 0);
return new Vector2(m_MaxWidthOfRows, m_RowRects[m_RowRects.Count - 1].yMax);
}
public virtual float halfDropBetweenHeight
{
get { return 8f; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82a8ce43816925d4b971951094cb755c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,180 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineTreeViewGUI
{
readonly TimelineAsset m_Timeline;
readonly TreeViewController m_TreeView;
readonly TimelineTreeView m_TimelineTreeView;
readonly TimelineWindow m_Window;
readonly TimelineDataSource m_DataSource;
TreeViewItem root
{
get { return m_DataSource.root; }
}
TimelineTrackBaseGUI[] visibleTrackGuis
{
get
{
int firstRow;
int lastRow;
var visibleRows = new List<TimelineTrackBaseGUI>();
m_TreeView.gui.GetFirstAndLastRowVisible(out firstRow, out lastRow);
for (int r = firstRow; r <= lastRow; r++)
{
var track = m_TreeView.data.GetItem(r) as TimelineTrackBaseGUI;
if (track != null && track != root)
{
AddVisibleTrackRecursive(ref visibleRows, track);
}
}
return visibleRows.ToArray();
}
}
public TrackAsset[] visibleTracks
{
get { return visibleTrackGuis.Select(x => x.track).ToArray(); }
}
public List<TimelineClipGUI> allClipGuis
{
get
{
TimelineDataSource dataSource = m_TreeView.data as TimelineDataSource;
if (dataSource != null && dataSource.allTrackGuis != null)
return dataSource.allTrackGuis.OfType<TimelineTrackGUI>().SelectMany(x => x.clips).ToList();
return null;
}
}
public List<TimelineTrackBaseGUI> allTrackGuis
{
get
{
var dataSource = m_TreeView.data as TimelineDataSource;
if (dataSource != null)
return dataSource.allTrackGuis;
return null;
}
}
public Vector2 contentSize
{
get { return m_TreeView.GetContentSize(); }
}
public Vector2 scrollPosition
{
get { return m_TreeView.state.scrollPos; }
set
{
Rect r = m_TreeView.GetTotalRect();
Vector2 visibleContent = m_TreeView.GetContentSize();
m_TreeView.state.scrollPos = new Vector2(value.x, Mathf.Clamp(value.y, 0, Mathf.Max(0, visibleContent.y - r.height)));
}
}
public bool showingVerticalScrollBar
{
get { return m_TreeView.showingVerticalScrollBar; }
}
public void FrameItem(TreeViewItem item)
{
m_TreeView.Frame(item.id, true, false, true);
}
public TimelineDragging timelineDragging { get {return m_TreeView.dragging as TimelineDragging; }}
public TimelineTreeViewGUI(TimelineWindow sequencerWindow, TimelineAsset timeline, Rect rect)
{
m_Timeline = timeline;
m_Window = sequencerWindow;
var treeviewState = new TreeViewState();
treeviewState.scrollPos = new Vector2(treeviewState.scrollPos.x, TimelineWindowViewPrefs.GetOrCreateViewModel(m_Timeline).verticalScroll);
m_TreeView = new TreeViewController(sequencerWindow, treeviewState);
m_TreeView.horizontalScrollbarStyle = GUIStyle.none;
m_TreeView.scrollViewStyle = GUI.skin.scrollView;
m_TimelineTreeView = new TimelineTreeView(sequencerWindow, m_TreeView);
var dragging = new TimelineDragging(m_TreeView, m_Window, m_Timeline);
m_DataSource = new TimelineDataSource(this, m_TreeView, sequencerWindow);
m_DataSource.onVisibleRowsChanged += m_TimelineTreeView.CalculateRowRects;
m_TreeView.Init(rect, m_DataSource, m_TimelineTreeView, dragging);
m_DataSource.ExpandItems(m_DataSource.root);
}
public ITreeViewGUI gui
{
get { return m_TimelineTreeView; }
}
public ITreeViewDataSource data
{
get { return m_TreeView == null ? null : m_TreeView.data; }
}
public TimelineWindow TimelineWindow
{
get { return m_Window; }
}
public void CalculateRowRects()
{
m_TimelineTreeView.CalculateRowRects();
}
public void Reload()
{
AnimationClipCurveCache.Instance.ClearCachedProxyClips();
m_TreeView.ReloadData();
m_DataSource.ExpandItems(m_DataSource.root);
m_TimelineTreeView.CalculateRowRects();
}
public void OnGUI(Rect rect)
{
int keyboardControl = GUIUtility.GetControlID(FocusType.Passive, rect);
m_TreeView.OnGUI(rect, keyboardControl);
TimelineWindowViewPrefs.GetOrCreateViewModel(m_Timeline).verticalScroll = m_TreeView.state.scrollPos.y;
}
public Rect GetRowRect(int row)
{
return m_TimelineTreeView.GetRowRect(row);
}
static void AddVisibleTrackRecursive(ref List<TimelineTrackBaseGUI> list, TimelineTrackBaseGUI track)
{
if (track == null)
return;
list.Add(track);
if (!track.isExpanded)
return;
if (track.children != null)
{
foreach (var c in track.children)
{
AddVisibleTrackRecursive(ref list, c as TimelineTrackBaseGUI);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9d8c8b21846a654092a24f25aa41421
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d453576028bb344db0d59db893bf3a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,310 @@
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
interface IClipCurveEditorOwner
{
ClipCurveEditor clipCurveEditor { get; }
bool inlineCurvesSelected { get; }
bool showLoops { get; }
TrackAsset owner { get; }
void SelectCurves();
void ValidateCurvesSelection();
}
class InlineCurveResizeHandle : IBounds
{
public Rect boundingRect { get; private set; }
public TimelineTrackGUI trackGUI { get; }
public InlineCurveResizeHandle(TimelineTrackGUI trackGUI)
{
this.trackGUI = trackGUI;
}
public void Draw(Rect headerRect, WindowState state)
{
const float resizeHandleHeight = WindowConstants.trackResizeHandleHeight;
var rect = new Rect(headerRect.xMin, headerRect.yMax - resizeHandleHeight + 1f, headerRect.width, resizeHandleHeight);
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeUpDown);
boundingRect = trackGUI.ToWindowSpace(rect);
if (Event.current.type == EventType.Repaint)
{
state.headerSpacePartitioner.AddBounds(this);
EditorGUI.DrawRect(rect, DirectorStyles.Instance.customSkin.colorAnimEditorBinding);
var dragStyle = DirectorStyles.Instance.inlineCurveHandle;
dragStyle.Draw(rect, GUIContent.none, false, false, false, false);
}
}
}
class InlineCurveEditor : IBounds
{
Rect m_TrackRect;
Rect m_HeaderRect;
readonly TimelineTrackGUI m_TrackGUI;
readonly InlineCurveResizeHandle m_ResizeHandle;
bool m_LastSelectionWasClip;
TimelineClipGUI m_LastSelectedClipGUI;
Rect IBounds.boundingRect { get { return m_TrackGUI.ToWindowSpace(m_TrackRect); } }
[UsedImplicitly] // Used in tests
public TimelineClipGUI currentClipGui
{
get { return m_LastSelectedClipGUI; }
}
public IClipCurveEditorOwner currentCurveEditor
{
get { return m_LastSelectionWasClip ? (IClipCurveEditorOwner)m_LastSelectedClipGUI : (IClipCurveEditorOwner)m_TrackGUI; }
}
public InlineCurveEditor(TimelineTrackGUI trackGUI)
{
m_TrackGUI = trackGUI;
m_ResizeHandle = new InlineCurveResizeHandle(trackGUI);
}
static bool MouseOverTrackArea(Rect curveRect, Rect trackRect)
{
curveRect.y = trackRect.y;
curveRect.height = trackRect.height;
// clamp the curve editor to the track. this allows the menu to scroll properly
curveRect.xMin = Mathf.Max(curveRect.xMin, trackRect.xMin);
curveRect.xMax = trackRect.xMax;
return curveRect.Contains(Event.current.mousePosition);
}
static bool MouseOverHeaderArea(Rect headerRect, Rect trackRect)
{
headerRect.y = trackRect.y;
headerRect.height = trackRect.height;
return headerRect.Contains(Event.current.mousePosition);
}
static void DrawCurveEditor(IClipCurveEditorOwner clipCurveEditorOwner, WindowState state, Rect headerRect, Rect trackRect, Vector2 activeRange, bool locked)
{
ClipCurveEditor clipCurveEditor = clipCurveEditorOwner.clipCurveEditor;
CurveDataSource dataSource = clipCurveEditor.dataSource;
Rect curveRect = dataSource.GetBackgroundRect(state);
var newlySelected = false;
var currentEvent = Event.current;
if (currentEvent.type == EventType.MouseDown || currentEvent.type == EventType.ContextClick)
newlySelected = MouseOverTrackArea(curveRect, trackRect) || MouseOverHeaderArea(headerRect, trackRect);
// make sure to not use any event before drawing the curve.
bool prevEnabledState = GUI.enabled;
GUI.enabled = true;
clipCurveEditorOwner.clipCurveEditor.DrawHeader(headerRect);
GUI.enabled = prevEnabledState;
bool displayAsSelected = !locked && (clipCurveEditorOwner.inlineCurvesSelected || newlySelected);
using (new EditorGUI.DisabledScope(locked))
clipCurveEditor.DrawCurveEditor(trackRect, state, activeRange, clipCurveEditorOwner.showLoops, displayAsSelected);
if (newlySelected && !locked)
OnMouseClick(clipCurveEditorOwner, currentEvent);
}
static void OnMouseClick(IClipCurveEditorOwner clipCurveEditorOwner, Event currentEvent)
{
if (currentEvent.modifiers == ManipulatorsUtils.actionModifier)
{
if (clipCurveEditorOwner.inlineCurvesSelected)
SelectionManager.Clear();
else
clipCurveEditorOwner.SelectCurves();
}
else
{
clipCurveEditorOwner.SelectCurves();
}
HandleCurrentEvent();
}
public void Draw(Rect headerRect, Rect trackRect, WindowState state)
{
const float inlineCurveBottomPadding = WindowConstants.inlineCurveContentPadding;
m_TrackRect = trackRect;
m_TrackRect.height -= inlineCurveBottomPadding;
if (Event.current.type == EventType.Repaint)
state.spacePartitioner.AddBounds(this);
// Remove the indentation of this track to render it properly, otherwise every GUI elements will be offsetted.
headerRect.x -= DirectorStyles.kBaseIndent;
headerRect.width += DirectorStyles.kBaseIndent;
// Remove the width of the color swatch.
headerRect.x += 4.0f;
headerRect.width -= 4.0f;
m_HeaderRect = headerRect;
EditorGUI.DrawRect(m_HeaderRect, DirectorStyles.Instance.customSkin.colorAnimEditorBinding);
if (ShouldShowClipCurves(state))
{
DrawCurveEditorsForClipsOnTrack(m_HeaderRect, m_TrackRect, state);
}
else if (ShouldShowTrackCurves())
{
DrawCurveEditorForTrack(m_HeaderRect, m_TrackRect, state);
}
else
{
DrawCurvesEditorForNothingSelected(m_HeaderRect, m_TrackRect, state);
}
m_ResizeHandle.Draw(headerRect, state);
var bottomPadding = new Rect(trackRect.xMin, trackRect.yMax - inlineCurveBottomPadding, trackRect.width, inlineCurveBottomPadding);
EditorGUI.DrawRect(bottomPadding, DirectorStyles.Instance.customSkin.colorTrackBackground);
// If MouseDown or ContextClick are not consumed by the curves, use the event to prevent it from going deeper into the treeview.
if (Event.current.type == EventType.ContextClick)
{
var r = Rect.MinMaxRect(m_HeaderRect.xMin, m_HeaderRect.yMin, m_TrackRect.xMax, m_TrackRect.yMax);
if (r.Contains(Event.current.mousePosition))
Event.current.Use();
}
UpdateViewModel();
}
void DrawCurveEditorForTrack(Rect headerRect, Rect trackRect, WindowState state)
{
if (m_TrackGUI.clipCurveEditor == null)
return;
var activeRange = new Vector2(state.TimeToPixel(0.0d), state.TimeToPixel(state.editSequence.duration));
DrawCurveEditor(m_TrackGUI, state, headerRect, trackRect, activeRange, m_TrackGUI.locked);
m_LastSelectionWasClip = false;
}
void DrawCurveEditorsForClipsOnTrack(Rect headerRect, Rect trackRect, WindowState state)
{
if (m_TrackGUI.clips.Count == 0)
return;
if (Event.current.type == EventType.Layout)
{
var selectedClip = SelectionManager.SelectedClipGUI().FirstOrDefault(x => x.parent == m_TrackGUI);
if (selectedClip != null)
{
m_LastSelectedClipGUI = selectedClip;
SelectFromCurveOwner(m_LastSelectedClipGUI);
}
else if (state.recording && state.IsArmedForRecord(m_TrackGUI.track))
{
if (m_LastSelectedClipGUI == null || !m_TrackGUI.track.IsRecordingToClip(m_LastSelectedClipGUI.clip))
{
var clip = m_TrackGUI.clips.FirstOrDefault(x => m_TrackGUI.track.IsRecordingToClip(x.clip));
if (clip != null)
m_LastSelectedClipGUI = clip;
}
}
if (m_LastSelectedClipGUI == null)
m_LastSelectedClipGUI = m_TrackGUI.clips[0];
}
if (m_LastSelectedClipGUI == null || m_LastSelectedClipGUI.clipCurveEditor == null || m_LastSelectedClipGUI.isInvalid)
return;
var activeRange = new Vector2(state.TimeToPixel(m_LastSelectedClipGUI.clip.start), state.TimeToPixel(m_LastSelectedClipGUI.clip.end));
DrawCurveEditor(m_LastSelectedClipGUI, state, headerRect, trackRect, activeRange, m_TrackGUI.locked);
m_LastSelectionWasClip = true;
}
void DrawCurvesEditorForNothingSelected(Rect headerRect, Rect trackRect, WindowState state)
{
if (m_LastSelectionWasClip || !TrackHasCurvesToShow() && m_TrackGUI.clips.Count > 0)
{
DrawCurveEditorsForClipsOnTrack(headerRect, trackRect, state);
}
else
{
DrawCurveEditorForTrack(headerRect, trackRect, state);
}
}
bool ShouldShowClipCurves(WindowState state)
{
if (m_TrackGUI.clips.Count == 0)
return false;
// Is a clip selected or being recorded to?
return SelectionManager.SelectedClipGUI().FirstOrDefault(x => x.parent == m_TrackGUI) != null ||
state.recording && state.IsArmedForRecord(m_TrackGUI.track) && m_TrackGUI.clips.FirstOrDefault(x => m_TrackGUI.track.IsRecordingToClip(x.clip)) != null;
}
bool ShouldShowTrackCurves()
{
if (m_TrackGUI == null)
return false;
var isTrackSelected = SelectionManager.SelectedTrackGUI().FirstOrDefault(x => x == m_TrackGUI) != null;
if (!isTrackSelected)
return false;
return TrackHasCurvesToShow();
}
bool TrackHasCurvesToShow()
{
var animTrack = m_TrackGUI.track as AnimationTrack;
if (animTrack != null && !animTrack.inClipMode)
return true;
return m_TrackGUI.track.HasAnyAnimatableParameters();
}
void UpdateViewModel()
{
var curveEditor = currentCurveEditor.clipCurveEditor;
if (curveEditor == null || curveEditor.bindingHierarchy.treeViewController == null)
return;
var vm = TimelineWindowViewPrefs.GetTrackViewModelData(m_TrackGUI.track);
vm.inlineCurvesState = curveEditor.bindingHierarchy.treeViewController.state;
vm.inlineCurvesShownAreaInsideMargins = curveEditor.shownAreaInsideMargins;
vm.lastInlineCurveDataID = curveEditor.dataSource.id;
}
static void HandleCurrentEvent()
{
#if UNITY_EDITOR_OSX
Event.current.type = EventType.Ignore;
#else
Event.current.Use();
#endif
}
static void SelectFromCurveOwner(IClipCurveEditorOwner curveOwner)
{
if (curveOwner.clipCurveEditor == null)
{
SelectionManager.SelectInlineCurveEditor(null);
}
else if (!curveOwner.inlineCurvesSelected && SelectionManager.Count() == 1)
{
SelectionManager.SelectInlineCurveEditor(curveOwner);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e96b886242ab404db69df796f7cd9b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,323 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineGroupGUI : TimelineTrackBaseGUI
{
protected DirectorStyles m_Styles;
protected Rect m_TreeViewRect = new Rect(0, 0, 0, 0);
protected GUIContent m_ProblemIcon = new GUIContent();
bool m_MustRecomputeUnions = true;
int m_GroupDepth;
readonly bool m_IsReferencedTrack;
readonly List<TimelineClipUnion> m_Unions = new List<TimelineClipUnion>();
public override Rect boundingRect
{
get { return ToWindowSpace(m_TreeViewRect); }
}
public Rect ToWindowSpace(Rect localRect)
{
localRect.position += treeViewToWindowTransformation;
return localRect;
}
public override bool expandable
{
get { return !m_IsRoot; }
}
// The expanded rectangle (contains children) as calculated by the the tree gui
public Rect expandedRect { get; set; }
// The row rectangle (header only) as calculated by the tree gui
public Rect rowRect { get; set; }
// the drop rectangle as set by the tree gui when targetted by a drag and drop
public Rect dropRect { get; set; }
public TimelineGroupGUI(TreeViewController treeview, TimelineTreeViewGUI treeviewGUI, int id, int depth, TreeViewItem parent, string displayName, TrackAsset trackAsset, bool isRoot)
: base(id, depth, parent, displayName, trackAsset, treeview, treeviewGUI)
{
m_Styles = DirectorStyles.Instance;
m_IsRoot = isRoot;
var trackPath = AssetDatabase.GetAssetPath(trackAsset);
var sequencePath = AssetDatabase.GetAssetPath(treeviewGUI.TimelineWindow.state.editSequence.asset);
if (trackPath != sequencePath)
m_IsReferencedTrack = true;
m_GroupDepth = CalculateGroupDepth(parent);
}
public virtual float GetHeight(WindowState state)
{
// group tracks don't scale in height
return TrackEditor.DefaultTrackHeight;
}
public override void OnGraphRebuilt() {}
static int CalculateGroupDepth(TreeViewItem parent)
{
int depth = 0;
bool done = false;
do
{
var gui = parent as TimelineGroupGUI;
if (gui == null || gui.track == null)
done = true;
else
{
if (gui.track is GroupTrack)
depth++;
parent = parent.parent;
}
}
while (!done);
return depth;
}
void DrawTrackButtons(Rect headerRect, WindowState state)
{
const float buttonSize = WindowConstants.trackHeaderButtonSize;
const float padding = WindowConstants.trackHeaderButtonPadding;
var buttonRect = new Rect(headerRect.xMax - buttonSize - padding, headerRect.y + ((headerRect.height - buttonSize) / 2f), buttonSize, buttonSize);
if (GUI.Button(buttonRect, EditorGUIUtility.IconContent("CreateAddNew"), m_Styles.trackGroupAddButton))
{
// the drop down will apply to all selected tracks
if (!SelectionManager.Contains(track))
{
SelectionManager.Clear();
SelectionManager.Add(track);
}
SequencerContextMenu.ShowNewTracksContextMenu(SelectionManager.SelectedTracks().ToArray(), TimelineWindow.state, buttonRect);
}
buttonRect.x -= buttonSize;
var suitePadding = DrawButtonSuite(2, ref buttonRect);
DrawMuteButton(buttonRect, state);
buttonRect.x -= buttonSize + padding;
DrawLockButton(buttonRect, state);
buttonRect.x -= suitePadding;
}
public void SetExpanded(bool expanded)
{
var collapseChanged = expanded != isExpanded;
isExpanded = expanded;
if (collapseChanged)
{
track.SetCollapsed(!expanded);
m_MustRecomputeUnions = true;
}
}
public override void Draw(Rect headerRect, Rect contentRect, WindowState state)
{
if (track == null || m_IsRoot)
return;
if (m_MustRecomputeUnions)
RecomputeRectUnions();
if (depth == 1)
Graphics.DrawBackgroundRect(state, headerRect);
var background = headerRect;
background.height = expandedRect.height;
var groupColor = TrackResourceCache.GetTrackColor(track);
m_TreeViewRect = contentRect;
var col = groupColor;
var isSelected = SelectionManager.Contains(track);
if (isSelected)
col = DirectorStyles.Instance.customSkin.colorSelection;
else if (isDropTarget)
col = DirectorStyles.Instance.customSkin.colorDropTarget;
else
{
if (m_GroupDepth % 2 == 1)
{
float h, s, v;
Color.RGBToHSV(col, out h, out s, out v);
v += 0.06f;
col = Color.HSVToRGB(h, s, v);
}
}
if (background.width > 0)
{
using (new GUIColorOverride(col))
GUI.Box(background, GUIContent.none, m_Styles.groupBackground);
}
var trackRectBackground = headerRect;
trackRectBackground.xMin += background.width;
trackRectBackground.width = contentRect.width;
trackRectBackground.height = background.height;
if (isSelected)
{
col = state.IsEditingASubTimeline()
? m_Styles.customSkin.colorTrackSubSequenceBackgroundSelected
: m_Styles.customSkin.colorTrackBackgroundSelected;
}
else
{
col = m_Styles.customSkin.colorGroupTrackBackground;
}
EditorGUI.DrawRect(trackRectBackground, col);
if (!isExpanded && children != null && children.Count > 0)
{
var collapsedTrackRect = contentRect;
foreach (var u in m_Unions)
u.Draw(collapsedTrackRect, state);
}
using (new GUIGroupScope(headerRect))
{
var groupRect = new Rect(0, 0, headerRect.width, headerRect.height);
DrawName(groupRect, isSelected);
DrawTrackButtons(groupRect, state);
}
if (IsTrackRecording(state))
{
using (new GUIColorOverride(DirectorStyles.Instance.customSkin.colorTrackBackgroundRecording))
GUI.Label(background, GUIContent.none, m_Styles.displayBackground);
}
// is this a referenced track?
if (m_IsReferencedTrack)
{
var refRect = contentRect;
refRect.x = state.timeAreaRect.xMax - 20.0f;
refRect.y += 5.0f;
refRect.width = 30.0f;
GUI.Label(refRect, DirectorStyles.referenceTrackLabel, EditorStyles.label);
}
var bgRect = contentRect;
if (track as GroupTrack != null || AllChildrenMuted(this))
bgRect.height = expandedRect.height;
DrawTrackState(contentRect, bgRect, track);
}
void DrawName(Rect rect, bool isSelected)
{
var labelRect = rect;
labelRect.xMin += 20;
var actorName = track != null ? track.name : "missing";
labelRect.width = m_Styles.groupFont.CalcSize(new GUIContent(actorName)).x;
labelRect.width = Math.Max(labelRect.width, 50.0f);
// if we aren't bound to anything, we show a text field that allows to rename the actor
// otherwise we show a ObjectField to allow binding to a go
if (track != null && track is GroupTrack)
{
var textColor = m_Styles.groupFont.normal.textColor;
if (isSelected)
textColor = Color.white;
string newName;
EditorGUI.BeginChangeCheck();
using (new StyleNormalColorOverride(m_Styles.groupFont, textColor))
{
newName = EditorGUI.DelayedTextField(labelRect, GUIContent.none, track.GetInstanceID(), track.name, m_Styles.groupFont);
}
if (EditorGUI.EndChangeCheck() && !string.IsNullOrEmpty(newName))
{
track.name = newName;
displayName = track.name;
}
}
}
protected bool IsSubTrack()
{
if (track == null)
return false;
var parentTrack = track.parent as TrackAsset;
if (parentTrack == null)
return false;
return parentTrack.GetType() != typeof(GroupTrack);
}
protected TrackAsset ParentTrack()
{
if (IsSubTrack())
return track.parent as TrackAsset;
return null;
}
// is there currently a recording track
bool IsTrackRecording(WindowState state)
{
if (!state.recording)
return false;
if (track.GetType() != typeof(GroupTrack))
return false;
return state.GetArmedTrack(track) != null;
}
void RecomputeRectUnions()
{
m_MustRecomputeUnions = false;
m_Unions.Clear();
if (children == null)
return;
foreach (var c in children.OfType<TimelineTrackGUI>())
{
c.RebuildGUICacheIfNecessary();
m_Unions.AddRange(TimelineClipUnion.Build(c.clips));
}
}
static bool AllChildrenMuted(TimelineGroupGUI groupGui)
{
if (!groupGui.track.muted)
return false;
if (groupGui.children == null)
return true;
return groupGui.children.OfType<TimelineGroupGUI>().All(AllChildrenMuted);
}
protected static float DrawButtonSuite(int numberOfButtons, ref Rect buttonRect)
{
var style = DirectorStyles.Instance.trackButtonSuite;
var buttonWidth = WindowConstants.trackHeaderButtonSize * numberOfButtons + WindowConstants.trackHeaderButtonPadding * Math.Max(0, numberOfButtons - 1);
var suiteWidth = buttonWidth + style.padding.right + style.padding.left;
var rect = new Rect(buttonRect.xMax - style.margin.right - suiteWidth, buttonRect.y + style.margin.top, suiteWidth, buttonRect.height);
if (Event.current.type == EventType.Repaint)
style.Draw(rect, false, false, false, false);
buttonRect.x -= style.margin.right + style.padding.right;
return style.margin.left + style.padding.left;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7072e9e47288bac4d8c6bdfb2cf785a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,214 @@
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
abstract class TimelineTrackBaseGUI : TreeViewItem, IBounds
{
static class Styles
{
public static readonly GUIContent s_LockedAndMuted = L10n.TextContent("Locked / Muted");
public static readonly GUIContent s_LockedAndPartiallyMuted = L10n.TextContent("Locked / Partially Muted");
public static readonly GUIContent s_Locked = L10n.TextContent("Locked");
public static readonly GUIContent s_Muted = L10n.TextContent("Muted");
public static readonly GUIContent s_PartiallyMuted = L10n.TextContent("Partially Muted");
public static readonly GUIContent trackMuteBtnOnTooltip = L10n.TextContent(string.Empty, "Umute");
public static readonly GUIContent trackMuteBtnOffTooltip = L10n.TextContent(string.Empty, "Mute");
public static readonly GUIContent trackLockBtnOnTooltip = L10n.TextContent(string.Empty, "Unlock");
public static readonly GUIContent trackLockBtnOffTooltip = L10n.TextContent(string.Empty, "Lock");
public static readonly Texture2D lockBg = DirectorStyles.GetBackgroundImage(DirectorStyles.Instance.trackLockOverlay);
}
protected bool m_IsRoot = false;
readonly TimelineTreeViewGUI m_TreeViewGUI;
readonly TrackDrawer m_Drawer;
public Vector2 treeViewToWindowTransformation { get; set; }
public bool isExpanded { get; set; }
public bool isDropTarget { protected get; set; }
public TrackAsset track { get; }
TreeViewController treeView { get; }
public TimelineWindow TimelineWindow
{
get
{
if (m_TreeViewGUI == null)
return null;
return m_TreeViewGUI.TimelineWindow;
}
}
public TrackDrawer drawer
{
get { return m_Drawer; }
}
public virtual float GetVerticalSpacingBetweenTracks()
{
return 3.0f;
}
public bool visibleRow { get; set; } // is the header row visible
public bool visibleExpanded { get; set; } // is the expanded area (group) visible
public bool drawInsertionMarkerBefore { get; set; }
public bool drawInsertionMarkerAfter { get; set; }
public abstract Rect boundingRect { get; }
public abstract bool expandable { get; }
public abstract void Draw(Rect headerRect, Rect contentRect, WindowState state);
public abstract void OnGraphRebuilt(); // callback when the corresponding graph is rebuilt. This can happen, but not have the GUI rebuilt.
protected TimelineTrackBaseGUI(int id, int depth, TreeViewItem parent, string displayName, TrackAsset trackAsset, TreeViewController tv, TimelineTreeViewGUI tvgui)
: base(id, depth, parent, displayName)
{
m_Drawer = TrackDrawer.CreateInstance(trackAsset);
m_Drawer.sequencerState = tvgui.TimelineWindow.state;
isExpanded = false;
isDropTarget = false;
track = trackAsset;
treeView = tv;
m_TreeViewGUI = tvgui;
}
public static TimelineTrackBaseGUI FindGUITrack(TrackAsset track)
{
var allTracks = TimelineWindow.instance.allTracks;
return allTracks.Find(x => x.track == track);
}
protected void DrawTrackState(Rect trackRect, Rect expandedRect, TrackAsset track)
{
if (Event.current.type == EventType.Layout)
{
bool needStateBox = false;
//Mute
if (track.muted && !TimelineUtility.IsParentMuted(track))
{
Rect bgRect = expandedRect;
TimelineWindow.instance.OverlayDrawData.Add(OverlayDrawer.CreateColorOverlay(
GUIClip.Unclip(bgRect),
DirectorStyles.Instance.customSkin.colorTrackDarken));
needStateBox = true;
}
//Lock
if (!needStateBox && track.locked && !TimelineUtility.IsLockedFromGroup(track))
{
Rect bgRect = expandedRect;
TimelineWindow.instance.OverlayDrawData.Add(OverlayDrawer.CreateTextureOverlay(
GUIClip.Unclip(bgRect),
Styles.lockBg));
needStateBox = true;
}
if (needStateBox)
{
DrawTrackStateBox(trackRect, track);
}
}
}
static void DrawTrackStateBox(Rect trackRect, TrackAsset track)
{
var styles = DirectorStyles.Instance;
bool locked = track.locked && !TimelineUtility.IsLockedFromGroup(track);
bool muted = track.muted && !TimelineUtility.IsParentMuted(track);
bool allSubTrackMuted = TimelineUtility.IsAllSubTrackMuted(track);
GUIContent content = null;
if (locked && muted)
{
content = Styles.s_LockedAndMuted;
if (!allSubTrackMuted)
content = Styles.s_LockedAndPartiallyMuted;
}
else if (locked) content = Styles.s_Locked;
else if (muted)
{
content = Styles.s_Muted;
if (!allSubTrackMuted)
content = Styles.s_PartiallyMuted;
}
// the track could be locked, but we only show the 'locked portion' on the upper most track
// that is causing the lock
if (content == null)
return;
Rect textRect = Graphics.CalculateTextBoxSize(trackRect, styles.fontClip, content, WindowConstants.overlayTextPadding);
TimelineWindow.instance.OverlayDrawData.Add(
OverlayDrawer.CreateTextBoxOverlay(
GUIClip.Unclip(textRect),
content.text, styles.fontClip,
Color.white,
styles.customSkin.colorLockTextBG,
styles.displayBackground));
}
protected void DrawMuteButton(Rect rect, WindowState state)
{
using (new EditorGUI.DisabledScope(TimelineUtility.IsParentMuted(track)))
{
EditorGUI.BeginChangeCheck();
var isMuted = track.mutedInHierarchy;
var tooltip = isMuted ? Styles.trackMuteBtnOnTooltip : Styles.trackMuteBtnOffTooltip;
var muted = GUI.Toggle(rect, isMuted, tooltip, TimelineWindow.styles.trackMuteButton);
if (EditorGUI.EndChangeCheck())
MuteTrack.Mute(new[] { track }, muted);
}
}
protected void DrawLockButton(Rect rect, WindowState state)
{
using (new EditorGUI.DisabledScope(TimelineUtility.IsLockedFromGroup(track)))
{
EditorGUI.BeginChangeCheck();
var isLocked = track.lockedInHierarchy;
var tooltip = isLocked ? Styles.trackLockBtnOnTooltip : Styles.trackLockBtnOffTooltip;
var locked = GUI.Toggle(rect, track.lockedInHierarchy, tooltip, TimelineWindow.styles.trackLockButton);
if (EditorGUI.EndChangeCheck())
LockTrack.SetLockState(new[] { track }, locked);
}
}
public void DrawInsertionMarkers(Rect rowRectWithIndent)
{
const float insertionHeight = WindowConstants.trackInsertionMarkerHeight;
if (Event.current.type == EventType.Repaint && (drawInsertionMarkerAfter || drawInsertionMarkerBefore))
{
if (drawInsertionMarkerBefore)
{
var rect = new Rect(rowRectWithIndent.x, rowRectWithIndent.y - insertionHeight * 0.5f - 2.0f, rowRectWithIndent.width, insertionHeight);
EditorGUI.DrawRect(rect, Color.white);
}
if (drawInsertionMarkerAfter)
{
var rect = new Rect(rowRectWithIndent.x, rowRectWithIndent.y + rowRectWithIndent.height - insertionHeight * 0.5f + 1.0f, rowRectWithIndent.width, insertionHeight);
EditorGUI.DrawRect(rect, Color.white);
}
}
}
public void ClearDrawFlags()
{
if (Event.current.type == EventType.Repaint)
{
isDropTarget = false;
drawInsertionMarkerAfter = false;
drawInsertionMarkerBefore = false;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6b75424ba2b73c4eb3182b08dbd71ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,185 @@
using System;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Playables;
namespace UnityEditor.Timeline
{
class TimelineTrackErrorGUI : TimelineTrackBaseGUI
{
static class Styles
{
public static readonly GUIContent ErrorText = L10n.TextContent("Track cannot be loaded.", "Please fix any compile errors in the script for this track");
public static readonly Texture2D IconWarn = EditorGUIUtility.LoadIconRequired("console.warnicon.inactive.sml");
public static readonly GUIContent RemoveTrack = L10n.TextContent("Delete");
public static readonly Color WarningBoxBackgroundColor = new Color(115.0f / 255.0f, 115.0f / 255.0f, 115.0f / 255.0f); // approved for both skins
public static readonly Color WarningBoxHighlightColor = new Color(229 / 255.0f, 208 / 255.0f, 54 / 255.0f); // brigher than standard warning color for contrast
}
Rect m_TrackRect;
ScriptableObject m_ScriptableObject;
PlayableAsset m_Owner;
static GUIContent s_GUIContent = new GUIContent();
public TimelineTrackErrorGUI(TreeViewController treeview, TimelineTreeViewGUI treeviewGUI, int id, int depth, TreeViewItem parent, string displayName, ScriptableObject track, PlayableAsset owner)
: base(id, depth, parent, displayName, null, treeview, treeviewGUI)
{
m_ScriptableObject = track;
m_Owner = owner;
}
public override Rect boundingRect
{
get { return m_TrackRect; }
}
public override bool expandable
{
get { return false; }
}
public override void Draw(Rect headerRect, Rect contentRect, WindowState state)
{
m_TrackRect = contentRect;
DrawMissingTrackHeader(headerRect, state);
DrawMissingTrackBody(contentRect);
}
void DrawMissingTrackHeader(Rect headerRect, WindowState state)
{
var styles = DirectorStyles.Instance;
// Draw a header
Color backgroundColor = styles.customSkin.colorTrackHeaderBackground;
var bgRect = headerRect;
bgRect.x += styles.trackSwatchStyle.fixedWidth;
bgRect.width -= styles.trackSwatchStyle.fixedWidth;
EditorGUI.DrawRect(bgRect, backgroundColor);
// draw the warning icon
var errorIcon = Styles.IconWarn;
Rect iconRect = new Rect(headerRect.xMin + styles.trackSwatchStyle.fixedWidth, headerRect.yMin + 0.5f * (headerRect.height - errorIcon.height), errorIcon.width, errorIcon.height);
if (iconRect.width > 0 && iconRect.height > 0)
{
GUI.DrawTexture(iconRect, errorIcon, ScaleMode.ScaleAndCrop, true, 0, DirectorStyles.kClipErrorColor, 0, 0);
}
// Draw the name
// m_ScriptableObject == null will return true because the script can't be loaded. so this checks
// to make sure it is actually not null so we can grab the name
object o = m_ScriptableObject;
if (o != null)
{
s_GUIContent.text = m_ScriptableObject.name;
var textStyle = styles.trackHeaderFont;
textStyle.normal.textColor = styles.customSkin.colorTrackFont; // TODO -- we shouldn't modify the style like this. track header does it though :(
Rect textRect = headerRect;
textRect.xMin = iconRect.xMax + 1;
textRect.xMax = Math.Min(textRect.xMin + styles.trackHeaderFont.CalcSize(s_GUIContent).x, headerRect.xMax - 1);
EditorGUI.LabelField(textRect, s_GUIContent, textStyle);
}
// Draw the color swatch to the left of the track, darkened by the mute
var color = Color.Lerp(DirectorStyles.kClipErrorColor, styles.customSkin.colorTrackDarken, styles.customSkin.colorTrackDarken.a);
color.a = 1;
using (new GUIColorOverride(color))
{
var colorSwatchRect = headerRect;
colorSwatchRect.width = styles.trackSwatchStyle.fixedWidth;
GUI.Label(colorSwatchRect, GUIContent.none, styles.trackSwatchStyle);
}
// draw darken overlay
EditorGUI.DrawRect(bgRect, styles.customSkin.colorTrackDarken);
DrawRemoveMenu(headerRect, state);
}
void DrawRemoveMenu(Rect headerRect, WindowState state)
{
const float pad = 3;
const float buttonSize = 16;
var buttonRect = new Rect(headerRect.xMax - buttonSize - pad, headerRect.y + ((headerRect.height - buttonSize) / 2f) + 2, buttonSize, buttonSize);
if (GUI.Button(buttonRect, GUIContent.none, DirectorStyles.Instance.trackOptions))
{
GenericMenu menu = new GenericMenu();
var owner = m_Owner;
var scriptableObject = m_ScriptableObject;
menu.AddItem(Styles.RemoveTrack, false, () =>
{
if (TrackExtensions.RemoveBrokenTrack(owner, scriptableObject))
state.Refresh();
}
);
menu.ShowAsContext();
}
}
static void DrawMissingTrackBody(Rect contentRect)
{
if (contentRect.width < 0)
return;
var styles = DirectorStyles.Instance;
// draw a track rectangle
EditorGUI.DrawRect(contentRect, styles.customSkin.colorTrackDarken);
// draw the warning box
DrawScriptWarningBox(contentRect, Styles.ErrorText);
}
static void DrawScriptWarningBox(Rect trackRect, GUIContent content)
{
var styles = DirectorStyles.Instance;
const float kTextPadding = 52f;
var errorIcon = Styles.IconWarn;
float textWidth = styles.fontClip.CalcSize(content).x;
var outerRect = trackRect;
outerRect.width = textWidth + kTextPadding + errorIcon.width;
outerRect.x += (trackRect.width - outerRect.width) / 2f;
outerRect.height -= 4f;
outerRect.y += 1f;
bool drawText = true;
if (outerRect.width > trackRect.width)
{
outerRect.x = trackRect.x;
outerRect.width = trackRect.width;
drawText = false;
}
var innerRect = new Rect(outerRect.x + 2, outerRect.y + 2, outerRect.width - 4, outerRect.height - 4);
using (new GUIColorOverride(Styles.WarningBoxHighlightColor))
GUI.Box(outerRect, GUIContent.none, styles.displayBackground);
using (new GUIColorOverride(Styles.WarningBoxBackgroundColor))
GUI.Box(innerRect, GUIContent.none, styles.displayBackground);
if (drawText)
{
var iconRect = new Rect(outerRect.x + kTextPadding / 2.0f - 4.0f, outerRect.y + (outerRect.height - errorIcon.height) / 2.0f, errorIcon.width, errorIcon.height);
var textRect = new Rect(iconRect.xMax + 4.0f, outerRect.y, textWidth, outerRect.height);
GUI.DrawTexture(iconRect, errorIcon, ScaleMode.ScaleAndCrop, true, 0, Styles.WarningBoxHighlightColor, 0, 0);
Graphics.ShadowLabel(textRect, content, styles.fontClip, Color.white, Color.black);
}
else if (errorIcon.width > innerRect.width)
{
var iconRect = new Rect(outerRect.x + (outerRect.width - errorIcon.width) / 2.0f, outerRect.y + (outerRect.height - errorIcon.height) / 2.0f, errorIcon.width, errorIcon.height);
GUI.DrawTexture(iconRect, errorIcon, ScaleMode.ScaleAndCrop, true, 0, Styles.WarningBoxHighlightColor, 0, 0);
}
}
public override void OnGraphRebuilt() {}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e00dc5a0cda466b41b09a50bd18b4916
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,826 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using Object = UnityEngine.Object;
namespace UnityEditor.Timeline
{
class TimelineTrackGUI : TimelineGroupGUI, IClipCurveEditorOwner, IRowGUI
{
struct TrackDrawData
{
public bool m_AllowsRecording;
public bool m_ShowTrackBindings;
public bool m_HasBinding;
public bool m_IsSubTrack;
public PlayableBinding m_Binding;
public Object m_TrackBinding;
public Texture m_TrackIcon;
public bool m_HasMarkers;
}
static class Styles
{
public static readonly GUIContent trackCurvesBtnOnTooltip = DirectorStyles.TrTextContent(string.Empty, "Hide curves view");
public static readonly GUIContent trackCurvesBtnOffTooltip = DirectorStyles.TrTextContent(string.Empty, "Show curves view");
public static readonly GUIContent trackMarkerBtnOnTooltip = DirectorStyles.TrTextContent(string.Empty, "Collapse Track Markers");
public static readonly GUIContent trackMarkerBtnOffTooltip = DirectorStyles.TrTextContent(string.Empty, "Expand Track Markers");
public static readonly GUIContent kActiveRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "End recording");
public static readonly GUIContent kInactiveRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "Start recording");
public static readonly GUIContent kIgnorePreviewRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "Recording is disabled: scene preview is ignored for this TimelineAsset");
public static readonly GUIContent kDisabledRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty,
"Recording is not permitted when Track Offsets are set to Auto. Track Offset settings can be changed in the track menu of the base track.");
public static Texture2D kProblemIcon = DirectorStyles.GetBackgroundImage(DirectorStyles.Instance.warning);
}
static GUIContent s_ArmForRecordContentOn;
static GUIContent s_ArmForRecordContentOff;
static GUIContent s_ArmForRecordDisabled;
readonly InfiniteTrackDrawer m_InfiniteTrackDrawer;
readonly TrackEditor m_TrackEditor;
readonly GUIContent m_DefaultTrackIcon;
readonly TrackResizeHandle m_ResizeHandle;
TrackItemsDrawer m_ItemsDrawer;
TrackDrawData m_TrackDrawData;
TrackDrawOptions m_TrackDrawOptions;
bool m_InlineCurvesSkipped;
int m_TrackHash = -1;
int m_BlendHash = -1;
int m_LastDirtyIndex = -1;
bool? m_TrackHasAnimatableParameters;
int m_HeightExtension;
public override bool expandable
{
get { return hasChildren; }
}
internal InlineCurveEditor inlineCurveEditor { get; set; }
public ClipCurveEditor clipCurveEditor { get; private set; }
public bool inlineCurvesSelected => SelectionManager.IsCurveEditorFocused(this);
bool IClipCurveEditorOwner.showLoops
{
get { return false; }
}
TrackAsset IClipCurveEditorOwner.owner
{
get { return track; }
}
static bool DoesTrackAllowsRecording(TrackAsset track)
{
// if the root animation track is in auto mode, recording is not allowed
var animTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
if (animTrack != null)
return animTrack.trackOffset != TrackOffset.Auto;
return false;
}
bool trackHasAnimatableParameters
{
get
{
// cache this value to avoid the recomputation
if (!m_TrackHasAnimatableParameters.HasValue)
m_TrackHasAnimatableParameters = track.HasAnyAnimatableParameters() ||
track.clips.Any(c => c.HasAnyAnimatableParameters());
return m_TrackHasAnimatableParameters.Value;
}
}
public bool locked
{
get { return track.lockedInHierarchy; }
}
public bool showMarkers
{
get { return track.GetShowMarkers(); }
}
public bool muted
{
get { return track.muted; }
}
public List<TimelineClipGUI> clips
{
get
{
return m_ItemsDrawer.clips == null ? new List<TimelineClipGUI>(0) : m_ItemsDrawer.clips;
}
}
TrackAsset IRowGUI.asset { get { return track; } }
bool showTrackRecordingDisabled
{
get
{
// if the root animation track is in auto mode, recording is not allowed
var animTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
return animTrack != null && animTrack.trackOffset == TrackOffset.Auto;
}
}
public int heightExtension
{
get => m_HeightExtension;
set => m_HeightExtension = Math.Max(0, value);
}
float minimumHeight => m_TrackDrawOptions.minimumHeight <= 0.0f ? TrackEditor.DefaultTrackHeight : m_TrackDrawOptions.minimumHeight;
public TimelineTrackGUI(TreeViewController tv, TimelineTreeViewGUI w, int id, int depth, TreeViewItem parent, string displayName, TrackAsset sequenceActor)
: base(tv, w, id, depth, parent, displayName, sequenceActor, false)
{
var animationTrack = sequenceActor as AnimationTrack;
if (animationTrack != null)
m_InfiniteTrackDrawer = new InfiniteTrackDrawer(new AnimationTrackKeyDataSource(animationTrack));
else if (sequenceActor.HasAnyAnimatableParameters() && !sequenceActor.clips.Any())
m_InfiniteTrackDrawer = new InfiniteTrackDrawer(new TrackPropertyCurvesDataSource(sequenceActor));
UpdateInfiniteClipEditor(w.TimelineWindow);
var bindings = track.outputs.ToArray();
m_TrackDrawData.m_HasBinding = bindings.Length > 0;
if (m_TrackDrawData.m_HasBinding)
m_TrackDrawData.m_Binding = bindings[0];
m_TrackDrawData.m_IsSubTrack = IsSubTrack();
m_TrackDrawData.m_AllowsRecording = DoesTrackAllowsRecording(sequenceActor);
m_TrackDrawData.m_HasMarkers = track.GetMarkerCount() > 0;
m_DefaultTrackIcon = TrackResourceCache.GetTrackIcon(track);
m_TrackEditor = CustomTimelineEditorCache.GetTrackEditor(sequenceActor);
m_TrackDrawOptions = m_TrackEditor.GetTrackOptions_Safe(track, null);
m_TrackDrawOptions.errorText = null; // explicitly setting to null for an uninitialized state
m_ResizeHandle = new TrackResizeHandle(this);
heightExtension = TimelineWindowViewPrefs.GetTrackHeightExtension(track);
RebuildGUICacheIfNecessary();
}
public override float GetVerticalSpacingBetweenTracks()
{
if (track != null && track.isSubTrack)
return 1.0f; // subtracks have less of a gap than tracks
return base.GetVerticalSpacingBetweenTracks();
}
void UpdateInfiniteClipEditor(TimelineWindow window)
{
if (clipCurveEditor != null || track == null || !ShouldShowInfiniteClipEditor())
return;
var dataSource = CurveDataSource.Create(this);
clipCurveEditor = new ClipCurveEditor(dataSource, window, track);
}
void DetectTrackChanged()
{
if (Event.current.type == EventType.Layout)
{
// incremented when a track or it's clips changed
if (m_LastDirtyIndex != track.DirtyIndex)
{
m_TrackEditor.OnTrackChanged_Safe(track);
m_LastDirtyIndex = track.DirtyIndex;
}
OnTrackChanged();
}
}
// Called when the source track data, including it's clips have changed has changed.
void OnTrackChanged()
{
// recompute blends if necessary
int newBlendHash = BlendHash();
if (m_BlendHash != newBlendHash)
{
UpdateClipOverlaps();
m_BlendHash = newBlendHash;
}
RebuildGUICacheIfNecessary();
}
void UpdateDrawData(WindowState state)
{
if (Event.current.type == EventType.Layout)
{
m_TrackDrawData.m_ShowTrackBindings = false;
m_TrackDrawData.m_TrackBinding = null;
if (state.editSequence.director != null && showSceneReference)
{
m_TrackDrawData.m_ShowTrackBindings = state.GetWindow().currentMode.ShouldShowTrackBindings(state);
m_TrackDrawData.m_TrackBinding = state.editSequence.director.GetGenericBinding(track);
}
var lastHeight = m_TrackDrawOptions.minimumHeight;
m_TrackDrawOptions = m_TrackEditor.GetTrackOptions_Safe(track, m_TrackDrawData.m_TrackBinding);
m_TrackDrawData.m_HasMarkers = track.GetMarkerCount() > 0;
m_TrackDrawData.m_AllowsRecording = DoesTrackAllowsRecording(track);
m_TrackDrawData.m_TrackIcon = m_TrackDrawOptions.icon;
if (m_TrackDrawData.m_TrackIcon == null)
m_TrackDrawData.m_TrackIcon = m_DefaultTrackIcon.image;
// track height has changed. need to update gui
if (!Mathf.Approximately(lastHeight, m_TrackDrawOptions.minimumHeight))
state.Refresh();
}
}
public override void Draw(Rect headerRect, Rect contentRect, WindowState state)
{
DetectTrackChanged();
UpdateDrawData(state);
UpdateInfiniteClipEditor(state.GetWindow());
var trackHeaderRect = headerRect;
var trackContentRect = contentRect;
float inlineCurveHeight = contentRect.height - GetTrackContentHeight(state);
bool hasInlineCurve = inlineCurveHeight > 0.0f;
if (hasInlineCurve)
{
trackHeaderRect.height -= inlineCurveHeight;
trackContentRect.height -= inlineCurveHeight;
}
if (Event.current.type == EventType.Repaint)
{
m_TreeViewRect = trackContentRect;
}
track.SetCollapsed(!isExpanded);
RebuildGUICacheIfNecessary();
// Prevents from drawing outside of bounds, but does not effect layout or markers
bool isOwnerDrawSucceed = false;
Vector2 visibleTime = state.timeAreaShownRange;
if (drawer != null)
isOwnerDrawSucceed = drawer.DrawTrack(trackContentRect, track, visibleTime, state);
if (!isOwnerDrawSucceed)
{
using (new GUIViewportScope(trackContentRect))
DrawBackground(trackContentRect, track, visibleTime, state);
if (m_InfiniteTrackDrawer != null)
m_InfiniteTrackDrawer.DrawTrack(trackContentRect, track, visibleTime, state);
// draw after user customization so overlay text shows up
using (new GUIViewportScope(trackContentRect))
m_ItemsDrawer.Draw(trackContentRect, state);
}
DrawTrackHeader(trackHeaderRect, state);
if (hasInlineCurve)
{
var curvesHeaderRect = headerRect;
curvesHeaderRect.yMin = trackHeaderRect.yMax;
var curvesContentRect = contentRect;
curvesContentRect.yMin = trackContentRect.yMax;
DrawInlineCurves(curvesHeaderRect, curvesContentRect, state);
}
DrawTrackColorKind(headerRect);
DrawTrackState(contentRect, contentRect, track);
}
void DrawInlineCurves(Rect curvesHeaderRect, Rect curvesContentRect, WindowState state)
{
if (!track.GetShowInlineCurves())
return;
// Inline curves are not within the editor window -- case 952571
if (!IsInlineCurvesEditorInBounds(ToWindowSpace(curvesHeaderRect), curvesContentRect.height, state))
{
m_InlineCurvesSkipped = true;
return;
}
// If inline curves were skipped during the last event; we want to avoid rendering them until
// the next Layout event. Otherwise, we still get the RTE prevented above when the user resizes
// the timeline window very fast. -- case 952571
if (m_InlineCurvesSkipped && Event.current.type != EventType.Layout)
return;
m_InlineCurvesSkipped = false;
if (inlineCurveEditor == null)
inlineCurveEditor = new InlineCurveEditor(this);
curvesHeaderRect.x += DirectorStyles.kBaseIndent;
curvesHeaderRect.width -= DirectorStyles.kBaseIndent;
inlineCurveEditor.Draw(curvesHeaderRect, curvesContentRect, state);
}
static bool IsInlineCurvesEditorInBounds(Rect windowSpaceTrackRect, float inlineCurveHeight, WindowState state)
{
var legalHeight = state.windowHeight;
var trackTop = windowSpaceTrackRect.y;
var inlineCurveOffset = windowSpaceTrackRect.height - inlineCurveHeight;
return legalHeight - trackTop - inlineCurveOffset > 0;
}
void DrawErrorIcon(Rect position, WindowState state)
{
Rect bindingLabel = position;
bindingLabel.x = position.xMax + 3;
bindingLabel.width = state.bindingAreaWidth;
EditorGUI.LabelField(position, m_ProblemIcon);
}
void DrawBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
bool canDrawRecordBackground = IsRecording(state);
if (canDrawRecordBackground)
{
DrawRecordingTrackBackground(trackRect, trackAsset, visibleTime, state);
}
else
{
Color trackBackgroundColor;
if (SelectionManager.Contains(track))
{
trackBackgroundColor = state.IsEditingASubTimeline() ?
DirectorStyles.Instance.customSkin.colorTrackSubSequenceBackgroundSelected :
DirectorStyles.Instance.customSkin.colorTrackBackgroundSelected;
}
else
{
trackBackgroundColor = state.IsEditingASubTimeline() ?
DirectorStyles.Instance.customSkin.colorTrackSubSequenceBackground :
DirectorStyles.Instance.customSkin.colorTrackBackground;
}
EditorGUI.DrawRect(trackRect, trackBackgroundColor);
}
}
float InlineCurveHeight()
{
return track.GetShowInlineCurves() && CanDrawInlineCurve()
? TimelineWindowViewPrefs.GetInlineCurveHeight(track)
: 0.0f;
}
public override float GetHeight(WindowState state)
{
var height = GetTrackContentHeight(state);
if (CanDrawInlineCurve())
height += InlineCurveHeight();
return height;
}
float GetTrackContentHeight(WindowState state)
{
var defaultHeight = Mathf.Min(minimumHeight, TrackEditor.MaximumTrackHeight);
return (defaultHeight + heightExtension) * state.trackScale;
}
static bool CanDrawIcon(GUIContent icon)
{
return icon != null && icon != GUIContent.none && icon.image != null;
}
bool showSceneReference
{
get
{
return track != null &&
m_TrackDrawData.m_HasBinding &&
!m_TrackDrawData.m_IsSubTrack &&
m_TrackDrawData.m_Binding.sourceObject != null &&
m_TrackDrawData.m_Binding.outputTargetType != null &&
typeof(Object).IsAssignableFrom(m_TrackDrawData.m_Binding.outputTargetType);
}
}
void DrawTrackHeader(Rect trackHeaderRect, WindowState state)
{
using (new GUIViewportScope(trackHeaderRect))
{
var rect = trackHeaderRect;
DrawHeaderBackground(trackHeaderRect);
rect.x += m_Styles.trackSwatchStyle.fixedWidth;
const float buttonSize = WindowConstants.trackHeaderButtonSize;
const float padding = WindowConstants.trackHeaderButtonPadding;
var buttonRect = new Rect(trackHeaderRect.xMax - buttonSize - padding, rect.y + (rect.height - buttonSize) / 2f, buttonSize, buttonSize);
rect.x += DrawTrackIconKind(rect, state);
if (track is GroupTrack)
return;
buttonRect.x -= DrawTrackDropDownMenu(buttonRect);
var suiteRect = DrawGeneralSuite(state, buttonRect);
suiteRect = DrawCustomSuite(state, suiteRect);
var bindingRect = new Rect(rect.x, rect.y, suiteRect.xMax - rect.x, rect.height);
DrawTrackBinding(bindingRect, trackHeaderRect);
}
m_ResizeHandle.Draw(trackHeaderRect, state);
}
Rect DrawGeneralSuite(WindowState state, Rect rect)
{
const float buttonWidth = WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
var padding = DrawButtonSuite(3, ref rect);
DrawMuteButton(rect, state);
rect.x -= buttonWidth;
DrawLockButton(rect, state);
rect.x -= buttonWidth;
DrawLockMarkersButton(rect, state);
rect.x -= buttonWidth;
rect.x -= padding;
return rect;
}
Rect DrawCustomSuite(WindowState state, Rect rect)
{
var numberOfButtons = 0;
if (m_TrackDrawData.m_AllowsRecording || showTrackRecordingDisabled)
numberOfButtons++;
if (CanDrawInlineCurve())
numberOfButtons++;
if (drawer.HasCustomTrackHeaderButton())
numberOfButtons++;
if (numberOfButtons == 0)
return rect;
var padding = DrawButtonSuite(numberOfButtons, ref rect);
rect.x -= DrawRecordButton(rect, state);
rect.x -= DrawInlineCurveButton(rect, state);
rect.x -= DrawCustomTrackButton(rect, state);
rect.x -= padding;
return rect;
}
void DrawHeaderBackground(Rect headerRect)
{
Color backgroundColor = SelectionManager.Contains(track)
? DirectorStyles.Instance.customSkin.colorSelection
: DirectorStyles.Instance.customSkin.colorTrackHeaderBackground;
var bgRect = headerRect;
bgRect.x += m_Styles.trackSwatchStyle.fixedWidth;
bgRect.width -= m_Styles.trackSwatchStyle.fixedWidth;
EditorGUI.DrawRect(bgRect, backgroundColor);
}
void DrawTrackColorKind(Rect rect)
{
// subtracks don't draw the color, the parent does that.
if (track != null && track.isSubTrack)
return;
if (rect.width <= 0) return;
using (new GUIColorOverride(m_TrackDrawOptions.trackColor))
{
rect.width = m_Styles.trackSwatchStyle.fixedWidth;
GUI.Label(rect, GUIContent.none, m_Styles.trackSwatchStyle);
}
}
float DrawTrackIconKind(Rect rect, WindowState state)
{
// no icons on subtracks
if (track != null && track.isSubTrack)
return 0.0f;
rect.yMin += (rect.height - 16f) / 2f;
rect.width = 16.0f;
rect.height = 16.0f;
if (!string.IsNullOrEmpty(m_TrackDrawOptions.errorText))
{
m_ProblemIcon.image = Styles.kProblemIcon;
m_ProblemIcon.tooltip = m_TrackDrawOptions.errorText;
if (CanDrawIcon(m_ProblemIcon))
DrawErrorIcon(rect, state);
}
else
{
var content = GUIContent.Temp(m_TrackDrawData.m_TrackIcon, m_DefaultTrackIcon.tooltip);
if (CanDrawIcon(content))
GUI.Box(rect, content, GUIStyle.none);
}
return rect.width;
}
void DrawTrackBinding(Rect rect, Rect headerRect)
{
if (m_TrackDrawData.m_ShowTrackBindings)
{
DoTrackBindingGUI(rect);
return;
}
var textStyle = m_Styles.trackHeaderFont;
textStyle.normal.textColor = SelectionManager.Contains(track) ? Color.white : m_Styles.customSkin.colorTrackFont;
string trackName = track.name;
EditorGUI.BeginChangeCheck();
// by default the size is just the width of the string (for selection purposes)
rect.width = m_Styles.trackHeaderFont.CalcSize(new GUIContent(trackName)).x;
// if we are editing, supply the entire width of the header
if (GUIUtility.keyboardControl == track.GetInstanceID())
rect.width = (headerRect.xMax - rect.xMin) - (5 * WindowConstants.trackHeaderButtonSize);
trackName = EditorGUI.DelayedTextField(rect, GUIContent.none, track.GetInstanceID(), track.name, textStyle);
if (EditorGUI.EndChangeCheck())
{
UndoExtensions.RegisterTrack(track, L10n.Tr("Rename Track"));
track.name = trackName;
}
}
float DrawTrackDropDownMenu(Rect rect)
{
if (GUI.Button(rect, GUIContent.none, m_Styles.trackOptions))
{
// the drop down will apply to all selected tracks
if (!SelectionManager.Contains(track))
{
SelectionManager.Clear();
SelectionManager.Add(track);
}
SequencerContextMenu.ShowTrackContextMenu(null);
}
return WindowConstants.trackHeaderButtonSize;
}
bool CanDrawInlineCurve()
{
// Note: A track with animatable parameters always has inline curves.
return trackHasAnimatableParameters || TimelineUtility.TrackHasAnimationCurves(track);
}
float DrawInlineCurveButton(Rect rect, WindowState state)
{
if (!CanDrawInlineCurve())
{
//Force to close Inline Curve UI if the inline cannot be drawn.
if (track.GetShowInlineCurves())
track.SetShowInlineCurves(false);
return 0.0f;
}
// Override enable state to display "Show Inline Curves" button in disabled state.
bool prevEnabledState = GUI.enabled;
GUI.enabled = true;
var showInlineCurves = track.GetShowInlineCurves();
var tooltip = showInlineCurves ? Styles.trackCurvesBtnOnTooltip : Styles.trackCurvesBtnOffTooltip;
var newValue = GUI.Toggle(rect, track.GetShowInlineCurves(), tooltip, DirectorStyles.Instance.trackCurvesButton);
GUI.enabled = prevEnabledState;
if (newValue != track.GetShowInlineCurves())
{
track.SetShowInlineCurves(newValue);
state.GetWindow().treeView.CalculateRowRects();
}
return WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
}
float DrawRecordButton(Rect rect, WindowState state)
{
var style = DirectorStyles.Instance.trackRecordButton;
const float buttonWidth = WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
if (m_TrackDrawData.m_AllowsRecording)
{
bool isPlayerDisabled = state.editSequence.director != null && !state.editSequence.director.isActiveAndEnabled;
GameObject goBinding = m_TrackDrawData.m_TrackBinding as GameObject;
if (goBinding == null)
{
Component c = m_TrackDrawData.m_TrackBinding as Component;
if (c != null)
goBinding = c.gameObject;
}
if (goBinding == null && m_TrackDrawData.m_IsSubTrack)
goBinding = ParentTrack().GetGameObjectBinding(state.editSequence.director);
var isTrackBindingValid = goBinding != null;
var trackErrorDisableButton = !string.IsNullOrEmpty(m_TrackDrawOptions.errorText) && isTrackBindingValid && goBinding.activeInHierarchy;
var disableButton = track.lockedInHierarchy || isPlayerDisabled || trackErrorDisableButton || !isTrackBindingValid || state.ignorePreview;
using (new EditorGUI.DisabledScope(disableButton))
{
if (IsRecording(state))
{
state.editorWindow.Repaint();
var remainder = Time.realtimeSinceStartup % 1;
if (remainder < 0.22f)
style = GUIStyle.none;
if (GUI.Button(rect, Styles.kActiveRecordButtonTooltip, style) || isPlayerDisabled || !isTrackBindingValid)
state.UnarmForRecord(track);
}
else if (!track.timelineAsset.editorSettings.scenePreview)
GUI.Button(rect, Styles.kIgnorePreviewRecordButtonTooltip, style);
else
{
if (GUI.Button(rect, Styles.kInactiveRecordButtonTooltip, style))
state.ArmForRecord(track);
}
return buttonWidth;
}
}
if (showTrackRecordingDisabled)
{
using (new EditorGUI.DisabledScope(true))
GUI.Button(rect, Styles.kDisabledRecordButtonTooltip, style);
return buttonWidth;
}
return 0.0f;
}
float DrawCustomTrackButton(Rect rect, WindowState state)
{
if (!drawer.HasCustomTrackHeaderButton())
return 0.0f;
drawer.DrawTrackHeaderButton(rect, state);
return WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
}
void DrawLockMarkersButton(Rect rect, WindowState state)
{
var hasMarkers = track.GetMarkerCount() != 0;
var markersShown = showMarkers && hasMarkers;
var style = TimelineWindow.styles.trackMarkerButton;
EditorGUI.BeginChangeCheck();
var tooltip = markersShown ? Styles.trackMarkerBtnOnTooltip : Styles.trackMarkerBtnOffTooltip;
var toggleMarkers = GUI.Toggle(rect, markersShown, tooltip, style);
if (EditorGUI.EndChangeCheck() && hasMarkers)
track.SetShowTrackMarkers(toggleMarkers);
}
static void ObjectBindingField(Rect position, Object obj, PlayableBinding binding, int controlId)
{
var allowScene =
typeof(GameObject).IsAssignableFrom(binding.outputTargetType) ||
typeof(Component).IsAssignableFrom(binding.outputTargetType);
var bindingFieldRect = EditorGUI.IndentedRect(position);
using (new GUIViewportScope(bindingFieldRect))
{
EditorGUI.BeginChangeCheck();
var newObject = UnityEditorInternals.DoObjectField(EditorGUI.IndentedRect(position), obj, binding.outputTargetType, controlId, allowScene, true);
if (EditorGUI.EndChangeCheck())
BindingUtility.BindWithInteractiveEditorValidation(TimelineEditor.inspectedDirector, binding.sourceObject as TrackAsset, newObject);
}
}
void DoTrackBindingGUI(Rect rect)
{
var bindingRect = new Rect(
rect.xMin,
rect.y + (rect.height - WindowConstants.trackHeaderBindingHeight) / 2f,
Mathf.Min(rect.width, WindowConstants.trackBindingMaxSize) - WindowConstants.trackBindingPadding,
WindowConstants.trackHeaderBindingHeight);
if (m_TrackDrawData.m_Binding.outputTargetType != null && typeof(Object).IsAssignableFrom(m_TrackDrawData.m_Binding.outputTargetType))
{
var controlId = GUIUtility.GetControlID("s_ObjectFieldHash".GetHashCode(), FocusType.Passive, rect);
var previousActiveControlId = DragAndDrop.activeControlID;
ObjectBindingField(bindingRect, m_TrackDrawData.m_TrackBinding, m_TrackDrawData.m_Binding, controlId);
if (previousActiveControlId != controlId && DragAndDrop.activeControlID == controlId)
TimelineDragging.OnTrackBindingDragUpdate(track);
}
}
bool IsRecording(WindowState state)
{
return state.recording && state.IsArmedForRecord(track);
}
// background to draw during recording
void DrawRecordingTrackBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
{
if (drawer != null)
drawer.DrawRecordingBackground(trackRect, trackAsset, visibleTime, state);
}
void UpdateClipOverlaps()
{
TrackExtensions.ComputeBlendsFromOverlaps(track.clips);
}
internal void RebuildGUICacheIfNecessary()
{
if (m_TrackHash == track.Hash())
return;
m_ItemsDrawer = new TrackItemsDrawer(this);
m_TrackHash = track.Hash();
}
int BlendHash()
{
var hash = 0;
foreach (var clip in track.clips)
{
hash = HashUtility.CombineHash(hash,
(clip.duration - clip.start).GetHashCode(),
((int)clip.blendInCurveMode).GetHashCode(),
((int)clip.blendOutCurveMode).GetHashCode());
}
return hash;
}
// callback when the corresponding graph is rebuilt. This can happen, but not have the GUI rebuilt.
public override void OnGraphRebuilt()
{
RefreshCurveEditor();
}
void RefreshCurveEditor()
{
var window = TimelineWindow.instance;
if (track != null && window != null && window.state != null)
{
bool hasEditor = clipCurveEditor != null;
bool shouldHaveEditor = ShouldShowInfiniteClipEditor();
if (hasEditor != shouldHaveEditor)
window.state.AddEndFrameDelegate((x, currentEvent) =>
{
x.Refresh();
return true;
});
}
}
bool ShouldShowInfiniteClipEditor()
{
var animationTrack = track as AnimationTrack;
if (animationTrack != null)
return animationTrack.ShouldShowInfiniteClipEditor();
return trackHasAnimatableParameters;
}
public void SelectCurves()
{
SelectionManager.RemoveTimelineSelection();
SelectionManager.SelectInlineCurveEditor(this);
}
public void ValidateCurvesSelection() {}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3b27007577486c4d8dc088f989b288c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,32 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
class TrackResizeHandle : IBounds
{
public Rect boundingRect { get; private set; }
public TimelineTrackGUI trackGUI { get; }
public TrackResizeHandle(TimelineTrackGUI trackGUI)
{
this.trackGUI = trackGUI;
}
public void Draw(Rect headerRect, WindowState state)
{
const float resizeHandleHeight = WindowConstants.trackResizeHandleHeight;
var rect = new Rect(headerRect.xMin, headerRect.yMax - (0.5f * resizeHandleHeight), headerRect.width, resizeHandleHeight);
boundingRect = trackGUI.ToWindowSpace(rect);
Rect cursorRect = rect;
cursorRect.height--;
if (GUIUtility.hotControl == 0)
{
EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.SplitResizeUpDown);
state.headerSpacePartitioner.AddBounds(this);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 042c802f94d43dc45a049bf3b5be79ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,15 @@
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrackPropertyCurvesDataSource : BasePropertyKeyDataSource
{
protected override AnimationClip animationClip { get; }
public TrackPropertyCurvesDataSource(TrackAsset track)
{
animationClip = track != null ? track.curves : null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8964ef46e1d446dc9eb17a36a3e1f14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: