using System; using System.Collections.Generic; using System.Linq; using UnityEditor.IMGUI.Controls; using UnityEngine; using UnityEngine.Timeline; namespace UnityEditor.Timeline { class TimelineGroupGUI : TimelineTrackBaseGUI { protected DirectorStyles m_Styles; protected Rect m_TreeViewRect = new Rect(0, 0, 0, 0); protected GUIContent m_ProblemIcon = new GUIContent(); bool m_MustRecomputeUnions = true; int m_GroupDepth; readonly bool m_IsReferencedTrack; readonly List m_Unions = new List(); public override Rect boundingRect { get { return ToWindowSpace(m_TreeViewRect); } } public Rect ToWindowSpace(Rect localRect) { localRect.position += treeViewToWindowTransformation; return localRect; } public override bool expandable { get { return !m_IsRoot; } } // The expanded rectangle (contains children) as calculated by the the tree gui public Rect expandedRect { get; set; } // The row rectangle (header only) as calculated by the tree gui public Rect rowRect { get; set; } // the drop rectangle as set by the tree gui when targetted by a drag and drop public Rect dropRect { get; set; } public TimelineGroupGUI(TreeViewController treeview, TimelineTreeViewGUI treeviewGUI, int id, int depth, TreeViewItem parent, string displayName, TrackAsset trackAsset, bool isRoot) : base(id, depth, parent, displayName, trackAsset, treeview, treeviewGUI) { m_Styles = DirectorStyles.Instance; m_IsRoot = isRoot; var trackPath = AssetDatabase.GetAssetPath(trackAsset); var sequencePath = AssetDatabase.GetAssetPath(treeviewGUI.TimelineWindow.state.editSequence.asset); if (trackPath != sequencePath) m_IsReferencedTrack = true; m_GroupDepth = CalculateGroupDepth(parent); } public virtual float GetHeight(WindowState state) { // group tracks don't scale in height return TrackEditor.DefaultTrackHeight; } public override void OnGraphRebuilt() { } static int CalculateGroupDepth(TreeViewItem parent) { int depth = 0; bool done = false; do { var gui = parent as TimelineGroupGUI; if (gui == null || gui.track == null) done = true; else { if (gui.track is GroupTrack) depth++; parent = parent.parent; } } while (!done); return depth; } void DrawTrackButtons(Rect headerRect, WindowState state) { const float buttonSize = WindowConstants.trackHeaderButtonSize; const float padding = WindowConstants.trackHeaderButtonPadding; var buttonRect = new Rect(headerRect.xMax - buttonSize - padding, headerRect.y + ((headerRect.height - buttonSize) / 2f), buttonSize, buttonSize); if (GUI.Button(buttonRect, EditorGUIUtility.IconContent("CreateAddNew"), m_Styles.trackGroupAddButton)) { // the drop down will apply to all selected tracks if (!SelectionManager.Contains(track)) { SelectionManager.Clear(); SelectionManager.Add(track); } SequencerContextMenu.ShowNewTracksContextMenu(SelectionManager.SelectedTracks().ToArray(), TimelineWindow.state, buttonRect); } buttonRect.x -= buttonSize; var suitePadding = DrawButtonSuite(2, ref buttonRect); DrawMuteButton(buttonRect, state); buttonRect.x -= buttonSize + padding; DrawLockButton(buttonRect, state); buttonRect.x -= suitePadding; } public void SetExpanded(bool expanded) { var collapseChanged = expanded != isExpanded; isExpanded = expanded; if (collapseChanged) { track.SetCollapsed(!expanded); m_MustRecomputeUnions = true; } } public override void Draw(Rect headerRect, Rect contentRect, WindowState state) { if (track == null || m_IsRoot) return; if (m_MustRecomputeUnions) RecomputeRectUnions(); if (depth == 1) Graphics.DrawBackgroundRect(state, headerRect); var background = headerRect; background.height = expandedRect.height; var groupColor = TrackResourceCache.GetTrackColor(track); m_TreeViewRect = contentRect; var col = groupColor; var isSelected = SelectionManager.Contains(track); if (isSelected) col = DirectorStyles.Instance.customSkin.colorSelection; else if (isDropTarget) col = DirectorStyles.Instance.customSkin.colorDropTarget; else { if (m_GroupDepth % 2 == 1) { float h, s, v; Color.RGBToHSV(col, out h, out s, out v); v += 0.06f; col = Color.HSVToRGB(h, s, v); } } if (background.width > 0) { using (new GUIColorOverride(col)) GUI.Box(background, GUIContent.none, m_Styles.groupBackground); } var trackRectBackground = headerRect; trackRectBackground.xMin += background.width; trackRectBackground.width = contentRect.width; trackRectBackground.height = background.height; if (isSelected) { col = state.IsEditingASubTimeline() ? m_Styles.customSkin.colorTrackSubSequenceBackgroundSelected : m_Styles.customSkin.colorTrackBackgroundSelected; } else { col = m_Styles.customSkin.colorGroupTrackBackground; } EditorGUI.DrawRect(trackRectBackground, col); if (!isExpanded && children != null && children.Count > 0) { var collapsedTrackRect = contentRect; foreach (var u in m_Unions) u.Draw(collapsedTrackRect, state); } using (new GUIGroupScope(headerRect)) { var groupRect = new Rect(0, 0, headerRect.width, headerRect.height); DrawName(groupRect, isSelected); DrawTrackButtons(groupRect, state); } if (IsTrackRecording(state)) { using (new GUIColorOverride(DirectorStyles.Instance.customSkin.colorTrackBackgroundRecording)) GUI.Label(background, GUIContent.none, m_Styles.displayBackground); } // is this a referenced track? if (m_IsReferencedTrack) { var refRect = contentRect; refRect.x = state.timeAreaRect.xMax - 20.0f; refRect.y += 5.0f; refRect.width = 30.0f; GUI.Label(refRect, DirectorStyles.referenceTrackLabel, EditorStyles.label); } var bgRect = contentRect; if (track as GroupTrack != null || AllChildrenMuted(this)) bgRect.height = expandedRect.height; DrawTrackState(contentRect, bgRect, track); } void DrawName(Rect rect, bool isSelected) { var labelRect = rect; labelRect.xMin += 20; var actorName = track != null ? track.name : "missing"; labelRect.width = m_Styles.groupFont.CalcSize(new GUIContent(actorName)).x; labelRect.width = Math.Max(labelRect.width, 50.0f); // if we aren't bound to anything, we show a text field that allows to rename the actor // otherwise we show a ObjectField to allow binding to a go if (track != null && track is GroupTrack) { var textColor = m_Styles.groupFont.normal.textColor; if (isSelected) textColor = Color.white; string newName; EditorGUI.BeginChangeCheck(); using (new StyleNormalColorOverride(m_Styles.groupFont, textColor)) { newName = EditorGUI.DelayedTextField(labelRect, GUIContent.none, track.GetInstanceID(), track.name, m_Styles.groupFont); } if (EditorGUI.EndChangeCheck() && !string.IsNullOrEmpty(newName)) { track.SetNameWithUndo(newName); displayName = track.name; } } } protected bool IsSubTrack() { if (track == null) return false; var parentTrack = track.parent as TrackAsset; if (parentTrack == null) return false; return parentTrack.GetType() != typeof(GroupTrack); } protected TrackAsset ParentTrack() { if (IsSubTrack()) return track.parent as TrackAsset; return null; } // is there currently a recording track bool IsTrackRecording(WindowState state) { if (!state.recording) return false; if (track.GetType() != typeof(GroupTrack)) return false; return state.GetArmedTrack(track) != null; } void RecomputeRectUnions() { m_MustRecomputeUnions = false; m_Unions.Clear(); if (children == null) return; foreach (var c in children.OfType()) { c.RebuildGUICacheIfNecessary(); m_Unions.AddRange(TimelineClipUnion.Build(c.clips)); } } static bool AllChildrenMuted(TimelineGroupGUI groupGui) { if (!groupGui.track.muted) return false; if (groupGui.children == null) return true; return groupGui.children.OfType().All(AllChildrenMuted); } protected static float DrawButtonSuite(int numberOfButtons, ref Rect buttonRect) { var style = DirectorStyles.Instance.trackButtonSuite; var buttonWidth = WindowConstants.trackHeaderButtonSize * numberOfButtons + WindowConstants.trackHeaderButtonPadding * Math.Max(0, numberOfButtons - 1); var suiteWidth = buttonWidth + style.padding.right + style.padding.left; var rect = new Rect(buttonRect.xMax - style.margin.right - suiteWidth, buttonRect.y + style.margin.top, suiteWidth, buttonRect.height); if (Event.current.type == EventType.Repaint) style.Draw(rect, false, false, false, false); buttonRect.x -= style.margin.right + style.padding.right; return style.margin.left + style.padding.left; } } }