512 lines
20 KiB
C#
512 lines
20 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEditor.Experimental.SceneManagement;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Timeline;
|
||
|
|
||
|
namespace UnityEditor.Timeline
|
||
|
{
|
||
|
partial class TimelineWindow
|
||
|
{
|
||
|
struct MarkerOverlay
|
||
|
{
|
||
|
public IMarker marker;
|
||
|
public Rect rect;
|
||
|
public bool isSelected;
|
||
|
public bool isCollapsed;
|
||
|
public MarkerEditor editor;
|
||
|
}
|
||
|
|
||
|
|
||
|
enum TimelineItemArea
|
||
|
{
|
||
|
Header,
|
||
|
Lines
|
||
|
}
|
||
|
|
||
|
[SerializeField] float m_HierarchySplitterPerc = WindowConstants.hierarchySplitterDefaultPercentage;
|
||
|
|
||
|
static internal readonly TimelineMode s_ActiveMode = new TimelineActiveMode();
|
||
|
static internal readonly TimelineMode s_EditAssetMode = new TimelineAssetEditionMode();
|
||
|
static internal readonly TimelineMode s_InactiveMode = new TimelineInactiveMode();
|
||
|
static internal readonly TimelineMode s_DisabledMode = new TimelineDisabledMode();
|
||
|
static internal readonly TimelineMode s_PrefabOutOfContextMode = new TimelineAssetEditionMode();
|
||
|
static internal readonly TimelineMode s_ReadonlyMode = new TimelineReadOnlyMode();
|
||
|
|
||
|
int m_SplitterCaptured;
|
||
|
float m_VerticalScrollBarSize, m_HorizontalScrollBarSize;
|
||
|
|
||
|
List<MarkerOverlay> m_OverlayQueue = new List<MarkerOverlay>(100);
|
||
|
|
||
|
|
||
|
float headerHeight
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return WindowConstants.markerRowYPosition + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Rect markerHeaderRect
|
||
|
{
|
||
|
get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, WindowConstants.markerRowHeight); }
|
||
|
}
|
||
|
|
||
|
public Rect markerContentRect
|
||
|
{
|
||
|
get { return Rect.MinMaxRect(state.sequencerHeaderWidth, WindowConstants.markerRowYPosition, position.width, WindowConstants.markerRowYPosition + WindowConstants.markerRowHeight); }
|
||
|
}
|
||
|
|
||
|
Rect trackRect
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var yMinHeight = headerHeight;
|
||
|
return new Rect(0, yMinHeight, position.width, position.height - yMinHeight - horizontalScrollbarHeight);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Rect sequenceRect
|
||
|
{
|
||
|
get { return new Rect(0.0f, WindowConstants.markerRowYPosition, position.width - WindowConstants.sliderWidth, position.height - WindowConstants.timeAreaYPosition); }
|
||
|
}
|
||
|
|
||
|
public Rect sequenceHeaderRect
|
||
|
{
|
||
|
get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, position.height - WindowConstants.timeAreaYPosition); }
|
||
|
}
|
||
|
|
||
|
public Rect sequenceContentRect
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return new Rect(
|
||
|
state.sequencerHeaderWidth,
|
||
|
WindowConstants.markerRowYPosition,
|
||
|
position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0),
|
||
|
position.height - WindowConstants.markerRowYPosition - horizontalScrollbarHeight);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public float verticalScrollbarWidth
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return m_VerticalScrollBarSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public float horizontalScrollbarHeight
|
||
|
{
|
||
|
get { return m_HorizontalScrollBarSize; }
|
||
|
}
|
||
|
|
||
|
float breadCrumbAreaWidth
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return state.timeAreaRect.width - WindowConstants.selectorWidth - WindowConstants.cogButtonWidth - WindowConstants.cogButtonPadding;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal TimelineMode currentMode
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (state == null || state.editSequence.asset == null)
|
||
|
return s_InactiveMode;
|
||
|
if (state.editSequence.isReadOnly)
|
||
|
return s_ReadonlyMode;
|
||
|
if (state.editSequence.director == null || state.masterSequence.director == null)
|
||
|
return s_EditAssetMode;
|
||
|
|
||
|
if (PrefabUtility.IsPartOfPrefabAsset(state.editSequence.director))
|
||
|
{
|
||
|
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||
|
if (stage == null || !stage.IsPartOfPrefabContents(state.editSequence.director.gameObject))
|
||
|
return s_PrefabOutOfContextMode;
|
||
|
}
|
||
|
|
||
|
if (!state.masterSequence.director.isActiveAndEnabled)
|
||
|
return s_DisabledMode;
|
||
|
|
||
|
return s_ActiveMode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DoLayout()
|
||
|
{
|
||
|
var rawType = Event.current.rawType; // TODO: rawType seems to be broken after calling Use(), use this Hack and remove it once it's fixed.
|
||
|
var mousePosition = Event.current.mousePosition; // mousePosition is also affected by this bug and does not reflect the original position after a Use()
|
||
|
|
||
|
Initialize();
|
||
|
HandleSplitterResize();
|
||
|
|
||
|
var processManipulators = Event.current.type != EventType.Repaint && Event.current.type != EventType.Layout;
|
||
|
|
||
|
if (processManipulators)
|
||
|
{
|
||
|
// Update what's under mouse the cursor
|
||
|
PickerUtils.DoPick(state, mousePosition);
|
||
|
|
||
|
if (state.editSequence.asset != null)
|
||
|
m_PreTreeViewControl.HandleManipulatorsEvents(state);
|
||
|
}
|
||
|
|
||
|
SequencerGUI();
|
||
|
|
||
|
if (processManipulators)
|
||
|
{
|
||
|
if (state.editSequence.asset != null)
|
||
|
m_PostTreeViewControl.HandleManipulatorsEvents(state);
|
||
|
}
|
||
|
|
||
|
m_RectangleSelect.OnGUI(state, rawType, mousePosition);
|
||
|
m_RectangleZoom.OnGUI(state, rawType, mousePosition);
|
||
|
}
|
||
|
|
||
|
void TimelineSectionGUI()
|
||
|
{
|
||
|
GUILayout.BeginVertical();
|
||
|
{
|
||
|
GUILayout.BeginHorizontal(EditorStyles.toolbar, GUILayout.Width(position.width - state.sequencerHeaderWidth));
|
||
|
{
|
||
|
DoSequenceSelectorGUI();
|
||
|
DoBreadcrumbGUI();
|
||
|
OptionsGUI();
|
||
|
}
|
||
|
GUILayout.EndHorizontal();
|
||
|
|
||
|
TimelineGUI();
|
||
|
}
|
||
|
GUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
void SplitterGUI()
|
||
|
{
|
||
|
if (!state.IsEditingAnEmptyTimeline())
|
||
|
{
|
||
|
var splitterLineRect = new Rect(state.sequencerHeaderWidth - 1.0f, 0.0f, 2.0f, clientArea.height);
|
||
|
EditorGUI.DrawRect(splitterLineRect, DirectorStyles.Instance.customSkin.colorTopOutline3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TrackViewsGUI()
|
||
|
{
|
||
|
using (new GUIViewportScope(trackRect))
|
||
|
{
|
||
|
TracksGUI(trackRect, state, currentMode.TrackState(state));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UserOverlaysGUI()
|
||
|
{
|
||
|
if (Event.current.type != EventType.Repaint)
|
||
|
return;
|
||
|
|
||
|
// the rect containing the time area plus the time ruler
|
||
|
var screenRect = new Rect(
|
||
|
state.sequencerHeaderWidth,
|
||
|
WindowConstants.timeAreaYPosition,
|
||
|
position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0),
|
||
|
position.height - WindowConstants.timeAreaYPosition - horizontalScrollbarHeight);
|
||
|
|
||
|
var startTime = state.PixelToTime(screenRect.xMin);
|
||
|
var endTime = state.PixelToTime(screenRect.xMax);
|
||
|
|
||
|
using (new GUIViewportScope(screenRect))
|
||
|
{
|
||
|
foreach (var entry in m_OverlayQueue)
|
||
|
{
|
||
|
var uiState = MarkerUIStates.None;
|
||
|
if (entry.isCollapsed)
|
||
|
uiState |= MarkerUIStates.Collapsed;
|
||
|
if (entry.isSelected)
|
||
|
uiState |= MarkerUIStates.Selected;
|
||
|
var region = new MarkerOverlayRegion(GUIClip.Clip(entry.rect), screenRect, startTime, endTime);
|
||
|
try
|
||
|
{
|
||
|
entry.editor.DrawOverlay(entry.marker, uiState, region);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Debug.LogException(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_OverlayQueue.Clear();
|
||
|
}
|
||
|
|
||
|
void DrawHeaderBackground()
|
||
|
{
|
||
|
var rect = state.timeAreaRect;
|
||
|
rect.xMin = 0.0f;
|
||
|
EditorGUI.DrawRect(rect, DirectorStyles.Instance.customSkin.colorTimelineBackground);
|
||
|
}
|
||
|
|
||
|
void HandleBottomFillerDragAndDrop(Rect rect)
|
||
|
{
|
||
|
if (Event.current.type != EventType.DragUpdated &&
|
||
|
Event.current.type != EventType.DragExited &&
|
||
|
Event.current.type != EventType.DragPerform)
|
||
|
return;
|
||
|
|
||
|
if (instance.treeView == null || instance.treeView.timelineDragging == null)
|
||
|
return;
|
||
|
|
||
|
if (!rect.Contains(Event.current.mousePosition))
|
||
|
return;
|
||
|
|
||
|
instance.treeView.timelineDragging.DragElement(null, new Rect(), -1);
|
||
|
}
|
||
|
|
||
|
void DrawHeaderBackgroundBottomFiller()
|
||
|
{
|
||
|
var rect = sequenceRect;
|
||
|
rect.yMin = rect.yMax;
|
||
|
rect.yMax = rect.yMax + WindowConstants.sliderWidth;
|
||
|
if (state.editSequence.asset != null && !state.IsEditingAnEmptyTimeline())
|
||
|
{
|
||
|
rect.width = state.sequencerHeaderWidth;
|
||
|
}
|
||
|
using (new GUIViewportScope(rect))
|
||
|
{
|
||
|
Graphics.DrawBackgroundRect(state, rect);
|
||
|
}
|
||
|
|
||
|
HandleBottomFillerDragAndDrop(rect);
|
||
|
}
|
||
|
|
||
|
void SequencerGUI()
|
||
|
{
|
||
|
var duration = state.editSequence.duration;
|
||
|
|
||
|
DrawHeaderBackground();
|
||
|
DurationGUI(TimelineItemArea.Header, duration);
|
||
|
|
||
|
GUILayout.BeginHorizontal();
|
||
|
{
|
||
|
SequencerHeaderGUI();
|
||
|
TimelineSectionGUI();
|
||
|
}
|
||
|
GUILayout.EndHorizontal();
|
||
|
|
||
|
TrackViewsGUI();
|
||
|
MarkerHeaderGUI();
|
||
|
UserOverlaysGUI();
|
||
|
|
||
|
DurationGUI(TimelineItemArea.Lines, duration);
|
||
|
PlayRangeGUI(TimelineItemArea.Lines);
|
||
|
TimeCursorGUI(TimelineItemArea.Lines);
|
||
|
DrawHeaderBackgroundBottomFiller();
|
||
|
|
||
|
SubTimelineRangeGUI();
|
||
|
|
||
|
PlayRangeGUI(TimelineItemArea.Header);
|
||
|
TimeCursorGUI(TimelineItemArea.Header);
|
||
|
|
||
|
SplitterGUI();
|
||
|
}
|
||
|
|
||
|
void SubTimelineRangeGUI()
|
||
|
{
|
||
|
if (!state.IsEditingASubTimeline() || state.IsEditingAnEmptyTimeline()) return;
|
||
|
|
||
|
var subTimelineOverlayColor = DirectorStyles.Instance.customSkin.colorSubSequenceOverlay;
|
||
|
|
||
|
var range = state.editSequence.GetEvaluableRange();
|
||
|
var area = new Vector2(state.TimeToPixel(range.start), state.TimeToPixel(range.end));
|
||
|
|
||
|
var fullRect = sequenceContentRect;
|
||
|
fullRect.yMin -= state.timeAreaRect.height;
|
||
|
|
||
|
if (fullRect.xMin < area.x)
|
||
|
{
|
||
|
var before = fullRect;
|
||
|
before.xMin = fullRect.xMin;
|
||
|
before.xMax = Mathf.Min(area.x, fullRect.xMax);
|
||
|
EditorGUI.DrawRect(before, subTimelineOverlayColor);
|
||
|
}
|
||
|
|
||
|
if (fullRect.xMax > area.y)
|
||
|
{
|
||
|
var after = fullRect;
|
||
|
after.xMin = Mathf.Max(area.y, fullRect.xMin);
|
||
|
after.xMax = fullRect.xMax;
|
||
|
EditorGUI.DrawRect(after, subTimelineOverlayColor);
|
||
|
|
||
|
// Space above the vertical scrollbar
|
||
|
after.xMin = after.xMax;
|
||
|
after.width = verticalScrollbarWidth;
|
||
|
after.yMax = state.timeAreaRect.y + state.timeAreaRect.height + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f);
|
||
|
EditorGUI.DrawRect(after, subTimelineOverlayColor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void HandleSplitterResize()
|
||
|
{
|
||
|
state.mainAreaWidth = position.width;
|
||
|
|
||
|
if (state.editSequence.asset == null)
|
||
|
return;
|
||
|
|
||
|
// Sequencer Header Splitter : The splitter has 6 pixels wide,center it around m_State.sequencerHeaderWidth. That's why there's this -3.
|
||
|
Rect sequencerHeaderSplitterRect = new Rect(state.sequencerHeaderWidth - 3.0f, 0.0f, 6.0f, clientArea.height);
|
||
|
EditorGUIUtility.AddCursorRect(sequencerHeaderSplitterRect, MouseCursor.SplitResizeLeftRight);
|
||
|
|
||
|
if (Event.current.type == EventType.MouseDown)
|
||
|
{
|
||
|
if (sequencerHeaderSplitterRect.Contains(Event.current.mousePosition))
|
||
|
m_SplitterCaptured = 1;
|
||
|
}
|
||
|
|
||
|
if (m_SplitterCaptured > 0)
|
||
|
{
|
||
|
if (Event.current.type == EventType.MouseUp)
|
||
|
{
|
||
|
m_SplitterCaptured = 0;
|
||
|
Event.current.Use();
|
||
|
}
|
||
|
|
||
|
if (Event.current.type == EventType.MouseDrag)
|
||
|
{
|
||
|
if (m_SplitterCaptured == 1)
|
||
|
{
|
||
|
var percInc = Event.current.delta.x / position.width;
|
||
|
m_HierarchySplitterPerc = Mathf.Clamp(m_HierarchySplitterPerc + percInc, WindowConstants.minHierarchySplitter, WindowConstants.maxHierarchySplitter);
|
||
|
state.sequencerHeaderWidth += Event.current.delta.x;
|
||
|
}
|
||
|
|
||
|
Event.current.Use();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OptionsGUI()
|
||
|
{
|
||
|
if (currentMode.headerState.options == TimelineModeGUIState.Hidden || state.editSequence.asset == null)
|
||
|
return;
|
||
|
|
||
|
using (new EditorGUI.DisabledScope(currentMode.headerState.options == TimelineModeGUIState.Disabled))
|
||
|
{
|
||
|
GUILayout.FlexibleSpace();
|
||
|
if (EditorGUILayout.DropdownButton(DirectorStyles.optionsCogIcon, FocusType.Keyboard, EditorStyles.toolbarButton))
|
||
|
{
|
||
|
GenericMenu menu = new GenericMenu();
|
||
|
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Seconds"), !state.timeInFrames, ChangeTimeCode, "seconds");
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Frames"), state.timeInFrames, ChangeTimeCode, "frames");
|
||
|
menu.AddSeparator("");
|
||
|
|
||
|
TimeAreaContextMenu.AddTimeAreaMenuItems(menu, state);
|
||
|
|
||
|
menu.AddSeparator("");
|
||
|
|
||
|
bool standardFrameRate = false;
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/Film (24)", 24.0f);
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/PAL (25)", 25.0f);
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/NTSC (29.97)", 29.97f);
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/30", 30.0f);
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/50", 50.0f);
|
||
|
standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/60", 60.0f);
|
||
|
|
||
|
if (standardFrameRate)
|
||
|
menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Frame Rate/Custom"));
|
||
|
else
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Frame Rate/Custom (" + state.editSequence.frameRate + ")"), true, () => {});
|
||
|
|
||
|
menu.AddSeparator("");
|
||
|
if (state.playRangeEnabled)
|
||
|
{
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Play Range Mode/Loop"), state.playRangeLoopMode, () => state.playRangeLoopMode = true);
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Play Range Mode/Once"), !state.playRangeLoopMode, () => state.playRangeLoopMode = false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Play Range Mode"));
|
||
|
}
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/None"), state.autoScrollMode == PlaybackScrollMode.None, () => state.autoScrollMode = PlaybackScrollMode.None);
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/Smooth"), state.autoScrollMode == PlaybackScrollMode.Smooth, () => state.autoScrollMode = PlaybackScrollMode.Smooth);
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/Pan"), state.autoScrollMode == PlaybackScrollMode.Pan, () => state.autoScrollMode = PlaybackScrollMode.Pan);
|
||
|
menu.AddSeparator("");
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Show Audio Waveforms"), state.showAudioWaveform, () =>
|
||
|
{
|
||
|
state.showAudioWaveform = !state.showAudioWaveform;
|
||
|
});
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Enable Audio Scrubbing"), !state.muteAudioScrubbing, () => state.muteAudioScrubbing = !state.muteAudioScrubbing);
|
||
|
|
||
|
menu.AddSeparator("");
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Snap to Frame"), state.frameSnap, () => state.frameSnap = !state.frameSnap);
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Edge Snap"), state.edgeSnaps, () => state.edgeSnaps = !state.edgeSnaps);
|
||
|
|
||
|
if (Unsupported.IsDeveloperMode())
|
||
|
{
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Show Snapping Debug"), SnapEngine.displayDebugLayout,
|
||
|
() => SnapEngine.displayDebugLayout = !SnapEngine.displayDebugLayout);
|
||
|
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Debug TimeArea"), false,
|
||
|
() =>
|
||
|
Debug.LogFormat("translation: {0} scale: {1} rect: {2} shownRange: {3}", m_TimeArea.translation, m_TimeArea.scale, m_TimeArea.rect, m_TimeArea.shownArea));
|
||
|
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Edit Skin"), false, () => Selection.activeObject = DirectorStyles.Instance.customSkin);
|
||
|
|
||
|
menu.AddItem(EditorGUIUtility.TrTextContent("Show QuadTree Debugger"), state.showQuadTree,
|
||
|
() => state.showQuadTree = !state.showQuadTree);
|
||
|
}
|
||
|
|
||
|
menu.ShowAsContext();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool AddStandardFrameRateMenu(GenericMenu menu, string name, float value)
|
||
|
{
|
||
|
bool on = state.editSequence.frameRate.Equals(value);
|
||
|
if (state.editSequence.isReadOnly)
|
||
|
{
|
||
|
menu.AddDisabledItem(EditorGUIUtility.TextContent(name), on);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
menu.AddItem(EditorGUIUtility.TextContent(name), on, r =>
|
||
|
{
|
||
|
state.editSequence.frameRate = value;
|
||
|
}, value);
|
||
|
}
|
||
|
return on;
|
||
|
}
|
||
|
|
||
|
void ChangeTimeCode(object obj)
|
||
|
{
|
||
|
string format = obj.ToString();
|
||
|
if (format == "frames")
|
||
|
{
|
||
|
state.timeInFrames = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
state.timeInFrames = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void AddUserOverlay(IMarker marker, Rect rect, MarkerEditor editor, bool collapsed, bool selected)
|
||
|
{
|
||
|
if (marker == null)
|
||
|
throw new ArgumentNullException("marker");
|
||
|
if (editor == null)
|
||
|
throw new ArgumentNullException("editor");
|
||
|
|
||
|
m_OverlayQueue.Add(new MarkerOverlay()
|
||
|
{
|
||
|
isCollapsed = collapsed,
|
||
|
isSelected = selected,
|
||
|
marker = marker,
|
||
|
rect = rect,
|
||
|
editor = editor
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|