PageRenderTime 4520ms CodeModel.GetById 24ms RepoModel.GetById 2ms app.codeStats 1ms

/src/com/pblabs/engine/debug/LogViewer.as

http://github.com/PushButtonLabs/PushButtonEngine
ActionScript | 443 lines | 323 code | 73 blank | 47 comment | 67 complexity | f6fdb4bb46fc04492fe941adfcf1ea8a MD5 | raw file
  1. /*******************************************************************************
  2. * PushButton Engine
  3. * Copyright (C) 2009 PushButton Labs, LLC
  4. * For more information see http://www.pushbuttonengine.com
  5. *
  6. * This file is licensed under the terms of the MIT license, which is included
  7. * in the License.html file at the root directory of this SDK.
  8. ******************************************************************************/
  9. package com.pblabs.engine.debug
  10. {
  11. import com.pblabs.engine.PBE;
  12. import com.pblabs.engine.PBUtil;
  13. import com.pblabs.engine.core.IAnimatedObject;
  14. import com.pblabs.engine.core.InputKey;
  15. import flash.display.Bitmap;
  16. import flash.display.BitmapData;
  17. import flash.display.Sprite;
  18. import flash.events.KeyboardEvent;
  19. import flash.events.MouseEvent;
  20. import flash.system.System;
  21. import flash.text.TextField;
  22. import flash.text.TextFieldType;
  23. import flash.text.TextFormat;
  24. import flash.ui.Keyboard;
  25. /**
  26. * Console UI, which shows console log activity in-game, and also accepts input from the user.
  27. */
  28. public class LogViewer extends Sprite implements ILogAppender, IAnimatedObject
  29. {
  30. protected var _messageQueue:Array = [];
  31. protected var _maxLength:uint = 200000;
  32. protected var _truncating:Boolean = false;
  33. protected var _width:uint = 500;
  34. protected var _height:uint = 150;
  35. protected var _consoleHistory:Array = [];
  36. protected var _historyIndex:uint = 0;
  37. protected var _outputBitmap:Bitmap = new Bitmap(new BitmapData(640, 480, false, 0x0));
  38. protected var _input:TextField;
  39. protected var tabCompletionPrefix:String = "";
  40. protected var tabCompletionCurrentStart:int = 0;
  41. protected var tabCompletionCurrentEnd:int = 0;
  42. protected var tabCompletionCurrentOffset:int = 0;
  43. protected var glyphCache:GlyphCache = new GlyphCache();
  44. protected var bottomLineIndex:int = int.MAX_VALUE;
  45. protected var logCache:Array = [];
  46. protected var _dirtyConsole:Boolean = true;
  47. public function LogViewer():void
  48. {
  49. layout();
  50. addListeners();
  51. name = "Console";
  52. Console.registerCommand("copy", onBitmapDoubleClick, "Copy the console to the clipboard.");
  53. Console.registerCommand("clear", onClearCommand, "Clears the console history.");
  54. PBE.processManager.addAnimatedObject(this);
  55. }
  56. protected function layout():void
  57. {
  58. if(!_input) createInputField();
  59. resize();
  60. _outputBitmap.name = "ConsoleOutput";
  61. addEventListener(MouseEvent.CLICK, onBitmapClick);
  62. addEventListener(MouseEvent.DOUBLE_CLICK, onBitmapDoubleClick);
  63. addChild(_outputBitmap);
  64. addChild(_input);
  65. graphics.clear();
  66. graphics.beginFill(0x111111, .95);
  67. graphics.drawRect(0, 0, _width+1, _height);
  68. graphics.endFill();
  69. // Necessary for click listeners.
  70. mouseEnabled = true;
  71. doubleClickEnabled = true;
  72. _dirtyConsole = true;
  73. }
  74. protected function addListeners():void
  75. {
  76. _input.addEventListener(KeyboardEvent.KEY_DOWN, onInputKeyDown, false, 1, true);
  77. }
  78. protected function removeListeners():void
  79. {
  80. _input.removeEventListener(KeyboardEvent.KEY_DOWN, onInputKeyDown);
  81. }
  82. protected function onBitmapClick(me:MouseEvent):void
  83. {
  84. // Give focus to input.
  85. PBE.mainStage.focus = _input;
  86. }
  87. protected function onBitmapDoubleClick(me:MouseEvent = null):void
  88. {
  89. // Put everything into a monster string.
  90. var logString:String = "";
  91. for(var i:int=0; i<logCache.length; i++)
  92. logString += logCache[i].text + "\n";
  93. // Copy content.
  94. System.setClipboard(logString);
  95. Logger.print(this, "Copied console contents to clipboard.");
  96. }
  97. /**
  98. * Wipe the displayed console output.
  99. */
  100. protected function onClearCommand():void
  101. {
  102. logCache = [];
  103. bottomLineIndex = -1;
  104. _dirtyConsole = true;
  105. }
  106. protected function resize():void
  107. {
  108. _outputBitmap.x = 5;
  109. _outputBitmap.y = 0;
  110. _input.x = 5;
  111. if(stage)
  112. {
  113. _width = stage.stageWidth-1;
  114. _height = (stage.stageHeight / 3) * 2;
  115. }
  116. // Resize display surface.
  117. Profiler.enter("LogViewer_resizeBitmap");
  118. _outputBitmap.bitmapData.dispose();
  119. _outputBitmap.bitmapData = new BitmapData(_width - 10, _height - 30, false, 0x0);
  120. Profiler.exit("LogViewer_resizeBitmap");
  121. _input.height = 18;
  122. _input.width = _width-10;
  123. _input.y = _outputBitmap.height + 7;
  124. _dirtyConsole = true;
  125. }
  126. protected function createInputField():TextField
  127. {
  128. _input = new TextField();
  129. _input.type = TextFieldType.INPUT;
  130. _input.border = true;
  131. _input.borderColor = 0xCCCCCC;
  132. _input.multiline = false;
  133. _input.wordWrap = false;
  134. _input.condenseWhite = false;
  135. var format:TextFormat = _input.getTextFormat();
  136. format.font = "_typewriter";
  137. format.size = 11;
  138. format.color = 0xFFFFFF;
  139. _input.setTextFormat(format);
  140. _input.defaultTextFormat = format;
  141. _input.name = "ConsoleInput";
  142. return _input;
  143. }
  144. protected function setHistory(old:String):void
  145. {
  146. _input.text = old;
  147. PBE.callLater(function():void { _input.setSelection(_input.length, _input.length); });
  148. }
  149. protected function onInputKeyDown(event:KeyboardEvent):void
  150. {
  151. // If this was a non-tab input, clear tab completion state.
  152. if(event.keyCode != Keyboard.TAB && event.keyCode != Keyboard.SHIFT)
  153. {
  154. tabCompletionPrefix = _input.text;
  155. tabCompletionCurrentStart = -1;
  156. tabCompletionCurrentOffset = 0;
  157. }
  158. if(event.keyCode == Keyboard.ENTER)
  159. {
  160. // Execute an entered command.
  161. if(_input.text.length <= 0)
  162. {
  163. // display a blank line
  164. addLogMessage("CMD", ">", _input.text);
  165. return;
  166. }
  167. // If Enter was pressed, process the command
  168. processCommand();
  169. }
  170. else if (event.keyCode == Keyboard.UP)
  171. {
  172. // Go to previous command.
  173. if(_historyIndex > 0)
  174. {
  175. setHistory(_consoleHistory[--_historyIndex]);
  176. }
  177. else if (_consoleHistory.length > 0)
  178. {
  179. setHistory(_consoleHistory[0]);
  180. }
  181. event.preventDefault();
  182. }
  183. else if(event.keyCode == Keyboard.DOWN)
  184. {
  185. // Go to next command.
  186. if(_historyIndex < _consoleHistory.length-1)
  187. {
  188. setHistory(_consoleHistory[++_historyIndex]);
  189. }
  190. else if (_historyIndex == _consoleHistory.length-1)
  191. {
  192. _input.text = "";
  193. }
  194. event.preventDefault();
  195. }
  196. else if(event.keyCode == Keyboard.PAGE_UP)
  197. {
  198. // Page the console view up.
  199. if(bottomLineIndex == int.MAX_VALUE)
  200. bottomLineIndex = logCache.length - 1;
  201. bottomLineIndex -= getScreenHeightInLines() - 2;
  202. if(bottomLineIndex < 0)
  203. bottomLineIndex = 0;
  204. }
  205. else if(event.keyCode == Keyboard.PAGE_DOWN)
  206. {
  207. // Page the console view down.
  208. if(bottomLineIndex != int.MAX_VALUE)
  209. {
  210. bottomLineIndex += getScreenHeightInLines() - 2;
  211. if(bottomLineIndex + getScreenHeightInLines() >= logCache.length)
  212. bottomLineIndex = int.MAX_VALUE;
  213. }
  214. }
  215. else if(event.keyCode == Keyboard.TAB)
  216. {
  217. // We are doing tab searching.
  218. var list:Array = Console.getCommandList();
  219. // Is this the first step?
  220. var isFirst:Boolean = false;
  221. if(tabCompletionCurrentStart == -1)
  222. {
  223. tabCompletionPrefix = _input.text.toLowerCase();
  224. tabCompletionCurrentStart = int.MAX_VALUE;
  225. tabCompletionCurrentEnd = -1;
  226. for(var i:int=0; i<list.length; i++)
  227. {
  228. // If we found a prefix match...
  229. if(list[i].name.substr(0, tabCompletionPrefix.length).toLowerCase() == tabCompletionPrefix)
  230. {
  231. // Note it.
  232. if(i < tabCompletionCurrentStart)
  233. tabCompletionCurrentStart = i;
  234. if(i > tabCompletionCurrentEnd)
  235. tabCompletionCurrentEnd = i;
  236. isFirst = true;
  237. }
  238. }
  239. tabCompletionCurrentOffset = tabCompletionCurrentStart;
  240. }
  241. // If there is a match, tab complete.
  242. if(tabCompletionCurrentEnd != -1)
  243. {
  244. // Update offset if appropriate.
  245. if(!isFirst)
  246. {
  247. if(event.shiftKey)
  248. tabCompletionCurrentOffset--;
  249. else
  250. tabCompletionCurrentOffset++;
  251. // Wrap the offset.
  252. if(tabCompletionCurrentOffset < tabCompletionCurrentStart)
  253. {
  254. tabCompletionCurrentOffset = tabCompletionCurrentEnd;
  255. }
  256. else if(tabCompletionCurrentOffset > tabCompletionCurrentEnd)
  257. {
  258. tabCompletionCurrentOffset = tabCompletionCurrentStart;
  259. }
  260. }
  261. // Get the match.
  262. var potentialMatch:String = list[tabCompletionCurrentOffset].name;
  263. // Update the text with the current completion, caret at the end.
  264. _input.text = potentialMatch;
  265. _input.setSelection(potentialMatch.length + 1, potentialMatch.length + 1);
  266. }
  267. // Make sure we keep focus. TODO: This is not ideal, it still flickers the yellow box.
  268. var oldfr:* = stage.stageFocusRect;
  269. stage.stageFocusRect = false;
  270. PBE.callLater(function():void {
  271. stage.focus = _input;
  272. stage.stageFocusRect = oldfr;
  273. });
  274. }
  275. else if(event.keyCode == Console.hotKeyCode)
  276. {
  277. // Hide the console window, have to check here due to
  278. // propagation stop at end of function.
  279. parent.removeChild(this);
  280. deactivate();
  281. }
  282. _dirtyConsole = true;
  283. // Keep console input from propagating up to the stage and messing up the game.
  284. event.stopImmediatePropagation();
  285. }
  286. protected function processCommand():void
  287. {
  288. addLogMessage("CMD", ">", _input.text);
  289. Console.processLine(_input.text);
  290. _consoleHistory.push(_input.text);
  291. _historyIndex = _consoleHistory.length;
  292. _input.text = "";
  293. _dirtyConsole = true;
  294. }
  295. public function getScreenHeightInLines():int
  296. {
  297. var roundedHeight:int = _outputBitmap.bitmapData.height;
  298. return Math.floor(roundedHeight / glyphCache.getLineHeight());
  299. }
  300. public function onFrame(dt:Number):void
  301. {
  302. // Don't draw if we are clean or invisible.
  303. if(_dirtyConsole == false || parent == null)
  304. return;
  305. _dirtyConsole = false;
  306. Profiler.enter("LogViewer.redrawLog");
  307. // Figure our visible range.
  308. var lineHeight:int = getScreenHeightInLines() - 1;
  309. var startLine:int = 0;
  310. var endLine:int = 0;
  311. if(bottomLineIndex == int.MAX_VALUE)
  312. startLine = PBUtil.clamp(logCache.length - lineHeight, 0, int.MAX_VALUE);
  313. else
  314. startLine = PBUtil.clamp(bottomLineIndex - lineHeight, 0, int.MAX_VALUE);
  315. endLine = PBUtil.clamp(startLine + lineHeight, 0, logCache.length - 1);
  316. startLine--;
  317. // Wipe it.
  318. var bd:BitmapData = _outputBitmap.bitmapData;
  319. bd.fillRect(bd.rect, 0x0);
  320. // Draw lines.
  321. for(var i:int=endLine; i>=startLine; i--)
  322. {
  323. // Skip empty.
  324. if(!logCache[i])
  325. continue;
  326. glyphCache.drawLineToBitmap(logCache[i].text, 0, _outputBitmap.height - (endLine+1-i)*glyphCache.getLineHeight(), logCache[i].color, _outputBitmap.bitmapData);
  327. }
  328. Profiler.exit("LogViewer.redrawLog");
  329. }
  330. public function addLogMessage(level:String, loggerName:String, message:String):void
  331. {
  332. var color:String = LogColor.getColor(level);
  333. // Cut down on the logger level if verbosity requests.
  334. if(Console.verbosity < 2)
  335. {
  336. var dotIdx:int = loggerName.lastIndexOf("::");
  337. if(dotIdx != -1)
  338. loggerName = loggerName.substr(dotIdx + 2);
  339. }
  340. // Split message by newline and add to the list.
  341. var messages:Array = message.split("\n");
  342. for each (var msg:String in messages)
  343. {
  344. var text:String = ((Console.verbosity > 0) ? level + ": " : "") + loggerName + " - " + msg;
  345. logCache.push({"color": parseInt(color.substr(1), 16), "text": text});
  346. }
  347. _dirtyConsole = true;
  348. }
  349. public function activate():void
  350. {
  351. layout();
  352. _input.text = "";
  353. addListeners();
  354. PBE.mainStage.focus = _input;
  355. }
  356. public function deactivate():void
  357. {
  358. removeListeners();
  359. PBE.mainStage.focus = null;
  360. }
  361. public function set restrict(value:String):void
  362. {
  363. _input.restrict = value;
  364. }
  365. public function get restrict():String
  366. {
  367. return _input.restrict;
  368. }
  369. }
  370. }