454 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			454 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using UnityEngine;
 | |
| 
 | |
| #if UNITY_6000_2_OR_NEWER
 | |
| using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController<int>;
 | |
| using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
 | |
| using ITreeViewGUI = UnityEditor.IMGUI.Controls.ITreeViewGUI<int>;
 | |
| #else
 | |
| using UnityEditor.IMGUI.Controls;
 | |
| #endif
 | |
| 
 | |
| namespace UnityEditor.Timeline
 | |
| {
 | |
|     class TimelineTreeView : ITreeViewGUI
 | |
|     {
 | |
|         float m_FoldoutWidth;
 | |
|         Rect m_DraggingInsertionMarkerRect;
 | |
|         readonly TreeViewController m_TreeView;
 | |
| 
 | |
|         List<Rect> m_RowRects = new List<Rect>();
 | |
|         List<Rect> m_ExpandedRowRects = new List<Rect>();
 | |
| 
 | |
|         float m_MaxWidthOfRows;
 | |
|         readonly WindowState m_State;
 | |
| 
 | |
|         static readonly float kMinTrackHeight = 25.0f;
 | |
|         static readonly float kFoldOutOffset = 14.0f;
 | |
| 
 | |
|         static DirectorStyles m_Styles;
 | |
| 
 | |
|         public bool showInsertionMarker { get; set; }
 | |
|         public virtual float topRowMargin { get; private set; }
 | |
|         public virtual float bottomRowMargin { get; private set; }
 | |
| 
 | |
|         public TimelineTreeView(TimelineWindow sequencerWindow, TreeViewController treeView)
 | |
|         {
 | |
|             m_TreeView = treeView;
 | |
|             m_TreeView.useExpansionAnimation = true;
 | |
| 
 | |
|             m_TreeView.selectionChangedCallback += SelectionChangedCallback;
 | |
|             m_TreeView.contextClickOutsideItemsCallback += ContextClickOutsideItemsCallback;
 | |
|             m_TreeView.itemDoubleClickedCallback += ItemDoubleClickedCallback;
 | |
|             m_TreeView.contextClickItemCallback += ContextClickItemCallback;
 | |
| 
 | |
|             m_TreeView.SetConsumeKeyDownEvents(false);
 | |
|             m_Styles = DirectorStyles.Instance;
 | |
|             m_State = sequencerWindow.state;
 | |
| 
 | |
|             m_FoldoutWidth = DirectorStyles.Instance.foldout.fixedWidth;
 | |
|         }
 | |
| 
 | |
|         internal void ItemDoubleClickedCallback(int id)
 | |
|         {
 | |
|             var gui = m_TreeView.FindItem(id);
 | |
|             var trackGUI = gui as TimelineTrackGUI;
 | |
|             if (trackGUI != null)
 | |
|             {
 | |
|                 if (trackGUI.track == null || trackGUI.track.lockedInHierarchy)
 | |
|                     return;
 | |
|                 var selection = SelectionManager.SelectedItems().ToList();
 | |
|                 var items = ItemsUtils.GetItems(trackGUI.track).ToList();
 | |
|                 var addToSelection = !selection.SequenceEqual(items);
 | |
| 
 | |
|                 foreach (var i in items)
 | |
|                 {
 | |
|                     if (addToSelection)
 | |
|                         SelectionManager.Add(i);
 | |
|                     else
 | |
|                         SelectionManager.Remove(i);
 | |
|                 }
 | |
| 
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (gui is TimelineGroupGUI groupGUI)
 | |
|             {
 | |
|                 KeyboardNavigation.ToggleCollapseGroup(new[] { groupGUI.track });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void ContextClickOutsideItemsCallback()
 | |
|         {
 | |
|             SequencerContextMenu.ShowNewTracksContextMenu(null, m_State);
 | |
|             Event.current.Use();
 | |
|         }
 | |
| 
 | |
|         void ContextClickItemCallback(int id)
 | |
|         {
 | |
|             // may not occur if another menu is active
 | |
|             if (!m_TreeView.IsSelected(id))
 | |
|                 SelectionChangedCallback(new[] { id });
 | |
| 
 | |
|             SequencerContextMenu.ShowTrackContextMenu(Event.current.mousePosition);
 | |
| 
 | |
|             Event.current.Use();
 | |
|         }
 | |
| 
 | |
|         void SelectionChangedCallback(int[] ids)
 | |
|         {
 | |
|             if (Event.current.button == 1 && PickerUtils.TopmostPickedItem() is ISelectable)
 | |
|                 return;
 | |
| 
 | |
|             if (Event.current.command || Event.current.control || Event.current.shift)
 | |
|                 SelectionManager.UnSelectTracks();
 | |
|             else
 | |
|                 SelectionManager.Clear();
 | |
| 
 | |
|             foreach (var id in ids)
 | |
|             {
 | |
|                 var trackGUI = (TimelineTrackBaseGUI)m_TreeView.FindItem(id);
 | |
|                 SelectionManager.Add(trackGUI.track);
 | |
|             }
 | |
| 
 | |
|             m_State.GetWindow().Repaint();
 | |
|         }
 | |
| 
 | |
|         public void OnInitialize() { }
 | |
| 
 | |
|         public Rect GetRectForFraming(int row)
 | |
|         {
 | |
|             return GetRowRect(row, 1); // We ignore width by default when framing (only y scroll is affected)
 | |
|         }
 | |
| 
 | |
|         protected virtual Vector2 GetSizeOfRow(TreeViewItem item)
 | |
|         {
 | |
|             if (item.displayName == "root")
 | |
|                 return new Vector2(m_TreeView.GetTotalRect().width, 0.0f);
 | |
| 
 | |
|             var trackGroupGui = item as TimelineGroupGUI;
 | |
|             if (trackGroupGui != null)
 | |
|             {
 | |
|                 return new Vector2(m_TreeView.GetTotalRect().width, trackGroupGui.GetHeight(m_State));
 | |
|             }
 | |
| 
 | |
|             float height = TrackEditor.DefaultTrackHeight;
 | |
|             if (item.hasChildren && m_TreeView.data.IsExpanded(item))
 | |
|             {
 | |
|                 height = Mathf.Min(TrackEditor.DefaultTrackHeight, kMinTrackHeight);
 | |
|             }
 | |
| 
 | |
|             return new Vector2(m_TreeView.GetTotalRect().width, height);
 | |
|         }
 | |
| 
 | |
|         public virtual void BeginRowGUI()
 | |
|         {
 | |
|             if (m_TreeView.GetTotalRect().width != GetRowRect(0).width)
 | |
|             {
 | |
|                 CalculateRowRects();
 | |
|             }
 | |
| 
 | |
|             m_DraggingInsertionMarkerRect.x = -1;
 | |
| 
 | |
|             m_TreeView.SetSelection(SelectionManager.SelectedTrackGUI().Select(t => t.id).ToArray(), false);
 | |
|         }
 | |
| 
 | |
|         public virtual void EndRowGUI()
 | |
|         {
 | |
|             // Draw row marker when dragging
 | |
|             if (m_DraggingInsertionMarkerRect.x >= 0 && Event.current.type == EventType.Repaint)
 | |
|             {
 | |
|                 Rect insertionRect = m_DraggingInsertionMarkerRect;
 | |
|                 const float insertionHeight = 1.0f;
 | |
|                 insertionRect.height = insertionHeight;
 | |
| 
 | |
|                 if (m_TreeView.dragging.drawRowMarkerAbove)
 | |
|                     insertionRect.y -= insertionHeight * 0.5f + 2.0f;
 | |
|                 else
 | |
|                     insertionRect.y += m_DraggingInsertionMarkerRect.height - insertionHeight * 0.5f + 1.0f;
 | |
| 
 | |
|                 EditorGUI.DrawRect(insertionRect, Color.white);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public virtual void OnRowGUI(Rect rowRect, TreeViewItem item, int row, bool selected, bool focused)
 | |
|         {
 | |
|             using (new EditorGUI.DisabledScope(TimelineWindow.instance.currentMode.TrackState(TimelineWindow.instance.state) == TimelineModeGUIState.Disabled))
 | |
|             {
 | |
|                 var sqvi = (TimelineTrackBaseGUI)item;
 | |
|                 sqvi.treeViewToWindowTransformation = m_TreeView.GetTotalRect().position - m_TreeView.state.scrollPos;
 | |
| 
 | |
|                 // this may be called because an encompassing parent is visible
 | |
|                 if (!sqvi.visibleExpanded)
 | |
|                     return;
 | |
| 
 | |
|                 Rect headerRect = rowRect;
 | |
|                 Rect contentRect = rowRect;
 | |
| 
 | |
|                 headerRect.width = m_State.sequencerHeaderWidth - 2.0f;
 | |
|                 contentRect.xMin += m_State.sequencerHeaderWidth;
 | |
|                 contentRect.width = rowRect.width - m_State.sequencerHeaderWidth - 1.0f;
 | |
| 
 | |
|                 Rect foldoutRect = rowRect;
 | |
| 
 | |
|                 var indent = GetFoldoutIndent(item);
 | |
|                 var headerRectWithIndent = headerRect;
 | |
|                 headerRectWithIndent.xMin = indent;
 | |
|                 var rowRectWithIndent = new Rect(rowRect.x + indent, rowRect.y, rowRect.width - indent, rowRect.height);
 | |
|                 sqvi.Draw(headerRectWithIndent, contentRect, m_State);
 | |
|                 sqvi.DrawInsertionMarkers(rowRectWithIndent);
 | |
| 
 | |
|                 if (Event.current.type == EventType.Repaint)
 | |
|                 {
 | |
|                     m_State.spacePartitioner.AddBounds(sqvi);
 | |
| 
 | |
|                     // Show marker below this Item
 | |
|                     if (showInsertionMarker)
 | |
|                     {
 | |
|                         if (m_TreeView.dragging != null && m_TreeView.dragging.GetRowMarkerControlID() == TreeViewController.GetItemControlID(item))
 | |
|                             m_DraggingInsertionMarkerRect = rowRectWithIndent;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // Draw foldout (after text content above to ensure drop down icon is rendered above selection highlight)
 | |
|                 DrawFoldout(item, foldoutRect, indent);
 | |
| 
 | |
|                 sqvi.ClearDrawFlags();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DrawFoldout(TreeViewItem item, Rect foldoutRect, float indent)
 | |
|         {
 | |
|             var showFoldout = m_TreeView.data.IsExpandable(item);
 | |
|             if (showFoldout)
 | |
|             {
 | |
|                 foldoutRect.x = indent - kFoldOutOffset;
 | |
|                 foldoutRect.width = m_FoldoutWidth;
 | |
|                 EditorGUI.BeginChangeCheck();
 | |
|                 float foldoutIconHeight = DirectorStyles.Instance.foldout.fixedHeight;
 | |
|                 foldoutRect.y += foldoutIconHeight / 2.0f;
 | |
|                 foldoutRect.height = foldoutIconHeight;
 | |
| 
 | |
|                 if (foldoutRect.xMax > m_State.sequencerHeaderWidth)
 | |
|                     return;
 | |
| 
 | |
|                 //Override Disable state for TrakGroup toggle button to expand/collapse group.
 | |
|                 bool previousEnableState = GUI.enabled;
 | |
|                 GUI.enabled = true;
 | |
|                 bool newExpandedValue = GUI.Toggle(foldoutRect, m_TreeView.data.IsExpanded(item), GUIContent.none, m_Styles.foldout);
 | |
|                 GUI.enabled = previousEnableState;
 | |
| 
 | |
|                 if (EditorGUI.EndChangeCheck())
 | |
|                 {
 | |
|                     if (Event.current.alt)
 | |
|                         m_TreeView.data.SetExpandedWithChildren(item, newExpandedValue);
 | |
|                     else
 | |
|                         m_TreeView.data.SetExpanded(item, newExpandedValue);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public Rect GetRenameRect(Rect rowRect, int row, TreeViewItem item)
 | |
|         {
 | |
|             return rowRect;
 | |
|         }
 | |
| 
 | |
|         public void BeginPingItem(TreeViewItem item, float topPixelOfRow, float availableWidth) { }
 | |
|         public void EndPingItem() { }
 | |
| 
 | |
|         public Rect GetRowRect(int row, float rowWidth)
 | |
|         {
 | |
|             return GetRowRect(row);
 | |
|         }
 | |
| 
 | |
|         public Rect GetRowRect(int row)
 | |
|         {
 | |
|             if (m_RowRects.Count == 0)
 | |
|                 return new Rect();
 | |
| 
 | |
|             if (row >= m_RowRects.Count)
 | |
|                 return new Rect();
 | |
| 
 | |
|             return m_RowRects[row];
 | |
|         }
 | |
| 
 | |
|         static float GetSpacing(TreeViewItem item)
 | |
|         {
 | |
|             var trackBase = item as TimelineTrackBaseGUI;
 | |
|             if (trackBase != null)
 | |
|                 return trackBase.GetVerticalSpacingBetweenTracks();
 | |
| 
 | |
|             return 3.0f;
 | |
|         }
 | |
| 
 | |
|         public void CalculateRowRects()
 | |
|         {
 | |
|             if (m_TreeView.isSearching)
 | |
|                 return;
 | |
| 
 | |
|             const float startY = 6.0f;
 | |
|             IList<TreeViewItem> rows = m_TreeView.data.GetRows();
 | |
|             m_RowRects = new List<Rect>(rows.Count);
 | |
|             m_ExpandedRowRects = new List<Rect>(rows.Count);
 | |
| 
 | |
|             float curY = startY;
 | |
|             m_MaxWidthOfRows = 1f;
 | |
| 
 | |
|             // first pass compute the row rects
 | |
|             for (int i = 0; i < rows.Count; ++i)
 | |
|             {
 | |
|                 var item = rows[i];
 | |
| 
 | |
|                 if (i != 0)
 | |
|                     curY += GetSpacing(item);
 | |
| 
 | |
|                 Vector2 rowSize = GetSizeOfRow(item);
 | |
|                 m_RowRects.Add(new Rect(0, curY, rowSize.x, rowSize.y));
 | |
|                 m_ExpandedRowRects.Add(m_RowRects[i]);
 | |
| 
 | |
|                 curY += rowSize.y;
 | |
| 
 | |
|                 if (rowSize.x > m_MaxWidthOfRows)
 | |
|                     m_MaxWidthOfRows = rowSize.x;
 | |
| 
 | |
|                 // updated the expanded state
 | |
|                 var groupGUI = item as TimelineGroupGUI;
 | |
|                 if (groupGUI != null)
 | |
|                     groupGUI.SetExpanded(m_TreeView.data.IsExpanded(item));
 | |
|             }
 | |
| 
 | |
|             float halfHeight = halfDropBetweenHeight;
 | |
|             const float kGroupPad = 1.0f;
 | |
|             const float kSkinPadding = 5.0f * 0.6f;
 | |
|             // work bottom up and compute visible regions for groups
 | |
|             for (int i = rows.Count - 1; i > 0; i--)
 | |
|             {
 | |
|                 float height = 0;
 | |
|                 TimelineTrackBaseGUI item = (TimelineTrackBaseGUI)rows[i];
 | |
|                 if (item.isExpanded && item.children != null && item.children.Count > 0)
 | |
|                 {
 | |
|                     for (var j = 0; j < item.children.Count; j++)
 | |
|                     {
 | |
|                         var child = item.children[j];
 | |
|                         int index = rows.IndexOf(child);
 | |
|                         if (index > i)
 | |
|                             height += m_ExpandedRowRects[index].height + kSkinPadding;
 | |
|                     }
 | |
| 
 | |
|                     height += kGroupPad;
 | |
|                 }
 | |
|                 m_ExpandedRowRects[i] = new Rect(m_RowRects[i].x, m_RowRects[i].y, m_RowRects[i].width, m_RowRects[i].height + height);
 | |
| 
 | |
|                 var groupGUI = item as TimelineGroupGUI;
 | |
|                 if (groupGUI != null)
 | |
|                 {
 | |
|                     var spacing = GetSpacing(item) + 1;
 | |
|                     groupGUI.expandedRect = m_ExpandedRowRects[i];
 | |
|                     groupGUI.rowRect = m_RowRects[i];
 | |
|                     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));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public virtual bool BeginRename(TreeViewItem item, float delay)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         public virtual void EndRename() { }
 | |
| 
 | |
|         protected virtual float GetFoldoutIndent(TreeViewItem item)
 | |
|         {
 | |
|             // Ignore depth when showing search results
 | |
|             if (item.depth <= 1 || m_TreeView.isSearching)
 | |
|                 return DirectorStyles.kBaseIndent;
 | |
| 
 | |
|             int depth = item.depth;
 | |
|             var trackGUI = item as TimelineTrackGUI;
 | |
| 
 | |
|             // first level subtracks are not indented
 | |
|             if (trackGUI != null && trackGUI.track != null && trackGUI.track.isSubTrack)
 | |
|                 depth--;
 | |
| 
 | |
|             return depth * DirectorStyles.kBaseIndent;
 | |
|         }
 | |
| 
 | |
|         public virtual float GetContentIndent(TreeViewItem item)
 | |
|         {
 | |
|             return GetFoldoutIndent(item);
 | |
|         }
 | |
| 
 | |
|         public int GetNumRowsOnPageUpDown(TreeViewItem fromItem, bool pageUp, float heightOfTreeView)
 | |
|         {
 | |
|             return (int)Mathf.Floor(heightOfTreeView / 30); // return something
 | |
|         }
 | |
| 
 | |
|         // Should return the row number of the first and last row thats fits in the pixel rect defined by top and height
 | |
|         public void GetFirstAndLastRowVisible(out int firstRowVisible, out int lastRowVisible)
 | |
|         {
 | |
|             int rowCount = m_TreeView.data.rowCount;
 | |
|             if (rowCount == 0)
 | |
|             {
 | |
|                 firstRowVisible = lastRowVisible = -1;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (rowCount != m_ExpandedRowRects.Count)
 | |
|             {
 | |
|                 Debug.LogError("Mismatch in state: rows vs cached rects. Did you remember to hook up: dataSource.onVisibleRowsChanged += gui.CalculateRowRects ?");
 | |
|                 CalculateRowRects();
 | |
|             }
 | |
| 
 | |
|             float topPixel = m_TreeView.state.scrollPos.y;
 | |
|             float heightInPixels = m_TreeView.GetTotalRect().height;
 | |
| 
 | |
|             int firstVisible = -1;
 | |
|             int lastVisible = -1;
 | |
| 
 | |
|             Rect visibleRect = new Rect(0, topPixel, m_ExpandedRowRects[0].width, heightInPixels);
 | |
|             for (int i = 0; i < m_ExpandedRowRects.Count; ++i)
 | |
|             {
 | |
|                 bool visible = visibleRect.Overlaps(m_ExpandedRowRects[i]);
 | |
|                 if (visible)
 | |
|                 {
 | |
|                     if (firstVisible == -1)
 | |
|                         firstVisible = i;
 | |
|                     lastVisible = i;
 | |
|                 }
 | |
| 
 | |
|                 TimelineTrackBaseGUI gui = m_TreeView.data.GetItem(i) as TimelineTrackBaseGUI;
 | |
|                 if (gui != null)
 | |
|                 {
 | |
|                     gui.visibleExpanded = visible;
 | |
|                     gui.visibleRow = visibleRect.Overlaps(m_RowRects[i]);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (firstVisible != -1 && lastVisible != -1)
 | |
|             {
 | |
|                 firstRowVisible = firstVisible;
 | |
|                 lastRowVisible = lastVisible;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 firstRowVisible = 0;
 | |
|                 lastRowVisible = rowCount - 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public Vector2 GetTotalSize()
 | |
|         {
 | |
|             if (m_RowRects.Count == 0)
 | |
|                 return new Vector2(0, 0);
 | |
| 
 | |
|             return new Vector2(m_MaxWidthOfRows, m_RowRects[m_RowRects.Count - 1].yMax);
 | |
|         }
 | |
| 
 | |
|         public virtual float halfDropBetweenHeight
 | |
|         {
 | |
|             get { return 8f; }
 | |
|         }
 | |
|     }
 | |
| }
 |