522 lines
18 KiB
C#
522 lines
18 KiB
C#
|
using System.Collections.Generic;
|
||
|
using System.ComponentModel;
|
||
|
using System.Linq;
|
||
|
using JetBrains.Annotations;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Timeline;
|
||
|
|
||
|
namespace UnityEditor.Timeline
|
||
|
{
|
||
|
[ActiveInMode(TimelineModes.Default)]
|
||
|
abstract class TrackAction : MenuItemActionBase
|
||
|
{
|
||
|
public abstract bool Execute(WindowState state, TrackAsset[] tracks);
|
||
|
|
||
|
protected virtual MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.Length > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||
|
}
|
||
|
|
||
|
protected virtual bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
protected virtual string GetDisplayName(TrackAsset[] tracks)
|
||
|
{
|
||
|
return menuName;
|
||
|
}
|
||
|
|
||
|
public static void Invoke<T>(WindowState state, TrackAsset[] tracks) where T : TrackAction
|
||
|
{
|
||
|
actions.First(x => x.GetType() == typeof(T)).Execute(state, tracks);
|
||
|
}
|
||
|
|
||
|
static List<TrackAction> s_ActionClasses;
|
||
|
|
||
|
static List<TrackAction> actions
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (s_ActionClasses == null)
|
||
|
s_ActionClasses =
|
||
|
GetActionsOfType(typeof(TrackAction))
|
||
|
.Select(x => (TrackAction)x.GetConstructors()[0].Invoke(null))
|
||
|
.OrderBy(x => x.priority).ThenBy(x => x.category)
|
||
|
.ToList();
|
||
|
|
||
|
return s_ActionClasses;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void GetMenuEntries(WindowState state, Vector2? mousePos, TrackAsset[] tracks, List<MenuActionItem> items)
|
||
|
{
|
||
|
var mode = TimelineWindow.instance.currentMode.mode;
|
||
|
foreach (var action in actions)
|
||
|
{
|
||
|
if (!action.showInMenu)
|
||
|
continue;
|
||
|
|
||
|
var actionItem = action;
|
||
|
items.Add(
|
||
|
new MenuActionItem()
|
||
|
{
|
||
|
category = action.category,
|
||
|
entryName = action.GetDisplayName(tracks),
|
||
|
shortCut = action.shortCut,
|
||
|
isChecked = action.IsChecked(state, tracks),
|
||
|
isActiveInMode = IsActionActiveInMode(action, mode),
|
||
|
priority = action.priority,
|
||
|
state = action.GetDisplayState(state, tracks),
|
||
|
callback = () =>
|
||
|
{
|
||
|
actionItem.mousePosition = mousePos;
|
||
|
actionItem.Execute(state, tracks);
|
||
|
actionItem.mousePosition = null;
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static bool HandleShortcut(WindowState state, Event evt, TrackAsset[] tracks)
|
||
|
{
|
||
|
foreach (var action in actions)
|
||
|
{
|
||
|
var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
|
||
|
|
||
|
foreach (ShortcutAttribute shortcut in attr)
|
||
|
{
|
||
|
if (shortcut.MatchesEvent(evt))
|
||
|
{
|
||
|
if (s_ShowActionTriggeredByShortcut)
|
||
|
Debug.Log(action.GetType().Name);
|
||
|
|
||
|
if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
|
||
|
return false;
|
||
|
|
||
|
return action.Execute(state, tracks);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// For testing
|
||
|
internal MenuActionDisplayState InternalGetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
return GetDisplayState(state, tracks);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Edit in Animation Window", MenuOrder.TrackAction.EditInAnimationWindow)]
|
||
|
class EditTrackInAnimationWindow : TrackAction
|
||
|
{
|
||
|
public static bool Do(WindowState state, TrackAsset track)
|
||
|
{
|
||
|
AnimationClip clipToEdit = null;
|
||
|
|
||
|
AnimationTrack animationTrack = track as AnimationTrack;
|
||
|
if (animationTrack != null)
|
||
|
{
|
||
|
if (!animationTrack.CanConvertToClipMode())
|
||
|
return false;
|
||
|
|
||
|
clipToEdit = animationTrack.infiniteClip;
|
||
|
}
|
||
|
else if (track.hasCurves)
|
||
|
{
|
||
|
clipToEdit = track.curves;
|
||
|
}
|
||
|
|
||
|
if (clipToEdit == null)
|
||
|
return false;
|
||
|
|
||
|
var gameObject = state.GetSceneReference(track);
|
||
|
var timeController = TimelineAnimationUtilities.CreateTimeController(state, CreateTimeControlClipData(track));
|
||
|
TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
|
||
|
if (tracks[0] is AnimationTrack)
|
||
|
{
|
||
|
var animTrack = tracks[0] as AnimationTrack;
|
||
|
if (animTrack.CanConvertToClipMode())
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
else if (tracks[0].hasCurves)
|
||
|
{
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
return Do(state, tracks[0]);
|
||
|
}
|
||
|
|
||
|
static TimelineWindowTimeControl.ClipData CreateTimeControlClipData(TrackAsset track)
|
||
|
{
|
||
|
var data = new TimelineWindowTimeControl.ClipData();
|
||
|
data.track = track;
|
||
|
data.start = track.start;
|
||
|
data.duration = track.duration;
|
||
|
return data;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Lock selected track only", MenuOrder.TrackAction.LockSelected)]
|
||
|
class LockSelectedTrack : TrackAction
|
||
|
{
|
||
|
public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only");
|
||
|
public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only");
|
||
|
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack ||
|
||
|
!track.subTracksObjects.Any()))
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (!tracks.Any()) return false;
|
||
|
|
||
|
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||
|
Lock(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
protected override string GetDisplayName(TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText;
|
||
|
}
|
||
|
|
||
|
public static void Lock(WindowState state, TrackAsset[] tracks, bool shouldlock)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return;
|
||
|
|
||
|
foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t)))
|
||
|
{
|
||
|
TimelineUndo.PushUndo(track, "Lock Tracks");
|
||
|
track.locked = shouldlock;
|
||
|
}
|
||
|
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Lock", MenuOrder.TrackAction.LockTrack)]
|
||
|
[Shortcut(Shortcuts.Timeline.toggleLock)]
|
||
|
class LockTrack : TrackAction
|
||
|
{
|
||
|
public static readonly string UnlockText = L10n.Tr("Unlock");
|
||
|
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
bool hasUnlockableTracks = tracks.Any(x => TimelineUtility.IsLockedFromGroup(x));
|
||
|
if (hasUnlockableTracks)
|
||
|
return MenuActionDisplayState.Disabled;
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
protected override string GetDisplayName(TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.Any(x => !x.locked) ? base.GetDisplayName(tracks) : UnlockText;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (!tracks.Any()) return false;
|
||
|
|
||
|
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||
|
SetLockState(tracks, hasUnlockedTracks, state);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public static void SetLockState(TrackAsset[] tracks, bool shouldLock, WindowState state = null)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return;
|
||
|
|
||
|
foreach (var track in tracks)
|
||
|
{
|
||
|
if (TimelineUtility.IsLockedFromGroup(track))
|
||
|
continue;
|
||
|
|
||
|
if (track as GroupTrack == null)
|
||
|
SetLockState(track.GetChildTracks().ToArray(), shouldLock, state);
|
||
|
|
||
|
TimelineUndo.PushUndo(track, "Lock Tracks");
|
||
|
track.locked = shouldLock;
|
||
|
}
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
// find the tracks we've locked. unselect anything locked and remove recording.
|
||
|
foreach (var track in tracks)
|
||
|
{
|
||
|
if (TimelineUtility.IsLockedFromGroup(track) || !track.locked)
|
||
|
continue;
|
||
|
|
||
|
var flattenedChildTracks = track.GetFlattenedChildTracks();
|
||
|
foreach (var i in track.clips)
|
||
|
SelectionManager.Remove(i);
|
||
|
state.UnarmForRecord(track);
|
||
|
foreach (var child in flattenedChildTracks)
|
||
|
{
|
||
|
SelectionManager.Remove(child);
|
||
|
state.UnarmForRecord(child);
|
||
|
foreach (var clip in child.GetClips())
|
||
|
SelectionManager.Remove(clip);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// no need to rebuild, just repaint (including inspectors)
|
||
|
InspectorWindow.RepaintAllInspectors();
|
||
|
state.editorWindow.Repaint();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[UsedImplicitly]
|
||
|
[MenuEntry("Show Markers", MenuOrder.TrackAction.ShowHideMarkers)]
|
||
|
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||
|
class ShowHideMarkers : TrackAction
|
||
|
{
|
||
|
protected override bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.All(x => x.GetShowMarkers());
|
||
|
}
|
||
|
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0))
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
|
||
|
if (tracks.Any(t => t.lockedInHierarchy))
|
||
|
return MenuActionDisplayState.Disabled;
|
||
|
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (!tracks.Any()) return false;
|
||
|
|
||
|
var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers());
|
||
|
ShowHide(state, tracks, hasUnlockedTracks);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void ShowHide(WindowState state, TrackAsset[] tracks, bool shouldLock)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return;
|
||
|
|
||
|
var window = state.GetWindow();
|
||
|
foreach (var track in tracks)
|
||
|
{
|
||
|
window.SetShowTrackMarkers(track, shouldLock);
|
||
|
}
|
||
|
|
||
|
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Mute selected track only", MenuOrder.TrackAction.MuteSelected), UsedImplicitly]
|
||
|
class MuteSelectedTrack : TrackAction
|
||
|
{
|
||
|
public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only");
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack ||
|
||
|
!track.subTracksObjects.Any()))
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (!tracks.Any())
|
||
|
return false;
|
||
|
|
||
|
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||
|
Mute(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
protected override string GetDisplayName(TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.All(t => t.muted) ? UnmuteSelectedText : base.GetDisplayName(tracks);
|
||
|
}
|
||
|
|
||
|
public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return;
|
||
|
|
||
|
foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t)))
|
||
|
{
|
||
|
TimelineUndo.PushUndo(track, "Mute Tracks");
|
||
|
track.muted = shouldMute;
|
||
|
}
|
||
|
|
||
|
state.Refresh();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Mute", MenuOrder.TrackAction.MuteTrack)]
|
||
|
[Shortcut(Shortcuts.Timeline.toggleMute)]
|
||
|
class MuteTrack : TrackAction
|
||
|
{
|
||
|
public static readonly string UnMuteText = L10n.Tr("Unmute");
|
||
|
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any(track => TimelineUtility.IsParentMuted(track)))
|
||
|
return MenuActionDisplayState.Disabled;
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
}
|
||
|
|
||
|
protected override string GetDisplayName(TrackAsset[] tracks)
|
||
|
{
|
||
|
return tracks.Any(x => !x.muted) ? base.GetDisplayName(tracks) : UnMuteText;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (!tracks.Any() || tracks.Any(track => TimelineUtility.IsParentMuted(track)))
|
||
|
return false;
|
||
|
|
||
|
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||
|
Mute(state, tracks, hasUnmutedTracks);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
|
||
|
{
|
||
|
if (tracks.Length == 0)
|
||
|
return;
|
||
|
|
||
|
foreach (var track in tracks)
|
||
|
{
|
||
|
if (track as GroupTrack == null)
|
||
|
Mute(state, track.GetChildTracks().ToArray(), shouldMute);
|
||
|
TimelineUndo.PushUndo(track, "Mute Tracks");
|
||
|
track.muted = shouldMute;
|
||
|
}
|
||
|
|
||
|
state.Refresh();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class DeleteTracks : TrackAction
|
||
|
{
|
||
|
public static void Do(TimelineAsset timeline, TrackAsset track)
|
||
|
{
|
||
|
SelectionManager.Remove(track);
|
||
|
TrackModifier.DeleteTrack(timeline, track);
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
// disable preview mode so deleted tracks revert to default state
|
||
|
// Case 956129: Disable preview mode _before_ deleting the tracks, since clip data is still needed
|
||
|
state.previewMode = false;
|
||
|
|
||
|
TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks);
|
||
|
|
||
|
foreach (var track in tracks)
|
||
|
Do(state.editSequence.asset, track);
|
||
|
|
||
|
state.Refresh();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CopyTracksToClipboard : TrackAction
|
||
|
{
|
||
|
public static bool Do(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
var action = new CopyTracksToClipboard();
|
||
|
|
||
|
return action.Execute(state, tracks);
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
TimelineEditor.clipboard.CopyTracks(tracks);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class DuplicateTracks : TrackAction
|
||
|
{
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any())
|
||
|
{
|
||
|
SelectionManager.RemoveTimelineSelection();
|
||
|
}
|
||
|
|
||
|
foreach (var track in TrackExtensions.FilterTracks(tracks))
|
||
|
{
|
||
|
var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector);
|
||
|
SelectionManager.Add(newTrack);
|
||
|
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
|
||
|
{
|
||
|
SelectionManager.Add(childTrack);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
state.Refresh();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MenuEntry("Remove Invalid Markers", MenuOrder.TrackAction.RemoveInvalidMarkers), UsedImplicitly]
|
||
|
class RemoveInvalidMarkersAction : TrackAction
|
||
|
{
|
||
|
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count()))
|
||
|
return MenuActionDisplayState.Visible;
|
||
|
|
||
|
return MenuActionDisplayState.Hidden;
|
||
|
}
|
||
|
|
||
|
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||
|
{
|
||
|
bool anyRemoved = false;
|
||
|
foreach (var target in tracks)
|
||
|
{
|
||
|
var invalids = target.GetMarkersRaw().Where(x => !(x is IMarker)).ToList();
|
||
|
foreach (var m in invalids)
|
||
|
{
|
||
|
anyRemoved = true;
|
||
|
target.DeleteMarkerRaw(m);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (anyRemoved)
|
||
|
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||
|
|
||
|
return anyRemoved;
|
||
|
}
|
||
|
}
|
||
|
}
|