From 4db5838a643624cf5daaa02da11cd072af6f56f2 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 20 Oct 2025 17:30:36 +0300 Subject: [PATCH] Using new RichTextBox instead of CollabTextBox in program. Fixes still needed to work properly --- CMakeLists.txt | 2 - src/CollabTextBox/CollabTextBox.cpp | 524 ---------------------- src/CollabTextBox/CollabTextBox.hpp | 125 ------ src/CollabTextBox/CollabTextBoxCursor.hpp | 33 -- src/CollabTextBox/CollabTextBoxShaper.cpp | 364 --------------- src/CollabTextBox/CollabTextBoxShaper.hpp | 21 - src/DrawComponents/DrawTextBox.cpp | 43 +- src/DrawComponents/DrawTextBox.hpp | 8 +- src/DrawingProgram/TextBoxEditTool.cpp | 117 +---- src/DrawingProgram/TextBoxTool.cpp | 1 - src/GUIStuff/Elements/Element.hpp | 3 + src/GUIStuff/Elements/TextBox.hpp | 161 ++----- src/InputManager.cpp | 9 + src/InputManager.hpp | 1 + src/MainProgram.cpp | 132 ++---- src/RichTextBox/RichTextBox.cpp | 38 +- src/RichTextBox/RichTextBox.hpp | 9 +- src/Toolbar.cpp | 7 +- 18 files changed, 157 insertions(+), 1441 deletions(-) delete mode 100644 src/CollabTextBox/CollabTextBox.cpp delete mode 100644 src/CollabTextBox/CollabTextBox.hpp delete mode 100644 src/CollabTextBox/CollabTextBoxCursor.hpp delete mode 100644 src/CollabTextBox/CollabTextBoxShaper.cpp delete mode 100644 src/CollabTextBox/CollabTextBoxShaper.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 409e0c6..5294f23 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,6 @@ set(sources "src/main.cpp" "src/ResourceDisplay/FileResourceDisplay.cpp" "src/Server/MainServer.cpp" "src/Server/ServerData.cpp" - "src/CollabTextBox/CollabTextBox.cpp" - "src/CollabTextBox/CollabTextBoxShaper.cpp" "src/RichTextBox/RichTextBox.cpp" "src/DrawingProgram/DrawingProgramCache.cpp" "src/DrawingProgram/DrawingProgram.cpp" diff --git a/src/CollabTextBox/CollabTextBox.cpp b/src/CollabTextBox/CollabTextBox.cpp deleted file mode 100644 index e5e2ae5..0000000 --- a/src/CollabTextBox/CollabTextBox.cpp +++ /dev/null @@ -1,524 +0,0 @@ -// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details - -#include "CollabTextBox.hpp" -#include "CollabTextBoxShaper.hpp" -#include -#include -#include - -#include -#include -#include -#include - -using namespace CollabTextBox; - -static inline SkRect offset(SkRect r, SkIPoint p) { - return r.makeOffset((float)p.x(), (float)p.y()); -} - -static constexpr SkRect kUnsetRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX}; - -//static bool valid_utf8(const char* ptr, size_t size) { return SkUTF::CountUTF8(ptr, size) >= 0; } - -// Kind of like Python's readlines(), but without any allocation. -// Calls f() on each line. -// F is [](const char*, size_t) -> void -template -static void readlines(const void* data, size_t size, F f) { - const char* start = (const char*)data; - const char* end = start + size; - const char* ptr = start; - while (ptr < end) { - while (*ptr++ != '\n' && ptr < end) {} - size_t len = ptr - start; - assert(len > 0); - f(start, len); - start = ptr; - } -} - -static std::string remove_newline(const char* str, size_t len) { - return assert((str != nullptr) || (len == 0)), std::string(str, (len > 0 && str[len - 1] == '\n') ? len - 1 : len); -} - -void Editor::markDirty(TextParagraph* line) { - line->fBlob = nullptr; - line->fShaped = false; - line->fWordBoundaries = std::vector(); -} - -void Editor::setFont(SkFont font) { - if (font != fFont) { - fFont = std::move(font); - fNeedsReshape = true; - for (auto& l : fLines) { this->markDirty(&l); } - } -} - -void Editor::setFontMgr(sk_sp fontMgr) { - if(fontMgr != fFontMgr) { - fFontMgr = fontMgr; - fNeedsReshape = true; - for (auto& l : fLines) { this->markDirty(&l); } - } -} - -void Editor::setWidth(int w) { - if (fWidth != w) { - fWidth = w; - fNeedsReshape = true; - for (auto& l : fLines) { this->markDirty(&l); } - } -} -static SkPoint to_point(SkIPoint p) { return {(float)p.x(), (float)p.y()}; } - -TextPosition Editor::getPosition(SkIPoint xy) { - // Clamp input coordinates to stop function from hanging - xy.fX = std::clamp(xy.fX, 0, fWidth - 1); - xy.fY = std::clamp(xy.fY, 0, fHeight - 1); - - TextPosition approximatePosition{0, 0}; - this->reshapeAll(); - for (size_t j = 0; j < fLines.size(); ++j) { - const TextParagraph& line = fLines[j]; - SkIRect lineRect = {0, - line.fOrigin.y(), - fWidth, - j + 1 < fLines.size() ? fLines[j + 1].fOrigin.y() : INT_MAX}; - if (const SkTextBlob* b = line.fBlob.get()) { - SkIRect r = b->bounds().roundOut(); - r.offset(line.fOrigin); - lineRect.join(r); - } - if (!lineRect.contains(xy.x(), xy.y())) { - continue; - } - SkPoint pt = to_point(xy - line.fOrigin); - const std::vector& pos = line.fCursorPos; - for (size_t i = 0; i < pos.size(); ++i) { - if (pos[i] != kUnsetRect && pos[i].contains(pt.x(), pt.y())) { - return TextPosition{i, j}; - } - } - approximatePosition = {xy.x() <= line.fOrigin.x() ? 0 : line.fText.size(), j}; - } - return approximatePosition; -} - -bool CollabTextBox::is_utf8_continuation(char v) { - return ((unsigned char)v & 0b11000000) == - 0b10000000; -} - -const char* CollabTextBox::next_utf8(const char* p, const char* end) { - if (p < end) { - do { - ++p; - } while (p < end && is_utf8_continuation(*p)); - } - return p; -} - -const char* CollabTextBox::align_utf8(const char* p, const char* begin) { - while (p > begin && is_utf8_continuation(*p)) { - --p; - } - return p; -} - -const char* CollabTextBox::prev_utf8(const char* p, const char* begin) { - return p > begin ? align_utf8(p - 1, begin) : begin; -} - -SkRect Editor::getLocation(TextPosition cursor) { - this->reshapeAll(); - cursor = this->move(Movement::kNowhere, cursor); - if (fLines.size() > 0) { - const TextParagraph& cLine = fLines[cursor.fParagraphIndex]; - SkRect pos = {0, 0, 0, 0}; - if (cursor.fTextByteIndex < cLine.fCursorPos.size()) { - pos = cLine.fCursorPos[cursor.fTextByteIndex]; - } - pos.fRight = pos.fLeft + 1; - pos.fLeft -= 1; - return offset(pos, cLine.fOrigin); - } - return SkRect{0, 0, 0, 0}; -} - -static size_t count_char(const std::string& string, char value) { - return std::ranges::count(string, value); -} - -TextPosition Editor::insert(TextPosition pos, std::string_view utf8Text) { - if (/*!valid_utf8(utf8Text, byteLen) ||*/ utf8Text.size() == 0) { // dont check for valid strings for now - return pos; - } - pos = this->move(Movement::kNowhere, pos); - fNeedsReshape = true; - if (pos.fParagraphIndex < fLines.size()) { - fLines[pos.fParagraphIndex].fText.insert(pos.fTextByteIndex, utf8Text.data(), utf8Text.size()); - this->markDirty(&fLines[pos.fParagraphIndex]); - } else { - assert(pos.fParagraphIndex == fLines.size()); - assert(pos.fTextByteIndex == 0); - fLines.push_back(TextParagraph(utf8Text)); - } - pos = TextPosition{pos.fTextByteIndex + utf8Text.size(), pos.fParagraphIndex}; - size_t newlinecount = count_char(fLines[pos.fParagraphIndex].fText, '\n'); - if (newlinecount > 0) { - std::string src = fLines[pos.fParagraphIndex].fText; - std::vector::const_iterator next = fLines.begin() + pos.fParagraphIndex + 1; - fLines.insert(next, newlinecount, TextParagraph()); - TextParagraph* line = &fLines[pos.fParagraphIndex]; - readlines(src.c_str(), src.size(), [&line](const char* str, size_t l) { - (line++)->fText = remove_newline(str, l); - }); - } - - // Calculate the TextPosition post insert properly - pos.fParagraphIndex = pos.fParagraphIndex + newlinecount; - size_t lastNewline = utf8Text.find_last_of('\n'); - if(std::string_view::npos != lastNewline) - pos.fTextByteIndex = utf8Text.size() - lastNewline - 1; - if(utf8Text == "\n") { // Special case where the string is just a newline - pos.fTextByteIndex = 0; - pos.fParagraphIndex--; - } - - return pos; -} - -TextPosition Editor::remove(TextPosition pos1, TextPosition pos2) { - pos1 = this->move(Movement::kNowhere, pos1); - pos2 = this->move(Movement::kNowhere, pos2); - auto cmp = [](const TextPosition& u, const TextPosition& v) { return u < v; }; - TextPosition start = std::min(pos1, pos2, cmp); - TextPosition end = std::max(pos1, pos2, cmp); - if (start == end || start.fParagraphIndex == fLines.size()) { - return start; - } - fNeedsReshape = true; - if (start.fParagraphIndex == end.fParagraphIndex) { - assert(end.fTextByteIndex > start.fTextByteIndex); - fLines[start.fParagraphIndex].fText.erase(start.fTextByteIndex, end.fTextByteIndex - start.fTextByteIndex); - this->markDirty(&fLines[start.fParagraphIndex]); - } else { - assert(end.fParagraphIndex < fLines.size()); - auto& line = fLines[start.fParagraphIndex]; - line.fText.erase(start.fTextByteIndex, line.fText.size() - start.fTextByteIndex); - line.fText.insert(start.fTextByteIndex, fLines[end.fParagraphIndex].fText.substr(end.fTextByteIndex)); - this->markDirty(&line); - fLines.erase(fLines.begin() + start.fParagraphIndex + 1, - fLines.begin() + end.fParagraphIndex + 1); - } - return start; -} - -std::string Editor::get_string() const { - std::string toRet; - for(const TextParagraph& p : fLines) { - toRet += p.fText; - toRet += '\n'; - } - if(!toRet.empty()) - toRet.pop_back(); - return toRet; -} - -std::string Editor::copy(TextPosition pos1, TextPosition pos2) const { - pos1 = this->move(Movement::kNowhere, pos1); - pos2 = this->move(Movement::kNowhere, pos2); - auto cmp = [](const TextPosition& u, const TextPosition& v) { return u < v; }; - TextPosition start = std::min(pos1, pos2, cmp); - TextPosition end = std::max(pos1, pos2, cmp); - if(start == end || start.fParagraphIndex == fLines.size()) - return {}; - if(start.fParagraphIndex == end.fParagraphIndex) { - assert(end.fTextByteIndex > start.fTextByteIndex); - auto& str = fLines[start.fParagraphIndex].fText; - return str.substr(start.fTextByteIndex, end.fTextByteIndex - start.fTextByteIndex); - } - assert(end.fParagraphIndex < fLines.size()); - const std::vector::const_iterator firstP = fLines.begin() + start.fParagraphIndex; - const std::vector::const_iterator lastP = fLines.begin() + end.fParagraphIndex; - const auto& first = firstP->fText; - const auto& last = lastP->fText; - - std::string toRet; - toRet += first.substr(start.fTextByteIndex); - for (auto line = firstP + 1; line < lastP; ++line) { - toRet += '\n'; - toRet += line->fText; - } - toRet += '\n'; - toRet += last.substr(0, end.fTextByteIndex); - return toRet; -} - -static inline const char* begin(const std::string& s) { return s.c_str(); } - -static inline const char* end(const std::string& s) { return s.c_str() + s.size(); } - -static size_t align_column(const std::string& str, size_t p) { - if(p >= str.size()) - return str.size(); - return align_utf8(begin(str) + p, begin(str)) - begin(str); -} - -// returns smallest i such that list[i] > value. value > list[i-1] -// Use a binary search since list is monotonic -template -static size_t find_first_larger(const std::vector& list, T value) { - return (size_t)(std::upper_bound(list.begin(), list.end(), value) - list.begin()); -} - -static size_t find_closest_x(const std::vector& bounds, float x, size_t b, size_t e) { - if (b >= e) { - return b; - } - assert(e <= bounds.size()); - size_t best_index = b; - float best_diff = ::fabsf(bounds[best_index].x() - x); - for (size_t i = b + 1; i < e; ++i) { - float d = ::fabsf(bounds[i].x() - x); - if (d < best_diff) { - best_diff = d; - best_index = i; - } - } - return best_index; -} - -TextPosition Editor::move(Movement move, TextPosition pos) const { - if (fLines.empty()) { - return {0, 0}; - } - // First thing: fix possible bad input values. - if (pos.fParagraphIndex >= fLines.size()) { - pos.fParagraphIndex = fLines.size() - 1; - pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size(); - } else { - pos.fTextByteIndex = align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex); - } - - assert(pos.fParagraphIndex < fLines.size()); - assert(pos.fTextByteIndex <= fLines[pos.fParagraphIndex].fText.size()); - - assert(pos.fTextByteIndex == fLines[pos.fParagraphIndex].fText.size() || - !is_utf8_continuation(fLines[pos.fParagraphIndex].fText.begin()[pos.fTextByteIndex])); - - switch (move) { - case Movement::kNowhere: - break; - case Movement::kLeft: - if (0 == pos.fTextByteIndex) { - if (pos.fParagraphIndex > 0) { - --pos.fParagraphIndex; - pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size(); - } - } else { - const auto& str = fLines[pos.fParagraphIndex].fText; - pos.fTextByteIndex = - prev_utf8(begin(str) + pos.fTextByteIndex, begin(str)) - begin(str); - } - break; - case Movement::kRight: - if (fLines[pos.fParagraphIndex].fText.size() == pos.fTextByteIndex) { - if (pos.fParagraphIndex + 1 < fLines.size()) { - ++pos.fParagraphIndex; - pos.fTextByteIndex = 0; - } - } else { - const auto& str = fLines[pos.fParagraphIndex].fText; - pos.fTextByteIndex = - next_utf8(begin(str) + pos.fTextByteIndex, end(str)) - begin(str); - } - break; - case Movement::kHome: - { - const std::vector& list = fLines[pos.fParagraphIndex].fLineEndOffsets; - size_t f = find_first_larger(list, pos.fTextByteIndex); - pos.fTextByteIndex = f > 0 ? list[f - 1] : 0; - } - break; - case Movement::kEnd: - { - const std::vector& list = fLines[pos.fParagraphIndex].fLineEndOffsets; - size_t f = find_first_larger(list, pos.fTextByteIndex); - if (f < list.size()) { - pos.fTextByteIndex = list[f] > 0 ? list[f] - 1 : 0; - } else { - pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size(); - } - } - break; - case Movement::kUp: - { - assert(pos.fTextByteIndex < fLines[pos.fParagraphIndex].fCursorPos.size()); - float x = fLines[pos.fParagraphIndex].fCursorPos[pos.fTextByteIndex].left(); - const std::vector& list = fLines[pos.fParagraphIndex].fLineEndOffsets; - size_t f = find_first_larger(list, pos.fTextByteIndex); - // list[f] > value. value > list[f-1] - if (f > 0) { - // not the first line in paragraph. - pos.fTextByteIndex = find_closest_x(fLines[pos.fParagraphIndex].fCursorPos, x, - (f == 1) ? 0 : list[f - 2], - list[f - 1]); - } else if (pos.fParagraphIndex > 0) { - --pos.fParagraphIndex; - const auto& newLine = fLines[pos.fParagraphIndex]; - size_t r = newLine.fLineEndOffsets.size(); - if (r > 0) { - pos.fTextByteIndex = find_closest_x(newLine.fCursorPos, x, - newLine.fLineEndOffsets[r - 1], - newLine.fCursorPos.size()); - } else { - pos.fTextByteIndex = find_closest_x(newLine.fCursorPos, x, 0, - newLine.fCursorPos.size()); - } - } - pos.fTextByteIndex = - align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex); - } - break; - case Movement::kDown: - { - const std::vector& list = fLines[pos.fParagraphIndex].fLineEndOffsets; - float x = fLines[pos.fParagraphIndex].fCursorPos[pos.fTextByteIndex].left(); - - size_t f = find_first_larger(list, pos.fTextByteIndex); - if (f < list.size()) { - const auto& bounds = fLines[pos.fParagraphIndex].fCursorPos; - pos.fTextByteIndex = find_closest_x(bounds, x, list[f], - f + 1 < list.size() ? list[f + 1] - : bounds.size()); - } else if (pos.fParagraphIndex + 1 < fLines.size()) { - ++pos.fParagraphIndex; - const auto& bounds = fLines[pos.fParagraphIndex].fCursorPos; - const std::vector& l2 = fLines[pos.fParagraphIndex].fLineEndOffsets; - pos.fTextByteIndex = find_closest_x(bounds, x, 0, - l2.size() > 0 ? l2[0] : bounds.size()); - } else { - pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size(); - } - pos.fTextByteIndex = - align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex); - } - break; - case Movement::kWordLeft: - { - for(;;) { - TextPosition prevCharPos = this->move(Movement::kLeft, pos); - if(prevCharPos != TextPosition{0, 0} && prevCharPos.fTextByteIndex != fLines[prevCharPos.fParagraphIndex].fText.size() && std::isspace(fLines[prevCharPos.fParagraphIndex].fText[prevCharPos.fTextByteIndex])) - pos = prevCharPos; - else - break; - } - - if (pos.fTextByteIndex == 0) { - pos = this->move(Movement::kLeft, pos); - break; - } - - for(;;) { - TextPosition prevCharPos = this->move(Movement::kLeft, pos); - const std::string& text = fLines[prevCharPos.fParagraphIndex].fText; - if(prevCharPos == TextPosition{0, 0}) { - pos = prevCharPos; - break; - } - else if(prevCharPos.fTextByteIndex != text.size() && !std::isspace(text[prevCharPos.fTextByteIndex])) - pos = prevCharPos; - else - break; - } - } - break; - case Movement::kWordRight: - { - const std::string& text = fLines[pos.fParagraphIndex].fText; - if(pos.fTextByteIndex == text.size()) - pos = this->move(Movement::kRight, pos); - else { - for(;;) { - const std::string& text = fLines[pos.fParagraphIndex].fText; - if(pos.fTextByteIndex != text.size() && !std::isspace(text[pos.fTextByteIndex])) - pos = this->move(Movement::kRight, pos); - else - break; - } - } - - for(;;) { - const std::string& text = fLines[pos.fParagraphIndex].fText; - if(pos.fTextByteIndex != text.size() && std::isspace(text[pos.fTextByteIndex])) - pos = this->move(Movement::kRight, pos); - else - break; - } - } - break; - - } - return pos; -} - -void Editor::paint(SkCanvas* c, PaintOpts options) { - this->reshapeAll(); - if(!c) - return; - - c->drawPaint(SkPaint(options.fBackgroundColor)); - - if(options.showCursor) { - auto& cur = options.cursor; - SkPaint selection = SkPaint(SkColor4f{options.cursorColor[0], options.cursorColor[1], options.cursorColor[2], 0.3f}); - auto cmp = [](const TextPosition& u, const TextPosition& v) { return u < v; }; - for(TextPosition pos = std::min(cur.selectionBeginPos, cur.selectionEndPos, cmp), end = std::max(cur.selectionBeginPos, cur.selectionEndPos, cmp); - pos < end; - pos = this->move(Movement::kRight, pos)) - { - assert(pos.fParagraphIndex < fLines.size()); - const TextParagraph& l = fLines[pos.fParagraphIndex]; - c->drawRect(offset(l.fCursorPos[pos.fTextByteIndex], l.fOrigin), selection); - } - - if(fLines.size() > 0) - c->drawRect(Editor::getLocation(cur.pos), SkPaint(options.cursorColor)); - } - - SkPaint foreground = SkPaint(options.fForegroundColor); - for(const TextParagraph& line : fLines) { - if(line.fBlob) - c->drawTextBlob(line.fBlob.get(), line.fOrigin.x(), line.fOrigin.y(), foreground); - } -} - -void Editor::reshapeAll() { - if(fNeedsReshape && fFontMgr) { // fFontMgr must not be null for text to be reshaped - if(fLines.empty()) - fLines.push_back(TextParagraph()); - float shape_width = (float)(fWidth); - for(TextParagraph& line : fLines) { - if(!line.fShaped) { - ShapeResult result = shape(line.fText.c_str(), line.fText.size(), fFont, fFontMgr, fLocale, shape_width); - line.fBlob = std::move(result.blob); - line.fLineEndOffsets = std::move(result.lineBreakOffsets); - line.fCursorPos = std::move(result.glyphBounds); - line.fWordBoundaries = std::move(result.wordBreaks); - line.fHeight = result.verticalAdvance; - line.fShaped = true; - } - } - int y = 0; - for (TextParagraph& line : fLines) { - line.fOrigin = {0, y}; - y += line.fHeight; - } - fHeight = y; - fNeedsReshape = false; - } -} - diff --git a/src/CollabTextBox/CollabTextBox.hpp b/src/CollabTextBox/CollabTextBox.hpp deleted file mode 100644 index 9bc3cb3..0000000 --- a/src/CollabTextBox/CollabTextBox.hpp +++ /dev/null @@ -1,125 +0,0 @@ -// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details - -#pragma once -#include "CollabTextBoxCursor.hpp" -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -class SkCanvas; -class SkShaper; - -using namespace Eigen; - -namespace CollabTextBox { - struct TextParagraph { - TextParagraph(std::string_view t) : fText(t) {} - TextParagraph() {} - - std::string fText; - sk_sp fBlob; - std::vector fCursorPos; - std::vector fLineEndOffsets; - std::vector fWordBoundaries; - SkIPoint fOrigin = {0, 0}; - int fHeight = 0; - bool fShaped = false; - }; - - enum class Movement { - kNowhere, - kLeft, - kUp, - kRight, - kDown, - kHome, - kEnd, - kWordLeft, - kWordRight, - }; - - bool is_utf8_continuation(char v); - const char* next_utf8(const char* p, const char* end); - const char* align_utf8(const char* p, const char* begin); - const char* prev_utf8(const char* p, const char* begin); - - class Editor { - public: - // total height in canvas display units. - int getHeight() const { return fHeight; } - - // set display width in canvas display units - void setWidth(int w); // may force re-shape - - // get/set current font (used for shaping and displaying text) - const SkFont& font() const { return fFont; } - void setFont(SkFont font); - - void setFontMgr(sk_sp fontMgr); - - struct Text { - const std::vector& fLines; - struct Iterator { - std::vector::const_iterator fPtr; - std::string_view operator*() { return std::string_view(fPtr->fText); } - void operator++() { ++fPtr; } - bool operator!=(const Iterator& other) const { return fPtr != other.fPtr; } - }; - Iterator begin() const { return Iterator{fLines.begin()}; } - Iterator end() const { return Iterator{fLines.end()}; } - }; - // Loop over all the lines of text. The lines are not '\0'- or '\n'-terminated. - // For example, to dump the entire file to standard output: - // for (SkPlainTextEditor::StringView str : editor.text()) { - // std::cout.write(str.data, str.size) << '\n'; - // } - Text text() const { return Text{fLines}; } - std::string get_string() const; - - // get size of line in canvas display units. - int lineHeight(size_t index) const { return fLines[index].fHeight; } - - TextPosition move(Movement move, TextPosition pos) const; - TextPosition getPosition(SkIPoint); - SkRect getLocation(TextPosition); - // insert into current text. - TextPosition insert(TextPosition, std::string_view utf8Text); - // remove text between two positions - TextPosition remove(TextPosition, TextPosition); - - std::string copy(TextPosition pos1, TextPosition pos2) const; - size_t lineCount() const { return fLines.size(); } - std::string_view line(size_t i) const { - return i < fLines.size() ? std::string_view(fLines[i].fText) : std::string_view(); - } - - struct PaintOpts { - SkColor4f fBackgroundColor = {1, 1, 1, 1}; - SkColor4f fForegroundColor = {0, 0, 0, 1}; - SkColor4f cursorColor = {1, 0, 0, 1}; - bool showCursor = false; - Cursor cursor; - }; - void paint(SkCanvas* canvas, PaintOpts options); - std::vector fLines; - private: - int fWidth = 0; - int fHeight = 0; - SkFont fFont; - sk_sp fFontMgr; - bool fNeedsReshape = false; - const char* fLocale = "en"; // TODO: make this setable - - void markDirty(TextParagraph*); - void reshapeAll(); - }; - -} - diff --git a/src/CollabTextBox/CollabTextBoxCursor.hpp b/src/CollabTextBox/CollabTextBoxCursor.hpp deleted file mode 100644 index 3f1b289..0000000 --- a/src/CollabTextBox/CollabTextBoxCursor.hpp +++ /dev/null @@ -1,33 +0,0 @@ -// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details - -#pragma once -#include -#include - -namespace CollabTextBox { - struct TextPosition { - // Set to uint32_t values because theyll be saved and sent over networks. Size has to be consistent - size_t fTextByteIndex = std::numeric_limits::max(); - size_t fParagraphIndex = std::numeric_limits::max(); - template void serialize(Archive& a) { - a((uint32_t)fTextByteIndex, (uint32_t)fParagraphIndex); - } - bool operator==(const TextPosition& o) const = default; - }; - - static inline bool operator<(const CollabTextBox::TextPosition& u, - const CollabTextBox::TextPosition& v) { - return u.fParagraphIndex < v.fParagraphIndex || - (u.fParagraphIndex == v.fParagraphIndex && u.fTextByteIndex < v.fTextByteIndex); - } - - struct Cursor { - TextPosition pos{0, 0}; - TextPosition selectionBeginPos{0, 0}; - TextPosition selectionEndPos{0, 0}; - template void serialize(Archive& a) { - a(pos, selectionBeginPos, selectionEndPos); - } - bool operator==(const Cursor& o) const = default; - }; -} diff --git a/src/CollabTextBox/CollabTextBoxShaper.cpp b/src/CollabTextBox/CollabTextBoxShaper.cpp deleted file mode 100644 index 8d0039f..0000000 --- a/src/CollabTextBox/CollabTextBoxShaper.cpp +++ /dev/null @@ -1,364 +0,0 @@ -// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details - -#include "CollabTextBoxShaper.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -sk_sp get_unicode_2() { - if(auto unicode = SkUnicodes::ICU::Make()) { - return unicode; - } - SkDEBUGFAIL("Cannot make SkUnicode"); - return nullptr; -} - -namespace { -class RunHandler final : public SkShaper::RunHandler { -public: - RunHandler(const char* utf8Text, size_t) : fUtf8Text(utf8Text) {} - using RunCallback = void (*)(void* context, - const char* utf8Text, - size_t utf8TextBytes, - size_t glyphCount, - const SkGlyphID* glyphs, - const SkPoint* positions, - const uint32_t* clusters, - const SkFont& font); - void setRunCallback(RunCallback f, void* context) { - fCallbackContext = context; - fCallbackFunction = f; - } - - sk_sp makeBlob(); - SkPoint endPoint() const { return fOffset; } - SkPoint finalPosition() const { return fCurrentPosition; } - - void beginLine() override; - void runInfo(const RunInfo&) override; - void commitRunInfo() override; - SkShaper::RunHandler::Buffer runBuffer(const RunInfo&) override; - void commitRunBuffer(const RunInfo&) override; - void commitLine() override; - - const std::vector& lineEndOffsets() const { return fLineEndOffsets; } - - SkRect finalRect(const SkFont& font) const { - if (0 == fMaxRunAscent || 0 == fMaxRunDescent) { - SkFontMetrics metrics; - font.getMetrics(&metrics); - return {fCurrentPosition.x(), - fCurrentPosition.y(), - fCurrentPosition.x() + font.getSize(), - fCurrentPosition.y() + metrics.fDescent - metrics.fAscent}; - } else { - return {fCurrentPosition.x(), - fCurrentPosition.y() + fMaxRunAscent, - fCurrentPosition.x() + font.getSize(), - fCurrentPosition.y() + fMaxRunDescent}; - } - } - - -private: - SkTextBlobBuilder fBuilder; - std::vector fLineEndOffsets; - const SkGlyphID* fCurrentGlyphs = nullptr; - const SkPoint* fCurrentPoints = nullptr; - void* fCallbackContext = nullptr; - RunCallback fCallbackFunction = nullptr; - char const * const fUtf8Text; - size_t fTextOffset = 0; - uint32_t* fClusters = nullptr; - int fClusterOffset = 0; - int fGlyphCount = 0; - SkScalar fMaxRunAscent = 0; - SkScalar fMaxRunDescent = 0; - SkScalar fMaxRunLeading = 0; - SkPoint fCurrentPosition = {0, 0}; - SkPoint fOffset = {0, 0}; -}; - -} // namespace - -void RunHandler::beginLine() { - fCurrentPosition = fOffset; - fMaxRunAscent = 0; - fMaxRunDescent = 0; - fMaxRunLeading = 0; -} - -void RunHandler::runInfo(const SkShaper::RunHandler::RunInfo& info) { - SkFontMetrics metrics; - info.fFont.getMetrics(&metrics); - fMaxRunAscent = std::min(fMaxRunAscent, metrics.fAscent); - fMaxRunDescent = std::max(fMaxRunDescent, metrics.fDescent); - fMaxRunLeading = std::max(fMaxRunLeading, metrics.fLeading); -} - -void RunHandler::commitRunInfo() { - fCurrentPosition.fY -= fMaxRunAscent; -} - -SkShaper::RunHandler::Buffer RunHandler::runBuffer(const RunInfo& info) { - int glyphCount = SkTFitsIn(info.glyphCount) ? info.glyphCount : INT_MAX; - int utf8RangeSize = SkTFitsIn(info.utf8Range.size()) ? info.utf8Range.size() : INT_MAX; - - const auto& runBuffer = fBuilder.allocRunTextPos(info.fFont, glyphCount, utf8RangeSize); - fCurrentGlyphs = runBuffer.glyphs; - fCurrentPoints = runBuffer.points(); - - if (runBuffer.utf8text && fUtf8Text) { - memcpy(runBuffer.utf8text, fUtf8Text + info.utf8Range.begin(), utf8RangeSize); - } - fClusters = runBuffer.clusters; - fGlyphCount = glyphCount; - fClusterOffset = info.utf8Range.begin(); - - return {runBuffer.glyphs, - runBuffer.points(), - nullptr, - runBuffer.clusters, - fCurrentPosition}; -} - -void RunHandler::commitRunBuffer(const RunInfo& info) { - // for (size_t i = 0; i < info.glyphCount; ++i) { - // SkASSERT(fClusters[i] >= info.utf8Range.begin()); - // // this fails for khmer example. - // SkASSERT(fClusters[i] < info.utf8Range.end()); - // } - if (fCallbackFunction) { - fCallbackFunction(fCallbackContext, - fUtf8Text, - info.utf8Range.end(), - info.glyphCount, - fCurrentGlyphs, - fCurrentPoints, - fClusters, - info.fFont); - } - SkASSERT(0 <= fClusterOffset); - for (int i = 0; i < fGlyphCount; ++i) { - SkASSERT(fClusters[i] >= (unsigned)fClusterOffset); - fClusters[i] -= fClusterOffset; - } - fCurrentPosition += info.fAdvance; - fTextOffset = std::max(fTextOffset, info.utf8Range.end()); -} - -void RunHandler::commitLine() { - if (fLineEndOffsets.empty() || fTextOffset > fLineEndOffsets.back()) { - // Ensure that fLineEndOffsets is monotonic. - fLineEndOffsets.push_back(fTextOffset); - } - fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent }; -} - -sk_sp RunHandler::makeBlob() { - return fBuilder.make(); -} - -static SkRect selection_box(const SkFontMetrics& metrics, - float advance, - SkPoint pos) { - if (fabsf(advance) < 1.0f) { - advance = copysignf(1.0f, advance); - } - return SkRect{pos.x(), - pos.y() + metrics.fAscent, - pos.x() + advance, - pos.y() + metrics.fDescent}.makeSorted(); -} - -static void set_character_bounds(void* context, - const char* utf8Text, - size_t utf8TextBytes, - size_t glyphCount, - const SkGlyphID* glyphs, - const SkPoint* positions, - const uint32_t* clusters, - const SkFont& font) -{ - SkASSERT(context); - SkASSERT(glyphCount > 0); - SkRect* cursors = (SkRect*)context; - - SkFontMetrics metrics; - font.getMetrics(&metrics); - std::unique_ptr advances(new float[glyphCount]); - font.getWidths({glyphs, glyphCount}, {advances.get(), glyphCount}); - - // Loop over each cluster in this run. - size_t clusterStart = 0; - for (size_t glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex) { - if (glyphIndex + 1 < glyphCount // more glyphs - && clusters[glyphIndex] == clusters[glyphIndex + 1]) { - continue; // multi-glyph cluster - } - unsigned textBegin = clusters[glyphIndex]; - unsigned textEnd = utf8TextBytes; - for (size_t i = 0; i < glyphCount; ++i) { - if (clusters[i] >= textEnd) { - textEnd = clusters[i] + 1; - } - } - for (size_t i = 0; i < glyphCount; ++i) { - if (clusters[i] > textBegin && clusters[i] < textEnd) { - textEnd = clusters[i]; - if (textEnd == textBegin + 1) { break; } - } - } - SkASSERT(glyphIndex + 1 > clusterStart); - unsigned clusterGlyphCount = glyphIndex + 1 - clusterStart; - const SkPoint* clusterGlyphPositions = &positions[clusterStart]; - const float* clusterAdvances = &advances[clusterStart]; - clusterStart = glyphIndex + 1; // for next loop - - SkRect clusterBox = selection_box(metrics, clusterAdvances[0], clusterGlyphPositions[0]); - for (unsigned i = 1; i < clusterGlyphCount; ++i) { // multiple glyphs - clusterBox.join(selection_box(metrics, clusterAdvances[i], clusterGlyphPositions[i])); - } - if (textBegin + 1 == textEnd) { // single byte, fast path. - cursors[textBegin] = clusterBox; - continue; - } - int textCount = textEnd - textBegin; - int codePointCount = SkUTF::CountUTF8(utf8Text + textBegin, textCount); - if (codePointCount == 1) { // single codepoint, fast path. - cursors[textBegin] = clusterBox; - continue; - } - - float width = clusterBox.width() / codePointCount; - SkASSERT(width > 0); - const char* ptr = utf8Text + textBegin; - const char* end = utf8Text + textEnd; - float x = clusterBox.left(); - while (ptr < end) { // for each codepoint in cluster - const char* nextPtr = ptr; - SkUTF::NextUTF8(&nextPtr, end); - int firstIndex = ptr - utf8Text; - float nextX = x + width; - cursors[firstIndex] = SkRect{x, clusterBox.top(), nextX, clusterBox.bottom()}; - x = nextX; - ptr = nextPtr; - } - } -} - -CollabTextBox::ShapeResult CollabTextBox::shape(const char* utf8Text, - size_t textByteLen, - const SkFont& font, - sk_sp fontMgr, - const char* locale, - float width) -{ - ShapeResult result; - if (SkUTF::CountUTF8(utf8Text, textByteLen) < 0) { - utf8Text = nullptr; - textByteLen = 0; - } - std::unique_ptr shaper = nullptr; - auto unicode = get_unicode_2(); - -//#ifdef __EMSCRIPTEN__ - //shaper = SkShaper::MakePrimitive(); -//#else - shaper = SkShapers::HB::ShaperDrivenWrapper(unicode, fontMgr); -//#endi - SkASSERT(shaper); - - float height = font.getSpacing(); - RunHandler runHandler(utf8Text, textByteLen); - if (textByteLen) { - result.glyphBounds.resize(textByteLen); - for (SkRect& c : result.glyphBounds) { - c = SkRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX}; - } - runHandler.setRunCallback(set_character_bounds, result.glyphBounds.data()); - - static constexpr uint8_t kBidiLevelLTR = 0; - std::unique_ptr bidi = nullptr; - bidi = SkShapers::unicode::BidiRunIterator( - unicode, utf8Text, textByteLen, kBidiLevelLTR); - //if (!bidi) { - // throw std::runtime_error("[CollabTextBox] SkShaper unicode or harfbuzz not available"); - // bidi = std::make_unique(kBidiLevelLTR, textByteLen); - //} - SkASSERT(bidi); - - std::unique_ptr language = - SkShaper::MakeStdLanguageRunIterator(utf8Text, textByteLen); - SkASSERT(language); - - std::unique_ptr script = - SkShaper::MakeScriptRunIterator(utf8Text, textByteLen, SkSetFourByteTag('Z','z','z','z')); - SkASSERT(script); - - std::unique_ptr fontRuns = - SkShaper::MakeFontMgrRunIterator(utf8Text, textByteLen, font, fontMgr); - SkASSERT(fontRuns); - - shaper->shape(utf8Text, - textByteLen, - *fontRuns, - *bidi, - *script, - *language, - nullptr, - 0, - width, - &runHandler); - if (runHandler.lineEndOffsets().size() > 1) { - result.lineBreakOffsets = runHandler.lineEndOffsets(); - SkASSERT(result.lineBreakOffsets.size() > 0); - result.lineBreakOffsets.pop_back(); - } - height = std::max(height, runHandler.endPoint().y()); - result.blob = runHandler.makeBlob(); - } - result.glyphBounds.push_back(runHandler.finalRect(font)); - result.verticalAdvance = (int)ceilf(height); - result.wordBreaks = get_utf8_word_boundaries(utf8Text, textByteLen, locale); - return result; -} - -std::vector CollabTextBox::get_utf8_word_boundaries(const char* begin, std::size_t byteCount, const char* locale) { - auto unicode = get_unicode_2(); - if (nullptr == unicode) { - return {}; - } - std::vector positions; - if (!unicode->getWords(begin, byteCount, locale, &positions) || byteCount == 0) { - return {}; - } - std::vector result; - result.resize(byteCount); - for (auto& pos : positions) { - if(pos < result.size()) // Sometimes the position to cut at is out of bounds - result[pos] = true; - } - return result; -} diff --git a/src/CollabTextBox/CollabTextBoxShaper.hpp b/src/CollabTextBox/CollabTextBoxShaper.hpp deleted file mode 100644 index e50dc58..0000000 --- a/src/CollabTextBox/CollabTextBoxShaper.hpp +++ /dev/null @@ -1,21 +0,0 @@ -// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details - -#pragma once -#include -#include - -#include -#include - -namespace CollabTextBox { - struct ShapeResult { - sk_sp blob; - std::vector lineBreakOffsets; - std::vector glyphBounds; - std::vector wordBreaks; - int verticalAdvance; - }; - - ShapeResult shape(const char* ut8text, size_t textByteLen, const SkFont& font, sk_sp fontMgr, const char* locale, float width); - std::vector get_utf8_word_boundaries(const char* begin, size_t byteCount, const char* locale); -}; diff --git a/src/DrawComponents/DrawTextBox.cpp b/src/DrawComponents/DrawTextBox.cpp index ef3f552..0fa0fde 100644 --- a/src/DrawComponents/DrawTextBox.cpp +++ b/src/DrawComponents/DrawTextBox.cpp @@ -9,16 +9,21 @@ #include "../DrawCollision.hpp" #endif +DrawTextBox::DrawTextBox(): + textBox(std::make_shared()), + cursor(std::make_shared()) +{} + DrawComponentType DrawTextBox::get_type() const { return DRAWCOMPONENT_TEXTBOX; } void DrawTextBox::save(cereal::PortableBinaryOutputArchive& a) const { - a(d.editing, d.p1, d.p2, d.textColor, d.textSize, d.cursor, d.currentText); + a(d.editing, d.p1, d.p2, d.textColor, d.textSize, *cursor, d.currentText); } void DrawTextBox::load(cereal::PortableBinaryInputArchive& a) { - a(d.editing, d.p1, d.p2, d.textColor, d.textSize, d.cursor, d.currentText); + a(d.editing, d.p1, d.p2, d.textColor, d.textSize, *cursor, d.currentText); #ifndef IS_SERVER textboxUpdate = true; #endif @@ -39,7 +44,6 @@ std::shared_ptr DrawTextBox::deep_copy() const { a->coords = coords; a->textboxUpdate = false; a->collisionTree = collisionTree; - a->textBox = textBox; return a; } @@ -50,18 +54,9 @@ void DrawTextBox::update_from_delayed_ptr(const std::shared_ptr& } void DrawTextBox::init_text_box(DrawingProgram& drawP) { - textBox.setFontMgr(drawP.world.main.fonts.mgr); - SkFont f(drawP.world.main.fonts.map["Roboto"], d.textSize); - f.setLinearMetrics(true); - f.setHinting(SkFontHinting::kNormal); - //font.setForceAutoHinting(true); - f.setSubpixel(true); - f.setBaselineSnap(true); - f.setEdging(SkFont::Edging::kSubpixelAntiAlias); - textBox.setFont(f); - textBox.setWidth(d.p2.x() - d.p1.x()); - textBox.fLines.clear(); - textBox.insert({0, 0}, d.currentText); + textBox->set_font_collection(drawP.world.main.fonts.collection); + textBox->set_width(d.p2.x() - d.p1.x()); + textBox->insert({0, 0}, d.currentText); } void DrawTextBox::draw(SkCanvas* canvas, const DrawData& drawData) { @@ -79,14 +74,12 @@ void DrawTextBox::draw(SkCanvas* canvas, const DrawData& drawData) { canvas->clipRect(clipR); canvas->translate(d.p1.x(), d.p1.y()); - CollabTextBox::Editor::PaintOpts paintOpts; - paintOpts.fForegroundColor = convert_vec4(d.textColor); - paintOpts.fBackgroundColor = {0.0f, 0.0f, 0.0f, 0.0f}; - paintOpts.cursorColor = {0.7f, 0.7f, 1.0f, 1.0f}; - paintOpts.showCursor = d.editing; - paintOpts.cursor = d.cursor; + RichTextBox::PaintOpts paintOpts; + paintOpts.cursorColor = {0.7f, 0.7f, 1.0f}; + if(d.editing && cursor) + paintOpts.cursor = *cursor; - textBox.paint(canvas, paintOpts); + textBox->paint(canvas, paintOpts); canvas->restore(); } } @@ -98,13 +91,13 @@ void DrawTextBox::update(DrawingProgram& drawP) { void DrawTextBox::set_textbox_string(const std::string& str) { d.currentText = str; - textBox.fLines.clear(); - textBox.insert({0, 0}, d.currentText); + textBox->remove({0, 0}, textBox->move(RichTextBox::Movement::END, {0, 0})); + textBox->insert({0, 0}, d.currentText); textboxUpdate = false; } void DrawTextBox::update_contained_string(DrawingProgram& drawP) { - d.currentText = textBox.get_string(); + d.currentText = textBox->get_string(); } Vector2f DrawTextBox::get_mouse_pos(DrawingProgram& drawP) { diff --git a/src/DrawComponents/DrawTextBox.hpp b/src/DrawComponents/DrawTextBox.hpp index 2bc1024..6904244 100644 --- a/src/DrawComponents/DrawTextBox.hpp +++ b/src/DrawComponents/DrawTextBox.hpp @@ -1,16 +1,16 @@ #pragma once #include "../SharedTypes.hpp" #include "DrawComponent.hpp" -#include "../CollabTextBox/CollabTextBoxCursor.hpp" #include "../CoordSpaceHelper.hpp" +#include "../RichTextBox/RichTextBox.hpp" #ifndef IS_SERVER #include - #include "../CollabTextBox/CollabTextBox.hpp" #endif class DrawTextBox : public DrawComponent { public: + DrawTextBox(); virtual DrawComponentType get_type() const override; virtual void save(cereal::PortableBinaryOutputArchive& a) const override; virtual void load(cereal::PortableBinaryInputArchive& a) override; @@ -22,7 +22,6 @@ class DrawTextBox : public DrawComponent { Vector2f p1; Vector2f p2; std::string currentText; - CollabTextBox::Cursor cursor; bool editing = false; bool operator==(const Data& o) const = default; bool operator!=(const Data& o) const = default; @@ -46,7 +45,8 @@ class DrawTextBox : public DrawComponent { virtual SCollision::AABB get_obj_coord_bounds() const override; bool textboxUpdate = false; - CollabTextBox::Editor textBox; + std::shared_ptr textBox; + std::shared_ptr cursor; SCollision::BVHContainer collisionTree; #endif }; diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 96cbe36..0516c0c 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -3,7 +3,6 @@ #include "../MainProgram.hpp" #include "../DrawData.hpp" #include "Helpers/SCollision.hpp" -#include "../CollabTextBox/CollabTextBox.hpp" #include #include #include "EditTool.hpp" @@ -59,11 +58,11 @@ void TextBoxEditTool::commit_edit_updates(const std::shared_ptr& void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptr& comp, std::any& prevData) { std::shared_ptr a = std::static_pointer_cast(comp); - auto& cur = a->d.cursor; + auto& cur = a->cursor; auto& textbox = a->textBox; + cur = std::make_shared(); Vector2f textSelectPos = a->get_mouse_pos(drawP); - SkIPoint p = convert_vec2(textSelectPos.cast()); - cur.pos = cur.selectionBeginPos = cur.selectionEndPos = textbox.getPosition(p); + textbox->process_mouse_left_button(*cur, textSelectPos, true, false, false); prevData = a->d; a->d.editing = true; a->client_send_update(drawP, false); @@ -78,117 +77,15 @@ bool TextBoxEditTool::edit_update(const std::shared_ptr& comp) { mousePointCollection.circle.emplace_back(drawP.world.main.input.mouse.pos, 1.0f); mousePointCollection.recalculate_bounds(); - a->textBox.setWidth(std::abs(a->d.p2.x() - a->d.p1.x())); + a->textBox->set_width(std::abs(a->d.p2.x() - a->d.p1.x())); InputManager& input = drawP.world.main.input; - auto& textbox = a->textBox; - auto& cur = a->d.cursor; - bool moved = false; - bool movedHeld = false; + bool collidesWithBox = a->collides_with_cam_coords(drawP.world.drawData.cam.c, mousePointCollection); - bool collidesWithBox = false; - if(a->collides_with_cam_coords(drawP.world.drawData.cam.c, mousePointCollection)) { - //drawP.world.main.input.cursorIcon = InputManager::SystemCursorType::TEXT; - collidesWithBox = true; - } + input.text.set_rich_text_box_input(a->textBox, a->cursor); + a->textBox->process_mouse_left_button(*a->cursor, a->get_mouse_pos(drawP), drawP.controls.leftClick && collidesWithBox, drawP.controls.leftClickHeld, input.key(InputManager::KEY_GENERIC_LSHIFT).held); - if(drawP.controls.leftClick) { - if(collidesWithBox) { - Vector2f textSelectPos = a->get_mouse_pos(drawP); - SkIPoint p = convert_vec2(textSelectPos.cast()); - cur.pos = cur.selectionBeginPos = textbox.getPosition(p); - moved = true; - } - } - else if(drawP.controls.leftClickHeld) { - Vector2f textSelectPos = a->get_mouse_pos(drawP); - SkIPoint p = convert_vec2(textSelectPos.cast()); - cur.pos = cur.selectionBeginPos = textbox.getPosition(p); - movedHeld = true; - } - - if(input.key(InputManager::KEY_TEXT_LEFT).repeat) - cur.pos = cur.selectionBeginPos = textbox.move(input.key(InputManager::KEY_TEXT_CTRL).held ? CollabTextBox::Movement::kWordLeft : CollabTextBox::Movement::kLeft, cur.selectionBeginPos); - if(input.key(InputManager::KEY_TEXT_RIGHT).repeat) - cur.pos = cur.selectionBeginPos = textbox.move(input.key(InputManager::KEY_TEXT_CTRL).held ? CollabTextBox::Movement::kWordRight : CollabTextBox::Movement::kRight, cur.selectionBeginPos); - if(input.key(InputManager::KEY_TEXT_UP).repeat) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kUp, cur.selectionBeginPos); - if(input.key(InputManager::KEY_TEXT_DOWN).repeat) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kDown, cur.selectionBeginPos); - if(input.key(InputManager::KEY_TEXT_HOME).repeat) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kHome, cur.selectionBeginPos); - - moved = moved || input.key(InputManager::KEY_TEXT_DOWN).repeat || input.key(InputManager::KEY_TEXT_UP).repeat || input.key(InputManager::KEY_TEXT_RIGHT).repeat || input.key(InputManager::KEY_TEXT_LEFT).repeat || input.key(InputManager::KEY_TEXT_HOME).repeat; - if(moved && !input.key(InputManager::KEY_TEXT_SHIFT).held && !movedHeld) - cur.selectionEndPos = cur.selectionBeginPos; - - if(moved || movedHeld) - update_textbox_network(a); - - if(input.key(InputManager::KEY_TEXT_BACKSPACE).repeat) { - edit_text([&]() { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kLeft, cur.pos)); - }, a); - } - if(input.key(InputManager::KEY_TEXT_DELETE).repeat) { - edit_text([&]() { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kRight, cur.pos)); - }, a); - } - if(input.get_clipboard_paste_happened()) { - edit_text([&]() { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - std::string clipboard = input.get_clipboard_str(); - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, clipboard); - }, a); - } - if(input.key(InputManager::KEY_TEXT_ENTER).repeat) { - edit_text([&]() { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - cur.pos = textbox.insert(cur.pos, "\n"); - cur.pos.fParagraphIndex++; - cur.pos.fTextByteIndex = 0; - cur.selectionBeginPos = cur.selectionEndPos = cur.pos; - }, a); - } - if(input.key(InputManager::KEY_TEXT_COPY).repeat) { - std::string clipboardData = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos); - input.set_clipboard_str(clipboardData); - } - if(input.key(InputManager::KEY_TEXT_CUT).repeat) { - edit_text([&]() { - std::string clipboardData = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos); - input.set_clipboard_str(clipboardData); - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - }, a); - } - if(input.key(InputManager::KEY_TEXT_SELECTALL).repeat) { - cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1; - cur.selectionEndPos.fTextByteIndex = textbox.line(cur.selectionEndPos.fParagraphIndex).size(); - cur.pos = cur.selectionEndPos; - cur.selectionBeginPos.fTextByteIndex = 0; - cur.selectionBeginPos.fParagraphIndex = 0; - update_textbox_network(a); - } - if(!input.text.newInput.empty()) { - edit_text([&]() { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, input.text.newInput); - }, a); - } - - input.text.set_accepting_input(); return true; } diff --git a/src/DrawingProgram/TextBoxTool.cpp b/src/DrawingProgram/TextBoxTool.cpp index de841c4..02d19a0 100644 --- a/src/DrawingProgram/TextBoxTool.cpp +++ b/src/DrawingProgram/TextBoxTool.cpp @@ -5,7 +5,6 @@ #include "../DrawData.hpp" #include "Helpers/MathExtras.hpp" #include "Helpers/SCollision.hpp" -#include "../CollabTextBox/CollabTextBox.hpp" #include #include #include "EditTool.hpp" diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index 331510c..8f56cc1 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -10,6 +10,7 @@ #include #include #include +#include "../../RichTextBox/RichTextBox.hpp" using namespace Eigen; @@ -104,6 +105,8 @@ struct UpdateInputData { std::optional textOut; } clipboard; + std::function, std::shared_ptr)> richTextBoxEdit; + SkFont get_font(float fSize) const; uint16_t fontSize = 20; diff --git a/src/GUIStuff/Elements/TextBox.hpp b/src/GUIStuff/Elements/TextBox.hpp index 94526dd..47a5245 100644 --- a/src/GUIStuff/Elements/TextBox.hpp +++ b/src/GUIStuff/Elements/TextBox.hpp @@ -1,20 +1,23 @@ #pragma once #include "Element.hpp" -#include "../../CollabTextBox/CollabTextBox.hpp" +#include "../../RichTextBox/RichTextBox.hpp" +#include namespace GUIStuff { template class TextBox : public Element { public: void update(UpdateInputData& io, T* newData, const std::function(const std::string&)>& newFromStr, const std::function newToStr, bool newSingleLine, bool updateEveryEdit, const std::function& elemUpdate) { + if(!textbox || !cur) + force_update_textbox(io, false); + data = newData; fromStr = newFromStr; toStr = newToStr; singleLine = newSingleLine; - force_update_textbox(false); + force_update_textbox(io, false); - textbox.setFont(SkFont(io.textTypeface, io.fontSize)); - textbox.setFontMgr(io.textFontMgr); + textbox->set_width(singleLine ? std::numeric_limits::max() : bb.width()); CLAY({ .layout = { @@ -24,109 +27,15 @@ template class TextBox : public Element { }) { selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld); if(data && selection.selected) { - bool moved = io.key.left || io.key.right || io.key.up || io.key.down || io.key.home; - bool movedHeld = false; - - if(io.mouse.leftClick) { - Vector2f textSelectPos = io.mouse.pos - bb.min; - SkIPoint p = convert_vec2(textSelectPos.cast()); - cur.pos = cur.selectionBeginPos = textbox.getPosition(p); - moved = true; - } - else if(io.mouse.leftHeld) { - Vector2f textSelectPos = io.mouse.pos - bb.min; - SkIPoint p = convert_vec2(textSelectPos.cast()); - cur.pos = cur.selectionBeginPos = textbox.getPosition(p); - movedHeld = true; - } - - if(io.key.left) - cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordLeft : CollabTextBox::Movement::kLeft, cur.selectionBeginPos); - if(io.key.right) - cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordRight : CollabTextBox::Movement::kRight, cur.selectionBeginPos); - if(io.key.up && !singleLine) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kUp, cur.selectionBeginPos); - if(io.key.down && !singleLine) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kDown, cur.selectionBeginPos); - if(io.key.home) - cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kHome, cur.selectionBeginPos); - - if(moved && !io.key.leftShift && !movedHeld) - cur.selectionEndPos = cur.selectionBeginPos; - - if(io.key.backspace) { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kLeft, cur.pos)); - update_on_edit(updateEveryEdit); - } - if(io.key.del) { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kRight, cur.pos)); - update_on_edit(updateEveryEdit); - } - if(io.key.paste) { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - if(singleLine) { - std::string removedNewlines = io.clipboard.textInFunc(); - std::erase(removedNewlines, '\n'); - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, removedNewlines); - } - else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.clipboard.textInFunc()); - update_on_edit(updateEveryEdit); - } - if(io.key.enter) { - if(singleLine) { - std::optional dataToAssign = fromStr(textbox.get_string()); - if(dataToAssign) - *data = dataToAssign.value(); - force_update_textbox(true); - } - else { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - cur.pos = textbox.insert(cur.pos, "\n"); - cur.pos.fParagraphIndex++; - cur.pos.fTextByteIndex = 0; - cur.selectionBeginPos = cur.selectionEndPos = cur.pos; - update_on_edit(updateEveryEdit); - } - } - if(io.key.copy) { - io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos); - } - if(io.key.cut) { - io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos); - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - update_on_edit(updateEveryEdit); - } - if(io.key.selectAll) { - cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1; - cur.selectionEndPos.fTextByteIndex = textbox.line(cur.selectionEndPos.fParagraphIndex).size(); - cur.pos = cur.selectionEndPos; - cur.selectionBeginPos.fTextByteIndex = 0; - cur.selectionBeginPos.fParagraphIndex = 0; - } - if(!io.textInput.empty()) { - if(cur.selectionBeginPos != cur.selectionEndPos) - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.textInput); - update_on_edit(updateEveryEdit); - } - - io.acceptingTextInput = true; + io.richTextBoxEdit(textbox, cur); + textbox->process_mouse_left_button(*cur, io.mouse.pos - bb.min, io.mouse.leftClick, io.mouse.leftHeld, io.key.leftShift); + update_on_edit(updateEveryEdit); } if(data && selection.justUnselected) { - std::optional dataToAssign = fromStr(textbox.get_string()); + std::optional dataToAssign = fromStr(textbox->get_string()); if(dataToAssign) *data = dataToAssign.value(); - force_update_textbox(true); + force_update_textbox(io, true); } if(elemUpdate) elemUpdate(selection); @@ -134,7 +43,7 @@ template class TextBox : public Element { } virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override { - if(!data) + if(!data || !textbox || !cur) return; bb = get_bb(command); @@ -150,48 +59,38 @@ template class TextBox : public Element { canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height())); - if(singleLine && selection.selected) { - SkRect cursorRect = textbox.getLocation(cur.pos); - canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), 0.0f), bb.min.y()); - } - else - canvas->translate(bb.min.x(), bb.min.y()); + canvas->translate(bb.min.x(), bb.min.y()); - textbox.setFont(SkFont(io.textTypeface, io.fontSize)); - textbox.setFontMgr(io.textFontMgr); - textbox.setWidth(singleLine ? 99999999 : bb.width()); + RichTextBox::PaintOpts paintOpts; + if(selection.selected) + paintOpts.cursor = *cur; + paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB}; - CollabTextBox::Editor::PaintOpts paintOpts; - paintOpts.fForegroundColor = io.theme->frontColor1; - paintOpts.fBackgroundColor = {0.0f, 0.0f, 0.0f, 0.0f}; - paintOpts.cursorColor = io.theme->fillColor5; - paintOpts.showCursor = selection.selected; - paintOpts.cursor = cur; - - textbox.paint(canvas, paintOpts); + textbox->paint(canvas, paintOpts); canvas->restore(); } SelectionHelper selection; private: - void force_update_textbox(bool reallyForce) { - if(data && (*data == oldData) && !reallyForce) + void force_update_textbox(UpdateInputData& io, bool reallyForce) { + if(data && (*data == oldData) && !reallyForce && textbox && cur) return; - textbox = CollabTextBox::Editor(); - cur = CollabTextBox::Cursor(); + textbox = std::make_shared(); + textbox->set_font_collection(io.fontCollection); + textbox->set_allow_newlines(!singleLine); + cur = std::make_shared(); + if(data) { - cur.pos = cur.selectionBeginPos = cur.selectionEndPos = textbox.insert({0, 0}, toStr(*data)); + cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*data)); oldData = *data; } - else {} - //oldData = T(); } void update_on_edit(bool updateEveryEdit) { if(updateEveryEdit) { - std::optional dataToAssign = fromStr(textbox.get_string()); + std::optional dataToAssign = fromStr(textbox->get_string()); if(dataToAssign) { *data = dataToAssign.value(); oldData = *data; @@ -207,8 +106,8 @@ template class TextBox : public Element { std::function(const std::string&)> fromStr; std::function toStr; - CollabTextBox::Editor textbox; - CollabTextBox::Cursor cur; + std::shared_ptr textbox; + std::shared_ptr cur; SCollision::AABB bb; }; diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 38625bf..8576177 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -301,6 +301,12 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held); break; } + case SDLK_TAB: { + set_key_down(e, KEY_TEXT_TAB); + if(text.textBox && key(KEY_TEXT_TAB).repeat) + text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::TAB, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held); + break; + } case SDLK_ESCAPE: set_key_down(e, KEY_GENERIC_ESCAPE); break; @@ -375,6 +381,9 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { case SDLK_RETURN: set_key_up(e, KEY_TEXT_ENTER); break; + case SDLK_TAB: + set_key_up(e, KEY_TEXT_TAB); + break; case SDLK_ESCAPE: set_key_up(e, KEY_GENERIC_ESCAPE); break; diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 5213246..9e44f5b 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -165,6 +165,7 @@ struct InputManager { KEY_TEXT_CUT, KEY_TEXT_PASTE, KEY_TEXT_ENTER, + KEY_TEXT_TAB, KEY_TEXT_CTRL, KEY_TEXT_META, KEY_TEXT_SELECTALL, diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index 2e9942c..9672af5 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -1,5 +1,4 @@ #include "MainProgram.hpp" -#include "CollabTextBox/CollabTextBoxCursor.hpp" #include "DrawingProgram/DrawingProgramToolBase.hpp" #include "cereal/archives/portable_binary.hpp" #include "cereal/details/helpers.hpp" @@ -37,12 +36,6 @@ #include #include -#include "RichTextBox/RichTextBox.hpp" - -std::shared_ptr textBox; -std::shared_ptr textBoxCursor; -RichTextBox::PaintOpts paintOpts; - MainProgram::MainProgram(): toolbar(*this) { @@ -68,87 +61,67 @@ MainProgram::MainProgram(): *logFile << "[CHAT] " << text << std::endl; std::cout << "[CHAT] " << text << std::endl; }); - - textBox = std::make_shared(); - textBoxCursor = std::make_shared(); - textBox->set_font_collection(fonts.collection); - textBox->insert({0, 0}, R"(Hello world)"); - paintOpts.cursor = RichTextBox::Cursor{}; - paintOpts.cursorColor = {1.0f, 0.0f, 0.0f}; } void MainProgram::update() { - input.text.set_rich_text_box_input(textBox, textBoxCursor); - textBox->process_mouse_left_button(*textBoxCursor, input.mouse.pos - Vector2f{10.0f, 10.0f}, input.mouse.leftClicks, input.mouse.leftDown, input.key(InputManager::KEY_GENERIC_LSHIFT).held); - textBox->set_width(window.size.x() - 20.0f); + while(std::chrono::duration_cast>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit); + lastFrameTime = std::chrono::steady_clock::now(); - //while(std::chrono::duration_cast>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit); - //lastFrameTime = std::chrono::steady_clock::now(); + std::erase_if(input.droppedItems, [&](auto& droppedItem) { + if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) { + std::filesystem::path droppedFilePath(droppedItem.droppedData); + if(droppedFilePath.has_extension() && droppedFilePath.extension().string() == std::string("." + World::FILE_EXTENSION)) { + new_tab({ + .conType = World::CONNECTIONTYPE_LOCAL, + .fileSource = droppedFilePath.string() + }, true); + return true; + } + } + return false; + }); - //if(input.key(InputManager::KEY_DRAW_TOOL_BRUSH).repeat) - // paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kRight); - //else if(input.key(InputManager::KEY_DRAW_TOOL_ERASER).repeat) - // paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kLeft); - //else if(input.key(InputManager::KEY_DRAW_TOOL_TEXTBOX).repeat) - // paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kUp); - //else if(input.key(InputManager::KEY_DRAW_TOOL_ELLIPSE).repeat) - // paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kDown); + if(setTabToClose) { + worlds.erase(worlds.begin() + setTabToClose.value()); + setTabToClose.reset(); + } - //std::erase_if(input.droppedItems, [&](auto& droppedItem) { - // if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) { - // std::filesystem::path droppedFilePath(droppedItem.droppedData); - // if(droppedFilePath.has_extension() && droppedFilePath.extension().string() == std::string("." + World::FILE_EXTENSION)) { - // new_tab({ - // .conType = World::CONNECTIONTYPE_LOCAL, - // .fileSource = droppedFilePath.string() - // }, true); - // return true; - // } - // } - // return false; - //}); + if(worldIndex >= worlds.size()) + worldIndex = worlds.size() - 1; + if(worlds.size() == 0) + new_tab({ + .conType = World::CONNECTIONTYPE_LOCAL + }, true); - //if(setTabToClose) { - // worlds.erase(worlds.begin() + setTabToClose.value()); - // setTabToClose.reset(); - //} + std::shared_ptr oldWorld = world; + world = worlds[worldIndex]; - //if(worldIndex >= worlds.size()) - // worldIndex = worlds.size() - 1; - //if(worlds.size() == 0) - // new_tab({ - // .conType = World::CONNECTIONTYPE_LOCAL - // }, true); + deltaTime.update_time_since(); + deltaTime.update_time_point(); - //std::shared_ptr oldWorld = world; - //world = worlds[worldIndex]; + toolbar.initialize_io_before_update(); - //deltaTime.update_time_since(); - //deltaTime.update_time_point(); + for(auto& w : worlds) { + if(w == world) + w->focus_update(); + else + w->unfocus_update(); + } - //toolbar.initialize_io_before_update(); + toolbar.update(); // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data - //for(auto& w : worlds) { - // if(w == world) - // w->focus_update(); - // else - // w->unfocus_update(); - //} + if(input.key(InputManager::KEY_NOGUI).pressed) + drawGui = !drawGui; - //toolbar.update(); // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data + if(input.key(InputManager::KEY_FULLSCREEN).pressed) + input.toggleFullscreen = true; - //if(input.key(InputManager::KEY_NOGUI).pressed) - // drawGui = !drawGui; + NetLibrary::update(); - //if(input.key(InputManager::KEY_FULLSCREEN).pressed) - // input.toggleFullscreen = true; - - //NetLibrary::update(); - - //if(tabSetToOpen) { - // tabSetToOpen = false; - // new_tab_open(); - //} + if(tabSetToOpen) { + tabSetToOpen = false; + new_tab_open(); + } return; } @@ -257,16 +230,9 @@ void MainProgram::load_config() { } void MainProgram::draw(SkCanvas* canvas) { - //canvas->clear(transparentBackground ? SkColor4f{0.0f, 0.0f, 0.0f, 0.0f} : world->canvasTheme.backColor); - //world->draw(canvas); - //toolbar.draw(canvas); - - canvas->clear(SkColor4f{0.0f, 0.0f, 0.0f, 1.0f}); - canvas->save(); - canvas->translate(10.0f, 10.0f); - paintOpts.cursor = *textBoxCursor; - textBox->paint(canvas, paintOpts); - canvas->restore(); + canvas->clear(transparentBackground ? SkColor4f{0.0f, 0.0f, 0.0f, 0.0f} : world->canvasTheme.backColor); + world->draw(canvas); + toolbar.draw(canvas); } bool MainProgram::network_being_used() { diff --git a/src/RichTextBox/RichTextBox.cpp b/src/RichTextBox/RichTextBox.cpp index 3b80f05..5ba1774 100644 --- a/src/RichTextBox/RichTextBox.cpp +++ b/src/RichTextBox/RichTextBox.cpp @@ -59,8 +59,10 @@ void RichTextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool sh cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(Movement::RIGHT, cur.pos)); break; case InputKey::ENTER: - if(newlinesAllowed) - process_text_input(cur, "\n"); + process_text_input(cur, "\n"); + break; + case InputKey::TAB: + process_text_input(cur, "\t"); break; case InputKey::SELECT_ALL: cur.selectionBeginPos = {0, 0}; @@ -214,6 +216,10 @@ RichTextBox::TextPosition RichTextBox::get_text_pos_closest_to_point(Vector2f po return TextPosition{0, pIndex}; } +std::string RichTextBox::get_string() { + return get_text_between({0, 0}, move(RichTextBox::Movement::END, {0, 0})); +} + std::string RichTextBox::get_text_between(TextPosition p1, TextPosition p2) { p1 = move(Movement::NOWHERE, p1); p2 = move(Movement::NOWHERE, p2); @@ -349,11 +355,10 @@ RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, break; } case Movement::LEFT_WORD: { - TextPosition endPos = move(Movement::END, {0, 0}); if(pos == TextPosition{0, 0}) break; - std::string fullText = get_text_between({0, 0}, endPos); + std::string fullText = get_string(); size_t bytePosInFullText = get_byte_pos_from_text_pos(pos); auto u = SkUnicodes::ICU::Make(); @@ -380,7 +385,7 @@ RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, if(pos == endPos) break; - std::string fullText = get_text_between({0, 0}, endPos); + std::string fullText = get_string(); size_t bytePosInFullText = get_byte_pos_from_text_pos(pos); auto u = SkUnicodes::ICU::Make(); @@ -421,7 +426,7 @@ void RichTextBox::set_width(float newWidth) { } void RichTextBox::set_allow_newlines(bool allow) { - newlinesAllowed = true; + newlinesAllowed = allow; } void RichTextBox::set_font_collection(const sk_sp& fC) { @@ -452,16 +457,26 @@ void RichTextBox::rebuild() { } } +void RichTextBox::set_tab_space_width(unsigned newTabWidth) { + tabWidth = newTabWidth; +} + RichTextBox::TextPosition RichTextBox::insert(TextPosition pos, std::string_view textToInsert) { pos = move(Movement::NOWHERE, pos); for(char c : textToInsert) { if(c == '\n') { - paragraphs.insert(paragraphs.begin() + pos.fParagraphIndex + 1, ParagraphData{}); - paragraphs[pos.fParagraphIndex + 1].text = paragraphs[pos.fParagraphIndex].text.substr(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex); - paragraphs[pos.fParagraphIndex].text.erase(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex); - pos.fParagraphIndex++; - pos.fTextByteIndex = 0; + if(newlinesAllowed) { + paragraphs.insert(paragraphs.begin() + pos.fParagraphIndex + 1, ParagraphData{}); + paragraphs[pos.fParagraphIndex + 1].text = paragraphs[pos.fParagraphIndex].text.substr(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex); + paragraphs[pos.fParagraphIndex].text.erase(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex); + pos.fParagraphIndex++; + pos.fTextByteIndex = 0; + } + } + else if(c == '\t') { + paragraphs[pos.fParagraphIndex].text.insert(pos.fTextByteIndex, std::string(tabWidth, ' ')); + pos.fTextByteIndex += tabWidth; } else { paragraphs[pos.fParagraphIndex].text.insert(paragraphs[pos.fParagraphIndex].text.begin() + pos.fTextByteIndex, c); @@ -571,7 +586,6 @@ void RichTextBox::rects_between_text_positions_func(TextPosition p1, TextPositio } } - void RichTextBox::paint(SkCanvas* canvas, const PaintOpts& paintOpts) { rebuild(); diff --git a/src/RichTextBox/RichTextBox.hpp b/src/RichTextBox/RichTextBox.hpp index dd5c51d..9377fdc 100644 --- a/src/RichTextBox/RichTextBox.hpp +++ b/src/RichTextBox/RichTextBox.hpp @@ -36,6 +36,9 @@ class RichTextBox { TextPosition pos = {0, 0}; TextPosition selectionBeginPos = {0, 0}; TextPosition selectionEndPos = {0, 0}; + template void serialize(Archive& a) { + a(pos, selectionBeginPos, selectionEndPos); + } std::optional previousX; }; @@ -62,6 +65,7 @@ class RichTextBox { BACKSPACE, DELETE, ENTER, + TAB, SELECT_ALL }; @@ -70,8 +74,10 @@ class RichTextBox { std::string process_copy(Cursor& cur); std::string process_cut(Cursor& cur); std::string get_text_between(TextPosition p1, TextPosition p2); - + std::string get_string(); + void set_tab_space_width(unsigned newTabWidth); void process_text_input(Cursor& cur, const std::string& in); + private: static size_t count_grapheme(const std::string& text); static size_t next_grapheme(const std::string& text, size_t textBytePos); @@ -91,6 +97,7 @@ class RichTextBox { float width = 0.0f; float fontSize = 0.0f; bool needsRebuild = true; + unsigned tabWidth = 8; sk_sp fontCollection; struct ParagraphData { diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 9358f07..e093340 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1941,12 +1941,9 @@ void Toolbar::initialize_io_before_update() { io->key.paste = main.input.get_clipboard_paste_happened(); io->key.cut = main.input.key(InputManager::KEY_TEXT_CUT).repeat; io->key.escape = main.input.key(InputManager::KEY_GENERIC_ESCAPE).repeat; - - io->textInput = main.input.text.newInput; - io->clipboard.textInFunc = [&]() { - return main.input.get_clipboard_str(); + io->richTextBoxEdit = [&](std::shared_ptr t, std::shared_ptr c) { + main.input.text.set_rich_text_box_input(t, c); }; - io->clipboard.textOut = std::nullopt; if(io->acceptingTextInput) main.input.text_input_silence_everything();