Writing a console within an application
c++, console, sfml, shell
Solution
I implemented the following as a console for an opengl game I was writing a while ago. It is by no means a definitive answer to you question but it worked for me and you might get something useful out of it.
The 2 files are at this bottom of this post. The code is unlikely to run directly as there is one for 2 library header files that I'm not going to include. If you want the full source let me know.
Basically though the console class allows you to add variable pointers to it that can be changed at run time. It accepts input from the windows event messages. (The actual handling of input is done elsewhere) The command parsing is done in the ProcessInput() method and variables are updated in the ChangeVariable() method.
A word of warning. This method essentially gives the console users direct access to the memory locations of the individual variable. This requires good input validation to ensure you the user cant cause the application to crash at runtime. If I ever sat down and tried to make another console I would likely do things slightly different. However I hope this gives you a little help.
The header file:
#ifndef CONSOLE_H
#define CONSOLE_H
#include <vector>
#include <map>
#include <string>
#include "Singleton.h"
#include <Windows.h>
#include "Enumerations.h"
#include "StringConversion.h"
class Console
{
public:
Console();
~Console();
void Update(std::vector<WPARAM> pressedKeys);
void AddInt(std::string varName, int *ptrToInt);
void AddFloat(std::string varName, float *ptrToFloat);
void AddLong(std::string varName, long *ptrToLong);
void AddBool(std::string varName, bool *ptrToBool);
const std::string &GetCurrentText();
const std::vector<std::string> &GetPreviousText();
private:
std::map<std::string, int *> m_Ints;
std::map<std::string, float *> m_Floats;
std::map<std::string, long *> m_Longs;
std::map<std::string, bool *> m_Bools;
std::map<std::string, std::string> m_Variables;
std::vector<std::string> m_PrevConsoleText;
std::string m_CurrInput;
int m_PrevSelection;
bool ProcessInput();
void ChangeVariable(const std::string &varName, const std::string &value);
};
typedef Singleton<Console> g_Console;
#endif // CONSOLE_H
The cpp file:
#include "Console.h"
Console::Console()
{
m_PrevSelection = 0;
}
Console::~Console()
{
}
void Console::AddInt(std::string varName, int *ptrToInt)
{
m_Ints[varName] = ptrToInt;
m_Variables[varName] = "int";
}
void Console::AddFloat(std::string varName, float *ptrToFloat)
{
m_Floats[varName] = ptrToFloat;
m_Variables[varName] = "float";
}
void Console::AddLong(std::string varName, long *ptrToLong)
{
m_Longs[varName] = ptrToLong;
m_Variables[varName] = "long";
}
void Console::AddBool(std::string varName, bool *ptrToBool)
{
m_Bools[varName] = ptrToBool;
m_Variables[varName] = "bool";
}
void Console::ChangeVariable(const std::string &varName, const std::string &value)
{
//*(m_Bools[varName]) = value;
std::string temp = m_Variables[varName];
if(temp == "int")
{
//*(m_Ints[varName]) = fromString<int>(value);
}
else if(temp == "float")
{
//*(m_Floats[varName]) = fromString<float>(value);
}
else if(temp == "long")
{
//*(m_Longs[varName]) = fromString<long>(value);
}
else if(temp == "bool")
{
if(value == "true" || value == "TRUE" || value == "True")
{
*(m_Bools[varName]) = true;
}
else if(value == "false" || value == "FALSE" || value == "False")
{
*(m_Bools[varName]) = false;
}
}
}
const std::string &Console::GetCurrentText()
{
return m_CurrInput;
}
void Console::Update(std::vector<WPARAM> pressedKeys)
{
for(int x = 0; x < (int)pressedKeys.size(); x++)
{
switch(pressedKeys[x])
{
case KEY_A:
m_CurrInput.push_back('a');
break;
case KEY_B:
m_CurrInput.push_back('b');
break;
case KEY_C:
m_CurrInput.push_back('c');
break;
case KEY_D:
m_CurrInput.push_back('d');
break;
case KEY_E:
m_CurrInput.push_back('e');
break;
case KEY_F:
m_CurrInput.push_back('f');
break;
case KEY_G:
m_CurrInput.push_back('g');
break;
case KEY_H:
m_CurrInput.push_back('h');
break;
case KEY_I:
m_CurrInput.push_back('i');
break;
case KEY_J:
m_CurrInput.push_back('j');
break;
case KEY_K:
m_CurrInput.push_back('k');
break;
case KEY_L:
m_CurrInput.push_back('l');
break;
case KEY_M:
m_CurrInput.push_back('m');
break;
case KEY_N:
m_CurrInput.push_back('n');
break;
case KEY_O:
m_CurrInput.push_back('o');
break;
case KEY_P:
m_CurrInput.push_back('p');
break;
case KEY_Q:
m_CurrInput.push_back('q');
break;
case KEY_R:
m_CurrInput.push_back('r');
break;
case KEY_S:
m_CurrInput.push_back('s');
break;
case KEY_T:
m_CurrInput.push_back('t');
break;
case KEY_U:
m_CurrInput.push_back('u');
break;
case KEY_V:
m_CurrInput.push_back('v');
break;
case KEY_W:
m_CurrInput.push_back('w');
break;
case KEY_X:
m_CurrInput.push_back('x');
break;
case KEY_Y:
m_CurrInput.push_back('y');
break;
case KEY_Z:
m_CurrInput.push_back('z');
break;
case KEY_0:
m_CurrInput.push_back('0');
break;
case KEY_1:
m_CurrInput.push_back('1');
break;
case KEY_2:
m_CurrInput.push_back('2');
break;
case KEY_3:
m_CurrInput.push_back('3');
break;
case KEY_4:
m_CurrInput.push_back('4');
break;
case KEY_5:
m_CurrInput.push_back('5');
break;
case KEY_6:
m_CurrInput.push_back('6');
break;
case KEY_7:
m_CurrInput.push_back('7');
break;
case KEY_8:
m_CurrInput.push_back('8');
break;
case KEY_9:
m_CurrInput.push_back('9');
break;
case KEY_QUOTE:
m_CurrInput.push_back('\"');
break;
case KEY_EQUALS:
m_CurrInput.push_back('=');
break;
case KEY_SPACE:
m_CurrInput.push_back(' ');
break;
case KEY_BACKSPACE:
if(m_CurrInput.size() > 0)
{
m_CurrInput.erase(m_CurrInput.end() - 1, m_CurrInput.end());
}
break;
case KEY_ENTER:
ProcessInput();
break;
case KEY_UP:
m_PrevSelection--;
if(m_PrevSelection < 1)
{
m_PrevSelection = m_PrevConsoleText.size() + 1;
m_CurrInput = "";
}
else
{
m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];
}
break;
case KEY_DOWN:
if(m_PrevSelection > (int)m_PrevConsoleText.size())
{
m_PrevSelection = 0;
m_CurrInput = "";
}
else
{
m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];
}
m_PrevSelection++;
break;
}
}
}
bool Console::ProcessInput()
{
int x;
std::string variable = "NULL", value;
bool ok = false;
std::string::iterator it;
//Split up the input from the user.
//variable will be the variable to change
//ok will = true if the syntax is correct
//value will be the value to change variable to.
for(x = 0; x < (int)m_CurrInput.size(); x++)
{
if(m_CurrInput[x] == ' ' && variable == "NULL")
{
variable = m_CurrInput.substr(0, x);
}
else if(m_CurrInput[x] == '=' && m_CurrInput[x - 1] == ' ' && m_CurrInput[x + 1] == ' ')
{
ok = true;
}
else if(m_CurrInput[x] == ' ')
{
value = m_CurrInput.substr(x + 1, m_CurrInput.size());
}
}
if(ok)
{
m_PrevConsoleText.push_back(m_CurrInput);
m_PrevSelection = m_PrevConsoleText.size();
if(m_PrevConsoleText.size() > 10)
{
m_PrevConsoleText.erase(m_PrevConsoleText.begin(), m_PrevConsoleText.begin() + 1);
}
m_CurrInput.clear();
ChangeVariable(variable, value);
}
else
{
m_PrevConsoleText.push_back("Error invalid console syntax! Use: <variableName> = <value>");
m_CurrInput.clear();
}
return ok;
}
const std::vector<std::string> &Console::GetPreviousText()
{
return m_PrevConsoleText;
}
Edit 1: Added DrawConsole() I just get the text from the console class render an image that looked similar to the source engine console window found in any recent valve game and then the text gets drawn in the appropriate places.
void View::DrawConsole()
{
Square console;
std::vector<std::string> temp;
temp = g_Console::Instance().GetPreviousText();
console.top = Vector3f(0.0, 0.0, 1.0);
console.bottom = Vector3f(640, 480, 1.0);
g_Render::Instance().SetOrthographicProjection();
g_Render::Instance().PushMatrix();
g_Render::Instance().LoadIdentity();
g_Render::Instance().BindTexture(m_ConsoleTexture);
g_Render::Instance().DrawPrimative(console, Vector3f(1.0f, 1.0f, 1.0f));
g_Render::Instance().DisableTexture();
g_Render::Instance().SetOrthographicProjection();
//Draw the current console text
g_Render::Instance().DrawString(g_Console::Instance().GetCurrentText(), 0.6f, 20, 465);
//Draw the previous console text
for(int x = (int)temp.size(); x > 0; x--)
{
g_Render::Instance().DrawString(temp[x-1], 0.6f, 20, (float)(425 - (abs((int)temp.size() - x) * 20)));
}
g_Render::Instance().SetPerspectiveProjection();
g_Render::Instance().PopMatrix();
g_Render::Instance().SetPerspectiveProjection();
}
Problem
My application requires a console to be embeded within the application window, an example would be in a program like autoCAD where the console is at the bottom of the window waiting for commands. I need the console within my application so that I can change variables and other things, so the console is not needing to be a fully blown out shell. At the moment I have a simple console within my application, but It seems very clunky compared to a terminal(shell) which is what I want the console to be like. The way that I have done it with the console I have is when the user presses the `TAB` key the console shows, then they can type in their command/line; Once the `Return` key has been pressed the string that they have typed gets parsed and the command is processed. I am using `sf::Text` objects to print out text in my application window. There are a total of 5 `sf::Text` objects that are used, 4 for the previous commands/error messages and 1 for the current command line. When the `Return` key is pressed the 4th `sf::Text` changes its current string to the 3rd, the 3rd to the 2nd, the 2nd to the 1st and the 1st to the current command string then the current command string gets cleared and ready for input again. That way there is room for 4 'history' of commands and/or errors. Not the greatest but it was the best I could come up with. Of course the amount of history could be changed via adding more `sf::Text` objects. So in the end this is how I render the console to the screen ``` sf::RectangleShape rectangle; rectangle.setSize(sf::Vector2f(App->getSize().x, App->getSize().y / 3)); rectangle.setPosition(0, 0); rectangle.setFillColor(sf::Color::black); App->draw(rectangle); // This renders the console looking background rectangle App->draw(CLine); // This renders the current command line for(int i = 4; i >= 0; --i) // This renders the history as described above, their idevidual positions are setup earlier on in the program { CHistory[i].setString(CS[i]); App->draw(CHistory[i]); } ``` `App` is just a `sf::RenderWindow*` My overall question is, Is there a way that I can embed a console into my SFML window without it having to be simply an image of text objects rendered to look like a console as I have above. I would prefer to have an actual console/shell/terminal within my application. Like the standard bash shell, but of course my own shell interpreter.