300 lines
7.6 KiB
C++
300 lines
7.6 KiB
C++
#include "Label.hpp"
|
|
|
|
#include <utility>
|
|
|
|
#include "assets/Assets.hpp"
|
|
#include "graphics/core/DrawContext.hpp"
|
|
#include "graphics/core/Batch2D.hpp"
|
|
#include "graphics/core/Font.hpp"
|
|
#include "graphics/ui/markdown.hpp"
|
|
#include "util/stringutil.hpp"
|
|
|
|
using namespace gui;
|
|
|
|
void LabelCache::prepare(Font* font, size_t wrapWidth) {
|
|
if (font != this->font) {
|
|
resetFlag = true;
|
|
this->font = font;
|
|
}
|
|
if (wrapWidth != this->wrapWidth) {
|
|
resetFlag = true;
|
|
this->wrapWidth = wrapWidth;
|
|
}
|
|
}
|
|
|
|
size_t LabelCache::getTextLineOffset(size_t line) const {
|
|
line = std::min(lines.size()-1, line);
|
|
return lines.at(line).offset;
|
|
}
|
|
|
|
uint LabelCache::getLineByTextIndex(size_t index) const {
|
|
for (size_t i = 0; i < lines.size(); i++) {
|
|
if (lines[i].offset > index) {
|
|
return i-1;
|
|
}
|
|
}
|
|
return lines.size()-1;
|
|
}
|
|
|
|
void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) {
|
|
resetFlag = false;
|
|
lines.clear();
|
|
lines.push_back(LineScheme {0, false});
|
|
|
|
if (font == nullptr) {
|
|
wrap = false;
|
|
}
|
|
|
|
if (multiline) {
|
|
size_t len = 0;
|
|
for (size_t i = 0; i < text.length(); i++, len++) {
|
|
if (text[i] == L'\n') {
|
|
lines.push_back(LineScheme {i+1, false});
|
|
len = 0;
|
|
} else if (i > 0 && wrap && text[i+1] != L'\n') {
|
|
size_t width = font->calcWidth(text, i-len-1, i-(i-len)+2);
|
|
if (width >= wrapWidth) {
|
|
// starting a fake line
|
|
lines.push_back(LineScheme {i+1, true});
|
|
len = 0;
|
|
}
|
|
}
|
|
}
|
|
if (font != nullptr) {
|
|
int lineHeight = font->getLineHeight();
|
|
int maxWidth = 0;
|
|
for (int i = 0; i < lines.size() - 1; i++) {
|
|
const auto& next = lines[i + 1];
|
|
const auto& cur = lines[i];
|
|
maxWidth = std::max(
|
|
font->calcWidth(
|
|
text.substr(cur.offset, next.offset - cur.offset)
|
|
),
|
|
maxWidth
|
|
);
|
|
}
|
|
maxWidth = std::max(
|
|
font->calcWidth(
|
|
text.substr(lines[lines.size() - 1].offset)
|
|
),
|
|
maxWidth
|
|
);
|
|
multilineWidth = maxWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
Label::Label(const std::string& text, std::string fontName)
|
|
: UINode(glm::vec2(text.length() * 8, 16)),
|
|
text(util::str2wstr_utf8(text)),
|
|
fontName(std::move(fontName))
|
|
{
|
|
setInteractive(false);
|
|
cache.update(this->text, multiline, textWrap);
|
|
}
|
|
|
|
|
|
Label::Label(const std::wstring& text, std::string fontName)
|
|
: UINode(glm::vec2(text.length() * 8, 16)),
|
|
text(text),
|
|
fontName(std::move(fontName))
|
|
{
|
|
setInteractive(false);
|
|
cache.update(this->text, multiline, textWrap);
|
|
}
|
|
|
|
Label::~Label() = default;
|
|
|
|
glm::vec2 Label::calcSize() {
|
|
auto font = cache.font;
|
|
uint lineHeight = font->getLineHeight();
|
|
if (cache.lines.size() > 1) {
|
|
lineHeight *= lineInterval;
|
|
}
|
|
auto view = std::wstring_view(text);
|
|
if (multiline) {
|
|
return glm::vec2(
|
|
cache.multilineWidth,
|
|
lineHeight * cache.lines.size() + font->getYOffset()
|
|
);
|
|
}
|
|
return glm::vec2 (
|
|
cache.font->calcWidth(view),
|
|
lineHeight * cache.lines.size() + font->getYOffset()
|
|
);
|
|
}
|
|
|
|
void Label::setText(std::wstring text) {
|
|
if (markup == "md") {
|
|
auto [processedText, styles] = markdown::process(text, true);
|
|
text = std::move(processedText);
|
|
setStyles(std::move(styles));
|
|
}
|
|
if (text == this->text && !cache.resetFlag) {
|
|
return;
|
|
}
|
|
this->text = std::move(text);
|
|
cache.update(this->text, multiline, textWrap);
|
|
|
|
if (cache.font && autoresize) {
|
|
setSize(calcSize());
|
|
}
|
|
}
|
|
|
|
const std::wstring& Label::getText() const {
|
|
return text;
|
|
}
|
|
|
|
void Label::setFontName(std::string name) {
|
|
this->fontName = std::move(name);
|
|
}
|
|
|
|
const std::string& Label::getFontName() const {
|
|
return fontName;
|
|
}
|
|
|
|
void Label::setVerticalAlign(Align align) {
|
|
this->valign = align;
|
|
}
|
|
|
|
Align Label::getVerticalAlign() const {
|
|
return valign;
|
|
}
|
|
|
|
float Label::getLineInterval() const {
|
|
return lineInterval;
|
|
}
|
|
|
|
void Label::setLineInterval(float interval) {
|
|
lineInterval = interval;
|
|
}
|
|
|
|
int Label::getTextYOffset() const {
|
|
return textYOffset;
|
|
}
|
|
|
|
size_t Label::getTextLineOffset(size_t line) const {
|
|
return cache.getTextLineOffset(line);
|
|
}
|
|
|
|
bool Label::isFakeLine(size_t line) const {
|
|
line = std::min(cache.lines.size()-1, line);
|
|
return cache.lines.at(line).fake;
|
|
}
|
|
|
|
int Label::getLineYOffset(uint line) const {
|
|
return line * totalLineHeight + textYOffset;
|
|
}
|
|
|
|
uint Label::getLineByYOffset(int offset) const {
|
|
if (offset < textYOffset) {
|
|
return 0;
|
|
}
|
|
return (offset - textYOffset) / totalLineHeight;
|
|
}
|
|
|
|
uint Label::getLineByTextIndex(size_t index) const {
|
|
return cache.getLineByTextIndex(index);
|
|
}
|
|
|
|
uint Label::getLinesNumber() const {
|
|
return cache.lines.size();
|
|
}
|
|
|
|
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
|
auto batch = pctx.getBatch2D();
|
|
auto font = assets.get<Font>(fontName);
|
|
cache.prepare(font, static_cast<size_t>(glm::abs(getSize().x)));
|
|
|
|
if (supplier) {
|
|
setText(supplier());
|
|
}
|
|
if (cache.resetFlag) {
|
|
cache.update(text, multiline, textWrap);
|
|
}
|
|
batch->setColor(calcColor());
|
|
|
|
uint lineHeight = font->getLineHeight();
|
|
if (cache.lines.size() > 1) {
|
|
lineHeight *= lineInterval;
|
|
}
|
|
glm::vec2 size = getSize();
|
|
glm::vec2 newsize = calcSize();
|
|
if (autoresize) {
|
|
setSize(newsize);
|
|
}
|
|
|
|
glm::vec2 pos = calcPos();
|
|
switch (align) {
|
|
case Align::left: break;
|
|
case Align::center: pos.x += (size.x-newsize.x)*0.5f; break;
|
|
case Align::right: pos.x += size.x-newsize.x; break;
|
|
}
|
|
switch (valign) {
|
|
case Align::top: break;
|
|
case Align::center: pos.y += (size.y-newsize.y)*0.5f; break;
|
|
case Align::bottom: pos.y += size.y-newsize.y; break;
|
|
}
|
|
textYOffset = pos.y-calcPos().y;
|
|
totalLineHeight = lineHeight;
|
|
|
|
if (multiline) {
|
|
for (size_t i = 0; i < cache.lines.size(); i++) {
|
|
auto& line = cache.lines[i];
|
|
size_t offset = line.offset;
|
|
std::wstring_view view(text.c_str()+offset, text.length()-offset);
|
|
if (i < cache.lines.size()-1) {
|
|
view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
|
|
}
|
|
font->draw(*batch, view, pos.x, pos.y + i * totalLineHeight, styles.get(), offset);
|
|
}
|
|
} else {
|
|
font->draw(*batch, text, pos.x, pos.y, styles.get(), 0);
|
|
}
|
|
}
|
|
|
|
void Label::textSupplier(wstringsupplier supplier) {
|
|
this->supplier = std::move(supplier);
|
|
}
|
|
|
|
void Label::setAutoResize(bool flag) {
|
|
this->autoresize = flag;
|
|
}
|
|
|
|
bool Label::isAutoResize() const {
|
|
return autoresize;
|
|
}
|
|
|
|
void Label::setMultiline(bool multiline) {
|
|
if (multiline != this->multiline) {
|
|
this->multiline = multiline;
|
|
cache.resetFlag = true;
|
|
}
|
|
}
|
|
|
|
bool Label::isMultiline() const {
|
|
return multiline;
|
|
}
|
|
|
|
void Label::setTextWrapping(bool flag) {
|
|
this->textWrap = flag;
|
|
cache.resetFlag = true;
|
|
}
|
|
|
|
bool Label::isTextWrapping() const {
|
|
return textWrap;
|
|
}
|
|
|
|
void Label::setMarkup(std::string_view lang) {
|
|
markup = lang;
|
|
setText(text);
|
|
}
|
|
|
|
const std::string& Label::getMarkup() const {
|
|
return markup;
|
|
}
|
|
|
|
void Label::setStyles(std::unique_ptr<FontStylesScheme> styles) {
|
|
this->styles = std::move(styles);
|
|
}
|