using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.Presets;
using UnityEngine;
using UnityEditor.Recorder.Input;
using UnityObject = UnityEngine.Object;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEditor.Experimental.UIElements;
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
namespace UnityEditor.Recorder
{
///
/// Main window class of the Unity 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";
static readonly string s_PrefsFileName = "/../Library/Recorder/recorder.pref";
static readonly string s_StylesFolder = "Styles/";
#if UNITY_2018_2_OR_NEWER
public const string MenuRoot = "Window/General/Recorder/";
public const int MenuRootIndex = 1000;
#else
public const string MenuRoot = "Window/Recorder/";
public const int MenuRootIndex = 2050;
#endif
[MenuItem(MenuRoot + "Recorder Window", false, MenuRootIndex)]
static void ShowRecorderWindow()
{
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.IsRecording())
{
recorderWindow.StartRecording();
}
else
{
recorderWindow.StopRecording();
}
}
static class Styles
{
internal static readonly GUIContent ExitPlayModeLabel = new GUIContent("Exit PlayMode");
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 readonly GUIContent WaitingForPlayModeLabel = new GUIContent("Waiting for Playmode to start...");
}
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_RecordOptionsPanel;
VisualElement m_RecordModeOptionsPanel;
VisualElement m_FrameRateOptionsPanel;
RecorderControllerSettings m_ControllerSettings;
RecorderController m_RecorderController;
enum State
{
Idle,
WaitingForPlayModeToStartRecording,
WaitingForScenesData,
Error,
Recording
}
State m_State = State.Idle;
int m_FrameCount = 0;
RecorderSettingsPrefsEditor m_RecorderSettingsPrefsEditor;
void OnEnable()
{
minSize = new Vector2(560.0f, 200.0f);
#if UNITY_2019_1_OR_NEWER
var root = rootVisualElement;
root.styleSheets.Add(Resources.Load(s_StylesFolder + "recorder"));
root.styleSheets.Add(Resources.Load(s_StylesFolder + (EditorGUIUtility.isProSkin ? "recorder_darkSkin" : "recorder_lightSkin")));
#else
var root = this.GetRootVisualContainer();
root.AddStyleSheetPath(s_StylesFolder + "recorder");
root.AddStyleSheetPath(s_StylesFolder + (EditorGUIUtility.isProSkin ? "recorder_darkSkin" : "recorder_lightSkin"));
#endif
root.style.flexDirection = FlexDirection.Column;
UIElementHelper.SetFocusable(root);
var mainControls = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
minHeight = 110.0f
}
};
root.Add(mainControls);
var controlLeftPane = new VisualElement
{
style =
{
#if UNITY_2018_1
minWidth = 350.0f,
#else
minWidth = 180.0f,
#endif
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 = Resources.Load("recorder_icon"),
}
};
controlLeftPane.Add(m_RecordButtonIcon);
var leftButtonsStack = new VisualElement
{
style =
{
flexDirection = FlexDirection.Column,
}
};
UIElementHelper.SetFlex(leftButtonsStack, 1.0f);
m_RecordButton = new Button(OnRecordButtonClick)
{
name = "recordButton"
};
UpdateRecordButtonText();
leftButtonsStack.Add(m_RecordButton);
m_RecordOptionsPanel = new IMGUIContainer(() =>
{
PrepareGUIState(m_RecordOptionsPanel.layout.width);
RecorderOptions.exitPlayMode = EditorGUILayout.Toggle(Styles.ExitPlayModeLabel, RecorderOptions.exitPlayMode);
})
{
name = "recordOptions"
};
UIElementHelper.SetFlex(m_RecordOptionsPanel, 1.0f);
leftButtonsStack.Add(m_RecordOptionsPanel);
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();
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 New Recorders");
UIElementHelper.SetFlex(addRecordButton, 1.0f);
var recorderListPresetButton = new VisualElement
{
name = "recorderListPreset"
};
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 = 11.0f
}
});
addRecordButton.AddToClassList("RecorderListHeader");
recorderListPresetButton.AddToClassList("RecorderListHeader");
addRecordButton.RegisterCallback(evt => ShowNewRecorderMenu());
m_AddNewRecordPanel = new VisualElement
{
name = "addRecordersButton",
style = { flexDirection = FlexDirection.Row }
};
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_ParametersControl.Add(new Button(OnRecorderSettingPresetClicked)
{
name = "presetButton",
style =
{
alignSelf = Align.FlexEnd,
backgroundImage = PresetHelper.presetIcon,
paddingTop = 0.0f,
paddingLeft = 0.0f,
paddingBottom = 0.0f,
paddingRight = 0.0f,
#if UNITY_2019_1_OR_NEWER
unityBackgroundScaleMode = ScaleMode.ScaleToFit,
#elif UNITY_2018_3_OR_NEWER
backgroundScaleMode = ScaleMode.ScaleToFit,
#else
backgroundSize = ScaleMode.ScaleToFit,
#endif
#if UNITY_2019_1_OR_NEWER
unitySliceTop = 0,
unitySliceBottom = 0,
unitySliceLeft = 0,
unitySliceRight = 0,
#else
sliceTop = 0,
sliceBottom = 0,
sliceLeft = 0,
sliceRight = 0,
#endif
}
});
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_ControllerSettings = RecorderControllerSettings.LoadOrCreate(Application.dataPath + s_PrefsFileName);
m_RecorderController = new RecorderController(m_ControllerSettings);
m_RecorderSettingsPrefsEditor = (RecorderSettingsPrefsEditor) Editor.CreateEditor(m_ControllerSettings);
#if UNITY_2018_2_OR_NEWER
m_RecordingListItem.RegisterCallback(OnRecorderListValidateCommand);
m_RecordingListItem.RegisterCallback(OnRecorderListExecuteCommand);
#else
m_RecordingListItem.RegisterCallback(OnRecorderListIMGUI);
#endif
m_RecordingListItem.RegisterCallback(OnRecorderListKeyUp);
ReloadRecordings();
Undo.undoRedoPerformed += SaveAndRepaint;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
///
/// Used to Start the recording with current settings.
/// If not already the case, the Editor will also switch to PlayMode.
///
public void StartRecording()
{
if (m_State == State.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);
}
#if UNITY_2018_2_OR_NEWER
void OnRecorderListValidateCommand(ValidateCommandEvent evt)
{
RecorderListValidateCommand(evt, evt.commandName);
}
void OnRecorderListExecuteCommand(ExecuteCommandEvent evt)
{
RecorderListExecuteCommand(evt, evt.commandName);
}
#else
void OnRecorderListIMGUI(IMGUIEvent evt)
{
if (evt.imguiEvent.type == EventType.ValidateCommand)
{
RecorderListValidateCommand(evt, evt.imguiEvent.commandName);
}
else if (evt.imguiEvent.type == EventType.ExecuteCommand)
{
RecorderListExecuteCommand(evt, evt.imguiEvent.commandName);
}
}
#endif
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, true);
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);
if (RecorderOptions.ShowLegacyRecorders)
{
newRecordMenu.AddSeparator(string.Empty);
foreach (var info in RecordersInventory.legacyRecorderInfos)
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 hasError = info == null;
var recorderItem = new RecorderItem(m_ControllerSettings, recorderSettings, hasError ? null : info.iconName);
recorderItem.OnEnableStateChanged += enabled =>
{
if (enabled)
m_RecordingListItem.selection = recorderItem;
};
if (hasError)
recorderItem.state = RecorderItem.State.HasErrors;
return recorderItem;
}
string CheckRecordersIncompatibility()
{
var activeRecorders = m_ControllerSettings.RecorderSettings.Where(r => r.Enabled).ToArray();
if (activeRecorders.Length == 0)
return null;
var outputPaths = new Dictionary();
foreach (var recorder in activeRecorders)
{
var path = recorder.fileNameGenerator.BuildAbsolutePath(null); // Does not detect all conflict or might have false positives
if (outputPaths.ContainsKey(path))
return "Recorders '" + outputPaths[path].name + "' and '" + recorder.name + "' might try to save into the same output file.";
outputPaths.Add(path, recorder);
}
var gameViewRecorders = new Dictionary();
foreach (var recorder in activeRecorders)
{
var gameView = recorder.InputsSettings.FirstOrDefault(i => i is GameViewInputSettings) as GameViewInputSettings;
if (gameView != null)
{
if (gameViewRecorders.Any() && !gameViewRecorders.ContainsKey(gameView.outputImageHeight))
{
return "Recorders '" + gameViewRecorders.Values.First().name + "' and '" +
recorder.name +
"' are recording the Game View using different resolutions. This can lead to unexpected behaviour.";
}
gameViewRecorders[gameView.outputImageHeight] = recorder;
}
}
return null;
}
bool ShouldDisableRecordSettings()
{
return IsRecording();
}
void Update()
{
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.HasErrors);
m_RecordModeOptionsPanel.SetEnabled(enable);
m_FrameRateOptionsPanel.SetEnabled(enable);
if (HaveActiveRecordings())
{
if (IsRecording())
{
SetRecordButtonsEnabled(EditorApplication.isPlaying && Time.frameCount - m_FrameCount > 5.0f);
}
else
{
SetRecordButtonsEnabled(true);
}
}
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.");
bool success = m_RecorderController.StartRecording();
if (success)
{
m_State = State.Recording;
m_FrameCount = 0;
}
else
{
StopRecordingInternal();
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()
{
if (RecorderOptions.VerboseMode)
Debug.Log("Stop Recording.");
m_RecorderController.StopRecording();
m_State = State.Idle;
m_FrameCount = 0;
// Settings might have changed after the session ended
m_ControllerSettings.Save();
if (RecorderOptions.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.HasErrors)
{
EditorGUILayout.LabelField("Missing reference to the recorder.");
}
else
{
var editor = m_SelectedRecorderItem.editor;
if (editor == null)
{
EditorGUILayout.LabelField("Error while displaying the recorder inspector");
}
else
{
EditorGUILayout.LabelField("Recorder Type",
ObjectNames.NicifyVariableName(editor.target.GetType().Name));
if (!(editor is RecorderEditor))
EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
EditorGUI.BeginChangeCheck();
editor.OnInspectorGUI();
if (EditorGUI.EndChangeCheck())
{
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, () =>
{
if (EditorUtility.DisplayDialog("Clear Recoder List?", "All recorder will be deleted. Proceed?", "Delete Recorders", "Cancel"))
{
foreach (var item in items)
{
if (item.editor != null)
DestroyImmediate(item.editor);
DeleteRecorder(item, false);
}
ReloadRecordings();
}
});
}
else
{
menu.AddDisabledItem(Styles.ClearRecorderListLabel);
}
menu.ShowAsContext();
}
void OnRecorderSettingPresetClicked()
{
if (m_SelectedRecorderItem != null && m_SelectedRecorderItem.settings != null)
{
var presetReceiver = CreateInstance();
presetReceiver.Init(m_SelectedRecorderItem.settings, Repaint, () => m_ControllerSettings.Save());
PresetSelector.ShowSelector(m_SelectedRecorderItem.settings, null, true, presetReceiver);
}
}
void OnDestroy()
{
if (IsRecording())
StopRecording();
if (m_ControllerSettings != null)
{
m_ControllerSettings.Save();
DestroyImmediate(m_ControllerSettings);
}
if (m_RecorderSettingsPrefsEditor != null)
DestroyImmediate(m_RecorderSettingsPrefsEditor);
Undo.undoRedoPerformed -= SaveAndRepaint;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
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, bool prompt)
{
if (!prompt || EditorUtility.DisplayDialog("Delete Recoder?",
"Are you sure you want to delete '" + item.settings.name + "' ?", "Delete", "Cancel"))
{
var s = item.settings;
m_ControllerSettings.RemoveRecorder(s);
UnityHelpers.Destroy(s, true);
UnityHelpers.Destroy(item.editor, true);
m_RecordingListItem.Remove(item);
}
if (prompt)
Focus();
}
void OnAddNewRecorder(RecorderInfo info)
{
var recorder = RecordersInventory.CreateDefaultRecorderSettings(info.settingsType);
AddLastAndSelect(recorder, ObjectNames.NicifyVariableName(info.displayName), true);
UIElementHelper.SetDirty(m_RecorderSettingPanel);
}
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, true);
}, recorder);
}
contextMenu.ShowAsContext();
}
void OnRecordSelectionChanged()
{
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);
Repaint();
}
bool HaveActiveRecordings()
{
return m_ControllerSettings.RecorderSettings.Any(r => r.Enabled);
}
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()
{
if (m_State == State.Error)
{
if (!HaveActiveRecordings())
{
ShowMessageInStatusBar("Unable to start recording because no recorder has been set.", MessageType.Warning);
}
else
{
ShowMessageInStatusBar("Unable to start recording. Please check Console logs for details.", MessageType.Error);
}
return;
}
if (m_State == State.Idle)
{
if (!HaveActiveRecordings())
{
ShowMessageInStatusBar("No active recorder", MessageType.Info);
}
else
{
var msg = CheckRecordersIncompatibility();
if (string.IsNullOrEmpty(msg))
{
ShowMessageInStatusBar("Ready to start recording", MessageType.None);
}
else
{
ShowMessageInStatusBar(msg, MessageType.Warning);
}
}
return;
}
if (m_State == State.WaitingForPlayModeToStartRecording)
{
EditorGUILayout.LabelField(Styles.WaitingForPlayModeLabel);
return;
}
var recordingSessions = m_RecorderController.GetRecordingSessions();
var session = recordingSessions.FirstOrDefault(); // Hack. We know each session uses the same global settings so take the first one...
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 label = session.frameIndex < settings.StartFrame
? string.Format("Skipping first {0} frame(s)...", settings.StartFrame - 1)
: string.Format("{0} Frame(s) processed", session.frameIndex - settings.StartFrame + 1);
EditorGUI.ProgressBar(progressBarRect, (session.frameIndex + 1) / (float) (settings.EndFrame + 1), label);
break;
}
case RecordMode.TimeInterval:
{
var label = session.currentFrameStartTS < settings.StartTime
? string.Format("Skipping first {0} second(s)...", settings.StartTime)
: string.Format("{0} Frame(s) processed", session.frameIndex - settings.StartFrame + 1);
EditorGUI.ProgressBar(progressBarRect, (float) session.currentFrameStartTS / (settings.EndTime.Equals(0.0f) ? 0.0001f : settings.EndTime), label);
break;
}
}
}
}
}