Extract XCUI text input controller to common layer
This commit is contained in:
@@ -517,7 +517,9 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIShortcutRegistry.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Input/UIInputDispatcher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Text/UITextEditing.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Text/UITextInputController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Text/UITextEditing.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Text/UITextInputController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenTypes.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenDocumentHost.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenPlayer.h
|
||||
|
||||
39
engine/include/XCEngine/UI/Text/UITextInputController.h
Normal file
39
engine/include/XCEngine/UI/Text/UITextInputController.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Text {
|
||||
|
||||
struct UITextInputState {
|
||||
std::string value = {};
|
||||
std::size_t caret = 0u;
|
||||
};
|
||||
|
||||
struct UITextInputOptions {
|
||||
bool multiline = false;
|
||||
std::size_t tabWidth = 4u;
|
||||
};
|
||||
|
||||
struct UITextInputEditResult {
|
||||
bool handled = false;
|
||||
bool valueChanged = false;
|
||||
bool submitRequested = false;
|
||||
};
|
||||
|
||||
void ClampCaret(UITextInputState& state);
|
||||
bool InsertCharacter(UITextInputState& state, std::uint32_t character);
|
||||
UITextInputEditResult HandleKeyDown(
|
||||
UITextInputState& state,
|
||||
std::int32_t keyCode,
|
||||
const UIInputModifiers& modifiers,
|
||||
const UITextInputOptions& options = {});
|
||||
|
||||
} // namespace Text
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
156
engine/src/UI/Text/UITextInputController.cpp
Normal file
156
engine/src/UI/Text/UITextInputController.cpp
Normal 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
|
||||
Reference in New Issue
Block a user