Getting String Input
Last Updated 3/28/10
Here we need to get the player's name after they got a high score. Back in your early days, you used cin to get string input. Since SDL has no built in ways of getting the user's name, we'll make our own function to handle keypresses to get string input.A Text Input and Clipboard Handling tutorial with SDL 2 is now available.
//The key press interpreter
class StringInput
{
private:
//The storage string
std::string str;
//The text surface
SDL_Surface *text;
public:
//Initializes variables
StringInput();
//Does clean up
~StringInput();
//Handles input
void handle_input();
//Shows the message on screen
void show_centered();
};
Here's is our class used to manage string input.
In terms of variables we have a string for the actual string data and a surface to render the text to.
Then we have our constructors and destructors, the function handle_input() to deal with user input, and show_centered() to make the string appear on the screen.
In terms of variables we have a string for the actual string data and a surface to render the text to.
Then we have our constructors and destructors, the function handle_input() to deal with user input, and show_centered() to make the string appear on the screen.
StringInput::StringInput()
{
//Initialize the string
str = "";
//Initialize the surface
text = NULL;
//Enable Unicode
SDL_EnableUNICODE( SDL_ENABLE );
}
StringInput::~StringInput()
{
//Free text surface
SDL_FreeSurface( text );
//Disable Unicode
SDL_EnableUNICODE( SDL_DISABLE );
}
In the constructor along with a variable initialization, we enable unicode with SDL_EnableUNICODE().
This will make getting string input much easier as you'll see later.
In the destructor we free our text surface and disable unicode. While enabling unicode makes string input easier, it does add a bit of overhead. It should be turned off when you're not using it.
In the destructor we free our text surface and disable unicode. While enabling unicode makes string input easier, it does add a bit of overhead. It should be turned off when you're not using it.
void StringInput::handle_input()
{
//If a key was pressed
if( event.type == SDL_KEYDOWN )
{
//Keep a copy of the current version of the string
std::string temp = str;
//If the string less than maximum size
if( str.length() <= 16 )
{
Now it's time to handle the user's input.
When the user presses a key, we first store a copy of the current string. I'll tell you why later.
Then we check that the string isn't at maximum length. Here I set it as 16, but you can set it to be whatever you want.
When the user presses a key, we first store a copy of the current string. I'll tell you why later.
Then we check that the string isn't at maximum length. Here I set it as 16, but you can set it to be whatever you want.
//If the key is a space
if( event.key.keysym.unicode == (Uint16)' ' )
{
//Append the character
str += (char)event.key.keysym.unicode;
}
The basic concept of string input is when the user presses 'A' add on a 'A',
when the user presses 'B' add on a 'B', etc, etc.
Because the SDLKey definitions don't match up with their ASCII/Unicode values, we enable unicode so the "unicode" member of the Keysym structure matches the unicode value of character pressed. Enabling unicode also automatically handles shift and caps lock when you want capital letters and symbols.
If you don't know what unicode is, it's just an extension of ASCII. Instead of being 8bit, it's 16bit so it can hold all the international characters.
Here if the key pressed has the unicode value of the space character, we append it the string. Since a standard string only uses 8bit ASCII characters, we have to convert it to a char when appending it.
Because the SDLKey definitions don't match up with their ASCII/Unicode values, we enable unicode so the "unicode" member of the Keysym structure matches the unicode value of character pressed. Enabling unicode also automatically handles shift and caps lock when you want capital letters and symbols.
If you don't know what unicode is, it's just an extension of ASCII. Instead of being 8bit, it's 16bit so it can hold all the international characters.
Here if the key pressed has the unicode value of the space character, we append it the string. Since a standard string only uses 8bit ASCII characters, we have to convert it to a char when appending it.
//If the key is a number
else if( ( event.key.keysym.unicode >= (Uint16)'0' ) && ( event.key.keysym.unicode <= (Uint16)'9' ) )
{
//Append the character
str += (char)event.key.keysym.unicode;
}
//If the key is a uppercase letter
else if( ( event.key.keysym.unicode >= (Uint16)'A' ) && ( event.key.keysym.unicode <= (Uint16)'Z' ) )
{
//Append the character
str += (char)event.key.keysym.unicode;
}
//If the key is a lowercase letter
else if( ( event.key.keysym.unicode >= (Uint16)'a' ) && ( event.key.keysym.unicode <= (Uint16)'z' ) )
{
//Append the character
str += (char)event.key.keysym.unicode;
}
}
In this program we only want spaces (ASCII/Unicode 32), numbers (48-57), uppercase (65-90), and lowercase (97-122) letters to appear.
So here we limit the numbers allowed to be appended to the string.
//If backspace was pressed and the string isn't blank
if( ( event.key.keysym.sym == SDLK_BACKSPACE ) && ( str.length() != 0 ) )
{
//Remove a character from the end
str.erase( str.length() - 1 );
}
Here we deal with when the user presses backspace.
We simply check if the string is empty, and if it's not we lop off the last character of the string.
We simply check if the string is empty, and if it's not we lop off the last character of the string.
//If the string was changed
if( str != temp )
{
//Free the old surface
SDL_FreeSurface( text );
//Render a new text surface
text = TTF_RenderText_Solid( font, str.c_str(), textColor );
}
}
}
Lastly, we check if string was altered by comparing it to the copy we made earlier.
If the string has changed, we free the old text surface, and render a new one.
If the string has changed, we free the old text surface, and render a new one.
void StringInput::show_centered()
{
//If the surface isn't blank
if( text != NULL )
{
//Show the name
apply_surface( ( SCREEN_WIDTH - text->w ) / 2, ( SCREEN_HEIGHT - text->h ) / 2, text, screen );
}
}
In our show_centered() function we apply the text surface centered on the screen.
In this program we check if the name surface is NULL because when you try to render a surface from a blank string ( that-> "" ), SDL_ttf returns NULL. Which makes sense because it was given nothing to render.
In this program we check if the name surface is NULL because when you try to render a surface from a blank string ( that-> "" ), SDL_ttf returns NULL. Which makes sense because it was given nothing to render.
int main( int argc, char* args[] )
{
//Quit flag
bool quit = false;
//Keep track if whether or not the user has entered their name
bool nameEntered = false;
//Initialize
if( init() == false )
{
return 1;
}
//The gets the user's name
StringInput name;
//Load the files
if( load_files() == false )
{
return 1;
}
//Set the message
message = TTF_RenderText_Solid( font, "New High Score! Enter Name:", textColor );
At the top of our main() function we have two new variables. "nameEntered" is a flag that tells whether or not the
user has entered their name which we obviously initialize to false. "name" is, of course, an object of the class we
made used to get the user's name.
We have our typical initialization and loading but we also render the message surface before we go into the main loop.
We have our typical initialization and loading but we also render the message surface before we go into the main loop.
//While the user hasn't quit
while( quit == false )
{
//While there's events to handle
while( SDL_PollEvent( &event ) )
{
//If the user hasn't entered their name yet
if( nameEntered == false )
{
//Get user input
name.handle_input();
//If the enter key was pressed
if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_RETURN ) )
{
//Change the flag
nameEntered = true;
//Free the old message surface
SDL_FreeSurface( message );
//Change the message
message = TTF_RenderText_Solid( font, "Rank: 1st", textColor );
}
}
//If the user has Xed out the window
if( event.type == SDL_QUIT )
{
//Quit the program
quit = true;
}
}
Here's the event handling part of our main loop.
First we check if the user is still entering their name. If they are we call handle_input() on our StringInput object and let it do its thing.
When the user presses enter, it means the user has finished so we set the "nameEntered" flag to true. Then we free the old message surface and render a new one.
and of course we also check if the user wants to X out.
First we check if the user is still entering their name. If they are we call handle_input() on our StringInput object and let it do its thing.
When the user presses enter, it means the user has finished so we set the "nameEntered" flag to true. Then we free the old message surface and render a new one.
and of course we also check if the user wants to X out.
//Apply the background
apply_surface( 0, 0, background, screen );
//Show the message
apply_surface( ( SCREEN_WIDTH - message->w ) / 2, ( ( SCREEN_HEIGHT / 2 ) - message->h ) / 2, message, screen );
//Show the name on the screen
name.show_centered();
//Update the screen
if( SDL_Flip( screen ) == -1 )
{
return 1;
}
}
Now here's the rendering part of our main loop.
Nothing really new here, we just apply the background, then message surface and show our text input.
In this tutorial we only handled string input, but getting integers isn't much harder. The string header has the function atoi() that gets an integer from a string. There's other functions that'll do floating point numbers. Look 'em up.
Nothing really new here, we just apply the background, then message surface and show our text input.
In this tutorial we only handled string input, but getting integers isn't much harder. The string header has the function atoi() that gets an integer from a string. There's other functions that'll do floating point numbers. Look 'em up.