#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.Events;
////TODO: add ability to inspect contents of event traces in a separate window
namespace UnityEngine.InputSystem.Editor
{
///
/// A custom inspector for . Adds UI elements to store captures in files, to load them from
/// there, and to initiate replays from within the editor. It also shows information for when captures or replays are
/// in progress.
///
[CustomEditor(typeof(InputRecorder))]
internal class InputRecorderInspector : UnityEditor.Editor
{
protected void OnEnable()
{
m_DevicePathProperty = serializedObject.FindProperty("m_DevicePath");
m_RecordButtonPath = serializedObject.FindProperty("m_RecordButtonPath");
m_PlayButtonPathProperty = serializedObject.FindProperty("m_PlayButtonPath");
m_RecordFramesProperty = serializedObject.FindProperty("m_RecordFrames");
m_RecordStateEventsOnlyProperty = serializedObject.FindProperty("m_RecordStateEventsOnly");
m_ReplayOnNewDevicesProperty = serializedObject.FindProperty("m_ReplayOnNewDevices");
m_SimulateTimingOnReplayProperty = serializedObject.FindProperty("m_SimulateOriginalTimingOnReplay");
m_CaptureMemoryDefaultSizeProperty = serializedObject.FindProperty("m_CaptureMemoryDefaultSize");
m_CaptureMemoryMaxSizeProperty = serializedObject.FindProperty("m_CaptureMemoryMaxSize");
m_StartRecordingWhenEnabledProperty = serializedObject.FindProperty("m_StartRecordingWhenEnabled");
m_AllInput = string.IsNullOrEmpty(m_DevicePathProperty.stringValue);
m_PlayText = EditorGUIUtility.TrIconContent("PlayButton", "Play the current input capture.");
m_PauseText = EditorGUIUtility.TrIconContent("PauseButton", "Pause the current input playback.");
m_ResumeText = EditorGUIUtility.TrIconContent("PauseButton On", "Resume the current input playback.");
m_StepForwardText = EditorGUIUtility.TrIconContent("d_StepButton", "Play the next input event.");
m_StepBackwardText = EditorGUIUtility.TrIconContent("d_StepLeftButton", "Play the previous input event.");
m_StopText = EditorGUIUtility.TrIconContent("PlayButton On", "Stop the current input playback.");
m_RecordText = EditorGUIUtility.TrIconContent("Animation.Record", "Start recording input.");
var recorder = (InputRecorder)serializedObject.targetObject;
m_OnRecordEvent = _ => Repaint();
recorder.changeEvent.AddListener(m_OnRecordEvent);
}
protected void OnDisable()
{
var recorder = (InputRecorder)serializedObject.targetObject;
recorder.changeEvent.RemoveListener(m_OnRecordEvent);
}
public override void OnInspectorGUI()
{
var recorder = (InputRecorder)serializedObject.targetObject;
using (var scope = new EditorGUI.ChangeCheckScope())
{
var newAllInput = EditorGUILayout.Toggle(m_AllInputText, m_AllInput);
if (!newAllInput)
{
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(m_DevicePathProperty, m_DeviceText);
}
}
else if (newAllInput != m_AllInput)
{
m_DevicePathProperty.stringValue = string.Empty;
}
m_AllInput = newAllInput;
EditorGUILayout.PropertyField(m_RecordFramesProperty);
EditorGUILayout.PropertyField(m_RecordStateEventsOnlyProperty);
EditorGUILayout.PropertyField(m_ReplayOnNewDevicesProperty);
EditorGUILayout.PropertyField(m_SimulateTimingOnReplayProperty);
EditorGUILayout.PropertyField(m_StartRecordingWhenEnabledProperty, m_RecordWhenEnabledText);
var defaultSizeInMB = m_CaptureMemoryDefaultSizeProperty.intValue / (1024 * 1024);
var newDefaultSizeInMB = EditorGUILayout.IntSlider(m_DefaultSizeText, defaultSizeInMB, 1, 100);
if (newDefaultSizeInMB != defaultSizeInMB)
m_CaptureMemoryDefaultSizeProperty.intValue = newDefaultSizeInMB * 1024 * 1024;
var maxSizeInMB = m_CaptureMemoryMaxSizeProperty.intValue / (1024 * 1024);
var newMaxSizeInMB = maxSizeInMB < newDefaultSizeInMB
? newDefaultSizeInMB
: EditorGUILayout.IntSlider(m_MaxSizeText, maxSizeInMB, 1, 100);
if (newMaxSizeInMB != maxSizeInMB)
m_CaptureMemoryMaxSizeProperty.intValue = newMaxSizeInMB * 1024 * 1024;
EditorGUILayout.PropertyField(m_RecordButtonPath, m_RecordButtonText);
EditorGUILayout.PropertyField(m_PlayButtonPathProperty, m_PlayButtonText);
if (scope.changed)
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
////TODO: go-to-next and go-to-previous button
// Play and pause buttons.
EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.captureIsRunning);
var oldIsPlaying = recorder.replayIsRunning;
var newIsPlaying = GUILayout.Toggle(oldIsPlaying, !oldIsPlaying ? m_PlayText : m_StopText, EditorStyles.miniButton,
GUILayout.Width(50));
if (oldIsPlaying != newIsPlaying)
{
if (newIsPlaying)
recorder.StartReplay();
else
recorder.StopReplay();
}
if (newIsPlaying && recorder.replay != null && GUILayout.Button(recorder.replay.paused ? m_ResumeText : m_PauseText, EditorStyles.miniButton,
GUILayout.Width(50)))
{
if (recorder.replay.paused)
recorder.StartReplay();
else
recorder.PauseReplay();
}
EditorGUI.EndDisabledGroup();
// Record button.
EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
var oldIsRecording = recorder.captureIsRunning;
var newIsRecording = GUILayout.Toggle(oldIsRecording, m_RecordText, EditorStyles.miniButton, GUILayout.Width(50));
if (oldIsRecording != newIsRecording)
{
if (newIsRecording)
recorder.StartCapture();
else
recorder.StopCapture();
}
EditorGUI.EndDisabledGroup();
// Load button.
EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
if (GUILayout.Button("Load"))
{
var filePath = EditorUtility.OpenFilePanel("Choose Input Event Trace to Load", string.Empty, "inputtrace");
if (!string.IsNullOrEmpty(filePath))
recorder.LoadCaptureFromFile(filePath);
}
EditorGUI.EndDisabledGroup();
// Save button.
EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.replayIsRunning);
if (GUILayout.Button("Save"))
{
var filePath = EditorUtility.SaveFilePanel("Choose Where to Save Input Event Trace", string.Empty, $"{recorder.gameObject.name}.inputtrace", "inputtrace");
if (!string.IsNullOrEmpty(filePath))
recorder.SaveCaptureToFile(filePath);
}
// Clear button.
if (GUILayout.Button("Clear"))
{
recorder.ClearCapture();
Repaint();
}
EditorGUI.EndDisabledGroup();
}
////TODO: allow hotscrubbing
// Play bar.
EditorGUILayout.IntSlider(recorder.replayPosition, 0, (int)recorder.eventCount);
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope())
{
EditorGUILayout.LabelField(m_InfoText, EditorStyles.miniBoldLabel);
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.LabelField($"{recorder.eventCount} events", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"{recorder.totalEventSizeInBytes / 1024} kb captured", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"{recorder.allocatedSizeInBytes / 1024} kb allocated", EditorStyles.miniLabel);
if (recorder.capture != null)
{
var devices = recorder.capture.deviceInfos;
if (devices.Count > 0)
{
EditorGUILayout.LabelField(m_DevicesText, EditorStyles.miniBoldLabel);
using (new EditorGUI.IndentLevelScope())
{
foreach (var device in devices)
{
EditorGUILayout.LabelField(device.layout, EditorStyles.miniLabel);
}
}
}
}
}
}
}
private bool m_AllInput;
private SerializedProperty m_DevicePathProperty;
private SerializedProperty m_RecordButtonPath;
private SerializedProperty m_PlayButtonPathProperty;
private SerializedProperty m_RecordFramesProperty;
private SerializedProperty m_RecordStateEventsOnlyProperty;
private SerializedProperty m_ReplayOnNewDevicesProperty;
private SerializedProperty m_SimulateTimingOnReplayProperty;
private SerializedProperty m_CaptureMemoryDefaultSizeProperty;
private SerializedProperty m_CaptureMemoryMaxSizeProperty;
private SerializedProperty m_StartRecordingWhenEnabledProperty;
private UnityAction m_OnRecordEvent;
private GUIContent m_RecordButtonText = new GUIContent("Record Button", "If set, this button will start and stop capture in play mode.");
private GUIContent m_PlayButtonText = new GUIContent("Play Button", "If set, this button will start and stop replay of the current capture in play mode.");
private GUIContent m_RecordWhenEnabledText = new GUIContent("Capture When Enabled", "If true, recording will start immediately when the component is enabled in play mode.");
private GUIContent m_DevicesText = new GUIContent("Devices");
private GUIContent m_AllInputText = new GUIContent("All Input", "Whether to record input from all devices or from just specific devices.");
private GUIContent m_DeviceText = new GUIContent("Device", "Type of device to record input from.");
private GUIContent m_InfoText = new GUIContent("Info:");
private GUIContent m_DefaultSizeText = new GUIContent("Default Size (MB)", "Memory allocate for capture by default. Will automatically grow up to max memory.");
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.");
private GUIContent m_PlayText;
private GUIContent m_PauseText;
private GUIContent m_ResumeText;
private GUIContent m_StepForwardText;
private GUIContent m_StepBackwardText;
private GUIContent m_StopText;
private GUIContent m_RecordText;
}
}
#endif