797 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			797 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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);
 | |
|         }
 | |
|     }
 | |
| }
 |