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

I’m a beginner in SDL2 and I’m trying to make a window, with a GUI (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 loadMedia();
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: ";
      }
    }
  }
}

void loadMedia()
{
  // 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();
    loadMedia();

    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*.

All i can say, try my GUI widget toolkit https://github.com/actsl/kiss_sdl so you can see how it can be done, you see all code that is, you can then write your own if you like. This is the most efficient way to make such simple GUI, at least this is what i tried to do, and this is what i made it for. People use it, it has 53 stars so far, for 10 months no one has found any issues any more. Try it and if you have questions, ask them here. Writing an entirely new GUI from scratch, you can do it, but it’s a useless effort, or then it’s an aim by itself, but too big project to make it all here, asking questions, and you may do much more useful with your time that you spend for making it.

This is not a promotion, just an honest and friendly advice, or in fact helping others is why i made my widget toolkit, what sense making it would otherwise make?

Thank you for replying! I will sure take look into your premade GUI. The only issue I have is that I use C++ in my program. I hope that I can use C++ for the game mechanics but keep C for the GUI. Maybe I will contribute to your GUI, if I can. Anyways, here’s the full repository. Maybe you can contribute something to it as well!

Thank you! Yes you can use it with C++, the same as SDL, it’s C, but you can use it with C++ with no problem.
------------------------
kiss_sdl - Simple generic GUI widget toolkit for SDL2 https://github.com/actsl/kiss_sdl