/* * Util.CSharpConsoleScreen.cs * Authors: Adam Nabinger * based on code by August Zinsser * Copyright (c) 2007-2008 Cornell University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Util { /// /// A Console Screen for entering and running C# Code inside the game. /// public sealed class CSharpConsoleScreen : GameScreen { /// /// Defines a history item within the console window /// private struct HistoryItem { #region Enums /// /// Enum representing what type of code the history item is /// internal enum Code { /// /// Defines a history item of code type command /// Command, /// /// Defines a history item of code type output /// Output, /// /// Defines a history item of code type error /// Error }; #endregion #region Fields /// /// The text that would be displayed in the console window /// internal readonly string Text; /// /// The type of code the history item is /// internal readonly Code CodeType; #endregion #region Creation /// /// Constructor /// /// The text that would be displayed in the console window /// The type of code the history item is internal HistoryItem(string text, Code codeType) { Text = text; CodeType = codeType; } #endregion } #region Constants // Offset from the left private const int mLeft = 5; // Offset from the bottom private const int mBottom = 40; // Offset between spacing private const int mSpacing = 40; // How much of the screen will be taken up by the console private const float mScreenPercentage = .333f; // The amount of items that can be saved to the consoles history private const int mHistoryCount = 25; // Character from the beginning of each input line private const string mCursor = "> "; // Constant for the name of the font type to use private const string fontname = @"Fonts\menufont"; #endregion #region Fields // List of history items in the console screen private static readonly List mHistory = new List(); // The index in the list of history items private static int mHistoryIndex; // The sprite batch used for rendering textures private SpriteBatch mSpriteBatch; // The sprite font used for rendering text private SpriteFont font; // The current input handler for handling the console screens input private InputHandler inputHandler; // The current text in the command line of the console screen private string commandString = string.Empty; // The texture used for lines private Texture2D lineTex; // Reference to the current game screen private readonly GameScreen mGameScreen; #endregion #region Properties /// /// The gamescreen we're acting upon. /// public GameScreen GameScreen { get { return mGameScreen; } } #endregion #region Creation /// /// Create a new console overlay, to call methods on the given game screen. /// /// Game public CSharpConsoleScreen(GameScreen game) { mGameScreen = game; } #endregion #region Initialization /// /// Initialize. /// public override void Initialize() { Content.RootDirectory = mGame.Content.RootDirectory; inputHandler = GetInputHandler(); inputHandler.Initialize(); inputHandler.ReportAllEvents = true; inputHandler.ReportCase = true; base.Initialize(); } /// /// The console overlay should never be drawn over, this should never be called. /// public override void Reinitialize() { } #endregion #region Management /// /// Loads information for all content dependent objects /// protected override void LoadContent() { mSpriteBatch = new SpriteBatch(GraphicsDevice); font = Content.Load(fontname); lineTex = Content.Load(@"blank"); base.LoadContent(); } /// /// Disposes of any unmanaged objects /// /// Whether or not this is currently being disposed protected override void Dispose(bool disposing) { if (disposing) { mSpriteBatch.Dispose(); } base.Dispose(disposing); } /// /// Write a line of text into the console. /// /// The line of text to write public void WriteLine(string text) { if (!string.IsNullOrEmpty(text)) { mHistory.Insert(0, new HistoryItem(text, HistoryItem.Code.Output)); } } #endregion #region Update /// /// Updates the C# console screen /// /// Time that has passed in game public override void Update(GameTime gameTime) { while (inputHandler.HasEvent()) { InputEvent e = inputHandler.GetNextEvent(); KeyInputEvent key = e as KeyInputEvent; if (key == null || e.Trigger != Trigger.Activated) { continue; } switch (key.Key) { case Keys.Back: commandString = commandString.Substring(0, Math.Max(commandString.Length - 1, 0)); break; case Keys.Up: int newIndexUp = mHistoryIndex; do { if (newIndexUp < mHistory.Count - 1) { ++newIndexUp; } else { return; } } while (mHistory[newIndexUp].CodeType != HistoryItem.Code.Command); mHistoryIndex = newIndexUp; commandString = mHistory[mHistoryIndex].Text; break; case Keys.Down: int newIndexDown = mHistoryIndex; do { if (newIndexDown > 0) { --newIndexDown; } else { return; } } while (mHistory[newIndexDown].CodeType != HistoryItem.Code.Command); mHistoryIndex = newIndexDown; commandString = mHistory[mHistoryIndex].Text; break; case Keys.Enter: if (!string.IsNullOrEmpty(commandString)) { mHistory.Insert(0, new HistoryItem(commandString, HistoryItem.Code.Command)); try { CSharpHelper.DoSimpleString(commandString, this, "Console"); } catch (ScriptCompileException ex) { mHistory.Insert(0, new HistoryItem(ex.Message, HistoryItem.Code.Error)); } // Update console commands commandString = string.Empty; mHistoryIndex = -1; } break; case Keys.Escape: case Keys.OemTilde: Done(); break; default: commandString += key.ToCodeString(); break; } } // Update history lines while (mHistory.Count > mHistoryCount) { mHistory.RemoveAt(mHistory.Count - 1); } base.Update(gameTime); } #endregion #region Render /// /// Renders the C# console screen /// TODO: Cleanup /// /// Time that has passed so far in game public override void Draw(GameTime gameTime) { mSpriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Immediate, SaveStateMode.None); int lineY = (int)(GraphicsDevice.Viewport.Height * mScreenPercentage); mSpriteBatch.Draw( lineTex, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, (int)(GraphicsDevice.Viewport.Height * mScreenPercentage)), null, new Color(75, 75, 155, 180), 0f, Vector2.Zero, SpriteEffects.None, 0f); mSpriteBatch.Draw( lineTex, new Rectangle(0, lineY - 2, GraphicsDevice.Viewport.Width, 4), null, new Color(200, 200, 200, 200), 0f, Vector2.Zero, SpriteEffects.None, 0f); // Draw the command line Vector2 cursorSize = font.MeasureString(mCursor); mSpriteBatch.DrawString(font, mCursor, new Vector2(mLeft, lineY - mBottom), new Color(255, 150, 0, 255)); mSpriteBatch.DrawString(font, commandString, new Vector2(mLeft + (int)cursorSize.X, lineY - mBottom), new Color(255, 255, 175, 255)); // Draw the history lines for (int i = 0; i < mHistory.Count; i++) { int y = lineY - (mSpacing) * (i + 2); if (mHistoryIndex == i) mSpriteBatch.DrawString(font, mCursor, new Vector2(mLeft, y), new Color(200, 150, 0, 200)); Color color; switch (mHistory[i].CodeType) { case HistoryItem.Code.Command: color = new Color(200, 200, 150, 255); break; case HistoryItem.Code.Output: color = new Color(155, 255, 255, 255); break; default: color = new Color(255, 0, 0, 255); break; } mSpriteBatch.DrawString(font, mHistory[i].Text, new Vector2(mLeft + (int)cursorSize.X, y), color); } mSpriteBatch.End(); base.Draw(gameTime); } #endregion } }