text-wrapping + fix

This commit is contained in:
MihailRis 2024-04-25 02:19:50 +03:00
parent 37e089169e
commit 56a8226028
9 changed files with 135 additions and 54 deletions

View File

@ -34,8 +34,12 @@ bool Font::isPrintableChar(uint codepoint) const {
}
}
int Font::calcWidth(std::wstring text, size_t length) {
return std::min(text.length(), length) * 8;
int Font::calcWidth(const std::wstring& text, size_t length) {
return calcWidth(text, 0, length);
}
int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) {
return std::min(text.length()-offset, length) * 8;
}
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {

View File

@ -28,9 +28,16 @@ public:
/// @brief Calculate text width in pixels
/// @param text selected text
/// @param length max text chunk length (default: no limit)
/// @return pixel width of the text
int calcWidth(std::wstring text, size_t length=-1);
/// @param length max substring length (default: no limit)
/// @return pixel width of the substring
int calcWidth(const std::wstring& text, size_t length=-1);
/// @brief Calculate text width in pixels
/// @param text selected text
/// @param offset start of the substring
/// @param length max substring length
/// @return pixel width of the substring
int calcWidth(const std::wstring& text, size_t offset, size_t length);
/// @brief Check if character is visible (non-whitespace)
/// @param codepoint character unicode codepoint

View File

@ -7,30 +7,50 @@
using namespace gui;
void LabelCache::update(const std::wstring& text, bool multiline) {
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;
}
}
void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) {
resetFlag = false;
lines.clear();
lines.push_back(LineScheme {0, false});
if (font == nullptr) {
wrap = false;
}
size_t len = 0;
if (multiline) {
lines.push_back(LineScheme {0});
for (size_t i = 0; i < text.length(); i++) {
for (size_t i = 0; i < text.length(); i++, len++) {
if (text[i] == L'\n') {
lines.push_back(LineScheme {i+1});
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 (lines.empty()) {
lines.push_back(LineScheme {0});
}
}
Label::Label(std::string text, std::string fontName)
: UINode(glm::vec2(text.length() * 8, 15)),
: UINode(glm::vec2(text.length() * 8, 15)),
text(util::str2wstr_utf8(text)),
fontName(fontName)
{
setInteractive(false);
cache.update(this->text, multiline);
cache.update(this->text, multiline, textWrap);
}
@ -40,7 +60,7 @@ Label::Label(std::wstring text, std::string fontName)
fontName(fontName)
{
setInteractive(false);
cache.update(this->text, multiline);
cache.update(this->text, multiline, textWrap);
}
void Label::setText(std::wstring text) {
@ -48,7 +68,7 @@ void Label::setText(std::wstring text) {
return;
}
this->text = text;
cache.update(this->text, multiline);
cache.update(this->text, multiline, textWrap);
}
const std::wstring& Label::getText() const {
@ -88,6 +108,11 @@ size_t Label::getTextLineOffset(size_t line) const {
return cache.lines.at(line).offset;
}
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;
}
@ -113,12 +138,16 @@ uint Label::getLinesNumber() const {
}
void Label::draw(const GfxContext* pctx, Assets* assets) {
auto batch = pctx->getBatch2D();
auto font = assets->getFont(fontName);
cache.prepare(font, static_cast<size_t>(glm::abs(getSize().x)));
if (supplier) {
setText(supplier());
}
auto batch = pctx->getBatch2D();
auto font = assets->getFont(fontName);
if (cache.resetFlag) {
cache.update(text, multiline, textWrap);
}
batch->setColor(getColor());
@ -157,13 +186,12 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
totalLineHeight = lineHeight;
if (multiline) {
size_t offset = 0;
for (uint i = 0; i < cache.lines.size(); i++) {
for (size_t i = 0; i < cache.lines.size(); i++) {
auto& line = cache.lines.at(i);
size_t offset = line.offset;
std::wstring_view view(text.c_str()+offset, text.length()-offset);
size_t end = view.find(L'\n');
if (end != std::wstring::npos) {
view = std::wstring_view(text.c_str()+offset, end);
offset += end + 1;
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, FontStyle::none);
}
@ -187,3 +215,12 @@ void Label::setMultiline(bool multiline) {
bool Label::isMultiline() const {
return multiline;
}
void Label::setTextWrapping(bool flag) {
this->textWrap = flag;
cache.resetFlag = true;
}
bool Label::isTextWrapping() const {
return textWrap;
}

View File

@ -3,17 +3,23 @@
#include "UINode.hpp"
class Font;
namespace gui {
struct LineScheme {
size_t offset;
bool fake;
};
struct LabelCache {
Font* font = nullptr;
std::vector<LineScheme> lines;
/// @brief Reset cache flag
bool resetFlag = true;
size_t wrapWidth = -1;
void update(const std::wstring& text, bool multiline);
void prepare(Font* font, size_t wrapWidth);
void update(const std::wstring& text, bool multiline, bool wrap);
};
class Label : public UINode {
@ -29,8 +35,11 @@ namespace gui {
/// @brief Vertical alignment (only when multiline is set to false)
Align valign = Align::center;
/// @brief Line separators will be ignored if set to false
/// @brief Line separators and wrapping will be ignored if set to false
bool multiline = false;
/// @brief Text wrapping (works only if multiline is enabled)
bool textWrap = true;
/// @brief Text Y offset relative to label position
/// (last calculated alignment)
@ -80,6 +89,7 @@ namespace gui {
virtual uint getLineByYOffset(int offset) const;
virtual uint getLineByTextIndex(size_t index) const;
virtual uint getLinesNumber() const;
virtual bool isFakeLine(size_t line) const;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
@ -87,6 +97,9 @@ namespace gui {
virtual void setMultiline(bool multiline);
virtual bool isMultiline() const;
virtual void setTextWrapping(bool flag);
virtual bool isTextWrapping() const;
};
}

View File

@ -101,11 +101,19 @@ void TextBox::drawBackground(const GfxContext* pctx, Assets* assets) {
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
glm::vec2 lcoord = label->calcPos();
lcoord.y -= 2;
uint line = label->getLineByTextIndex(caret);
int lineY = label->getLineYOffset(line);
int lineHeight = font->getLineHeight() * label->getLineInterval();
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, 1);
batch->rect(lcoord.x, lcoord.y+lineY+lineHeight-2, label->getSize().x, 1);
while (label->isFakeLine(line)) {
line--;
}
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
do {
int lineY = label->getLineYOffset(line);
int lineHeight = font->getLineHeight() * label->getLineInterval();
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, lineHeight);
line++;
} while (line < label->getLinesNumber() && label->isFakeLine(line));
}
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
@ -225,6 +233,14 @@ void TextBox::setMultiline(bool multiline) {
bool TextBox::isMultiline() const {
return multiline;
}
void TextBox::setTextWrapping(bool flag) {
label->setTextWrapping(flag);
}
bool TextBox::isTextWrapping() const {
return label->isTextWrapping();
}
void TextBox::setEditable(bool editable) {
this->editable = editable;

View File

@ -124,6 +124,12 @@ namespace gui {
/// @brief Check if multiline mode is enabled
virtual bool isMultiline() const;
/// @brief Enable/disable text wrapping
virtual void setTextWrapping(bool flag);
/// @brief Check if text wrapping is enabled
virtual bool isTextWrapping() const;
/// @brief Enable/disable text editing feature
virtual void setEditable(bool editable);

View File

@ -42,27 +42,13 @@ std::shared_ptr<gui::UINode> guiutil::create(const std::string& source, scripten
void guiutil::alert(GUI* gui, const std::wstring& text, runnable on_hidden) {
auto menu = gui->getMenu();
auto panel = std::make_shared<Panel>(glm::vec2(500, 200), glm::vec4(8.0f), 8.0f);
auto panel = std::make_shared<Panel>(glm::vec2(500, 300), glm::vec4(8.0f), 8.0f);
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
// TODO: implement built-in text wrapping
const int wrap_length = 60;
if (text.length() > wrap_length) {
size_t offset = 0;
int extra;
while ((extra = text.length() - offset) > 0) {
size_t endline = text.find(L'\n', offset);
if (endline != std::string::npos) {
extra = std::min(extra, int(endline-offset)+1);
}
extra = std::min(extra, wrap_length);
std::wstring part = text.substr(offset, extra);
panel->add(std::make_shared<Label>(part));
offset += extra;
}
} else {
panel->add(std::make_shared<Label>(text));
}
auto label = std::make_shared<Label>(text);
label->setMultiline(true);
label->setSize(glm::vec2(1, 80));
panel->add(label);
panel->add(std::make_shared<Button>(
langs::get(L"Ok"), glm::vec4(10.f),
[=](GUI* gui) {

View File

@ -215,6 +215,12 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
);
label->textSupplier(supplier);
}
if (element->has("multiline")) {
label->setMultiline(element->attr("multiline").asBool());
}
if (element->has("text-wrap")) {
label->setTextWrapping(element->attr("text-wrap").asBool());
}
return label;
}
@ -291,6 +297,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
if (element->has("multiline")) {
textbox->setMultiline(element->attr("multiline").asBool());
}
if (element->has("text-wrap")) {
textbox->setTextWrapping(element->attr("text-wrap").asBool());
}
if (element->has("editable")) {
textbox->setEditable(element->attr("editable").asBool());

View File

@ -85,9 +85,10 @@ static void show_content_missing(
menus::show(engine, "reports/missing_content", std::move(args));
}
static void loadWorldContent(Engine* engine, fs::path folder) {
static bool loadWorldContent(Engine* engine, fs::path folder) {
try {
engine->loadWorldContent(folder);
return true;
} catch (const contentpack_error& error) {
engine->setScreen(std::make_shared<MenuScreen>(engine));
// could not to find or read pack
@ -95,14 +96,14 @@ static void loadWorldContent(Engine* engine, fs::path folder) {
engine->getGUI(), langs::get(L"error.pack-not-found")+L": "+
util::str2wstr_utf8(error.getPackId())
);
return;
return false;
} catch (const std::runtime_error& error) {
engine->setScreen(std::make_shared<MenuScreen>(engine));
guiutil::alert(
engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+
util::str2wstr_utf8(error.what())
);
return;
return false;
}
}
@ -126,7 +127,9 @@ static void loadWorld(Engine* engine, fs::path folder) {
void EngineController::openWorld(std::string name, bool confirmConvert) {
auto paths = engine->getPaths();
auto folder = paths->getWorldsFolder()/fs::u8path(name);
loadWorldContent(engine, folder);
if (!loadWorldContent(engine, folder)) {
return;
}
auto* content = engine->getContent();