A game about forced loneliness, made by TACStudios
1using System.Collections.Generic;
2using System.Linq;
3using UnityEditor.IMGUI.Controls;
4using UnityEngine;
5
6namespace UnityEditor.Timeline
7{
8 class TimelineTreeView : ITreeViewGUI
9 {
10 float m_FoldoutWidth;
11 Rect m_DraggingInsertionMarkerRect;
12 readonly TreeViewController m_TreeView;
13
14 List<Rect> m_RowRects = new List<Rect>();
15 List<Rect> m_ExpandedRowRects = new List<Rect>();
16
17 float m_MaxWidthOfRows;
18 readonly WindowState m_State;
19
20 static readonly float kMinTrackHeight = 25.0f;
21 static readonly float kFoldOutOffset = 14.0f;
22
23 static DirectorStyles m_Styles;
24
25 public bool showInsertionMarker { get; set; }
26 public virtual float topRowMargin { get; private set; }
27 public virtual float bottomRowMargin { get; private set; }
28
29 public TimelineTreeView(TimelineWindow sequencerWindow, TreeViewController treeView)
30 {
31 m_TreeView = treeView;
32 m_TreeView.useExpansionAnimation = true;
33
34 m_TreeView.selectionChangedCallback += SelectionChangedCallback;
35 m_TreeView.contextClickOutsideItemsCallback += ContextClickOutsideItemsCallback;
36 m_TreeView.itemDoubleClickedCallback += ItemDoubleClickedCallback;
37 m_TreeView.contextClickItemCallback += ContextClickItemCallback;
38
39 m_TreeView.SetConsumeKeyDownEvents(false);
40 m_Styles = DirectorStyles.Instance;
41 m_State = sequencerWindow.state;
42
43 m_FoldoutWidth = DirectorStyles.Instance.foldout.fixedWidth;
44 }
45
46 internal void ItemDoubleClickedCallback(int id)
47 {
48 var gui = m_TreeView.FindItem(id);
49 var trackGUI = gui as TimelineTrackGUI;
50 if (trackGUI != null)
51 {
52 if (trackGUI.track == null || trackGUI.track.lockedInHierarchy)
53 return;
54 var selection = SelectionManager.SelectedItems().ToList();
55 var items = ItemsUtils.GetItems(trackGUI.track).ToList();
56 var addToSelection = !selection.SequenceEqual(items);
57
58 foreach (var i in items)
59 {
60 if (addToSelection)
61 SelectionManager.Add(i);
62 else
63 SelectionManager.Remove(i);
64 }
65
66 return;
67 }
68
69 if (gui is TimelineGroupGUI groupGUI)
70 {
71 KeyboardNavigation.ToggleCollapseGroup(new[] { groupGUI.track });
72 }
73 }
74
75 void ContextClickOutsideItemsCallback()
76 {
77 SequencerContextMenu.ShowNewTracksContextMenu(null, m_State);
78 Event.current.Use();
79 }
80
81 void ContextClickItemCallback(int id)
82 {
83 // may not occur if another menu is active
84 if (!m_TreeView.IsSelected(id))
85 SelectionChangedCallback(new[] { id });
86
87 SequencerContextMenu.ShowTrackContextMenu(Event.current.mousePosition);
88
89 Event.current.Use();
90 }
91
92 void SelectionChangedCallback(int[] ids)
93 {
94 if (Event.current.button == 1 && PickerUtils.TopmostPickedItem() is ISelectable)
95 return;
96
97 if (Event.current.command || Event.current.control || Event.current.shift)
98 SelectionManager.UnSelectTracks();
99 else
100 SelectionManager.Clear();
101
102 foreach (var id in ids)
103 {
104 var trackGUI = (TimelineTrackBaseGUI)m_TreeView.FindItem(id);
105 SelectionManager.Add(trackGUI.track);
106 }
107
108 m_State.GetWindow().Repaint();
109 }
110
111 public void OnInitialize() { }
112
113 public Rect GetRectForFraming(int row)
114 {
115 return GetRowRect(row, 1); // We ignore width by default when framing (only y scroll is affected)
116 }
117
118 protected virtual Vector2 GetSizeOfRow(TreeViewItem item)
119 {
120 if (item.displayName == "root")
121 return new Vector2(m_TreeView.GetTotalRect().width, 0.0f);
122
123 var trackGroupGui = item as TimelineGroupGUI;
124 if (trackGroupGui != null)
125 {
126 return new Vector2(m_TreeView.GetTotalRect().width, trackGroupGui.GetHeight(m_State));
127 }
128
129 float height = TrackEditor.DefaultTrackHeight;
130 if (item.hasChildren && m_TreeView.data.IsExpanded(item))
131 {
132 height = Mathf.Min(TrackEditor.DefaultTrackHeight, kMinTrackHeight);
133 }
134
135 return new Vector2(m_TreeView.GetTotalRect().width, height);
136 }
137
138 public virtual void BeginRowGUI()
139 {
140 if (m_TreeView.GetTotalRect().width != GetRowRect(0).width)
141 {
142 CalculateRowRects();
143 }
144
145 m_DraggingInsertionMarkerRect.x = -1;
146
147 m_TreeView.SetSelection(SelectionManager.SelectedTrackGUI().Select(t => t.id).ToArray(), false);
148 }
149
150 public virtual void EndRowGUI()
151 {
152 // Draw row marker when dragging
153 if (m_DraggingInsertionMarkerRect.x >= 0 && Event.current.type == EventType.Repaint)
154 {
155 Rect insertionRect = m_DraggingInsertionMarkerRect;
156 const float insertionHeight = 1.0f;
157 insertionRect.height = insertionHeight;
158
159 if (m_TreeView.dragging.drawRowMarkerAbove)
160 insertionRect.y -= insertionHeight * 0.5f + 2.0f;
161 else
162 insertionRect.y += m_DraggingInsertionMarkerRect.height - insertionHeight * 0.5f + 1.0f;
163
164 EditorGUI.DrawRect(insertionRect, Color.white);
165 }
166 }
167
168 public virtual void OnRowGUI(Rect rowRect, TreeViewItem item, int row, bool selected, bool focused)
169 {
170 using (new EditorGUI.DisabledScope(TimelineWindow.instance.currentMode.TrackState(TimelineWindow.instance.state) == TimelineModeGUIState.Disabled))
171 {
172 var sqvi = (TimelineTrackBaseGUI)item;
173 sqvi.treeViewToWindowTransformation = m_TreeView.GetTotalRect().position - m_TreeView.state.scrollPos;
174
175 // this may be called because an encompassing parent is visible
176 if (!sqvi.visibleExpanded)
177 return;
178
179 Rect headerRect = rowRect;
180 Rect contentRect = rowRect;
181
182 headerRect.width = m_State.sequencerHeaderWidth - 2.0f;
183 contentRect.xMin += m_State.sequencerHeaderWidth;
184 contentRect.width = rowRect.width - m_State.sequencerHeaderWidth - 1.0f;
185
186 Rect foldoutRect = rowRect;
187
188 var indent = GetFoldoutIndent(item);
189 var headerRectWithIndent = headerRect;
190 headerRectWithIndent.xMin = indent;
191 var rowRectWithIndent = new Rect(rowRect.x + indent, rowRect.y, rowRect.width - indent, rowRect.height);
192 sqvi.Draw(headerRectWithIndent, contentRect, m_State);
193 sqvi.DrawInsertionMarkers(rowRectWithIndent);
194
195 if (Event.current.type == EventType.Repaint)
196 {
197 m_State.spacePartitioner.AddBounds(sqvi);
198
199 // Show marker below this Item
200 if (showInsertionMarker)
201 {
202 if (m_TreeView.dragging != null && m_TreeView.dragging.GetRowMarkerControlID() == TreeViewController.GetItemControlID(item))
203 m_DraggingInsertionMarkerRect = rowRectWithIndent;
204 }
205 }
206
207 // Draw foldout (after text content above to ensure drop down icon is rendered above selection highlight)
208 DrawFoldout(item, foldoutRect, indent);
209
210 sqvi.ClearDrawFlags();
211 }
212 }
213
214 void DrawFoldout(TreeViewItem item, Rect foldoutRect, float indent)
215 {
216 var showFoldout = m_TreeView.data.IsExpandable(item);
217 if (showFoldout)
218 {
219 foldoutRect.x = indent - kFoldOutOffset;
220 foldoutRect.width = m_FoldoutWidth;
221 EditorGUI.BeginChangeCheck();
222 float foldoutIconHeight = DirectorStyles.Instance.foldout.fixedHeight;
223 foldoutRect.y += foldoutIconHeight / 2.0f;
224 foldoutRect.height = foldoutIconHeight;
225
226 if (foldoutRect.xMax > m_State.sequencerHeaderWidth)
227 return;
228
229 //Override Disable state for TrakGroup toggle button to expand/collapse group.
230 bool previousEnableState = GUI.enabled;
231 GUI.enabled = true;
232 bool newExpandedValue = GUI.Toggle(foldoutRect, m_TreeView.data.IsExpanded(item), GUIContent.none, m_Styles.foldout);
233 GUI.enabled = previousEnableState;
234
235 if (EditorGUI.EndChangeCheck())
236 {
237 if (Event.current.alt)
238 m_TreeView.data.SetExpandedWithChildren(item, newExpandedValue);
239 else
240 m_TreeView.data.SetExpanded(item, newExpandedValue);
241 }
242 }
243 }
244
245 public Rect GetRenameRect(Rect rowRect, int row, TreeViewItem item)
246 {
247 return rowRect;
248 }
249
250 public void BeginPingItem(TreeViewItem item, float topPixelOfRow, float availableWidth) { }
251 public void EndPingItem() { }
252
253 public Rect GetRowRect(int row, float rowWidth)
254 {
255 return GetRowRect(row);
256 }
257
258 public Rect GetRowRect(int row)
259 {
260 if (m_RowRects.Count == 0)
261 return new Rect();
262
263 if (row >= m_RowRects.Count)
264 return new Rect();
265
266 return m_RowRects[row];
267 }
268
269 static float GetSpacing(TreeViewItem item)
270 {
271 var trackBase = item as TimelineTrackBaseGUI;
272 if (trackBase != null)
273 return trackBase.GetVerticalSpacingBetweenTracks();
274
275 return 3.0f;
276 }
277
278 public void CalculateRowRects()
279 {
280 if (m_TreeView.isSearching)
281 return;
282
283 const float startY = 6.0f;
284 IList<TreeViewItem> rows = m_TreeView.data.GetRows();
285 m_RowRects = new List<Rect>(rows.Count);
286 m_ExpandedRowRects = new List<Rect>(rows.Count);
287
288 float curY = startY;
289 m_MaxWidthOfRows = 1f;
290
291 // first pass compute the row rects
292 for (int i = 0; i < rows.Count; ++i)
293 {
294 var item = rows[i];
295
296 if (i != 0)
297 curY += GetSpacing(item);
298
299 Vector2 rowSize = GetSizeOfRow(item);
300 m_RowRects.Add(new Rect(0, curY, rowSize.x, rowSize.y));
301 m_ExpandedRowRects.Add(m_RowRects[i]);
302
303 curY += rowSize.y;
304
305 if (rowSize.x > m_MaxWidthOfRows)
306 m_MaxWidthOfRows = rowSize.x;
307
308 // updated the expanded state
309 var groupGUI = item as TimelineGroupGUI;
310 if (groupGUI != null)
311 groupGUI.SetExpanded(m_TreeView.data.IsExpanded(item));
312 }
313
314 float halfHeight = halfDropBetweenHeight;
315 const float kGroupPad = 1.0f;
316 const float kSkinPadding = 5.0f * 0.6f;
317 // work bottom up and compute visible regions for groups
318 for (int i = rows.Count - 1; i > 0; i--)
319 {
320 float height = 0;
321 TimelineTrackBaseGUI item = (TimelineTrackBaseGUI)rows[i];
322 if (item.isExpanded && item.children != null && item.children.Count > 0)
323 {
324 for (var j = 0; j < item.children.Count; j++)
325 {
326 var child = item.children[j];
327 int index = rows.IndexOf(child);
328 if (index > i)
329 height += m_ExpandedRowRects[index].height + kSkinPadding;
330 }
331
332 height += kGroupPad;
333 }
334 m_ExpandedRowRects[i] = new Rect(m_RowRects[i].x, m_RowRects[i].y, m_RowRects[i].width, m_RowRects[i].height + height);
335
336 var groupGUI = item as TimelineGroupGUI;
337 if (groupGUI != null)
338 {
339 var spacing = GetSpacing(item) + 1;
340 groupGUI.expandedRect = m_ExpandedRowRects[i];
341 groupGUI.rowRect = m_RowRects[i];
342 groupGUI.dropRect = new Rect(m_RowRects[i].x, m_RowRects[i].y - spacing, m_RowRects[i].width, m_RowRects[i].height + Mathf.Max(halfHeight, spacing));
343 }
344 }
345 }
346
347 public virtual bool BeginRename(TreeViewItem item, float delay)
348 {
349 return false;
350 }
351
352 public virtual void EndRename() { }
353
354 protected virtual float GetFoldoutIndent(TreeViewItem item)
355 {
356 // Ignore depth when showing search results
357 if (item.depth <= 1 || m_TreeView.isSearching)
358 return DirectorStyles.kBaseIndent;
359
360 int depth = item.depth;
361 var trackGUI = item as TimelineTrackGUI;
362
363 // first level subtracks are not indented
364 if (trackGUI != null && trackGUI.track != null && trackGUI.track.isSubTrack)
365 depth--;
366
367 return depth * DirectorStyles.kBaseIndent;
368 }
369
370 public virtual float GetContentIndent(TreeViewItem item)
371 {
372 return GetFoldoutIndent(item);
373 }
374
375 public int GetNumRowsOnPageUpDown(TreeViewItem fromItem, bool pageUp, float heightOfTreeView)
376 {
377 return (int)Mathf.Floor(heightOfTreeView / 30); // return something
378 }
379
380 // Should return the row number of the first and last row thats fits in the pixel rect defined by top and height
381 public void GetFirstAndLastRowVisible(out int firstRowVisible, out int lastRowVisible)
382 {
383 int rowCount = m_TreeView.data.rowCount;
384 if (rowCount == 0)
385 {
386 firstRowVisible = lastRowVisible = -1;
387 return;
388 }
389
390 if (rowCount != m_ExpandedRowRects.Count)
391 {
392 Debug.LogError("Mismatch in state: rows vs cached rects. Did you remember to hook up: dataSource.onVisibleRowsChanged += gui.CalculateRowRects ?");
393 CalculateRowRects();
394 }
395
396 float topPixel = m_TreeView.state.scrollPos.y;
397 float heightInPixels = m_TreeView.GetTotalRect().height;
398
399 int firstVisible = -1;
400 int lastVisible = -1;
401
402 Rect visibleRect = new Rect(0, topPixel, m_ExpandedRowRects[0].width, heightInPixels);
403 for (int i = 0; i < m_ExpandedRowRects.Count; ++i)
404 {
405 bool visible = visibleRect.Overlaps(m_ExpandedRowRects[i]);
406 if (visible)
407 {
408 if (firstVisible == -1)
409 firstVisible = i;
410 lastVisible = i;
411 }
412
413 TimelineTrackBaseGUI gui = m_TreeView.data.GetItem(i) as TimelineTrackBaseGUI;
414 if (gui != null)
415 {
416 gui.visibleExpanded = visible;
417 gui.visibleRow = visibleRect.Overlaps(m_RowRects[i]);
418 }
419 }
420
421 if (firstVisible != -1 && lastVisible != -1)
422 {
423 firstRowVisible = firstVisible;
424 lastRowVisible = lastVisible;
425 }
426 else
427 {
428 firstRowVisible = 0;
429 lastRowVisible = rowCount - 1;
430 }
431 }
432
433 public Vector2 GetTotalSize()
434 {
435 if (m_RowRects.Count == 0)
436 return new Vector2(0, 0);
437
438 return new Vector2(m_MaxWidthOfRows, m_RowRects[m_RowRects.Count - 1].yMax);
439 }
440
441 public virtual float halfDropBetweenHeight
442 {
443 get { return 8f; }
444 }
445 }
446}