PageRenderTime 4690ms CodeModel.GetById 26ms RepoModel.GetById 2ms app.codeStats 0ms

/org/haxel/system/debug/VCR.hx

http://github.com/sluther/haXel
Haxe | 609 lines | 386 code | 68 blank | 155 comment | 93 complexity | b6ef37e0a8d0cd9cf5b80e060c88ec04 MD5 | raw file
  1. package org.haxel.system.debug;
  2. import flash.display.Bitmap;
  3. import flash.display.BitmapData;
  4. import flash.display.Sprite;
  5. import flash.events.Event;
  6. import flash.events.IOErrorEvent;
  7. import flash.events.MouseEvent;
  8. import flash.net.FileFilter;
  9. import flash.net.FileReference;
  10. import flash.text.TextField;
  11. import flash.text.TextFormat;
  12. import flash.utils.ByteArray;
  13. import org.haxel.HxlG;
  14. import org.haxel.HxlU;
  15. import org.haxel.system.HxlReplay;
  16. import org.haxel.system.replay.FrameRecord;
  17. import org.haxel.system.replay.MouseRecord;
  18. /**
  19. * This class contains the record, stop, play, and step 1 frame buttons seen on the top edge of the debugger overlay.
  20. *
  21. * @author Adam Atomic
  22. */
  23. class VCR extends Sprite
  24. {
  25. /* [Embed(source="../../data/vcr/open.png")]
  26. [Embed(source="../../data/vcr/record_off.png")]
  27. [Embed(source="../../data/vcr/record_on.png")]
  28. [Embed(source="../../data/vcr/stop.png")]
  29. [Embed(source="../../data/vcr/flixel.png")]
  30. [Embed(source="../../data/vcr/restart.png")]
  31. [Embed(source="../../data/vcr/pause.png")]
  32. [Embed(source="../../data/vcr/play.png")]
  33. [Embed(source="../../data/vcr/step.png")]*/
  34. private var ImgOpen:Class<HxlBasic>;
  35. private var ImgRecordOff:Class<HxlBasic>;
  36. private var ImgRecordOn:Class<HxlBasic>;
  37. private var ImgStop:Class<HxlBasic>;
  38. private var ImgHaxel:Class<HxlBasic>;
  39. private var ImgRestart:Class<HxlBasic>;
  40. private var ImgPause:Class<HxlBasic>;
  41. private var ImgPlay:Class<HxlBasic>;
  42. private var ImgStep:Class<HxlBasic>;
  43. private static inline var FILE_TYPES:Array<Dynamic> = [new FileFilter("Haxel Game Recording", "*.fgr")];
  44. private static inline var DEFAULT_FILE_NAME:String = "replay.fgr";
  45. /**
  46. * Whether the debugger has been paused.
  47. */
  48. public var paused:Bool;
  49. /**
  50. * Whether a "1 frame step forward" was requested.
  51. */
  52. public var stepRequested:Bool;
  53. private var _open:Bitmap;
  54. private var _recordOff:Bitmap;
  55. private var _recordOn:Bitmap;
  56. private var _stop:Bitmap;
  57. private var _flixel:Bitmap;
  58. private var _restart:Bitmap;
  59. private var _pause:Bitmap;
  60. private var _play:Bitmap;
  61. private var _step:Bitmap;
  62. private var _overOpen:Bool;
  63. private var _overRecord:Bool;
  64. private var _overRestart:Bool;
  65. private var _overPause:Bool;
  66. private var _overStep:Bool;
  67. private var _pressingOpen:Bool;
  68. private var _pressingRecord:Bool;
  69. private var _pressingRestart:Bool;
  70. private var _pressingPause:Bool;
  71. private var _pressingStep:Bool;
  72. private var _file:FileReference;
  73. private var _runtimeDisplay:TextField;
  74. private var _runtime:UInt;
  75. /**
  76. * Creates the "VCR" control panel for debugger pausing, stepping, and recording.
  77. */
  78. public function new()
  79. {
  80. super();
  81. var spacing:UInt = 7;
  82. _open = new ImgOpen();
  83. addChild(_open);
  84. _recordOff = new ImgRecordOff();
  85. _recordOff.x = _open.x + _open.width + spacing;
  86. addChild(_recordOff);
  87. _recordOn = new ImgRecordOn();
  88. _recordOn.x = _recordOff.x;
  89. _recordOn.visible = false;
  90. addChild(_recordOn);
  91. _stop = new ImgStop();
  92. _stop.x = _recordOff.x;
  93. _stop.visible = false;
  94. addChild(_stop);
  95. _flixel = new ImgHaxel();
  96. _flixel.x = _recordOff.x + _recordOff.width + spacing;
  97. addChild(_flixel);
  98. _restart = new ImgRestart();
  99. _restart.x = _flixel.x + _flixel.width + spacing;
  100. addChild(_restart);
  101. _pause = new ImgPause();
  102. _pause.x = _restart.x + _restart.width + spacing;
  103. addChild(_pause);
  104. _play = new ImgPlay();
  105. _play.x = _pause.x;
  106. _play.visible = false;
  107. addChild(_play);
  108. _step = new ImgStep();
  109. _step.x = _pause.x + _pause.width + spacing;
  110. addChild(_step);
  111. _runtimeDisplay = new TextField();
  112. _runtimeDisplay.width = width;
  113. _runtimeDisplay.x = width;
  114. _runtimeDisplay.y = -2;
  115. _runtimeDisplay.multiline = false;
  116. _runtimeDisplay.wordWrap = false;
  117. _runtimeDisplay.selectable = false;
  118. _runtimeDisplay.defaultTextFormat = new TextFormat("Courier",12,0xffffff,false,false,false,null,null,"center", null, null, null, null);
  119. _runtimeDisplay.visible = false;
  120. addChild(_runtimeDisplay);
  121. _runtime = 0;
  122. stepRequested = false;
  123. _file = null;
  124. unpress();
  125. checkOver();
  126. updateGUI();
  127. addEventListener(Event.ENTER_FRAME,init);
  128. }
  129. /**
  130. * Clean up memory.
  131. */
  132. public function destroy():Void
  133. {
  134. _file = null;
  135. removeChild(_open);
  136. _open = null;
  137. removeChild(_recordOff);
  138. _recordOff = null;
  139. removeChild(_recordOn);
  140. _recordOn = null;
  141. removeChild(_stop);
  142. _stop = null;
  143. removeChild(_flixel);
  144. _flixel = null;
  145. removeChild(_restart);
  146. _restart = null;
  147. removeChild(_pause);
  148. _pause = null;
  149. removeChild(_play);
  150. _play = null;
  151. removeChild(_step);
  152. _step = null;
  153. parent.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
  154. parent.removeEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
  155. parent.removeEventListener(MouseEvent.MOUSE_UP,onMouseUp);
  156. }
  157. /**
  158. * Usually called by HxlGame when a requested recording has begun.
  159. * Just updates the VCR GUI so the buttons are in the right state.
  160. */
  161. public function recording():Void
  162. {
  163. _stop.visible = false;
  164. _recordOff.visible = false;
  165. _recordOn.visible = true;
  166. }
  167. /**
  168. * Usually called by HxlGame when a replay has been stopped.
  169. * Just updates the VCR GUI so the buttons are in the right state.
  170. */
  171. public function stopped():Void
  172. {
  173. _stop.visible = false;
  174. _recordOn.visible = false;
  175. _recordOff.visible = true;
  176. }
  177. /**
  178. * Usually called by HxlGame when a requested replay has begun.
  179. * Just updates the VCR GUI so the buttons are in the right state.
  180. */
  181. public function playing():Void
  182. {
  183. _recordOff.visible = false;
  184. _recordOn.visible = false;
  185. _stop.visible = true;
  186. }
  187. /**
  188. * Just updates the VCR GUI so the runtime displays roughly the right thing.
  189. */
  190. public function updateRuntime(Time:UInt):Void
  191. {
  192. _runtime += Time;
  193. _runtimeDisplay.text = HxlU.formatTime(_runtime/1000,true);
  194. if(!_runtimeDisplay.visible)
  195. _runtimeDisplay.visible = true;
  196. }
  197. //*** ACTUAL BUTTON BEHAVIORS ***//
  198. /**
  199. * Called when the "open file" button is pressed.
  200. * Opens the file dialog and registers event handlers for the file dialog.
  201. */
  202. public function onOpen():Void
  203. {
  204. _file = new FileReference();
  205. _file.addEventListener(Event.SELECT, onOpenSelect);
  206. _file.addEventListener(Event.CANCEL, onOpenCancel);
  207. _file.browse(FILE_TYPES);
  208. }
  209. /**
  210. * Called when a file is picked from the file dialog.
  211. * Attempts to load the file and registers file loading event handlers.
  212. *
  213. * @param E Flash event.
  214. */
  215. private function onOpenSelect(E:Event=null):Void
  216. {
  217. _file.removeEventListener(Event.SELECT, onOpenSelect);
  218. _file.removeEventListener(Event.CANCEL, onOpenCancel);
  219. _file.addEventListener(Event.COMPLETE, onOpenComplete);
  220. _file.addEventListener(IOErrorEvent.IO_ERROR, onOpenError);
  221. _file.load();
  222. }
  223. /**
  224. * Called when a file is opened successfully.
  225. * If there's stuff inside, then the contents are loaded into a new replay.
  226. *
  227. * @param E Flash Event.
  228. */
  229. private function onOpenComplete(E:Event=null):Void
  230. {
  231. _file.removeEventListener(Event.COMPLETE, onOpenComplete);
  232. _file.removeEventListener(IOErrorEvent.IO_ERROR, onOpenError);
  233. //Turn the file into a giant string
  234. var fileContents:String = null;
  235. var data:ByteArray = _file.data;
  236. if(data != null)
  237. fileContents = data.readUTFBytes(data.bytesAvailable);
  238. _file = null;
  239. if((fileContents == null) || (fileContents.length <= 0))
  240. {
  241. HxlG.log("ERROR: Empty flixel gameplay record.");
  242. return;
  243. }
  244. HxlG.loadReplay(fileContents);
  245. }
  246. /**
  247. * Called if the open file dialog is canceled.
  248. *
  249. * @param E Flash Event.
  250. */
  251. private function onOpenCancel(E:Event=null):Void
  252. {
  253. _file.removeEventListener(Event.SELECT, onOpenSelect);
  254. _file.removeEventListener(Event.CANCEL, onOpenCancel);
  255. _file = null;
  256. }
  257. /**
  258. * Called if there is a file open error.
  259. *
  260. * @param E Flash Event.
  261. */
  262. private function onOpenError(E:Event=null):Void
  263. {
  264. _file.removeEventListener(Event.COMPLETE, onOpenComplete);
  265. _file.removeEventListener(IOErrorEvent.IO_ERROR, onOpenError);
  266. _file = null;
  267. HxlG.log("ERROR: Unable to open flixel gameplay record.");
  268. }
  269. /**
  270. * Called when the user presses the white record button.
  271. * If Alt is pressed, the current state is reset, and a new recording is requested.
  272. * If Alt is NOT pressed, the game is reset, and a new recording is requested.
  273. *
  274. * @param StandardMode Whether to reset the whole game, or just this <code>HxlState</code>. StandardMode == false is useful for recording demos or attract modes.
  275. */
  276. public function onRecord(StandardMode:Bool=false):Void
  277. {
  278. if(_play.visible)
  279. onPlay();
  280. HxlG.recordReplay(StandardMode);
  281. }
  282. /**
  283. * Called when the user presses the red record button.
  284. * Stops the current recording, opens the save file dialog, and registers event handlers.
  285. */
  286. public function stopRecording():Void
  287. {
  288. var data:String = HxlG.stopRecording();
  289. if((data != null) && (data.length > 0))
  290. {
  291. _file = new FileReference();
  292. _file.addEventListener(Event.COMPLETE, onSaveComplete);
  293. _file.addEventListener(Event.CANCEL,onSaveCancel);
  294. _file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
  295. _file.save(data, DEFAULT_FILE_NAME);
  296. }
  297. }
  298. /**
  299. * Called when the file is saved successfully.
  300. *
  301. * @param E Flash Event.
  302. */
  303. private function onSaveComplete(E:Event=null):Void
  304. {
  305. _file.removeEventListener(Event.COMPLETE, onSaveComplete);
  306. _file.removeEventListener(Event.CANCEL,onSaveCancel);
  307. _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
  308. _file = null;
  309. HxlG.log("FLIXEL: successfully saved flixel gameplay record.");
  310. }
  311. /**
  312. * Called when the save file dialog is cancelled.
  313. *
  314. * @param E Flash Event.
  315. */
  316. private function onSaveCancel(E:Event=null):Void
  317. {
  318. _file.removeEventListener(Event.COMPLETE, onSaveComplete);
  319. _file.removeEventListener(Event.CANCEL,onSaveCancel);
  320. _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
  321. _file = null;
  322. }
  323. /**
  324. * Called if there is an error while saving the gameplay recording.
  325. *
  326. * @param E Flash Event.
  327. */
  328. private function onSaveError(E:Event=null):Void
  329. {
  330. _file.removeEventListener(Event.COMPLETE, onSaveComplete);
  331. _file.removeEventListener(Event.CANCEL,onSaveCancel);
  332. _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
  333. _file = null;
  334. HxlG.log("ERROR: problem saving flixel gameplay record.");
  335. }
  336. /**
  337. * Called when the user presses the stop button.
  338. * Stops the current replay.
  339. */
  340. public function onStop():Void
  341. {
  342. HxlG.stopReplay();
  343. }
  344. /**
  345. * Called when the user presses the Rewind-looking button.
  346. * If Alt is pressed, the entire game is reset.
  347. * If Alt is NOT pressed, only the current state is reset.
  348. * The GUI is updated accordingly.
  349. *
  350. * @param StandardMode Whether to reset the current game (== true), or just the current state. Just resetting the current state can be very handy for debugging.
  351. */
  352. public function onRestart(StandardMode:Bool=false):Void
  353. {
  354. if(HxlG.reloadReplay(StandardMode))
  355. {
  356. _recordOff.visible = false;
  357. _recordOn.visible = false;
  358. _stop.visible = true;
  359. }
  360. }
  361. /**
  362. * Called when the user presses the Pause button.
  363. * This is different from user-defined pause behavior, or focus lost behavior.
  364. * Does NOT pause music playback!!
  365. */
  366. public function onPause():Void
  367. {
  368. paused = true;
  369. _pause.visible = false;
  370. _play.visible = true;
  371. }
  372. /**
  373. * Called when the user presses the Play button.
  374. * This is different from user-defined unpause behavior, or focus gained behavior.
  375. */
  376. public function onPlay():Void
  377. {
  378. paused = false;
  379. _play.visible = false;
  380. _pause.visible = true;
  381. }
  382. /**
  383. * Called when the user presses the fast-forward-looking button.
  384. * Requests a 1-frame step forward in the game loop.
  385. */
  386. public function onStep():Void
  387. {
  388. if(!paused)
  389. onPause();
  390. stepRequested = true;
  391. }
  392. //***EVENT HANDLERS***//
  393. /**
  394. * Just sets up basic mouse listeners, a la HxlWindow.
  395. *
  396. * @param E Flash event.
  397. */
  398. private function init(E:Event=null):Void
  399. {
  400. if(root == null)
  401. return;
  402. removeEventListener(Event.ENTER_FRAME,init);
  403. parent.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
  404. parent.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
  405. parent.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
  406. }
  407. /**
  408. * If the mouse moves, check to see if any buttons should be highlighted.
  409. *
  410. * @param E Flash mouse event.
  411. */
  412. private function onMouseMove(E:MouseEvent=null):Void
  413. {
  414. if(!checkOver())
  415. unpress();
  416. updateGUI();
  417. }
  418. /**
  419. * If the mouse is pressed down, check to see if the user started pressing down a specific button.
  420. *
  421. * @param E Flash mouse event.
  422. */
  423. private function onMouseDown(E:MouseEvent=null):Void
  424. {
  425. unpress();
  426. if(_overOpen)
  427. _pressingOpen = true;
  428. if(_overRecord)
  429. _pressingRecord = true;
  430. if(_overRestart)
  431. _pressingRestart = true;
  432. if(_overPause)
  433. _pressingPause = true;
  434. if(_overStep)
  435. _pressingStep = true;
  436. }
  437. /**
  438. * If the mouse is released, check to see if it was released over a button that was pressed.
  439. * If it was, take the appropriate action based on button state and visibility.
  440. *
  441. * @param E Flash mouse event.
  442. */
  443. private function onMouseUp(E:MouseEvent=null):Void
  444. {
  445. if(_overOpen && _pressingOpen)
  446. onOpen();
  447. else if(_overRecord && _pressingRecord)
  448. {
  449. if(_stop.visible)
  450. onStop();
  451. else if(_recordOn.visible)
  452. stopRecording();
  453. else
  454. onRecord(!E.altKey);
  455. }
  456. else if(_overRestart && _pressingRestart)
  457. onRestart(!E.altKey);
  458. else if(_overPause && _pressingPause)
  459. {
  460. if(_play.visible)
  461. onPlay();
  462. else
  463. onPause();
  464. }
  465. else if(_overStep && _pressingStep)
  466. onStep();
  467. unpress();
  468. checkOver();
  469. updateGUI();
  470. }
  471. //***MISC GUI MGMT STUFF***//
  472. /**
  473. * This function checks to see what button the mouse is currently over.
  474. * Has some special behavior based on whether a recording is happening or not.
  475. *
  476. * @return Whether the mouse was over any buttons or not.
  477. */
  478. private function checkOver():Bool
  479. {
  480. _overOpen = _overRecord = _overRestart = _overPause = _overStep = false;
  481. if((mouseX < 0) || (mouseX > width) || (mouseY < 0) || (mouseY > 15))
  482. return false;
  483. if((mouseX >= _recordOff.x) && (mouseX <= _recordOff.x + _recordOff.width))
  484. _overRecord = true;
  485. if(!_recordOn.visible && !_overRecord)
  486. {
  487. if((mouseX >= _open.x) && (mouseX <= _open.x + _open.width))
  488. _overOpen = true;
  489. else if((mouseX >= _restart.x) && (mouseX <= _restart.x + _restart.width))
  490. _overRestart = true;
  491. else if((mouseX >= _pause.x) && (mouseX <= _pause.x + _pause.width))
  492. _overPause = true;
  493. else if((mouseX >= _step.x) && (mouseX <= _step.x + _step.width))
  494. _overStep = true;
  495. }
  496. return true;
  497. }
  498. /**
  499. * Sets all the pressed state variables for the buttons to false.
  500. */
  501. private function unpress():Void
  502. {
  503. _pressingOpen = false;
  504. _pressingRecord = false;
  505. _pressingRestart = false;
  506. _pressingPause = false;
  507. _pressingStep = false;
  508. }
  509. /**
  510. * Figures out what buttons to highlight based on the _overWhatever and _pressingWhatever variables.
  511. */
  512. private function updateGUI():Void
  513. {
  514. if(_recordOn.visible)
  515. {
  516. _open.alpha = _restart.alpha = _pause.alpha = _step.alpha = 0.35;
  517. _recordOn.alpha = 1.0;
  518. return;
  519. }
  520. if(_overOpen && (_open.alpha != 1.0))
  521. _open.alpha = 1.0;
  522. else if(!_overOpen && (_open.alpha != 0.8))
  523. _open.alpha = 0.8;
  524. if(_overRecord && (_recordOff.alpha != 1.0))
  525. _recordOff.alpha = _recordOn.alpha = _stop.alpha = 1.0;
  526. else if(!_overRecord && (_recordOff.alpha != 0.8))
  527. _recordOff.alpha = _recordOn.alpha = _stop.alpha = 0.8;
  528. if(_overRestart && (_restart.alpha != 1.0))
  529. _restart.alpha = 1.0;
  530. else if(!_overRestart && (_restart.alpha != 0.8))
  531. _restart.alpha = 0.8;
  532. if(_overPause && (_pause.alpha != 1.0))
  533. _pause.alpha = _play.alpha = 1.0;
  534. else if(!_overPause && (_pause.alpha != 0.8))
  535. _pause.alpha = _play.alpha = 0.8;
  536. if(_overStep && (_step.alpha != 1.0))
  537. _step.alpha = 1.0;
  538. else if(!_overStep && (_step.alpha != 0.8))
  539. _step.alpha = 0.8;
  540. }
  541. }