DebugCommandUI.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // DebugCommandUI.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Input;
  14. using Microsoft.Xna.Framework.Graphics;
  15. #endregion
  16. namespace PerformanceMeasuring.GameDebugTools
  17. {
  18. /// <summary>
  19. /// Command Window class for Debug purpose.
  20. /// </summary>
  21. /// <remarks>
  22. /// Debug command UI that runs in the Game.
  23. /// You can type commands using the keyboard, even on the Xbox
  24. /// just connect a USB keyboard to it
  25. /// This works on all 3 platforms (Xbox, Windows, Phone)
  26. ///
  27. /// How to Use:
  28. /// 1) Add this component to the game.
  29. /// 2) Register command by RegisterCommand method.
  30. /// 3) Open/Close Debug window by Tab key.
  31. /// </remarks>
  32. public class DebugCommandUI : DrawableGameComponent, IDebugCommandHost
  33. {
  34. #region Constants
  35. /// <summary>
  36. /// Maximum lines that shows in Debug command window.
  37. /// </summary>
  38. const int MaxLineCount = 20;
  39. /// <summary>
  40. /// Maximum command history number.
  41. /// </summary>
  42. const int MaxCommandHistory = 32;
  43. /// <summary>
  44. /// Cursor character.
  45. /// </summary>
  46. const string Cursor = "_";
  47. /// <summary>
  48. /// Default Prompt string.
  49. /// </summary>
  50. public const string DefaultPrompt = "CMD>";
  51. #endregion
  52. #region Properties
  53. /// <summary>
  54. /// Gets/Sets Prompt string.
  55. /// </summary>
  56. public string Prompt { get; set; }
  57. /// <summary>
  58. /// Is it waiting for key inputs?
  59. /// </summary>
  60. public bool Focused { get { return state != State.Closed; } }
  61. #endregion
  62. #region Fields
  63. // Command window states.
  64. enum State
  65. {
  66. Closed,
  67. Opening,
  68. Opened,
  69. Closing
  70. }
  71. /// <summary>
  72. /// CommandInfo class that contains information to run the command.
  73. /// </summary>
  74. class CommandInfo
  75. {
  76. public CommandInfo(
  77. string command, string description, DebugCommandExecute callback)
  78. {
  79. this.command = command;
  80. this.description = description;
  81. this.callback = callback;
  82. }
  83. // command name
  84. public string command;
  85. // Description of command.
  86. public string description;
  87. // delegate for execute the command.
  88. public DebugCommandExecute callback;
  89. }
  90. // Reference to DebugManager.
  91. private DebugManager debugManager;
  92. // Current state
  93. private State state = State.Closed;
  94. // timer for state transition.
  95. private float stateTransition;
  96. // Registered echo listeners.
  97. List<IDebugEchoListner> listenrs = new List<IDebugEchoListner>();
  98. // Registered command executioner.
  99. Stack<IDebugCommandExecutioner> executioners = new Stack<IDebugCommandExecutioner>();
  100. // Registered commands
  101. private Dictionary<string, CommandInfo> commandTable =
  102. new Dictionary<string, CommandInfo>();
  103. // Current command line string and cursor position.
  104. private string commandLine = String.Empty;
  105. private int cursorIndex = 0;
  106. private Queue<string> lines = new Queue<string>();
  107. // Command history buffer.
  108. private List<string> commandHistory = new List<string>();
  109. // Selecting command history index.
  110. private int commandHistoryIndex;
  111. #region variables for keyboard input handling.
  112. // Previous frame keyboard state.
  113. private KeyboardState prevKeyState;
  114. // Key that pressed last frame.
  115. private Keys pressedKey;
  116. // Timer for key repeating.
  117. private float keyRepeatTimer;
  118. // Key repeat duration in seconds for the first key press.
  119. private float keyRepeatStartDuration = 0.3f;
  120. // Key repeat duration in seconds after the first key press.
  121. private float keyRepeatDuration = 0.03f;
  122. #endregion
  123. #endregion
  124. #region Initialization
  125. /// <summary>
  126. /// Constructor
  127. /// </summary>
  128. public DebugCommandUI(Game game)
  129. : base(game)
  130. {
  131. Prompt = DefaultPrompt;
  132. // Add this instance as a service.
  133. Game.Services.AddService(typeof(IDebugCommandHost), this);
  134. // Draw the command UI on top of everything
  135. DrawOrder = int.MaxValue;
  136. // Adding default commands.
  137. // Help command displays registered command information.
  138. RegisterCommand("help", "Show Command helps",
  139. delegate(IDebugCommandHost host, string command, IList<string> args)
  140. {
  141. int maxLen = 0;
  142. foreach (CommandInfo cmd in commandTable.Values)
  143. maxLen = Math.Max(maxLen, cmd.command.Length);
  144. string fmt = String.Format("{{0,-{0}}} {{1}}", maxLen);
  145. foreach (CommandInfo cmd in commandTable.Values)
  146. {
  147. Echo(String.Format(fmt, cmd.command, cmd.description));
  148. }
  149. });
  150. // Clear screen command
  151. RegisterCommand("cls", "Clear Screen",
  152. delegate(IDebugCommandHost host, string command, IList<string> args)
  153. {
  154. lines.Clear();
  155. });
  156. // Echo command
  157. RegisterCommand("echo", "Display Messages",
  158. delegate(IDebugCommandHost host, string command, IList<string> args)
  159. {
  160. Echo(command.Substring(5));
  161. });
  162. }
  163. /// <summary>
  164. /// Initialize component
  165. /// </summary>
  166. public override void Initialize()
  167. {
  168. debugManager =
  169. Game.Services.GetService(typeof(DebugManager)) as DebugManager;
  170. if (debugManager == null)
  171. throw new InvalidOperationException("Coudn't find DebugManager.");
  172. base.Initialize();
  173. }
  174. #endregion
  175. #region IDebugCommandHostinterface implemenration
  176. public void RegisterCommand(
  177. string command, string description, DebugCommandExecute callback)
  178. {
  179. string lowerCommand = command.ToLower();
  180. if (commandTable.ContainsKey(lowerCommand))
  181. {
  182. throw new InvalidOperationException(
  183. String.Format("Command \"{0}\" is already registered.", command));
  184. }
  185. commandTable.Add(
  186. lowerCommand, new CommandInfo(command, description, callback));
  187. }
  188. public void UnregisterCommand(string command)
  189. {
  190. string lowerCommand = command.ToLower();
  191. if (!commandTable.ContainsKey(lowerCommand))
  192. {
  193. throw new InvalidOperationException(
  194. String.Format("Command \"{0}\" is not registered.", command));
  195. }
  196. commandTable.Remove(command);
  197. }
  198. public void ExecuteCommand(string command)
  199. {
  200. // Call registered executioner.
  201. if (executioners.Count != 0)
  202. {
  203. executioners.Peek().ExecuteCommand(command);
  204. return;
  205. }
  206. // Run the command.
  207. char[] spaceChars = new char[] { ' ' };
  208. Echo(Prompt + command);
  209. command = command.TrimStart(spaceChars);
  210. List<string> args = new List<string>(command.Split(spaceChars));
  211. string cmdText = args[0];
  212. args.RemoveAt(0);
  213. CommandInfo cmd;
  214. if (commandTable.TryGetValue(cmdText.ToLower(), out cmd))
  215. {
  216. try
  217. {
  218. // Call registered command delegate.
  219. cmd.callback(this, command, args);
  220. }
  221. catch (Exception e)
  222. {
  223. // Exception occurred while running command.
  224. EchoError("Unhandled Exception occurred");
  225. string[] lines = e.Message.Split(new char[] { '\n' });
  226. foreach (string line in lines)
  227. EchoError(line);
  228. }
  229. }
  230. else
  231. {
  232. Echo("Unknown Command");
  233. }
  234. // Add to command history.
  235. commandHistory.Add(command);
  236. while (commandHistory.Count > MaxCommandHistory)
  237. commandHistory.RemoveAt(0);
  238. commandHistoryIndex = commandHistory.Count;
  239. }
  240. public void RegisterEchoListner(IDebugEchoListner listner)
  241. {
  242. listenrs.Add(listner);
  243. }
  244. public void UnregisterEchoListner(IDebugEchoListner listner)
  245. {
  246. listenrs.Remove(listner);
  247. }
  248. public void Echo(DebugCommandMessage messageType, string text)
  249. {
  250. lines.Enqueue(text);
  251. while (lines.Count >= MaxLineCount)
  252. lines.Dequeue();
  253. // Call registered listeners.
  254. foreach (IDebugEchoListner listner in listenrs)
  255. listner.Echo(messageType, text);
  256. }
  257. public void Echo(string text)
  258. {
  259. Echo(DebugCommandMessage.Standard, text);
  260. }
  261. public void EchoWarning(string text)
  262. {
  263. Echo(DebugCommandMessage.Warning, text);
  264. }
  265. public void EchoError(string text)
  266. {
  267. Echo(DebugCommandMessage.Error, text);
  268. }
  269. public void PushExecutioner(IDebugCommandExecutioner executioner)
  270. {
  271. executioners.Push(executioner);
  272. }
  273. public void PopExecutioner()
  274. {
  275. executioners.Pop();
  276. }
  277. #endregion
  278. #region Update and Draw
  279. /// <summary>
  280. /// Show Debug Command window.
  281. /// </summary>
  282. public void Show()
  283. {
  284. if (state == State.Closed)
  285. {
  286. stateTransition = 0.0f;
  287. state = State.Opening;
  288. }
  289. }
  290. /// <summary>
  291. /// Hide Debug Command window.
  292. /// </summary>
  293. public void Hide()
  294. {
  295. if (state == State.Opened)
  296. {
  297. stateTransition = 1.0f;
  298. state = State.Closing;
  299. }
  300. }
  301. public override void Update(GameTime gameTime)
  302. {
  303. KeyboardState keyState = Keyboard.GetState();
  304. float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
  305. const float OpenSpeed = 8.0f;
  306. const float CloseSpeed = 8.0f;
  307. switch (state)
  308. {
  309. case State.Closed:
  310. if (keyState.IsKeyDown(Keys.Tab))
  311. Show();
  312. break;
  313. case State.Opening:
  314. stateTransition += dt * OpenSpeed;
  315. if (stateTransition > 1.0f)
  316. {
  317. stateTransition = 1.0f;
  318. state = State.Opened;
  319. }
  320. break;
  321. case State.Opened:
  322. ProcessKeyInputs(dt);
  323. break;
  324. case State.Closing:
  325. stateTransition -= dt * CloseSpeed;
  326. if (stateTransition < 0.0f)
  327. {
  328. stateTransition = 0.0f;
  329. state = State.Closed;
  330. }
  331. break;
  332. }
  333. prevKeyState = keyState;
  334. base.Update(gameTime);
  335. }
  336. /// <summary>
  337. /// Hand keyboard input.
  338. /// </summary>
  339. /// <param name="dt"></param>
  340. public void ProcessKeyInputs(float dt)
  341. {
  342. KeyboardState keyState = Keyboard.GetState();
  343. Keys[] keys = keyState.GetPressedKeys();
  344. bool shift = keyState.IsKeyDown(Keys.LeftShift) ||
  345. keyState.IsKeyDown(Keys.RightShift);
  346. foreach (Keys key in keys)
  347. {
  348. if (!IsKeyPressed(key, dt)) continue;
  349. char ch;
  350. if (KeyboardUtils.KeyToString(key, shift, out ch))
  351. {
  352. // Handle typical character input.
  353. commandLine = commandLine.Insert(cursorIndex, new string(ch, 1));
  354. cursorIndex++;
  355. }
  356. else
  357. {
  358. switch (key)
  359. {
  360. case Keys.Back:
  361. if (cursorIndex > 0)
  362. commandLine = commandLine.Remove(--cursorIndex, 1);
  363. break;
  364. case Keys.Delete:
  365. if (cursorIndex < commandLine.Length)
  366. commandLine = commandLine.Remove(cursorIndex, 1);
  367. break;
  368. case Keys.Left:
  369. if (cursorIndex > 0)
  370. cursorIndex--;
  371. break;
  372. case Keys.Right:
  373. if (cursorIndex < commandLine.Length)
  374. cursorIndex++;
  375. break;
  376. case Keys.Enter:
  377. // Run the command.
  378. ExecuteCommand(commandLine);
  379. commandLine = string.Empty;
  380. cursorIndex = 0;
  381. break;
  382. case Keys.Up:
  383. // Show command history.
  384. if (commandHistory.Count > 0)
  385. {
  386. commandHistoryIndex =
  387. Math.Max(0, commandHistoryIndex - 1);
  388. commandLine = commandHistory[commandHistoryIndex];
  389. cursorIndex = commandLine.Length;
  390. }
  391. break;
  392. case Keys.Down:
  393. // Show command history.
  394. if (commandHistory.Count > 0)
  395. {
  396. commandHistoryIndex = Math.Min(commandHistory.Count - 1,
  397. commandHistoryIndex + 1);
  398. commandLine = commandHistory[commandHistoryIndex];
  399. cursorIndex = commandLine.Length;
  400. }
  401. break;
  402. case Keys.Tab:
  403. Hide();
  404. break;
  405. }
  406. }
  407. }
  408. }
  409. /// <summary>
  410. /// Pressing check with key repeating.
  411. /// </summary>
  412. /// <param name="key"></param>
  413. /// <returns></returns>
  414. bool IsKeyPressed(Keys key, float dt)
  415. {
  416. // Treat it as pressed if given key has not pressed in previous frame.
  417. if (prevKeyState.IsKeyUp(key))
  418. {
  419. keyRepeatTimer = keyRepeatStartDuration;
  420. pressedKey = key;
  421. return true;
  422. }
  423. // Handling key repeating if given key has pressed in previous frame.
  424. if (key == pressedKey)
  425. {
  426. keyRepeatTimer -= dt;
  427. if (keyRepeatTimer <= 0.0f)
  428. {
  429. keyRepeatTimer += keyRepeatDuration;
  430. return true;
  431. }
  432. }
  433. return false;
  434. }
  435. public override void Draw(GameTime gameTime)
  436. {
  437. // Do nothing when command window is completely closed.
  438. if (state == State.Closed)
  439. return;
  440. SpriteFont font = debugManager.DebugFont;
  441. SpriteBatch spriteBatch = debugManager.SpriteBatch;
  442. Texture2D whiteTexture = debugManager.WhiteTexture;
  443. // Compute command window size and draw.
  444. float w = GraphicsDevice.Viewport.Width;
  445. float h = GraphicsDevice.Viewport.Height;
  446. float topMargin = h * 0.1f;
  447. float leftMargin = w * 0.1f;
  448. Rectangle rect = new Rectangle();
  449. rect.X = (int)leftMargin;
  450. rect.Y = (int)topMargin;
  451. rect.Width = (int)(w * 0.8f);
  452. rect.Height = (int)(MaxLineCount * font.LineSpacing);
  453. Matrix mtx = Matrix.CreateTranslation(
  454. new Vector3(0, -rect.Height * (1.0f - stateTransition), 0));
  455. spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, mtx);
  456. spriteBatch.Draw(whiteTexture, rect, new Color(0, 0, 0, 200));
  457. // Draw each lines.
  458. Vector2 pos = new Vector2(leftMargin, topMargin);
  459. foreach (string line in lines)
  460. {
  461. spriteBatch.DrawString(font, line, pos, Color.White);
  462. pos.Y += font.LineSpacing;
  463. }
  464. // Draw prompt string.
  465. string leftPart = Prompt + commandLine.Substring(0, cursorIndex);
  466. Vector2 cursorPos = pos + font.MeasureString(leftPart);
  467. cursorPos.Y = pos.Y;
  468. spriteBatch.DrawString(font,
  469. String.Format("{0}{1}", Prompt, commandLine), pos, Color.White);
  470. spriteBatch.DrawString(font, Cursor, cursorPos, Color.White);
  471. spriteBatch.End();
  472. }
  473. #endregion
  474. }
  475. }