Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Text Input and Clipboard Handling

Text Input and Clipboard Handling screenshot

Last Updated: Oct 29th, 2024

Whether you want to input names for high scores or custom player names, SDL has text input capabilities you can use.
            //The quit flag
            bool quit{ false };

            //The event data
            SDL_Event e;
            SDL_zero( e );

            //Timer to cap frame rate
            LTimer capTimer;

            //The current input text
            std::string inputText = kStartingText;

            //Enable text input
            SDL_StartTextInput( gWindow );
Before entering the main loop, we initialize our input text and enable text input with SDL_StartTextInput. Text input does add some overhead so it's a good idea to only enable it when you need it.
            //The main loop
            while( quit == false )
            {
                //Start frame time
                capTimer.start();

                //The rerendering text flag
                bool renderText = false;

                //Get event data
                while( SDL_PollEvent( &e ) )
                {
                    //If event is quit type
                    if( e.type == SDL_EVENT_QUIT )
                    {
                        //End the main loop
                        quit = true;
                    }

In our main loop we have a flag to keep track whether we want to rerender our input text texture because rendering it over again every frame as opposed to only when its updated is wasteful.
                    //Special key input
                    else if( e.type == SDL_EVENT_KEY_DOWN )
                    {
                        //Handle backspace
                        if( e.key.key == SDLK_BACKSPACE && inputText.length() > 0 )
                        {
                            //lop off character
                            inputText.pop_back();
                            renderText = true;
                        }
                        //Handle copy
                        else if( e.key.key == SDLK_C && SDL_GetModState() & SDL_KMOD_CTRL )
                        {
                            SDL_SetClipboardText( inputText.c_str() );
                        }
                        //Handle paste
                        else if( e.key.key == SDLK_V && SDL_GetModState() & SDL_KMOD_CTRL )
                        {
                            //Copy text from temporary buffer
                            char* tempText = SDL_GetClipboardText();
                            inputText = tempText;
                            SDL_free( tempText );

                            renderText = true;
                        }
                    }
Here are a couple special key inputs we want to process.

When the user presses backspace we want to remove the last character in the string with pop_back. Since this changes the contents of the string, we want to set the update flag.

Whent the user presses the C key and holds down ctrl, we want to copy the text. We get the state of key modifiers using SDL_GetModState. It returns a SDL_Keymod which is a set of bits that represent things like ctrl keys, shift keys, alt key, etc. The bit we care about is the ctrl bit which is SDL_KMOD_CTRL. To actually copy the text, we call SDL_SetClipboardText.

When the user holds ctrl and presses V we want to paste the clipboard text input our input text. Set get the clipboard text using SDL_GetClipboardText. We copy this into our input string and remember to free it after we copied it because SDL_GetClipboardText does return a newly allocated string.
                    //Special text input event
                    else if( e.type == SDL_EVENT_TEXT_INPUT )
                    {
                        //If not copying or pasting
                        char firstChar = toupper( e.text.text[ 0 ] );
                        if( !( SDL_GetModState() & SDL_KMOD_CTRL && ( firstChar == 'C' || firstChar == 'V' ) ) )
                        {
                            //Append character
                            inputText += e.text.text;
                            renderText = true;
                        }
                    }
                }
When we get a SDL_TextInputEvent, we want to append the character to the input string. First thing we do is get the first char from the text input event. You may be wondering why the text input has an entire string when it's supposed to get a single character. It's because it's a single UTF-8 character which can be multiple bytes. I recommend looking into how UTF-8 works because it's important for things like localization and memory safety.

We convert the char to uppercase to make the logic case insensitive. We do this so we can check if the user is copying or pasting. If they are not, we append the character from the text input and set the text update flag.
                //Rerender text if needed
                if( renderText )
                {
                    //Text is not empty
                    if( inputText != "" )
                    {
                        //Render new text
                        gInputTextTexture.loadFromRenderedText( inputText.c_str(), kTextColor );
                    }
                    //Text is empty
                    else
                    {
                        //Render space texture
                        gInputTextTexture.loadFromRenderedText( " ", kTextColor );
                    }
                }
If the text update flag is set, we rerender the text input texture. We have a special case for when the string is empty, we just render a space so SDL_ttf doesn't get mad at us.
                //Fill the background
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF,  0xFF );
                SDL_RenderClear( gRenderer );

                //Render text textures
                gPromptTextTexture.render( ( kScreenWidth - gPromptTextTexture.getWidth() ) / 2.f, ( kScreenHeight - gPromptTextTexture.getHeight() * 2.f ) / 2.f );
                gInputTextTexture.render( ( kScreenWidth - gInputTextTexture.getWidth() ) / 2.f, ( kScreenHeight - gPromptTextTexture.getHeight() * 2.f ) / 2.f + gPromptTextTexture.getHeight() );

                //Update screen
                SDL_RenderPresent( gRenderer );

                //Cap frame rate
                constexpr Uint64 nsPerFrame = 1000000000 / kScreenFps; 
                Uint64 frameNs = capTimer.getTicksNS();
                if( frameNs < nsPerFrame )
                {
                    SDL_DelayNS( nsPerFrame - frameNs );
                }
            } 

            //Disable text input
            SDL_StopTextInput( gWindow );
        }
    }
At the end of the main loop we render our prompt and input text textures. Once we exit the main loop, we call SDL_StopTextInput as we are done with text input.

Addendum: Beware of GUIs

Now that you did some basic text input, you may be tempted to write your own GUI library. Don't. For the love of pointers, DON'T.

If over engineering is the number one killer of student projects, GUIs are public enemy number two. The problem with GUIs is that they are not complicated to make (unless you're doing fancy animation effects), but they are very time consuming. GUI programming is something we make interns and junior developers do because it eats up so much of development time and we would rather have senior developers spend their valuable time on more technically complex tasks. The problem is, what is going to impress the people in charge of hiring you is not how much time you spent on something but how technically impressive it is. The opportunity cost of GUIs in portfolio projects is massive because the surprising long amount of time it took to make your text input area is time you could have spent on something more technically complex. This is why I recommend staying away from GUI heavy portfolio projects like RPGs because while your SNES era RPG game might be cool and fun to play, the person who rolled their own graphics or physics engine has better shown their ability to take on complex tasks. If you have your heart set on that RPG, go for it but to realize the risk you are taking.

If you are going to do a GUI heavy project, I recommend using an existing GUI library or engine that already comes with a GUI framework. GUI libraries can be part of a good portfolio project, but it would in reality be closer to a graphics engine. Honestly if you're going to make a GUI library as part of a portfolio project, you should consider it as part of your larger graphics engine.