I am trying to EFFICIENTLY highlight text from SDL2. Say I have a string of text displayed, and the text is something like: "Hello World!", and I want to highlight "orl" from world. If I was using word, I could click on 'l' and move left with my mouse or vice versa with clicking on 'o' and moving right from there. Is there any EFFICIENT way of doing this? If so, how? I can't get an efficient solution that doesn't make my fps drop exponentially as the text increases. Also (additional if possible), if I have a textbox that wraps text around it and new lines if it won't fit, how can I highlight the text if I begin clicking on the first word, and move my mouse down, and it highlights the entire line up to the character I am hovering over. Thanks
FYI: These are my text objects (header and cpp file)
text.h
#ifndef TEXT_H
#define TEXT_H
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL.h>
#include "events.h"
#include <iostream>
#include <string>
#include <vector>
using std::vector;
using std::string;
using std::cout;
using std::endl;
using std::exception;
class Text
{
public:
Events EVENTS;
Text(void);
///Public variables
static vector <TTF_Font*> fonts;
const int lowestFontSize = 1;
const int highestFontSize = 100;
int fontIndex;
int textSize;
SDL_Rect textRect;
int x;
int y;
int initialX, initialY;
bool setup;
static int numOfInstances;
///Functions
void Setup(SDL_Renderer *renderer, string txt, int x, int y, int txtSize, SDL_Color Colour = {0,0,0}, bool isBold = false, string fontType = "arial.ttf", bool isWrapped=false, int theWrapWidth=0);
string Get_Text();
void Change_Text(SDL_Renderer *renderer, string newText);
void Draw_Text(SDL_Renderer *renderer);
void Change_Position(SDL_Renderer *renderer, int xPos, int yPos);
void Change_Position_And_Text(SDL_Renderer *renderer, int xPos, int yPos, string newText);
int Text_Width(int FirstCharIndex, int numOfCharsPastFirstIndex);
int Text_Height(int FirstCharIndex, int numOfCharsPastFirstIndex);
void Free_All();
private:
///SDL stuff
SDL_Texture *textTexture;
SDL_Surface *textSurface;
SDL_Color colour;
SDL_Point point;
///text varibales
string text;
int textW;
int textH;
bool bold;
bool wrapped;
int wrappedWidth;
};
#endif
text.cpp
#include "text.h"
int Text::numOfInstances = 0;
vector <TTF_Font*> Text::fonts;
Text::Text()
{
setup = false;
textTexture = NULL;
textSurface = NULL;
}
void Text::Setup(SDL_Renderer *renderer, string txt, int xPos, int yPos, int txtSize, SDL_Color Colour, bool isBold, string fontType, bool isWrapped, int theWrapWidth )
{
if (setup == false){
numOfInstances += 1;
wrapped = isWrapped;
wrappedWidth = theWrapWidth;
text = txt;
textSize = txtSize;
bold = isBold;
colour = Colour;
textW = 0;
textH = 0;
x = xPos;
y = yPos;
initialX = x;
initialY = y;
fontIndex = textSize-lowestFontSize -1;
///One time setups
if (numOfInstances == 1){
try{
TTF_Init();
//cout << "Initialised ttf" << endl;
}
catch (exception &err){
cout << "Could not initialise ttf for text \"" << text << "\". Error from SDL is: " << TTF_GetError() << ". Error from C++ is: " << err.what() << endl;
}
for (int i=lowestFontSize; i <= highestFontSize; i++){
TTF_Font *currentFont = TTF_OpenFont(fontType.c_str(), i);
if (!currentFont){
cout << "Error with font in text \"" << txt << "\" Error is: " << SDL_GetError() << endl;
}
//TTF_SetFontKerning(currentFont, 0);
fonts.push_back(currentFont);
}
}
if (bold == true){
TTF_SetFontStyle(fonts[fontIndex], TTF_STYLE_BOLD);
}
if (!SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "2" ) ){ ///2 is highest
cout << "Text rendering quality not enabled " << text << endl;
}
if (text != ""){ ///Only create textures if there is text
if (wrapped == true){
textSurface = TTF_RenderText_Blended_Wrapped(fonts[fontIndex], text.c_str(), colour, wrappedWidth); ///Recreate the textures/surfaces
}
else{
textSurface = TTF_RenderText_Blended(fonts[fontIndex], text.c_str(), colour); ///Recreate the textures/surfaces
}
if (!textSurface){
cout << "Unable to create surface of text " << text << " error is: " << SDL_GetError() << endl;
}
textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture){
cout << "Unable to create texture from surface of text " << text << " error is: " << SDL_GetError() << endl;
}
SDL_FreeSurface(textSurface);
textSurface = NULL;
SDL_QueryTexture(textTexture, NULL, NULL, &textW, &textH);
textRect = {x, y, textW, textH};
}
point = {0, 0};
setup = true;
}
else{
//cout << "Trying to setup a text already setup! " << text << endl;
}
}
void Text::Change_Position_And_Text(SDL_Renderer *renderer, int xPos, int yPos, string newText )
{
if (setup == true){
text = newText;
x = xPos;
y = yPos;
textRect.x = x;
textRect.y = y;
if (textTexture != NULL){
SDL_DestroyTexture(textTexture); ///Free memory not going to be used again.
textTexture = NULL;
}
if (text != ""){
if (wrapped == true){
textSurface = TTF_RenderText_Blended_Wrapped(fonts[fontIndex], text.c_str(), colour, wrappedWidth); ///Recreate the textures/surfaces
}
else{
textSurface = TTF_RenderText_Blended(fonts[fontIndex], text.c_str(), colour); ///Recreate the textures/surfaces
}
if (!textSurface){
cout << "Unable to create surface of text " << text << " error is: " << SDL_GetError() << endl;
}
textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture){
cout << "Unable to create texture from surface of text " << text << " error is: " << SDL_GetError() << endl;
}
SDL_FreeSurface(textSurface);
textSurface = NULL;
SDL_QueryTexture(textTexture, NULL, NULL, &textW, &textH); ///neeed this
textRect = {x, y, textW, textH};
}
}
}
void Text::Change_Position(SDL_Renderer *renderer, int xPos, int yPos)
{
if (setup == true){
x = xPos;
y = yPos ;
textRect.x = xPos;
textRect.y = yPos;
}
}
void Text::Change_Text(SDL_Renderer *renderer, string newText)
{
if (setup == true){
text = newText;
if (textTexture != NULL){
SDL_DestroyTexture(textTexture); ///Free memory not going to be used again.
textTexture = NULL;
}
if (text != ""){
if (wrapped == true){
textSurface = TTF_RenderText_Blended_Wrapped(fonts[fontIndex], text.c_str(), colour, wrappedWidth); ///Recreate the textures/surfaces
}
else{
textSurface = TTF_RenderText_Blended(fonts[fontIndex], text.c_str(), colour); ///Recreate the textures/surfaces
}
if (!textSurface){
cout << "Unable to create surface of text " << text << " error is: " << SDL_GetError() << endl;
}
textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture){
cout << "Unable to create texture from surface of text " << text << " error is: " << SDL_GetError() << endl;
}
SDL_FreeSurface(textSurface);
textSurface = NULL;
SDL_QueryTexture(textTexture, NULL, NULL, &textW, &textH); ///neeed this
textRect = {x, y, textW, textH};
}
}
}
int Text::Text_Width(int FirstCharIndex, int numOfCharsPastFirstIndex)
{
int w,h;
string textSelection = text.substr(FirstCharIndex, numOfCharsPastFirstIndex);
TTF_SizeText(fonts[fontIndex], textSelection.c_str(), &w, &h);
return w;
}
int Text::Text_Height(int FirstCharIndex, int numOfCharsPastFirstIndex)
{
int w,h;
string textSelection = text.substr(FirstCharIndex, numOfCharsPastFirstIndex);
TTF_SizeText(fonts[fontIndex], textSelection.c_str(), &w, &h);
return h;
}
string Text::Get_Text()
{
if (setup == true){
return text;
}
else{
return "";
//cout << "Text not setup when trying to obtain text through Get_Text() function" << endl;
}
}
void Text::Draw_Text(SDL_Renderer *renderer)
{
if (setup == true){
if (SDL_PointInRect(&EVENTS.mousePos, &textRect) && EVENTS.currentCursor != SDL_SYSTEM_CURSOR_IBEAM){
EVENTS.Change_Cursor(SDL_SYSTEM_CURSOR_IBEAM);
}
SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
}
else{
//cout << "Text not setup when trying to draw it in Draw_Text() function" << endl;
}
}
void Text::Free_All()
{
if (setup == true){
if (textSurface == NULL){
//cout << "Text surface already free'd" << endl;
}
else{
SDL_FreeSurface(textSurface);
textSurface = NULL;
//cout << "Free'd surface \n";
}
if (textTexture == NULL){
//cout << "Could not free memory for text \"" << text << "\". Error from SDL is: " << TTF_GetError() << endl;
}
else{
SDL_DestroyTexture(textTexture);
textTexture = NULL;
}
if (numOfInstances == 1){
for (int i=0; i <= (highestFontSize-lowestFontSize); i++){
TTF_CloseFont(fonts[i]);
//cout << "Closed " << lowestFontSize+i << endl;
}
try{
TTF_Quit();
//cout << "Quit ttf" << endl;
}
catch (exception &err){
cout << "Could not quit ttf for text \"" << text << "\". Error from SDL is: " << TTF_GetError() << ". Error from C++ is: " << err.what() << endl;
}
}
///For TTF_Init();
numOfInstances -= 1;
//cout << "Free'd " << text << endl;
}
else{
//cout << "Text not setup yet when trying to free!" << endl;
}
setup = false;
}
If anyone has the same problem, I figured out something pretty good. Its effectively zero performance loss. Just for now, I can get the rects of each individual character, I will update it for highlighting as I come up with some more ways. For now, I have 2 integer vectors belonging to the text class. As I setup the text or change the text, I iterate through the string, and lets say with the string of
abcdefg
, first iteration my currentString is a, second, ab, third abc, fourth, abcd and so on. Then i call TTF_SizeText on this current string for each loop. I then append the width and height obtained from this to a vector. Rather than redoing this as you are highlighting, you can use the change in widths of the consecutive values in the vector. For example, if I want the width of 'c', I can do,int deltaWidth = widthOfChars[3] - widthOfChars[2]
and the difference is the width of 'c'. That way when I iterate through the string, I create a rectangle of the current character, in this case its 'c', and whilst doing it, check if the mouse has hovered over it. Currently I am just drawing a filled rectangle for each character and it is not costing me any noticable performance loss!