RecorderWindow.cs 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Linq;
  4. using UnityEditor.Presets;
  5. using UnityEngine;
  6. using UnityEditor.Recorder.Input;
  7. using UnityObject = UnityEngine.Object;
  8. #if UNITY_2019_1_OR_NEWER
  9. using UnityEngine.UIElements;
  10. #else
  11. using UnityEditor.Experimental.UIElements;
  12. using UnityEngine.Experimental.UIElements;
  13. using UnityEngine.Experimental.UIElements.StyleEnums;
  14. #endif
  15. namespace UnityEditor.Recorder
  16. {
  17. /// <summary>
  18. /// Main window class of the Unity Recorder.
  19. /// It can be accessed from an Editor script to show the Recorder Window and eventually Start and Stop the recording using current settings.
  20. /// Recorder settings are saved in Library/Recorder/recorder.pref
  21. /// </summary>
  22. public class RecorderWindow : EditorWindow
  23. {
  24. static readonly string s_WindowTitle = "Recorder";
  25. static readonly string s_PrefsFileName = "/../Library/Recorder/recorder.pref";
  26. static readonly string s_StylesFolder = "Styles/";
  27. #if UNITY_2018_2_OR_NEWER
  28. public const string MenuRoot = "Window/General/Recorder/";
  29. public const int MenuRootIndex = 1000;
  30. #else
  31. public const string MenuRoot = "Window/Recorder/";
  32. public const int MenuRootIndex = 2050;
  33. #endif
  34. [MenuItem(MenuRoot + "Recorder Window", false, MenuRootIndex)]
  35. static void ShowRecorderWindow()
  36. {
  37. GetWindow(typeof(RecorderWindow), false, s_WindowTitle);
  38. }
  39. [MenuItem(MenuRoot + "Quick Recording _F10", false, MenuRootIndex + 1)]
  40. static void QuickRecording()
  41. {
  42. var recorderWindow = (RecorderWindow) GetWindow(typeof(RecorderWindow), false, s_WindowTitle, false);
  43. if (!recorderWindow.IsRecording())
  44. {
  45. recorderWindow.StartRecording();
  46. }
  47. else
  48. {
  49. recorderWindow.StopRecording();
  50. }
  51. }
  52. static class Styles
  53. {
  54. internal static readonly GUIContent ExitPlayModeLabel = new GUIContent("Exit PlayMode");
  55. internal static readonly GUIContent DuplicateLabel = new GUIContent("Duplicate");
  56. internal static readonly GUIContent DeleteLabel = new GUIContent("Delete");
  57. internal static readonly GUIContent SaveRecorderListLabel = new GUIContent("Save Recorder List");
  58. internal static readonly GUIContent LoadRecorderListLabel = new GUIContent("Load Recorder List");
  59. internal static readonly GUIContent ClearRecorderListLabel = new GUIContent("Clear Recorder List");
  60. internal static readonly GUIContent WaitingForPlayModeLabel = new GUIContent("Waiting for Playmode to start...");
  61. }
  62. class RecorderItemList : VisualListItem<RecorderItem>
  63. {
  64. }
  65. RecorderItemList m_RecordingListItem;
  66. VisualElement m_SettingsPanel;
  67. VisualElement m_RecordersPanel;
  68. RecorderItem m_SelectedRecorderItem;
  69. VisualElement m_ParametersControl;
  70. VisualElement m_RecorderSettingPanel;
  71. Button m_RecordButton;
  72. Button m_RecordButtonIcon;
  73. PanelSplitter m_PanelSplitter;
  74. VisualElement m_AddNewRecordPanel;
  75. VisualElement m_RecordOptionsPanel;
  76. VisualElement m_RecordModeOptionsPanel;
  77. VisualElement m_FrameRateOptionsPanel;
  78. RecorderControllerSettings m_ControllerSettings;
  79. RecorderController m_RecorderController;
  80. enum State
  81. {
  82. Idle,
  83. WaitingForPlayModeToStartRecording,
  84. WaitingForScenesData,
  85. Error,
  86. Recording
  87. }
  88. State m_State = State.Idle;
  89. int m_FrameCount = 0;
  90. RecorderSettingsPrefsEditor m_RecorderSettingsPrefsEditor;
  91. void OnEnable()
  92. {
  93. minSize = new Vector2(560.0f, 200.0f);
  94. #if UNITY_2019_1_OR_NEWER
  95. var root = rootVisualElement;
  96. root.styleSheets.Add(Resources.Load<StyleSheet>(s_StylesFolder + "recorder"));
  97. root.styleSheets.Add(Resources.Load<StyleSheet>(s_StylesFolder + (EditorGUIUtility.isProSkin ? "recorder_darkSkin" : "recorder_lightSkin")));
  98. #else
  99. var root = this.GetRootVisualContainer();
  100. root.AddStyleSheetPath(s_StylesFolder + "recorder");
  101. root.AddStyleSheetPath(s_StylesFolder + (EditorGUIUtility.isProSkin ? "recorder_darkSkin" : "recorder_lightSkin"));
  102. #endif
  103. root.style.flexDirection = FlexDirection.Column;
  104. UIElementHelper.SetFocusable(root);
  105. var mainControls = new VisualElement
  106. {
  107. style =
  108. {
  109. flexDirection = FlexDirection.Row,
  110. minHeight = 110.0f
  111. }
  112. };
  113. root.Add(mainControls);
  114. var controlLeftPane = new VisualElement
  115. {
  116. style =
  117. {
  118. #if UNITY_2018_1
  119. minWidth = 350.0f,
  120. #else
  121. minWidth = 180.0f,
  122. #endif
  123. maxWidth = 450.0f,
  124. flexDirection = FlexDirection.Row,
  125. }
  126. };
  127. UIElementHelper.SetFlex(controlLeftPane, 0.5f);
  128. var controlRightPane = new VisualElement
  129. {
  130. style =
  131. {
  132. flexDirection = FlexDirection.Column,
  133. }
  134. };
  135. UIElementHelper.SetFlex(controlRightPane, 0.5f);
  136. mainControls.Add(controlLeftPane);
  137. mainControls.Add(controlRightPane);
  138. controlLeftPane.AddToClassList("StandardPanel");
  139. controlRightPane.AddToClassList("StandardPanel");
  140. m_RecordButtonIcon = new Button(OnRecordButtonClick)
  141. {
  142. name = "recorderIcon",
  143. style =
  144. {
  145. backgroundImage = Resources.Load<Texture2D>("recorder_icon"),
  146. }
  147. };
  148. controlLeftPane.Add(m_RecordButtonIcon);
  149. var leftButtonsStack = new VisualElement
  150. {
  151. style =
  152. {
  153. flexDirection = FlexDirection.Column,
  154. }
  155. };
  156. UIElementHelper.SetFlex(leftButtonsStack, 1.0f);
  157. m_RecordButton = new Button(OnRecordButtonClick)
  158. {
  159. name = "recordButton"
  160. };
  161. UpdateRecordButtonText();
  162. leftButtonsStack.Add(m_RecordButton);
  163. m_RecordOptionsPanel = new IMGUIContainer(() =>
  164. {
  165. PrepareGUIState(m_RecordOptionsPanel.layout.width);
  166. RecorderOptions.exitPlayMode = EditorGUILayout.Toggle(Styles.ExitPlayModeLabel, RecorderOptions.exitPlayMode);
  167. })
  168. {
  169. name = "recordOptions"
  170. };
  171. UIElementHelper.SetFlex(m_RecordOptionsPanel, 1.0f);
  172. leftButtonsStack.Add(m_RecordOptionsPanel);
  173. m_RecordModeOptionsPanel = new IMGUIContainer(() =>
  174. {
  175. PrepareGUIState(m_RecordModeOptionsPanel.layout.width);
  176. if (m_RecorderSettingsPrefsEditor.RecordModeGUI())
  177. OnGlobalSettingsChanged();
  178. });
  179. UIElementHelper.SetFlex(m_RecordModeOptionsPanel, 1.0f);
  180. leftButtonsStack.Add(m_RecordModeOptionsPanel);
  181. controlLeftPane.Add(leftButtonsStack);
  182. m_FrameRateOptionsPanel = new IMGUIContainer(() =>
  183. {
  184. PrepareGUIState(m_FrameRateOptionsPanel.layout.width);
  185. if (m_RecorderSettingsPrefsEditor.FrameRateGUI())
  186. OnGlobalSettingsChanged();
  187. });
  188. UIElementHelper.SetFlex(m_FrameRateOptionsPanel, 1.0f);
  189. controlRightPane.Add(m_FrameRateOptionsPanel);
  190. m_SettingsPanel = new ScrollView();
  191. UIElementHelper.SetFlex(m_SettingsPanel, 1.0f);
  192. UIElementHelper.ResetStylePosition(m_SettingsPanel.contentContainer.style);
  193. var recordersAndParameters = new VisualElement
  194. {
  195. style =
  196. {
  197. alignSelf = Align.Stretch,
  198. flexDirection = FlexDirection.Row,
  199. }
  200. };
  201. UIElementHelper.SetFlex(recordersAndParameters, 1.0f);
  202. m_RecordersPanel = new VisualElement
  203. {
  204. name = "recordersPanel",
  205. style =
  206. {
  207. width = 200.0f,
  208. minWidth = 150.0f,
  209. maxWidth = 500.0f
  210. }
  211. };
  212. m_RecordersPanel.AddToClassList("StandardPanel");
  213. m_PanelSplitter = new PanelSplitter(m_RecordersPanel);
  214. recordersAndParameters.Add(m_RecordersPanel);
  215. recordersAndParameters.Add(m_PanelSplitter);
  216. recordersAndParameters.Add(m_SettingsPanel);
  217. m_SettingsPanel.AddToClassList("StandardPanel");
  218. root.Add(recordersAndParameters);
  219. var addRecordButton = new Label("+ Add New Recorders");
  220. UIElementHelper.SetFlex(addRecordButton, 1.0f);
  221. var recorderListPresetButton = new VisualElement
  222. {
  223. name = "recorderListPreset"
  224. };
  225. recorderListPresetButton.RegisterCallback<MouseUpEvent>(evt => ShowRecorderListMenu());
  226. recorderListPresetButton.Add(new Image
  227. {
  228. image = (Texture2D)EditorGUIUtility.Load("Builtin Skins/" + (EditorGUIUtility.isProSkin ? "DarkSkin" : "LightSkin") + "/Images/pane options.png"),
  229. style =
  230. {
  231. width = 16.0f,
  232. height = 11.0f
  233. }
  234. });
  235. addRecordButton.AddToClassList("RecorderListHeader");
  236. recorderListPresetButton.AddToClassList("RecorderListHeader");
  237. addRecordButton.RegisterCallback<MouseUpEvent>(evt => ShowNewRecorderMenu());
  238. m_AddNewRecordPanel = new VisualElement
  239. {
  240. name = "addRecordersButton",
  241. style = { flexDirection = FlexDirection.Row }
  242. };
  243. m_AddNewRecordPanel.Add(addRecordButton);
  244. m_AddNewRecordPanel.Add(recorderListPresetButton);
  245. m_RecordingListItem = new RecorderItemList
  246. {
  247. name = "recorderList"
  248. };
  249. UIElementHelper.SetFlex(m_RecordingListItem, 1.0f);
  250. UIElementHelper.SetFocusable(m_RecordingListItem);
  251. m_RecordingListItem.OnItemContextMenu += OnRecordContextMenu;
  252. m_RecordingListItem.OnSelectionChanged += OnRecordSelectionChanged;
  253. m_RecordingListItem.OnItemRename += item => item.StartRenaming();
  254. m_RecordingListItem.OnContextMenu += ShowNewRecorderMenu;
  255. m_RecordersPanel.Add(m_AddNewRecordPanel);
  256. m_RecordersPanel.Add(m_RecordingListItem);
  257. m_ParametersControl = new VisualElement
  258. {
  259. style =
  260. {
  261. minWidth = 300.0f,
  262. }
  263. };
  264. UIElementHelper.SetFlex(m_ParametersControl, 1.0f);
  265. m_ParametersControl.Add(new Button(OnRecorderSettingPresetClicked)
  266. {
  267. name = "presetButton",
  268. style =
  269. {
  270. alignSelf = Align.FlexEnd,
  271. backgroundImage = PresetHelper.presetIcon,
  272. paddingTop = 0.0f,
  273. paddingLeft = 0.0f,
  274. paddingBottom = 0.0f,
  275. paddingRight = 0.0f,
  276. #if UNITY_2019_1_OR_NEWER
  277. unityBackgroundScaleMode = ScaleMode.ScaleToFit,
  278. #elif UNITY_2018_3_OR_NEWER
  279. backgroundScaleMode = ScaleMode.ScaleToFit,
  280. #else
  281. backgroundSize = ScaleMode.ScaleToFit,
  282. #endif
  283. #if UNITY_2019_1_OR_NEWER
  284. unitySliceTop = 0,
  285. unitySliceBottom = 0,
  286. unitySliceLeft = 0,
  287. unitySliceRight = 0,
  288. #else
  289. sliceTop = 0,
  290. sliceBottom = 0,
  291. sliceLeft = 0,
  292. sliceRight = 0,
  293. #endif
  294. }
  295. });
  296. m_RecorderSettingPanel = new IMGUIContainer(OnRecorderSettingsGUI)
  297. {
  298. name = "recorderSettings"
  299. };
  300. UIElementHelper.SetFlex(m_RecorderSettingPanel, 1.0f);
  301. var statusBar = new VisualElement
  302. {
  303. name = "statusBar"
  304. };
  305. statusBar.Add(new IMGUIContainer(UpdateRecordingProgressGUI));
  306. root.Add(statusBar);
  307. m_ParametersControl.Add(m_RecorderSettingPanel);
  308. m_SettingsPanel.Add(m_ParametersControl);
  309. m_ControllerSettings = RecorderControllerSettings.LoadOrCreate(Application.dataPath + s_PrefsFileName);
  310. m_RecorderController = new RecorderController(m_ControllerSettings);
  311. m_RecorderSettingsPrefsEditor = (RecorderSettingsPrefsEditor) Editor.CreateEditor(m_ControllerSettings);
  312. #if UNITY_2018_2_OR_NEWER
  313. m_RecordingListItem.RegisterCallback<ValidateCommandEvent>(OnRecorderListValidateCommand);
  314. m_RecordingListItem.RegisterCallback<ExecuteCommandEvent>(OnRecorderListExecuteCommand);
  315. #else
  316. m_RecordingListItem.RegisterCallback<IMGUIEvent>(OnRecorderListIMGUI);
  317. #endif
  318. m_RecordingListItem.RegisterCallback<KeyUpEvent>(OnRecorderListKeyUp);
  319. ReloadRecordings();
  320. Undo.undoRedoPerformed += SaveAndRepaint;
  321. EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
  322. }
  323. /// <summary>
  324. /// Used to Start the recording with current settings.
  325. /// If not already the case, the Editor will also switch to PlayMode.
  326. /// </summary>
  327. public void StartRecording()
  328. {
  329. if (m_State == State.Idle)
  330. {
  331. m_State = State.WaitingForPlayModeToStartRecording;
  332. GameViewSize.DisableMaxOnPlay();
  333. EditorApplication.isPlaying = true;
  334. m_FrameCount = Time.frameCount;
  335. }
  336. }
  337. /// <summary>
  338. /// Used to get the current state of the recording.
  339. /// </summary>
  340. /// <returns>True if the Recorder is started or being started. False otherwise.</returns>
  341. public bool IsRecording()
  342. {
  343. return m_State == State.Recording || m_State == State.WaitingForPlayModeToStartRecording;
  344. }
  345. /// <summary>
  346. /// Used to Stop current recordings if any.
  347. /// Exiting PlayMode while the Recorder is recording will automatically Stop the recorder.
  348. /// </summary>
  349. public void StopRecording()
  350. {
  351. if (IsRecording())
  352. StopRecordingInternal();
  353. }
  354. [RuntimeInitializeOnLoadMethod]
  355. static void RuntimeInit()
  356. {
  357. var windows = Resources.FindObjectsOfTypeAll<RecorderWindow>();
  358. if (windows != null && windows.Length > 0)
  359. {
  360. RecorderWindow win = windows[0];
  361. if (win.m_State == State.WaitingForPlayModeToStartRecording)
  362. win.RequestStartRecording();
  363. }
  364. }
  365. void OnPlayModeStateChanged(PlayModeStateChange obj)
  366. {
  367. if (obj == PlayModeStateChange.ExitingPlayMode)
  368. {
  369. if (m_State == State.Recording)
  370. {
  371. StopRecordingInternal();
  372. }
  373. else
  374. {
  375. m_State = State.Idle;
  376. }
  377. }
  378. }
  379. void OnGlobalSettingsChanged()
  380. {
  381. if (m_ControllerSettings == null)
  382. return;
  383. m_ControllerSettings.ApplyGlobalSettingToAllRecorders();
  384. foreach (var recorderItem in m_RecordingListItem.items)
  385. recorderItem.UpdateState();
  386. SaveAndRepaint();
  387. }
  388. void SaveAndRepaint()
  389. {
  390. if (m_ControllerSettings != null)
  391. m_ControllerSettings.Save();
  392. if (m_SelectedRecorderItem != null)
  393. UIElementHelper.SetDirty(m_RecorderSettingPanel);
  394. Repaint();
  395. }
  396. void ReloadRecordings()
  397. {
  398. if (m_ControllerSettings == null)
  399. return;
  400. m_ControllerSettings.ApplyGlobalSettingToAllRecorders();
  401. var recorderItems = m_ControllerSettings.RecorderSettings.Select(CreateRecorderItem).ToArray();
  402. foreach (var recorderItem in recorderItems)
  403. recorderItem.UpdateState();
  404. m_RecordingListItem.Reload(recorderItems);
  405. }
  406. #if UNITY_2018_2_OR_NEWER
  407. void OnRecorderListValidateCommand(ValidateCommandEvent evt)
  408. {
  409. RecorderListValidateCommand(evt, evt.commandName);
  410. }
  411. void OnRecorderListExecuteCommand(ExecuteCommandEvent evt)
  412. {
  413. RecorderListExecuteCommand(evt, evt.commandName);
  414. }
  415. #else
  416. void OnRecorderListIMGUI(IMGUIEvent evt)
  417. {
  418. if (evt.imguiEvent.type == EventType.ValidateCommand)
  419. {
  420. RecorderListValidateCommand(evt, evt.imguiEvent.commandName);
  421. }
  422. else if (evt.imguiEvent.type == EventType.ExecuteCommand)
  423. {
  424. RecorderListExecuteCommand(evt, evt.imguiEvent.commandName);
  425. }
  426. }
  427. #endif
  428. void RecorderListValidateCommand(EventBase evt, string commandName)
  429. {
  430. if (m_RecordingListItem == null || m_RecordingListItem.selection == null)
  431. return;
  432. if (commandName == "Duplicate" || commandName == "SoftDelete" || commandName == "Delete")
  433. {
  434. evt.StopPropagation();
  435. }
  436. }
  437. void RecorderListExecuteCommand(EventBase evt, string commandName)
  438. {
  439. if (m_RecordingListItem == null)
  440. return;
  441. var item = m_RecordingListItem.selection;
  442. if (item == null)
  443. return;
  444. if (commandName == "Duplicate")
  445. {
  446. DuplicateRecorder(item);
  447. evt.StopPropagation();
  448. }
  449. else if (commandName == "SoftDelete" || commandName == "Delete")
  450. {
  451. DeleteRecorder(item, true);
  452. evt.StopPropagation();
  453. }
  454. }
  455. void OnRecorderListKeyUp(KeyUpEvent evt)
  456. {
  457. if (m_RecordingListItem == null)
  458. return;
  459. if (m_RecordingListItem.items == null || m_RecordingListItem.items.Count == 0)
  460. return;
  461. if (!m_RecordingListItem.HasFocus())
  462. return;
  463. if (evt.keyCode == KeyCode.UpArrow || evt.keyCode == KeyCode.DownArrow)
  464. {
  465. if (m_RecordingListItem.selection == null)
  466. {
  467. m_RecordingListItem.selection = m_RecordingListItem.items.First();
  468. }
  469. else
  470. {
  471. var currentIndex = m_RecordingListItem.selectedIndex;
  472. var newIndex = ((evt.keyCode == KeyCode.UpArrow ? currentIndex - 1 : currentIndex + 1) + m_RecordingListItem.items.Count) %
  473. m_RecordingListItem.items.Count;
  474. m_RecordingListItem.selectedIndex = newIndex;
  475. }
  476. evt.StopPropagation();
  477. }
  478. }
  479. void ApplyPreset(string presetPath)
  480. {
  481. var candidate = AssetDatabase.LoadAssetAtPath<RecorderControllerSettingsPreset>(presetPath);
  482. ApplyPreset(candidate);
  483. }
  484. /// <summary>
  485. /// Loads a previously saved Recorder List.
  486. /// </summary>
  487. /// <param name="preset">The instance of Recorder List to load.</param>
  488. public void ApplyPreset(RecorderControllerSettingsPreset preset)
  489. {
  490. if (preset == null)
  491. return;
  492. preset.ApplyTo(m_ControllerSettings);
  493. ReloadRecordings();
  494. }
  495. void ShowNewRecorderMenu()
  496. {
  497. var newRecordMenu = new GenericMenu();
  498. foreach (var info in RecordersInventory.builtInRecorderInfos)
  499. AddRecorderInfoToMenu(info, newRecordMenu);
  500. if (RecorderOptions.ShowLegacyRecorders)
  501. {
  502. newRecordMenu.AddSeparator(string.Empty);
  503. foreach (var info in RecordersInventory.legacyRecorderInfos)
  504. AddRecorderInfoToMenu(info, newRecordMenu);
  505. }
  506. var recorderList = RecordersInventory.customRecorderInfos.ToList();
  507. if (recorderList.Any())
  508. {
  509. newRecordMenu.AddSeparator(string.Empty);
  510. foreach (var info in recorderList)
  511. AddRecorderInfoToMenu(info, newRecordMenu);
  512. }
  513. newRecordMenu.ShowAsContext();
  514. }
  515. void AddRecorderInfoToMenu(RecorderInfo info, GenericMenu menu)
  516. {
  517. if (ShouldDisableRecordSettings())
  518. menu.AddDisabledItem(new GUIContent(info.displayName));
  519. else
  520. menu.AddItem(new GUIContent(info.displayName), false, data => OnAddNewRecorder((RecorderInfo) data), info);
  521. }
  522. RecorderItem CreateRecorderItem(RecorderSettings recorderSettings)
  523. {
  524. var info = RecordersInventory.GetRecorderInfo(recorderSettings.GetType());
  525. var hasError = info == null;
  526. var recorderItem = new RecorderItem(m_ControllerSettings, recorderSettings, hasError ? null : info.iconName);
  527. recorderItem.OnEnableStateChanged += enabled =>
  528. {
  529. if (enabled)
  530. m_RecordingListItem.selection = recorderItem;
  531. };
  532. if (hasError)
  533. recorderItem.state = RecorderItem.State.HasErrors;
  534. return recorderItem;
  535. }
  536. string CheckRecordersIncompatibility()
  537. {
  538. var activeRecorders = m_ControllerSettings.RecorderSettings.Where(r => r.Enabled).ToArray();
  539. if (activeRecorders.Length == 0)
  540. return null;
  541. var outputPaths = new Dictionary<string, RecorderSettings>();
  542. foreach (var recorder in activeRecorders)
  543. {
  544. var path = recorder.fileNameGenerator.BuildAbsolutePath(null); // Does not detect all conflict or might have false positives
  545. if (outputPaths.ContainsKey(path))
  546. return "Recorders '" + outputPaths[path].name + "' and '" + recorder.name + "' might try to save into the same output file.";
  547. outputPaths.Add(path, recorder);
  548. }
  549. var gameViewRecorders = new Dictionary<ImageHeight, RecorderSettings>();
  550. foreach (var recorder in activeRecorders)
  551. {
  552. var gameView = recorder.InputsSettings.FirstOrDefault(i => i is GameViewInputSettings) as GameViewInputSettings;
  553. if (gameView != null)
  554. {
  555. if (gameViewRecorders.Any() && !gameViewRecorders.ContainsKey(gameView.outputImageHeight))
  556. {
  557. return "Recorders '" + gameViewRecorders.Values.First().name + "' and '" +
  558. recorder.name +
  559. "' are recording the Game View using different resolutions. This can lead to unexpected behaviour.";
  560. }
  561. gameViewRecorders[gameView.outputImageHeight] = recorder;
  562. }
  563. }
  564. return null;
  565. }
  566. bool ShouldDisableRecordSettings()
  567. {
  568. return IsRecording();
  569. }
  570. void Update()
  571. {
  572. if (!EditorApplication.isPlaying)
  573. {
  574. if (m_State == State.Recording)
  575. {
  576. StopRecordingInternal();
  577. }
  578. }
  579. else if (m_State == State.WaitingForScenesData && UnityHelpers.AreAllSceneDataLoaded())
  580. {
  581. StartRecordingInternal();
  582. }
  583. var enable = !ShouldDisableRecordSettings();
  584. m_AddNewRecordPanel.SetEnabled(enable);
  585. m_ParametersControl.SetEnabled(enable && m_SelectedRecorderItem != null && m_SelectedRecorderItem.state != RecorderItem.State.HasErrors);
  586. m_RecordModeOptionsPanel.SetEnabled(enable);
  587. m_FrameRateOptionsPanel.SetEnabled(enable);
  588. if (HaveActiveRecordings())
  589. {
  590. if (IsRecording())
  591. {
  592. SetRecordButtonsEnabled(EditorApplication.isPlaying && Time.frameCount - m_FrameCount > 5.0f);
  593. }
  594. else
  595. {
  596. SetRecordButtonsEnabled(true);
  597. }
  598. }
  599. else
  600. {
  601. SetRecordButtonsEnabled(false);
  602. }
  603. UpdateRecordButtonText();
  604. if (m_State == State.Recording)
  605. {
  606. if (!m_RecorderController.IsRecording())
  607. StopRecordingInternal();
  608. Repaint();
  609. }
  610. }
  611. void SetRecordButtonsEnabled(bool enabled)
  612. {
  613. m_RecordButton.SetEnabled(enabled);
  614. m_RecordButtonIcon.SetEnabled(enabled);
  615. }
  616. void StartRecordingInternal()
  617. {
  618. if (RecorderOptions.VerboseMode)
  619. Debug.Log("Start Recording.");
  620. bool success = m_RecorderController.StartRecording();
  621. if (success)
  622. {
  623. m_State = State.Recording;
  624. m_FrameCount = 0;
  625. }
  626. else
  627. {
  628. StopRecordingInternal();
  629. m_State = State.Error;
  630. }
  631. }
  632. void RequestStartRecording()
  633. {
  634. if (RecorderOptions.VerboseMode)
  635. Debug.Log("Prepare and wait all scenes to load.");
  636. m_RecorderController.PrepareRecording();
  637. m_State = State.WaitingForScenesData;
  638. }
  639. void OnRecordButtonClick()
  640. {
  641. if (m_State == State.Error)
  642. m_State = State.Idle;
  643. switch (m_State)
  644. {
  645. case State.Idle:
  646. {
  647. StartRecording();
  648. break;
  649. }
  650. case State.WaitingForPlayModeToStartRecording:
  651. case State.Recording:
  652. {
  653. StopRecording();
  654. break;
  655. }
  656. }
  657. UpdateRecordButtonText();
  658. }
  659. void UpdateRecordButtonText()
  660. {
  661. m_RecordButton.text = m_State == State.Recording ? "STOP RECORDING" : "START RECORDING";
  662. }
  663. void StopRecordingInternal()
  664. {
  665. if (RecorderOptions.VerboseMode)
  666. Debug.Log("Stop Recording.");
  667. m_RecorderController.StopRecording();
  668. m_State = State.Idle;
  669. m_FrameCount = 0;
  670. // Settings might have changed after the session ended
  671. m_ControllerSettings.Save();
  672. if (RecorderOptions.exitPlayMode)
  673. EditorApplication.isPlaying = false;
  674. }
  675. static void PrepareGUIState(float contextWidth)
  676. {
  677. EditorGUIUtility.labelWidth = Mathf.Min(Mathf.Max(contextWidth * 0.45f - 40, 100), 160);
  678. }
  679. void OnRecorderSettingsGUI()
  680. {
  681. PrepareGUIState(m_RecorderSettingPanel.layout.width);
  682. if (m_SelectedRecorderItem != null)
  683. {
  684. if (m_SelectedRecorderItem.state == RecorderItem.State.HasErrors)
  685. {
  686. EditorGUILayout.LabelField("Missing reference to the recorder.");
  687. }
  688. else
  689. {
  690. var editor = m_SelectedRecorderItem.editor;
  691. if (editor == null)
  692. {
  693. EditorGUILayout.LabelField("Error while displaying the recorder inspector");
  694. }
  695. else
  696. {
  697. EditorGUILayout.LabelField("Recorder Type",
  698. ObjectNames.NicifyVariableName(editor.target.GetType().Name));
  699. if (!(editor is RecorderEditor))
  700. EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
  701. EditorGUI.BeginChangeCheck();
  702. editor.OnInspectorGUI();
  703. if (EditorGUI.EndChangeCheck())
  704. {
  705. m_ControllerSettings.Save();
  706. m_SelectedRecorderItem.UpdateState();
  707. UIElementHelper.SetDirty(m_RecorderSettingPanel);
  708. }
  709. }
  710. }
  711. }
  712. else
  713. {
  714. EditorGUILayout.LabelField("No recorder selected");
  715. }
  716. }
  717. void ShowRecorderListMenu()
  718. {
  719. var menu = new GenericMenu();
  720. menu.AddItem(Styles.SaveRecorderListLabel, false, () =>
  721. {
  722. var path = EditorUtility.SaveFilePanelInProject("Save Preset", "RecorderSettingPreset.asset", "asset", "");
  723. if (path.Length != 0)
  724. RecorderControllerSettingsPreset.SaveAtPath(m_ControllerSettings, path);
  725. });
  726. var presets = AssetDatabase.FindAssets("t:" + typeof(RecorderControllerSettingsPreset).Name);
  727. if (presets.Length > 0)
  728. {
  729. foreach (var preset in presets)
  730. {
  731. var path = AssetDatabase.GUIDToAssetPath(preset);
  732. var fileName = Path.GetFileNameWithoutExtension(path);
  733. menu.AddItem(new GUIContent("Load Recorder List/" + fileName), false, data => { ApplyPreset((string)data); }, path);
  734. }
  735. }
  736. else
  737. {
  738. menu.AddDisabledItem(Styles.LoadRecorderListLabel);
  739. }
  740. var items = m_RecordingListItem.items.ToArray();
  741. if (items.Length > 0)
  742. {
  743. menu.AddItem(Styles.ClearRecorderListLabel, false, () =>
  744. {
  745. if (EditorUtility.DisplayDialog("Clear Recoder List?", "All recorder will be deleted. Proceed?", "Delete Recorders", "Cancel"))
  746. {
  747. foreach (var item in items)
  748. {
  749. if (item.editor != null)
  750. DestroyImmediate(item.editor);
  751. DeleteRecorder(item, false);
  752. }
  753. ReloadRecordings();
  754. }
  755. });
  756. }
  757. else
  758. {
  759. menu.AddDisabledItem(Styles.ClearRecorderListLabel);
  760. }
  761. menu.ShowAsContext();
  762. }
  763. void OnRecorderSettingPresetClicked()
  764. {
  765. if (m_SelectedRecorderItem != null && m_SelectedRecorderItem.settings != null)
  766. {
  767. var presetReceiver = CreateInstance<PresetHelper.PresetReceiver>();
  768. presetReceiver.Init(m_SelectedRecorderItem.settings, Repaint, () => m_ControllerSettings.Save());
  769. PresetSelector.ShowSelector(m_SelectedRecorderItem.settings, null, true, presetReceiver);
  770. }
  771. }
  772. void OnDestroy()
  773. {
  774. if (IsRecording())
  775. StopRecording();
  776. if (m_ControllerSettings != null)
  777. {
  778. m_ControllerSettings.Save();
  779. DestroyImmediate(m_ControllerSettings);
  780. }
  781. if (m_RecorderSettingsPrefsEditor != null)
  782. DestroyImmediate(m_RecorderSettingsPrefsEditor);
  783. Undo.undoRedoPerformed -= SaveAndRepaint;
  784. EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
  785. }
  786. void AddLastAndSelect(RecorderSettings recorder, string desiredName, bool enabled)
  787. {
  788. recorder.name = GetUniqueRecorderName(desiredName);
  789. recorder.Enabled = enabled;
  790. m_ControllerSettings.AddRecorderSettings(recorder);
  791. var item = CreateRecorderItem(recorder);
  792. m_RecordingListItem.Add(item);
  793. m_RecordingListItem.selection = item;
  794. m_RecordingListItem.Focus();
  795. }
  796. void DuplicateRecorder(RecorderItem item)
  797. {
  798. var candidate = item.settings;
  799. var copy = Instantiate(candidate);
  800. copy.OnAfterDuplicate();
  801. AddLastAndSelect(copy, candidate.name, candidate.Enabled);
  802. }
  803. void DeleteRecorder(RecorderItem item, bool prompt)
  804. {
  805. if (!prompt || EditorUtility.DisplayDialog("Delete Recoder?",
  806. "Are you sure you want to delete '" + item.settings.name + "' ?", "Delete", "Cancel"))
  807. {
  808. var s = item.settings;
  809. m_ControllerSettings.RemoveRecorder(s);
  810. UnityHelpers.Destroy(s, true);
  811. UnityHelpers.Destroy(item.editor, true);
  812. m_RecordingListItem.Remove(item);
  813. }
  814. if (prompt)
  815. Focus();
  816. }
  817. void OnAddNewRecorder(RecorderInfo info)
  818. {
  819. var recorder = RecordersInventory.CreateDefaultRecorderSettings(info.settingsType);
  820. AddLastAndSelect(recorder, ObjectNames.NicifyVariableName(info.displayName), true);
  821. UIElementHelper.SetDirty(m_RecorderSettingPanel);
  822. }
  823. string GetUniqueRecorderName(string desiredName)
  824. {
  825. return ObjectNames.GetUniqueName(m_ControllerSettings.RecorderSettings.Select(r => r.name).ToArray(),
  826. desiredName);
  827. }
  828. void OnRecordContextMenu(RecorderItem recorder)
  829. {
  830. var contextMenu = new GenericMenu();
  831. if (ShouldDisableRecordSettings())
  832. {
  833. contextMenu.AddDisabledItem(Styles.DuplicateLabel);
  834. contextMenu.AddDisabledItem(Styles.DeleteLabel);
  835. }
  836. else
  837. {
  838. contextMenu.AddItem(Styles.DuplicateLabel, false,
  839. data =>
  840. {
  841. DuplicateRecorder((RecorderItem) data);
  842. }, recorder);
  843. contextMenu.AddItem(Styles.DeleteLabel, false,
  844. data =>
  845. {
  846. DeleteRecorder((RecorderItem) data, true);
  847. }, recorder);
  848. }
  849. contextMenu.ShowAsContext();
  850. }
  851. void OnRecordSelectionChanged()
  852. {
  853. m_SelectedRecorderItem = m_RecordingListItem.selection;
  854. foreach (var r in m_RecordingListItem.items)
  855. {
  856. r.SetItemSelected(m_SelectedRecorderItem == r);
  857. }
  858. if (m_SelectedRecorderItem != null)
  859. UIElementHelper.SetDirty(m_RecorderSettingPanel);
  860. Repaint();
  861. }
  862. bool HaveActiveRecordings()
  863. {
  864. return m_ControllerSettings.RecorderSettings.Any(r => r.Enabled);
  865. }
  866. static void ShowMessageInStatusBar(string msg, MessageType messageType)
  867. {
  868. var r = EditorGUILayout.GetControlRect();
  869. if (messageType != MessageType.None)
  870. {
  871. var iconR = r;
  872. iconR.width = iconR.height;
  873. var icon = messageType == MessageType.Error
  874. ? StatusBarHelper.errorIcon
  875. : (messageType == MessageType.Warning ? StatusBarHelper.warningIcon : StatusBarHelper.infoIcon);
  876. GUI.DrawTexture(iconR, icon);
  877. r.xMin = iconR.xMax + 5.0f;
  878. }
  879. var style = messageType == MessageType.Error
  880. ? StatusBarHelper.errorStyle
  881. : (messageType == MessageType.Warning ? StatusBarHelper.warningStyle : StatusBarHelper.infoStyle);
  882. GUI.Label(r, msg, style);
  883. }
  884. void UpdateRecordingProgressGUI()
  885. {
  886. if (m_State == State.Error)
  887. {
  888. if (!HaveActiveRecordings())
  889. {
  890. ShowMessageInStatusBar("Unable to start recording because no recorder has been set.", MessageType.Warning);
  891. }
  892. else
  893. {
  894. ShowMessageInStatusBar("Unable to start recording. Please check Console logs for details.", MessageType.Error);
  895. }
  896. return;
  897. }
  898. if (m_State == State.Idle)
  899. {
  900. if (!HaveActiveRecordings())
  901. {
  902. ShowMessageInStatusBar("No active recorder", MessageType.Info);
  903. }
  904. else
  905. {
  906. var msg = CheckRecordersIncompatibility();
  907. if (string.IsNullOrEmpty(msg))
  908. {
  909. ShowMessageInStatusBar("Ready to start recording", MessageType.None);
  910. }
  911. else
  912. {
  913. ShowMessageInStatusBar(msg, MessageType.Warning);
  914. }
  915. }
  916. return;
  917. }
  918. if (m_State == State.WaitingForPlayModeToStartRecording)
  919. {
  920. EditorGUILayout.LabelField(Styles.WaitingForPlayModeLabel);
  921. return;
  922. }
  923. var recordingSessions = m_RecorderController.GetRecordingSessions();
  924. var session = recordingSessions.FirstOrDefault(); // Hack. We know each session uses the same global settings so take the first one...
  925. if (session == null)
  926. return;
  927. var progressBarRect = EditorGUILayout.GetControlRect();
  928. var settings = session.settings;
  929. switch (settings.RecordMode)
  930. {
  931. case RecordMode.Manual:
  932. {
  933. var label = string.Format("{0} Frame(s) processed", session.frameIndex);
  934. EditorGUI.ProgressBar(progressBarRect, 0, label);
  935. break;
  936. }
  937. case RecordMode.SingleFrame:
  938. case RecordMode.FrameInterval:
  939. {
  940. var label = session.frameIndex < settings.StartFrame
  941. ? string.Format("Skipping first {0} frame(s)...", settings.StartFrame - 1)
  942. : string.Format("{0} Frame(s) processed", session.frameIndex - settings.StartFrame + 1);
  943. EditorGUI.ProgressBar(progressBarRect, (session.frameIndex + 1) / (float) (settings.EndFrame + 1), label);
  944. break;
  945. }
  946. case RecordMode.TimeInterval:
  947. {
  948. var label = session.currentFrameStartTS < settings.StartTime
  949. ? string.Format("Skipping first {0} second(s)...", settings.StartTime)
  950. : string.Format("{0} Frame(s) processed", session.frameIndex - settings.StartFrame + 1);
  951. EditorGUI.ProgressBar(progressBarRect, (float) session.currentFrameStartTS / (settings.EndTime.Equals(0.0f) ? 0.0001f : settings.EndTime), label);
  952. break;
  953. }
  954. }
  955. }
  956. }
  957. }