2022-01-12 10:06:03 +03:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2022-01-12 10:39:15 +03:00
using UnityEditor.Timeline.Actions ;
2022-01-12 10:06:03 +03:00
using UnityEngine ;
using UnityEngine.Playables ;
using UnityEngine.Timeline ;
using Object = UnityEngine . Object ;
namespace UnityEditor.Timeline
{
static class SequencerContextMenu
{
static class Styles
{
public static readonly string addItemFromAssetTemplate = L10n . Tr ( "Add {0} From {1}" ) ;
public static readonly string addSingleItemFromAssetTemplate = L10n . Tr ( "Add From {1}" ) ;
public static readonly string addItemTemplate = L10n . Tr ( "Add {0}" ) ;
public static readonly string typeSelectorTemplate = L10n . Tr ( "Select {0}" ) ;
public static readonly string trackGroup = L10n . Tr ( "Track Group" ) ;
public static readonly string trackSubGroup = L10n . Tr ( "Track Sub-Group" ) ;
public static readonly string addTrackLayer = L10n . Tr ( "Add Layer" ) ;
public static readonly string layerName = L10n . Tr ( "Layer {0}" ) ;
}
public static void ShowMarkerHeaderContextMenu ( Vector2 ? mousePosition , WindowState state )
{
var menu = new GenericMenu ( ) ;
List < MenuActionItem > items = new List < MenuActionItem > ( 100 ) ;
BuildMarkerHeaderContextMenu ( items , mousePosition , state ) ;
2022-01-12 10:39:15 +03:00
ActionManager . BuildMenu ( menu , items ) ;
2022-01-12 10:06:03 +03:00
menu . ShowAsContext ( ) ;
}
public static void ShowNewTracksContextMenu ( ICollection < TrackAsset > tracks , WindowState state )
{
var menu = new GenericMenu ( ) ;
List < MenuActionItem > items = new List < MenuActionItem > ( 100 ) ;
BuildNewTracksContextMenu ( items , tracks , state ) ;
2022-01-12 10:39:15 +03:00
ActionManager . BuildMenu ( menu , items ) ;
2022-01-12 10:06:03 +03:00
menu . ShowAsContext ( ) ;
}
public static void ShowNewTracksContextMenu ( ICollection < TrackAsset > tracks , WindowState state , Rect rect )
{
var menu = new GenericMenu ( ) ;
List < MenuActionItem > items = new List < MenuActionItem > ( 100 ) ;
BuildNewTracksContextMenu ( items , tracks , state ) ;
2022-01-12 10:39:15 +03:00
ActionManager . BuildMenu ( menu , items ) ;
2022-01-12 10:06:03 +03:00
menu . DropDown ( rect ) ;
}
2022-01-12 10:39:15 +03:00
public static void ShowTrackContextMenu ( Vector2 ? mousePosition )
2022-01-12 10:06:03 +03:00
{
var items = new List < MenuActionItem > ( ) ;
var menu = new GenericMenu ( ) ;
2022-01-12 10:39:15 +03:00
BuildTrackContextMenu ( items , mousePosition ) ;
ActionManager . BuildMenu ( menu , items ) ;
2022-01-12 10:06:03 +03:00
menu . ShowAsContext ( ) ;
}
2022-01-12 10:39:15 +03:00
public static void ShowItemContextMenu ( Vector2 mousePosition )
2022-01-12 10:06:03 +03:00
{
var menu = new GenericMenu ( ) ;
var items = new List < MenuActionItem > ( ) ;
2022-01-12 10:39:15 +03:00
BuildItemContextMenu ( items , mousePosition ) ;
ActionManager . BuildMenu ( menu , items ) ;
2022-01-12 10:06:03 +03:00
menu . ShowAsContext ( ) ;
}
2022-01-12 10:39:15 +03:00
public static void BuildItemContextMenu ( List < MenuActionItem > items , Vector2 mousePosition )
2022-01-12 10:06:03 +03:00
{
2022-01-12 10:39:15 +03:00
ActionManager . GetMenuEntries ( ActionManager . TimelineActions , mousePosition , items ) ;
ActionManager . GetMenuEntries ( ActionManager . ClipActions , items ) ;
ActionManager . GetMenuEntries ( ActionManager . MarkerActions , items ) ;
2022-01-12 10:06:03 +03:00
2022-01-12 10:39:15 +03:00
var clips = TimelineEditor . selectedClips ;
2022-01-12 10:06:03 +03:00
if ( clips . Length > 0 )
2022-01-12 10:39:15 +03:00
AddMarkerMenuCommands ( items , clips . Select ( c = > c . parentTrack ) . Distinct ( ) . ToList ( ) , TimelineHelpers . GetCandidateTime ( mousePosition ) ) ;
2022-01-12 10:06:03 +03:00
}
2022-01-12 10:39:15 +03:00
public static void BuildNewTracksContextMenu ( List < MenuActionItem > menuItems , ICollection < TrackAsset > parentTracks , WindowState state , string format = null )
2022-01-12 10:06:03 +03:00
{
if ( parentTracks = = null )
parentTracks = new TrackAsset [ 0 ] ;
if ( string . IsNullOrEmpty ( format ) )
format = "{0}" ;
// Add Group or SubGroup
var title = string . Format ( format , parentTracks . Any ( t = > t ! = null ) ? Styles . trackSubGroup : Styles . trackGroup ) ;
2022-01-12 10:39:15 +03:00
var menuState = ActionValidity . Valid ;
2022-01-12 10:06:03 +03:00
if ( state . editSequence . isReadOnly )
2022-01-12 10:39:15 +03:00
menuState = ActionValidity . Invalid ;
2022-01-12 10:06:03 +03:00
if ( parentTracks . Any ( ) & & parentTracks . Any ( t = > t ! = null & & t . lockedInHierarchy ) )
2022-01-12 10:39:15 +03:00
menuState = ActionValidity . Invalid ;
2022-01-12 10:06:03 +03:00
GenericMenu . MenuFunction command = ( ) = >
{
SelectionManager . Clear ( ) ;
if ( parentTracks . Count = = 0 )
Selection . Add ( TimelineHelpers . CreateTrack < GroupTrack > ( null , title ) ) ;
foreach ( var parentTrack in parentTracks )
Selection . Add ( TimelineHelpers . CreateTrack < GroupTrack > ( parentTrack , title ) ) ;
TimelineEditor . Refresh ( RefreshReason . ContentsAddedOrRemoved ) ;
} ;
menuItems . Add (
new MenuActionItem ( )
{
category = string . Empty ,
entryName = title ,
isActiveInMode = true ,
2022-01-12 10:39:15 +03:00
priority = MenuPriority . AddItem . addGroup ,
2022-01-12 10:06:03 +03:00
state = menuState ,
2022-01-12 10:39:15 +03:00
isChecked = false ,
2022-01-12 10:06:03 +03:00
callback = command
}
) ;
var allTypes = TypeUtility . AllTrackTypes ( ) . Where ( x = > x ! = typeof ( GroupTrack ) & & ! TypeUtility . IsHiddenInMenu ( x ) ) . ToList ( ) ;
2022-01-12 10:39:15 +03:00
int builtInPriority = MenuPriority . AddItem . addTrack ;
int customPriority = MenuPriority . AddItem . addCustomTrack ;
2022-01-12 10:06:03 +03:00
foreach ( var trackType in allTypes )
{
var trackItemType = trackType ;
command = ( ) = >
{
SelectionManager . Clear ( ) ;
if ( parentTracks . Count = = 0 )
SelectionManager . Add ( TimelineHelpers . CreateTrack ( ( Type ) trackItemType , null ) ) ;
foreach ( var parentTrack in parentTracks )
SelectionManager . Add ( TimelineHelpers . CreateTrack ( ( Type ) trackItemType , parentTrack ) ) ;
} ;
menuItems . Add (
new MenuActionItem ( )
{
category = TimelineHelpers . GetTrackCategoryName ( trackType ) ,
entryName = string . Format ( format , TimelineHelpers . GetTrackMenuName ( trackItemType ) ) ,
isActiveInMode = true ,
priority = TypeUtility . IsBuiltIn ( trackType ) ? builtInPriority + + : customPriority + + ,
state = menuState ,
callback = command
}
) ;
}
}
2022-01-12 10:39:15 +03:00
public static void BuildMarkerHeaderContextMenu ( List < MenuActionItem > menu , Vector2 ? mousePosition , WindowState state )
2022-01-12 10:06:03 +03:00
{
2022-01-12 10:39:15 +03:00
ActionManager . GetMenuEntries ( ActionManager . TimelineActions , null , menu , MenuFilter . MarkerHeader ) ;
2022-01-12 10:06:03 +03:00
var timeline = state . editSequence . asset ;
2022-01-12 10:39:15 +03:00
var time = TimelineHelpers . GetCandidateTime ( mousePosition ) ;
2022-01-12 10:06:03 +03:00
var enabled = timeline . markerTrack = = null | | ! timeline . markerTrack . lockedInHierarchy ;
var addMarkerCommand = new Action < Type , Object >
(
( type , obj ) = > AddSingleMarkerCallback ( type , time , timeline , state . editSequence . director , obj )
) ;
AddMarkerMenuCommands ( menu , new TrackAsset [ ] { timeline . markerTrack } , addMarkerCommand , enabled ) ;
}
2022-01-12 10:39:15 +03:00
public static void BuildTrackContextMenu ( List < MenuActionItem > items , Vector2 ? mousePosition )
2022-01-12 10:06:03 +03:00
{
2022-01-12 10:39:15 +03:00
var tracks = SelectionManager . SelectedTracks ( ) . ToArray ( ) ;
if ( tracks . Length = = 0 )
2022-01-12 10:06:03 +03:00
return ;
2022-01-12 10:39:15 +03:00
ActionManager . GetMenuEntries ( ActionManager . TimelineActions , mousePosition , items ) ;
ActionManager . GetMenuEntries ( ActionManager . TrackActions , items ) ;
2022-01-12 10:06:03 +03:00
AddLayeredTrackCommands ( items , tracks ) ;
var first = tracks . First ( ) . GetType ( ) ;
var allTheSame = tracks . All ( t = > t . GetType ( ) = = first ) ;
if ( allTheSame )
{
if ( first ! = typeof ( GroupTrack ) )
{
2022-01-12 10:39:15 +03:00
var candidateTime = TimelineHelpers . GetCandidateTime ( mousePosition , tracks ) ;
2022-01-12 10:06:03 +03:00
AddClipMenuCommands ( items , tracks , candidateTime ) ;
AddMarkerMenuCommands ( items , tracks , candidateTime ) ;
}
else
{
BuildNewTracksContextMenu ( items , tracks , TimelineWindow . instance . state , Styles . addItemTemplate ) ;
}
}
}
static void AddLayeredTrackCommands ( List < MenuActionItem > menuItems , ICollection < TrackAsset > tracks )
{
if ( tracks . Count = = 0 )
return ;
var layeredType = tracks . First ( ) . GetType ( ) ;
// animation tracks have a special menu.
if ( layeredType = = typeof ( AnimationTrack ) )
return ;
// must implement ILayerable
if ( ! typeof ( UnityEngine . Timeline . ILayerable ) . IsAssignableFrom ( layeredType ) )
return ;
if ( tracks . Any ( t = > t . GetType ( ) ! = layeredType ) )
return ;
// only supported on the master track no nesting.
if ( tracks . Any ( t = > t . isSubTrack ) )
return ;
var enabled = tracks . All ( t = > t ! = null & & ! t . lockedInHierarchy ) & & ! TimelineWindow . instance . state . editSequence . isReadOnly ;
2022-01-12 10:39:15 +03:00
int priority = MenuPriority . AddTrackMenu . addLayerTrack ;
2022-01-12 10:06:03 +03:00
GenericMenu . MenuFunction menuCallback = ( ) = >
{
foreach ( var track in tracks )
TimelineHelpers . CreateTrack ( layeredType , track , string . Format ( Styles . layerName , track . GetChildTracks ( ) . Count ( ) + 1 ) ) ;
} ;
var entryName = Styles . addTrackLayer ;
menuItems . Add (
new MenuActionItem ( )
{
category = string . Empty ,
entryName = entryName ,
isActiveInMode = true ,
priority = priority + + ,
2022-01-12 10:39:15 +03:00
state = enabled ? ActionValidity . Valid : ActionValidity . Invalid ,
2022-01-12 10:06:03 +03:00
callback = menuCallback
}
) ;
}
static void AddClipMenuCommands ( List < MenuActionItem > menuItems , ICollection < TrackAsset > tracks , double candidateTime )
{
if ( ! tracks . Any ( ) )
return ;
var trackAsset = tracks . First ( ) ;
var trackType = trackAsset . GetType ( ) ;
if ( tracks . Any ( t = > t . GetType ( ) ! = trackType ) )
return ;
var enabled = tracks . All ( t = > t ! = null & & ! t . lockedInHierarchy ) & & ! TimelineWindow . instance . state . editSequence . isReadOnly ;
var assetTypes = TypeUtility . GetPlayableAssetsHandledByTrack ( trackType ) ;
var visibleAssetTypes = TypeUtility . GetVisiblePlayableAssetsHandledByTrack ( trackType ) ;
// skips the name if there is only a single type
var commandNameTemplate = assetTypes . Count ( ) = = 1 ? Styles . addSingleItemFromAssetTemplate : Styles . addItemFromAssetTemplate ;
2022-01-12 10:39:15 +03:00
int builtInPriority = MenuPriority . AddItem . addClip ;
int customPriority = MenuPriority . AddItem . addCustomClip ;
2022-01-12 10:06:03 +03:00
foreach ( var assetType in assetTypes )
{
var assetItemType = assetType ;
var category = TimelineHelpers . GetItemCategoryName ( assetType ) ;
Action < Object > onObjectChanged = obj = >
{
if ( obj ! = null )
{
foreach ( var t in tracks )
{
TimelineHelpers . CreateClipOnTrack ( assetItemType , obj , t , candidateTime ) ;
}
}
} ;
foreach ( var objectReference in TypeUtility . ObjectReferencesForType ( assetType ) )
{
var isSceneReference = objectReference . isSceneReference ;
var dataType = objectReference . type ;
GenericMenu . MenuFunction menuCallback = ( ) = >
{
ObjectSelector . get . Show ( null , dataType , null , isSceneReference , null , ( obj ) = > onObjectChanged ( obj ) , null ) ;
ObjectSelector . get . titleContent = EditorGUIUtility . TrTextContent ( string . Format ( Styles . typeSelectorTemplate , TypeUtility . GetDisplayName ( dataType ) ) ) ;
} ;
menuItems . Add (
new MenuActionItem ( )
{
category = category ,
entryName = string . Format ( commandNameTemplate , TypeUtility . GetDisplayName ( assetType ) , TypeUtility . GetDisplayName ( objectReference . type ) ) ,
isActiveInMode = true ,
priority = TypeUtility . IsBuiltIn ( assetType ) ? builtInPriority + + : customPriority + + ,
2022-01-12 10:39:15 +03:00
state = enabled ? ActionValidity . Valid : ActionValidity . Invalid ,
2022-01-12 10:06:03 +03:00
callback = menuCallback
}
) ;
}
}
foreach ( var assetType in visibleAssetTypes )
{
var assetItemType = assetType ;
var category = TimelineHelpers . GetItemCategoryName ( assetType ) ;
var commandName = string . Format ( Styles . addItemTemplate , TypeUtility . GetDisplayName ( assetType ) ) ;
GenericMenu . MenuFunction command = ( ) = >
{
foreach ( var t in tracks )
{
TimelineHelpers . CreateClipOnTrack ( assetItemType , t , candidateTime ) ;
}
} ;
menuItems . Add (
new MenuActionItem ( )
{
category = category ,
entryName = commandName ,
isActiveInMode = true ,
priority = TypeUtility . IsBuiltIn ( assetItemType ) ? builtInPriority + + : customPriority + + ,
2022-01-12 10:39:15 +03:00
state = enabled ? ActionValidity . Valid : ActionValidity . Invalid ,
2022-01-12 10:06:03 +03:00
callback = command
}
) ;
}
}
static void AddMarkerMenuCommands ( List < MenuActionItem > menu , IEnumerable < Type > markerTypes , Action < Type , Object > addMarkerCommand , bool enabled )
{
2022-01-12 10:39:15 +03:00
int builtInPriority = MenuPriority . AddItem . addMarker ;
int customPriority = MenuPriority . AddItem . addCustomMarker ;
2022-01-12 10:06:03 +03:00
foreach ( var markerType in markerTypes )
{
var markerItemType = markerType ;
string category = TimelineHelpers . GetItemCategoryName ( markerItemType ) ;
menu . Add (
new MenuActionItem ( )
{
category = category ,
entryName = string . Format ( Styles . addItemTemplate , TypeUtility . GetDisplayName ( markerType ) ) ,
isActiveInMode = true ,
priority = TypeUtility . IsBuiltIn ( markerType ) ? builtInPriority + + : customPriority + + ,
2022-01-12 10:39:15 +03:00
state = enabled ? ActionValidity . Valid : ActionValidity . Invalid ,
2022-01-12 10:06:03 +03:00
callback = ( ) = > addMarkerCommand ( markerItemType , null )
}
) ;
foreach ( var objectReference in TypeUtility . ObjectReferencesForType ( markerType ) )
{
var isSceneReference = objectReference . isSceneReference ;
GenericMenu . MenuFunction menuCallback = ( ) = >
{
2022-01-12 10:39:15 +03:00
Type assetDataType = objectReference . type ;
ObjectSelector . get . titleContent = EditorGUIUtility . TrTextContent ( string . Format ( Styles . typeSelectorTemplate , TypeUtility . GetDisplayName ( assetDataType ) ) ) ;
ObjectSelector . get . Show ( null , assetDataType , null , isSceneReference , null , obj = >
{
if ( obj ! = null )
addMarkerCommand ( markerItemType , obj ) ;
} , null ) ;
2022-01-12 10:06:03 +03:00
} ;
menu . Add (
2022-01-12 10:39:15 +03:00
new MenuActionItem
2022-01-12 10:06:03 +03:00
{
category = TimelineHelpers . GetItemCategoryName ( markerItemType ) ,
entryName = string . Format ( Styles . addItemFromAssetTemplate , TypeUtility . GetDisplayName ( markerType ) , TypeUtility . GetDisplayName ( objectReference . type ) ) ,
isActiveInMode = true ,
priority = TypeUtility . IsBuiltIn ( markerType ) ? builtInPriority + + : customPriority + + ,
2022-01-12 10:39:15 +03:00
state = enabled ? ActionValidity . Valid : ActionValidity . Invalid ,
2022-01-12 10:06:03 +03:00
callback = menuCallback
}
) ;
}
}
}
static void AddMarkerMenuCommands ( List < MenuActionItem > menuItems , ICollection < TrackAsset > tracks , double candidateTime )
{
if ( tracks . Count = = 0 )
return ;
var enabled = tracks . All ( t = > ! t . lockedInHierarchy ) & & ! TimelineWindow . instance . state . editSequence . isReadOnly ;
var addMarkerCommand = new Action < Type , Object > ( ( type , obj ) = > AddMarkersCallback ( tracks , type , candidateTime , obj ) ) ;
AddMarkerMenuCommands ( menuItems , tracks , addMarkerCommand , enabled ) ;
}
static void AddMarkerMenuCommands ( List < MenuActionItem > menuItems , ICollection < TrackAsset > tracks , Action < Type , Object > command , bool enabled )
{
var markerTypes = TypeUtility . GetBuiltInMarkerTypes ( ) . Union ( TypeUtility . GetUserMarkerTypes ( ) ) ;
if ( tracks ! = null )
markerTypes = markerTypes . Where ( x = > tracks . All ( track = > ( track = = null ) | | TypeUtility . DoesTrackSupportMarkerType ( track , x ) ) ) ; // null track indicates marker track to be created
AddMarkerMenuCommands ( menuItems , markerTypes , command , enabled ) ;
}
static void AddMarkersCallback ( ICollection < TrackAsset > targets , Type markerType , double time , Object obj )
{
SelectionManager . Clear ( ) ;
foreach ( var target in targets )
{
var marker = TimelineHelpers . CreateMarkerOnTrack ( markerType , obj , target , time ) ;
SelectionManager . Add ( marker ) ;
}
TimelineEditor . Refresh ( RefreshReason . ContentsAddedOrRemoved ) ;
}
static void AddSingleMarkerCallback ( Type markerType , double time , TimelineAsset timeline , PlayableDirector director , Object assignableObject )
{
timeline . CreateMarkerTrack ( ) ;
var markerTrack = timeline . markerTrack ;
SelectionManager . Clear ( ) ;
var marker = TimelineHelpers . CreateMarkerOnTrack ( markerType , assignableObject , markerTrack , time ) ;
SelectionManager . Add ( marker ) ;
if ( typeof ( INotification ) . IsAssignableFrom ( markerType ) & & director ! = null )
{
if ( director ! = null & & director . GetGenericBinding ( markerTrack ) = = null )
director . SetGenericBinding ( markerTrack , director . gameObject ) ;
}
TimelineEditor . Refresh ( RefreshReason . ContentsAddedOrRemoved ) ;
}
}
}