InputRecorderInspector.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #if UNITY_EDITOR
  2. using UnityEditor;
  3. using UnityEngine.Events;
  4. ////TODO: add ability to inspect contents of event traces in a separate window
  5. namespace UnityEngine.InputSystem.Editor
  6. {
  7. /// <summary>
  8. /// A custom inspector for <see cref="InputRecorder"/>. Adds UI elements to store captures in files, to load them from
  9. /// there, and to initiate replays from within the editor. It also shows information for when captures or replays are
  10. /// in progress.
  11. /// </summary>
  12. [CustomEditor(typeof(InputRecorder))]
  13. internal class InputRecorderInspector : UnityEditor.Editor
  14. {
  15. protected void OnEnable()
  16. {
  17. m_DevicePathProperty = serializedObject.FindProperty("m_DevicePath");
  18. m_RecordButtonPath = serializedObject.FindProperty("m_RecordButtonPath");
  19. m_PlayButtonPathProperty = serializedObject.FindProperty("m_PlayButtonPath");
  20. m_RecordFramesProperty = serializedObject.FindProperty("m_RecordFrames");
  21. m_RecordStateEventsOnlyProperty = serializedObject.FindProperty("m_RecordStateEventsOnly");
  22. m_ReplayOnNewDevicesProperty = serializedObject.FindProperty("m_ReplayOnNewDevices");
  23. m_SimulateTimingOnReplayProperty = serializedObject.FindProperty("m_SimulateOriginalTimingOnReplay");
  24. m_CaptureMemoryDefaultSizeProperty = serializedObject.FindProperty("m_CaptureMemoryDefaultSize");
  25. m_CaptureMemoryMaxSizeProperty = serializedObject.FindProperty("m_CaptureMemoryMaxSize");
  26. m_StartRecordingWhenEnabledProperty = serializedObject.FindProperty("m_StartRecordingWhenEnabled");
  27. m_AllInput = string.IsNullOrEmpty(m_DevicePathProperty.stringValue);
  28. m_PlayText = EditorGUIUtility.TrIconContent("PlayButton", "Play the current input capture.");
  29. m_PauseText = EditorGUIUtility.TrIconContent("PauseButton", "Pause the current input playback.");
  30. m_ResumeText = EditorGUIUtility.TrIconContent("PauseButton On", "Resume the current input playback.");
  31. m_StepForwardText = EditorGUIUtility.TrIconContent("d_StepButton", "Play the next input event.");
  32. m_StepBackwardText = EditorGUIUtility.TrIconContent("d_StepLeftButton", "Play the previous input event.");
  33. m_StopText = EditorGUIUtility.TrIconContent("PlayButton On", "Stop the current input playback.");
  34. m_RecordText = EditorGUIUtility.TrIconContent("Animation.Record", "Start recording input.");
  35. var recorder = (InputRecorder)serializedObject.targetObject;
  36. m_OnRecordEvent = _ => Repaint();
  37. recorder.changeEvent.AddListener(m_OnRecordEvent);
  38. }
  39. protected void OnDisable()
  40. {
  41. var recorder = (InputRecorder)serializedObject.targetObject;
  42. recorder.changeEvent.RemoveListener(m_OnRecordEvent);
  43. }
  44. public override void OnInspectorGUI()
  45. {
  46. var recorder = (InputRecorder)serializedObject.targetObject;
  47. using (var scope = new EditorGUI.ChangeCheckScope())
  48. {
  49. var newAllInput = EditorGUILayout.Toggle(m_AllInputText, m_AllInput);
  50. if (!newAllInput)
  51. {
  52. using (new EditorGUI.IndentLevelScope())
  53. {
  54. EditorGUILayout.PropertyField(m_DevicePathProperty, m_DeviceText);
  55. }
  56. }
  57. else if (newAllInput != m_AllInput)
  58. {
  59. m_DevicePathProperty.stringValue = string.Empty;
  60. }
  61. m_AllInput = newAllInput;
  62. EditorGUILayout.PropertyField(m_RecordFramesProperty);
  63. EditorGUILayout.PropertyField(m_RecordStateEventsOnlyProperty);
  64. EditorGUILayout.PropertyField(m_ReplayOnNewDevicesProperty);
  65. EditorGUILayout.PropertyField(m_SimulateTimingOnReplayProperty);
  66. EditorGUILayout.PropertyField(m_StartRecordingWhenEnabledProperty, m_RecordWhenEnabledText);
  67. var defaultSizeInMB = m_CaptureMemoryDefaultSizeProperty.intValue / (1024 * 1024);
  68. var newDefaultSizeInMB = EditorGUILayout.IntSlider(m_DefaultSizeText, defaultSizeInMB, 1, 100);
  69. if (newDefaultSizeInMB != defaultSizeInMB)
  70. m_CaptureMemoryDefaultSizeProperty.intValue = newDefaultSizeInMB * 1024 * 1024;
  71. var maxSizeInMB = m_CaptureMemoryMaxSizeProperty.intValue / (1024 * 1024);
  72. var newMaxSizeInMB = maxSizeInMB < newDefaultSizeInMB
  73. ? newDefaultSizeInMB
  74. : EditorGUILayout.IntSlider(m_MaxSizeText, maxSizeInMB, 1, 100);
  75. if (newMaxSizeInMB != maxSizeInMB)
  76. m_CaptureMemoryMaxSizeProperty.intValue = newMaxSizeInMB * 1024 * 1024;
  77. EditorGUILayout.PropertyField(m_RecordButtonPath, m_RecordButtonText);
  78. EditorGUILayout.PropertyField(m_PlayButtonPathProperty, m_PlayButtonText);
  79. if (scope.changed)
  80. serializedObject.ApplyModifiedProperties();
  81. }
  82. EditorGUILayout.Space();
  83. using (new EditorGUILayout.HorizontalScope())
  84. {
  85. ////TODO: go-to-next and go-to-previous button
  86. // Play and pause buttons.
  87. EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.captureIsRunning);
  88. var oldIsPlaying = recorder.replayIsRunning;
  89. var newIsPlaying = GUILayout.Toggle(oldIsPlaying, !oldIsPlaying ? m_PlayText : m_StopText, EditorStyles.miniButton,
  90. GUILayout.Width(50));
  91. if (oldIsPlaying != newIsPlaying)
  92. {
  93. if (newIsPlaying)
  94. recorder.StartReplay();
  95. else
  96. recorder.StopReplay();
  97. }
  98. if (newIsPlaying && recorder.replay != null && GUILayout.Button(recorder.replay.paused ? m_ResumeText : m_PauseText, EditorStyles.miniButton,
  99. GUILayout.Width(50)))
  100. {
  101. if (recorder.replay.paused)
  102. recorder.StartReplay();
  103. else
  104. recorder.PauseReplay();
  105. }
  106. EditorGUI.EndDisabledGroup();
  107. // Record button.
  108. EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
  109. var oldIsRecording = recorder.captureIsRunning;
  110. var newIsRecording = GUILayout.Toggle(oldIsRecording, m_RecordText, EditorStyles.miniButton, GUILayout.Width(50));
  111. if (oldIsRecording != newIsRecording)
  112. {
  113. if (newIsRecording)
  114. recorder.StartCapture();
  115. else
  116. recorder.StopCapture();
  117. }
  118. EditorGUI.EndDisabledGroup();
  119. // Load button.
  120. EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
  121. if (GUILayout.Button("Load"))
  122. {
  123. var filePath = EditorUtility.OpenFilePanel("Choose Input Event Trace to Load", string.Empty, "inputtrace");
  124. if (!string.IsNullOrEmpty(filePath))
  125. recorder.LoadCaptureFromFile(filePath);
  126. }
  127. EditorGUI.EndDisabledGroup();
  128. // Save button.
  129. EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.replayIsRunning);
  130. if (GUILayout.Button("Save"))
  131. {
  132. var filePath = EditorUtility.SaveFilePanel("Choose Where to Save Input Event Trace", string.Empty, $"{recorder.gameObject.name}.inputtrace", "inputtrace");
  133. if (!string.IsNullOrEmpty(filePath))
  134. recorder.SaveCaptureToFile(filePath);
  135. }
  136. // Clear button.
  137. if (GUILayout.Button("Clear"))
  138. {
  139. recorder.ClearCapture();
  140. Repaint();
  141. }
  142. EditorGUI.EndDisabledGroup();
  143. }
  144. ////TODO: allow hotscrubbing
  145. // Play bar.
  146. EditorGUILayout.IntSlider(recorder.replayPosition, 0, (int)recorder.eventCount);
  147. EditorGUILayout.Space();
  148. using (new EditorGUI.DisabledScope())
  149. {
  150. EditorGUILayout.LabelField(m_InfoText, EditorStyles.miniBoldLabel);
  151. using (new EditorGUI.IndentLevelScope())
  152. {
  153. EditorGUILayout.LabelField($"{recorder.eventCount} events", EditorStyles.miniLabel);
  154. EditorGUILayout.LabelField($"{recorder.totalEventSizeInBytes / 1024} kb captured", EditorStyles.miniLabel);
  155. EditorGUILayout.LabelField($"{recorder.allocatedSizeInBytes / 1024} kb allocated", EditorStyles.miniLabel);
  156. if (recorder.capture != null)
  157. {
  158. var devices = recorder.capture.deviceInfos;
  159. if (devices.Count > 0)
  160. {
  161. EditorGUILayout.LabelField(m_DevicesText, EditorStyles.miniBoldLabel);
  162. using (new EditorGUI.IndentLevelScope())
  163. {
  164. foreach (var device in devices)
  165. {
  166. EditorGUILayout.LabelField(device.layout, EditorStyles.miniLabel);
  167. }
  168. }
  169. }
  170. }
  171. }
  172. }
  173. }
  174. private bool m_AllInput;
  175. private SerializedProperty m_DevicePathProperty;
  176. private SerializedProperty m_RecordButtonPath;
  177. private SerializedProperty m_PlayButtonPathProperty;
  178. private SerializedProperty m_RecordFramesProperty;
  179. private SerializedProperty m_RecordStateEventsOnlyProperty;
  180. private SerializedProperty m_ReplayOnNewDevicesProperty;
  181. private SerializedProperty m_SimulateTimingOnReplayProperty;
  182. private SerializedProperty m_CaptureMemoryDefaultSizeProperty;
  183. private SerializedProperty m_CaptureMemoryMaxSizeProperty;
  184. private SerializedProperty m_StartRecordingWhenEnabledProperty;
  185. private UnityAction<InputRecorder.Change> m_OnRecordEvent;
  186. private GUIContent m_RecordButtonText = new GUIContent("Record Button", "If set, this button will start and stop capture in play mode.");
  187. private GUIContent m_PlayButtonText = new GUIContent("Play Button", "If set, this button will start and stop replay of the current capture in play mode.");
  188. private GUIContent m_RecordWhenEnabledText = new GUIContent("Capture When Enabled", "If true, recording will start immediately when the component is enabled in play mode.");
  189. private GUIContent m_DevicesText = new GUIContent("Devices");
  190. private GUIContent m_AllInputText = new GUIContent("All Input", "Whether to record input from all devices or from just specific devices.");
  191. private GUIContent m_DeviceText = new GUIContent("Device", "Type of device to record input from.");
  192. private GUIContent m_InfoText = new GUIContent("Info:");
  193. private GUIContent m_DefaultSizeText = new GUIContent("Default Size (MB)", "Memory allocate for capture by default. Will automatically grow up to max memory.");
  194. private GUIContent m_MaxSizeText = new GUIContent("Max Size (MB)", "Maximum memory allocated for capture. Once a capture reaches this limit, new events will start overwriting old ones.");
  195. private GUIContent m_PlayText;
  196. private GUIContent m_PauseText;
  197. private GUIContent m_ResumeText;
  198. private GUIContent m_StepForwardText;
  199. private GUIContent m_StepBackwardText;
  200. private GUIContent m_StopText;
  201. private GUIContent m_RecordText;
  202. }
  203. }
  204. #endif