A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor.IMGUI.Controls;
5using UnityEngine.Timeline;
6
7namespace UnityEditor.Timeline
8{
9 static class KeyboardNavigation
10 {
11 public static void FrameTrackHeader(TreeViewItem treeItem = null)
12 {
13 if (TrackHeadActive())
14 treeItem = treeItem ?? SelectionManager.SelectedTrackGUI().Last();
15 else
16 {
17 var item = GetVisibleSelectedItems().LastOrDefault();
18 treeItem = TimelineWindow.instance.allTracks.FirstOrDefault(
19 x => item != null && x.track == item.parentTrack);
20 }
21
22 if (treeItem != null)
23 TimelineWindow.instance.treeView.FrameItem(treeItem);
24 }
25
26 public static bool TrackHeadActive()
27 {
28 return SelectionManager.SelectedTracks().Any(x => x.IsVisibleInHierarchy()) && !ClipAreaActive();
29 }
30
31 public static bool ClipAreaActive()
32 {
33 return GetVisibleSelectedItems().Any();
34 }
35
36 public static IEnumerable<ITimelineItem> GetVisibleSelectedItems()
37 {
38 return SelectionManager.SelectedItems().Where(x => x.parentTrack.IsVisibleInHierarchy());
39 }
40
41 public static IEnumerable<TimelineTrackBaseGUI> GetVisibleTracks()
42 {
43 return TimelineWindow.instance.allTracks.Where(x => x.track.IsVisibleInHierarchy());
44 }
45
46 static TrackAsset PreviousTrack(this TrackAsset track)
47 {
48 var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList();
49 var selIdx = uiOrderTracks.IndexOf(track);
50 return selIdx > 0 ? uiOrderTracks[selIdx - 1] : null;
51 }
52
53 static TrackAsset NextTrack(this TrackAsset track)
54 {
55 var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList();
56 var selIdx = uiOrderTracks.IndexOf(track);
57 return selIdx < uiOrderTracks.Count - 1 && selIdx != -1 ? uiOrderTracks[selIdx + 1] : null;
58 }
59
60 static ITimelineItem PreviousItem(this ITimelineItem item, bool clipOnly)
61 {
62 var items = item.parentTrack.GetItems().ToArray();
63 if (clipOnly)
64 {
65 items = items.Where(x => x is ClipItem).ToArray();
66 }
67 else
68 {
69 items = items.Where(x => x is MarkerItem).ToArray();
70 }
71
72 var idx = Array.IndexOf(items, item);
73 return idx > 0 ? items[idx - 1] : null;
74 }
75
76 static ITimelineItem NextItem(this ITimelineItem item, bool clipOnly)
77 {
78 var items = item.parentTrack.GetItems().ToArray();
79 if (clipOnly)
80 {
81 items = items.Where(x => x is ClipItem).ToArray();
82 }
83 else
84 {
85 items = items.Where(x => x is MarkerItem).ToArray();
86 }
87
88 var idx = Array.IndexOf(items, item);
89 return idx < items.Length - 1 ? items[idx + 1] : null;
90 }
91
92 static bool FilterItems(ref List<ITimelineItem> items)
93 {
94 var clipOnly = false;
95 if (items.Any(x => x is ClipItem))
96 {
97 items = items.Where(x => x is ClipItem).ToList();
98 clipOnly = true;
99 }
100
101 return clipOnly;
102 }
103
104 static ITimelineItem GetClosestItem(TrackAsset track, ITimelineItem refItem)
105 {
106 var start = refItem.start;
107 var end = refItem.end;
108 var items = track.GetItems().ToList();
109
110 if (refItem is ClipItem)
111 {
112 items = items.Where(x => x is ClipItem).ToList();
113 }
114 else
115 {
116 items = items.Where(x => x is MarkerItem).ToList();
117 }
118
119 if (!items.Any())
120 return null;
121 ITimelineItem ret = null;
122 var scoreToBeat = double.NegativeInfinity;
123
124 foreach (var item in items)
125 {
126 // test for overlap
127 var low = Math.Max(item.start, start);
128 var high = Math.Min(item.end, end);
129 if (low <= high)
130 {
131 var score = high - low;
132 if (score >= scoreToBeat)
133 {
134 scoreToBeat = score;
135 ret = item;
136 }
137 }
138 }
139
140 return ret;
141 }
142
143 public static bool FocusFirstVisibleItem(IEnumerable<TrackAsset> focusTracks = null)
144 {
145 var timeRange = TimelineEditor.visibleTimeRange;
146
147 var tracks = focusTracks ?? TimelineWindow.instance.treeView.visibleTracks.Where(x => x.IsVisibleInHierarchy() && x.GetItems().Any());
148 var items = tracks.SelectMany(t => t.GetItems().OfType<ClipItem>().Where(x => x.end >= timeRange.x && x.end <= timeRange.y ||
149 x.start >= timeRange.x && x.start <= timeRange.y)).ToList();
150 var itemFullyInView = items.Where(x => x.end >= timeRange.x && x.end <= timeRange.y &&
151 x.start >= timeRange.x && x.start <= timeRange.y);
152 var itemToSelect = itemFullyInView.FirstOrDefault() ?? items.FirstOrDefault();
153 if (itemToSelect != null && !itemToSelect.parentTrack.lockedInHierarchy)
154 {
155 SelectionManager.SelectOnly(itemToSelect);
156 return true;
157 }
158 return false;
159 }
160
161 public static bool NavigateLeft(IEnumerable<TrackAsset> tracks)
162 {
163 if (!TrackHeadActive())
164 return false;
165
166 if (TryCollapse(tracks))
167 return true;
168
169 return SelectAndShowParentTrack(tracks.LastOrDefault());
170 }
171
172 /// <summary>
173 /// Tries to collapse any track from a list of tracks
174 /// </summary>
175 /// <param name="tracks"></param>
176 /// <returns>true if any were collapsed, false otherwise</returns>
177 public static bool TryCollapse(IEnumerable<TrackAsset> tracks)
178 {
179 var didCollapse = false;
180
181 foreach (TrackAsset track in tracks)
182 {
183 if (!track.GetChildTracks().Any())
184 continue;
185
186 if (!track.IsCollapsed())
187 {
188 didCollapse = true;
189 track.SetCollapsed(true);
190 }
191 }
192
193 if (didCollapse)
194 {
195 TimelineEditor.window.treeView.Reload();
196 return true;
197 }
198
199 return false;
200 }
201
202 public static bool ToggleCollapseGroup(IEnumerable<TrackAsset> tracks)
203 {
204 if (!TrackHeadActive())
205 return false;
206
207 var didChange = false;
208
209 foreach (TrackAsset track in tracks)
210 {
211 if (!track.GetChildTracks().Any())
212 continue;
213
214 track.SetCollapsed(!track.IsCollapsed());
215 didChange = true;
216 }
217
218 if (didChange)
219 TimelineEditor.window.treeView.Reload();
220
221 return didChange;
222 }
223
224 static bool SelectAndShowParentTrack(TrackAsset track)
225 {
226 TrackAsset parent = track != null ? track.parent as TrackAsset : null;
227 if (parent)
228 {
229 SelectionManager.SelectOnly(parent);
230 FrameTrackHeader(GetVisibleTracks().FirstOrDefault(x => x.track == parent));
231 return true;
232 }
233
234 return false;
235 }
236
237 public static bool SelectLeftItem(bool shift = false)
238 {
239 if (ClipAreaActive())
240 {
241 var items = SelectionManager.SelectedItems().ToList();
242 var clipOnly = FilterItems(ref items);
243
244 var item = items.Last();
245 var prev = item.PreviousItem(clipOnly);
246 if (prev != null && !prev.parentTrack.lockedInHierarchy)
247 {
248 if (shift)
249 {
250 if (SelectionManager.Contains(prev))
251 SelectionManager.Remove(item);
252 SelectionManager.Add(prev);
253 }
254 else
255 SelectionManager.SelectOnly(prev);
256 TimelineHelpers.FrameItems(new[] { prev });
257 }
258 else if (item != null && !shift && item.parentTrack != TimelineEditor.inspectedAsset.markerTrack)
259 SelectionManager.SelectOnly(item.parentTrack);
260 return true;
261 }
262 return false;
263 }
264
265 public static bool SelectRightItem(bool shift = false)
266 {
267 if (ClipAreaActive())
268 {
269 var items = SelectionManager.SelectedItems().ToList();
270 var clipOnly = FilterItems(ref items);
271
272 var item = items.Last();
273 var next = item.NextItem(clipOnly);
274 if (next != null && !next.parentTrack.lockedInHierarchy)
275 {
276 if (shift)
277 {
278 if (SelectionManager.Contains(next))
279 SelectionManager.Remove(item);
280 SelectionManager.Add(next);
281 }
282 else
283 SelectionManager.SelectOnly(next);
284 TimelineHelpers.FrameItems(new[] { next });
285 return true;
286 }
287 }
288 return false;
289 }
290
291 public static bool NavigateRight(IEnumerable<TrackAsset> tracks)
292 {
293 if (!TrackHeadActive())
294 return false;
295
296 if (TryExpand(tracks))
297 return true;
298
299 return TrySelectFirstChild(tracks);
300 }
301
302 /// <summary>
303 /// Tries to expand from a list of tracks
304 /// </summary>
305 /// <param name="tracks"></param>
306 /// <returns>true if any expanded, false otherwise</returns>
307 public static bool TryExpand(IEnumerable<TrackAsset> tracks)
308 {
309 var didExpand = false;
310 foreach (TrackAsset track in tracks)
311 {
312 if (!track.GetChildTracks().Any())
313 continue;
314
315 if (track.IsCollapsed())
316 {
317 didExpand = true;
318 track.SetCollapsed(false);
319 }
320 }
321
322 if (didExpand)
323 {
324 TimelineEditor.window.treeView.Reload();
325 }
326
327 return didExpand;
328 }
329
330 /// <summary>
331 /// Tries to select the first clip from a list of tracks
332 /// </summary>
333 /// <param name="tracks"></param>
334 /// <returns>true if any expanded, false otherwise</returns>
335 public static bool TrySelectFirstChild(IEnumerable<TrackAsset> tracks)
336 {
337 foreach (var track in tracks)
338 {
339 //Try to navigate in group tracks first
340 if (track is GroupTrack)
341 {
342 if (track.GetChildTracks().Any())
343 {
344 SelectionManager.SelectOnly(track.GetChildTracks().First());
345 return true;
346 }
347 //Group tracks should not halt navigation
348 continue;
349 }
350 //if track is locked or has no clips, do nothing
351 if (track.lockedInHierarchy || !track.clips.Any())
352 continue;
353
354 var firstClip = track.clips.First();
355 SelectionManager.SelectOnly(firstClip);
356 TimelineHelpers.FrameItems(new ITimelineItem[] { firstClip.ToItem() });
357
358 return true;
359 }
360
361 return false;
362 }
363
364 public static bool SelectUpTrack(bool shift = false)
365 {
366 if (TrackHeadActive())
367 {
368 var prevTrack = PreviousTrack(SelectionManager.SelectedTracks().Last());
369 if (prevTrack != null)
370 {
371 if (shift)
372 {
373 if (SelectionManager.Contains(prevTrack))
374 SelectionManager.Remove(SelectionManager.SelectedTracks().Last());
375 SelectionManager.Add(prevTrack);
376 }
377 else
378 SelectionManager.SelectOnly(prevTrack);
379 FrameTrackHeader(GetVisibleTracks().First(x => x.track == prevTrack));
380 }
381 return true;
382 }
383 return false;
384 }
385
386 public static bool SelectUpItem()
387 {
388 if (ClipAreaActive())
389 {
390 var refItem = SelectionManager.SelectedItems().Last();
391 var prevTrack = refItem.parentTrack.PreviousTrack();
392 while (prevTrack != null)
393 {
394 var selectionItem = GetClosestItem(prevTrack, refItem);
395 if (selectionItem == null || prevTrack.lockedInHierarchy)
396 {
397 prevTrack = prevTrack.PreviousTrack();
398 continue;
399 }
400
401 SelectionManager.SelectOnly(selectionItem);
402 TimelineHelpers.FrameItems(new[] { selectionItem });
403 FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack));
404 break;
405 }
406 return true;
407 }
408
409 return false;
410 }
411
412 public static bool SelectDownTrack(bool shift = false)
413 {
414 if (TrackHeadActive())
415 {
416 var nextTrack = SelectionManager.SelectedTracks().Last().NextTrack();
417 if (nextTrack != null)
418 {
419 if (shift)
420 {
421 if (SelectionManager.Contains(nextTrack))
422 SelectionManager.Remove(SelectionManager.SelectedTracks().Last());
423 SelectionManager.Add(nextTrack);
424 }
425 else
426 SelectionManager.SelectOnly(nextTrack);
427
428 FrameTrackHeader(GetVisibleTracks().First(x => x.track == nextTrack));
429 }
430 return true;
431 }
432
433 return false;
434 }
435
436 public static bool SelectDownItem()
437 {
438 if (ClipAreaActive())
439 {
440 var refItem = SelectionManager.SelectedItems().Last();
441 var nextTrack = refItem.parentTrack.NextTrack();
442 while (nextTrack != null)
443 {
444 var selectionItem = GetClosestItem(nextTrack, refItem);
445 if (selectionItem == null || nextTrack.lockedInHierarchy)
446 {
447 nextTrack = nextTrack.NextTrack();
448 continue;
449 }
450
451 SelectionManager.SelectOnly(selectionItem);
452 TimelineHelpers.FrameItems(new[] { selectionItem });
453 FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack));
454 break;
455 }
456 return true;
457 }
458 return false;
459 }
460 }
461}