407 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			407 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Linq; | ||
|  | using UnityEditor.Timeline.Actions; | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Timeline; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     static class Gaps | ||
|  |     { | ||
|  |         static readonly string kInsertTime = L10n.Tr("Insert Time"); | ||
|  | 
 | ||
|  |         public static void Insert(TimelineAsset asset, double at, double amount, double tolerance) | ||
|  |         { | ||
|  |             var tracks = asset.flattenedTracks.Where(x => x.lockedInHierarchy == false).ToList(); | ||
|  |             // gather all clips | ||
|  |             var clips = tracks.SelectMany(x => x.clips).Where(x => (x.start - at) >= -tolerance).ToList(); | ||
|  |             var markers = tracks.SelectMany(x => x.GetMarkers()).Where(x => (x.time - at) >= -tolerance).ToList(); | ||
|  | 
 | ||
|  |             // push undo on the tracks for the clips that are being modified | ||
|  |             UndoExtensions.RegisterClips(clips, kInsertTime); | ||
|  | 
 | ||
|  |             // push the clips | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 clip.start += amount; | ||
|  |             } | ||
|  | 
 | ||
|  |             // push undos and move the markers | ||
|  |             UndoExtensions.RegisterMarkers(markers, kInsertTime); | ||
|  |             foreach (var marker in markers) | ||
|  |             { | ||
|  |                 marker.time += amount; | ||
|  |             } | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.ContentsModified); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class PlayheadContextMenu : Manipulator | ||
|  |     { | ||
|  |         readonly TimeAreaItem m_TimeAreaItem; | ||
|  |         static readonly int[] kFrameInsertionValues = { 5, 10, 25, 100 }; | ||
|  |         static readonly GUIContent[] kFrameInsertionValuesGuiContents = | ||
|  |         { | ||
|  |             L10n.TextContent("Insert/Frame/5 Frames"), | ||
|  |             L10n.TextContent("Insert/Frame/10 Frames"), | ||
|  |             L10n.TextContent("Insert/Frame/25 Frames"), | ||
|  |             L10n.TextContent("Insert/Frame/100 Frames") | ||
|  |         }; | ||
|  | 
 | ||
|  |         static readonly GUIContent kSingleFrameGuiContents = L10n.TextContent("Insert/Frame/Single"); | ||
|  |         static readonly GUIContent kSelectedTimeGuiContents = L10n.TextContent("Insert/Selected Time"); | ||
|  | 
 | ||
|  |         public PlayheadContextMenu(TimeAreaItem timeAreaItem) | ||
|  |         { | ||
|  |             m_TimeAreaItem = timeAreaItem; | ||
|  |         } | ||
|  | 
 | ||
|  |         protected override bool ContextClick(Event evt, WindowState state) | ||
|  |         { | ||
|  |             if (!m_TimeAreaItem.bounds.Contains(evt.mousePosition)) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate); | ||
|  |             var menu = new GenericMenu(); | ||
|  | 
 | ||
|  |             if (!TimelineWindow.instance.state.editSequence.isReadOnly) | ||
|  |             { | ||
|  |                 menu.AddItem(kSingleFrameGuiContents, false, () => | ||
|  |                     Gaps.Insert(state.editSequence.asset, state.editSequence.time, | ||
|  |                         1.0 / state.referenceSequence.frameRate, tolerance) | ||
|  |                 ); | ||
|  | 
 | ||
|  |                 for (var i = 0; i != kFrameInsertionValues.Length; ++i) | ||
|  |                 { | ||
|  |                     double f = kFrameInsertionValues[i]; | ||
|  |                     menu.AddItem( | ||
|  |                         kFrameInsertionValuesGuiContents[i], | ||
|  |                         false, () => | ||
|  |                             Gaps.Insert(state.editSequence.asset, state.editSequence.time, | ||
|  |                                 f / state.referenceSequence.frameRate, tolerance) | ||
|  |                     ); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 var playRangeTime = state.playRange; | ||
|  |                 if (playRangeTime.start > playRangeTime.end) | ||
|  |                 { | ||
|  |                     menu.AddItem(kSelectedTimeGuiContents, false, () => | ||
|  |                         Gaps.Insert(state.editSequence.asset, playRangeTime.start, playRangeTime.end - playRangeTime.start, | ||
|  |                             TimeUtility.GetEpsilon(playRangeTime.start, state.referenceSequence.frameRate)) | ||
|  |                     ); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             menu.AddItem(L10n.TextContent("Select/Clips Ending Before"), false, () => SelectClipsEndingBefore(state)); | ||
|  |             menu.AddItem(L10n.TextContent("Select/Clips Starting Before"), false, () => SelectClipsStartingBefore(state)); | ||
|  |             menu.AddItem(L10n.TextContent("Select/Clips Ending After"), false, () => SelectClipsEndingAfter(state)); | ||
|  |             menu.AddItem(L10n.TextContent("Select/Clips Starting After"), false, () => SelectClipsStartingAfter(state)); | ||
|  |             menu.AddItem(L10n.TextContent("Select/Clips Intersecting"), false, () => SelectClipsIntersecting(state)); | ||
|  |             menu.AddItem(L10n.TextContent("Select/Blends Intersecting"), false, () => SelectBlendsIntersecting(state)); | ||
|  |             menu.ShowAsContext(); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectClipsEndingBefore(WindowState state) | ||
|  |         { | ||
|  |             var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate); | ||
|  |             SelectMenuCallback(x => x.end < state.editSequence.time + tolerance, state); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectClipsStartingBefore(WindowState state) | ||
|  |         { | ||
|  |             var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate); | ||
|  |             SelectMenuCallback(x => x.start < state.editSequence.time + tolerance, state); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectClipsEndingAfter(WindowState state) | ||
|  |         { | ||
|  |             var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate); | ||
|  |             SelectMenuCallback(x => x.end - state.editSequence.time >= -tolerance, state); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectClipsStartingAfter(WindowState state) | ||
|  |         { | ||
|  |             var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate); | ||
|  |             SelectMenuCallback(x => x.start - state.editSequence.time >= -tolerance, state); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectClipsIntersecting(WindowState state) | ||
|  |         { | ||
|  |             SelectMenuCallback(x => x.start <= state.editSequence.time && state.editSequence.time <= x.end, state); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void SelectBlendsIntersecting(WindowState state) | ||
|  |         { | ||
|  |             SelectMenuCallback(x => SelectBlendingIntersecting(x, state.editSequence.time), state); | ||
|  |         } | ||
|  | 
 | ||
|  |         static bool SelectBlendingIntersecting(TimelineClip clip, double time) | ||
|  |         { | ||
|  |             return clip.start <= time && time <= clip.end && ( | ||
|  |                 (time <= clip.start + clip.blendInDuration) || | ||
|  |                 (time >= clip.end - clip.blendOutDuration) | ||
|  |             ); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void SelectMenuCallback(Func<TimelineClip, bool> selector, WindowState state) | ||
|  |         { | ||
|  |             var allClips = state.GetWindow().treeView.allClipGuis; | ||
|  |             if (allClips == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             SelectionManager.Clear(); | ||
|  |             for (var i = 0; i != allClips.Count; ++i) | ||
|  |             { | ||
|  |                 var c = allClips[i]; | ||
|  | 
 | ||
|  |                 if (c != null && c.clip != null && c.clip.GetParentTrack().lockedInHierarchy == false && selector(c.clip)) | ||
|  |                 { | ||
|  |                     SelectionManager.Add(c.clip); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class TimeAreaContextMenu : Manipulator | ||
|  |     { | ||
|  |         protected override bool ContextClick(Event evt, WindowState state) | ||
|  |         { | ||
|  |             if (state.timeAreaRect.Contains(Event.current.mousePosition)) | ||
|  |             { | ||
|  |                 var menu = new GenericMenu(); | ||
|  |                 AddTimeAreaMenuItems(menu, state); | ||
|  |                 menu.ShowAsContext(); | ||
|  |                 return true; | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void AddTimeAreaMenuItems(GenericMenu menu, WindowState state) | ||
|  |         { | ||
|  |             foreach (var value in Enum.GetValues(typeof(TimelineAsset.DurationMode))) | ||
|  |             { | ||
|  |                 var mode = (TimelineAsset.DurationMode)value; | ||
|  |                 var item = EditorGUIUtility.TextContent(string.Format(TimelineWindow.Styles.DurationModeText, L10n.Tr(ObjectNames.NicifyVariableName(mode.ToString())))); | ||
|  | 
 | ||
|  |                 if (state.recording || state.IsEditingASubTimeline() || state.editSequence.asset == null | ||
|  |                     || state.editSequence.isReadOnly) | ||
|  |                     menu.AddDisabledItem(item); | ||
|  |                 else | ||
|  |                     menu.AddItem(item, state.editSequence.asset.durationMode == mode, () => SelectDurationCallback(state, mode)); | ||
|  | 
 | ||
|  |                 menu.AddItem(DirectorStyles.showMarkersOnTimeline, state.showMarkerHeader, () => state.showMarkerHeader = !state.showMarkerHeader); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         static void SelectDurationCallback(WindowState state, TimelineAsset.DurationMode mode) | ||
|  |         { | ||
|  |             if (mode == state.editSequence.asset.durationMode) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterTimeline(state.editSequence.asset, "Duration Mode"); | ||
|  | 
 | ||
|  | 
 | ||
|  |             // if we switched from Auto to Fixed, use the auto duration as the new fixed duration so the end marker stay in the same position. | ||
|  |             if (state.editSequence.asset.durationMode == TimelineAsset.DurationMode.BasedOnClips && mode == TimelineAsset.DurationMode.FixedLength) | ||
|  |             { | ||
|  |                 state.editSequence.asset.UpdateFixedDurationWithItemsDuration(); | ||
|  |             } | ||
|  | 
 | ||
|  |             state.editSequence.asset.durationMode = mode; | ||
|  |             state.UpdateRootPlayableDuration(state.editSequence.duration); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class Scrub : Manipulator | ||
|  |     { | ||
|  |         readonly Func<Event, WindowState, bool> m_OnMouseDown; | ||
|  |         readonly Action<double> m_OnMouseDrag; | ||
|  |         readonly System.Action m_OnMouseUp; | ||
|  | 
 | ||
|  |         bool m_IsCaptured; | ||
|  | 
 | ||
|  |         public Scrub(Func<Event, WindowState, bool> onMouseDown, Action<double> onMouseDrag, System.Action onMouseUp) | ||
|  |         { | ||
|  |             m_OnMouseDown = onMouseDown; | ||
|  |             m_OnMouseDrag = onMouseDrag; | ||
|  |             m_OnMouseUp = onMouseUp; | ||
|  |         } | ||
|  | 
 | ||
|  |         protected override bool MouseDown(Event evt, WindowState state) | ||
|  |         { | ||
|  |             if (evt.button != 0) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (!m_OnMouseDown(evt, state)) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             state.AddCaptured(this); | ||
|  |             m_IsCaptured = true; | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         protected override bool MouseUp(Event evt, WindowState state) | ||
|  |         { | ||
|  |             if (!m_IsCaptured) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             m_IsCaptured = false; | ||
|  |             state.RemoveCaptured(this); | ||
|  | 
 | ||
|  |             m_OnMouseUp(); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         protected override bool MouseDrag(Event evt, WindowState state) | ||
|  |         { | ||
|  |             if (!m_IsCaptured) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             m_OnMouseDrag(state.GetSnappedTimeAtMousePosition(evt.mousePosition)); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class TimeAreaItem : Control | ||
|  |     { | ||
|  |         public Color headColor { get; set; } | ||
|  |         public Color lineColor { get; set; } | ||
|  |         public bool drawLine { get; set; } | ||
|  |         public bool drawHead { get; set; } | ||
|  |         public bool canMoveHead { get; set; } | ||
|  |         public string tooltip { get; set; } | ||
|  |         public Vector2 boundOffset { get; set; } | ||
|  | 
 | ||
|  |         readonly GUIContent m_HeaderContent = new GUIContent(); | ||
|  |         readonly GUIStyle m_Style; | ||
|  |         readonly Tooltip m_Tooltip; | ||
|  | 
 | ||
|  |         Rect m_BoundingRect; | ||
|  | 
 | ||
|  |         float widgetHeight { get { return m_Style.fixedHeight; } } | ||
|  |         float widgetWidth { get { return m_Style.fixedWidth; } } | ||
|  | 
 | ||
|  |         public Rect bounds | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 Rect r = m_BoundingRect; | ||
|  |                 r.y = TimelineWindow.instance.state.timeAreaRect.yMax - widgetHeight; | ||
|  |                 r.position += boundOffset; | ||
|  | 
 | ||
|  |                 return r; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public GUIStyle style | ||
|  |         { | ||
|  |             get { return m_Style; } | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         public bool showTooltip { get; set; } | ||
|  | 
 | ||
|  |         // is this the first frame the drag callback is being invoked | ||
|  |         public bool firstDrag { get; private set; } | ||
|  | 
 | ||
|  |         public TimeAreaItem(GUIStyle style, Action<double> onDrag) | ||
|  |         { | ||
|  |             m_Style = style; | ||
|  |             headColor = Color.white; | ||
|  |             var scrub = new Scrub( | ||
|  |                 (evt, state) => | ||
|  |                 { | ||
|  |                     firstDrag = true; | ||
|  |                     return state.timeAreaRect.Contains(evt.mousePosition) && bounds.Contains(evt.mousePosition); | ||
|  |                 }, | ||
|  |                 (d) => | ||
|  |                 { | ||
|  |                     if (onDrag != null) | ||
|  |                         onDrag(d); | ||
|  |                     firstDrag = false; | ||
|  |                 }, | ||
|  |                 () => | ||
|  |                 { | ||
|  |                     showTooltip = false; | ||
|  |                     firstDrag = false; | ||
|  |                 } | ||
|  |             ); | ||
|  |             AddManipulator(scrub); | ||
|  |             lineColor = m_Style.normal.textColor; | ||
|  |             drawLine = true; | ||
|  |             drawHead = true; | ||
|  |             canMoveHead = false; | ||
|  |             tooltip = string.Empty; | ||
|  |             boundOffset = Vector2.zero; | ||
|  |             m_Tooltip = new Tooltip(DirectorStyles.Instance.displayBackground, DirectorStyles.Instance.tinyFont); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Draw(Rect rect, WindowState state, double time) | ||
|  |         { | ||
|  |             var clipRect = new Rect(0.0f, 0.0f, TimelineWindow.instance.position.width, TimelineWindow.instance.position.height); | ||
|  |             clipRect.xMin += state.sequencerHeaderWidth; | ||
|  | 
 | ||
|  |             using (new GUIViewportScope(clipRect)) | ||
|  |             { | ||
|  |                 Vector2 windowCoordinate = rect.min; | ||
|  |                 windowCoordinate.y += 4.0f; | ||
|  | 
 | ||
|  |                 windowCoordinate.x = state.TimeToPixel(time); | ||
|  | 
 | ||
|  |                 m_BoundingRect = new Rect((windowCoordinate.x - widgetWidth / 2.0f), windowCoordinate.y, widgetWidth, widgetHeight); | ||
|  | 
 | ||
|  |                 // Do not paint if the time cursor goes outside the timeline bounds... | ||
|  |                 if (Event.current.type == EventType.Repaint) | ||
|  |                 { | ||
|  |                     if (m_BoundingRect.xMax < state.timeAreaRect.xMin) | ||
|  |                         return; | ||
|  |                     if (m_BoundingRect.xMin > state.timeAreaRect.xMax) | ||
|  |                         return; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 var top = new Vector3(windowCoordinate.x, rect.y - DirectorStyles.kDurationGuiThickness); | ||
|  |                 var bottom = new Vector3(windowCoordinate.x, rect.yMax); | ||
|  | 
 | ||
|  |                 if (drawLine) | ||
|  |                 { | ||
|  |                     Rect lineRect = Rect.MinMaxRect(top.x - 0.5f, top.y, bottom.x + 0.5f, bottom.y); | ||
|  |                     EditorGUI.DrawRect(lineRect, lineColor); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (drawHead && Event.current.type == EventType.Repaint) | ||
|  |                 { | ||
|  |                     Color c = GUI.color; | ||
|  |                     GUI.color = headColor; | ||
|  |                     style.Draw(bounds, m_HeaderContent, false, false, false, false); | ||
|  |                     GUI.color = c; | ||
|  | 
 | ||
|  |                     if (canMoveHead) | ||
|  |                         EditorGUIUtility.AddCursorRect(bounds, MouseCursor.MoveArrow); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (showTooltip) | ||
|  |                 { | ||
|  |                     m_Tooltip.text = TimeReferenceUtility.ToTimeString(time); | ||
|  | 
 | ||
|  |                     Vector2 position = bounds.position; | ||
|  |                     position.y = state.timeAreaRect.y; | ||
|  |                     position.y -= m_Tooltip.bounds.height; | ||
|  |                     position.x -= Mathf.Abs(m_Tooltip.bounds.width - bounds.width) / 2.0f; | ||
|  | 
 | ||
|  |                     Rect tooltipBounds = bounds; | ||
|  |                     tooltipBounds.position = position; | ||
|  |                     m_Tooltip.bounds = tooltipBounds; | ||
|  | 
 | ||
|  |                     m_Tooltip.Draw(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |