Using new RichTextBox instead of CollabTextBox in program. Fixes still needed to work properly
This commit is contained in:
parent
ec68dd9b71
commit
4db5838a64
18 changed files with 157 additions and 1441 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/core/SkCanvas.h>
|
||||
#include <include/core/SkPath.h>
|
||||
#include <src/base/SkUTF.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
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 <typename F>
|
||||
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<bool>();
|
||||
}
|
||||
|
||||
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<SkFontMgr> 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<SkRect>& 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<TextParagraph>::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<TextParagraph>::const_iterator firstP = fLines.begin() + start.fParagraphIndex;
|
||||
const std::vector<TextParagraph>::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 <typename T>
|
||||
static size_t find_first_larger(const std::vector<T>& 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<SkRect>& 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<size_t>& 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<size_t>& 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<size_t>& 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<size_t>& 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<size_t>& 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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/core/SkColor.h>
|
||||
#include <include/core/SkFont.h>
|
||||
#include <include/core/SkFontMgr.h>
|
||||
#include <include/core/SkRefCnt.h>
|
||||
#include <include/core/SkTextBlob.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <Eigen/Dense>
|
||||
|
||||
class SkCanvas;
|
||||
class SkShaper;
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace CollabTextBox {
|
||||
struct TextParagraph {
|
||||
TextParagraph(std::string_view t) : fText(t) {}
|
||||
TextParagraph() {}
|
||||
|
||||
std::string fText;
|
||||
sk_sp<const SkTextBlob> fBlob;
|
||||
std::vector<SkRect> fCursorPos;
|
||||
std::vector<size_t> fLineEndOffsets;
|
||||
std::vector<bool> 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<SkFontMgr> fontMgr);
|
||||
|
||||
struct Text {
|
||||
const std::vector<TextParagraph>& fLines;
|
||||
struct Iterator {
|
||||
std::vector<TextParagraph>::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<TextParagraph> fLines;
|
||||
private:
|
||||
int fWidth = 0;
|
||||
int fHeight = 0;
|
||||
SkFont fFont;
|
||||
sk_sp<SkFontMgr> fFontMgr;
|
||||
bool fNeedsReshape = false;
|
||||
const char* fLocale = "en"; // TODO: make this setable
|
||||
|
||||
void markDirty(TextParagraph*);
|
||||
void reshapeAll();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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 <iostream>
|
||||
#include <limits>
|
||||
|
||||
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<uint32_t>::max();
|
||||
size_t fParagraphIndex = std::numeric_limits<uint32_t>::max();
|
||||
template <typename Archive> 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 <typename Archive> void serialize(Archive& a) {
|
||||
a(pos, selectionBeginPos, selectionEndPos);
|
||||
}
|
||||
bool operator==(const Cursor& o) const = default;
|
||||
};
|
||||
}
|
||||
|
|
@ -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 <iostream>
|
||||
|
||||
#include <include/core/SkFont.h>
|
||||
#include <include/core/SkFontMetrics.h>
|
||||
#include <include/core/SkFontMgr.h>
|
||||
#include <include/core/SkPoint.h>
|
||||
#include <include/core/SkRefCnt.h>
|
||||
#include <include/core/SkScalar.h>
|
||||
#include <include/core/SkString.h>
|
||||
#include <include/core/SkTextBlob.h>
|
||||
#include <include/core/SkTypes.h>
|
||||
#include <include/private/base/SkTFitsIn.h>
|
||||
#include <src/base/SkUTF.h>
|
||||
|
||||
#include <modules/skshaper/include/SkShaper.h>
|
||||
#include <modules/skshaper/include/SkShaper_harfbuzz.h>
|
||||
#include <modules/skshaper/include/SkShaper_skunicode.h>
|
||||
#include <modules/skunicode/include/SkUnicode.h>
|
||||
#include <modules/skunicode/include/SkUnicode_icu.h>
|
||||
|
||||
#include <cfloat>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
sk_sp<SkUnicode> 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<SkTextBlob> 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<size_t>& 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<size_t> 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<int>(info.glyphCount) ? info.glyphCount : INT_MAX;
|
||||
int utf8RangeSize = SkTFitsIn<int>(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<SkTextBlob> 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<float[]> 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<SkFontMgr> fontMgr,
|
||||
const char* locale,
|
||||
float width)
|
||||
{
|
||||
ShapeResult result;
|
||||
if (SkUTF::CountUTF8(utf8Text, textByteLen) < 0) {
|
||||
utf8Text = nullptr;
|
||||
textByteLen = 0;
|
||||
}
|
||||
std::unique_ptr<SkShaper> 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<SkShaper::BiDiRunIterator> 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<SkShaper::TrivialBiDiRunIterator>(kBidiLevelLTR, textByteLen);
|
||||
//}
|
||||
SkASSERT(bidi);
|
||||
|
||||
std::unique_ptr<SkShaper::LanguageRunIterator> language =
|
||||
SkShaper::MakeStdLanguageRunIterator(utf8Text, textByteLen);
|
||||
SkASSERT(language);
|
||||
|
||||
std::unique_ptr<SkShaper::ScriptRunIterator> script =
|
||||
SkShaper::MakeScriptRunIterator(utf8Text, textByteLen, SkSetFourByteTag('Z','z','z','z'));
|
||||
SkASSERT(script);
|
||||
|
||||
std::unique_ptr<SkShaper::FontRunIterator> 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<bool> 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<SkUnicode::Position> positions;
|
||||
if (!unicode->getWords(begin, byteCount, locale, &positions) || byteCount == 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<bool> 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;
|
||||
}
|
||||
|
|
@ -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/core/SkFont.h>
|
||||
#include <include/core/SkTextBlob.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace CollabTextBox {
|
||||
struct ShapeResult {
|
||||
sk_sp<SkTextBlob> blob;
|
||||
std::vector<size_t> lineBreakOffsets;
|
||||
std::vector<SkRect> glyphBounds;
|
||||
std::vector<bool> wordBreaks;
|
||||
int verticalAdvance;
|
||||
};
|
||||
|
||||
ShapeResult shape(const char* ut8text, size_t textByteLen, const SkFont& font, sk_sp<SkFontMgr> fontMgr, const char* locale, float width);
|
||||
std::vector<bool> get_utf8_word_boundaries(const char* begin, size_t byteCount, const char* locale);
|
||||
};
|
||||
|
|
@ -9,16 +9,21 @@
|
|||
#include "../DrawCollision.hpp"
|
||||
#endif
|
||||
|
||||
DrawTextBox::DrawTextBox():
|
||||
textBox(std::make_shared<RichTextBox>()),
|
||||
cursor(std::make_shared<RichTextBox::Cursor>())
|
||||
{}
|
||||
|
||||
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<DrawComponent> 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<DrawComponent>&
|
|||
}
|
||||
|
||||
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<SkColor4f>(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) {
|
||||
|
|
|
|||
|
|
@ -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/core/SkPath.h>
|
||||
#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<float> get_obj_coord_bounds() const override;
|
||||
|
||||
bool textboxUpdate = false;
|
||||
CollabTextBox::Editor textBox;
|
||||
std::shared_ptr<RichTextBox> textBox;
|
||||
std::shared_ptr<RichTextBox::Cursor> cursor;
|
||||
SCollision::BVHContainer<float> collisionTree;
|
||||
#endif
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
#include "../MainProgram.hpp"
|
||||
#include "../DrawData.hpp"
|
||||
#include "Helpers/SCollision.hpp"
|
||||
#include "../CollabTextBox/CollabTextBox.hpp"
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <memory>
|
||||
#include "EditTool.hpp"
|
||||
|
|
@ -59,11 +58,11 @@ void TextBoxEditTool::commit_edit_updates(const std::shared_ptr<DrawComponent>&
|
|||
|
||||
void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptr<DrawComponent>& comp, std::any& prevData) {
|
||||
std::shared_ptr<DrawTextBox> a = std::static_pointer_cast<DrawTextBox>(comp);
|
||||
auto& cur = a->d.cursor;
|
||||
auto& cur = a->cursor;
|
||||
auto& textbox = a->textBox;
|
||||
cur = std::make_shared<RichTextBox::Cursor>();
|
||||
Vector2f textSelectPos = a->get_mouse_pos(drawP);
|
||||
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
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<DrawComponent>& 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<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
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<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#include "../DrawData.hpp"
|
||||
#include "Helpers/MathExtras.hpp"
|
||||
#include "Helpers/SCollision.hpp"
|
||||
#include "../CollabTextBox/CollabTextBox.hpp"
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <memory>
|
||||
#include "EditTool.hpp"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <Helpers/SCollision.hpp>
|
||||
#include <Helpers/Serializers.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "../../RichTextBox/RichTextBox.hpp"
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
|
|
@ -104,6 +105,8 @@ struct UpdateInputData {
|
|||
std::optional<std::string> textOut;
|
||||
} clipboard;
|
||||
|
||||
std::function<void(std::shared_ptr<RichTextBox>, std::shared_ptr<RichTextBox::Cursor>)> richTextBoxEdit;
|
||||
|
||||
SkFont get_font(float fSize) const;
|
||||
|
||||
uint16_t fontSize = 20;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
#include "../../CollabTextBox/CollabTextBox.hpp"
|
||||
#include "../../RichTextBox/RichTextBox.hpp"
|
||||
#include <limits>
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
template <typename T> class TextBox : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, bool updateEveryEdit, const std::function<void(SelectionHelper&)>& 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<float>::max() : bb.width());
|
||||
|
||||
CLAY({
|
||||
.layout = {
|
||||
|
|
@ -24,109 +27,15 @@ template <typename T> 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<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
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<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
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<T> 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<T> dataToAssign = fromStr(textbox.get_string());
|
||||
std::optional<T> 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 <typename T> 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 <typename T> 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<RichTextBox>();
|
||||
textbox->set_font_collection(io.fontCollection);
|
||||
textbox->set_allow_newlines(!singleLine);
|
||||
cur = std::make_shared<RichTextBox::Cursor>();
|
||||
|
||||
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<T> dataToAssign = fromStr(textbox.get_string());
|
||||
std::optional<T> dataToAssign = fromStr(textbox->get_string());
|
||||
if(dataToAssign) {
|
||||
*data = dataToAssign.value();
|
||||
oldData = *data;
|
||||
|
|
@ -207,8 +106,8 @@ template <typename T> class TextBox : public Element {
|
|||
std::function<std::optional<T>(const std::string&)> fromStr;
|
||||
std::function<std::string(const T&)> toStr;
|
||||
|
||||
CollabTextBox::Editor textbox;
|
||||
CollabTextBox::Cursor cur;
|
||||
std::shared_ptr<RichTextBox> textbox;
|
||||
std::shared_ptr<RichTextBox::Cursor> cur;
|
||||
|
||||
SCollision::AABB<float> bb;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 <Helpers/Logger.hpp>
|
||||
#include <Helpers/Networking/NetLibrary.hpp>
|
||||
|
||||
#include "RichTextBox/RichTextBox.hpp"
|
||||
|
||||
std::shared_ptr<RichTextBox> textBox;
|
||||
std::shared_ptr<RichTextBox::Cursor> 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<RichTextBox>();
|
||||
textBoxCursor = std::make_shared<RichTextBox::Cursor>();
|
||||
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::duration<float>>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit);
|
||||
lastFrameTime = std::chrono::steady_clock::now();
|
||||
|
||||
//while(std::chrono::duration_cast<std::chrono::duration<float>>(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<World> 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<World> 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() {
|
||||
|
|
|
|||
|
|
@ -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<skia::textlayout::FontCollection>& 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class RichTextBox {
|
|||
TextPosition pos = {0, 0};
|
||||
TextPosition selectionBeginPos = {0, 0};
|
||||
TextPosition selectionEndPos = {0, 0};
|
||||
template <typename Archive> void serialize(Archive& a) {
|
||||
a(pos, selectionBeginPos, selectionEndPos);
|
||||
}
|
||||
std::optional<float> 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<skia::textlayout::FontCollection> fontCollection;
|
||||
|
||||
struct ParagraphData {
|
||||
|
|
|
|||
|
|
@ -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<RichTextBox> t, std::shared_ptr<RichTextBox::Cursor> 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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue