diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index 79ede561..12975797 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -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) { diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 1a8dc4a6..daa9b1d3 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -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 diff --git a/src/graphics/ui/elements/Label.cpp b/src/graphics/ui/elements/Label.cpp index 1d84a0ea..7e7e7928 100644 --- a/src/graphics/ui/elements/Label.cpp +++ b/src/graphics/ui/elements/Label.cpp @@ -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(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; +} diff --git a/src/graphics/ui/elements/Label.hpp b/src/graphics/ui/elements/Label.hpp index 43d553aa..1bd6ad30 100644 --- a/src/graphics/ui/elements/Label.hpp +++ b/src/graphics/ui/elements/Label.hpp @@ -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 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; }; } diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index bad7fbcc..3a9c1671 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -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; diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 6dfe61e9..9013fef0 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -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); diff --git a/src/graphics/ui/gui_util.cpp b/src/graphics/ui/gui_util.cpp index af950907..d31ffc0e 100644 --- a/src/graphics/ui/gui_util.cpp +++ b/src/graphics/ui/gui_util.cpp @@ -42,27 +42,13 @@ std::shared_ptr 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(glm::vec2(500, 200), glm::vec4(8.0f), 8.0f); + auto panel = std::make_shared(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