From 31d5cb68800d965a668a67fd60bb4d3365181242 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 15 Feb 2025 23:26:32 +0300 Subject: [PATCH] fix TextBox behavior with markup used --- src/graphics/ui/elements/Label.cpp | 24 +++++++---- src/graphics/ui/elements/Label.hpp | 3 ++ src/graphics/ui/elements/TextBox.cpp | 64 +++++++++++++++++----------- src/graphics/ui/elements/TextBox.hpp | 8 ++-- 4 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/graphics/ui/elements/Label.cpp b/src/graphics/ui/elements/Label.cpp index d2678fd7..ddf532b9 100644 --- a/src/graphics/ui/elements/Label.cpp +++ b/src/graphics/ui/elements/Label.cpp @@ -21,6 +21,20 @@ void LabelCache::prepare(Font* font, size_t 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(const std::wstring& text, bool multiline, bool wrap) { resetFlag = false; lines.clear(); @@ -131,8 +145,7 @@ int Label::getTextYOffset() const { } size_t Label::getTextLineOffset(size_t line) const { - line = std::min(cache.lines.size()-1, line); - return cache.lines.at(line).offset; + return cache.getTextLineOffset(line); } bool Label::isFakeLine(size_t line) const { @@ -152,12 +165,7 @@ uint Label::getLineByYOffset(int offset) const { } uint Label::getLineByTextIndex(size_t index) const { - for (size_t i = 0; i < cache.lines.size(); i++) { - if (cache.lines[i].offset > index) { - return i-1; - } - } - return cache.lines.size()-1; + return cache.getLineByTextIndex(index); } uint Label::getLinesNumber() const { diff --git a/src/graphics/ui/elements/Label.hpp b/src/graphics/ui/elements/Label.hpp index df177f71..63637d5f 100644 --- a/src/graphics/ui/elements/Label.hpp +++ b/src/graphics/ui/elements/Label.hpp @@ -20,6 +20,9 @@ namespace gui { void prepare(Font* font, size_t wrapWidth); void update(const std::wstring& text, bool multiline, bool wrap); + + size_t getTextLineOffset(size_t line) const; + uint getLineByTextIndex(size_t index) const; }; class Label : public UINode { diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 1daa590d..27bee570 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -57,6 +57,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { if (!isFocused()) { return; } + const auto& labelText = getText(); + glm::vec2 pos = calcPos(); glm::vec2 size = getSize(); @@ -70,8 +72,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { batch->texture(nullptr); batch->setColor(glm::vec4(1.0f)); if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) { - uint line = label->getLineByTextIndex(caret); - uint lcaret = caret - label->getTextLineOffset(line); + uint line = rawTextCache.getLineByTextIndex(caret); + uint lcaret = caret - rawTextCache.getTextLineOffset(line); int width = font->calcWidth(input, lcaret); batch->rect( lcoord.x + width, @@ -89,10 +91,10 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { batch->setColor(glm::vec4(0.8f, 0.9f, 1.0f, 0.25f)); int start = font->calcWidth( - input, selectionStart - label->getTextLineOffset(startLine) + labelText, selectionStart - label->getTextLineOffset(startLine) ); int end = font->calcWidth( - input, selectionEnd - label->getTextLineOffset(endLine) + labelText, selectionEnd - label->getTextLineOffset(endLine) ); int lineY = label->getLineYOffset(startLine); @@ -192,11 +194,14 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets&) { } void TextBox::refreshLabel() { + rawTextCache.prepare(font, static_cast(getSize().x)); + rawTextCache.update(input, multiline, false); + label->setColor(textColor * glm::vec4(input.empty() ? 0.5f : 1.0f)); const auto& displayText = input.empty() && !hint.empty() ? hint : getText(); if (markup == "md") { - auto [processedText, styles] = markdown::process(displayText, !focused); + auto [processedText, styles] = markdown::process(displayText, !focused || !editable); label->setText(std::move(processedText)); label->setStyles(std::move(styles)); } else { @@ -313,7 +318,7 @@ size_t TextBox::getLineLength(uint line) const { size_t position = label->getTextLineOffset(line); size_t lineLength = label->getTextLineOffset(line+1)-position; if (lineLength == 0) { - lineLength = input.length() - position + 1; + lineLength = label->getText().length() - position + 1; } return lineLength; } @@ -325,8 +330,8 @@ size_t TextBox::getSelectionLength() const { /// @brief Set scroll offset /// @param x scroll offset void TextBox::setTextOffset(uint x) { - label->setPos(glm::vec2(textInitX - int(x), label->getPos().y)); textOffset = x; + refresh(); } void TextBox::typed(unsigned int codepoint) { @@ -403,7 +408,7 @@ void TextBox::refresh() { Container::refresh(); label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y)); label->setPos(glm::vec2( - padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers, padding.y + padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers + textInitX - static_cast(textOffset), padding.y )); } @@ -421,15 +426,16 @@ size_t TextBox::normalizeIndex(int index) { int TextBox::calcIndexAt(int x, int y) const { if (font == nullptr) return 0; + const auto& labelText = label->getText(); glm::vec2 lcoord = label->calcPos(); uint line = label->getLineByYOffset(y-lcoord.y); line = std::min(line, label->getLinesNumber()-1); size_t lineLength = getLineLength(line); uint offset = 0; - while (lcoord.x + font->calcWidth(input, offset) < x && offset < lineLength-1) { + while (lcoord.x + font->calcWidth(labelText, offset) < x && offset < lineLength-1) { offset++; } - return std::min(offset+label->getTextLineOffset(line), input.length()); + return std::min(offset+label->getTextLineOffset(line), labelText.length()); } static inline std::wstring get_alphabet(wchar_t c) { @@ -443,21 +449,22 @@ static inline std::wstring get_alphabet(wchar_t c) { } void TextBox::tokenSelectAt(int index) { - if (input.empty()) { + const auto& actualText = label->getText(); + if (actualText.empty()) { return; } int left = index; int right = index; - std::wstring alphabet = get_alphabet(input.at(index)); + std::wstring alphabet = get_alphabet(actualText.at(index)); while (left >= 0) { - if (alphabet.find(input.at(left)) == std::wstring::npos) { + if (alphabet.find(actualText.at(left)) == std::wstring::npos) { break; } left--; } - while (static_cast(right) < input.length()) { - if (alphabet.find(input.at(right)) == std::wstring::npos) { + while (static_cast(right) < actualText.length()) { + if (alphabet.find(actualText.at(right)) == std::wstring::npos) { break; } right++; @@ -800,34 +807,43 @@ void TextBox::setHint(const std::wstring& text) { } std::wstring TextBox::getSelection() const { - return input.substr(selectionStart, selectionEnd-selectionStart); + const auto& text = label->getText(); + return text.substr(selectionStart, selectionEnd-selectionStart); } size_t TextBox::getCaret() const { return caret; } -void TextBox::setCaret(size_t position) { - this->caret = std::min(static_cast(position), input.length()); +void TextBox::setCaret(size_t position, bool ignoreFormatting) { + const auto& labelText = ignoreFormatting ? label->getText() : input; + caret = std::min(static_cast(position), input.length()); if (font == nullptr) { return; } - caretLastMove = Window::time(); int width = label->getSize().x; - uint line = label->getLineByTextIndex(caret); + + rawTextCache.prepare(font, width); + rawTextCache.update(input, multiline, label->isTextWrapping()); + + caretLastMove = Window::time(); + + uint line = rawTextCache.getLineByTextIndex(caret); int offset = label->getLineYOffset(line) + getContentOffset().y; uint lineHeight = font->getLineHeight()*label->getLineInterval(); if (scrollStep == 0) { scrollStep = lineHeight; } if (offset < 0) { - scrolled(-glm::floor(offset/static_cast(scrollStep)+0.5f)); + scrolled(-glm::floor(offset / static_cast(scrollStep)+0.5f)); } else if (offset >= getSize().y) { offset -= getSize().y; - scrolled(-glm::ceil(offset/static_cast(scrollStep)+0.5f)); + scrolled(-glm::ceil(offset / static_cast(scrollStep)+0.5f)); } - uint lcaret = caret - label->getTextLineOffset(line); - int realoffset = font->calcWidth(input, lcaret)-int(textOffset) + 2; + int lcaret = caret - rawTextCache.getTextLineOffset(line); + int realoffset = + font->calcWidth(labelText, lcaret) - static_cast(textOffset) + 2; + if (realoffset-width > 0) { setTextOffset(textOffset + realoffset-width); } else if (realoffset < 0) { diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index be459b89..bc3a811d 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -6,9 +6,8 @@ class Font; namespace gui { - class Label; - class TextBox : public Container { + LabelCache rawTextCache; protected: glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f}; glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f}; @@ -43,11 +42,12 @@ namespace gui { /// @brief Actual local (line) position of the caret on vertical move size_t maxLocalCaret = 0; size_t textOffset = 0; - int textInitX; + int textInitX = 0; /// @brief Last time of the caret was moved (used for blink animation) double caretLastMove = 0.0; Font* font = nullptr; + // Note: selection does not include markup size_t selectionStart = 0; size_t selectionEnd = 0; size_t selectionOrigin = 0; @@ -152,7 +152,7 @@ namespace gui { /// @brief Set caret position in the text /// @param position integer in range [0, text.length()] - virtual void setCaret(size_t position); + virtual void setCaret(size_t position, bool ignoreFormatting = true); /// @brief Set caret position in the text /// @param position integer in range [-text.length(), text.length()]