440 lines
16 KiB
C#
440 lines
16 KiB
C#
![]() |
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEditor.IMGUI.Controls;
|
||
|
using UnityEngine;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
void ItemDoubleClickedCallback(int id)
|
||
|
{
|
||
|
var trackGUI = m_TreeView.FindItem(id) as TimelineTrackGUI;
|
||
|
if (trackGUI == null)
|
||
|
return;
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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; }
|
||
|
}
|
||
|
}
|
||
|
}
|