You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
446 lines
16 KiB
446 lines
16 KiB
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; |
|
} |
|
|
|
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; } |
|
} |
|
} |
|
}
|
|
|