/*
* 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
}
}