Extract XCUI text editing core and window seam headers

This commit is contained in:
2026-04-05 06:23:49 +08:00
parent da85109a31
commit b4c95e4085
9 changed files with 446 additions and 197 deletions

View 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