A game about forced loneliness, made by TACStudios
at master 446 lines 16 kB view raw
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}