using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.PackageManager.Requests;
using UnityEditor.Presets;
using UnityEngine;
using UnityEditor.Recorder.Input;
using UnityObject = UnityEngine.Object;
using UnityEditor.PackageManager;
using UnityEngine.UIElements;
namespace UnityEditor.Recorder
{
///
/// Main window class of the Recorder.
/// It can be accessed from an Editor script to show the Recorder Window and eventually Start and Stop the recording using current settings.
/// Recorder settings are saved in Library/Recorder/recorder.pref
///
public class RecorderWindow : EditorWindow
{
static readonly string s_WindowTitle = "Recorder";
///
/// The location in the Unity Editor menu for the Recorder Window.
///
public const string MenuRoot = "Window/General/Recorder/";
///
/// The priority of the Recorder Window entries in the menu.
///
public const int MenuRootIndex = 1000;
private const string k_DefaultPackageVersion = "1.0.0-preview.1";
private static bool s_PackageVersionInitialized = false;
private static string s_PackageVersionNumber = k_DefaultPackageVersion;
private static string s_PackageShortVersionNumber = "1.0";
private static ListRequest s_Request;
private static bool s_NeedToResetView = false;
private static bool HasFocus()
{
return (focusedWindow != null && focusedWindow.GetType() == typeof(RecorderWindow));
}
[MenuItem(MenuRoot + "Recorder Window", false, MenuRootIndex)]
static void ToggleRecorderWindow()
{
if (HasFocus())
{
var recorderWindow = GetWindow(typeof(RecorderWindow), false, s_WindowTitle, false);
recorderWindow.Close();
return;
}
GetWindow(typeof(RecorderWindow), false, s_WindowTitle);
}
[MenuItem(MenuRoot + "Quick Recording _F10", false, MenuRootIndex + 1)]
static void QuickRecording()
{
var recorderWindow = (RecorderWindow)GetWindow(typeof(RecorderWindow), false, s_WindowTitle, false);
if (recorderWindow.m_State == State.Error || recorderWindow.m_State == State.AccumulationErrors)
{
Debug.LogError($"There are errors in your Recorder Window. Please address them before launching a quick recording.");
return;
}
if (!recorderWindow.IsRecording())
{
recorderWindow.StartRecording();
}
else
{
recorderWindow.StopRecording();
}
}
[MenuItem(MenuRoot + "Quick Recording _F10", true, MenuRootIndex + 1)]
static bool QuickRecordingValidate()
{
return !EditorUtility.scriptCompilationFailed;
}
static void Progress()
{
if (s_NeedToResetView)
{
var windows = Resources.FindObjectsOfTypeAll();
if (windows != null && windows.Length > 0)
{
RecorderWindow win = windows[0];
win.ClearView();
win.UnregisterCallbacks();
win.CreateView();
win.RegisterCallbacks();
}
s_NeedToResetView = false;
return;
}
if (s_Request.IsCompleted)
{
if (s_Request.Status == StatusCode.Success)
{
var result = s_Request.Result;
foreach (var package in s_Request.Result)
{
var name = package.name;
if (name.Contains("com.unity.recorder"))
{
var newPackageVersion = package.version;
s_PackageShortVersionNumber = newPackageVersion.Substring(0, 3);
s_PackageVersionInitialized = true;
if (newPackageVersion != s_PackageVersionNumber && s_PackageVersionNumber != k_DefaultPackageVersion)
{
// Reset the view when we detect a version upgrade but not the default that gets triggered at startup
s_NeedToResetView = true;
}
s_PackageVersionNumber = newPackageVersion;
return;
}
}
}
else if (s_Request.Status >= StatusCode.Failure)
Debug.Log(s_Request.Error.message);
EditorApplication.update -= Progress;
}
}
static class Styles
{
internal static readonly GUIContent ExitPlayModeLabel = new GUIContent("Exit Play Mode", "To exit the Play mode when the recording stops.");
internal static readonly GUIContent DuplicateLabel = new GUIContent("Duplicate");
internal static readonly GUIContent DeleteLabel = new GUIContent("Delete");
internal static readonly GUIContent SaveRecorderListLabel = new GUIContent("Save Recorder List");
internal static readonly GUIContent LoadRecorderListLabel = new GUIContent("Load Recorder List");
internal static readonly GUIContent ClearRecorderListLabel = new GUIContent("Clear Recorder List");
internal static string GetLinkToManual(string shortVersion)
{
return $"http://docs.unity3d.com/Packages/com.unity.recorder@{shortVersion}/index.html";
}
internal static readonly GUIContent WaitingForPlayModeLabel = new GUIContent("Waiting for Play Mode to start...");
}
static Hashtable s_IconGUIContents = new Hashtable();
internal static GUIContent GetIconAndTooltip(string iconName, string tooltip = null)
{
string key = string.Format("{0}|{1}", iconName, EditorGUIUtility.pixelsPerPoint);
GUIContent gc = (GUIContent)s_IconGUIContents[key];
if (gc != null)
{
return gc;
}
gc = new GUIContent();
if (tooltip != null)
{
gc.tooltip = L10n.Tr(tooltip);
}
string iconForSkinName = EditorGUIUtility.pixelsPerPoint > 1.0f ? iconName + "@2x" : iconName;
iconForSkinName = EditorGUIUtility.isProSkin ? "d_" + iconForSkinName : iconForSkinName;
gc.image = (Texture)EditorGUIUtility.Load(iconForSkinName);
s_IconGUIContents[key] = gc;
return gc;
}
class RecorderItemList : VisualListItem
{
}
RecorderItemList m_RecordingListItem;
VisualElement m_SettingsPanel;
VisualElement m_RecordersPanel;
RecorderItem m_SelectedRecorderItem;
VisualElement m_ParametersControl;
VisualElement m_RecorderSettingPanel;
Button m_RecordButton;
Button m_RecordButtonIcon;
PanelSplitter m_PanelSplitter;
VisualElement m_AddNewRecordPanel;
VisualElement m_RecordModeOptionsPanel;
VisualElement m_FrameRateOptionsPanel;
RecorderControllerSettings m_ControllerSettings;
RecorderController m_RecorderController;
enum State
{
Idle,
WaitingForPlayModeToStartRecording,
WaitingForScenesData,
Error,
AccumulationErrors,
Recording
}
State m_State = State.Idle;
int m_FrameCount = 0;
RecorderSettingsPrefsEditor m_RecorderSettingsPrefsEditor;
///
/// Set the RecorderWindow controller settings and update the UI. This allow to set settings even if
/// the window is open.
///
/// The new to set.
public void SetRecorderControllerSettings(RecorderControllerSettings settings)
{
m_ControllerSettings = settings;
m_RecorderController = new RecorderController(settings);
m_RecorderSettingsPrefsEditor = (RecorderSettingsPrefsEditor)Editor.CreateEditor(m_ControllerSettings);
ReloadRecordings();
}
internal RecorderControllerSettings GetRecorderControllerSettings()
{
return m_ControllerSettings;
}
void ClearView()
{
var root = rootVisualElement;
root.Clear();
}
void RegisterCallbacks()
{
Undo.undoRedoPerformed += OnUndoRedoPerformed;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
EditorApplication.update += UpdateInternal;
}
void UnregisterCallbacks()
{
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.update -= UpdateInternal;
}
void OnUndoRedoPerformed()
{
ReloadRecordings();
SaveAndRepaint();
}
void CreateView()
{
minSize = new Vector2(560.0f, 200.0f);
var pathPrefix1 = $"Styles/recorder.uss";
var lightOrDark = EditorGUIUtility.isProSkin ? "recorder_darkSkin" : "recorder_lightSkin";
var pathPrefix2 = $"Styles/{lightOrDark}.uss";
var root = rootVisualElement;
var sheet1 = UnityHelpers.LoadLocalPackageAsset(pathPrefix1, false);
var sheet2 = UnityHelpers.LoadLocalPackageAsset(pathPrefix2, false);
bool sheetNotFound = sheet1 == null || sheet2 == null;
if (sheetNotFound)
{
s_NeedToResetView = true;
return;
}
root.styleSheets.Add(sheet1);
root.styleSheets.Add(sheet2);
root.style.flexDirection = FlexDirection.Column;
UIElementHelper.SetFocusable(root);
var mainControls = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
minHeight = 120.0f
}
};
root.Add(mainControls);
var controlLeftPane = new VisualElement
{
style =
{
minWidth = 180.0f,
maxWidth = 450.0f,
flexDirection = FlexDirection.Row,
}
};
UIElementHelper.SetFlex(controlLeftPane, 0.5f);
var controlRightPane = new VisualElement
{
style =
{
flexDirection = FlexDirection.Column,
}
};
UIElementHelper.SetFlex(controlRightPane, 0.5f);
mainControls.Add(controlLeftPane);
mainControls.Add(controlRightPane);
controlLeftPane.AddToClassList("StandardPanel");
controlRightPane.AddToClassList("StandardPanel");
m_RecordButtonIcon = new Button(OnRecordButtonClick)
{
name = "recorderIcon",
style =
{
backgroundImage = UnityHelpers.LoadLocalPackageAsset("recorder_icon.png", true),
},
tooltip = "Start the recording for all active recorders of the list\n\n This automatically activates the Play mode first (if not activated yet)."
};
controlLeftPane.Add(m_RecordButtonIcon);
var leftButtonsStack = new VisualElement
{
style =
{
flexDirection = FlexDirection.Column,
}
};
UIElementHelper.SetFlex(leftButtonsStack, 1.0f);
m_RecordButton = new Button(OnRecordButtonClick)
{
name = "recordButton",
tooltip = "Start/Stop the recording for all active recorders of the list\n\nStarting the recording automatically activates the Play mode first (if not activated yet)."
};
UpdateRecordButtonText();
leftButtonsStack.Add(m_RecordButton);
m_RecordModeOptionsPanel = new IMGUIContainer(() =>
{
PrepareGUIState(m_RecordModeOptionsPanel.layout.width);
if (m_RecorderSettingsPrefsEditor.RecordModeGUI())
OnGlobalSettingsChanged();
});
UIElementHelper.SetFlex(m_RecordModeOptionsPanel, 1.0f);
leftButtonsStack.Add(m_RecordModeOptionsPanel);
controlLeftPane.Add(leftButtonsStack);
m_FrameRateOptionsPanel = new IMGUIContainer(() =>
{
PrepareGUIState(m_FrameRateOptionsPanel.layout.width);
if (m_RecorderSettingsPrefsEditor.FrameRateGUI())
OnGlobalSettingsChanged();
});
UIElementHelper.SetFlex(m_FrameRateOptionsPanel, 1.0f);
controlRightPane.Add(m_FrameRateOptionsPanel);
m_SettingsPanel = new ScrollView();
m_SettingsPanel.name = "SettingPanelScrollView";
UIElementHelper.SetFlex(m_SettingsPanel, 1.0f);
UIElementHelper.ResetStylePosition(m_SettingsPanel.contentContainer.style);
var recordersAndParameters = new VisualElement
{
style =
{
alignSelf = Align.Stretch,
flexDirection = FlexDirection.Row,
}
};
UIElementHelper.SetFlex(recordersAndParameters, 1.0f);
m_RecordersPanel = new VisualElement
{
name = "recordersPanel",
style =
{
width = 200.0f,
minWidth = 150.0f,
maxWidth = 500.0f
}
};
m_RecordersPanel.AddToClassList("StandardPanel");
m_PanelSplitter = new PanelSplitter(m_RecordersPanel);
recordersAndParameters.Add(m_RecordersPanel);
recordersAndParameters.Add(m_PanelSplitter);
recordersAndParameters.Add(m_SettingsPanel);
m_SettingsPanel.AddToClassList("StandardPanel");
root.Add(recordersAndParameters);
var addRecordButton = new Label("+ Add Recorder");
UIElementHelper.SetFlex(addRecordButton, 1.0f);
var recorderListPresetButton = new VisualElement
{
name = "recorderListPreset",
tooltip = "Manage the recorder list"
};
recorderListPresetButton.RegisterCallback(evt => ShowRecorderListMenu());
recorderListPresetButton.Add(new Image
{
image = (Texture2D)EditorGUIUtility.Load("Builtin Skins/" + (EditorGUIUtility.isProSkin ? "DarkSkin" : "LightSkin") + "/Images/pane options.png"),
style =
{
width = 16.0f,
height = 16.0f
}
});
addRecordButton.AddToClassList("RecorderListHeader");
recorderListPresetButton.AddToClassList("RecorderListHeader");
addRecordButton.RegisterCallback(evt => ShowNewRecorderMenu());
m_AddNewRecordPanel = new VisualElement
{
name = "addRecordersButton",
style = { flexDirection = FlexDirection.Row },
tooltip = "Add a new recorder item to the list"
};
m_AddNewRecordPanel.Add(addRecordButton);
m_AddNewRecordPanel.Add(recorderListPresetButton);
m_RecordingListItem = new RecorderItemList
{
name = "recorderList"
};
UIElementHelper.SetFlex(m_RecordingListItem, 1.0f);
UIElementHelper.SetFocusable(m_RecordingListItem);
m_RecordingListItem.OnItemContextMenu += OnRecordContextMenu;
m_RecordingListItem.OnSelectionChanged += OnRecordSelectionChanged;
m_RecordingListItem.OnItemRename += item => item.StartRenaming();
m_RecordingListItem.OnContextMenu += ShowNewRecorderMenu;
m_RecordersPanel.Add(m_AddNewRecordPanel);
m_RecordersPanel.Add(m_RecordingListItem);
m_ParametersControl = new VisualElement
{
style =
{
minWidth = 300.0f,
}
};
UIElementHelper.SetFlex(m_ParametersControl, 1.0f);
m_RecorderSettingPanel = new IMGUIContainer(OnRecorderSettingsGUI)
{
name = "recorderSettings"
};
UIElementHelper.SetFlex(m_RecorderSettingPanel, 1.0f);
var statusBar = new VisualElement
{
name = "statusBar"
};
statusBar.Add(new IMGUIContainer(UpdateRecordingProgressGUI));
root.Add(statusBar);
m_ParametersControl.Add(m_RecorderSettingPanel);
m_SettingsPanel.Add(m_ParametersControl);
m_RecordingListItem.RegisterCallback(OnRecorderListValidateCommand);
m_RecordingListItem.RegisterCallback(OnRecorderListExecuteCommand);
m_RecordingListItem.RegisterCallback(OnRecorderListKeyUp);
SetRecorderControllerSettings(RecorderControllerSettings.GetGlobalSettings());
}
void OnEnable()
{
// Register a callback listener to get the version of the package
if (!s_PackageVersionInitialized)
{
s_Request = Client.List(); // List packages installed for the Project
EditorApplication.update += Progress;
}
CreateView();
RegisterCallbacks();
}
///
/// Used to Start the recording with current settings.
/// If not already the case, the Editor will also switch to PlayMode.
///
public void StartRecording()
{
if (EditorUtility.scriptCompilationFailed)
{
return;
}
if (EditorApplication.isPlaying)
{
// Already in play mode, so start recording now
StartRecordingInternal();
}
else if (m_State == State.Idle)
{
// Not playing yet and idle
m_State = State.WaitingForPlayModeToStartRecording;
GameViewSize.DisableMaxOnPlay();
EditorApplication.isPlaying = true;
m_FrameCount = Time.frameCount;
}
}
///
/// Used to get the current state of the recording.
///
/// True if the Recorder is started or being started. False otherwise.
public bool IsRecording()
{
return m_State == State.Recording || m_State == State.WaitingForPlayModeToStartRecording;
}
///
/// Used to Stop current recordings if any.
/// Exiting PlayMode while the Recorder is recording will automatically Stop the recorder.
///
public void StopRecording()
{
if (IsRecording())
StopRecordingInternal();
}
[RuntimeInitializeOnLoadMethod]
static void RuntimeInit()
{
var windows = Resources.FindObjectsOfTypeAll();
if (windows != null && windows.Length > 0)
{
RecorderWindow win = windows[0];
if (win.m_State == State.WaitingForPlayModeToStartRecording)
win.RequestStartRecording();
}
}
void OnPlayModeStateChanged(PlayModeStateChange obj)
{
if (obj == PlayModeStateChange.ExitingPlayMode)
{
if (m_State == State.Recording)
{
StopRecordingInternal();
}
else
{
m_State = State.Idle;
}
}
}
void OnGlobalSettingsChanged()
{
if (m_ControllerSettings == null)
return;
m_ControllerSettings.ApplyGlobalSettingToAllRecorders();
foreach (var recorderItem in m_RecordingListItem.items)
recorderItem.UpdateState();
SaveAndRepaint();
}
void SaveAndRepaint()
{
if (m_ControllerSettings != null)
m_ControllerSettings.Save();
if (m_SelectedRecorderItem != null)
UIElementHelper.SetDirty(m_RecorderSettingPanel);
Repaint();
}
void ReloadRecordings()
{
if (m_ControllerSettings == null)
return;
m_ControllerSettings.ApplyGlobalSettingToAllRecorders();
var recorderItems = m_ControllerSettings.RecorderSettings.Select(CreateRecorderItem).ToArray();
foreach (var recorderItem in recorderItems)
recorderItem.UpdateState();
m_RecordingListItem.Reload(recorderItems);
}
void UpdateStateOfSelectedItem()
{
if (m_SelectedRecorderItem != null)
m_SelectedRecorderItem.UpdateState();
}
void OnRecorderListValidateCommand(ValidateCommandEvent evt)
{
RecorderListValidateCommand(evt, evt.commandName);
}
void OnRecorderListExecuteCommand(ExecuteCommandEvent evt)
{
RecorderListExecuteCommand(evt, evt.commandName);
}
void RecorderListValidateCommand(EventBase evt, string commandName)
{
if (m_RecordingListItem == null || m_RecordingListItem.selection == null)
return;
if (commandName == "Duplicate" || commandName == "SoftDelete" || commandName == "Delete")
{
evt.StopPropagation();
}
}
void RecorderListExecuteCommand(EventBase evt, string commandName)
{
if (m_RecordingListItem == null)
return;
var item = m_RecordingListItem.selection;
if (item == null)
return;
if (commandName == "Duplicate")
{
DuplicateRecorder(item);
evt.StopPropagation();
}
else if (commandName == "SoftDelete" || commandName == "Delete")
{
DeleteRecorder(item);
evt.StopPropagation();
}
}
void OnRecorderListKeyUp(KeyUpEvent evt)
{
if (m_RecordingListItem == null)
return;
if (m_RecordingListItem.items == null || m_RecordingListItem.items.Count == 0)
return;
if (!m_RecordingListItem.HasFocus())
return;
if (evt.keyCode == KeyCode.UpArrow || evt.keyCode == KeyCode.DownArrow)
{
if (m_RecordingListItem.selection == null)
{
m_RecordingListItem.selection = m_RecordingListItem.items.First();
}
else
{
var currentIndex = m_RecordingListItem.selectedIndex;
var newIndex = ((evt.keyCode == KeyCode.UpArrow ? currentIndex - 1 : currentIndex + 1) + m_RecordingListItem.items.Count) %
m_RecordingListItem.items.Count;
m_RecordingListItem.selectedIndex = newIndex;
}
evt.StopPropagation();
}
}
void ApplyPreset(string presetPath)
{
var candidate = AssetDatabase.LoadAssetAtPath(presetPath);
ApplyPreset(candidate);
}
///
/// Loads a previously saved Recorder List.
///
/// The instance of Recorder List to load.
public void ApplyPreset(RecorderControllerSettingsPreset preset)
{
if (preset == null)
return;
preset.ApplyTo(m_ControllerSettings);
ReloadRecordings();
}
void ShowNewRecorderMenu()
{
var newRecordMenu = new GenericMenu();
foreach (var info in RecordersInventory.builtInRecorderInfos)
AddRecorderInfoToMenu(info, newRecordMenu);
var recorderList = RecordersInventory.customRecorderInfos.ToList();
if (recorderList.Any())
{
newRecordMenu.AddSeparator(string.Empty);
foreach (var info in recorderList)
AddRecorderInfoToMenu(info, newRecordMenu);
}
newRecordMenu.ShowAsContext();
}
void AddRecorderInfoToMenu(RecorderInfo info, GenericMenu menu)
{
if (ShouldDisableRecordSettings())
menu.AddDisabledItem(new GUIContent(info.displayName));
else
menu.AddItem(new GUIContent(info.displayName), false, data => OnAddNewRecorder((RecorderInfo)data), info);
}
RecorderItem CreateRecorderItem(RecorderSettings recorderSettings)
{
var info = RecordersInventory.GetRecorderInfo(recorderSettings.GetType());
var isInvalid = info == null;
var recorderItem = new RecorderItem(m_ControllerSettings, recorderSettings, isInvalid ? null : info.iconName);
recorderItem.OnEnableStateChanged += enabled =>
{
if (enabled)
m_RecordingListItem.selection = recorderItem;
};
if (isInvalid)
recorderItem.state = RecorderItem.State.Invalid;
return recorderItem;
}
void CheckRecordersIncompatibility()
{
var activeRecorders = m_ControllerSettings.RecorderSettings.Where(r => r.Enabled).ToArray();
HasDuplicateOutputNames(activeRecorders);
HasDifferentResolutions(activeRecorders);
// Refresh the state of all items in the window
foreach (var recorderItem in m_RecordingListItem.items)
recorderItem.UpdateState();
if (m_ControllerSettings.InvalidContextBecauseOfAccumulation())
{
ShowMessageInStatusBar("You can only use one active Recorder at a time when you capture accumulation.", MessageType.Error);
m_State = State.AccumulationErrors;
// Flag all the recorders that support accumulation with a warning (important to perform this after calling UpdateState on all items)
foreach (var recorderItem in m_RecordingListItem.items)
{
if (recorderItem.settings.Enabled)
{
recorderItem.state = RecorderItem.State.HasErrors;
}
}
return;
}
if (m_State == State.AccumulationErrors)
{
// There were accumulation errors but now the context has been fixed. Return to idle state.
m_State = State.Idle;
}
var activeRecordersThatCanRun = activeRecorders.Where(r => r.Enabled && !r.HasErrors()).ToArray();
if (activeRecordersThatCanRun.Length > 0)
{
ShowMessageInStatusBar("Ready to start recording", MessageType.None);
}
}
bool HasDuplicateOutputNames(RecorderSettings[] activeRecorders)
{
var duplicateNames = 0;
m_RecorderController.ValidateRecorderNames();
foreach (var recorder in activeRecorders)
{
if (recorder.IsOutputNameDuplicate)
duplicateNames++;
}
if (duplicateNames > 0)
{
var msg = $"There are {duplicateNames} Recorders that do not have unique output file names.";
ShowMessageInStatusBar(msg, MessageType.Error);
}
return duplicateNames > 0;
}
void HasDifferentResolutions(RecorderSettings[] activeRecorders)
{
m_RecorderController.ValidateRecorderResolutions();
var duplicateRes = activeRecorders.OfType()
.Count(x => x.IsOutputResolutionContradictory);
if (duplicateRes > 0)
{
var msg = $"There are {duplicateRes} Recorders that use different resolutions.";
ShowMessageInStatusBar(msg, MessageType.Error);
}
}
bool ShouldDisableRecordSettings()
{
return IsRecording();
}
void UpdateInternal()
{
if (!EditorApplication.isPlaying)
{
if (m_State == State.Recording)
{
StopRecordingInternal();
}
}
else if (m_State == State.WaitingForScenesData && UnityHelpers.AreAllSceneDataLoaded())
{
StartRecordingInternal();
}
var enable = !ShouldDisableRecordSettings();
m_AddNewRecordPanel.SetEnabled(enable);
m_ParametersControl.SetEnabled(enable && m_SelectedRecorderItem != null && m_SelectedRecorderItem.state != RecorderItem.State.Invalid);
m_RecordModeOptionsPanel.SetEnabled(enable);
m_FrameRateOptionsPanel.SetEnabled(enable);
if (HaveActiveRecordingsThatCanRun())
{
if (IsRecording())
{
SetRecordButtonsEnabled(EditorApplication.isPlaying && Time.frameCount - m_FrameCount > 5.0f);
}
else
{
SetRecordButtonsEnabled(!EditorUtility.scriptCompilationFailed && !m_ControllerSettings.InvalidContextBecauseOfAccumulation());
}
}
else
{
SetRecordButtonsEnabled(false);
}
UpdateRecordButtonText();
if (m_State == State.Recording)
{
if (!m_RecorderController.IsRecording())
StopRecordingInternal();
Repaint();
}
}
void SetRecordButtonsEnabled(bool enabled)
{
m_RecordButton.SetEnabled(enabled);
m_RecordButtonIcon.SetEnabled(enabled);
}
void StartRecordingInternal()
{
if (RecorderOptions.VerboseMode)
Debug.Log("Start Recording.");
try
{
m_RecorderController.PrepareRecording();
}
catch (InvalidOperationException)
{
StopRecordingInternal(false);
m_State = State.AccumulationErrors;
return;
}
var success = m_RecorderController.StartRecording();
RecorderAnalytics.SendStartEvent(m_RecorderController);
if (success)
{
m_State = State.Recording;
m_FrameCount = 0;
}
else
{
StopRecordingInternal(false);
m_State = State.Error;
}
}
void RequestStartRecording()
{
if (RecorderOptions.VerboseMode)
Debug.Log("Prepare and wait all scenes to load.");
m_RecorderController.PrepareRecording();
m_State = State.WaitingForScenesData;
}
void OnRecordButtonClick()
{
if (m_State == State.Error)
m_State = State.Idle;
switch (m_State)
{
case State.Idle:
{
StartRecording();
break;
}
case State.WaitingForPlayModeToStartRecording:
case State.Recording:
{
StopRecording();
break;
}
}
UpdateRecordButtonText();
}
void UpdateRecordButtonText()
{
m_RecordButton.text = m_State == State.Recording ? "STOP RECORDING" : "START RECORDING";
}
void StopRecordingInternal(bool success = true)
{
if (RecorderOptions.VerboseMode)
Debug.Log("Stop Recording.");
RecorderAnalytics.SendStopEvent(m_RecorderController, !success);
m_RecorderController.StopRecording();
m_State = State.Idle;
m_FrameCount = 0;
// Settings might have changed after the session ended
m_ControllerSettings.Save();
if (m_RecorderController.Settings.ExitPlayMode)
EditorApplication.isPlaying = false;
}
static void PrepareGUIState(float contextWidth)
{
EditorGUIUtility.labelWidth = Mathf.Min(Mathf.Max(contextWidth * 0.45f - 40, 100), 160);
}
void OnRecorderSettingsGUI()
{
PrepareGUIState(m_RecorderSettingPanel.layout.width);
if (m_SelectedRecorderItem != null)
{
if (m_SelectedRecorderItem.state == RecorderItem.State.Invalid)
{
EditorGUILayout.LabelField("This Recorder has errors and cannot record any data.");
}
else
{
var editor = m_SelectedRecorderItem.editor;
if (editor == null)
{
EditorGUILayout.LabelField("Error while displaying the recorder inspector");
}
else
{
EditorGUIUtility.labelWidth = Mathf.Min(Mathf.Max(m_RecorderSettingPanel.layout.width * 0.45f - 40, 110), 140);
EditorGUILayout.Separator();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Recorder Type",
ObjectNames.NicifyVariableName(editor.target.GetType().Name));
if (!(editor is RecorderEditor))
EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
// Open Manual
if (GUILayout.Button(GetIconAndTooltip("_Help", "Open Reference for Recorder"), PresetHelper.presetButtonStyle))
{
Application.OpenURL(Styles.GetLinkToManual(s_PackageShortVersionNumber));
}
if (GUILayout.Button(PresetHelper.presetIcon, PresetHelper.presetButtonStyle))
{
var settings = editor.target as RecorderSettings;
if (settings != null)
{
PresetHelper.ShowPresetSelectorWrapper(settings, null, Repaint, () =>
{
EditorUtility.SetDirty(settings);
});
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();
EditorGUI.BeginChangeCheck();
EditorGUILayout.Separator();
var prevValue = RecorderEditor.FromRecorderWindow;
RecorderEditor.FromRecorderWindow = true;
editor.OnInspectorGUI();
RecorderEditor.FromRecorderWindow = prevValue;
if (EditorGUI.EndChangeCheck() || EditorUtility.IsDirty(m_SelectedRecorderItem.settings))
{
m_ControllerSettings.Save();
m_SelectedRecorderItem.UpdateState();
UIElementHelper.SetDirty(m_RecorderSettingPanel);
}
}
}
}
else
{
EditorGUILayout.LabelField("No recorder selected");
}
}
void ShowRecorderListMenu()
{
var menu = new GenericMenu();
menu.AddItem(Styles.SaveRecorderListLabel, false, () =>
{
var path = EditorUtility.SaveFilePanelInProject("Save Preset", "RecorderSettingPreset.asset", "asset", "");
if (path.Length != 0)
RecorderControllerSettingsPreset.SaveAtPath(m_ControllerSettings, path);
});
var presets = AssetDatabase.FindAssets("t:" + typeof(RecorderControllerSettingsPreset).Name);
if (presets.Length > 0)
{
foreach (var preset in presets)
{
var path = AssetDatabase.GUIDToAssetPath(preset);
var fileName = Path.GetFileNameWithoutExtension(path);
menu.AddItem(new GUIContent("Load Recorder List/" + fileName), false, data => { ApplyPreset((string)data); }, path);
}
}
else
{
menu.AddDisabledItem(Styles.LoadRecorderListLabel);
}
var items = m_RecordingListItem.items.ToArray();
if (items.Length > 0)
{
menu.AddItem(Styles.ClearRecorderListLabel, false, () =>
{
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName("Clear Recorder List");
var undoGroupIndex = Undo.GetCurrentGroup();
foreach (var item in items)
{
if (item.editor != null)
DestroyImmediate(item.editor);
DeleteRecorder(item);
}
Undo.CollapseUndoOperations(undoGroupIndex);
ReloadRecordings();
});
}
else
{
menu.AddDisabledItem(Styles.ClearRecorderListLabel);
}
menu.ShowAsContext();
}
void OnDestroy()
{
if (IsRecording())
StopRecording();
if (m_ControllerSettings != null)
{
m_ControllerSettings.Save();
DestroyImmediate(m_ControllerSettings);
}
if (m_RecorderSettingsPrefsEditor != null)
DestroyImmediate(m_RecorderSettingsPrefsEditor);
UnregisterCallbacks();
}
void AddLastAndSelect(RecorderSettings recorder, string desiredName, bool enabled)
{
recorder.name = GetUniqueRecorderName(desiredName);
recorder.Enabled = enabled;
m_ControllerSettings.AddRecorderSettings(recorder);
var item = CreateRecorderItem(recorder);
m_RecordingListItem.Add(item);
m_RecordingListItem.selection = item;
m_RecordingListItem.Focus();
}
void DuplicateRecorder(RecorderItem item)
{
var candidate = item.settings;
var copy = Instantiate(candidate);
copy.OnAfterDuplicate();
AddLastAndSelect(copy, candidate.name, candidate.Enabled);
}
void DeleteRecorder(RecorderItem item)
{
var s = item.settings;
m_ControllerSettings.RemoveRecorder(s);
UnityHelpers.Destroy(item.editor, true);
m_RecordingListItem.Remove(item);
}
void OnAddNewRecorder(RecorderInfo info)
{
var recorder = RecordersInventory.CreateDefaultRecorderSettings(info.settingsType);
AddLastAndSelect(recorder, ObjectNames.NicifyVariableName(info.displayName), true);
UIElementHelper.SetDirty(m_RecorderSettingPanel);
m_State = State.Idle;
}
string GetUniqueRecorderName(string desiredName)
{
return ObjectNames.GetUniqueName(m_ControllerSettings.RecorderSettings.Select(r => r.name).ToArray(),
desiredName);
}
void OnRecordContextMenu(RecorderItem recorder)
{
var contextMenu = new GenericMenu();
if (ShouldDisableRecordSettings())
{
contextMenu.AddDisabledItem(Styles.DuplicateLabel);
contextMenu.AddDisabledItem(Styles.DeleteLabel);
}
else
{
contextMenu.AddItem(Styles.DuplicateLabel, false,
data =>
{
DuplicateRecorder((RecorderItem)data);
}, recorder);
contextMenu.AddItem(Styles.DeleteLabel, false,
data =>
{
DeleteRecorder((RecorderItem)data);
}, recorder);
}
contextMenu.ShowAsContext();
}
void OnRecordSelectionChanged()
{
var previousSelection = m_SelectedRecorderItem;
if (previousSelection != null)
{
// Remove the action trigger
var recorderEditor = (RecorderEditor)(previousSelection.editor);
recorderEditor.OnRecorderDataHasChaged -= RecorderDataHasChanged;
}
m_SelectedRecorderItem = m_RecordingListItem.selection;
foreach (var r in m_RecordingListItem.items)
{
r.SetItemSelected(m_SelectedRecorderItem == r);
}
if (m_SelectedRecorderItem != null)
{
UIElementHelper.SetDirty(m_RecorderSettingPanel);
// Listen to the action trigger
var recorderEditor = (RecorderEditor)(m_SelectedRecorderItem.editor);
recorderEditor.OnRecorderDataHasChaged += RecorderDataHasChanged;
}
Repaint();
}
void RecorderDataHasChanged()
{
// Save the controller settings when the data has changed
if (m_ControllerSettings != null)
m_ControllerSettings.Save();
}
bool HaveActiveRecordingsThatCanRun()
{
return m_ControllerSettings.RecorderSettings.Any(r => r.Enabled && !r.HasErrors());
}
static void ShowMessageInStatusBar(string msg, MessageType messageType)
{
var r = EditorGUILayout.GetControlRect();
if (messageType != MessageType.None)
{
var iconR = r;
iconR.width = iconR.height;
var icon = messageType == MessageType.Error
? StatusBarHelper.errorIcon
: (messageType == MessageType.Warning ? StatusBarHelper.warningIcon : StatusBarHelper.infoIcon);
GUI.DrawTexture(iconR, icon);
r.xMin = iconR.xMax + 5.0f;
}
var style = messageType == MessageType.Error
? StatusBarHelper.errorStyle
: (messageType == MessageType.Warning ? StatusBarHelper.warningStyle : StatusBarHelper.infoStyle);
GUI.Label(r, msg, style);
}
void UpdateRecordingProgressGUI()
{
CheckRecordersIncompatibility();
if (m_State == State.Error)
{
if (!HaveActiveRecordingsThatCanRun())
{
ShowMessageInStatusBar("Unable to start recording because no recorders are active and have no errors.", MessageType.Warning);
}
else
{
var activeRecorders = m_ControllerSettings.RecorderSettings.Where(r => r.Enabled).ToArray();
var thereAreWarnings = activeRecorders.Any(r => r.HasWarnings());
var thereAreErrors = activeRecorders.Any(r => r.HasErrors());
if (thereAreWarnings || thereAreErrors)
{
ShowMessageInStatusBar("Please check the recorders for warnings and errors.", MessageType.Warning);
}
}
return;
}
if (m_State == State.Idle)
{
if (!HaveActiveRecordingsThatCanRun())
{
ShowMessageInStatusBar("No active recorder with no errors.", MessageType.Info);
}
return;
}
if (m_State == State.WaitingForPlayModeToStartRecording)
{
EditorGUILayout.LabelField(Styles.WaitingForPlayModeLabel);
return;
}
var recordingSessions = m_RecorderController.GetRecordingSessions();
var session = recordingSessions.FirstOrDefault(); // Follow the progress of the first recorder, since they all share the same timing.
if (session == null)
return;
var progressBarRect = EditorGUILayout.GetControlRect();
var settings = session.settings;
switch (settings.RecordMode)
{
case RecordMode.Manual:
{
var label = string.Format("{0} Frame(s) processed", session.frameIndex);
EditorGUI.ProgressBar(progressBarRect, 0, label);
break;
}
case RecordMode.SingleFrame:
case RecordMode.FrameInterval:
{
var expectedFrames = settings.EndFrame - settings.StartFrame;
var waitingText = settings.StartFrame == 0
? "Waiting for the first frame..." // not skipping frames, just waiting for the first frame to be recorded
: $"Skipping first {settings.StartFrame} frame(s)..."; // if we start at frame 30, we are skipping frames [0;29] so total 30 frames skipped
var label = session.recorder.RecordedFramesCount == 0
? waitingText
: $"{session.recorder.RecordedFramesCount} frame(s) processed";
var progress = (float)session.recorder.RecordedFramesCount / expectedFrames;
EditorGUI.ProgressBar(progressBarRect, progress, label);
break;
}
case RecordMode.TimeInterval:
{
var expectedFrames = (settings.EndTime - settings.StartTime) * settings.FrameRate;
var waitingText = settings.StartTime == 0
? "Waiting for the first frame..." // not skipping frames, just waiting for the first frame to be recorded
: $"Skipping first {settings.StartTime} second(s)..."; // really skipping some time
var label = session.recorder.RecordedFramesCount == 0
? waitingText
: $"{session.recorder.RecordedFramesCount} frame(s) processed";
var progress = (float)session.recorder.RecordedFramesCount / expectedFrames;
EditorGUI.ProgressBar(progressBarRect, progress, label);
break;
}
}
}
}
}