561 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			561 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using UnityEditor.Callbacks; | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Events; | ||
|  | using UnityEngine.Playables; | ||
|  | using UnityEngine.SceneManagement; | ||
|  | using UnityEngine.Timeline; | ||
|  | using Object = UnityEngine.Object; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     internal interface IWindowStateProvider | ||
|  |     { | ||
|  |         IWindowState windowState { get; } | ||
|  |     } | ||
|  | 
 | ||
|  |     [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)] | ||
|  |     partial class TimelineWindow : TimelineEditorWindow, IHasCustomMenu, IWindowStateProvider | ||
|  |     { | ||
|  |         [Serializable] | ||
|  |         public class TimelineWindowPreferences | ||
|  |         { | ||
|  |             public EditMode.EditType editType = EditMode.EditType.Mix; | ||
|  |             public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local; | ||
|  |         } | ||
|  | 
 | ||
|  |         [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences(); | ||
|  |         public TimelineWindowPreferences preferences { get { return m_Preferences; } } | ||
|  | 
 | ||
|  |         [SerializeField] | ||
|  |         EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker(); | ||
|  | 
 | ||
|  |         readonly PreviewResizer m_PreviewResizer = new PreviewResizer(); | ||
|  |         bool m_LastFrameHadSequence; | ||
|  |         bool m_ForceRefreshLastSelection; | ||
|  |         int m_CurrentSceneHashCode = -1; | ||
|  | 
 | ||
|  |         [NonSerialized] | ||
|  |         bool m_HasBeenInitialized; | ||
|  | 
 | ||
|  |         [SerializeField] | ||
|  |         SequenceHierarchy m_SequenceHierarchy; | ||
|  |         static SequenceHierarchy s_LastHierarchy; | ||
|  | 
 | ||
|  |         public static TimelineWindow instance { get; private set; } | ||
|  |         public Rect clientArea { get; set; } | ||
|  |         public bool isDragging { get; set; } | ||
|  |         public static DirectorStyles styles { get { return DirectorStyles.Instance; } } | ||
|  |         public List<TimelineTrackBaseGUI> allTracks | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public WindowState state { get; private set; } | ||
|  | 
 | ||
|  |         IWindowState IWindowStateProvider.windowState => state; | ||
|  | 
 | ||
|  |         public override bool locked | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 // we can never be in a locked state if there is no timeline asset | ||
|  |                 if (state.editSequence.asset == null) | ||
|  |                     return false; | ||
|  | 
 | ||
|  |                 return m_LockTracker.isLocked; | ||
|  |             } | ||
|  |             set { m_LockTracker.isLocked = value; } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool hierarchyChangedThisFrame { get; private set; } | ||
|  | 
 | ||
|  |         public TimelineWindow() | ||
|  |         { | ||
|  |             InitializeManipulators(); | ||
|  |             m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnLockStateChanged(bool locked) | ||
|  |         { | ||
|  |             // Make sure that upon unlocking, any selection change is updated | ||
|  |             // Case 1123119 -- only force rebuild if not recording | ||
|  |             if (!locked) | ||
|  |                 RefreshSelection(state != null && !state.recording); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnEnable() | ||
|  |         { | ||
|  |             if (m_SequencePath == null) | ||
|  |                 m_SequencePath = new SequencePath(); | ||
|  | 
 | ||
|  |             if (m_SequenceHierarchy == null) | ||
|  |             { | ||
|  |                 // The sequence hierarchy will become null if maximize on play is used for in/out of playmode | ||
|  |                 // a static var will hang on to the reference | ||
|  |                 if (s_LastHierarchy != null) | ||
|  |                     m_SequenceHierarchy = s_LastHierarchy; | ||
|  |                 else | ||
|  |                     m_SequenceHierarchy = SequenceHierarchy.CreateInstance(); | ||
|  | 
 | ||
|  |                 state = null; | ||
|  |             } | ||
|  |             s_LastHierarchy = m_SequenceHierarchy; | ||
|  | 
 | ||
|  |             titleContent = GetLocalizedTitleContent(); | ||
|  | 
 | ||
|  |             UpdateTitle(); | ||
|  | 
 | ||
|  |             m_PreviewResizer.Init("TimelineWindow"); | ||
|  | 
 | ||
|  |             // Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer. | ||
|  |             if (instance == null) | ||
|  |                 instance = this; | ||
|  | 
 | ||
|  |             AnimationClipCurveCache.Instance.OnEnable(); | ||
|  |             TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; | ||
|  |             TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; | ||
|  | 
 | ||
|  |             if (state == null) | ||
|  |             { | ||
|  |                 state = new WindowState(this, s_LastHierarchy); | ||
|  |                 Initialize(); | ||
|  |                 RefreshSelection(true); | ||
|  |                 m_ForceRefreshLastSelection = true; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnDisable() | ||
|  |         { | ||
|  |             if (instance == this) | ||
|  |                 instance = null; | ||
|  | 
 | ||
|  |             if (state != null) | ||
|  |                 state.Reset(); | ||
|  | 
 | ||
|  |             if (instance == null) | ||
|  |                 SelectionManager.RemoveTimelineSelection(); | ||
|  | 
 | ||
|  |             AnimationClipCurveCache.Instance.OnDisable(); | ||
|  |             TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; | ||
|  |             TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; | ||
|  |             TimelineWindowViewPrefs.SaveAll(); | ||
|  |             TimelineWindowViewPrefs.UnloadAllViewModels(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnDestroy() | ||
|  |         { | ||
|  |             if (state != null) | ||
|  |             { | ||
|  |                 state.OnDestroy(instance == this); | ||
|  |             } | ||
|  |             m_HasBeenInitialized = false; | ||
|  |             RemoveEditorCallbacks(); | ||
|  |             AnimationClipCurveCache.Instance.Clear(); | ||
|  |             TimelineAnimationUtilities.UnlinkAnimationWindow(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnLostFocus() | ||
|  |         { | ||
|  |             isDragging = false; | ||
|  | 
 | ||
|  |             if (state != null) | ||
|  |                 state.captured.Clear(); | ||
|  | 
 | ||
|  |             Repaint(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnHierarchyChange() | ||
|  |         { | ||
|  |             hierarchyChangedThisFrame = true; | ||
|  |             Repaint(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnStateChange() | ||
|  |         { | ||
|  |             state.UpdateRecordingState(); | ||
|  |             state.editSequence.InvalidateChildAssetCache(); | ||
|  |             if (treeView != null && state.editSequence.asset != null) | ||
|  |                 treeView.Reload(); | ||
|  |             if (m_MarkerHeaderGUI != null) | ||
|  |                 m_MarkerHeaderGUI.Rebuild(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnGUI() | ||
|  |         { | ||
|  |             InitializeGUIIfRequired(); | ||
|  |             UpdateGUIConstants(); | ||
|  |             UpdateViewStateHash(); | ||
|  | 
 | ||
|  |             EditMode.HandleModeClutch(); // TODO We Want that here? | ||
|  | 
 | ||
|  |             DetectStylesChange(); | ||
|  |             DetectActiveSceneChanges(); | ||
|  |             DetectStateChanges(); | ||
|  | 
 | ||
|  |             state.ProcessStartFramePendingUpdates(); | ||
|  | 
 | ||
|  |             var clipRect = new Rect(0.0f, 0.0f, position.width, position.height); | ||
|  | 
 | ||
|  |             using (new GUIViewportScope(clipRect)) | ||
|  |                 state.InvokeWindowOnGuiStarted(Event.current); | ||
|  | 
 | ||
|  |             if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f) | ||
|  |             { | ||
|  |                 state.mouseDragLag -= Time.deltaTime; | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (PerformUndo()) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (state != null && state.ignorePreview && state.playing) | ||
|  |             { | ||
|  |                 if (state.recording) | ||
|  |                     state.recording = false; | ||
|  |                 Repaint(); | ||
|  |             } | ||
|  | 
 | ||
|  |             clientArea = position; | ||
|  | 
 | ||
|  |             PlaybackScroller.AutoScroll(state); | ||
|  |             DoLayout(); | ||
|  | 
 | ||
|  |             // overlays | ||
|  |             if (state.captured.Count > 0) | ||
|  |             { | ||
|  |                 using (new GUIViewportScope(clipRect)) | ||
|  |                 { | ||
|  |                     foreach (var o in state.captured) | ||
|  |                     { | ||
|  |                         o.Overlay(Event.current, state); | ||
|  |                     } | ||
|  |                     Repaint(); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (state.showQuadTree) | ||
|  |             { | ||
|  |                 var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f); | ||
|  |                 state.spacePartitioner.DebugDraw(fillColor, Color.yellow); | ||
|  |                 state.headerSpacePartitioner.DebugDraw(fillColor, Color.green); | ||
|  |             } | ||
|  | 
 | ||
|  |             // attempt another rebuild -- this will avoid 1 frame flashes | ||
|  |             if (Event.current.type == EventType.Repaint) | ||
|  |             { | ||
|  |                 RebuildGraphIfNecessary(); | ||
|  |                 state.ProcessEndFramePendingUpdates(); | ||
|  |             } | ||
|  | 
 | ||
|  |             using (new GUIViewportScope(clipRect)) | ||
|  |             { | ||
|  |                 if (Event.current.type == EventType.Repaint) | ||
|  |                     EditMode.inputHandler.OnGUI(state, Event.current); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (Event.current.type == EventType.Repaint) | ||
|  |             { | ||
|  |                 hierarchyChangedThisFrame = false; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (Event.current.type == EventType.Layout) | ||
|  |             { | ||
|  |                 UpdateTitle(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void UpdateTitle() | ||
|  |         { | ||
|  | #if UNITY_2020_2_OR_NEWER | ||
|  |             bool dirty = false; | ||
|  |             List<Object> children = state?.editSequence.cachedChildAssets; | ||
|  |             if (children != null) | ||
|  |             { | ||
|  |                 foreach (var child in children) | ||
|  |                 { | ||
|  |                     dirty = EditorUtility.IsDirty(child); | ||
|  |                     if (dirty) | ||
|  |                     { | ||
|  |                         break; | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             hasUnsavedChanges = dirty; | ||
|  | #endif | ||
|  |         } | ||
|  | 
 | ||
|  |         static void DetectStylesChange() | ||
|  |         { | ||
|  |             DirectorStyles.ReloadStylesIfNeeded(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void DetectActiveSceneChanges() | ||
|  |         { | ||
|  |             if (m_CurrentSceneHashCode == -1) | ||
|  |             { | ||
|  |                 m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode()) | ||
|  |             { | ||
|  |                 bool isSceneStillLoaded = false; | ||
|  |                 for (int a = 0; a < SceneManager.sceneCount; a++) | ||
|  |                 { | ||
|  |                     var scene = SceneManager.GetSceneAt(a); | ||
|  |                     if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded) | ||
|  |                     { | ||
|  |                         isSceneStillLoaded = true; | ||
|  |                         break; | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (!isSceneStillLoaded) | ||
|  |                 { | ||
|  |                     if (!locked) | ||
|  |                         ClearTimeline(); | ||
|  |                     m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void DetectStateChanges() | ||
|  |         { | ||
|  |             if (state != null) | ||
|  |             { | ||
|  |                 foreach (var sequenceState in state.allSequences) | ||
|  |                 { | ||
|  |                     sequenceState.ResetIsReadOnly(); | ||
|  |                 } | ||
|  |                 // detect if the sequence was removed under our feet | ||
|  |                 if (m_LastFrameHadSequence && state.editSequence.asset == null) | ||
|  |                 { | ||
|  |                     ClearTimeline(); | ||
|  |                 } | ||
|  |                 m_LastFrameHadSequence = state.editSequence.asset != null; | ||
|  | 
 | ||
|  |                 // the currentDirector can get set to null by a deletion or scene unloading so polling is required | ||
|  |                 if (state.editSequence.director == null) | ||
|  |                 { | ||
|  |                     state.recording = false; | ||
|  |                     state.previewMode = false; | ||
|  | 
 | ||
|  |                     if (locked) | ||
|  |                     { | ||
|  |                         //revert lock if the original context was not asset mode | ||
|  |                         if (!state.masterSequence.isAssetOnly) | ||
|  |                             locked = false; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (!locked && m_LastFrameHadSequence) | ||
|  |                     { | ||
|  |                         // the user may be adding a new PlayableDirector to a selected GameObject, make sure the timeline editor is shows the proper director if none is already showing | ||
|  |                         var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null; | ||
|  |                         var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null; | ||
|  |                         if (selectedDirector != null) | ||
|  |                         { | ||
|  |                             SetTimeline(selectedDirector); | ||
|  |                         } | ||
|  |                         else | ||
|  |                         { | ||
|  |                             state.masterSequence.isAssetOnly = true; | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     // the user may have changed the timeline associated with the current director | ||
|  |                     if (state.editSequence.asset != state.editSequence.director.playableAsset) | ||
|  |                     { | ||
|  |                         if (!locked) | ||
|  |                         { | ||
|  |                             SetTimeline(state.editSequence.director); | ||
|  |                         } | ||
|  |                         else | ||
|  |                         { | ||
|  |                             // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore | ||
|  |                             SetTimeline(state.editSequence.asset); | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void Initialize() | ||
|  |         { | ||
|  |             if (!m_HasBeenInitialized) | ||
|  |             { | ||
|  |                 InitializeStateChange(); | ||
|  |                 InitializeEditorCallbacks(); | ||
|  |                 m_HasBeenInitialized = true; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void RefreshLastSelectionIfRequired() | ||
|  |         { | ||
|  |             // case 1088918 - workaround for the instanceID to object cache being update during Awake. | ||
|  |             // This corrects any playableDirector ptrs with the correct cached version | ||
|  |             // This can happen when going from edit to playmode | ||
|  |             if (m_ForceRefreshLastSelection) | ||
|  |             { | ||
|  |                 m_ForceRefreshLastSelection = false; | ||
|  |                 RestoreLastSelection(true); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void InitializeGUIIfRequired() | ||
|  |         { | ||
|  |             RefreshLastSelectionIfRequired(); | ||
|  |             InitializeTimeArea(); | ||
|  |             if (treeView == null && state.editSequence.asset != null) | ||
|  |             { | ||
|  |                 treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void UpdateGUIConstants() | ||
|  |         { | ||
|  |             m_HorizontalScrollBarSize = | ||
|  |                 GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; | ||
|  |             m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar) | ||
|  |                 ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left | ||
|  |                 : 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         void UpdateViewStateHash() | ||
|  |         { | ||
|  |             if (Event.current.type == EventType.Layout) | ||
|  |                 state.UpdateViewStateHash(); | ||
|  |         } | ||
|  | 
 | ||
|  |         static bool PerformUndo() | ||
|  |         { | ||
|  |             if (!Event.current.isKey) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (Event.current.keyCode != KeyCode.Z) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (!EditorGUI.actionKey) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void RebuildGraphIfNecessary(bool evaluate = true) | ||
|  |         { | ||
|  |             if (state == null || currentMode.mode != TimelineModes.Active || state.editSequence.director == null || state.editSequence.asset == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (state.rebuildGraph) | ||
|  |             { | ||
|  |                 // rebuilding the graph resets the time | ||
|  |                 double time = state.editSequence.time; | ||
|  | 
 | ||
|  |                 var wasPlaying = false; | ||
|  | 
 | ||
|  |                 // disable preview mode, | ||
|  |                 if (!state.ignorePreview) | ||
|  |                 { | ||
|  |                     wasPlaying = state.playing; | ||
|  | 
 | ||
|  |                     state.previewMode = false; | ||
|  |                     state.GatherProperties(state.masterSequence.director); | ||
|  |                 } | ||
|  |                 state.RebuildPlayableGraph(); | ||
|  |                 state.editSequence.time = time; | ||
|  | 
 | ||
|  |                 if (wasPlaying) | ||
|  |                     state.Play(); | ||
|  | 
 | ||
|  |                 if (evaluate) | ||
|  |                 { | ||
|  |                     // put the scene back in the correct state | ||
|  |                     state.EvaluateImmediate(); | ||
|  | 
 | ||
|  |                     // this is necessary to see accurate results when inspector refreshes | ||
|  |                     // case 1154802 - this will property re-force time on the director, so | ||
|  |                     //  the play head won't snap back to the timeline duration on rebuilds | ||
|  |                     if (!state.playing) | ||
|  |                         state.Evaluate(); | ||
|  |                 } | ||
|  |                 Repaint(); | ||
|  |             } | ||
|  | 
 | ||
|  |             state.rebuildGraph = false; | ||
|  |         } | ||
|  | 
 | ||
|  |         // for tests | ||
|  |         public new void RepaintImmediately() | ||
|  |         { | ||
|  |             base.RepaintImmediately(); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset) | ||
|  |         { | ||
|  |             return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset) | ||
|  |         { | ||
|  |             if (IsEditingTimelineAsset(timelineAsset)) | ||
|  |                 instance.Repaint(); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction | ||
|  |         { | ||
|  |             public override void Action(int instanceId, string pathName, string resourceFile) | ||
|  |             { | ||
|  |                 var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName); | ||
|  |                 ProjectWindowUtil.ShowCreatedAsset(timeline); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [MenuItem("Assets/Create/Timeline/Timeline", false, -124)] | ||
|  |         public static void CreateNewTimeline() | ||
|  |         { | ||
|  |             var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D; | ||
|  |             ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null); | ||
|  |         } | ||
|  | 
 | ||
|  |         [MenuItem("Window/Sequencing/Timeline", false, 1)] | ||
|  |         public static void ShowWindow() | ||
|  |         { | ||
|  |             GetWindow<TimelineWindow>(typeof(SceneView)); | ||
|  |             instance.Focus(); | ||
|  |         } | ||
|  | 
 | ||
|  |         [OnOpenAsset(1)] | ||
|  |         public static bool OnDoubleClick(int instanceID, int line) | ||
|  |         { | ||
|  |             var assetDoubleClicked = SelectionUtility.IdToObject(instanceID) as TimelineAsset; | ||
|  |             if (assetDoubleClicked == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             ShowWindow(); | ||
|  |             instance.SetTimeline(assetDoubleClicked); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public virtual void AddItemsToMenu(GenericMenu menu) | ||
|  |         { | ||
|  |             bool disabled = state == null || state.editSequence.asset == null; | ||
|  | 
 | ||
|  |             m_LockTracker.AddItemsToMenu(menu, disabled); | ||
|  |         } | ||
|  | 
 | ||
|  |         protected virtual void ShowButton(Rect r) | ||
|  |         { | ||
|  |             bool disabled = state == null || state.editSequence.asset == null; | ||
|  | 
 | ||
|  |             m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled); | ||
|  |         } | ||
|  |     } | ||
|  | } |