528 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			528 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System.Collections.Generic; | ||
|  | using System.Linq; | ||
|  | using JetBrains.Annotations; | ||
|  | using UnityEditor.Timeline.Actions; | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.Playables; | ||
|  | using UnityEngine.Timeline; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     [MenuEntry("Edit in Animation Window", MenuPriority.TrackActionSection.editInAnimationWindow)] | ||
|  |     class EditTrackInAnimationWindow : TrackAction | ||
|  |     { | ||
|  |         public static bool Do(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; | ||
|  | 
 | ||
|  |             GameObject gameObject = null; | ||
|  |             if (TimelineEditor.inspectedDirector != null) | ||
|  |                 gameObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track); | ||
|  | 
 | ||
|  |             var timeController = TimelineAnimationUtilities.CreateTimeController(CreateTimeControlClipData(track)); | ||
|  |             TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any()) | ||
|  |                 return ActionValidity.Invalid; | ||
|  | 
 | ||
|  |             var firstTrack = tracks.First(); | ||
|  |             if (firstTrack is AnimationTrack) | ||
|  |             { | ||
|  |                 var animTrack = firstTrack as AnimationTrack; | ||
|  |                 if (animTrack.CanConvertToClipMode()) | ||
|  |                     return ActionValidity.Valid; | ||
|  |             } | ||
|  |             else if (firstTrack.hasCurves) | ||
|  |             { | ||
|  |                 return ActionValidity.Valid; | ||
|  |             } | ||
|  | 
 | ||
|  |             return ActionValidity.NotApplicable; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             return Do(tracks.First()); | ||
|  |         } | ||
|  | 
 | ||
|  |         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", MenuPriority.TrackActionSection.lockSelected)] | ||
|  |     class LockSelectedTrack : TrackAction, IMenuName | ||
|  |     { | ||
|  |         public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only"); | ||
|  |         public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only"); | ||
|  | 
 | ||
|  |         public string menuName { get; private set; } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             UpdateMenuName(tracks); | ||
|  |             if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack || !track.subTracksObjects.Any())) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any()) return false; | ||
|  | 
 | ||
|  |             var hasUnlockedTracks = tracks.Any(x => !x.locked); | ||
|  |             Lock(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks); | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         void UpdateMenuName(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             menuName = tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void Lock(TrackAsset[] tracks, bool shouldlock) | ||
|  |         { | ||
|  |             if (tracks.Length == 0) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t))) | ||
|  |             { | ||
|  |                 TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks")); | ||
|  |                 track.locked = shouldlock; | ||
|  |             } | ||
|  |             TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [MenuEntry("Lock", MenuPriority.TrackActionSection.lockTrack)] | ||
|  |     [Shortcut(Shortcuts.Timeline.toggleLock)] | ||
|  |     class LockTrack : TrackAction, IMenuName | ||
|  |     { | ||
|  |         static readonly string k_LockText = L10n.Tr("Lock"); | ||
|  |         static readonly string k_UnlockText = L10n.Tr("Unlock"); | ||
|  | 
 | ||
|  |         public string menuName { get; private set; } | ||
|  | 
 | ||
|  |         void UpdateMenuName(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             menuName = tracks.Any(x => !x.locked) ? k_LockText : k_UnlockText; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any()) return false; | ||
|  | 
 | ||
|  |             var hasUnlockedTracks = tracks.Any(x => !x.locked); | ||
|  |             SetLockState(tracks, hasUnlockedTracks); | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             UpdateMenuName(tracks); | ||
|  |             tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset); | ||
|  | 
 | ||
|  |             if (!tracks.Any()) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  |             if (tracks.Any(TimelineUtility.IsLockedFromGroup)) | ||
|  |                 return ActionValidity.Invalid; | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void SetLockState(IEnumerable<TrackAsset> tracks, bool shouldLock) | ||
|  |         { | ||
|  |             if (!tracks.Any()) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             foreach (var track in tracks) | ||
|  |             { | ||
|  |                 if (TimelineUtility.IsLockedFromGroup(track)) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 if (track as GroupTrack == null) | ||
|  |                     SetLockState(track.GetChildTracks().ToArray(), shouldLock); | ||
|  | 
 | ||
|  |                 TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks")); | ||
|  |                 track.locked = shouldLock; | ||
|  |             } | ||
|  | 
 | ||
|  |             // 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); | ||
|  |                 track.UnarmForRecord(); | ||
|  |                 foreach (var child in flattenedChildTracks) | ||
|  |                 { | ||
|  |                     SelectionManager.Remove(child); | ||
|  |                     child.UnarmForRecord(); | ||
|  |                     foreach (var clip in child.GetClips()) | ||
|  |                         SelectionManager.Remove(clip); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             // no need to rebuild, just repaint (including inspectors) | ||
|  |             InspectorWindow.RepaintAllInspectors(); | ||
|  |             TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [UsedImplicitly] | ||
|  |     [MenuEntry("Show Markers", MenuPriority.TrackActionSection.showHideMarkers)] | ||
|  |     [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)] | ||
|  |     class ShowHideMarkers : TrackAction, IMenuChecked | ||
|  |     { | ||
|  |         public bool isChecked { get; private set; } | ||
|  | 
 | ||
|  |         void UpdateCheckedStatus(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             isChecked = tracks.All(x => x.GetShowMarkers()); | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             UpdateCheckedStatus(tracks); | ||
|  |             if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0)) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  | 
 | ||
|  |             if (tracks.Any(t => t.lockedInHierarchy)) | ||
|  |             { | ||
|  |                 return ActionValidity.Invalid; | ||
|  |             } | ||
|  | 
 | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any()) return false; | ||
|  | 
 | ||
|  |             var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers()); | ||
|  |             ShowHide(tracks, hasUnlockedTracks); | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         static void ShowHide(IEnumerable<TrackAsset> tracks, bool shouldLock) | ||
|  |         { | ||
|  |             if (!tracks.Any()) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             foreach (var track in tracks) | ||
|  |                 track.SetShowTrackMarkers(shouldLock); | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [MenuEntry("Mute selected track only", MenuPriority.TrackActionSection.muteSelected), UsedImplicitly] | ||
|  |     class MuteSelectedTrack : TrackAction, IMenuName | ||
|  |     { | ||
|  |         public static readonly string MuteSelectedText = L10n.Tr("Mute selected track only"); | ||
|  |         public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only"); | ||
|  | 
 | ||
|  |         public string menuName { get; private set; } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             UpdateMenuName(tracks); | ||
|  |             if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack || !track.subTracksObjects.Any())) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any()) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             var hasUnmutedTracks = tracks.Any(x => !x.muted); | ||
|  |             Mute(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks); | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         void UpdateMenuName(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             menuName = tracks.All(t => t.muted) ? UnmuteSelectedText : MuteSelectedText; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void Mute(TrackAsset[] tracks, bool shouldMute) | ||
|  |         { | ||
|  |             if (tracks.Length == 0) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t))) | ||
|  |             { | ||
|  |                 TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks")); | ||
|  |                 track.muted = shouldMute; | ||
|  |             } | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.ContentsModified); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [MenuEntry("Mute", MenuPriority.TrackActionSection.mute)] | ||
|  |     [Shortcut(Shortcuts.Timeline.toggleMute)] | ||
|  |     class MuteTrack : TrackAction, IMenuName | ||
|  |     { | ||
|  |         static readonly string k_MuteText = L10n.Tr("Mute"); | ||
|  |         static readonly string k_UnMuteText = L10n.Tr("Unmute"); | ||
|  | 
 | ||
|  |         public string menuName { get; private set; } | ||
|  | 
 | ||
|  |         void UpdateMenuName(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             menuName = tracks.Any(x => !x.muted) ? k_MuteText : k_UnMuteText; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (!tracks.Any() || tracks.Any(TimelineUtility.IsParentMuted)) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             var hasUnmutedTracks = tracks.Any(x => !x.muted); | ||
|  |             Mute(tracks, hasUnmutedTracks); | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             UpdateMenuName(tracks); | ||
|  |             if (tracks.Any(TimelineUtility.IsLockedFromGroup)) | ||
|  |                 return ActionValidity.Invalid; | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void Mute(IEnumerable<TrackAsset> tracks, bool shouldMute) | ||
|  |         { | ||
|  |             if (!tracks.Any()) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             foreach (var track in tracks) | ||
|  |             { | ||
|  |                 if (track as GroupTrack == null) | ||
|  |                     Mute(track.GetChildTracks().ToArray(), shouldMute); | ||
|  |                 TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks")); | ||
|  |                 track.muted = shouldMute; | ||
|  |             } | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.ContentsModified); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class DeleteTracks : TrackAction | ||
|  |     { | ||
|  |         public static void Do(TimelineAsset timeline, TrackAsset track) | ||
|  |         { | ||
|  |             SelectionManager.Remove(track); | ||
|  |             TrackModifier.DeleteTrack(timeline, track); | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid; | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset); | ||
|  | 
 | ||
|  |             // 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 | ||
|  |             TimelineEditor.state.previewMode = false; | ||
|  | 
 | ||
|  |             TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks); | ||
|  | 
 | ||
|  |             foreach (var track in tracks) | ||
|  |                 Do(TimelineEditor.inspectedAsset, track); | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class CopyTracksToClipboard : TrackAction | ||
|  |     { | ||
|  |         public static bool Do(TrackAsset[] tracks) | ||
|  |         { | ||
|  |             var action = new CopyTracksToClipboard(); | ||
|  |             return action.Execute(tracks); | ||
|  |         } | ||
|  | 
 | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid; | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset); | ||
|  |             TimelineEditor.clipboard.CopyTracks(tracks); | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     class DuplicateTracks : TrackAction | ||
|  |     { | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid; | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset); | ||
|  |             if (tracks.Any()) | ||
|  |             { | ||
|  |                 SelectionManager.RemoveTimelineSelection(); | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var track in TrackExtensions.FilterTracks(tracks)) | ||
|  |             { | ||
|  |                 var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector); | ||
|  |                 //Add all duplicated tracks to selection | ||
|  |                 SelectionManager.Add(newTrack); | ||
|  |                 foreach (var childTrack in newTrack.GetFlattenedChildTracks()) | ||
|  |                 { | ||
|  |                     SelectionManager.Add(childTrack); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 //Duplicate bindings for tracks and subtracks | ||
|  |                 if (TimelineEditor.inspectedDirector != null) | ||
|  |                 { | ||
|  |                     DuplicateBindings(track, newTrack, TimelineEditor.inspectedDirector); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void DuplicateBindings(TrackAsset track, TrackAsset newTrack, PlayableDirector director) | ||
|  |         { | ||
|  |             var originalTracks = track.GetFlattenedChildTracks().Append(track); | ||
|  |             var newTracks = newTrack.GetFlattenedChildTracks().Append(newTrack); | ||
|  |             var toBind = new List<Tuple<TrackAsset, Object>>(); | ||
|  | 
 | ||
|  |             // Collect all track bindings to duplicate | ||
|  |             var originalIt = originalTracks.GetEnumerator(); | ||
|  |             var newIt = newTracks.GetEnumerator(); | ||
|  |             while (originalIt.MoveNext() && newIt.MoveNext()) | ||
|  |             { | ||
|  |                 var binding = director.GetGenericBinding(originalIt.Current); | ||
|  |                 if (binding != null) | ||
|  |                     toBind.Add(new Tuple<TrackAsset, Object>(newIt.Current, binding)); | ||
|  |             } | ||
|  | 
 | ||
|  |             //Only create Director undo if there are bindings to duplicate | ||
|  |             if (toBind.Count > 0) | ||
|  |                 TimelineUndo.PushUndo(TimelineEditor.inspectedDirector, L10n.Tr("Duplicate")); | ||
|  | 
 | ||
|  |             //Assign bindings for all tracks after undo. | ||
|  |             foreach (var binding in toBind) | ||
|  |             { | ||
|  |                 TimelineEditor.inspectedDirector.SetGenericBinding(binding.Item1, binding.Item2); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [MenuEntry("Remove Invalid Markers", MenuPriority.TrackActionSection.removeInvalidMarkers), UsedImplicitly] | ||
|  |     class RemoveInvalidMarkersAction : TrackAction | ||
|  |     { | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count())) | ||
|  |                 return ActionValidity.Valid; | ||
|  | 
 | ||
|  |             return ActionValidity.NotApplicable; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<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; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [Shortcut(Shortcuts.Timeline.collapseTrack)] | ||
|  |     [UsedImplicitly] | ||
|  |     class CollapseTrackAction : TrackAction | ||
|  |     { | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             var collapsibleTracks = tracks.Where(track => track.subTracksObjects.Any()); | ||
|  | 
 | ||
|  |             if (!collapsibleTracks.Any()) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  | 
 | ||
|  |             if (collapsibleTracks.All(track => track.IsCollapsed())) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  | 
 | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             return KeyboardNavigation.TryCollapse(tracks.Where(track => track.subTracksObjects.Any() && !track.IsCollapsed())); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [Shortcut(Shortcuts.Timeline.expandTrack)] | ||
|  |     [UsedImplicitly] | ||
|  |     class ExpandTrackAction : TrackAction | ||
|  |     { | ||
|  |         public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             var collapsibleTracks = tracks.Where(track => track.subTracksObjects.Any()); | ||
|  | 
 | ||
|  |             if (!collapsibleTracks.Any()) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  | 
 | ||
|  |             if (collapsibleTracks.All(track => !track.IsCollapsed())) | ||
|  |                 return ActionValidity.NotApplicable; | ||
|  | 
 | ||
|  |             return ActionValidity.Valid; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override bool Execute(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             return KeyboardNavigation.TryExpand(tracks.Where(track => track.subTracksObjects.Any() && track.IsCollapsed())); | ||
|  |         } | ||
|  |     } | ||
|  | } |