using System; using System.Collections.Generic; using System.Linq; using UnityEditor.IMGUI.Controls; using UnityEngine.Timeline; namespace UnityEditor.Timeline { static class KeyboardNavigation { public static void FrameTrackHeader(TreeViewItem treeItem = null) { if (TrackHeadActive()) treeItem = treeItem ?? SelectionManager.SelectedTrackGUI().Last(); else { var item = GetVisibleSelectedItems().LastOrDefault(); treeItem = TimelineWindow.instance.allTracks.FirstOrDefault( x => item != null && x.track == item.parentTrack); } if (treeItem != null) TimelineWindow.instance.treeView.FrameItem(treeItem); } public static bool TrackHeadActive() { return SelectionManager.SelectedTracks().Any(x => x.IsVisibleRecursive()) && !ClipAreaActive(); } public static bool ClipAreaActive() { return GetVisibleSelectedItems().Any(); } public static IEnumerable GetVisibleSelectedItems() { return SelectionManager.SelectedItems().Where(x => x.parentTrack.IsVisibleRecursive()); } public static IEnumerable GetVisibleTracks() { return TimelineWindow.instance.allTracks.Where(x => x.track.IsVisibleRecursive()); } static TrackAsset PreviousTrack(this TrackAsset track) { var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList(); var selIdx = uiOrderTracks.IndexOf(track); return selIdx > 0 ? uiOrderTracks[selIdx - 1] : null; } static TrackAsset NextTrack(this TrackAsset track) { var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList(); var selIdx = uiOrderTracks.IndexOf(track); return selIdx < uiOrderTracks.Count - 1 && selIdx != -1 ? uiOrderTracks[selIdx + 1] : null; } static ITimelineItem PreviousItem(this ITimelineItem item, bool clipOnly) { var items = item.parentTrack.GetItems().ToArray(); if (clipOnly) { items = items.Where(x => x is ClipItem).ToArray(); } else { items = items.Where(x => x is MarkerItem).ToArray(); } var idx = Array.IndexOf(items, item); return idx > 0 ? items[idx - 1] : null; } static ITimelineItem NextItem(this ITimelineItem item, bool clipOnly) { var items = item.parentTrack.GetItems().ToArray(); if (clipOnly) { items = items.Where(x => x is ClipItem).ToArray(); } else { items = items.Where(x => x is MarkerItem).ToArray(); } var idx = Array.IndexOf(items, item); return idx < items.Length - 1 ? items[idx + 1] : null; } static bool FilterItems(ref List items) { var clipOnly = false; if (items.Any(x => x is ClipItem)) { items = items.Where(x => x is ClipItem).ToList(); clipOnly = true; } return clipOnly; } static ITimelineItem GetClosestItem(TrackAsset track, ITimelineItem refItem) { var start = refItem.start; var end = refItem.end; var items = track.GetItems().ToList(); if (refItem is ClipItem) { items = items.Where(x => x is ClipItem).ToList(); } else { items = items.Where(x => x is MarkerItem).ToList(); } if (!items.Any()) return null; ITimelineItem ret = null; var scoreToBeat = double.NegativeInfinity; foreach (var item in items) { // test for overlap var low = Math.Max(item.start, start); var high = Math.Min(item.end, end); if (low <= high) { var score = high - low; if (score >= scoreToBeat) { scoreToBeat = score; ret = item; } } } return ret; } public static bool FocusFirstVisibleItem(IEnumerable focusTracks = null) { var timeRange = TimelineEditor.visibleTimeRange; var tracks = focusTracks ?? TimelineWindow.instance.treeView.visibleTracks.Where(x => x.IsVisibleRecursive() && x.GetItems().Any()); var items = tracks.SelectMany(t => t.GetItems().OfType().Where(x => x.end >= timeRange.x && x.end <= timeRange.y || x.start >= timeRange.x && x.start <= timeRange.y)).ToList(); var itemFullyInView = items.Where(x => x.end >= timeRange.x && x.end <= timeRange.y && x.start >= timeRange.x && x.start <= timeRange.y); var itemToSelect = itemFullyInView.FirstOrDefault() ?? items.FirstOrDefault(); if (itemToSelect != null && !itemToSelect.parentTrack.lockedInHierarchy) { SelectionManager.SelectOnly(itemToSelect); return true; } return false; } public static bool CollapseGroup(IEnumerable tracks) { if (!TrackHeadActive()) return false; var didCollapse = false; foreach (TrackAsset track in tracks) { if (!track.GetChildTracks().Any()) continue; if (!track.GetCollapsed()) { didCollapse = true; track.SetCollapsed(true); } } if (didCollapse) { TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved); return true; } return SelectAndShowParentTrack(tracks.LastOrDefault()); } static bool SelectAndShowParentTrack(TrackAsset track) { TrackAsset parent = track != null ? track.parent as TrackAsset : null; if (parent) { SelectionManager.SelectOnly(parent); FrameTrackHeader(GetVisibleTracks().First(x => x.track == parent)); return true; } return false; } public static bool SelectLeftItem(bool shift = false) { if (ClipAreaActive()) { var items = SelectionManager.SelectedItems().ToList(); var clipOnly = FilterItems(ref items); var item = items.Last(); var prev = item.PreviousItem(clipOnly); if (prev != null && !prev.parentTrack.lockedInHierarchy) { if (shift) { if (SelectionManager.Contains(prev)) SelectionManager.Remove(item); SelectionManager.Add(prev); } else SelectionManager.SelectOnly(prev); TimelineHelpers.FrameItems(new[] {prev}); } else if (item != null && !shift && item.parentTrack != TimelineEditor.inspectedAsset.markerTrack) SelectionManager.SelectOnly(item.parentTrack); return true; } return false; } public static bool SelectRightItem(bool shift = false) { if (ClipAreaActive()) { var items = SelectionManager.SelectedItems().ToList(); var clipOnly = FilterItems(ref items); var item = items.Last(); var next = item.NextItem(clipOnly); if (next != null && !next.parentTrack.lockedInHierarchy) { if (shift) { if (SelectionManager.Contains(next)) SelectionManager.Remove(item); SelectionManager.Add(next); } else SelectionManager.SelectOnly(next); TimelineHelpers.FrameItems(new[] {next}); return true; } } return false; } public static bool UnCollapseGroup(IEnumerable tracks) { if (!TrackHeadActive()) return false; var didUncollapse = false; foreach (TrackAsset track in tracks) { if (!track.GetChildTracks().Any()) continue; if (track.GetCollapsed()) { didUncollapse = true; track.SetCollapsed(false); } } if (didUncollapse) { TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved); return true; } return SelectFirstClipStartingFrom(tracks.Last()); } static bool SelectFirstClipStartingFrom(TrackAsset track) { List visibleTracks = GetVisibleTracks().Select(x => x.track).ToList(); int idx = visibleTracks.IndexOf(track); ITimelineItem item = null; for (int i = idx; i < visibleTracks.Count; ++i) { var items = visibleTracks[i].GetItems().OfType(); if (!items.Any() || visibleTracks[i].lockedInHierarchy) continue; item = items.First(); break; } if (item != null) { SelectionManager.SelectOnly(item); TimelineHelpers.FrameItems(new[] { item }); return true; } return false; } public static bool SelectUpTrack(bool shift = false) { if (TrackHeadActive()) { var prevTrack = PreviousTrack(SelectionManager.SelectedTracks().Last()); if (prevTrack != null) { if (shift) { if (SelectionManager.Contains(prevTrack)) SelectionManager.Remove(SelectionManager.SelectedTracks().Last()); SelectionManager.Add(prevTrack); } else SelectionManager.SelectOnly(prevTrack); FrameTrackHeader(GetVisibleTracks().First(x => x.track == prevTrack)); } return true; } return false; } public static bool SelectUpItem() { if (ClipAreaActive()) { var refItem = SelectionManager.SelectedItems().Last(); var prevTrack = refItem.parentTrack.PreviousTrack(); while (prevTrack != null) { var selectionItem = GetClosestItem(prevTrack, refItem); if (selectionItem == null || prevTrack.lockedInHierarchy) { prevTrack = prevTrack.PreviousTrack(); continue; } SelectionManager.SelectOnly(selectionItem); TimelineHelpers.FrameItems(new[] {selectionItem}); FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack)); break; } return true; } return false; } public static bool SelectDownTrack(bool shift = false) { if (TrackHeadActive()) { var nextTrack = SelectionManager.SelectedTracks().Last().NextTrack(); if (nextTrack != null) { if (shift) { if (SelectionManager.Contains(nextTrack)) SelectionManager.Remove(SelectionManager.SelectedTracks().Last()); SelectionManager.Add(nextTrack); } else SelectionManager.SelectOnly(nextTrack); FrameTrackHeader(GetVisibleTracks().First(x => x.track == nextTrack)); } return true; } return false; } public static bool SelectDownItem() { if (ClipAreaActive()) { var refItem = SelectionManager.SelectedItems().Last(); var nextTrack = refItem.parentTrack.NextTrack(); while (nextTrack != null) { var selectionItem = GetClosestItem(nextTrack, refItem); if (selectionItem == null || nextTrack.lockedInHierarchy) { nextTrack = nextTrack.NextTrack(); continue; } SelectionManager.SelectOnly(selectionItem); TimelineHelpers.FrameItems(new[] {selectionItem}); FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack)); break; } return true; } return false; } } }