SignalEmitterInspector.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEngine.Playables;
  5. using UnityEngine.Timeline;
  6. using UnityObject = UnityEngine.Object;
  7. namespace UnityEditor.Timeline.Signals
  8. {
  9. [CustomEditor(typeof(SignalEmitter), true)]
  10. [CanEditMultipleObjects]
  11. class SignalEmitterInspector : MarkerInspector, ISignalAssetProvider
  12. {
  13. SerializedProperty m_RetroactiveProperty;
  14. SerializedProperty m_EmitOnceProperty;
  15. SignalEmitter m_Signal;
  16. GameObject m_BoundGameObject;
  17. PlayableDirector m_AssociatedDirector;
  18. bool m_TargetsHaveTheSameBinding;
  19. readonly Dictionary<Component, Editor> m_Editors = new Dictionary<Component, Editor>();
  20. readonly Dictionary<Component, bool> m_Foldouts = new Dictionary<Component, bool>();
  21. List<Component> m_Receivers = new List<Component>();
  22. static GUIStyle s_FoldoutStyle;
  23. internal static GUIStyle foldoutStyle
  24. {
  25. get
  26. {
  27. if (s_FoldoutStyle == null)
  28. {
  29. s_FoldoutStyle = new GUIStyle(EditorStyles.foldout) {fontStyle = FontStyle.Bold};
  30. }
  31. return s_FoldoutStyle;
  32. }
  33. }
  34. public SignalAsset signalAsset
  35. {
  36. get
  37. {
  38. var emitter = target as SignalEmitter;
  39. return signalAssetSameValue ? emitter.asset : null;
  40. }
  41. set
  42. {
  43. AssignSignalAsset(value);
  44. }
  45. }
  46. bool signalAssetSameValue
  47. {
  48. get
  49. {
  50. var emitters = targets.Cast<SignalEmitter>().ToList();
  51. return emitters.Select(x => x.asset).Distinct().Count() == 1;
  52. }
  53. }
  54. void OnEnable()
  55. {
  56. Undo.undoRedoPerformed += OnUndoRedo; // subscribe to the event
  57. m_Signal = target as SignalEmitter;
  58. m_RetroactiveProperty = serializedObject.FindProperty("m_Retroactive");
  59. m_EmitOnceProperty = serializedObject.FindProperty("m_EmitOnce");
  60. // In a vast majority of the cases, when this becomes enabled,
  61. // the timeline window will be focused on the correct timeline
  62. // in which case TimelineEditor.inspectedDirector is safe to use
  63. m_AssociatedDirector = TimelineEditor.inspectedDirector;
  64. UpdateState();
  65. }
  66. internal override bool IsEnabled()
  67. {
  68. return TimelineUtility.IsCurrentSequenceValid() && !IsCurrentSequenceReadOnly() && base.IsEnabled();
  69. }
  70. public override void OnInspectorGUI()
  71. {
  72. serializedObject.Update();
  73. using (var changeScope = new EditorGUI.ChangeCheckScope())
  74. {
  75. var property = serializedObject.GetIterator();
  76. var expanded = true;
  77. while (property.NextVisible(expanded))
  78. {
  79. expanded = false;
  80. if (SkipField(property.propertyPath))
  81. continue;
  82. EditorGUILayout.PropertyField(property, true);
  83. }
  84. DrawSignalFlags();
  85. UpdateState();
  86. DrawNameSelectorAndSignalList();
  87. if (changeScope.changed)
  88. {
  89. serializedObject.ApplyModifiedProperties();
  90. TimelineEditor.Refresh(RefreshReason.ContentsModified | RefreshReason.WindowNeedsRedraw);
  91. }
  92. }
  93. }
  94. internal override void OnHeaderIconGUI(Rect iconRect)
  95. {
  96. using (new EditorGUI.DisabledScope(!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly()))
  97. {
  98. GUI.Label(iconRect, Styles.SignalEmitterIcon);
  99. }
  100. }
  101. internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
  102. {
  103. using (new EditorGUI.DisabledScope(!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly()))
  104. {
  105. var helpSize = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.helpIcon);
  106. const int kTopMargin = 5;
  107. return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - helpSize.x, r.y + kTopMargin, helpSize.x, helpSize.y), targets);
  108. }
  109. }
  110. IEnumerable<SignalAsset> ISignalAssetProvider.AvailableSignalAssets()
  111. {
  112. return SignalManager.assets;
  113. }
  114. void ISignalAssetProvider.CreateNewSignalAsset(string path)
  115. {
  116. var newSignalAsset = SignalManager.CreateSignalAssetInstance(path);
  117. AssignSignalAsset(newSignalAsset);
  118. var receivers = m_Receivers.OfType<SignalReceiver>().ToList();
  119. if (signalAsset != null && receivers.Count == 1 && !receivers.Any(r => r.IsSignalAssetHandled(newSignalAsset))) // Only when one receiver is present
  120. {
  121. receivers[0].AddNewReaction(newSignalAsset); // Add reaction on the first receiver from the list
  122. ApplyChangesAndRefreshReceivers();
  123. }
  124. }
  125. void UpdateState()
  126. {
  127. m_BoundGameObject = GetBoundGameObject(m_Signal.parent, m_AssociatedDirector);
  128. m_Receivers = m_BoundGameObject == null || m_BoundGameObject.Equals(null)
  129. ? new List<Component>()
  130. : m_BoundGameObject.GetComponents<Component>().Where(t => t is INotificationReceiver).ToList();
  131. m_TargetsHaveTheSameBinding = targets.Cast<SignalEmitter>()
  132. .Select(x => GetBoundGameObject(x.parent, m_AssociatedDirector))
  133. .Distinct().Count() == 1;
  134. }
  135. Editor GetOrCreateReceiverEditor(Component c)
  136. {
  137. Editor ret;
  138. if (m_Editors.TryGetValue(c, out ret))
  139. {
  140. return ret;
  141. }
  142. ret = CreateEditorWithContext(new Object[] {c}, target);
  143. m_Editors[c] = ret;
  144. if (!m_Foldouts.ContainsKey(c))
  145. {
  146. m_Foldouts[c] = true;
  147. }
  148. return ret;
  149. }
  150. void OnDisable()
  151. {
  152. Undo.undoRedoPerformed -= OnUndoRedo;
  153. }
  154. void OnDestroy()
  155. {
  156. foreach (var editor in m_Editors)
  157. {
  158. DestroyImmediate(editor.Value);
  159. }
  160. m_Editors.Clear();
  161. }
  162. void OnUndoRedo()
  163. {
  164. ApplyChangesAndRefreshReceivers();
  165. }
  166. void ApplyChangesAndRefreshReceivers()
  167. {
  168. foreach (var receiverInspector in m_Editors.Values.OfType<SignalReceiverInspector>())
  169. {
  170. receiverInspector.SetAssetContext(signalAsset);
  171. }
  172. }
  173. void DrawNameSelectorAndSignalList()
  174. {
  175. using (var change = new EditorGUI.ChangeCheckScope())
  176. {
  177. DrawSignal();
  178. DrawReceivers();
  179. if (change.changed)
  180. {
  181. ApplyChangesAndRefreshReceivers();
  182. }
  183. }
  184. }
  185. void DrawReceivers()
  186. {
  187. if (!m_TargetsHaveTheSameBinding)
  188. {
  189. EditorGUILayout.HelpBox(Styles.MultiEditNotSupportedOnDifferentBindings, MessageType.None);
  190. return;
  191. }
  192. if (targets.OfType<SignalEmitter>().Select(x => x.asset).Distinct().Count() > 1)
  193. {
  194. EditorGUILayout.HelpBox(Styles.MultiEditNotSupportedOnDifferentSignals, MessageType.None);
  195. return;
  196. }
  197. //do not display the receiver if the current timeline is not the same as the emitter's timeline
  198. //can happen if the inspector is locked
  199. if (m_Signal.parent != null && m_Signal.parent.timelineAsset != TimelineEditor.inspectedAsset)
  200. return;
  201. if (m_BoundGameObject != null)
  202. {
  203. if (!m_Receivers.Any(x => x is SignalReceiver))
  204. {
  205. EditorGUILayout.Separator();
  206. var message = string.Format(Styles.NoSignalReceiverComponent, m_BoundGameObject.name);
  207. SignalUtility.DrawCenteredMessage(message);
  208. if (SignalUtility.DrawCenteredButton(Styles.AddSignalReceiverComponent))
  209. AddReceiverComponent();
  210. }
  211. foreach (var receiver in m_Receivers)
  212. {
  213. var editor = GetOrCreateReceiverEditor(receiver);
  214. if (DrawReceiverHeader(receiver))
  215. {
  216. editor.OnInspectorGUI();
  217. }
  218. }
  219. }
  220. else if (m_AssociatedDirector != null) //not in asset mode
  221. {
  222. EditorGUILayout.HelpBox(Styles.NoBoundGO, MessageType.None);
  223. }
  224. }
  225. void DrawSignalFlags()
  226. {
  227. EditorGUILayout.PropertyField(m_RetroactiveProperty, Styles.RetroactiveLabel);
  228. EditorGUILayout.PropertyField(m_EmitOnceProperty, Styles.EmitOnceLabel);
  229. }
  230. void DrawSignal()
  231. {
  232. //should show button to create new signal if there are no signals asset in the project
  233. if (!SignalManager.assets.Any())
  234. {
  235. using (new EditorGUI.DisabledScope(true))
  236. {
  237. DrawNameSelector();
  238. }
  239. EditorGUILayout.Separator();
  240. SignalUtility.DrawCenteredMessage(Styles.ProjectHasNoSignalAsset);
  241. if (SignalUtility.DrawCenteredButton(Styles.CreateNewSignal))
  242. CreateNewSignalAsset(SignalUtility.GetNewSignalPath());
  243. EditorGUILayout.Separator();
  244. }
  245. else
  246. {
  247. DrawNameSelector();
  248. }
  249. }
  250. internal void CreateNewSignalAsset(string path)
  251. {
  252. if (!string.IsNullOrEmpty(path))
  253. ((ISignalAssetProvider)this).CreateNewSignalAsset(path);
  254. GUIUtility.ExitGUI();
  255. }
  256. void AssignSignalAsset(SignalAsset newAsset)
  257. {
  258. foreach (var o in targets)
  259. {
  260. var signalEmitter = (SignalEmitter)o;
  261. TimelineUndo.PushUndo(signalEmitter, Styles.UndoCreateSignalAsset);
  262. signalEmitter.asset = newAsset;
  263. }
  264. }
  265. void DrawNameSelector()
  266. {
  267. SignalUtility.DrawSignalNames(this, EditorGUILayout.GetControlRect(), Styles.EmitSignalLabel, !signalAssetSameValue);
  268. }
  269. bool DrawReceiverHeader(Component receiver)
  270. {
  271. EditorGUILayout.Space();
  272. var lineRect = GUILayoutUtility.GetRect(10, 4, EditorStyles.inspectorTitlebar);
  273. DrawSplitLine(lineRect.y);
  274. var style = EditorGUIUtility.TrTextContentWithIcon(
  275. ObjectNames.NicifyVariableName(receiver.GetType().Name),
  276. AssetPreview.GetMiniThumbnail(receiver));
  277. m_Foldouts[receiver] =
  278. EditorGUILayout.Foldout(m_Foldouts[receiver], style, true, foldoutStyle);
  279. if (m_Foldouts[receiver])
  280. {
  281. DrawReceiverObjectField();
  282. }
  283. return m_Foldouts[receiver];
  284. }
  285. void DrawReceiverObjectField()
  286. {
  287. EditorGUI.BeginDisabledGroup(true);
  288. EditorGUILayout.ObjectField(Styles.ObjectLabel, m_BoundGameObject, typeof(GameObject), false);
  289. EditorGUI.EndDisabledGroup();
  290. }
  291. void AddReceiverComponent()
  292. {
  293. var receiver = Undo.AddComponent<SignalReceiver>(m_BoundGameObject);
  294. receiver.AddNewReaction(signalAsset);
  295. }
  296. static bool SkipField(string fieldName)
  297. {
  298. return fieldName == "m_Script" || fieldName == "m_Asset" || fieldName == "m_Retroactive" || fieldName == "m_EmitOnce";
  299. }
  300. static void DrawSplitLine(float y)
  301. {
  302. if (Event.current.type != EventType.Repaint) return;
  303. var width = EditorGUIUtility.currentViewWidth;
  304. var position = new Rect(0, y, width + 1, 1);
  305. if (EditorStyles.inspectorTitlebar != null)
  306. EditorStyles.inspectorTitlebar.Draw(position, false, false, false, false);
  307. }
  308. static GameObject GetBoundGameObject(TrackAsset track, PlayableDirector associatedDirector)
  309. {
  310. if (associatedDirector == null || track == null) //if in asset mode, no bound object for you
  311. return null;
  312. var boundObj = TimelineUtility.GetSceneGameObject(associatedDirector, track);
  313. //if the signal is on the timeline marker track and user did not set a binding, assume it's bound to PlayableDirector
  314. if (boundObj == null && track.timelineAsset.markerTrack == track)
  315. boundObj = associatedDirector.gameObject;
  316. return boundObj;
  317. }
  318. static bool IsCurrentSequenceReadOnly()
  319. {
  320. return TimelineWindow.instance.state.editSequence.isReadOnly;
  321. }
  322. }
  323. }