DebugCommandUI.cs 17 KB

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