Extract XCUI text input controller to common layer
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <XCEngine/UI/Style/StyleResolver.h>
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Text/UITextEditing.h>
|
||||
#include <XCEngine/UI/Text/UITextInputController.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -107,8 +108,7 @@ struct RuntimeBuildContext {
|
||||
std::unordered_map<std::string, std::size_t> nodeIndexByKey = {};
|
||||
std::unordered_map<UIElementId, std::size_t> nodeIndexById = {};
|
||||
std::unordered_map<std::string, bool> toggleStates = {};
|
||||
std::unordered_map<std::string, std::string> textFieldValues = {};
|
||||
std::unordered_map<std::string, std::size_t> textFieldCarets = {};
|
||||
std::unordered_map<std::string, UIText::UITextInputState> textInputStates = {};
|
||||
UIElementId toggleButtonId = 0;
|
||||
UIElementId registeredShortcutOwnerId = 0;
|
||||
UIElementId armedElementId = 0;
|
||||
@@ -780,17 +780,17 @@ std::string BuildNodeDisplayText(
|
||||
std::string(diagnosticsEnabled ? "on" : "off");
|
||||
}
|
||||
if (node.elementKey == "promptMeta") {
|
||||
const auto promptIt = state.textFieldValues.find("agentPrompt");
|
||||
const auto promptIt = state.textInputStates.find("agentPrompt");
|
||||
const std::string& promptValue =
|
||||
promptIt != state.textFieldValues.end() ? promptIt->second : node.staticText;
|
||||
promptIt != state.textInputStates.end() ? promptIt->second.value : node.staticText;
|
||||
return "Single-line input, Enter submits, " +
|
||||
std::to_string(static_cast<unsigned long long>(UIText::CountUtf8Codepoints(promptValue))) +
|
||||
" chars";
|
||||
}
|
||||
if (node.elementKey == "notesMeta") {
|
||||
const auto notesIt = state.textFieldValues.find("sessionNotes");
|
||||
const auto notesIt = state.textInputStates.find("sessionNotes");
|
||||
const std::string& notesValue =
|
||||
notesIt != state.textFieldValues.end() ? notesIt->second : node.staticText;
|
||||
notesIt != state.textInputStates.end() ? notesIt->second.value : node.staticText;
|
||||
return "Multiline input, click caret, Tab indent, " +
|
||||
std::to_string(static_cast<unsigned long long>(UIText::CountTextLines(notesValue))) +
|
||||
" lines";
|
||||
@@ -951,32 +951,30 @@ void EnsureTextInputStateInitialized(RuntimeBuildContext& state, const DemoNode&
|
||||
}
|
||||
|
||||
const std::string stateKey = ResolveTextInputStateKey(node);
|
||||
auto valueIt = state.textFieldValues.find(stateKey);
|
||||
if (valueIt == state.textFieldValues.end()) {
|
||||
valueIt = state.textFieldValues
|
||||
.emplace(stateKey, GetNodeAttribute(node, "value"))
|
||||
auto textInputIt = state.textInputStates.find(stateKey);
|
||||
if (textInputIt == state.textInputStates.end()) {
|
||||
UIText::UITextInputState textInput = {};
|
||||
textInput.value = GetNodeAttribute(node, "value");
|
||||
textInput.caret = textInput.value.size();
|
||||
textInputIt = state.textInputStates
|
||||
.emplace(stateKey, std::move(textInput))
|
||||
.first;
|
||||
}
|
||||
|
||||
auto caretIt = state.textFieldCarets.find(stateKey);
|
||||
if (caretIt == state.textFieldCarets.end()) {
|
||||
caretIt = state.textFieldCarets.emplace(stateKey, valueIt->second.size()).first;
|
||||
}
|
||||
|
||||
caretIt->second = (std::min)(caretIt->second, valueIt->second.size());
|
||||
UIText::ClampCaret(textInputIt->second);
|
||||
}
|
||||
|
||||
std::string ResolveTextInputValue(RuntimeBuildContext& state, const DemoNode& node) {
|
||||
EnsureTextInputStateInitialized(state, node);
|
||||
return state.textFieldValues[ResolveTextInputStateKey(node)];
|
||||
return state.textInputStates[ResolveTextInputStateKey(node)].value;
|
||||
}
|
||||
|
||||
std::size_t ResolveTextInputCaret(RuntimeBuildContext& state, const DemoNode& node) {
|
||||
EnsureTextInputStateInitialized(state, node);
|
||||
const std::string stateKey = ResolveTextInputStateKey(node);
|
||||
std::size_t& caret = state.textFieldCarets[stateKey];
|
||||
caret = (std::min)(caret, state.textFieldValues[stateKey].size());
|
||||
return caret;
|
||||
UIText::UITextInputState& textInput = state.textInputStates[stateKey];
|
||||
UIText::ClampCaret(textInput);
|
||||
return textInput.caret;
|
||||
}
|
||||
|
||||
DemoNode* TryGetNodeByElementId(RuntimeBuildContext& state, UIElementId elementId) {
|
||||
@@ -1036,8 +1034,11 @@ void SetTextInputCaretFromPoint(
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureTextInputStateInitialized(state, *node);
|
||||
const std::string stateKey = ResolveTextInputStateKey(*node);
|
||||
state.textFieldCarets[stateKey] = FindCaretOffsetFromPoint(state, *node, point);
|
||||
UIText::UITextInputState& textInput = state.textInputStates[stateKey];
|
||||
textInput.caret = FindCaretOffsetFromPoint(state, *node, point);
|
||||
UIText::ClampCaret(textInput);
|
||||
}
|
||||
|
||||
bool HandleTextInputCharacterInput(
|
||||
@@ -1055,18 +1056,11 @@ bool HandleTextInputCharacterInput(
|
||||
|
||||
EnsureTextInputStateInitialized(state, *node);
|
||||
const std::string stateKey = ResolveTextInputStateKey(*node);
|
||||
std::string& value = state.textFieldValues[stateKey];
|
||||
std::size_t& caret = state.textFieldCarets[stateKey];
|
||||
caret = (std::min)(caret, value.size());
|
||||
|
||||
std::string encoded = {};
|
||||
UIText::AppendUtf8Codepoint(encoded, character);
|
||||
if (encoded.empty()) {
|
||||
UIText::UITextInputState& textInput = state.textInputStates[stateKey];
|
||||
if (!UIText::InsertCharacter(textInput, character)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value.insert(caret, encoded);
|
||||
caret += encoded.size();
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
return true;
|
||||
}
|
||||
@@ -1083,103 +1077,24 @@ bool HandleTextInputKeyDown(
|
||||
|
||||
EnsureTextInputStateInitialized(state, *node);
|
||||
const std::string stateKey = ResolveTextInputStateKey(*node);
|
||||
std::string& value = state.textFieldValues[stateKey];
|
||||
std::size_t& caret = state.textFieldCarets[stateKey];
|
||||
caret = (std::min)(caret, value.size());
|
||||
UIText::UITextInputState& textInput = state.textInputStates[stateKey];
|
||||
UIText::UITextInputOptions options = {};
|
||||
options.multiline = IsTextAreaNode(*node);
|
||||
options.tabWidth = kTextAreaTabWidth;
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Backspace)) {
|
||||
if (caret > 0u) {
|
||||
const std::size_t previousCaret = UIText::RetreatUtf8Offset(value, caret);
|
||||
value.erase(previousCaret, caret - previousCaret);
|
||||
caret = previousCaret;
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
}
|
||||
return true;
|
||||
const UIText::UITextInputEditResult result =
|
||||
UIText::HandleKeyDown(textInput, keyCode, inputModifiers, options);
|
||||
if (!result.handled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Delete)) {
|
||||
if (caret < value.size()) {
|
||||
const std::size_t nextCaret = UIText::AdvanceUtf8Offset(value, caret);
|
||||
value.erase(caret, nextCaret - caret);
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Left)) {
|
||||
caret = UIText::RetreatUtf8Offset(value, caret);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Right)) {
|
||||
caret = UIText::AdvanceUtf8Offset(value, caret);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Up) && IsTextAreaNode(*node)) {
|
||||
caret = UIText::MoveCaretVertically(value, caret, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Down) && IsTextAreaNode(*node)) {
|
||||
caret = UIText::MoveCaretVertically(value, caret, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Home)) {
|
||||
caret = IsTextAreaNode(*node)
|
||||
? UIText::FindLineStartOffset(value, caret)
|
||||
: 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::End)) {
|
||||
caret = IsTextAreaNode(*node)
|
||||
? UIText::FindLineEndOffset(value, caret)
|
||||
: value.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Enter)) {
|
||||
if (IsTextAreaNode(*node)) {
|
||||
value.insert(caret, "\n");
|
||||
++caret;
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result.submitRequested) {
|
||||
state.lastCommandId = "demo.text.submit." + stateKey;
|
||||
return true;
|
||||
} else if (result.valueChanged) {
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
}
|
||||
|
||||
if (keyCode == static_cast<std::int32_t>(KeyCode::Tab) &&
|
||||
IsTextAreaNode(*node) &&
|
||||
!inputModifiers.control &&
|
||||
!inputModifiers.alt &&
|
||||
!inputModifiers.super) {
|
||||
if (inputModifiers.shift) {
|
||||
const std::size_t lineStart = UIText::FindLineStartOffset(value, caret);
|
||||
std::size_t removed = 0u;
|
||||
while (removed < kTextAreaTabWidth &&
|
||||
lineStart + removed < value.size() &&
|
||||
value[lineStart + removed] == ' ') {
|
||||
++removed;
|
||||
}
|
||||
|
||||
if (removed > 0u) {
|
||||
value.erase(lineStart, removed);
|
||||
caret = caret >= lineStart + removed ? caret - removed : lineStart;
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
}
|
||||
} else {
|
||||
value.insert(caret, std::string(kTextAreaTabWidth, ' '));
|
||||
caret += kTextAreaTabWidth;
|
||||
state.lastCommandId = "demo.text.edit." + stateKey;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string BuildActivationCommandId(const DemoNode& node) {
|
||||
|
||||
Reference in New Issue
Block a user