Singularity/Library/PackageCache/com.unity.timeline@1.6.4/Editor/treeview/TimelineTreeView.cs

447 lines
16 KiB
C#
Raw Normal View History

2024-05-06 14:45:45 -04:00
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; }
}
}
}