farmlands/src/gui/widgets/TextArea.cpp

281 lines
5.8 KiB
C++

/*
* TextArea.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/widgets/TextArea.h>
#include <resources/Resources.h>
#include <cassert>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <SDL2/SDL_ttf.h>
namespace farmlands
{
namespace gui
{
namespace widgets
{
TextArea::TextArea()
: m_text(),
m_wrappedText(),
m_textChanged(true),
m_fontId(resources::R::Fonts::DejaVuSans),
m_fontSize(9),
m_color(),
m_align(TextAlign::MiddleCenter),
m_horWrap(TextHorizontalWrapping::Wrap),
m_verWrap(TextVerticalWrapping::Overflow),
m_renderedLines()
{
}
TextArea::~TextArea()
{
}
void TextArea::render(RenderContext& context)
{
UIElement::render(context);
// Obtain font
TTF_Font* font = context.resManager->font(m_fontId, m_fontSize * context.uiScale);
if (font == nullptr)
return; // TODO: handle error (maybe log it)
// Split text in lines
if (m_textChanged)
{
wrapText(font);
renderLines(context, font);
}
// Compute y location of first line
int lineH = TTF_FontLineSkip(font);
int totalH = 0;
if (m_align == TextAlign::MiddleLeft || m_align == TextAlign::MiddleCenter || m_align == TextAlign::MiddleRight)
{
int spaceLeft = height() - lineH * m_renderedLines.size();
totalH = spaceLeft / 2;
}
else if (m_align == TextAlign::BottomLeft || m_align == TextAlign::BottomCenter || m_align == TextAlign::BottomRight)
{
int spaceLeft = height() - lineH * m_renderedLines.size();
totalH = spaceLeft;
}
// Draw each line
for (SDL_Texture* lineTexture : m_renderedLines)
{
SDL_Rect dest;
SDL_QueryTexture(lineTexture, NULL, NULL, &dest.w, &dest.h);
// Compute X position
dest.x = x();
if (m_align == TextAlign::TopCenter || m_align == TextAlign::MiddleCenter || m_align == TextAlign::BottomCenter)
{
int spaceLeft = width() - dest.w;
dest.x += spaceLeft / 2;
}
else if (m_align == TextAlign::TopRight || m_align == TextAlign::MiddleRight || m_align == TextAlign::BottomRight)
{
int spaceLeft = width() - dest.w;
dest.x += spaceLeft;
}
// Compute Y position
dest.y = y() + totalH;
// Draw
SDL_RenderCopy(context.sdlRenderer->internalRenderer(), lineTexture, NULL, &dest);
totalH += lineH;
}
}
void TextArea::setText(const std::string& text)
{
m_text = text;
m_textChanged = true;
}
void TextArea::setFont(int fontId)
{
m_fontId = fontId;
m_textChanged = true;
}
void TextArea::setTextSize(int size)
{
m_fontSize = size;
m_textChanged = true;
}
void TextArea::setColor(SDL_Color color)
{
m_color = color;
}
void TextArea::setColor(float r, float g, float b, float a)
{
m_color.r = r * 255;
m_color.g = g * 255;
m_color.b = b * 255;
m_color.a = a * 255;
}
void TextArea::setAlignment(TextAlign align)
{
m_align = align;
}
void TextArea::setHorizontalWrap(TextHorizontalWrapping wrap)
{
m_horWrap = wrap;
m_textChanged = true;
}
void TextArea::setVerticalWrap(TextVerticalWrapping wrap)
{
m_verWrap = wrap;
m_textChanged = true;
}
void TextArea::wrapText(TTF_Font* font)
{
assert(font != NULL);
// Current width and total height
int currentW = 0;
int totalH = 0;
int lineBegin = 0;
int wordBegin = 0;
// Measure required sizes
int lineHeight = TTF_FontLineSkip(font);
int ellipsisW = 0;
if (m_horWrap == TextHorizontalWrapping::Ellipsis)
TTF_SizeText(font, "...", &ellipsisW, NULL);
// Start
m_wrappedText = m_text + " ";
// Process characters
for (int i = 0; i < m_wrappedText.size(); i++)
{
// Remove unwanted character
if (m_wrappedText[i] == '\r')
{
m_wrappedText.erase(i--, 1);
continue;
}
if (m_horWrap != TextHorizontalWrapping::Overflow && isspace(m_wrappedText[i]))
{
std::string word = m_wrappedText.substr(wordBegin, i - wordBegin);
int wordW = 0;
TTF_SizeText(font, word.c_str(), &wordW, NULL);
// Word doesn't fit?
if (currentW + wordW + ellipsisW < width())
{
currentW += wordW;
wordBegin = i;
}
else
{
if (wordBegin == lineBegin)
{
// Search for a split point
int lastGood = -1;
for (int j = wordBegin + 1; j < i; j++)
{
std::string word = m_wrappedText.substr(wordBegin, j - wordBegin);
TTF_SizeText(font, word.c_str(), &wordW, NULL);
if (currentW + wordW + ellipsisW < width())
lastGood = j;
else break;
}
// Cannot split anything :( we don't have enough space
if (lastGood == -1)
return;
m_wrappedText.insert(lastGood + 1, "\n");
i = lastGood + 1;
wordBegin = i;
}
if (m_horWrap == TextHorizontalWrapping::Ellipsis)
{
m_wrappedText.insert(wordBegin, "...");
wordBegin += 3;
i += 3;
}
m_wrappedText[wordBegin] = '\n';
i = wordBegin;
// Trim rest of line
if (m_horWrap == TextHorizontalWrapping::Trim || m_horWrap == TextHorizontalWrapping::Ellipsis)
{
size_t nextLine = i + 1;
while (nextLine < m_wrappedText.size() && m_wrappedText[nextLine] != '\n')
++nextLine;
m_wrappedText.erase(i, nextLine - i);
}
}
}
if (m_wrappedText[i] == '\n')
{
// Trim rest of text
if (m_verWrap == TextVerticalWrapping::Trim && totalH + lineHeight > height())
m_wrappedText.erase(i);
// Update positions
lineBegin = i + 1;
wordBegin = i + 1;
currentW = 0;
totalH += lineHeight;
}
}
m_wrappedText.erase(m_wrappedText.size() - 1);
}
void TextArea::renderLines(RenderContext& context, TTF_Font* font)
{
// Clean up old textures
for (SDL_Texture* tex : m_renderedLines)
SDL_DestroyTexture(tex);
m_renderedLines.clear();
// Break down string into lines
std::vector<std::string> lines;
boost::split(lines, m_wrappedText, boost::is_any_of("\n"), boost::token_compress_off);
for (std::string line : lines)
{
// Render line
SDL_Texture* tex = context.sdlRenderer->renderText(line, font, m_color);
m_renderedLines.push_back(tex);
}
}
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */