RichText/RichText.cpp

338 lines
8.3 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////////////////////////
#include "RichText.hpp"
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/System/String.hpp>
namespace sfe
{
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::setCharacterSize(unsigned int size)
{
for (sf::Text &text : m_texts)
text.setCharacterSize(size);
updateGeometry();
}
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::setFont(const sf::Font &font)
{
for (sf::Text &text : m_texts)
text.setFont(font);
updateGeometry();
}
////////////////////////////////////////////////////////////////////////////////
const std::vector<sf::Text> &RichText::Line::getTexts() const
{
return m_texts;
}
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::appendText(sf::Text text)
{
// Set text offset
updateTextAndGeometry(text);
// Push back
m_texts.push_back(std::move(text));
}
////////////////////////////////////////////////////////////////////////////////
sf::FloatRect RichText::Line::getLocalBounds() const
{
return m_bounds;
}
////////////////////////////////////////////////////////////////////////////////
sf::FloatRect RichText::Line::getGlobalBounds() const
{
return getTransform().transformRect(getLocalBounds());
}
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
states.transform *= getTransform();
for (const sf::Text &text : m_texts)
target.draw(text, states);
}
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::updateGeometry() const
{
m_bounds = sf::FloatRect();
for (sf::Text &text : m_texts)
updateTextAndGeometry(text);
}
////////////////////////////////////////////////////////////////////////////////
void RichText::Line::updateTextAndGeometry(sf::Text &text) const
{
// Set text offset
text.setPosition(m_bounds.width, 0.f);
// Update bounds
int lineSpacing = text.getFont()->getLineSpacing(text.getCharacterSize());
m_bounds.height = std::max(m_bounds.height, static_cast<float>(lineSpacing));
m_bounds.width += text.getGlobalBounds().width;
}
////////////////////////////////////////////////////////////////////////////////
RichText::RichText()
: RichText(nullptr)
{
}
////////////////////////////////////////////////////////////////////////////////
RichText::RichText(const sf::Font &font)
: RichText(&font)
{
}
////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (const sf::Color &color)
{
m_currentColor = color;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (sf::Text::Style style)
{
m_currentStyle = style;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
std::vector<sf::String> explode(const sf::String &string, sf::Uint32 delimiter) {
if (string.isEmpty())
return std::vector<sf::String>();
// For each character in the string
std::vector<sf::String> result;
sf::String buffer;
for (sf::Uint32 character : string) {
// If we've hit the delimiter character
if (character == delimiter) {
// Add them to the result vector
result.push_back(buffer);
buffer.clear();
} else {
// Accumulate the next character into the sequence
buffer += character;
}
}
// Add to the result if buffer still has contents or if the last character is a delimiter
if (!buffer.isEmpty() || string[string.getSize() - 1] == delimiter)
result.push_back(buffer);
return result;
}
////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (const sf::String &string)
{
// Maybe skip
if (string.isEmpty())
return *this;
// Explode into substrings
std::vector<sf::String> subStrings = explode(string, '\n');
// Append first substring using the last line
auto it = subStrings.begin();
if (it != subStrings.end()) {
// If there isn't any line, just create it
if (m_lines.empty())
m_lines.resize(1);
// Remove last line's height
Line &line = m_lines.back();
m_bounds.height -= line.getGlobalBounds().height;
// Append text
line.appendText(createText(*it));
// Update bounds
m_bounds.height += line.getGlobalBounds().height;
m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
}
// Append the rest of substrings as new lines
while (++it != subStrings.end()) {
Line line;
line.setPosition(0.f, m_bounds.height);
line.appendText(createText(*it));
m_lines.push_back(std::move(line));
// Update bounds
m_bounds.height += line.getGlobalBounds().height;
m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
}
// Return
return *this;
}
////////////////////////////////////////////////////////////////////////////////
void RichText::setCharacterSize(unsigned int size)
{
// Maybe skip
if (m_characterSize == size)
return;
// Update character size
m_characterSize = size;
// Set texts character size
for (Line &line : m_lines)
line.setCharacterSize(size);
updateGeometry();
}
////////////////////////////////////////////////////////////////////////////////
void RichText::setFont(const sf::Font &font)
{
// Maybe skip
if (m_font == &font)
return;
// Update font
m_font = &font;
// Set texts font
for (Line &line : m_lines)
line.setFont(font);
updateGeometry();
}
////////////////////////////////////////////////////////////////////////////////
void RichText::clear()
{
// Clear texts
m_lines.clear();
// Reset bounds
m_bounds = sf::FloatRect();
}
////////////////////////////////////////////////////////////////////////////////
const std::vector<RichText::Line> &RichText::getLines() const
{
return m_lines;
}
////////////////////////////////////////////////////////////////////////////////
unsigned int RichText::getCharacterSize() const
{
return m_characterSize;
}
////////////////////////////////////////////////////////////////////////////////
const sf::Font *RichText::getFont() const
{
return m_font;
}
////////////////////////////////////////////////////////////
sf::FloatRect RichText::getLocalBounds() const
{
return m_bounds;
}
////////////////////////////////////////////////////////////
sf::FloatRect RichText::getGlobalBounds() const
{
return getTransform().transformRect(getLocalBounds());
}
////////////////////////////////////////////////////////////////////////////////
void RichText::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
states.transform *= getTransform();
for (const Line &line : m_lines)
target.draw(line, states);
}
////////////////////////////////////////////////////////////////////////////////
RichText::RichText(const sf::Font *font)
: m_font(font),
m_characterSize(30),
m_currentColor(sf::Color::White),
m_currentStyle(sf::Text::Regular)
{
}
////////////////////////////////////////////////////////////////////////////////
sf::Text RichText::createText(const sf::String &string) const
{
sf::Text text;
text.setString(string);
text.setColor(m_currentColor);
text.setStyle(m_currentStyle);
text.setCharacterSize(m_characterSize);
if (m_font)
text.setFont(*m_font);
return text;
}
////////////////////////////////////////////////////////////////////////////////
void RichText::updateGeometry() const
{
m_bounds = sf::FloatRect();
for (Line &line : m_lines) {
line.setPosition(0.f, m_bounds.height);
m_bounds.height += line.getGlobalBounds().height;
m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
}
}
}