Files
XCEngine/new_editor/src/XCUIBackend/XCUIInputBridge.cpp

588 lines
22 KiB
C++
Raw Normal View History

2026-04-05 04:55:25 +08:00
#include "XCUIBackend/XCUIInputBridge.h"
#include <algorithm>
#include <chrono>
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
namespace {
using XCEngine::Input::KeyCode;
using XCEngine::UI::UIInputEvent;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIPoint;
UIPoint Subtract(const UIPoint& lhs, const UIPoint& rhs) {
return UIPoint(lhs.x - rhs.x, lhs.y - rhs.y);
}
UIPointerButton ToPointerButton(std::size_t index) {
switch (index) {
case 0u:
return UIPointerButton::Left;
case 1u:
return UIPointerButton::Right;
case 2u:
return UIPointerButton::Middle;
case 3u:
return UIPointerButton::X1;
case 4u:
return UIPointerButton::X2;
default:
return UIPointerButton::None;
}
}
void AppendEvent(std::vector<UIInputEvent>& events, const UIInputEvent& event) {
events.push_back(event);
}
UIInputEvent MakeBaseEvent(
UIInputEventType type,
const XCUIInputBridgeFrameSnapshot& snapshot) {
UIInputEvent event = {};
event.type = type;
event.position = snapshot.pointerPosition;
event.timestampNanoseconds = snapshot.timestampNanoseconds;
event.modifiers = snapshot.modifiers;
return event;
}
void AppendUniqueKeyCode(std::vector<std::int32_t>& keyCodes, std::int32_t keyCode) {
if (keyCode == 0) {
return;
}
const auto it = std::find(keyCodes.begin(), keyCodes.end(), keyCode);
if (it == keyCodes.end()) {
keyCodes.push_back(keyCode);
}
}
std::uint64_t GetTimestampNanoseconds() {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count());
}
UIPoint MakeClientPoint(LPARAM lParam) {
return UIPoint(
static_cast<float>(static_cast<short>(LOWORD(static_cast<DWORD_PTR>(lParam)))),
static_cast<float>(static_cast<short>(HIWORD(static_cast<DWORD_PTR>(lParam)))));
}
std::int32_t TranslateVirtualKeyToXCUIKeyCode(WPARAM wParam, LPARAM lParam) {
switch (static_cast<UINT>(wParam)) {
case 'A': return static_cast<std::int32_t>(KeyCode::A);
case 'B': return static_cast<std::int32_t>(KeyCode::B);
case 'C': return static_cast<std::int32_t>(KeyCode::C);
case 'D': return static_cast<std::int32_t>(KeyCode::D);
case 'E': return static_cast<std::int32_t>(KeyCode::E);
case 'F': return static_cast<std::int32_t>(KeyCode::F);
case 'G': return static_cast<std::int32_t>(KeyCode::G);
case 'H': return static_cast<std::int32_t>(KeyCode::H);
case 'I': return static_cast<std::int32_t>(KeyCode::I);
case 'J': return static_cast<std::int32_t>(KeyCode::J);
case 'K': return static_cast<std::int32_t>(KeyCode::K);
case 'L': return static_cast<std::int32_t>(KeyCode::L);
case 'M': return static_cast<std::int32_t>(KeyCode::M);
case 'N': return static_cast<std::int32_t>(KeyCode::N);
case 'O': return static_cast<std::int32_t>(KeyCode::O);
case 'P': return static_cast<std::int32_t>(KeyCode::P);
case 'Q': return static_cast<std::int32_t>(KeyCode::Q);
case 'R': return static_cast<std::int32_t>(KeyCode::R);
case 'S': return static_cast<std::int32_t>(KeyCode::S);
case 'T': return static_cast<std::int32_t>(KeyCode::T);
case 'U': return static_cast<std::int32_t>(KeyCode::U);
case 'V': return static_cast<std::int32_t>(KeyCode::V);
case 'W': return static_cast<std::int32_t>(KeyCode::W);
case 'X': return static_cast<std::int32_t>(KeyCode::X);
case 'Y': return static_cast<std::int32_t>(KeyCode::Y);
case 'Z': return static_cast<std::int32_t>(KeyCode::Z);
case '0': return static_cast<std::int32_t>(KeyCode::Zero);
case '1': return static_cast<std::int32_t>(KeyCode::One);
case '2': return static_cast<std::int32_t>(KeyCode::Two);
case '3': return static_cast<std::int32_t>(KeyCode::Three);
case '4': return static_cast<std::int32_t>(KeyCode::Four);
case '5': return static_cast<std::int32_t>(KeyCode::Five);
case '6': return static_cast<std::int32_t>(KeyCode::Six);
case '7': return static_cast<std::int32_t>(KeyCode::Seven);
case '8': return static_cast<std::int32_t>(KeyCode::Eight);
case '9': return static_cast<std::int32_t>(KeyCode::Nine);
case VK_SPACE: return static_cast<std::int32_t>(KeyCode::Space);
case VK_TAB: return static_cast<std::int32_t>(KeyCode::Tab);
case VK_RETURN: return static_cast<std::int32_t>(KeyCode::Enter);
case VK_ESCAPE: return static_cast<std::int32_t>(KeyCode::Escape);
case VK_SHIFT: {
const UINT scanCode = (static_cast<UINT>(lParam) >> 16) & 0xFFu;
const UINT leftShiftScanCode = MapVirtualKeyW(VK_LSHIFT, MAPVK_VK_TO_VSC);
return static_cast<std::int32_t>(
scanCode == leftShiftScanCode ? KeyCode::LeftShift : KeyCode::RightShift);
}
case VK_CONTROL:
return static_cast<std::int32_t>(
(static_cast<UINT>(lParam) & 0x01000000u) != 0u ? KeyCode::RightCtrl : KeyCode::LeftCtrl);
case VK_MENU:
return static_cast<std::int32_t>(
(static_cast<UINT>(lParam) & 0x01000000u) != 0u ? KeyCode::RightAlt : KeyCode::LeftAlt);
case VK_UP: return static_cast<std::int32_t>(KeyCode::Up);
case VK_DOWN: return static_cast<std::int32_t>(KeyCode::Down);
case VK_LEFT: return static_cast<std::int32_t>(KeyCode::Left);
case VK_RIGHT: return static_cast<std::int32_t>(KeyCode::Right);
case VK_HOME: return static_cast<std::int32_t>(KeyCode::Home);
case VK_END: return static_cast<std::int32_t>(KeyCode::End);
case VK_PRIOR: return static_cast<std::int32_t>(KeyCode::PageUp);
case VK_NEXT: return static_cast<std::int32_t>(KeyCode::PageDown);
case VK_DELETE: return static_cast<std::int32_t>(KeyCode::Delete);
case VK_BACK: return static_cast<std::int32_t>(KeyCode::Backspace);
case VK_F1: return static_cast<std::int32_t>(KeyCode::F1);
case VK_F2: return static_cast<std::int32_t>(KeyCode::F2);
case VK_F3: return static_cast<std::int32_t>(KeyCode::F3);
case VK_F4: return static_cast<std::int32_t>(KeyCode::F4);
case VK_F5: return static_cast<std::int32_t>(KeyCode::F5);
case VK_F6: return static_cast<std::int32_t>(KeyCode::F6);
case VK_F7: return static_cast<std::int32_t>(KeyCode::F7);
case VK_F8: return static_cast<std::int32_t>(KeyCode::F8);
case VK_F9: return static_cast<std::int32_t>(KeyCode::F9);
case VK_F10: return static_cast<std::int32_t>(KeyCode::F10);
case VK_F11: return static_cast<std::int32_t>(KeyCode::F11);
case VK_F12: return static_cast<std::int32_t>(KeyCode::F12);
case VK_OEM_MINUS: return static_cast<std::int32_t>(KeyCode::Minus);
case VK_OEM_PLUS: return static_cast<std::int32_t>(KeyCode::Equals);
case VK_OEM_4: return static_cast<std::int32_t>(KeyCode::BracketLeft);
case VK_OEM_6: return static_cast<std::int32_t>(KeyCode::BracketRight);
case VK_OEM_1: return static_cast<std::int32_t>(KeyCode::Semicolon);
case VK_OEM_7: return static_cast<std::int32_t>(KeyCode::Quote);
case VK_OEM_COMMA: return static_cast<std::int32_t>(KeyCode::Comma);
case VK_OEM_PERIOD: return static_cast<std::int32_t>(KeyCode::Period);
case VK_OEM_2: return static_cast<std::int32_t>(KeyCode::Slash);
case VK_OEM_5: return static_cast<std::int32_t>(KeyCode::Backslash);
case VK_OEM_3: return static_cast<std::int32_t>(KeyCode::Backtick);
default:
return static_cast<std::int32_t>(KeyCode::None);
}
}
} // namespace
const XCUIInputBridgeKeyState* XCUIInputBridgeFrameSnapshot::FindKeyState(std::int32_t keyCode) const {
for (const XCUIInputBridgeKeyState& keyState : keys) {
if (keyState.keyCode == keyCode) {
return &keyState;
}
}
return nullptr;
}
bool XCUIInputBridgeFrameSnapshot::IsKeyDown(std::int32_t keyCode) const {
const XCUIInputBridgeKeyState* keyState = FindKeyState(keyCode);
return keyState != nullptr && keyState->down;
}
bool XCUIInputBridgeFrameDelta::HasEvents() const {
return !events.empty();
}
bool XCUIInputBridgeFrameDelta::HasPointerActivity() const {
return pointer.moved ||
pointer.entered ||
pointer.left ||
pointer.wheelDelta.x != 0.0f ||
pointer.wheelDelta.y != 0.0f ||
std::any_of(pointer.pressed.begin(), pointer.pressed.end(), [](bool value) { return value; }) ||
std::any_of(pointer.released.begin(), pointer.released.end(), [](bool value) { return value; });
}
bool XCUIInputBridgeFrameDelta::HasKeyboardActivity() const {
return !keyboard.pressedKeys.empty() ||
!keyboard.releasedKeys.empty() ||
!keyboard.repeatedKeys.empty() ||
!keyboard.characters.empty();
}
bool XCUIInputBridgeFrameDelta::HasEventType(UI::UIInputEventType type) const {
return std::any_of(
events.begin(),
events.end(),
[type](const UIInputEvent& event) {
return event.type == type;
});
}
void XCUIInputBridge::Reset() {
m_hasBaseline = false;
m_baseline = {};
}
void XCUIInputBridge::Prime(const XCUIInputBridgeFrameSnapshot& snapshot) {
m_baseline = snapshot;
m_hasBaseline = true;
}
XCUIInputBridgeFrameDelta XCUIInputBridge::Translate(const XCUIInputBridgeFrameSnapshot& current) {
const XCUIInputBridgeFrameSnapshot previous = m_hasBaseline ? m_baseline : XCUIInputBridgeFrameSnapshot();
XCUIInputBridgeFrameDelta delta = Translate(previous, current);
m_baseline = current;
m_hasBaseline = true;
return delta;
}
XCUIInputBridgeFrameDelta XCUIInputBridge::Translate(
const XCUIInputBridgeFrameSnapshot& previous,
const XCUIInputBridgeFrameSnapshot& current) {
XCUIInputBridgeFrameDelta delta = {};
delta.state = current;
delta.pointer.wheelDelta = current.wheelDelta;
delta.keyboard.characters = current.characters;
delta.focusGained = current.windowFocused && !previous.windowFocused;
delta.focusLost = previous.windowFocused && !current.windowFocused;
if (delta.focusGained) {
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::FocusGained, current));
}
if (delta.focusLost) {
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::FocusLost, current));
}
delta.pointer.entered = current.pointerInside && !previous.pointerInside;
delta.pointer.left = previous.pointerInside && !current.pointerInside;
if (previous.pointerInside && current.pointerInside) {
delta.pointer.delta = Subtract(current.pointerPosition, previous.pointerPosition);
}
const bool pointerMovedInside =
current.pointerInside &&
(delta.pointer.entered ||
previous.pointerPosition.x != current.pointerPosition.x ||
previous.pointerPosition.y != current.pointerPosition.y);
delta.pointer.moved = pointerMovedInside;
if (delta.pointer.entered) {
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::PointerEnter, current));
}
if (delta.pointer.moved) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerMove, current);
event.delta = delta.pointer.delta;
AppendEvent(delta.events, event);
}
for (std::size_t index = 0; index < XCUIInputBridgeFrameSnapshot::PointerButtonCount; ++index) {
const bool wasDown = previous.pointerButtonsDown[index];
const bool isDown = current.pointerButtonsDown[index];
if (isDown == wasDown) {
continue;
}
UIInputEvent event = MakeBaseEvent(
isDown ? UIInputEventType::PointerButtonDown : UIInputEventType::PointerButtonUp,
current);
event.pointerButton = ToPointerButton(index);
if (isDown) {
delta.pointer.pressed[index] = true;
} else {
delta.pointer.released[index] = true;
}
AppendEvent(delta.events, event);
}
if (current.wheelDelta.x != 0.0f || current.wheelDelta.y != 0.0f) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerWheel, current);
event.delta = current.wheelDelta;
event.wheelDelta = current.wheelDelta.y != 0.0f ? current.wheelDelta.y : current.wheelDelta.x;
AppendEvent(delta.events, event);
}
if (delta.pointer.left) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerLeave, current);
if (previous.pointerInside) {
event.delta = Subtract(current.pointerPosition, previous.pointerPosition);
}
AppendEvent(delta.events, event);
}
std::vector<std::int32_t> keyCodes = {};
keyCodes.reserve(previous.keys.size() + current.keys.size());
for (const XCUIInputBridgeKeyState& keyState : previous.keys) {
AppendUniqueKeyCode(keyCodes, keyState.keyCode);
}
for (const XCUIInputBridgeKeyState& keyState : current.keys) {
AppendUniqueKeyCode(keyCodes, keyState.keyCode);
}
std::sort(keyCodes.begin(), keyCodes.end());
for (std::int32_t keyCode : keyCodes) {
const XCUIInputBridgeKeyState* previousKeyState = previous.FindKeyState(keyCode);
const XCUIInputBridgeKeyState* currentKeyState = current.FindKeyState(keyCode);
const bool wasDown = previousKeyState != nullptr && previousKeyState->down;
const bool isDown = currentKeyState != nullptr && currentKeyState->down;
if (isDown && !wasDown) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyDown, current);
event.keyCode = keyCode;
delta.keyboard.pressedKeys.push_back(keyCode);
AppendEvent(delta.events, event);
continue;
}
if (!isDown && wasDown) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyUp, current);
event.keyCode = keyCode;
delta.keyboard.releasedKeys.push_back(keyCode);
AppendEvent(delta.events, event);
continue;
}
if (isDown && wasDown && currentKeyState != nullptr && currentKeyState->repeat) {
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyDown, current);
event.keyCode = keyCode;
event.repeat = true;
delta.keyboard.repeatedKeys.push_back(keyCode);
AppendEvent(delta.events, event);
}
}
for (std::uint32_t character : current.characters) {
if (character == 0u) {
continue;
}
UIInputEvent event = MakeBaseEvent(UIInputEventType::Character, current);
event.character = character;
AppendEvent(delta.events, event);
}
return delta;
}
void XCUIWin32InputSource::Reset() {
m_pointerPosition = {};
m_pointerInside = false;
m_windowFocused = false;
m_trackingMouseLeave = false;
m_pointerButtonsDown.fill(false);
m_wheelDelta = {};
m_modifiers = {};
m_keyStates.clear();
m_characters.clear();
}
void XCUIWin32InputSource::HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SETFOCUS:
m_windowFocused = true;
UpdateModifierState();
return;
case WM_KILLFOCUS:
m_windowFocused = false;
m_pointerInside = false;
m_trackingMouseLeave = false;
m_pointerButtonsDown.fill(false);
m_keyStates.clear();
UpdateModifierState();
return;
case WM_MOUSEMOVE: {
m_pointerPosition = MakeClientPoint(lParam);
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, true);
UpdateModifierState();
if (hwnd != nullptr && !m_trackingMouseLeave) {
TRACKMOUSEEVENT trackMouseEvent = {};
trackMouseEvent.cbSize = sizeof(trackMouseEvent);
trackMouseEvent.dwFlags = TME_LEAVE;
trackMouseEvent.hwndTrack = hwnd;
m_trackingMouseLeave = TrackMouseEvent(&trackMouseEvent) == TRUE;
}
return;
}
case WM_MOUSELEAVE:
m_trackingMouseLeave = false;
m_pointerInside = false;
return;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP: {
m_pointerPosition = MakeClientPoint(lParam);
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, true);
UpdateModifierState();
switch (message) {
case WM_LBUTTONDOWN: SetPointerButtonDown(0u, true); return;
case WM_LBUTTONUP: SetPointerButtonDown(0u, false); return;
case WM_RBUTTONDOWN: SetPointerButtonDown(1u, true); return;
case WM_RBUTTONUP: SetPointerButtonDown(1u, false); return;
case WM_MBUTTONDOWN: SetPointerButtonDown(2u, true); return;
case WM_MBUTTONUP: SetPointerButtonDown(2u, false); return;
case WM_XBUTTONDOWN:
SetPointerButtonDown(HIWORD(wParam) == XBUTTON1 ? 3u : 4u, true);
return;
case WM_XBUTTONUP:
SetPointerButtonDown(HIWORD(wParam) == XBUTTON1 ? 3u : 4u, false);
return;
default:
return;
}
}
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL: {
POINT screenPoint = {
static_cast<LONG>(static_cast<short>(LOWORD(static_cast<DWORD_PTR>(lParam)))),
static_cast<LONG>(static_cast<short>(HIWORD(static_cast<DWORD_PTR>(lParam))))
};
if (hwnd != nullptr && ScreenToClient(hwnd, &screenPoint)) {
m_pointerPosition = UIPoint(static_cast<float>(screenPoint.x), static_cast<float>(screenPoint.y));
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, false);
}
const float wheelStep = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam)) / static_cast<float>(WHEEL_DELTA);
if (message == WM_MOUSEHWHEEL) {
m_wheelDelta.x += wheelStep;
} else {
m_wheelDelta.y += wheelStep;
}
UpdateModifierState();
return;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN: {
UpdateModifierState();
const std::int32_t keyCode = TranslateVirtualKeyToXCUIKeyCode(wParam, lParam);
const bool repeat = (static_cast<UINT>(lParam) & 0x40000000u) != 0u;
SetKeyDown(keyCode, true, repeat);
UpdateModifierState();
return;
}
case WM_KEYUP:
case WM_SYSKEYUP:
SetKeyDown(TranslateVirtualKeyToXCUIKeyCode(wParam, lParam), false, false);
UpdateModifierState();
return;
case WM_CHAR:
case WM_SYSCHAR:
if (wParam != 0u) {
m_characters.push_back(static_cast<std::uint32_t>(wParam));
}
return;
default:
return;
}
}
void XCUIWin32InputSource::ClearFrameTransients() {
m_wheelDelta = {};
m_characters.clear();
for (XCUIInputBridgeKeyState& keyState : m_keyStates) {
keyState.repeat = false;
}
}
XCUIInputBridgeFrameSnapshot XCUIWin32InputSource::CaptureSnapshot(
const XCUIInputBridgeCaptureOptions& options) const {
XCUIInputBridgeFrameSnapshot snapshot = {};
snapshot.pointerPosition = UIPoint(
m_pointerPosition.x - options.pointerOffset.x,
m_pointerPosition.y - options.pointerOffset.y);
snapshot.pointerInside = options.hasPointerInsideOverride ? options.pointerInsideOverride : m_pointerInside;
snapshot.pointerButtonsDown = m_pointerButtonsDown;
snapshot.wheelDelta = m_wheelDelta;
snapshot.modifiers = m_modifiers;
snapshot.windowFocused = options.windowFocused && m_windowFocused;
snapshot.wantCaptureMouse = false;
snapshot.wantCaptureKeyboard = false;
snapshot.wantTextInput = false;
snapshot.timestampNanoseconds =
options.timestampNanoseconds != 0u ? options.timestampNanoseconds : GetTimestampNanoseconds();
snapshot.keys = m_keyStates;
snapshot.characters = m_characters;
return snapshot;
}
void XCUIWin32InputSource::UpdateModifierState() {
m_modifiers.shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
m_modifiers.control = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
m_modifiers.alt = (GetKeyState(VK_MENU) & 0x8000) != 0;
m_modifiers.super =
(GetKeyState(VK_LWIN) & 0x8000) != 0 ||
(GetKeyState(VK_RWIN) & 0x8000) != 0;
}
void XCUIWin32InputSource::UpdatePointerInside(
HWND hwnd,
float x,
float y,
bool assumeInsideIfUnknown) {
if (hwnd == nullptr) {
m_pointerInside = assumeInsideIfUnknown;
return;
}
RECT clientRect = {};
if (!GetClientRect(hwnd, &clientRect)) {
m_pointerInside = assumeInsideIfUnknown;
return;
}
m_pointerInside =
x >= static_cast<float>(clientRect.left) &&
y >= static_cast<float>(clientRect.top) &&
x < static_cast<float>(clientRect.right) &&
y < static_cast<float>(clientRect.bottom);
}
void XCUIWin32InputSource::SetPointerButtonDown(std::size_t index, bool down) {
if (index < m_pointerButtonsDown.size()) {
m_pointerButtonsDown[index] = down;
}
}
void XCUIWin32InputSource::SetKeyDown(std::int32_t keyCode, bool down, bool repeat) {
if (keyCode == 0) {
return;
}
const auto it = std::find_if(
m_keyStates.begin(),
m_keyStates.end(),
[keyCode](const XCUIInputBridgeKeyState& state) {
return state.keyCode == keyCode;
});
if (!down) {
if (it != m_keyStates.end()) {
m_keyStates.erase(it);
}
return;
}
if (it != m_keyStates.end()) {
it->down = true;
it->repeat = it->repeat || repeat;
return;
}
XCUIInputBridgeKeyState state = {};
state.keyCode = keyCode;
state.down = true;
state.repeat = repeat;
m_keyStates.push_back(state);
std::sort(
m_keyStates.begin(),
m_keyStates.end(),
[](const XCUIInputBridgeKeyState& lhs, const XCUIInputBridgeKeyState& rhs) {
return lhs.keyCode < rhs.keyCode;
});
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine