# What is the most efficient way to render a textbox in C++ SDL2?

I'm a beginner in and I'm trying to make a window, with a (where you can move a sprite around) and a CLI (a textbox where the game can return some text and the user can insert commands).

Let's say the entire output textbox is 800x280 pixels and below is the input textbox which is 800x20 pixels. This means, each line of text should be 800x20 pixels, we can divide the entire output by 14 parts, or 14 textures. Maybe I can use a class but I'm still looking for more efficient ways.

I don't plan to make the textbox scrolling but when there is an input, all the boxes get incremented down (where the lowest box get moved up to the top).

This is my unfinished attempt (excuse my horrible code structure):

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <string>

// Screen dimensions, constants
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 900; // 600 for ground, 280 for output, 20 for input

SDL_Window* gWindow = NULL; // The window we'll be rendering to
SDL_Surface* gScreenSurface = NULL; // The surface contained by the window
SDL_Surface* gCurrentSurface = NULL; // Current displayed image
SDL_Surface* gTextSurface = NULL;

SDL_Renderer* gRenderer = NULL; // The renderer we'll be using
SDL_Texture* gTextOutput = NULL;
TTF_Font* gFont = NULL;
SDL_Color gTextColor = { 0, 0, 0, 0xFF };
SDL_Rect rcSprite, rcGround, rcTextInput;

SDL_Rect rcTOB0, rcTOB1, rcTOB2, rcTOB3, rcTOB4, rcTOB5, rcTOB6, rcTOB7,
rcTOB8, rcTOB9, rcTOB10, rcTOB11, rcTOB12, rcTOB13;
SDL_Rect rcTextOutputBox[14] = { rcTOB0, rcTOB1, rcTOB2, rcTOB3, rcTOB4, rcTOB5,
rcTOB6, rcTOB7, rcTOB8, rcTOB9, rcTOB10,
rcTOB11, rcTOB12, rcTOB13 };
int iTOBInc = 0;

void init();
void quit();
void output(std::string text);

void output(std::string text)
{
SDL_Surface* gTextSurface = TTF_RenderText_Solid(gFont, text.c_str(), gTextColor);
if (gTextSurface != NULL)
{
gTextOutput = SDL_CreateTextureFromSurface(gRenderer, gTextSurface);
if (gTextOutput == NULL)
{
throw "Unable to render texture! SDL ERROR: ";
}
SDL_RenderCopy(gRenderer, gTextOutput, NULL, &rcTextOutputBox[iTOBInc]);
SDL_RenderCopyEx(gRenderer, gTextOutput, &rcTextOutputBox[iTOBInc], NULL, 0.0, NULL, SDL_FLIP_NONE);
}
else
{
throw "Unable to render text surface! SDL_ttf Error: ";
}
}

void init()
{
if(SDL_Init(SDL_INIT_VIDEO) > 0)
{
throw "SDL failed to initialise! ERROR: ";
}
else
{
gWindow = SDL_CreateWindow("Caventure",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH,
SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
if(gWindow == NULL)
{
throw "Window failed to initialise! ERROR: ";
}
else
{
gScreenSurface = SDL_GetWindowSurface(gWindow);
}

gRenderer = SDL_CreateRenderer(gWindow,
-1,
SDL_RENDERER_ACCELERATED);
if (gRenderer == NULL)
{
throw "Renderer could not be initialised! ERROR: ";
}
else
{
SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0x00, 0x00);
if (TTF_Init() == -1)
{
throw "TTF could not be initialised! ERROR: ";
}
}
}
}

{
// Ground rendering
rcGround.x = 0;
rcGround.y = 0;
rcGround.w = 800;
rcGround.h = 600;

// Sprite rendering
rcSprite.x = 400;
rcSprite.y = 300;
rcSprite.w = 4;
rcSprite.h = 4;

// TextOutput box rendering
for (int i = 0; i < 14; i++)
{
rcTextOutputBox[i].x = 0;
rcTextOutputBox[i].y = 20 * i + 600;
rcTextOutputBox[i].w = 800;
rcTextOutputBox[i].h = 20;
}

// TextInput box rendering
rcTextInput.x = 0;
rcTextInput.y = 880;
rcTextInput.w = 800;
rcTextInput.h = 20;

gFont = TTF_OpenFont("resources/consolas.ttf", 14);
if (gFont == NULL)
{
throw "Failed to load font! ERROR: ";
}

SDL_SetTextInputRect(&rcTextInput);
}

void quit()
{
// Destroy window
SDL_DestroyWindow(gWindow);
SDL_DestroyRenderer(gRenderer);
TTF_CloseFont(gFont);
gWindow = NULL;
gRenderer = NULL;
gFont = NULL;

// Quit SDL subsystems
TTF_Quit();
SDL_Quit();
}

int main()
{
try
{
init();

bool quit = false;
bool renderText = false;
SDL_Event event;
std::string inputText = "";
std::string inputCmd = "";
SDL_StartTextInput();

while(!quit)
{
while(SDL_PollEvent(&event) != 0)
{
if(event.type == SDL_QUIT)
{
quit = true;
}
else if(event.type == SDL_KEYDOWN)
{
switch(event.key.keysym.sym)
{
case SDLK_UP:
rcSprite.y -= 5;
break;

case SDLK_DOWN:
rcSprite.y += 5;
break;

case SDLK_LEFT:
rcSprite.x -= 5;
break;

case SDLK_RIGHT:
rcSprite.x += 5;
break;
}
}
else if (event.type == SDL_TEXTINPUT)
{
inputText += event.text.text;
}
else if (event.key.keysym.sym == SDLK_BACKSPACE && inputText.length() > 0)
{
inputText.pop_back();
}
else if (event.key.keysym.sym == SDLK_RETURN && inputText.size() != 0)
{
inputCmd = inputText.c_str();
renderText = true;
inputText = "";
}
}
if (rcSprite.x < 0 ||
rcSprite.y < 0 ||
rcSprite.y > rcGround.h ||
rcSprite.x > rcGround.w)
{
rcSprite.x = 400;
rcSprite.y = 300;
}

SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(gRenderer);

SDL_RenderFillRect(gRenderer, &rcGround);
SDL_BlitSurface(gCurrentSurface, NULL, gScreenSurface, &rcGround);

SDL_RenderFillRect(gRenderer, &rcTextInput);
SDL_BlitSurface(gCurrentSurface, NULL, gScreenSurface, &rcTextInput);

SDL_SetRenderDrawColor(gRenderer, 0x40, 0x40, 0x40, 0x40);

for (int i = 0; i < 14; i++)
{
SDL_RenderFillRect(gRenderer, &rcTextOutputBox[i]);
SDL_BlitSurface(gCurrentSurface, NULL, gScreenSurface, &rcTextOutputBox[i]);
if (i == iTOBInc &&
renderText &&
inputCmd != "")
{
output(inputCmd);
iTOBInc++;
renderText = false;
}
if (SDL_GetError() != NULL || TTF_GetError() != NULL)
{
throw "ERROR: ";
}
}

SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);

SDL_RenderDrawLine(gRenderer, 0, 600, 800, 600);
SDL_RenderDrawLine(gRenderer, 0, 880, 800, 880);

SDL_RenderFillRect(gRenderer, &rcSprite);
SDL_BlitSurface(gCurrentSurface, NULL, gScreenSurface, &rcSprite);

SDL_RenderPresent(gRenderer);
}
SDL_StopTextInput();
}
catch (const std::string& msg)
{
printf("%s", msg.c_str());
if (SDL_GetError() != NULL)
{
printf("%s", SDL_GetError());
}
else if (TTF_GetError() != NULL)
{
printf("%s", TTF_GetError());
}
else
{
printf("%s", "NULL");
}
quit();
exit(EXIT_FAILURE);
}
quit();
return 0;
}


Right now, this code doesn't work, it just returns a SIGABRT regarding a const char*.

• So what are you actually asking? Are you looking for a ready-made textbox solution? Are you looking to fix the current code (the issue you mentioned at the bottom of the post)? – Tyyppi_77 Apr 24 '17 at 11:53
• @Tyyppi_77 I'm looking for a method, for example my method is to separate the textbox into 14 parts and then render each textures separately (I gave you my code as an example). Is there any other way? Maybe I have to use a class? If so, just send me a code snippet of that class and its methods. – Pradana Aumars Apr 24 '17 at 12:00
• So essentially you just want a smoother/nicer way to display the correct portion of the text you want to render? – Tyyppi_77 Apr 24 '17 at 12:03
• @Tyyppi_77 Yes, exactly. – Pradana Aumars Apr 24 '17 at 12:05
• tiny file dialogs on sourceforge is a single C C++ cross-platform file offering many native dialogs including an input box. It has no init, no main loop, no linking and no include. – rolobo Sep 18 '17 at 9:39

So what you're looking for is SDL_RenderSetClipRect. This allows you to specificy where you want the text to be visible. The following example makes some assumptions but I'm sure you can figure out how to make it more generic.

So, let's say that you have your textbox at X, Y and it's W wide and H tall. You have rendered text into TextTexture, and TextTexture is TextWidth pixels wide and TextHeight tall.

So, start by figuring out where you want the text to render. The logic of this can be a little tricky and depends on how you want it to behave, but for this example, I'm just going to right-align the text as that's easiest.

SDL_Rect destination;
destination.x = X + W - TextWidth;
destination.y = Y;
destination.W = TextWidth;
destination.H = TextHeight;


So, the important thing here is the x coordinate. We calculate where the textbox ends, and then decrease that position with the text width, making it so that the last character of the text always ends at the edge of the text box. You can tweak this to behave how you want (like using the textbox' X-coordinate when the text is shorter than the textbox).

Now, all you have to do is to set the clip rect:

SDL_Rect clip;
clip.x = X;
clip.y = Y;
clip.w = W;
clip.h = H;
SDL_RenderSetClipRect(renderer, &clip);


This prevents the text from drawing outside of the text box.

Then you can just render the whole string of text, and then clear the cliprect.

SDL_RenderCopy(renderer, TextTexture, NULL, &destination);
SDL_RenderSetClipRect(renderer, NULL);


This way you don't have to worry about splitting the textbox into smaller rects or whatever.

Abstracting this all into a nice reusable class can surely help but is not required, and is left for the asker to do.

• Thank you for the help! However, the code doesn't seem to work, it keeps returning terminate called after throwing an instance of 'char const*' and terminated by signal SIGABRT (abort). Can you give me a larger example? – Pradana Aumars May 7 '17 at 8:25
• No, not really, this is everything that answers the initial question. If you're having issues with your code, post a new question with the relevant code, the error message and the line that throws the error. – Tyyppi_77 May 7 '17 at 8:46
• @tyyppi-77 I've posted my question on Stack Overflow: stackoverflow.com/questions/43830844/… – Pradana Aumars May 7 '17 at 11:21
• Quick question: multiple SDL_Rect can be assigned to one clip SDL_Rect, right? – Pradana Aumars May 7 '17 at 17:51
• I'm sorry, what? – Tyyppi_77 May 7 '17 at 17:55

I think what you're referring to is line wrapping i.e. when a line of text gets too wide for the current display, it is automatically distributed across a number of lines (much like this text depending on the device you're reading this on).

You can use the SDL_TTF function TTF_RenderText_Blended_Wrapped to do this, passing your desired width at which the line will wrap (e.g. 800 pixels).

Also one thing that might make your coding easier, you can declare an SDL_Rect like this (note the curly braces):

SDL_Rect RectToDeclare = {int x, int y, int w, int h};


so

SDL_Rect rcGround = {0, 0, 800, 600};
`