A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor.IMGUI.Controls;
5using UnityEngine;
6using UnityEngine.Timeline;
7using UnityEngine.Playables;
8using Object = UnityEngine.Object;
9
10namespace UnityEditor.Timeline
11{
12 class TimelineTrackGUI : TimelineGroupGUI, IClipCurveEditorOwner, IRowGUI
13 {
14 struct TrackDrawData
15 {
16 public bool m_AllowsRecording;
17 public bool m_ShowTrackBindings;
18 public bool m_HasBinding;
19 public bool m_IsSubTrack;
20 public PlayableBinding m_Binding;
21 public Object m_TrackBinding;
22 public Texture m_TrackIcon;
23 public bool m_HasMarkers;
24 }
25
26 static class Styles
27 {
28 public static readonly GUIContent trackCurvesBtnOnTooltip = DirectorStyles.TrTextContent(string.Empty, "Hide curves view");
29 public static readonly GUIContent trackCurvesBtnOffTooltip = DirectorStyles.TrTextContent(string.Empty, "Show curves view");
30 public static readonly GUIContent trackMarkerBtnOnTooltip = DirectorStyles.TrTextContent(string.Empty, "Collapse Track Markers");
31 public static readonly GUIContent trackMarkerBtnOffTooltip = DirectorStyles.TrTextContent(string.Empty, "Expand Track Markers");
32
33 public static readonly GUIContent kActiveRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "End recording");
34 public static readonly GUIContent kInactiveRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "Start recording");
35 public static readonly GUIContent kIgnorePreviewRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty, "Recording is disabled: scene preview is ignored for this TimelineAsset");
36 public static readonly GUIContent kDisabledRecordButtonTooltip = DirectorStyles.TrTextContent(string.Empty,
37 "Recording is not permitted when Track Offsets are set to Auto. Track Offset settings can be changed in the track menu of the base track.");
38 public static Texture2D kProblemIcon = DirectorStyles.GetBackgroundImage(DirectorStyles.Instance.warning);
39 }
40
41 static GUIContent s_ArmForRecordContentOn;
42 static GUIContent s_ArmForRecordContentOff;
43 static GUIContent s_ArmForRecordDisabled;
44
45 readonly InfiniteTrackDrawer m_InfiniteTrackDrawer;
46 readonly TrackEditor m_TrackEditor;
47 readonly GUIContent m_DefaultTrackIcon;
48 readonly TrackResizeHandle m_ResizeHandle;
49
50 TrackItemsDrawer m_ItemsDrawer;
51 TrackDrawData m_TrackDrawData;
52 TrackDrawOptions m_TrackDrawOptions;
53
54 bool m_InlineCurvesSkipped;
55 int m_TrackHash = -1;
56 int m_BlendHash = -1;
57 int m_LastDirtyIndex = -1;
58
59 bool? m_TrackHasAnimatableParameters;
60 int m_HeightExtension;
61
62 public override bool expandable
63 {
64 get { return hasChildren; }
65 }
66
67 internal InlineCurveEditor inlineCurveEditor { get; set; }
68
69 public ClipCurveEditor clipCurveEditor { get; private set; }
70
71 public bool inlineCurvesSelected => SelectionManager.IsCurveEditorFocused(this);
72
73 bool IClipCurveEditorOwner.showLoops
74 {
75 get { return false; }
76 }
77
78 TrackAsset IClipCurveEditorOwner.owner
79 {
80 get { return track; }
81 }
82
83 static bool DoesTrackAllowsRecording(TrackAsset track)
84 {
85 // if the root animation track is in auto mode, recording is not allowed
86 var animTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
87 if (animTrack != null)
88 return animTrack.trackOffset != TrackOffset.Auto;
89
90 return false;
91 }
92
93 bool trackHasAnimatableParameters
94 {
95 get
96 {
97 // cache this value to avoid the recomputation
98 if (!m_TrackHasAnimatableParameters.HasValue)
99 m_TrackHasAnimatableParameters = track.HasAnyAnimatableParameters() ||
100 track.clips.Any(c => c.HasAnyAnimatableParameters());
101
102 return m_TrackHasAnimatableParameters.Value;
103 }
104 }
105
106 public bool locked
107 {
108 get { return track.lockedInHierarchy; }
109 }
110
111 public bool showMarkers
112 {
113 get { return track.GetShowMarkers(); }
114 }
115
116 public bool muted
117 {
118 get { return track.muted; }
119 }
120
121 public List<TimelineClipGUI> clips
122 {
123 get
124 {
125 return m_ItemsDrawer.clips == null ? new List<TimelineClipGUI>(0) : m_ItemsDrawer.clips;
126 }
127 }
128
129 TrackAsset IRowGUI.asset { get { return track; } }
130
131 bool showTrackRecordingDisabled
132 {
133 get
134 {
135 // if the root animation track is in auto mode, recording is not allowed
136 var animTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
137 return animTrack != null && animTrack.trackOffset == TrackOffset.Auto;
138 }
139 }
140
141 public int heightExtension
142 {
143 get => m_HeightExtension;
144 set => m_HeightExtension = Math.Max(0, value);
145 }
146
147 float minimumHeight => m_TrackDrawOptions.minimumHeight <= 0.0f ? TrackEditor.DefaultTrackHeight : m_TrackDrawOptions.minimumHeight;
148
149 public TimelineTrackGUI(TreeViewController tv, TimelineTreeViewGUI w, int id, int depth, TreeViewItem parent, string displayName, TrackAsset sequenceActor)
150 : base(tv, w, id, depth, parent, displayName, sequenceActor, false)
151 {
152 var animationTrack = sequenceActor as AnimationTrack;
153 if (animationTrack != null)
154 m_InfiniteTrackDrawer = new InfiniteTrackDrawer(new AnimationTrackKeyDataSource(animationTrack));
155 else if (sequenceActor.HasAnyAnimatableParameters() && !sequenceActor.clips.Any())
156 m_InfiniteTrackDrawer = new InfiniteTrackDrawer(new TrackPropertyCurvesDataSource(sequenceActor));
157
158 UpdateInfiniteClipEditor(w.TimelineWindow);
159
160 var bindings = track.outputs.ToArray();
161 m_TrackDrawData.m_HasBinding = bindings.Length > 0;
162 if (m_TrackDrawData.m_HasBinding)
163 m_TrackDrawData.m_Binding = bindings[0];
164 m_TrackDrawData.m_IsSubTrack = IsSubTrack();
165 m_TrackDrawData.m_AllowsRecording = DoesTrackAllowsRecording(sequenceActor);
166 m_TrackDrawData.m_HasMarkers = track.GetMarkerCount() > 0;
167 m_DefaultTrackIcon = TrackResourceCache.GetTrackIcon(track);
168
169 m_TrackEditor = CustomTimelineEditorCache.GetTrackEditor(sequenceActor);
170 m_TrackDrawOptions = m_TrackEditor.GetTrackOptions_Safe(track, null);
171
172 m_TrackDrawOptions.errorText = null; // explicitly setting to null for an uninitialized state
173 m_ResizeHandle = new TrackResizeHandle(this);
174 heightExtension = TimelineWindowViewPrefs.GetTrackHeightExtension(track);
175
176 RebuildGUICacheIfNecessary();
177 }
178
179 public override float GetVerticalSpacingBetweenTracks()
180 {
181 if (track != null && track.isSubTrack)
182 return 1.0f; // subtracks have less of a gap than tracks
183 return base.GetVerticalSpacingBetweenTracks();
184 }
185
186 void UpdateInfiniteClipEditor(TimelineWindow window)
187 {
188 if (clipCurveEditor != null || track == null || !ShouldShowInfiniteClipEditor())
189 return;
190
191 var dataSource = CurveDataSource.Create(this);
192 clipCurveEditor = new ClipCurveEditor(dataSource, window, track);
193 }
194
195 void DetectTrackChanged()
196 {
197 if (Event.current.type == EventType.Layout)
198 {
199 // incremented when a track or it's clips changed
200 if (m_LastDirtyIndex != track.DirtyIndex)
201 {
202 m_TrackEditor.OnTrackChanged_Safe(track);
203 m_LastDirtyIndex = track.DirtyIndex;
204 }
205 OnTrackChanged();
206 }
207 }
208
209 // Called when the source track data, including it's clips have changed has changed.
210 void OnTrackChanged()
211 {
212 // recompute blends if necessary
213 int newBlendHash = BlendHash();
214 if (m_BlendHash != newBlendHash)
215 {
216 UpdateClipOverlaps();
217 m_BlendHash = newBlendHash;
218 }
219
220 RebuildGUICacheIfNecessary();
221 }
222
223 void UpdateDrawData(WindowState state)
224 {
225 if (Event.current.type == EventType.Layout)
226 {
227 m_TrackDrawData.m_ShowTrackBindings = false;
228 m_TrackDrawData.m_TrackBinding = null;
229
230 if (state.editSequence.director != null && showSceneReference)
231 {
232 m_TrackDrawData.m_ShowTrackBindings = state.GetWindow().currentMode.ShouldShowTrackBindings(state);
233 m_TrackDrawData.m_TrackBinding = state.editSequence.director.GetGenericBinding(track);
234 }
235
236 var lastHeight = m_TrackDrawOptions.minimumHeight;
237 m_TrackDrawOptions = m_TrackEditor.GetTrackOptions_Safe(track, m_TrackDrawData.m_TrackBinding);
238
239 m_TrackDrawData.m_HasMarkers = track.GetMarkerCount() > 0;
240 m_TrackDrawData.m_AllowsRecording = DoesTrackAllowsRecording(track);
241 m_TrackDrawData.m_TrackIcon = m_TrackDrawOptions.icon;
242 if (m_TrackDrawData.m_TrackIcon == null)
243 m_TrackDrawData.m_TrackIcon = m_DefaultTrackIcon.image;
244
245 // track height has changed. need to update gui
246 if (!Mathf.Approximately(lastHeight, m_TrackDrawOptions.minimumHeight))
247 state.Refresh();
248 }
249 }
250
251 public override void Draw(Rect headerRect, Rect contentRect, WindowState state)
252 {
253 DetectTrackChanged();
254 UpdateDrawData(state);
255
256 UpdateInfiniteClipEditor(state.GetWindow());
257
258 var trackHeaderRect = headerRect;
259 var trackContentRect = contentRect;
260
261 float inlineCurveHeight = contentRect.height - GetTrackContentHeight(state);
262 bool hasInlineCurve = inlineCurveHeight > 0.0f;
263
264 if (hasInlineCurve)
265 {
266 trackHeaderRect.height -= inlineCurveHeight;
267 trackContentRect.height -= inlineCurveHeight;
268 }
269
270 if (Event.current.type == EventType.Repaint)
271 {
272 m_TreeViewRect = trackContentRect;
273 }
274
275 track.SetCollapsed(!isExpanded);
276
277 RebuildGUICacheIfNecessary();
278
279 // Prevents from drawing outside of bounds, but does not effect layout or markers
280 bool isOwnerDrawSucceed = false;
281
282 Vector2 visibleTime = state.timeAreaShownRange;
283
284 if (drawer != null)
285 isOwnerDrawSucceed = drawer.DrawTrack(trackContentRect, track, visibleTime, state);
286
287 if (!isOwnerDrawSucceed)
288 {
289 using (new GUIViewportScope(trackContentRect))
290 DrawBackground(trackContentRect, track, visibleTime, state);
291
292 if (m_InfiniteTrackDrawer != null)
293 m_InfiniteTrackDrawer.DrawTrack(trackContentRect, track, visibleTime, state);
294
295 // draw after user customization so overlay text shows up
296 using (new GUIViewportScope(trackContentRect))
297 m_ItemsDrawer.Draw(trackContentRect, state);
298 }
299
300 DrawTrackHeader(trackHeaderRect, state);
301
302 if (hasInlineCurve)
303 {
304 var curvesHeaderRect = headerRect;
305 curvesHeaderRect.yMin = trackHeaderRect.yMax;
306
307 var curvesContentRect = contentRect;
308 curvesContentRect.yMin = trackContentRect.yMax;
309
310 DrawInlineCurves(curvesHeaderRect, curvesContentRect, state);
311 }
312
313 DrawTrackColorKind(headerRect);
314 DrawTrackState(contentRect, contentRect, track);
315 }
316
317 void DrawInlineCurves(Rect curvesHeaderRect, Rect curvesContentRect, WindowState state)
318 {
319 if (!track.GetShowInlineCurves())
320 return;
321
322 // Inline curves are not within the editor window -- case 952571
323 if (!IsInlineCurvesEditorInBounds(ToWindowSpace(curvesHeaderRect), curvesContentRect.height, state))
324 {
325 m_InlineCurvesSkipped = true;
326 return;
327 }
328
329 // If inline curves were skipped during the last event; we want to avoid rendering them until
330 // the next Layout event. Otherwise, we still get the RTE prevented above when the user resizes
331 // the timeline window very fast. -- case 952571
332 if (m_InlineCurvesSkipped && Event.current.type != EventType.Layout)
333 return;
334
335 m_InlineCurvesSkipped = false;
336
337 if (inlineCurveEditor == null)
338 inlineCurveEditor = new InlineCurveEditor(this);
339
340
341 curvesHeaderRect.x += DirectorStyles.kBaseIndent;
342 curvesHeaderRect.width -= DirectorStyles.kBaseIndent;
343
344 inlineCurveEditor.Draw(curvesHeaderRect, curvesContentRect, state);
345 }
346
347 static bool IsInlineCurvesEditorInBounds(Rect windowSpaceTrackRect, float inlineCurveHeight, WindowState state)
348 {
349 var legalHeight = state.windowHeight;
350 var trackTop = windowSpaceTrackRect.y;
351 var inlineCurveOffset = windowSpaceTrackRect.height - inlineCurveHeight;
352 return legalHeight - trackTop - inlineCurveOffset > 0;
353 }
354
355 void DrawErrorIcon(Rect position, WindowState state)
356 {
357 Rect bindingLabel = position;
358 bindingLabel.x = position.xMax + 3;
359 bindingLabel.width = state.bindingAreaWidth;
360 EditorGUI.LabelField(position, m_ProblemIcon);
361 }
362
363 void DrawBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
364 {
365 bool canDrawRecordBackground = IsRecording(state);
366 if (canDrawRecordBackground)
367 {
368 DrawRecordingTrackBackground(trackRect, trackAsset, visibleTime, state);
369 }
370 else
371 {
372 Color trackBackgroundColor;
373
374 if (SelectionManager.Contains(track))
375 {
376 trackBackgroundColor = state.IsEditingASubTimeline() ?
377 DirectorStyles.Instance.customSkin.colorTrackSubSequenceBackgroundSelected :
378 DirectorStyles.Instance.customSkin.colorTrackBackgroundSelected;
379 }
380 else
381 {
382 trackBackgroundColor = state.IsEditingASubTimeline() ?
383 DirectorStyles.Instance.customSkin.colorTrackSubSequenceBackground :
384 DirectorStyles.Instance.customSkin.colorTrackBackground;
385 }
386
387 EditorGUI.DrawRect(trackRect, trackBackgroundColor);
388 }
389 }
390
391 float InlineCurveHeight()
392 {
393 return track.GetShowInlineCurves() && CanDrawInlineCurve()
394 ? TimelineWindowViewPrefs.GetInlineCurveHeight(track)
395 : 0.0f;
396 }
397
398 public override float GetHeight(WindowState state)
399 {
400 var height = GetTrackContentHeight(state);
401
402 if (CanDrawInlineCurve())
403 height += InlineCurveHeight();
404
405 return height;
406 }
407
408 float GetTrackContentHeight(WindowState state)
409 {
410 var defaultHeight = Mathf.Min(minimumHeight, TrackEditor.MaximumTrackHeight);
411 return (defaultHeight + heightExtension) * state.trackScale;
412 }
413
414 static bool CanDrawIcon(GUIContent icon)
415 {
416 return icon != null && icon != GUIContent.none && icon.image != null;
417 }
418
419 bool showSceneReference
420 {
421 get
422 {
423 return track != null &&
424 m_TrackDrawData.m_HasBinding &&
425 !m_TrackDrawData.m_IsSubTrack &&
426 m_TrackDrawData.m_Binding.sourceObject != null &&
427 m_TrackDrawData.m_Binding.outputTargetType != null &&
428 typeof(Object).IsAssignableFrom(m_TrackDrawData.m_Binding.outputTargetType);
429 }
430 }
431
432 void DrawTrackHeader(Rect trackHeaderRect, WindowState state)
433 {
434 using (new GUIViewportScope(trackHeaderRect))
435 {
436 var rect = trackHeaderRect;
437
438 DrawHeaderBackground(trackHeaderRect);
439 rect.x += m_Styles.trackSwatchStyle.fixedWidth;
440
441 const float buttonSize = WindowConstants.trackHeaderButtonSize;
442 const float padding = WindowConstants.trackHeaderButtonPadding;
443 var buttonRect = new Rect(trackHeaderRect.xMax - buttonSize - padding, rect.y + (rect.height - buttonSize) / 2f, buttonSize, buttonSize);
444
445 rect.x += DrawTrackIconKind(rect, state);
446
447 if (track is GroupTrack)
448 return;
449
450 buttonRect.x -= DrawTrackDropDownMenu(buttonRect);
451 var suiteRect = DrawGeneralSuite(state, buttonRect);
452 suiteRect = DrawCustomSuite(state, suiteRect);
453
454 var bindingRect = new Rect(rect.x, rect.y, suiteRect.xMax - rect.x, rect.height);
455 DrawTrackBinding(bindingRect, trackHeaderRect);
456 }
457
458 m_ResizeHandle.Draw(trackHeaderRect, state);
459 }
460
461 Rect DrawGeneralSuite(WindowState state, Rect rect)
462 {
463 const float buttonWidth = WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
464 var padding = DrawButtonSuite(3, ref rect);
465
466 DrawMuteButton(rect, state);
467 rect.x -= buttonWidth;
468 DrawLockButton(rect, state);
469 rect.x -= buttonWidth;
470 DrawLockMarkersButton(rect, state);
471 rect.x -= buttonWidth;
472
473 rect.x -= padding;
474 return rect;
475 }
476
477 Rect DrawCustomSuite(WindowState state, Rect rect)
478 {
479 var numberOfButtons = 0;
480 if (m_TrackDrawData.m_AllowsRecording || showTrackRecordingDisabled)
481 numberOfButtons++;
482 if (CanDrawInlineCurve())
483 numberOfButtons++;
484 if (drawer.HasCustomTrackHeaderButton())
485 numberOfButtons++;
486 if (numberOfButtons == 0)
487 return rect;
488
489 var padding = DrawButtonSuite(numberOfButtons, ref rect);
490
491 rect.x -= DrawRecordButton(rect, state);
492 rect.x -= DrawInlineCurveButton(rect, state);
493 rect.x -= DrawCustomTrackButton(rect, state);
494 rect.x -= padding;
495 return rect;
496 }
497
498 void DrawHeaderBackground(Rect headerRect)
499 {
500 Color backgroundColor = SelectionManager.Contains(track)
501 ? DirectorStyles.Instance.customSkin.colorSelection
502 : DirectorStyles.Instance.customSkin.colorTrackHeaderBackground;
503
504 var bgRect = headerRect;
505 bgRect.x += m_Styles.trackSwatchStyle.fixedWidth;
506 bgRect.width -= m_Styles.trackSwatchStyle.fixedWidth;
507
508 EditorGUI.DrawRect(bgRect, backgroundColor);
509 }
510
511 void DrawTrackColorKind(Rect rect)
512 {
513 // subtracks don't draw the color, the parent does that.
514 if (track != null && track.isSubTrack)
515 return;
516
517 if (rect.width <= 0) return;
518
519 using (new GUIColorOverride(m_TrackDrawOptions.trackColor))
520 {
521 rect.width = m_Styles.trackSwatchStyle.fixedWidth;
522 GUI.Label(rect, GUIContent.none, m_Styles.trackSwatchStyle);
523 }
524 }
525
526 float DrawTrackIconKind(Rect rect, WindowState state)
527 {
528 // no icons on subtracks
529 if (track != null && track.isSubTrack)
530 return 0.0f;
531
532 rect.yMin += (rect.height - 16f) / 2f;
533 rect.width = 16.0f;
534 rect.height = 16.0f;
535
536 if (!string.IsNullOrEmpty(m_TrackDrawOptions.errorText))
537 {
538 m_ProblemIcon.image = Styles.kProblemIcon;
539 m_ProblemIcon.tooltip = m_TrackDrawOptions.errorText;
540
541 if (CanDrawIcon(m_ProblemIcon))
542 DrawErrorIcon(rect, state);
543 }
544 else
545 {
546 var content = GUIContent.Temp(m_TrackDrawData.m_TrackIcon, m_DefaultTrackIcon.tooltip);
547 if (CanDrawIcon(content))
548 GUI.Box(rect, content, GUIStyle.none);
549 }
550
551 return rect.width;
552 }
553
554 void DrawTrackBinding(Rect rect, Rect headerRect)
555 {
556 if (m_TrackDrawData.m_ShowTrackBindings)
557 {
558 DoTrackBindingGUI(rect);
559 return;
560 }
561
562 var textStyle = m_Styles.trackHeaderFont;
563 textStyle.normal.textColor = SelectionManager.Contains(track) ? Color.white : m_Styles.customSkin.colorTrackFont;
564
565 string trackName = track.name;
566
567 EditorGUI.BeginChangeCheck();
568
569 // by default the size is just the width of the string (for selection purposes)
570 rect.width = m_Styles.trackHeaderFont.CalcSize(new GUIContent(trackName)).x;
571
572 // if we are editing, supply the entire width of the header
573 if (GUIUtility.keyboardControl == track.GetInstanceID())
574 rect.width = (headerRect.xMax - rect.xMin) - (5 * WindowConstants.trackHeaderButtonSize);
575
576 trackName = EditorGUI.DelayedTextField(rect, GUIContent.none, track.GetInstanceID(), track.name, textStyle);
577
578 if (EditorGUI.EndChangeCheck())
579 {
580 track.SetNameWithUndo(trackName);
581 }
582 }
583
584 float DrawTrackDropDownMenu(Rect rect)
585 {
586 if (GUI.Button(rect, GUIContent.none, m_Styles.trackOptions))
587 {
588 // the drop down will apply to all selected tracks
589 if (!SelectionManager.Contains(track))
590 {
591 SelectionManager.Clear();
592 SelectionManager.Add(track);
593 }
594
595 SequencerContextMenu.ShowTrackContextMenu(null);
596 }
597
598 return WindowConstants.trackHeaderButtonSize;
599 }
600
601 bool CanDrawInlineCurve()
602 {
603 // Note: A track with animatable parameters always has inline curves.
604 return trackHasAnimatableParameters || TimelineUtility.TrackHasAnimationCurves(track);
605 }
606
607 float DrawInlineCurveButton(Rect rect, WindowState state)
608 {
609 if (!CanDrawInlineCurve())
610 {
611 //Force to close Inline Curve UI if the inline cannot be drawn.
612 if (track.GetShowInlineCurves())
613 track.SetShowInlineCurves(false);
614 return 0.0f;
615 }
616
617 // Override enable state to display "Show Inline Curves" button in disabled state.
618 bool prevEnabledState = GUI.enabled;
619 GUI.enabled = true;
620 var showInlineCurves = track.GetShowInlineCurves();
621 var tooltip = showInlineCurves ? Styles.trackCurvesBtnOnTooltip : Styles.trackCurvesBtnOffTooltip;
622 var newValue = GUI.Toggle(rect, track.GetShowInlineCurves(), tooltip, DirectorStyles.Instance.trackCurvesButton);
623 GUI.enabled = prevEnabledState;
624
625 if (newValue != track.GetShowInlineCurves())
626 {
627 track.SetShowInlineCurves(newValue);
628 state.GetWindow().treeView.CalculateRowRects();
629 }
630
631 return WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
632 }
633
634 float DrawRecordButton(Rect rect, WindowState state)
635 {
636 var style = DirectorStyles.Instance.trackRecordButton;
637 const float buttonWidth = WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
638
639 if (m_TrackDrawData.m_AllowsRecording)
640 {
641 bool isPlayerDisabled = state.editSequence.director != null && !state.editSequence.director.isActiveAndEnabled;
642
643 GameObject goBinding = m_TrackDrawData.m_TrackBinding as GameObject;
644 if (goBinding == null)
645 {
646 Component c = m_TrackDrawData.m_TrackBinding as Component;
647 if (c != null)
648 goBinding = c.gameObject;
649 }
650
651 if (goBinding == null && m_TrackDrawData.m_IsSubTrack)
652 goBinding = ParentTrack().GetGameObjectBinding(state.editSequence.director);
653
654 var isTrackBindingValid = goBinding != null;
655 var trackErrorDisableButton = !string.IsNullOrEmpty(m_TrackDrawOptions.errorText) && isTrackBindingValid && goBinding.activeInHierarchy;
656 var disableButton = track.lockedInHierarchy || isPlayerDisabled || trackErrorDisableButton || !isTrackBindingValid || state.ignorePreview;
657 using (new EditorGUI.DisabledScope(disableButton))
658 {
659 if (IsRecording(state))
660 {
661 state.editorWindow.Repaint();
662 var remainder = Time.realtimeSinceStartup % 1;
663
664 if (remainder < 0.22f)
665 style = GUIStyle.none;
666 if (GUI.Button(rect, Styles.kActiveRecordButtonTooltip, style) || isPlayerDisabled || !isTrackBindingValid)
667 state.UnarmForRecord(track);
668 }
669 else if (!track.timelineAsset.editorSettings.scenePreview)
670 GUI.Button(rect, Styles.kIgnorePreviewRecordButtonTooltip, style);
671 else
672 {
673 if (GUI.Button(rect, Styles.kInactiveRecordButtonTooltip, style))
674 state.ArmForRecord(track);
675 }
676 return buttonWidth;
677 }
678 }
679
680 if (showTrackRecordingDisabled)
681 {
682 using (new EditorGUI.DisabledScope(true))
683 GUI.Button(rect, Styles.kDisabledRecordButtonTooltip, style);
684 return buttonWidth;
685 }
686
687 return 0.0f;
688 }
689
690 float DrawCustomTrackButton(Rect rect, WindowState state)
691 {
692 if (!drawer.HasCustomTrackHeaderButton())
693 return 0.0f;
694
695 drawer.DrawTrackHeaderButton(rect, state);
696 return WindowConstants.trackHeaderButtonSize + WindowConstants.trackHeaderButtonPadding;
697 }
698
699 void DrawLockMarkersButton(Rect rect, WindowState state)
700 {
701 var hasMarkers = track.GetMarkerCount() != 0;
702 var markersShown = showMarkers && hasMarkers;
703 var style = TimelineWindow.styles.trackMarkerButton;
704
705 EditorGUI.BeginChangeCheck();
706 var tooltip = markersShown ? Styles.trackMarkerBtnOnTooltip : Styles.trackMarkerBtnOffTooltip;
707 var toggleMarkers = GUI.Toggle(rect, markersShown, tooltip, style);
708 if (EditorGUI.EndChangeCheck() && hasMarkers)
709 track.SetShowTrackMarkers(toggleMarkers);
710 }
711
712 static void ObjectBindingField(Rect position, Object obj, PlayableBinding binding, int controlId)
713 {
714 var allowScene =
715 typeof(GameObject).IsAssignableFrom(binding.outputTargetType) ||
716 typeof(Component).IsAssignableFrom(binding.outputTargetType);
717
718 var bindingFieldRect = EditorGUI.IndentedRect(position);
719 using (new GUIViewportScope(bindingFieldRect))
720 {
721 EditorGUI.BeginChangeCheck();
722 var newObject = UnityEditorInternals.DoObjectField(EditorGUI.IndentedRect(position), obj, binding.outputTargetType, controlId, allowScene, true);
723 if (EditorGUI.EndChangeCheck())
724 BindingUtility.BindWithInteractiveEditorValidation(TimelineEditor.inspectedDirector, binding.sourceObject as TrackAsset, newObject);
725 }
726 }
727
728 void DoTrackBindingGUI(Rect rect)
729 {
730 var bindingRect = new Rect(
731 rect.xMin,
732 rect.y + (rect.height - WindowConstants.trackHeaderBindingHeight) / 2f,
733 Mathf.Min(rect.width, WindowConstants.trackBindingMaxSize) - WindowConstants.trackBindingPadding,
734 WindowConstants.trackHeaderBindingHeight);
735
736 if (m_TrackDrawData.m_Binding.outputTargetType != null && typeof(Object).IsAssignableFrom(m_TrackDrawData.m_Binding.outputTargetType))
737 {
738 var controlId = GUIUtility.GetControlID("s_ObjectFieldHash".GetHashCode(), FocusType.Passive, rect);
739 var previousActiveControlId = DragAndDrop.activeControlID;
740
741 ObjectBindingField(bindingRect, m_TrackDrawData.m_TrackBinding, m_TrackDrawData.m_Binding, controlId);
742 if (previousActiveControlId != controlId && DragAndDrop.activeControlID == controlId)
743 TimelineDragging.OnTrackBindingDragUpdate(track);
744 }
745 }
746
747 bool IsRecording(WindowState state)
748 {
749 return state.recording && state.IsArmedForRecord(track);
750 }
751
752 // background to draw during recording
753 void DrawRecordingTrackBackground(Rect trackRect, TrackAsset trackAsset, Vector2 visibleTime, WindowState state)
754 {
755 if (drawer != null)
756 drawer.DrawRecordingBackground(trackRect, trackAsset, visibleTime, state);
757 }
758
759 void UpdateClipOverlaps()
760 {
761 TrackExtensions.ComputeBlendsFromOverlaps(track.clips);
762 }
763
764 internal void RebuildGUICacheIfNecessary()
765 {
766 if (m_TrackHash == track.Hash())
767 return;
768
769 m_ItemsDrawer = new TrackItemsDrawer(this);
770 m_TrackHash = track.Hash();
771 }
772
773 int BlendHash()
774 {
775 var hash = 0;
776 foreach (var clip in track.clips)
777 {
778 hash = HashUtility.CombineHash(hash,
779 (clip.duration - clip.start).GetHashCode(),
780 ((int)clip.blendInCurveMode).GetHashCode(),
781 ((int)clip.blendOutCurveMode).GetHashCode());
782 }
783 return hash;
784 }
785
786 // callback when the corresponding graph is rebuilt. This can happen, but not have the GUI rebuilt.
787 public override void OnGraphRebuilt()
788 {
789 RefreshCurveEditor();
790 }
791
792 void RefreshCurveEditor()
793 {
794 var window = TimelineWindow.instance;
795 if (track != null && window != null && window.state != null)
796 {
797 bool hasEditor = clipCurveEditor != null;
798 bool shouldHaveEditor = ShouldShowInfiniteClipEditor();
799 if (hasEditor != shouldHaveEditor)
800 window.state.AddEndFrameDelegate((x, currentEvent) =>
801 {
802 x.Refresh();
803 return true;
804 });
805 }
806 }
807
808 bool ShouldShowInfiniteClipEditor()
809 {
810 var animationTrack = track as AnimationTrack;
811 if (animationTrack != null)
812 return animationTrack.ShouldShowInfiniteClipEditor();
813
814 return trackHasAnimatableParameters;
815 }
816
817 public void SelectCurves()
818 {
819 SelectionManager.RemoveTimelineSelection();
820 SelectionManager.SelectInlineCurveEditor(this);
821 }
822
823 public void ValidateCurvesSelection() { }
824 }
825}