378 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Playables;
 | |
| using UnityEngine.Timeline;
 | |
| using UnityObject = UnityEngine.Object;
 | |
| 
 | |
| namespace UnityEditor.Timeline.Signals
 | |
| {
 | |
|     [CustomEditor(typeof(SignalEmitter), true)]
 | |
|     [CanEditMultipleObjects]
 | |
|     class SignalEmitterInspector : MarkerInspector, ISignalAssetProvider
 | |
|     {
 | |
|         SerializedProperty m_RetroactiveProperty;
 | |
|         SerializedProperty m_EmitOnceProperty;
 | |
| 
 | |
|         SignalEmitter m_Signal;
 | |
|         GameObject m_BoundGameObject;
 | |
|         PlayableDirector m_AssociatedDirector;
 | |
|         bool m_TargetsHaveTheSameBinding;
 | |
| 
 | |
|         readonly Dictionary<Component, Editor> m_Editors = new Dictionary<Component, Editor>();
 | |
|         readonly Dictionary<Component, bool> m_Foldouts = new Dictionary<Component, bool>();
 | |
|         List<Component> m_Receivers = new List<Component>();
 | |
| 
 | |
|         static GUIStyle s_FoldoutStyle;
 | |
|         internal static GUIStyle foldoutStyle
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 if (s_FoldoutStyle == null)
 | |
|                 {
 | |
|                     s_FoldoutStyle = new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold };
 | |
|                 }
 | |
| 
 | |
|                 return s_FoldoutStyle;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public SignalAsset signalAsset
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 var emitter = target as SignalEmitter;
 | |
|                 return signalAssetSameValue ? emitter.asset : null;
 | |
|             }
 | |
|             set
 | |
|             {
 | |
|                 AssignSignalAsset(value);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool signalAssetSameValue
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 var emitters = targets.Cast<SignalEmitter>().ToList();
 | |
|                 return emitters.Select(x => x.asset).Distinct().Count() == 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnEnable()
 | |
|         {
 | |
|             Undo.undoRedoPerformed += OnUndoRedo; // subscribe to the event
 | |
|             m_Signal = target as SignalEmitter;
 | |
|             m_RetroactiveProperty = serializedObject.FindProperty("m_Retroactive");
 | |
|             m_EmitOnceProperty = serializedObject.FindProperty("m_EmitOnce");
 | |
|             // In a vast majority of the cases, when this becomes enabled,
 | |
|             // the timeline window will be focused on the correct timeline
 | |
|             // in which case TimelineEditor.inspectedDirector is safe to use
 | |
|             m_AssociatedDirector = TimelineEditor.inspectedDirector;
 | |
|             UpdateState();
 | |
|         }
 | |
| 
 | |
|         internal override bool IsEnabled()
 | |
|         {
 | |
|             return TimelineUtility.IsCurrentSequenceValid() && !IsCurrentSequenceReadOnly() && base.IsEnabled();
 | |
|         }
 | |
| 
 | |
|         public override void OnInspectorGUI()
 | |
|         {
 | |
|             serializedObject.Update();
 | |
| 
 | |
|             using (var changeScope = new EditorGUI.ChangeCheckScope())
 | |
|             {
 | |
|                 var property = serializedObject.GetIterator();
 | |
|                 var expanded = true;
 | |
|                 while (property.NextVisible(expanded))
 | |
|                 {
 | |
|                     expanded = false;
 | |
|                     if (SkipField(property.propertyPath))
 | |
|                         continue;
 | |
|                     EditorGUILayout.PropertyField(property, true);
 | |
|                 }
 | |
| 
 | |
|                 DrawSignalFlags();
 | |
|                 UpdateState();
 | |
|                 DrawNameSelectorAndSignalList();
 | |
| 
 | |
|                 if (changeScope.changed)
 | |
|                 {
 | |
|                     serializedObject.ApplyModifiedProperties();
 | |
|                     TimelineEditor.Refresh(RefreshReason.ContentsModified | RefreshReason.WindowNeedsRedraw);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal override void OnHeaderIconGUI(Rect iconRect)
 | |
|         {
 | |
|             using (new EditorGUI.DisabledScope(!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly()))
 | |
|             {
 | |
|                 GUI.Label(iconRect, Styles.SignalEmitterIcon);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
 | |
|         {
 | |
|             using (new EditorGUI.DisabledScope(!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly()))
 | |
|             {
 | |
|                 var helpSize = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.helpIcon);
 | |
|                 const int kTopMargin = 5;
 | |
|                 return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - helpSize.x, r.y + kTopMargin, helpSize.x, helpSize.y), targets);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         IEnumerable<SignalAsset> ISignalAssetProvider.AvailableSignalAssets()
 | |
|         {
 | |
|             return SignalManager.assets;
 | |
|         }
 | |
| 
 | |
|         void ISignalAssetProvider.CreateNewSignalAsset(string path)
 | |
|         {
 | |
|             var newSignalAsset = SignalManager.CreateSignalAssetInstance(path);
 | |
|             AssignSignalAsset(newSignalAsset);
 | |
|             var receivers = m_Receivers.OfType<SignalReceiver>().ToList();
 | |
|             if (signalAsset != null && receivers.Count == 1 && !receivers.Any(r => r.IsSignalAssetHandled(newSignalAsset))) // Only when one receiver is present
 | |
|             {
 | |
|                 receivers[0].AddNewReaction(newSignalAsset); // Add reaction on the first receiver from the list
 | |
|                 ApplyChangesAndRefreshReceivers();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void UpdateState()
 | |
|         {
 | |
|             m_BoundGameObject = GetBoundGameObject(m_Signal.parent, m_AssociatedDirector);
 | |
|             m_Receivers = m_BoundGameObject == null || m_BoundGameObject.Equals(null)
 | |
|                 ? new List<Component>()
 | |
|                 : m_BoundGameObject.GetComponents<Component>().Where(t => t is INotificationReceiver).ToList();
 | |
| 
 | |
|             m_TargetsHaveTheSameBinding = targets.Cast<SignalEmitter>()
 | |
|                 .Select(x => GetBoundGameObject(x.parent, m_AssociatedDirector))
 | |
|                 .Distinct().Count() == 1;
 | |
|         }
 | |
| 
 | |
|         Editor GetOrCreateReceiverEditor(Component c)
 | |
|         {
 | |
|             Editor ret;
 | |
|             if (m_Editors.TryGetValue(c, out ret))
 | |
|             {
 | |
|                 return ret;
 | |
|             }
 | |
| 
 | |
|             ret = CreateEditorWithContext(new Object[] { c }, target);
 | |
|             m_Editors[c] = ret;
 | |
|             if (!m_Foldouts.ContainsKey(c))
 | |
|             {
 | |
|                 m_Foldouts[c] = true;
 | |
|             }
 | |
| 
 | |
|             return ret;
 | |
|         }
 | |
| 
 | |
|         void OnDisable()
 | |
|         {
 | |
|             Undo.undoRedoPerformed -= OnUndoRedo;
 | |
|         }
 | |
| 
 | |
|         void OnDestroy()
 | |
|         {
 | |
|             foreach (var editor in m_Editors)
 | |
|             {
 | |
|                 DestroyImmediate(editor.Value);
 | |
|             }
 | |
|             m_Editors.Clear();
 | |
|         }
 | |
| 
 | |
|         void OnUndoRedo()
 | |
|         {
 | |
|             ApplyChangesAndRefreshReceivers();
 | |
|         }
 | |
| 
 | |
|         void ApplyChangesAndRefreshReceivers()
 | |
|         {
 | |
|             foreach (var receiverInspector in m_Editors.Values.OfType<SignalReceiverInspector>())
 | |
|             {
 | |
|                 receiverInspector.SetAssetContext(signalAsset);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DrawNameSelectorAndSignalList()
 | |
|         {
 | |
|             using (var change = new EditorGUI.ChangeCheckScope())
 | |
|             {
 | |
|                 DrawSignal();
 | |
|                 DrawReceivers();
 | |
| 
 | |
|                 if (change.changed)
 | |
|                 {
 | |
|                     ApplyChangesAndRefreshReceivers();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DrawReceivers()
 | |
|         {
 | |
|             if (!m_TargetsHaveTheSameBinding)
 | |
|             {
 | |
|                 EditorGUILayout.HelpBox(Styles.MultiEditNotSupportedOnDifferentBindings, MessageType.None);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (targets.OfType<SignalEmitter>().Select(x => x.asset).Distinct().Count() > 1)
 | |
|             {
 | |
|                 EditorGUILayout.HelpBox(Styles.MultiEditNotSupportedOnDifferentSignals, MessageType.None);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //do not display the receiver if the current timeline is not the same as the emitter's timeline
 | |
|             //can happen if the inspector is locked
 | |
|             if (m_Signal.parent != null && m_Signal.parent.timelineAsset != TimelineEditor.inspectedAsset)
 | |
|                 return;
 | |
| 
 | |
|             if (m_BoundGameObject != null)
 | |
|             {
 | |
|                 if (!m_Receivers.Any(x => x is SignalReceiver))
 | |
|                 {
 | |
|                     EditorGUILayout.Separator();
 | |
|                     var message = string.Format(Styles.NoSignalReceiverComponent, m_BoundGameObject.name);
 | |
|                     SignalUtility.DrawCenteredMessage(message);
 | |
|                     if (SignalUtility.DrawCenteredButton(Styles.AddSignalReceiverComponent))
 | |
|                         AddReceiverComponent();
 | |
|                 }
 | |
| 
 | |
|                 foreach (var receiver in m_Receivers)
 | |
|                 {
 | |
|                     var editor = GetOrCreateReceiverEditor(receiver);
 | |
|                     if (DrawReceiverHeader(receiver))
 | |
|                     {
 | |
|                         editor.OnInspectorGUI();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else if (m_AssociatedDirector != null) //not in asset mode
 | |
|             {
 | |
|                 EditorGUILayout.HelpBox(Styles.NoBoundGO, MessageType.None);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DrawSignalFlags()
 | |
|         {
 | |
|             EditorGUILayout.PropertyField(m_RetroactiveProperty, Styles.RetroactiveLabel);
 | |
|             EditorGUILayout.PropertyField(m_EmitOnceProperty, Styles.EmitOnceLabel);
 | |
|         }
 | |
| 
 | |
|         void DrawSignal()
 | |
|         {
 | |
|             //should show button to create new signal if there are no signals asset in the project
 | |
|             if (!SignalManager.assets.Any())
 | |
|             {
 | |
|                 using (new EditorGUI.DisabledScope(true))
 | |
|                 {
 | |
|                     DrawNameSelector();
 | |
|                 }
 | |
| 
 | |
|                 EditorGUILayout.Separator();
 | |
|                 SignalUtility.DrawCenteredMessage(Styles.ProjectHasNoSignalAsset);
 | |
|                 if (SignalUtility.DrawCenteredButton(Styles.CreateNewSignal))
 | |
|                     CreateNewSignalAsset(SignalUtility.GetNewSignalPath());
 | |
|                 EditorGUILayout.Separator();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 DrawNameSelector();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void CreateNewSignalAsset(string path)
 | |
|         {
 | |
|             if (!string.IsNullOrEmpty(path))
 | |
|                 ((ISignalAssetProvider)this).CreateNewSignalAsset(path);
 | |
|             GUIUtility.ExitGUI();
 | |
|         }
 | |
| 
 | |
|         void AssignSignalAsset(SignalAsset newAsset)
 | |
|         {
 | |
|             foreach (var o in targets)
 | |
|             {
 | |
|                 var signalEmitter = (SignalEmitter)o;
 | |
|                 UndoExtensions.RegisterMarker(signalEmitter, Styles.UndoCreateSignalAsset);
 | |
|                 signalEmitter.asset = newAsset;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DrawNameSelector()
 | |
|         {
 | |
|             SignalUtility.DrawSignalNames(this, EditorGUILayout.GetControlRect(), Styles.EmitSignalLabel, !signalAssetSameValue);
 | |
|         }
 | |
| 
 | |
|         bool DrawReceiverHeader(Component receiver)
 | |
|         {
 | |
|             EditorGUILayout.Space();
 | |
|             var lineRect = GUILayoutUtility.GetRect(10, 4, EditorStyles.inspectorTitlebar);
 | |
|             DrawSplitLine(lineRect.y);
 | |
| 
 | |
|             var style = EditorGUIUtility.TrTextContentWithIcon(
 | |
|                 ObjectNames.NicifyVariableName(receiver.GetType().Name),
 | |
|                 AssetPreview.GetMiniThumbnail(receiver));
 | |
| 
 | |
|             m_Foldouts[receiver] =
 | |
|                 EditorGUILayout.Foldout(m_Foldouts[receiver], style, true, foldoutStyle);
 | |
|             if (m_Foldouts[receiver])
 | |
|             {
 | |
|                 DrawReceiverObjectField();
 | |
|             }
 | |
| 
 | |
|             return m_Foldouts[receiver];
 | |
|         }
 | |
| 
 | |
|         void DrawReceiverObjectField()
 | |
|         {
 | |
|             EditorGUI.BeginDisabledGroup(true);
 | |
|             EditorGUILayout.ObjectField(Styles.ObjectLabel, m_BoundGameObject, typeof(GameObject), false);
 | |
|             EditorGUI.EndDisabledGroup();
 | |
|         }
 | |
| 
 | |
|         void AddReceiverComponent()
 | |
|         {
 | |
|             var receiver = Undo.AddComponent<SignalReceiver>(m_BoundGameObject);
 | |
|             receiver.AddNewReaction(signalAsset);
 | |
|         }
 | |
| 
 | |
|         static bool SkipField(string fieldName)
 | |
|         {
 | |
|             return fieldName == "m_Script" || fieldName == "m_Asset" || fieldName == "m_Retroactive" || fieldName == "m_EmitOnce";
 | |
|         }
 | |
| 
 | |
|         static void DrawSplitLine(float y)
 | |
|         {
 | |
|             if (Event.current.type != EventType.Repaint) return;
 | |
| 
 | |
|             var width = EditorGUIUtility.currentViewWidth;
 | |
|             var position = new Rect(0, y, width + 1, 1);
 | |
| 
 | |
|             if (EditorStyles.inspectorTitlebar != null)
 | |
|                 EditorStyles.inspectorTitlebar.Draw(position, false, false, false, false);
 | |
|         }
 | |
| 
 | |
|         static GameObject GetBoundGameObject(TrackAsset track, PlayableDirector associatedDirector)
 | |
|         {
 | |
|             if (associatedDirector == null || track == null) //if in asset mode, no bound object for you
 | |
|                 return null;
 | |
| 
 | |
|             var boundObj = TimelineUtility.GetSceneGameObject(associatedDirector, track);
 | |
| 
 | |
|             //if the signal is on the timeline marker track and user did not set a binding, assume it's bound to PlayableDirector
 | |
|             if (boundObj == null && track.timelineAsset.markerTrack == track)
 | |
|                 boundObj = associatedDirector.gameObject;
 | |
| 
 | |
|             return boundObj;
 | |
|         }
 | |
| 
 | |
|         static bool IsCurrentSequenceReadOnly()
 | |
|         {
 | |
|             return TimelineWindow.instance.state.editSequence.isReadOnly;
 | |
|         }
 | |
|     }
 | |
| }
 |