Extract XCUI text editing core and window seam headers
This commit is contained in:
179
engine/src/UI/Text/UITextEditing.cpp
Normal file
179
engine/src/UI/Text/UITextEditing.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include <XCEngine/UI/Text/UITextEditing.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Text {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsUtf8ContinuationByte(unsigned char value) {
|
||||
return (value & 0xC0u) == 0x80u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::size_t CountUtf8Codepoints(const std::string& text) {
|
||||
std::size_t count = 0u;
|
||||
for (unsigned char ch : text) {
|
||||
if (!IsUtf8ContinuationByte(ch)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t AdvanceUtf8Offset(const std::string& text, std::size_t offset) {
|
||||
if (offset >= text.size()) {
|
||||
return text.size();
|
||||
}
|
||||
|
||||
++offset;
|
||||
while (offset < text.size() && IsUtf8ContinuationByte(static_cast<unsigned char>(text[offset]))) {
|
||||
++offset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t RetreatUtf8Offset(const std::string& text, std::size_t offset) {
|
||||
if (offset == 0u || text.empty()) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
offset = (std::min)(offset, text.size());
|
||||
do {
|
||||
--offset;
|
||||
} while (offset > 0u && IsUtf8ContinuationByte(static_cast<unsigned char>(text[offset])));
|
||||
return offset;
|
||||
}
|
||||
|
||||
void AppendUtf8Codepoint(std::string& text, std::uint32_t codepoint) {
|
||||
if (codepoint <= 0x7Fu) {
|
||||
text.push_back(static_cast<char>(codepoint));
|
||||
return;
|
||||
}
|
||||
|
||||
if (codepoint <= 0x7FFu) {
|
||||
text.push_back(static_cast<char>(0xC0u | ((codepoint >> 6u) & 0x1Fu)));
|
||||
text.push_back(static_cast<char>(0x80u | (codepoint & 0x3Fu)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (codepoint >= 0xD800u && codepoint <= 0xDFFFu) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (codepoint <= 0xFFFFu) {
|
||||
text.push_back(static_cast<char>(0xE0u | ((codepoint >> 12u) & 0x0Fu)));
|
||||
text.push_back(static_cast<char>(0x80u | ((codepoint >> 6u) & 0x3Fu)));
|
||||
text.push_back(static_cast<char>(0x80u | (codepoint & 0x3Fu)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (codepoint <= 0x10FFFFu) {
|
||||
text.push_back(static_cast<char>(0xF0u | ((codepoint >> 18u) & 0x07u)));
|
||||
text.push_back(static_cast<char>(0x80u | ((codepoint >> 12u) & 0x3Fu)));
|
||||
text.push_back(static_cast<char>(0x80u | ((codepoint >> 6u) & 0x3Fu)));
|
||||
text.push_back(static_cast<char>(0x80u | (codepoint & 0x3Fu)));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitLines(const std::string& text) {
|
||||
std::vector<std::string> lines = {};
|
||||
std::size_t lineStart = 0u;
|
||||
for (std::size_t index = 0u; index < text.size(); ++index) {
|
||||
if (text[index] != '\n') {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push_back(text.substr(lineStart, index - lineStart));
|
||||
lineStart = index + 1u;
|
||||
}
|
||||
|
||||
lines.push_back(text.substr(lineStart));
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::size_t CountTextLines(const std::string& text) {
|
||||
return SplitLines(text).size();
|
||||
}
|
||||
|
||||
std::size_t CountUtf8CodepointsInRange(
|
||||
const std::string& text,
|
||||
std::size_t beginOffset,
|
||||
std::size_t endOffset) {
|
||||
beginOffset = (std::min)(beginOffset, text.size());
|
||||
endOffset = (std::min)(endOffset, text.size());
|
||||
if (endOffset <= beginOffset) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::size_t count = 0u;
|
||||
std::size_t offset = beginOffset;
|
||||
while (offset < endOffset) {
|
||||
++count;
|
||||
offset = AdvanceUtf8Offset(text, offset);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t FindLineStartOffset(const std::string& text, std::size_t caret) {
|
||||
caret = (std::min)(caret, text.size());
|
||||
while (caret > 0u && text[caret - 1u] != '\n') {
|
||||
--caret;
|
||||
}
|
||||
return caret;
|
||||
}
|
||||
|
||||
std::size_t FindLineEndOffset(const std::string& text, std::size_t caret) {
|
||||
caret = (std::min)(caret, text.size());
|
||||
while (caret < text.size() && text[caret] != '\n') {
|
||||
++caret;
|
||||
}
|
||||
return caret;
|
||||
}
|
||||
|
||||
std::size_t MoveCaretVertically(const std::string& text, std::size_t caret, int lineDelta) {
|
||||
if (lineDelta == 0 || text.empty()) {
|
||||
return (std::min)(caret, text.size());
|
||||
}
|
||||
|
||||
const std::size_t currentLineStart = FindLineStartOffset(text, caret);
|
||||
const std::size_t currentLineEnd = FindLineEndOffset(text, caret);
|
||||
const std::size_t column = CountUtf8CodepointsInRange(text, currentLineStart, caret);
|
||||
|
||||
if (lineDelta < 0) {
|
||||
if (currentLineStart == 0u) {
|
||||
return caret;
|
||||
}
|
||||
|
||||
const std::size_t previousLineEnd = currentLineStart - 1u;
|
||||
const std::size_t previousLineStart = FindLineStartOffset(text, previousLineEnd);
|
||||
const std::size_t previousLineLength =
|
||||
CountUtf8CodepointsInRange(text, previousLineStart, previousLineEnd);
|
||||
std::size_t offset = previousLineStart;
|
||||
for (std::size_t step = 0u; step < (std::min)(column, previousLineLength); ++step) {
|
||||
offset = AdvanceUtf8Offset(text, offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (currentLineEnd >= text.size()) {
|
||||
return caret;
|
||||
}
|
||||
|
||||
const std::size_t nextLineStart = currentLineEnd + 1u;
|
||||
const std::size_t nextLineEnd = FindLineEndOffset(text, nextLineStart);
|
||||
const std::size_t nextLineLength =
|
||||
CountUtf8CodepointsInRange(text, nextLineStart, nextLineEnd);
|
||||
std::size_t offset = nextLineStart;
|
||||
for (std::size_t step = 0u; step < (std::min)(column, nextLineLength); ++step) {
|
||||
offset = AdvanceUtf8Offset(text, offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
} // namespace Text
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user