diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index 996226c2..a68830be 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -252,6 +252,8 @@ TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y)); add(label); setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f)); + + textInitX = label->getCoord().x; } void TextBox::draw(const GfxContext* pctx, Assets* assets) { @@ -262,14 +264,22 @@ void TextBox::draw(const GfxContext* pctx, Assets* assets) { if (!isFocused()) return; + const int yoffset = 2; + const int lineHeight = font->getLineHeight(); + glm::vec2 lcoord = label->calcCoord(); + auto batch = pctx->getBatch2D(); + batch->texture(nullptr); if (int((Window::time() - caretLastMove) * 2) % 2 == 0) { - auto batch = pctx->getBatch2D(); - batch->texture(nullptr); batch->color = glm::vec4(1.0f); - glm::vec2 lcoord = label->calcCoord(); - int width = font->calcWidth(input.substr(0, caret)); - batch->rect(lcoord.x + width, lcoord.y, 2, font->getLineHeight()); + int width = font->calcWidth(input, caret); + batch->rect(lcoord.x + width, lcoord.y+yoffset, 2, lineHeight); + } + if (selectionStart != selectionEnd) { + batch->color = glm::vec4(0.8f, 0.9f, 1.0f, 0.5f); + int start = font->calcWidth(input, selectionStart); + int end = font->calcWidth(input, selectionEnd); + batch->rect(lcoord.x + start, lcoord.y+yoffset, end-start, lineHeight); } } @@ -296,17 +306,15 @@ void TextBox::drawBackground(const GfxContext* pctx, Assets* assets) { input = supplier(); } - if (input.empty()) { - label->setColor(glm::vec4(0.5f)); - label->setText(placeholder); - } else { - label->setColor(glm::vec4(1.0f)); - label->setText(input); - } + label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f)); + label->setText(getText()); setScrollable(false); } +/// @brief Insert text at the caret. Also selected text will be erased +/// @param text Inserting text void TextBox::paste(const std::wstring& text) { + eraseSelected(); if (caret >= input.length()) { input += text; } else { @@ -318,6 +326,49 @@ void TextBox::paste(const std::wstring& text) { validate(); } +/// @brief Remove part of the text and move caret to start of the part +/// @param start start of the part +/// @param length length of part that will be removed +void TextBox::erase(size_t start, size_t length) { + size_t end = start + length; + if (caret > start) { + setCaret(caret - length); + } + auto left = input.substr(0, start); + auto right = input.substr(end); + input = left + right; +} + +/// @brief Remove all selected text and reset selection +/// @return true if erased anything +bool TextBox::eraseSelected() { + if (selectionStart == selectionEnd) { + return false; + } + erase(selectionStart, selectionEnd-selectionStart); + resetSelection(); + return true; +} + +void TextBox::resetSelection() { + selectionOrigin = 0; + selectionStart = 0; + selectionEnd = 0; +} + +void TextBox::extendSelection(int index) { + size_t normalized = normalizeIndex(index); + selectionStart = std::min(selectionOrigin, normalized); + selectionEnd = std::max(selectionOrigin, normalized); +} + +/// @brief Set scroll offset +/// @param x scroll offset +void TextBox::setTextOffset(uint x) { + label->setCoord(glm::vec2(textInitX - int(x), label->getCoord().y)); + textOffset = x; +} + void TextBox::typed(unsigned int codepoint) { paste(std::wstring({(wchar_t)codepoint})); } @@ -356,24 +407,45 @@ void TextBox::refresh() { label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y)); } -void TextBox::clicked(GUI*, int button) { - +/// @brief Clamp index to range [0, input.length()] +/// @param index non-normalized index +/// @return normalized index +size_t TextBox::normalizeIndex(int index) { + return std::min(input.length(), static_cast(std::max(0, index))); } -void TextBox::mouseMove(GUI*, int x, int y) { +/// @brief Calculate index of character at defined screen X position +/// @param x screen X position +/// @return non-normalized character index +int TextBox::calcIndexAt(int x) const { if (font == nullptr) - return; + return 0; glm::vec2 lcoord = label->calcCoord(); uint offset = 0; while (lcoord.x + font->calcWidth(input, offset) < x && offset <= input.length()) { offset++; } - setCaret(offset); + return offset; +} + +void TextBox::click(GUI*, int x, int) { + int index = normalizeIndex(calcIndexAt(x)); + selectionStart = index; + selectionEnd = index; + selectionOrigin = index; +} + +void TextBox::mouseMove(GUI*, int x, int y) { + int index = calcIndexAt(x); + setCaret(index); + extendSelection(index); } void TextBox::keyPressed(int key) { + bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT); + uint previousCaret = caret; if (key == keycode::BACKSPACE) { - if (caret > 0 && input.length() > 0) { + if (!eraseSelected() && caret > 0 && input.length() > 0) { if (caret > input.length()) { caret = input.length(); } @@ -382,7 +454,7 @@ void TextBox::keyPressed(int key) { validate(); } } else if (key == keycode::DELETE) { - if (caret < input.length()) { + if (!eraseSelected() && caret < input.length()) { input = input.substr(0, caret) + input.substr(caret + 1); validate(); } @@ -398,22 +470,71 @@ void TextBox::keyPressed(int key) { } else { setCaret(caret-1); } + if (shiftPressed) { + if (selectionStart == selectionEnd) { + selectionOrigin = previousCaret; + } + extendSelection(caret); + } else { + resetSelection(); + } } } else if (key == keycode::RIGHT) { if (caret < input.length()) { setCaret(caret+1); caretLastMove = Window::time(); + if (shiftPressed) { + if (selectionStart == selectionEnd) { + selectionOrigin = previousCaret; + } + extendSelection(caret); + } else { + resetSelection(); + } } } - // Pasting text from clipboard - if (key == keycode::V && Events::pressed(keycode::LEFT_CONTROL)) { - const char* text = Window::getClipboardText(); - if (text) { - paste(util::str2wstr_utf8(text)); + if (Events::pressed(keycode::LEFT_CONTROL)) { + // Copy selected text to clipboard + if (key == keycode::C || key == keycode::X) { + std::string text = util::wstr2str_utf8(getSelection()); + if (!text.empty()) { + Window::setClipboardText(text.c_str()); + } + if (key == keycode::X) { + eraseSelected(); + } + } + // Paste text from clipboard + if (key == keycode::V) { + const char* text = Window::getClipboardText(); + if (text) { + paste(util::str2wstr_utf8(text)); + } + } + // Select/deselect all + if (key == keycode::A) { + if (selectionStart == selectionEnd) { + select(0, input.length()); + } else { + resetSelection(); + } } } } +void TextBox::select(int start, int end) { + if (end < start) { + std::swap(start, end); + } + start = normalizeIndex(start); + end = normalizeIndex(end); + + selectionStart = start; + selectionEnd = end; + selectionOrigin = start; + setCaret(selectionEnd); +} + std::shared_ptr TextBox::getAt(glm::vec2 pos, std::shared_ptr self) { return UINode::getAt(pos, self); } @@ -464,6 +585,10 @@ void TextBox::setPlaceholder(const std::wstring& placeholder) { this->placeholder = placeholder; } +std::wstring TextBox::getSelection() const { + return input.substr(selectionStart, selectionEnd-selectionStart); +} + uint TextBox::getCaret() const { return caret; } @@ -471,6 +596,14 @@ uint TextBox::getCaret() const { void TextBox::setCaret(uint position) { this->caret = position; caretLastMove = Window::time(); + + int width = label->getSize().x; + int realoffset = font->calcWidth(input, caret)-int(textOffset); + if (realoffset-width > 0) { + setTextOffset(textOffset + realoffset-width); + } else if (realoffset < 0) { + setTextOffset(std::max(textOffset + realoffset, 0U)); + } } // ============================== InputBindBox ================================ diff --git a/src/frontend/gui/controls.h b/src/frontend/gui/controls.h index 8dbbf797..9e2219b9 100644 --- a/src/frontend/gui/controls.h +++ b/src/frontend/gui/controls.h @@ -112,10 +112,24 @@ namespace gui { bool valid = true; /// @brief text input pointer, value may be greather than text length uint caret = 0; + uint textOffset = 0; + int textInitX; double caretLastMove = 0.0; Font* font = nullptr; + size_t selectionStart = 0; + size_t selectionEnd = 0; + size_t selectionOrigin = 0; + + size_t normalizeIndex(int index); + + int calcIndexAt(int x) const; void paste(const std::wstring& text); + void setTextOffset(uint x); + void erase(size_t start, size_t length); + bool eraseSelected(); + void resetSelection(); + void extendSelection(int index); public: TextBox(std::wstring placeholder, glm::vec4 padding=glm::vec4(4.0f)); @@ -134,21 +148,23 @@ namespace gui { virtual glm::vec4 getFocusedColor() const; virtual void setErrorColor(glm::vec4 color); virtual glm::vec4 getErrorColor() const; - /* Get TextBox content text or placeholder if empty */ + /// @brief Get TextBox content text or placeholder if empty virtual std::wstring getText() const; - /* Set TextBox content text */ + /// @brief Set TextBox content text virtual void setText(std::wstring value); virtual std::wstring getPlaceholder() const; virtual void setPlaceholder(const std::wstring&); + virtual std::wstring getSelection() const; virtual uint getCaret() const; virtual void setCaret(uint position); + virtual void select(int start, int end); virtual bool validate(); virtual void setValid(bool valid); virtual bool isValid() const; virtual void setOnEditStart(runnable oneditstart); virtual void focus(GUI*) override; virtual void refresh() override; - virtual void clicked(GUI*, int button) override; + virtual void click(GUI*, int, int) override; virtual void mouseMove(GUI*, int x, int y) override; }; diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index e3650b99..0879978f 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -19,7 +19,6 @@ #include "../files/WorldConverter.h" #include "../files/WorldFiles.h" #include "../world/World.h" -#include "../world/WorldTypes.h" #include "../world/Level.h" #include "../window/Events.h" #include "../window/Window.h" @@ -39,10 +38,6 @@ using glm::vec4; namespace fs = std::filesystem; using namespace gui; -namespace menus { - std::string worldType; -} - inline uint64_t randU64() { srand(time(NULL)); return rand() ^ (rand() << 8) ^ @@ -87,6 +82,7 @@ static std::shared_ptr