281 lines
5.8 KiB
C++
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 */
|