2025-03-20 20:59:19 +03:00

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);
}