470 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			470 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using System.Linq; | ||
|  | 
 | ||
|  | #if UNITY_2021_2_OR_NEWER | ||
|  | using UnityEditor.SceneManagement; | ||
|  | #else | ||
|  | using UnityEditor.Experimental.SceneManagement; | ||
|  | #endif | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Playables; | ||
|  | using UnityEngine.Timeline; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     partial class TimelineWindow | ||
|  |     { | ||
|  |         struct MarkerOverlay | ||
|  |         { | ||
|  |             public IMarker marker; | ||
|  |             public Rect rect; | ||
|  |             public bool isSelected; | ||
|  |             public bool isCollapsed; | ||
|  |             public MarkerEditor editor; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         enum TimelineItemArea | ||
|  |         { | ||
|  |             Header, | ||
|  |             Lines | ||
|  |         } | ||
|  | 
 | ||
|  |         static internal readonly TimelineMode s_ActiveMode = new TimelineActiveMode(); | ||
|  |         static internal readonly TimelineMode s_EditAssetMode = new TimelineAssetEditionMode(); | ||
|  |         static internal readonly TimelineMode s_InactiveMode = new TimelineInactiveMode(); | ||
|  |         static internal readonly TimelineMode s_DisabledMode = new TimelineDisabledMode(); | ||
|  |         static internal readonly TimelineMode s_PrefabOutOfContextMode = new TimelineAssetEditionMode(); | ||
|  |         static internal readonly TimelineMode s_ReadonlyMode = new TimelineReadOnlyMode(); | ||
|  | 
 | ||
|  |         private static readonly GUIContent MenuItemFrames = L10n.TextContent("Frames"); | ||
|  |         private static readonly GUIContent MenuItemTimecode = L10n.TextContent("Timecode"); | ||
|  |         private static readonly GUIContent MenuItemSeconds = L10n.TextContent("Seconds"); | ||
|  | 
 | ||
|  |         static readonly string k_FrameRateMenuLabel = L10n.Tr("Frame Rate/{0}"); | ||
|  |         static readonly string k_CustomFpsLabel = L10n.Tr("{0}: {1:f2} fps"); | ||
|  | 
 | ||
|  |         float m_VerticalScrollBarSize, m_HorizontalScrollBarSize; | ||
|  | 
 | ||
|  |         List<MarkerOverlay> m_OverlayQueue = new List<MarkerOverlay>(100); | ||
|  | 
 | ||
|  |         float headerHeight | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return WindowConstants.markerRowYPosition + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect markerHeaderRect | ||
|  |         { | ||
|  |             get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, WindowConstants.markerRowHeight); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect markerContentRect | ||
|  |         { | ||
|  |             get { return Rect.MinMaxRect(state.sequencerHeaderWidth, WindowConstants.markerRowYPosition, position.width, WindowConstants.markerRowYPosition + WindowConstants.markerRowHeight); } | ||
|  |         } | ||
|  | 
 | ||
|  |         Rect trackRect | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 var yMinHeight = headerHeight; | ||
|  |                 return new Rect(0, yMinHeight, position.width, position.height - yMinHeight - horizontalScrollbarHeight); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect sequenceRect | ||
|  |         { | ||
|  |             get { return new Rect(0.0f, WindowConstants.markerRowYPosition, position.width - WindowConstants.sliderWidth, position.height - WindowConstants.timeAreaYPosition); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect sequenceHeaderRect | ||
|  |         { | ||
|  |             get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, position.height - WindowConstants.timeAreaYPosition); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect headerSplitterRect | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return new Rect(state.sequencerHeaderWidth - WindowConstants.headerSplitterWidth / 2.0f, WindowConstants.timeAreaYPosition, WindowConstants.headerSplitterWidth, clientArea.height); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect sequenceContentRect | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return new Rect( | ||
|  |                     state.sequencerHeaderWidth, | ||
|  |                     WindowConstants.markerRowYPosition, | ||
|  |                     position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0), | ||
|  |                     position.height - WindowConstants.markerRowYPosition - horizontalScrollbarHeight); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float verticalScrollbarWidth | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return m_VerticalScrollBarSize; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float horizontalScrollbarHeight | ||
|  |         { | ||
|  |             get { return m_HorizontalScrollBarSize; } | ||
|  |         } | ||
|  | 
 | ||
|  |         internal TimelineMode currentMode | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 if (state == null || state.editSequence.asset == null) | ||
|  |                     return s_InactiveMode; | ||
|  |                 if (state.editSequence.isReadOnly) | ||
|  |                     return s_ReadonlyMode; | ||
|  |                 if (state.editSequence.director == null || state.masterSequence.director == null) | ||
|  |                     return s_EditAssetMode; | ||
|  | 
 | ||
|  |                 if (PrefabUtility.IsPartOfPrefabAsset(state.editSequence.director)) | ||
|  |                 { | ||
|  |                     var stage = PrefabStageUtility.GetCurrentPrefabStage(); | ||
|  |                     if (stage == null || !stage.IsPartOfPrefabContents(state.editSequence.director.gameObject)) | ||
|  |                         return s_PrefabOutOfContextMode; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (!state.masterSequence.director.isActiveAndEnabled) | ||
|  |                     return s_DisabledMode; | ||
|  | 
 | ||
|  |                 return s_ActiveMode; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void DoLayout() | ||
|  |         { | ||
|  |             EventType rawType = Event.current.rawType; | ||
|  |             var mousePosition = Event.current.mousePosition; // mousePosition is also affected by this bug and does not reflect the original position after a Use() | ||
|  | 
 | ||
|  |             Initialize(); | ||
|  | 
 | ||
|  |             var processManipulators = Event.current.type != EventType.Repaint && Event.current.type != EventType.Layout; | ||
|  | 
 | ||
|  |             if (processManipulators) | ||
|  |             { | ||
|  |                 // Update what's under mouse the cursor | ||
|  |                 PickerUtils.DoPick(state, mousePosition); | ||
|  | 
 | ||
|  |                 if (state.editSequence.asset != null) | ||
|  |                     m_PreTreeViewControl.HandleManipulatorsEvents(state); | ||
|  |             } | ||
|  | 
 | ||
|  |             SequencerGUI(); | ||
|  | 
 | ||
|  |             if (processManipulators) | ||
|  |             { | ||
|  |                 if (state.editSequence.asset != null) | ||
|  |                     m_PostTreeViewControl.HandleManipulatorsEvents(state); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (state.editSequence.asset != null) | ||
|  |             { | ||
|  |                 m_RectangleSelect.OnGUI(state, rawType, mousePosition); | ||
|  |                 m_RectangleZoom.OnGUI(state, rawType, mousePosition); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void SplitterGUI() | ||
|  |         { | ||
|  |             if (!state.IsEditingAnEmptyTimeline()) | ||
|  |             { | ||
|  |                 var splitterVisualRect = new Rect(state.sequencerHeaderWidth - WindowConstants.headerSplitterWidth / 2.0f, | ||
|  |                     WindowConstants.timeAreaYPosition + 2.0f, | ||
|  |                     WindowConstants.headerSplitterVisualWidth, | ||
|  |                     clientArea.height); | ||
|  | 
 | ||
|  |                 EditorGUI.DrawRect(splitterVisualRect, DirectorStyles.Instance.customSkin.colorTopOutline3); | ||
|  | 
 | ||
|  |                 if (GUIUtility.hotControl == 0) | ||
|  |                     EditorGUIUtility.AddCursorRect(headerSplitterRect, MouseCursor.SplitResizeLeftRight); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void TrackViewsGUI() | ||
|  |         { | ||
|  |             using (new GUIViewportScope(trackRect)) | ||
|  |             { | ||
|  |                 TracksGUI(trackRect, state, currentMode.TrackState(state)); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void UserOverlaysGUI() | ||
|  |         { | ||
|  |             if (Event.current.type != EventType.Repaint) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (m_OverlayQueue.Count == 0) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             // the rect containing the time area plus the time ruler | ||
|  |             var screenRect = new Rect( | ||
|  |                 state.sequencerHeaderWidth, | ||
|  |                 WindowConstants.timeAreaYPosition, | ||
|  |                 position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0), | ||
|  |                 position.height - WindowConstants.timeAreaYPosition - horizontalScrollbarHeight); | ||
|  | 
 | ||
|  |             var trackOffset = trackRect.y - screenRect.y; | ||
|  |             var startTime = state.PixelToTime(screenRect.xMin); | ||
|  |             var endTime = state.PixelToTime(screenRect.xMax); | ||
|  | 
 | ||
|  |             using (new GUIViewportScope(screenRect)) | ||
|  |             { | ||
|  |                 foreach (var entry in m_OverlayQueue) | ||
|  |                 { | ||
|  |                     var uiState = MarkerUIStates.None; | ||
|  |                     if (entry.isCollapsed) | ||
|  |                         uiState |= MarkerUIStates.Collapsed; | ||
|  |                     if (entry.isSelected) | ||
|  |                         uiState |= MarkerUIStates.Selected; | ||
|  |                     var region = new MarkerOverlayRegion(GUIClip.Clip(entry.rect), screenRect, startTime, endTime, trackOffset); | ||
|  |                     try | ||
|  |                     { | ||
|  |                         entry.editor.DrawOverlay(entry.marker, uiState, region); | ||
|  |                     } | ||
|  |                     catch (Exception e) | ||
|  |                     { | ||
|  |                         Debug.LogException(e); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             m_OverlayQueue.Clear(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void DrawHeaderBackground() | ||
|  |         { | ||
|  |             var rect = state.timeAreaRect; | ||
|  |             rect.xMin = 0.0f; | ||
|  |             EditorGUI.DrawRect(rect, DirectorStyles.Instance.customSkin.colorTimelineBackground); | ||
|  |         } | ||
|  | 
 | ||
|  |         void HandleBottomFillerDragAndDrop(Rect rect) | ||
|  |         { | ||
|  |             if (Event.current.type != EventType.DragUpdated && | ||
|  |                 Event.current.type != EventType.DragExited && | ||
|  |                 Event.current.type != EventType.DragPerform) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (instance.treeView == null || instance.treeView.timelineDragging == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (!rect.Contains(Event.current.mousePosition)) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             instance.treeView.timelineDragging.DragElement(null, new Rect(), -1); | ||
|  |         } | ||
|  | 
 | ||
|  |         void DrawHeaderBackgroundBottomFiller() | ||
|  |         { | ||
|  |             var rect = sequenceRect; | ||
|  |             rect.yMin = rect.yMax; | ||
|  |             rect.yMax = rect.yMax + WindowConstants.sliderWidth; | ||
|  |             if (state.editSequence.asset != null && !state.IsEditingAnEmptyTimeline()) | ||
|  |             { | ||
|  |                 rect.width = state.sequencerHeaderWidth; | ||
|  |             } | ||
|  |             using (new GUIViewportScope(rect)) | ||
|  |             { | ||
|  |                 Graphics.DrawBackgroundRect(state, rect); | ||
|  |             } | ||
|  | 
 | ||
|  |             HandleBottomFillerDragAndDrop(rect); | ||
|  |         } | ||
|  | 
 | ||
|  |         void SequencerGUI() | ||
|  |         { | ||
|  |             var duration = state.editSequence.duration; | ||
|  | 
 | ||
|  |             DrawHeaderBackground(); | ||
|  |             DurationGUI(TimelineItemArea.Header, duration); | ||
|  | 
 | ||
|  |             DrawToolbar(); | ||
|  | 
 | ||
|  |             TrackViewsGUI(); | ||
|  |             MarkerHeaderGUI(); | ||
|  |             UserOverlaysGUI(); | ||
|  | 
 | ||
|  |             DurationGUI(TimelineItemArea.Lines, duration); | ||
|  |             PlayRangeGUI(TimelineItemArea.Lines); | ||
|  |             TimeCursorGUI(TimelineItemArea.Lines); | ||
|  |             DrawHeaderBackgroundBottomFiller(); | ||
|  | 
 | ||
|  |             SubTimelineRangeGUI(); | ||
|  | 
 | ||
|  |             PlayRangeGUI(TimelineItemArea.Header); | ||
|  |             TimeCursorGUI(TimelineItemArea.Header); | ||
|  | 
 | ||
|  |             SplitterGUI(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void DrawToolbar() | ||
|  |         { | ||
|  |             DrawOptions(); | ||
|  |             using (new GUILayout.VerticalScope()) | ||
|  |             { | ||
|  |                 using (new GUILayout.HorizontalScope(EditorStyles.toolbar)) | ||
|  |                 { | ||
|  |                     using (new GUILayout.HorizontalScope()) | ||
|  |                     { | ||
|  |                         DrawTransportToolbar(); | ||
|  |                     } | ||
|  | 
 | ||
|  |                     DrawSequenceSelector(); | ||
|  |                     DrawBreadcrumbs(); | ||
|  |                     GUILayout.FlexibleSpace(); | ||
|  |                     DrawOptions(); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 using (new GUILayout.HorizontalScope()) | ||
|  |                 { | ||
|  |                     DrawHeaderEditButtons(); | ||
|  |                     DrawTimelineRuler(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void SubTimelineRangeGUI() | ||
|  |         { | ||
|  |             if (!state.IsEditingASubTimeline() || state.IsEditingAnEmptyTimeline()) return; | ||
|  | 
 | ||
|  |             var subTimelineOverlayColor = DirectorStyles.Instance.customSkin.colorSubSequenceOverlay; | ||
|  | 
 | ||
|  |             var range = state.editSequence.GetEvaluableRange(); | ||
|  |             var area = new Vector2(state.TimeToPixel(range.start), state.TimeToPixel(range.end)); | ||
|  | 
 | ||
|  |             var fullRect = sequenceContentRect; | ||
|  |             fullRect.yMin = WindowConstants.timeAreaYPosition + 1.0f; | ||
|  | 
 | ||
|  |             if (fullRect.xMin < area.x) | ||
|  |             { | ||
|  |                 var before = fullRect; | ||
|  |                 before.xMin = fullRect.xMin; | ||
|  |                 before.xMax = Mathf.Min(area.x, fullRect.xMax); | ||
|  |                 EditorGUI.DrawRect(before, subTimelineOverlayColor); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (fullRect.xMax > area.y) | ||
|  |             { | ||
|  |                 var after = fullRect; | ||
|  |                 after.xMin = Mathf.Max(area.y, fullRect.xMin); | ||
|  |                 after.xMax = fullRect.xMax; | ||
|  |                 EditorGUI.DrawRect(after, subTimelineOverlayColor); | ||
|  | 
 | ||
|  |                 // Space above the vertical scrollbar | ||
|  |                 after.xMin = after.xMax; | ||
|  |                 after.width = verticalScrollbarWidth; | ||
|  |                 after.yMax = state.timeAreaRect.y + state.timeAreaRect.height + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f); | ||
|  |                 EditorGUI.DrawRect(after, subTimelineOverlayColor); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void DrawOptions() | ||
|  |         { | ||
|  |             if (currentMode.headerState.options == TimelineModeGUIState.Hidden || state.editSequence.asset == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             using (new EditorGUI.DisabledScope(currentMode.headerState.options == TimelineModeGUIState.Disabled)) | ||
|  |             { | ||
|  |                 var rect = new Rect(position.width - WindowConstants.cogButtonWidth, 0, WindowConstants.cogButtonWidth, WindowConstants.timeAreaYPosition); | ||
|  |                 if (EditorGUI.DropdownButton(rect, DirectorStyles.optionsCogIcon, FocusType.Keyboard, EditorStyles.toolbarButton)) | ||
|  |                 { | ||
|  |                     GenericMenu menu = new GenericMenu(); | ||
|  | 
 | ||
|  |                     menu.AddItem(L10n.TextContent("Preferences Page..."), false, () => SettingsWindow.Show(SettingsScope.User, "Preferences/Timeline")); | ||
|  |                     menu.AddSeparator(""); | ||
|  | 
 | ||
|  |                     menu.AddItem(MenuItemFrames, state.timeFormat == TimeFormat.Frames, () => state.timeFormat = TimeFormat.Frames); | ||
|  |                     menu.AddItem(MenuItemTimecode, state.timeFormat == TimeFormat.Timecode, () => state.timeFormat = TimeFormat.Timecode); | ||
|  |                     menu.AddItem(MenuItemSeconds, state.timeFormat == TimeFormat.Seconds, () => state.timeFormat = TimeFormat.Seconds); | ||
|  | 
 | ||
|  |                     menu.AddSeparator(""); | ||
|  | 
 | ||
|  |                     TimeAreaContextMenu.AddTimeAreaMenuItems(menu, state); | ||
|  | 
 | ||
|  |                     menu.AddSeparator(""); | ||
|  |                     bool isCustom = !FrameRateDisplayUtility.GetStandardFromFrameRate(state.editSequence.frameRate, out StandardFrameRates option); | ||
|  |                     var frameRatesLabels = FrameRateDisplayUtility.GetDefaultFrameRatesLabels(option); | ||
|  |                     if (isCustom) | ||
|  |                         frameRatesLabels[frameRatesLabels.Length - 1] = String.Format(k_CustomFpsLabel, frameRatesLabels.Last(), state.editSequence.frameRate); | ||
|  | 
 | ||
|  |                     for (var i = 0; i < frameRatesLabels.Length; i++) | ||
|  |                     { | ||
|  |                         var currentStandard = (StandardFrameRates)i; | ||
|  |                         AddStandardFrameRateMenu(menu, currentStandard, frameRatesLabels[i], currentStandard == option); | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (Unsupported.IsDeveloperMode()) | ||
|  |                     { | ||
|  |                         menu.AddSeparator(""); | ||
|  |                         menu.AddItem(L10n.TextContent("Show Snapping Debug"), SnapEngine.displayDebugLayout, | ||
|  |                             () => SnapEngine.displayDebugLayout = !SnapEngine.displayDebugLayout); | ||
|  | 
 | ||
|  |                         menu.AddItem(L10n.TextContent("Debug TimeArea"), false, | ||
|  |                             () => | ||
|  |                                 Debug.LogFormat("translation: {0}   scale: {1}   rect: {2}   shownRange: {3}", m_TimeArea.translation, m_TimeArea.scale, m_TimeArea.rect, m_TimeArea.shownArea)); | ||
|  | 
 | ||
|  |                         menu.AddItem(L10n.TextContent("Edit Skin"), false, () => Selection.activeObject = DirectorStyles.Instance.customSkin); | ||
|  | 
 | ||
|  |                         menu.AddItem(L10n.TextContent("Show QuadTree Debugger"), state.showQuadTree, | ||
|  |                             () => state.showQuadTree = !state.showQuadTree); | ||
|  |                     } | ||
|  | 
 | ||
|  |                     menu.DropDown(rect); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void AddStandardFrameRateMenu(GenericMenu menu, StandardFrameRates option, string label, bool on) | ||
|  |         { | ||
|  |             var gui = EditorGUIUtility.TextContent(String.Format(k_FrameRateMenuLabel, label)); | ||
|  |             if (state.editSequence.isReadOnly) | ||
|  |             { | ||
|  |                 menu.AddDisabledItem(gui, on); | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             FrameRate value = TimeUtility.ToFrameRate(option); | ||
|  |             if (!value.IsValid()) | ||
|  |                 menu.AddItem(gui, true, () => { }); | ||
|  |             else | ||
|  |             { | ||
|  |                 menu.AddItem(gui, on, r => | ||
|  |                 { | ||
|  |                     state.editSequence.frameRate = (float)value.rate; | ||
|  |                 }, value); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void AddUserOverlay(IMarker marker, Rect rect, MarkerEditor editor, bool collapsed, bool selected) | ||
|  |         { | ||
|  |             if (marker == null) | ||
|  |                 throw new ArgumentNullException("marker"); | ||
|  |             if (editor == null) | ||
|  |                 throw new ArgumentNullException("editor"); | ||
|  | 
 | ||
|  |             m_OverlayQueue.Add(new MarkerOverlay() | ||
|  |             { | ||
|  |                 isCollapsed = collapsed, | ||
|  |                 isSelected = selected, | ||
|  |                 marker = marker, | ||
|  |                 rect = rect, | ||
|  |                 editor = editor | ||
|  |             } | ||
|  |             ); | ||
|  |         } | ||
|  |     } | ||
|  | } |