#include "TextBox.hpp" #include #include #include #include "Label.hpp" #include "devtools/syntax_highlighting.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/Batch2D.hpp" #include "graphics/core/Font.hpp" #include "assets/Assets.hpp" #include "util/stringutil.hpp" #include "window/Events.hpp" #include "window/Window.hpp" #include "devtools/actions.hpp" #include "../markdown.hpp" using namespace gui; inline constexpr int LINE_NUMBERS_PANE_WIDTH = 40; class InputAction : public Action { std::weak_ptr textbox; size_t position; std::wstring string; public: InputAction( std::weak_ptr textbox, size_t position, std::wstring string ) : textbox(std::move(textbox)), position(position), string(std::move(string)) { } void apply() override { if (auto box = textbox.lock()) { box->select(position, position); box->paste(string); } } void revert() override { if (auto box = textbox.lock()) { box->select(position, position); box->erase(position, string.length()); } } }; class SelectionAction : public Action { std::weak_ptr textbox; size_t start; size_t end; public: SelectionAction(std::weak_ptr textbox, size_t start, size_t end) : textbox(std::move(textbox)), start(start), end(end) {} void apply() override { if (auto box = textbox.lock()) { box->select(start, end); } } void revert() override { if (auto box = textbox.lock()) { box->select(0, 0); } } }; namespace gui { /// @brief Accumulates small changes into words for InputAction creation class TextBoxHistorian { public: TextBoxHistorian(TextBox& textBox, ActionsHistory& history) : textBox(textBox), history(history) { } void onPaste(size_t pos, std::wstring_view text) { if (locked) { return; } if (erasing) { sync(); } if (this->pos == static_cast(-1)) { this->pos = pos; } if (this->pos + length != pos || text == L" " || text == L"\n") { sync(); this->pos = pos; } ss << text; length += text.length(); } void onErase(size_t pos, std::wstring_view text, bool selection=false) { if (locked) { return; } if (!erasing) { sync(); erasing = true; } if (selection) { history.store( std::make_unique( getTextBoxWeakptr(), textBox.getSelectionStart(), textBox.getSelectionEnd() ), true ); } if (this->pos == static_cast(-1)) { this->pos = pos; } else if (this->pos - text.length() != pos) { sync(); erasing = true; this->pos = pos; } if (text == L" " || text == L"\n") { sync(); erasing = true; this->pos = pos; } auto str = ss.str(); ss.seekp(0); ss << text << str; this->pos = pos; length += text.length(); } /// @brief Flush buffer and push all changes to the ActionsHistory void sync() { auto string = ss.str(); if (string.empty()) { return; } auto action = std::make_unique(getTextBoxWeakptr(), pos, string); history.store(std::move(action), erasing); reset(); } void undo() { sync(); locked = true; history.undo(); locked = false; } void redo() { sync(); locked = true; history.redo(); locked = false; } void reset() { pos = -1; length = 0; erasing = false; ss = {}; } bool isSynced() const { return length == 0; } private: TextBox& textBox; ActionsHistory& history; std::wstringstream ss; size_t pos = -1; size_t length = 0; bool erasing = false; bool locked = false; std::weak_ptr getTextBoxWeakptr() { return std::weak_ptr(std::dynamic_pointer_cast( textBox.shared_from_this() )); } }; } TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) : Container(glm::vec2(200, 32)), history(std::make_shared()), historian(std::make_unique(*this, *history)), padding(padding), input(L""), placeholder(std::move(placeholder)) { setCursor(CursorShape::TEXT); setOnUpPressed(nullptr); setOnDownPressed(nullptr); setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.75f)); label = std::make_shared