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
+6 -2
View File
@@ -34,8 +34,12 @@ bool Font::isPrintableChar(uint codepoint) const {
} }
} }
int Font::calcWidth(std::wstring text, size_t length) { int Font::calcWidth(const std::wstring& text, size_t length) {
return std::min(text.length(), length) * 8; 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) { void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {
+10 -3
View File
@@ -28,9 +28,16 @@ public:
/// @brief Calculate text width in pixels /// @brief Calculate text width in pixels
/// @param text selected text /// @param text selected text
/// @param length max text chunk length (default: no limit) /// @param length max substring length (default: no limit)
/// @return pixel width of the text /// @return pixel width of the substring
int calcWidth(std::wstring text, size_t length=-1); 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) /// @brief Check if character is visible (non-whitespace)
/// @param codepoint character unicode codepoint /// @param codepoint character unicode codepoint
+56 -19
View File
@@ -7,21 +7,41 @@
using namespace gui; 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; resetFlag = false;
lines.clear(); lines.clear();
lines.push_back(LineScheme {0, false});
if (font == nullptr) {
wrap = false;
}
size_t len = 0;
if (multiline) { if (multiline) {
lines.push_back(LineScheme {0}); for (size_t i = 0; i < text.length(); i++, len++) {
for (size_t i = 0; i < text.length(); i++) {
if (text[i] == L'\n') { 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) Label::Label(std::string text, std::string fontName)
@@ -30,7 +50,7 @@ Label::Label(std::string text, std::string fontName)
fontName(fontName) fontName(fontName)
{ {
setInteractive(false); 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) fontName(fontName)
{ {
setInteractive(false); setInteractive(false);
cache.update(this->text, multiline); cache.update(this->text, multiline, textWrap);
} }
void Label::setText(std::wstring text) { void Label::setText(std::wstring text) {
@@ -48,7 +68,7 @@ void Label::setText(std::wstring text) {
return; return;
} }
this->text = text; this->text = text;
cache.update(this->text, multiline); cache.update(this->text, multiline, textWrap);
} }
const std::wstring& Label::getText() const { const std::wstring& Label::getText() const {
@@ -88,6 +108,11 @@ size_t Label::getTextLineOffset(size_t line) const {
return cache.lines.at(line).offset; 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 { int Label::getLineYOffset(uint line) const {
return line * totalLineHeight + textYOffset; return line * totalLineHeight + textYOffset;
} }
@@ -113,12 +138,16 @@ uint Label::getLinesNumber() const {
} }
void Label::draw(const GfxContext* pctx, Assets* assets) { 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) { if (supplier) {
setText(supplier()); setText(supplier());
} }
if (cache.resetFlag) {
auto batch = pctx->getBatch2D(); cache.update(text, multiline, textWrap);
auto font = assets->getFont(fontName); }
batch->setColor(getColor()); batch->setColor(getColor());
@@ -157,13 +186,12 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
totalLineHeight = lineHeight; totalLineHeight = lineHeight;
if (multiline) { if (multiline) {
size_t offset = 0; for (size_t i = 0; i < cache.lines.size(); i++) {
for (uint 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); std::wstring_view view(text.c_str()+offset, text.length()-offset);
size_t end = view.find(L'\n'); if (i < cache.lines.size()-1) {
if (end != std::wstring::npos) { view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
view = std::wstring_view(text.c_str()+offset, end);
offset += end + 1;
} }
font->draw(batch, view, pos.x, pos.y + i * totalLineHeight, FontStyle::none); 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 { bool Label::isMultiline() const {
return multiline; return multiline;
} }
void Label::setTextWrapping(bool flag) {
this->textWrap = flag;
cache.resetFlag = true;
}
bool Label::isTextWrapping() const {
return textWrap;
}
+15 -2
View File
@@ -3,17 +3,23 @@
#include "UINode.hpp" #include "UINode.hpp"
class Font;
namespace gui { namespace gui {
struct LineScheme { struct LineScheme {
size_t offset; size_t offset;
bool fake;
}; };
struct LabelCache { struct LabelCache {
Font* font = nullptr;
std::vector<LineScheme> lines; std::vector<LineScheme> lines;
/// @brief Reset cache flag /// @brief Reset cache flag
bool resetFlag = true; 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 { class Label : public UINode {
@@ -29,9 +35,12 @@ namespace gui {
/// @brief Vertical alignment (only when multiline is set to false) /// @brief Vertical alignment (only when multiline is set to false)
Align valign = Align::center; 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; bool multiline = false;
/// @brief Text wrapping (works only if multiline is enabled)
bool textWrap = true;
/// @brief Text Y offset relative to label position /// @brief Text Y offset relative to label position
/// (last calculated alignment) /// (last calculated alignment)
int textYOffset = 0; int textYOffset = 0;
@@ -80,6 +89,7 @@ namespace gui {
virtual uint getLineByYOffset(int offset) const; virtual uint getLineByYOffset(int offset) const;
virtual uint getLineByTextIndex(size_t index) const; virtual uint getLineByTextIndex(size_t index) const;
virtual uint getLinesNumber() const; virtual uint getLinesNumber() const;
virtual bool isFakeLine(size_t line) const;
virtual void draw(const GfxContext* pctx, Assets* assets) override; virtual void draw(const GfxContext* pctx, Assets* assets) override;
@@ -87,6 +97,9 @@ namespace gui {
virtual void setMultiline(bool multiline); virtual void setMultiline(bool multiline);
virtual bool isMultiline() const; virtual bool isMultiline() const;
virtual void setTextWrapping(bool flag);
virtual bool isTextWrapping() const;
}; };
} }
+20 -4
View File
@@ -101,11 +101,19 @@ void TextBox::drawBackground(const GfxContext* pctx, Assets* assets) {
batch->setColor(glm::vec4(1, 1, 1, 0.1f)); batch->setColor(glm::vec4(1, 1, 1, 0.1f));
glm::vec2 lcoord = label->calcPos(); glm::vec2 lcoord = label->calcPos();
lcoord.y -= 2; lcoord.y -= 2;
uint line = label->getLineByTextIndex(caret); uint line = label->getLineByTextIndex(caret);
int lineY = label->getLineYOffset(line); while (label->isFakeLine(line)) {
int lineHeight = font->getLineHeight() * label->getLineInterval(); line--;
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, 1); }
batch->rect(lcoord.x, lcoord.y+lineY+lineHeight-2, label->getSize().x, 1); 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)); label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
@@ -226,6 +234,14 @@ bool TextBox::isMultiline() const {
return multiline; return multiline;
} }
void TextBox::setTextWrapping(bool flag) {
label->setTextWrapping(flag);
}
bool TextBox::isTextWrapping() const {
return label->isTextWrapping();
}
void TextBox::setEditable(bool editable) { void TextBox::setEditable(bool editable) {
this->editable = editable; this->editable = editable;
} }
+6
View File
@@ -124,6 +124,12 @@ namespace gui {
/// @brief Check if multiline mode is enabled /// @brief Check if multiline mode is enabled
virtual bool isMultiline() const; 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 /// @brief Enable/disable text editing feature
virtual void setEditable(bool editable); virtual void setEditable(bool editable);
+5 -19
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) { void guiutil::alert(GUI* gui, const std::wstring& text, runnable on_hidden) {
auto menu = gui->getMenu(); 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)); panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
// TODO: implement built-in text wrapping auto label = std::make_shared<Label>(text);
const int wrap_length = 60; label->setMultiline(true);
if (text.length() > wrap_length) { label->setSize(glm::vec2(1, 80));
size_t offset = 0; panel->add(label);
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));
}
panel->add(std::make_shared<Button>( panel->add(std::make_shared<Button>(
langs::get(L"Ok"), glm::vec4(10.f), langs::get(L"Ok"), glm::vec4(10.f),
[=](GUI* gui) { [=](GUI* gui) {
+9
View File
@@ -215,6 +215,12 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
); );
label->textSupplier(supplier); 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; return label;
} }
@@ -291,6 +297,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
if (element->has("multiline")) { if (element->has("multiline")) {
textbox->setMultiline(element->attr("multiline").asBool()); textbox->setMultiline(element->attr("multiline").asBool());
} }
if (element->has("text-wrap")) {
textbox->setTextWrapping(element->attr("text-wrap").asBool());
}
if (element->has("editable")) { if (element->has("editable")) {
textbox->setEditable(element->attr("editable").asBool()); textbox->setEditable(element->attr("editable").asBool());
+7 -4
View File
@@ -85,9 +85,10 @@ static void show_content_missing(
menus::show(engine, "reports/missing_content", std::move(args)); 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 { try {
engine->loadWorldContent(folder); engine->loadWorldContent(folder);
return true;
} catch (const contentpack_error& error) { } catch (const contentpack_error& error) {
engine->setScreen(std::make_shared<MenuScreen>(engine)); engine->setScreen(std::make_shared<MenuScreen>(engine));
// could not to find or read pack // 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": "+ engine->getGUI(), langs::get(L"error.pack-not-found")+L": "+
util::str2wstr_utf8(error.getPackId()) util::str2wstr_utf8(error.getPackId())
); );
return; return false;
} catch (const std::runtime_error& error) { } catch (const std::runtime_error& error) {
engine->setScreen(std::make_shared<MenuScreen>(engine)); engine->setScreen(std::make_shared<MenuScreen>(engine));
guiutil::alert( guiutil::alert(
engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+ engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+
util::str2wstr_utf8(error.what()) 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) { void EngineController::openWorld(std::string name, bool confirmConvert) {
auto paths = engine->getPaths(); auto paths = engine->getPaths();
auto folder = paths->getWorldsFolder()/fs::u8path(name); auto folder = paths->getWorldsFolder()/fs::u8path(name);
loadWorldContent(engine, folder); if (!loadWorldContent(engine, folder)) {
return;
}
auto* content = engine->getContent(); auto* content = engine->getContent();