/* * TextArea.cpp * * Created on: Nov 27, 2016 * Author: tibi */ #include #include #include #include #include #include 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 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 */