Extract XCUI text input controller to common layer

This commit is contained in:
2026-04-05 06:33:06 +08:00
parent b4c95e4085
commit 6159eef3af
7 changed files with 354 additions and 123 deletions

View File

@@ -0,0 +1,156 @@
#include <XCEngine/UI/Text/UITextInputController.h>
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/Text/UITextEditing.h>
#include <algorithm>
namespace XCEngine {
namespace UI {
namespace Text {
namespace {
using XCEngine::Input::KeyCode;
} // namespace
void ClampCaret(UITextInputState& state) {
state.caret = (std::min)(state.caret, state.value.size());
}
bool InsertCharacter(UITextInputState& state, std::uint32_t character) {
if (character < 32u || character == 127u) {
return false;
}
std::string encoded = {};
AppendUtf8Codepoint(encoded, character);
if (encoded.empty()) {
return false;
}
ClampCaret(state);
state.value.insert(state.caret, encoded);
state.caret += encoded.size();
return true;
}
UITextInputEditResult HandleKeyDown(
UITextInputState& state,
std::int32_t keyCode,
const UIInputModifiers& modifiers,
const UITextInputOptions& options) {
ClampCaret(state);
UITextInputEditResult result = {};
if (keyCode == static_cast<std::int32_t>(KeyCode::Backspace)) {
result.handled = true;
if (state.caret > 0u) {
const std::size_t previousCaret = RetreatUtf8Offset(state.value, state.caret);
state.value.erase(previousCaret, state.caret - previousCaret);
state.caret = previousCaret;
result.valueChanged = true;
}
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Delete)) {
result.handled = true;
if (state.caret < state.value.size()) {
const std::size_t nextCaret = AdvanceUtf8Offset(state.value, state.caret);
state.value.erase(state.caret, nextCaret - state.caret);
result.valueChanged = true;
}
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Left)) {
result.handled = true;
state.caret = RetreatUtf8Offset(state.value, state.caret);
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Right)) {
result.handled = true;
state.caret = AdvanceUtf8Offset(state.value, state.caret);
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Up) && options.multiline) {
result.handled = true;
state.caret = MoveCaretVertically(state.value, state.caret, -1);
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Down) && options.multiline) {
result.handled = true;
state.caret = MoveCaretVertically(state.value, state.caret, 1);
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Home)) {
result.handled = true;
state.caret = options.multiline
? FindLineStartOffset(state.value, state.caret)
: 0u;
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::End)) {
result.handled = true;
state.caret = options.multiline
? FindLineEndOffset(state.value, state.caret)
: state.value.size();
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Enter)) {
result.handled = true;
if (options.multiline) {
state.value.insert(state.caret, "\n");
++state.caret;
result.valueChanged = true;
} else {
result.submitRequested = true;
}
return result;
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Tab) &&
options.multiline &&
!modifiers.control &&
!modifiers.alt &&
!modifiers.super) {
result.handled = true;
if (modifiers.shift) {
const std::size_t lineStart = FindLineStartOffset(state.value, state.caret);
std::size_t removed = 0u;
while (removed < options.tabWidth &&
lineStart + removed < state.value.size() &&
state.value[lineStart + removed] == ' ') {
++removed;
}
if (removed > 0u) {
state.value.erase(lineStart, removed);
state.caret = state.caret >= lineStart + removed ? state.caret - removed : lineStart;
result.valueChanged = true;
}
} else {
state.value.insert(state.caret, std::string(options.tabWidth, ' '));
state.caret += options.tabWidth;
result.valueChanged = true;
}
return result;
}
return result;
}
} // namespace Text
} // namespace UI
} // namespace XCEngine