1220 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			1220 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using System.Linq; | ||
|  | using UnityEditorInternal; | ||
|  | #if UNITY_2021_2_OR_NEWER | ||
|  | using UnityEditor.SceneManagement; | ||
|  | #else | ||
|  | using UnityEditor.Experimental.SceneManagement; | ||
|  | #endif | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Playables; | ||
|  | using UnityEngine.Timeline; | ||
|  | #if !UNITY_2020_2_OR_NEWER | ||
|  | using UnityEngine.Experimental.Animations; | ||
|  | #endif | ||
|  | using UnityEngine.Animations; | ||
|  | using Object = System.Object; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     delegate bool PendingUpdateDelegate(WindowState state, Event currentEvent); | ||
|  | 
 | ||
|  |     /// <summary> | ||
|  |     /// Interface for faking purposes | ||
|  |     /// </summary> | ||
|  |     interface IWindowState | ||
|  |     { | ||
|  |         ISequenceState masterSequence { get; } | ||
|  |         ISequenceState editSequence { get; } | ||
|  |         IEnumerable<ISequenceState> allSequences { get; } | ||
|  |         PlayRange playRange { get; set; } | ||
|  |         void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip); | ||
|  |         void PopSequencesUntilCount(int count); | ||
|  |         IEnumerable<SequenceContext> GetSubSequences(); | ||
|  |         void SetPlaying(bool start); | ||
|  |     } | ||
|  |     class WindowState : IWindowState | ||
|  |     { | ||
|  |         const int k_TimeCodeTextFieldId = 3790; | ||
|  | 
 | ||
|  |         readonly TimelineWindow m_Window; | ||
|  |         bool m_Recording; | ||
|  | 
 | ||
|  |         readonly SpacePartitioner m_SpacePartitioner = new SpacePartitioner(); | ||
|  |         readonly SpacePartitioner m_HeaderSpacePartitioner = new SpacePartitioner(); | ||
|  |         readonly List<Manipulator> m_CaptureSession = new List<Manipulator>(); | ||
|  | 
 | ||
|  |         int m_DirtyStamp; | ||
|  |         float m_BindingAreaWidth = WindowConstants.defaultBindingAreaWidth; | ||
|  | 
 | ||
|  |         bool m_MustRebuildGraph; | ||
|  | 
 | ||
|  |         float m_LastTime; | ||
|  | 
 | ||
|  |         readonly PropertyCollector m_PropertyCollector = new PropertyCollector(); | ||
|  | 
 | ||
|  |         static AnimationModeDriver s_PreviewDriver; | ||
|  |         PreviewedBindings<Animator> m_PreviewedAnimators; | ||
|  |         List<Component> m_PreviewedComponents; | ||
|  |         IEnumerable<IAnimationWindowPreview> previewedComponents => | ||
|  |             m_PreviewedComponents.Where(component => component != null).Cast<IAnimationWindowPreview>(); | ||
|  | 
 | ||
|  |         public static double kTimeEpsilon { get { return TimeUtility.kTimeEpsilon; } } | ||
|  |         public static readonly float kMaxShownTime = (float)TimeUtility.k_MaxTimelineDurationInSeconds; | ||
|  | 
 | ||
|  |         static readonly ISequenceState k_NullSequenceState = new NullSequenceState(); | ||
|  | 
 | ||
|  |         // which tracks are armed for record - only one allowed per 'actor' | ||
|  |         Dictionary<TrackAsset, TrackAsset> m_ArmedTracks = new Dictionary<TrackAsset, TrackAsset>(); | ||
|  | 
 | ||
|  |         TimelineWindow.TimelineWindowPreferences m_Preferences; | ||
|  | 
 | ||
|  |         List<PendingUpdateDelegate> m_OnStartFrameUpdates; | ||
|  |         List<PendingUpdateDelegate> m_OnEndFrameUpdates; | ||
|  | 
 | ||
|  |         readonly SequenceHierarchy m_SequenceHierarchy; | ||
|  | 
 | ||
|  |         public event Action<WindowState, Event> windowOnGuiStarted; | ||
|  | 
 | ||
|  |         public event Action<bool> OnPlayStateChange; | ||
|  |         public event System.Action OnDirtyStampChange; | ||
|  |         public event System.Action OnRebuildGraphChange; | ||
|  |         public event System.Action OnTimeChange; | ||
|  |         public event System.Action OnRecordingChange; | ||
|  | 
 | ||
|  |         public event System.Action OnBeforeSequenceChange; | ||
|  |         public event System.Action OnAfterSequenceChange; | ||
|  | 
 | ||
|  |         public WindowState(TimelineWindow w, SequenceHierarchy hierarchy) | ||
|  |         { | ||
|  |             m_Window = w; | ||
|  |             m_Preferences = w.preferences; | ||
|  |             hierarchy.Init(this); | ||
|  |             m_SequenceHierarchy = hierarchy; | ||
|  |             TimelinePlayable.muteAudioScrubbing = muteAudioScrubbing; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static AnimationModeDriver previewDriver | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 if (s_PreviewDriver == null) | ||
|  |                 { | ||
|  |                     s_PreviewDriver = ScriptableObject.CreateInstance<AnimationModeDriver>(); | ||
|  |                     AnimationPreviewUtilities.s_PreviewDriver = s_PreviewDriver; | ||
|  |                 } | ||
|  |                 return s_PreviewDriver; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public EditorWindow editorWindow | ||
|  |         { | ||
|  |             get { return m_Window; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public ISequenceState editSequence | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 // Using "null" ISequenceState to avoid checking against null all the time. | ||
|  |                 // This *should* be removed in a phase 2 of refactoring, where we make sure | ||
|  |                 // to pass around the correct state object instead of letting clients dig | ||
|  |                 // into the WindowState for whatever they want. | ||
|  |                 return m_SequenceHierarchy.editSequence ?? k_NullSequenceState; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public ISequenceState masterSequence | ||
|  |         { | ||
|  |             get { return m_SequenceHierarchy.masterSequence ?? k_NullSequenceState; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public ISequenceState referenceSequence | ||
|  |         { | ||
|  |             get { return timeReferenceMode == TimeReferenceMode.Local ? editSequence : masterSequence; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public IEnumerable<ISequenceState> allSequences | ||
|  |         { | ||
|  |             get { return m_SequenceHierarchy.allSequences; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool rebuildGraph | ||
|  |         { | ||
|  |             get { return m_MustRebuildGraph; } | ||
|  |             set { SyncNotifyValue(ref m_MustRebuildGraph, value, OnRebuildGraphChange); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float mouseDragLag { get; set; } | ||
|  | 
 | ||
|  |         public SpacePartitioner spacePartitioner | ||
|  |         { | ||
|  |             get { return m_SpacePartitioner; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public SpacePartitioner headerSpacePartitioner | ||
|  |         { | ||
|  |             get { return m_HeaderSpacePartitioner; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public List<Manipulator> captured | ||
|  |         { | ||
|  |             get { return m_CaptureSession; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void AddCaptured(Manipulator manipulator) | ||
|  |         { | ||
|  |             if (!m_CaptureSession.Contains(manipulator)) | ||
|  |                 m_CaptureSession.Add(manipulator); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void RemoveCaptured(Manipulator manipulator) | ||
|  |         { | ||
|  |             m_CaptureSession.Remove(manipulator); | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool isJogging { get; set; } | ||
|  | 
 | ||
|  |         public int viewStateHash { get; private set; } | ||
|  | 
 | ||
|  |         public float bindingAreaWidth | ||
|  |         { | ||
|  |             get { return m_BindingAreaWidth; } | ||
|  |             set { m_BindingAreaWidth = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float sequencerHeaderWidth | ||
|  |         { | ||
|  |             get { return editSequence.viewModel.sequencerHeaderWidth; } | ||
|  |             set | ||
|  |             { | ||
|  |                 editSequence.viewModel.sequencerHeaderWidth = Mathf.Clamp(value, WindowConstants.minHeaderWidth, WindowConstants.maxHeaderWidth); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float mainAreaWidth { get; set; } | ||
|  | 
 | ||
|  |         public float trackScale | ||
|  |         { | ||
|  |             get { return editSequence.viewModel.trackScale; } | ||
|  |             set | ||
|  |             { | ||
|  |                 editSequence.viewModel.trackScale = value; | ||
|  |                 m_Window.treeView.CalculateRowRects(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public int dirtyStamp | ||
|  |         { | ||
|  |             get { return m_DirtyStamp; } | ||
|  |             private set { SyncNotifyValue(ref m_DirtyStamp, value, OnDirtyStampChange); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool showQuadTree { get; set; } | ||
|  | 
 | ||
|  |         public bool canRecord | ||
|  |         { | ||
|  |             get { return AnimationMode.InAnimationMode(previewDriver) || !AnimationMode.InAnimationMode(); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool recording | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 if (!previewMode) | ||
|  |                     m_Recording = false; | ||
|  |                 return m_Recording; | ||
|  |             } | ||
|  |             // set can only be used to disable recording | ||
|  |             set | ||
|  |             { | ||
|  |                 if (ignorePreview) | ||
|  |                     return; | ||
|  | 
 | ||
|  |                 // force preview mode on | ||
|  |                 if (value) | ||
|  |                     previewMode = true; | ||
|  | 
 | ||
|  |                 bool newValue = value; | ||
|  |                 if (!previewMode) | ||
|  |                     newValue = false; | ||
|  | 
 | ||
|  |                 if (newValue && m_ArmedTracks.Count == 0) | ||
|  |                 { | ||
|  |                     Debug.LogError("Cannot enable recording without an armed track"); | ||
|  |                     newValue = false; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (!newValue) | ||
|  |                     m_ArmedTracks.Clear(); | ||
|  | 
 | ||
|  |                 if (newValue != m_Recording) | ||
|  |                 { | ||
|  |                     if (newValue) | ||
|  |                         AnimationMode.StartAnimationRecording(); | ||
|  |                     else | ||
|  |                         AnimationMode.StopAnimationRecording(); | ||
|  | 
 | ||
|  |                     InspectorWindow.RepaintAllInspectors(); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 SyncNotifyValue(ref m_Recording, newValue, OnRecordingChange); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool previewMode | ||
|  |         { | ||
|  |             get { return ignorePreview || AnimationMode.InAnimationMode(previewDriver); } | ||
|  |             set | ||
|  |             { | ||
|  |                 if (ignorePreview) | ||
|  |                     return; | ||
|  |                 bool inAnimationMode = AnimationMode.InAnimationMode(previewDriver); | ||
|  |                 if (!value) | ||
|  |                 { | ||
|  |                     if (inAnimationMode) | ||
|  |                     { | ||
|  |                         Stop(); | ||
|  | 
 | ||
|  |                         OnStopPreview(); | ||
|  | 
 | ||
|  |                         AnimationMode.StopAnimationMode(previewDriver); | ||
|  | 
 | ||
|  |                         AnimationPropertyContextualMenu.Instance.SetResponder(null); | ||
|  |                         previewedDirectors = null; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else if (!inAnimationMode) | ||
|  |                 { | ||
|  |                     editSequence.time = editSequence.viewModel.windowTime; | ||
|  |                     EvaluateImmediate(); // does appropriate caching prior to enabling | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool playing | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return masterSequence.director != null && masterSequence.director.state == PlayState.Playing; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float playbackSpeed { get; set; } | ||
|  | 
 | ||
|  |         public bool frameSnap | ||
|  |         { | ||
|  |             get { return TimelinePreferences.instance.snapToFrame; } | ||
|  |             set { TimelinePreferences.instance.snapToFrame = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool edgeSnaps | ||
|  |         { | ||
|  |             get { return TimelinePreferences.instance.edgeSnap; } | ||
|  |             set { TimelinePreferences.instance.edgeSnap = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool muteAudioScrubbing | ||
|  |         { | ||
|  |             get { return !TimelinePreferences.instance.audioScrubbing; } | ||
|  |             set | ||
|  |             { | ||
|  |                 TimelinePreferences.instance.audioScrubbing = !value; | ||
|  |                 TimelinePlayable.muteAudioScrubbing = value; | ||
|  |                 RebuildPlayableGraph(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public TimeReferenceMode timeReferenceMode | ||
|  |         { | ||
|  |             get { return m_Preferences.timeReferenceMode; } | ||
|  |             set { m_Preferences.timeReferenceMode = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public TimeFormat timeFormat | ||
|  |         { | ||
|  |             get { return TimelinePreferences.instance.timeFormat; } | ||
|  |             set { TimelinePreferences.instance.timeFormat = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool showAudioWaveform | ||
|  |         { | ||
|  |             get { return TimelinePreferences.instance.showAudioWaveform; } | ||
|  |             set { TimelinePreferences.instance.showAudioWaveform = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public PlayRange playRange | ||
|  |         { | ||
|  |             get { return masterSequence.viewModel.timeAreaPlayRange; } | ||
|  |             set { masterSequence.viewModel.timeAreaPlayRange = ValidatePlayRange(value, masterSequence); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool showMarkerHeader | ||
|  |         { | ||
|  |             get { return editSequence.asset != null && editSequence.asset.markerTrack != null && editSequence.asset.markerTrack.GetShowMarkers(); } | ||
|  |             set { GetWindow().SetShowMarkerHeader(value); } | ||
|  |         } | ||
|  | 
 | ||
|  |         public EditMode.EditType editType | ||
|  |         { | ||
|  |             get { return m_Preferences.editType; } | ||
|  |             set { m_Preferences.editType = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public PlaybackScrollMode autoScrollMode | ||
|  |         { | ||
|  |             get { return TimelinePreferences.instance.playbackScrollMode; } | ||
|  |             set { TimelinePreferences.instance.playbackScrollMode = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public List<PlayableDirector> previewedDirectors { get; private set; } | ||
|  | 
 | ||
|  |         public void OnDestroy(bool clearHierarchy = true) | ||
|  |         { | ||
|  |             if (!ignorePreview) | ||
|  |                 Stop(); | ||
|  | 
 | ||
|  |             if (m_OnStartFrameUpdates != null) | ||
|  |                 m_OnStartFrameUpdates.Clear(); | ||
|  | 
 | ||
|  |             if (m_OnEndFrameUpdates != null) | ||
|  |                 m_OnEndFrameUpdates.Clear(); | ||
|  | 
 | ||
|  |             if (clearHierarchy) | ||
|  |                 m_SequenceHierarchy.Clear(); | ||
|  |             windowOnGuiStarted = null; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void OnSceneSaved() | ||
|  |         { | ||
|  |             // the director will reset it's time when the scene is saved. | ||
|  |             EnsureWindowTimeConsistency(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip) | ||
|  |         { | ||
|  |             if (OnBeforeSequenceChange != null) | ||
|  |                 OnBeforeSequenceChange.Invoke(); | ||
|  | 
 | ||
|  |             OnCurrentDirectorWillChange(); | ||
|  | 
 | ||
|  |             if (hostClip == null || timelineAsset == null) | ||
|  |             { | ||
|  |                 m_PropertyCollector.Clear(); | ||
|  |                 m_SequenceHierarchy.Clear(); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (timelineAsset != null) | ||
|  |                 m_SequenceHierarchy.Add(timelineAsset, director, hostClip); | ||
|  | 
 | ||
|  |             if (OnAfterSequenceChange != null) | ||
|  |                 OnAfterSequenceChange.Invoke(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void PopSequencesUntilCount(int count) | ||
|  |         { | ||
|  |             if (count >= m_SequenceHierarchy.count) return; | ||
|  |             if (count < 1) return; | ||
|  | 
 | ||
|  |             if (OnBeforeSequenceChange != null) | ||
|  |                 OnBeforeSequenceChange.Invoke(); | ||
|  | 
 | ||
|  |             var nextDirector = m_SequenceHierarchy.GetStateAtIndex(count - 1).director; | ||
|  |             OnCurrentDirectorWillChange(); | ||
|  | 
 | ||
|  |             m_SequenceHierarchy.RemoveUntilCount(count); | ||
|  | 
 | ||
|  |             EnsureWindowTimeConsistency(); | ||
|  | 
 | ||
|  |             if (OnAfterSequenceChange != null) | ||
|  |                 OnAfterSequenceChange.Invoke(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public SequencePath GetCurrentSequencePath() | ||
|  |         { | ||
|  |             return m_SequenceHierarchy.ToSequencePath(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void SetCurrentSequencePath(SequencePath path, bool forceRebuild) | ||
|  |         { | ||
|  |             if (!m_SequenceHierarchy.NeedsUpdate(path, forceRebuild)) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (OnBeforeSequenceChange != null) | ||
|  |                 OnBeforeSequenceChange.Invoke(); | ||
|  | 
 | ||
|  |             m_SequenceHierarchy.FromSequencePath(path, forceRebuild); | ||
|  | 
 | ||
|  |             if (OnAfterSequenceChange != null) | ||
|  |                 OnAfterSequenceChange.Invoke(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public IEnumerable<ISequenceState> GetAllSequences() | ||
|  |         { | ||
|  |             return m_SequenceHierarchy.allSequences; | ||
|  |         } | ||
|  | 
 | ||
|  |         public IEnumerable<SequenceContext> GetSubSequences() | ||
|  |         { | ||
|  |             var contexts = | ||
|  |                 editSequence.asset?.flattenedTracks | ||
|  |                     .SelectMany(x => x.clips) | ||
|  |                     .Where((TimelineUtility.HasCustomEditor)) | ||
|  |                     .SelectMany((clip => | ||
|  |                         TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector) | ||
|  |                             .Select(director => new SequenceContext(director, clip)))); | ||
|  | 
 | ||
|  |             return contexts; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Reset() | ||
|  |         { | ||
|  |             recording = false; | ||
|  |             previewMode = false; | ||
|  |         } | ||
|  | 
 | ||
|  |         public double GetSnappedTimeAtMousePosition(Vector2 mousePos) | ||
|  |         { | ||
|  |             return TimeReferenceUtility.SnapToFrameIfRequired(ScreenSpacePixelToTimeAreaTime(mousePos.x)); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void SyncNotifyValue<T>(ref T oldValue, T newValue, System.Action changeStateCallback) | ||
|  |         { | ||
|  |             var stateChanged = false; | ||
|  | 
 | ||
|  |             if (oldValue == null) | ||
|  |             { | ||
|  |                 oldValue = newValue; | ||
|  |                 stateChanged = true; | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 if (!oldValue.Equals(newValue)) | ||
|  |                 { | ||
|  |                     oldValue = newValue; | ||
|  |                     stateChanged = true; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (stateChanged && changeStateCallback != null) | ||
|  |             { | ||
|  |                 changeStateCallback.Invoke(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public TimelineWindowAnalytics analytics = new TimelineWindowAnalytics(); | ||
|  | 
 | ||
|  |         public void SetTimeAreaTransform(Vector2 newTranslation, Vector2 newScale) | ||
|  |         { | ||
|  |             m_Window.timeArea.SetTransform(newTranslation, newScale); | ||
|  |             TimeAreaChanged(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void SetTimeAreaShownRange(float min, float max) | ||
|  |         { | ||
|  |             m_Window.timeArea.SetShownHRange(min, max); | ||
|  |             TimeAreaChanged(); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void TimeAreaChanged() | ||
|  |         { | ||
|  |             if (editSequence.asset != null) | ||
|  |             { | ||
|  |                 editSequence.viewModel.timeAreaShownRange = new Vector2(m_Window.timeArea.shownArea.x, m_Window.timeArea.shownArea.xMax); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void ResetPreviewMode() | ||
|  |         { | ||
|  |             var mode = previewMode; | ||
|  |             previewMode = false; | ||
|  |             previewMode = mode; | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool TimeIsInRange(float value) | ||
|  |         { | ||
|  |             Rect shownArea = m_Window.timeArea.shownArea; | ||
|  |             return value >= shownArea.x && value <= shownArea.xMax; | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool RangeIsVisible(Range range) | ||
|  |         { | ||
|  |             var shownArea = m_Window.timeArea.shownArea; | ||
|  |             return range.start < shownArea.xMax && range.end > shownArea.xMin; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void EnsurePlayHeadIsVisible() | ||
|  |         { | ||
|  |             double minDisplayedTime = PixelToTime(timeAreaRect.xMin); | ||
|  |             double maxDisplayedTime = PixelToTime(timeAreaRect.xMax); | ||
|  | 
 | ||
|  |             double currentTime = editSequence.time; | ||
|  |             if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime); | ||
|  |             float minimumTimeToDisplay = (float)currentTime - displayedTimeRange / 2.0f; | ||
|  |             float maximumTimeToDisplay = (float)currentTime + displayedTimeRange / 2.0f; | ||
|  |             SetTimeAreaShownRange(minimumTimeToDisplay, maximumTimeToDisplay); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void SetPlayHeadToMiddle() | ||
|  |         { | ||
|  |             double minDisplayedTime = PixelToTime(timeAreaRect.xMin); | ||
|  |             double maxDisplayedTime = PixelToTime(timeAreaRect.xMax); | ||
|  | 
 | ||
|  |             double currentTime = editSequence.time; | ||
|  |             float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime); | ||
|  | 
 | ||
|  |             if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime) | ||
|  |             { | ||
|  |                 if (currentTime < minDisplayedTime + displayedTimeRange / 2) | ||
|  |                     return; | ||
|  |             } | ||
|  | 
 | ||
|  |             const float kCatchUpSpeed = 3f; | ||
|  |             float realDelta = Mathf.Clamp(Time.realtimeSinceStartup - m_LastTime, 0f, 1f) * kCatchUpSpeed; | ||
|  |             float scrollCatchupAmount = kCatchUpSpeed * realDelta * displayedTimeRange / 2; | ||
|  | 
 | ||
|  |             if (currentTime < minDisplayedTime) | ||
|  |             { | ||
|  |                 SetTimeAreaShownRange((float)currentTime, (float)currentTime + displayedTimeRange); | ||
|  |             } | ||
|  |             else if (currentTime > maxDisplayedTime) | ||
|  |             { | ||
|  |                 SetTimeAreaShownRange((float)currentTime - displayedTimeRange + scrollCatchupAmount, (float)currentTime + scrollCatchupAmount); | ||
|  |             } | ||
|  |             else if (currentTime > minDisplayedTime + displayedTimeRange / 2) | ||
|  |             { | ||
|  |                 float targetMinDisplayedTime = Mathf.Min((float)minDisplayedTime + scrollCatchupAmount, | ||
|  |                     (float)(currentTime - displayedTimeRange / 2)); | ||
|  |                 SetTimeAreaShownRange(targetMinDisplayedTime, targetMinDisplayedTime + displayedTimeRange); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void UpdateLastFrameTime() | ||
|  |         { | ||
|  |             m_LastTime = Time.realtimeSinceStartup; | ||
|  |         } | ||
|  | 
 | ||
|  |         public Vector2 timeAreaShownRange | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 if (m_Window.state.editSequence.asset != null) | ||
|  |                     return editSequence.viewModel.timeAreaShownRange; | ||
|  | 
 | ||
|  |                 return TimelineAssetViewModel.TimeAreaDefaultRange; | ||
|  |             } | ||
|  |             set | ||
|  |             { | ||
|  |                 SetTimeAreaShownRange(value.x, value.y); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Vector2 timeAreaTranslation | ||
|  |         { | ||
|  |             get { return m_Window.timeArea.translation; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Vector2 timeAreaScale | ||
|  |         { | ||
|  |             get { return m_Window.timeArea.scale; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public Rect timeAreaRect | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 var sequenceContentRect = m_Window.sequenceContentRect; | ||
|  |                 return new Rect( | ||
|  |                     sequenceContentRect.x, | ||
|  |                     WindowConstants.timeAreaYPosition, | ||
|  |                     Mathf.Max(sequenceContentRect.width, WindowConstants.timeAreaMinWidth), | ||
|  |                     WindowConstants.timeAreaHeight | ||
|  |                 ); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public float windowHeight | ||
|  |         { | ||
|  |             get { return m_Window.position.height; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool playRangeEnabled | ||
|  |         { | ||
|  |             get { return !ignorePreview && masterSequence.viewModel.playRangeEnabled && !IsEditingASubTimeline(); } | ||
|  |             set | ||
|  |             { | ||
|  |                 if (!ignorePreview) | ||
|  |                     masterSequence.viewModel.playRangeEnabled = value; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool ignorePreview | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 var shouldIgnorePreview = masterSequence.asset != null && !masterSequence.asset.editorSettings.scenePreview; | ||
|  |                 return Application.isPlaying || shouldIgnorePreview; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         public TimelineWindow GetWindow() | ||
|  |         { | ||
|  |             return m_Window; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Play() | ||
|  |         { | ||
|  |             if (masterSequence.director == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (!previewMode) | ||
|  |                 previewMode = true; | ||
|  | 
 | ||
|  |             if (previewMode) | ||
|  |             { | ||
|  |                 if (masterSequence.time > masterSequence.duration) | ||
|  |                     masterSequence.time = 0; | ||
|  | #if TIMELINE_FRAMEACCURATE | ||
|  |                 if (TimelinePreferences.instance.playbackLockedToFrame) | ||
|  |                 { | ||
|  |                     FrameRate frameRate = FrameRate.DoubleToFrameRate(masterSequence.asset.editorSettings.frameRate); | ||
|  |                     masterSequence.director.Play(frameRate); | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     masterSequence.director.Play(); | ||
|  |                 } | ||
|  | #else | ||
|  |                 masterSequence.director.Play(); | ||
|  | #endif | ||
|  |                 masterSequence.director.ProcessPendingGraphChanges(); | ||
|  |                 PlayableDirector.ResetFrameTiming(); | ||
|  |                 InvokePlayStateChangeCallback(true); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Pause() | ||
|  |         { | ||
|  |             if (masterSequence.director != null) | ||
|  |             { | ||
|  |                 masterSequence.director.Pause(); | ||
|  |                 masterSequence.director.ProcessPendingGraphChanges(); | ||
|  |                 SynchronizeSequencesAfterPlayback(); | ||
|  |                 InvokePlayStateChangeCallback(false); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void SetPlaying(bool start) | ||
|  |         { | ||
|  |             if (start && !playing) | ||
|  |             { | ||
|  |                 Play(); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (!start && playing) | ||
|  |             { | ||
|  |                 Pause(); | ||
|  |             } | ||
|  | 
 | ||
|  |             analytics.SendPlayEvent(start); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Stop() | ||
|  |         { | ||
|  |             if (masterSequence.director != null) | ||
|  |             { | ||
|  |                 masterSequence.director.Stop(); | ||
|  |                 masterSequence.director.ProcessPendingGraphChanges(); | ||
|  |                 InvokePlayStateChangeCallback(false); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void InvokePlayStateChangeCallback(bool isPlaying) | ||
|  |         { | ||
|  |             if (OnPlayStateChange != null) | ||
|  |                 OnPlayStateChange.Invoke(isPlaying); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void RebuildPlayableGraph() | ||
|  |         { | ||
|  |             if (masterSequence.director != null) | ||
|  |             { | ||
|  |                 masterSequence.director.RebuildGraph(); | ||
|  |                 // rebuild both the parent and the edit sequences. control tracks don't necessary | ||
|  |                 // rebuild the subdirector on recreation | ||
|  |                 if (editSequence.director != null && editSequence.director != masterSequence.director) | ||
|  |                 { | ||
|  |                     editSequence.director.RebuildGraph(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Evaluate() | ||
|  |         { | ||
|  |             if (masterSequence.director != null) | ||
|  |             { | ||
|  |                 if (!EditorApplication.isPlaying && !previewMode) | ||
|  |                     GatherProperties(masterSequence.director); | ||
|  | 
 | ||
|  |                 ForceTimeOnDirector(masterSequence.director); | ||
|  |                 masterSequence.director.DeferredEvaluate(); | ||
|  | 
 | ||
|  |                 if (EditorApplication.isPlaying == false) | ||
|  |                 { | ||
|  |                     PlayModeView.RepaintAll(); | ||
|  |                     SceneView.RepaintAll(); | ||
|  |                     AudioMixerWindow.RepaintAudioMixerWindow(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void EvaluateImmediate() | ||
|  |         { | ||
|  |             if (masterSequence.director != null && masterSequence.director.isActiveAndEnabled) | ||
|  |             { | ||
|  |                 if (!EditorApplication.isPlaying && !previewMode) | ||
|  |                     GatherProperties(masterSequence.director); | ||
|  | 
 | ||
|  |                 if (previewMode) | ||
|  |                 { | ||
|  |                     ForceTimeOnDirector(masterSequence.director); | ||
|  |                     masterSequence.director.ProcessPendingGraphChanges(); | ||
|  |                     masterSequence.director.Evaluate(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Refresh() | ||
|  |         { | ||
|  |             CheckRecordingState(); | ||
|  |             dirtyStamp = dirtyStamp + 1; | ||
|  | 
 | ||
|  |             rebuildGraph = true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void UpdateViewStateHash() | ||
|  |         { | ||
|  |             viewStateHash = timeAreaTranslation.GetHashCode() | ||
|  |                 .CombineHash(timeAreaScale.GetHashCode()) | ||
|  |                 .CombineHash(trackScale.GetHashCode()); | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsEditingASubTimeline() | ||
|  |         { | ||
|  |             return editSequence != masterSequence; | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsEditingAnEmptyTimeline() | ||
|  |         { | ||
|  |             return editSequence.asset == null; | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsEditingAPrefabAsset() | ||
|  |         { | ||
|  |             var stage = PrefabStageUtility.GetCurrentPrefabStage(); | ||
|  |             return stage != null && editSequence.director != null && stage.IsPartOfPrefabContents(editSequence.director.gameObject); | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsCurrentEditingASequencerTextField() | ||
|  |         { | ||
|  |             if (editSequence.asset == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (k_TimeCodeTextFieldId == GUIUtility.keyboardControl) | ||
|  |                 return true; | ||
|  | 
 | ||
|  |             return editSequence.asset.flattenedTracks.Count(t => t.GetInstanceID() == GUIUtility.keyboardControl) != 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         public float TimeToTimeAreaPixel(double t) // TimeToTimeAreaPixel | ||
|  |         { | ||
|  |             float pixelX = (float)t; | ||
|  |             pixelX *= timeAreaScale.x; | ||
|  |             pixelX += timeAreaTranslation.x + sequencerHeaderWidth; | ||
|  |             return pixelX; | ||
|  |         } | ||
|  | 
 | ||
|  |         public float TimeToScreenSpacePixel(double time) | ||
|  |         { | ||
|  |             float pixelX = (float)time; | ||
|  |             pixelX *= timeAreaScale.x; | ||
|  |             pixelX += timeAreaTranslation.x; | ||
|  |             return pixelX; | ||
|  |         } | ||
|  | 
 | ||
|  |         public float TimeToPixel(double time) | ||
|  |         { | ||
|  |             return m_Window.timeArea.TimeToPixel((float)time, timeAreaRect); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float PixelToTime(float pixel) | ||
|  |         { | ||
|  |             return m_Window.timeArea.PixelToTime(pixel, timeAreaRect); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float PixelDeltaToDeltaTime(float p) | ||
|  |         { | ||
|  |             return PixelToTime(p) - PixelToTime(0); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float TimeAreaPixelToTime(float pixel) | ||
|  |         { | ||
|  |             return PixelToTime(pixel); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float ScreenSpacePixelToTimeAreaTime(float p) | ||
|  |         { | ||
|  |             // transform into track space by offsetting the pixel by the screen-space offset of the time area | ||
|  |             p -= timeAreaRect.x; | ||
|  |             return TrackSpacePixelToTimeAreaTime(p); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float TrackSpacePixelToTimeAreaTime(float p) | ||
|  |         { | ||
|  |             p -= timeAreaTranslation.x; | ||
|  | 
 | ||
|  |             if (timeAreaScale.x > 0.0f) | ||
|  |                 return p / timeAreaScale.x; | ||
|  | 
 | ||
|  |             return p; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void OffsetTimeArea(int pixels) | ||
|  |         { | ||
|  |             Vector3 tx = timeAreaTranslation; | ||
|  |             tx.x += pixels; | ||
|  |             SetTimeAreaTransform(tx, timeAreaScale); | ||
|  |         } | ||
|  | 
 | ||
|  |         public GameObject GetSceneReference(TrackAsset asset) | ||
|  |         { | ||
|  |             if (editSequence.director == null) | ||
|  |                 return null; // no player bound | ||
|  | 
 | ||
|  |             return TimelineUtility.GetSceneGameObject(editSequence.director, asset); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void CalculateRowRects() | ||
|  |         { | ||
|  |             // arming a track might add inline curve tracks, recalc track heights | ||
|  |             if (m_Window != null && m_Window.treeView != null) | ||
|  |                 m_Window.treeView.CalculateRowRects(); | ||
|  |         } | ||
|  | 
 | ||
|  |         // Only one track within a 'track' hierarchy can be armed | ||
|  |         public void ArmForRecord(TrackAsset track) | ||
|  |         { | ||
|  |             m_ArmedTracks[TimelineUtility.GetSceneReferenceTrack(track)] = track; | ||
|  |             if (track != null && !recording) | ||
|  |                 recording = true; | ||
|  |             if (!recording) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             track.OnRecordingArmed(editSequence.director); | ||
|  |             CalculateRowRects(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void UnarmForRecord(TrackAsset track) | ||
|  |         { | ||
|  |             m_ArmedTracks.Remove(TimelineUtility.GetSceneReferenceTrack(track)); | ||
|  |             if (m_ArmedTracks.Count == 0) | ||
|  |                 recording = false; | ||
|  |             track.OnRecordingUnarmed(editSequence.director); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void UpdateRecordingState() | ||
|  |         { | ||
|  |             if (recording) | ||
|  |             { | ||
|  |                 foreach (var track in m_ArmedTracks.Values) | ||
|  |                 { | ||
|  |                     if (track != null) | ||
|  |                         track.OnRecordingTimeChanged(editSequence.director); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsTrackRecordable(TrackAsset track) | ||
|  |         { | ||
|  |             // A track with animated parameters can always be recorded to | ||
|  |             return IsArmedForRecord(track) || track.HasAnyAnimatableParameters(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsArmedForRecord(TrackAsset track) | ||
|  |         { | ||
|  |             return track == GetArmedTrack(track); | ||
|  |         } | ||
|  | 
 | ||
|  |         public TrackAsset GetArmedTrack(TrackAsset track) | ||
|  |         { | ||
|  |             TrackAsset outTrack; | ||
|  |             m_ArmedTracks.TryGetValue(TimelineUtility.GetSceneReferenceTrack(track), out outTrack); | ||
|  |             return outTrack; | ||
|  |         } | ||
|  | 
 | ||
|  |         void CheckRecordingState() | ||
|  |         { | ||
|  |             // checks for deleted tracks, and makes sure the recording state matches | ||
|  |             if (m_ArmedTracks.Any(t => t.Value == null)) | ||
|  |             { | ||
|  |                 m_ArmedTracks = m_ArmedTracks.Where(t => t.Value != null).ToDictionary(t => t.Key, t => t.Value); | ||
|  |                 if (m_ArmedTracks.Count == 0) | ||
|  |                     recording = false; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnCurrentDirectorWillChange() | ||
|  |         { | ||
|  |             if (ignorePreview) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             SynchronizeViewModelTime(editSequence); | ||
|  |             Stop(); | ||
|  |             rebuildGraph = true; // needed for asset previews | ||
|  |         } | ||
|  | 
 | ||
|  |         public void GatherProperties(PlayableDirector director) | ||
|  |         { | ||
|  |             if (director == null || Application.isPlaying) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             var asset = director.playableAsset as TimelineAsset; | ||
|  |             if (asset != null && !asset.editorSettings.scenePreview) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (!previewMode) | ||
|  |             { | ||
|  |                 AnimationMode.StartAnimationMode(previewDriver); | ||
|  | 
 | ||
|  |                 OnStartPreview(director); | ||
|  | 
 | ||
|  |                 AnimationPropertyContextualMenu.Instance.SetResponder(new TimelineRecordingContextualResponder(this)); | ||
|  |                 if (!previewMode) | ||
|  |                     return; | ||
|  |                 EnsureWindowTimeConsistency(); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (asset != null) | ||
|  |             { | ||
|  |                 m_PropertyCollector.Reset(); | ||
|  |                 m_PropertyCollector.PushActiveGameObject(null); // avoid overflow on unbound tracks | ||
|  |                 asset.GatherProperties(director, m_PropertyCollector); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnStartPreview(PlayableDirector director) | ||
|  |         { | ||
|  |             previewedDirectors = TimelineUtility.GetAllDirectorsInHierarchy(director).ToList(); | ||
|  | 
 | ||
|  |             if (previewedDirectors == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             m_PreviewedAnimators = PreviewedBindings<Animator>.GetPreviewedBindings(previewedDirectors); | ||
|  | 
 | ||
|  |             m_PreviewedComponents = m_PreviewedAnimators.GetUniqueBindings() | ||
|  |                 .SelectMany(animator => animator.GetComponents<IAnimationWindowPreview>() | ||
|  |                     .Cast<Component>()) | ||
|  |                 .ToList(); | ||
|  | 
 | ||
|  |             foreach (var previewedComponent in previewedComponents) | ||
|  |             { | ||
|  |                 previewedComponent.StartPreview(); | ||
|  |             } | ||
|  | #if UNITY_2022_2_OR_NEWER | ||
|  |             PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor; | ||
|  | #endif //UNITY_2022_2_OR_NEWER | ||
|  |         } | ||
|  | 
 | ||
|  |         internal bool AllowRecordingPrefabPropertyOverridesFor(Object componentOrGameObject) | ||
|  |         { | ||
|  |             if (componentOrGameObject == null) | ||
|  |                 throw new ArgumentNullException(nameof(componentOrGameObject)); | ||
|  | 
 | ||
|  |             if (previewMode == false) | ||
|  |                 return true; | ||
|  | 
 | ||
|  |             if (m_ArmedTracks.Count == 0) | ||
|  |                 return true; | ||
|  | 
 | ||
|  |             GameObject inputGameObject = null; | ||
|  |             if (componentOrGameObject is Component component) | ||
|  |             { | ||
|  |                 inputGameObject = component.gameObject; | ||
|  |             } | ||
|  |             else if (componentOrGameObject is GameObject gameObject) | ||
|  |             { | ||
|  |                 inputGameObject = gameObject; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (inputGameObject == null) | ||
|  |                 return true; | ||
|  | 
 | ||
|  |             var armedTracks = m_ArmedTracks.Keys; | ||
|  |             foreach (var track in armedTracks) | ||
|  |             { | ||
|  |                 var animators = m_PreviewedAnimators.GetBindingsForObject(track); | ||
|  |                 foreach (var animator in animators) | ||
|  |                 { | ||
|  |                     if (animator == null) | ||
|  |                         continue; | ||
|  | 
 | ||
|  |                     if (inputGameObject.transform.IsChildOf(animator.transform)) | ||
|  |                         return false; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnStopPreview() | ||
|  |         { | ||
|  |             if (m_PreviewedComponents != null) | ||
|  |             { | ||
|  |                 foreach (var previewComponent in previewedComponents) | ||
|  |                 { | ||
|  |                     previewComponent.StopPreview(); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 m_PreviewedComponents = null; | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var previewAnimator in m_PreviewedAnimators.GetUniqueBindings()) | ||
|  |             { | ||
|  |                 if (previewAnimator != null) | ||
|  |                     previewAnimator.UnbindAllHandles(); | ||
|  |             } | ||
|  |             m_PreviewedAnimators = default; | ||
|  | 
 | ||
|  | #if UNITY_2022_2_OR_NEWER | ||
|  |             PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor; | ||
|  | #endif //UNITY_2022_2_OR_NEWER | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void ProcessStartFramePendingUpdates() | ||
|  |         { | ||
|  |             if (m_OnStartFrameUpdates != null) | ||
|  |                 m_OnStartFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current)); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void ProcessEndFramePendingUpdates() | ||
|  |         { | ||
|  |             if (m_OnEndFrameUpdates != null) | ||
|  |                 m_OnEndFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current)); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void AddStartFrameDelegate(PendingUpdateDelegate updateDelegate) | ||
|  |         { | ||
|  |             if (m_OnStartFrameUpdates == null) | ||
|  |                 m_OnStartFrameUpdates = new List<PendingUpdateDelegate>(); | ||
|  |             if (m_OnStartFrameUpdates.Contains(updateDelegate)) | ||
|  |                 return; | ||
|  |             m_OnStartFrameUpdates.Add(updateDelegate); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void AddEndFrameDelegate(PendingUpdateDelegate updateDelegate) | ||
|  |         { | ||
|  |             if (m_OnEndFrameUpdates == null) | ||
|  |                 m_OnEndFrameUpdates = new List<PendingUpdateDelegate>(); | ||
|  |             if (m_OnEndFrameUpdates.Contains(updateDelegate)) | ||
|  |                 return; | ||
|  |             m_OnEndFrameUpdates.Add(updateDelegate); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void InvokeWindowOnGuiStarted(Event evt) | ||
|  |         { | ||
|  |             if (windowOnGuiStarted != null) | ||
|  |                 windowOnGuiStarted.Invoke(this, evt); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void UpdateRootPlayableDuration(double duration) | ||
|  |         { | ||
|  |             if (editSequence.director != null) | ||
|  |             { | ||
|  |                 if (editSequence.director.playableGraph.IsValid()) | ||
|  |                 { | ||
|  |                     if (editSequence.director.playableGraph.GetRootPlayableCount() > 0) | ||
|  |                     { | ||
|  |                         var rootPlayable = editSequence.director.playableGraph.GetRootPlayable(0); | ||
|  |                         if (rootPlayable.IsValid()) | ||
|  |                             rootPlayable.SetDuration(duration); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void InvokeTimeChangeCallback() | ||
|  |         { | ||
|  |             if (OnTimeChange != null) | ||
|  |                 OnTimeChange.Invoke(); | ||
|  |         } | ||
|  | 
 | ||
|  |         PlayRange ValidatePlayRange(PlayRange range, ISequenceState sequenceState) | ||
|  |         { | ||
|  |             if (range == TimelineAssetViewModel.NoPlayRangeSet) | ||
|  |                 return range; | ||
|  | 
 | ||
|  |             double minimumPlayRangeTime = (0.01 / Math.Max(1.0, sequenceState.frameRate)); | ||
|  | 
 | ||
|  |             // Validate min | ||
|  |             if (range.end - range.start < minimumPlayRangeTime) | ||
|  |                 range.start = range.end - minimumPlayRangeTime; | ||
|  | 
 | ||
|  |             if (range.start < 0.0) | ||
|  |                 range.start = 0.0; | ||
|  | 
 | ||
|  |             // Validate max | ||
|  |             if (range.end > sequenceState.duration) | ||
|  |                 range.end = sequenceState.duration; | ||
|  | 
 | ||
|  |             if (range.end - range.start < minimumPlayRangeTime) | ||
|  |                 range.end = Math.Min(range.start + minimumPlayRangeTime, sequenceState.duration); | ||
|  | 
 | ||
|  |             return range; | ||
|  |         } | ||
|  | 
 | ||
|  |         void EnsureWindowTimeConsistency() | ||
|  |         { | ||
|  |             if (masterSequence.director != null && masterSequence.viewModel != null && !ignorePreview) | ||
|  |                 masterSequence.time = masterSequence.viewModel.windowTime; | ||
|  |         } | ||
|  | 
 | ||
|  |         void SynchronizeSequencesAfterPlayback() | ||
|  |         { | ||
|  |             // Synchronizing editSequence will synchronize all view models up to the master | ||
|  |             SynchronizeViewModelTime(editSequence); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void SynchronizeViewModelTime(ISequenceState state) | ||
|  |         { | ||
|  |             if (state.director == null || state.viewModel == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             var t = state.time; | ||
|  |             state.time = t; | ||
|  |         } | ||
|  | 
 | ||
|  |         // because we may be evaluating outside the duration of the root playable | ||
|  |         //  we explicitly set the time - this causes the graph to not 'advance' the time | ||
|  |         //  because advancing it can force it to change due to wrapping to the duration | ||
|  |         // This can happen if the graph is force evaluated outside it's duration | ||
|  |         // case 910114, 936844 and 943377 | ||
|  |         static void ForceTimeOnDirector(PlayableDirector director) | ||
|  |         { | ||
|  |             var directorTime = director.time; | ||
|  |             director.time = directorTime; | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool IsPlayableGraphDone() | ||
|  |         { | ||
|  |             return masterSequence.director != null | ||
|  |                 && masterSequence.director.playableGraph.IsValid() | ||
|  |                 && masterSequence.director.playableGraph.IsDone(); | ||
|  |         } | ||
|  |     } | ||
|  | } |