#include "XCUIBackend/XCUIInputBridge.h" #include #include 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& 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& 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::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count()); } UIPoint MakeClientPoint(LPARAM lParam) { return UIPoint( static_cast(static_cast(LOWORD(static_cast(lParam)))), static_cast(static_cast(HIWORD(static_cast(lParam))))); } std::int32_t TranslateVirtualKeyToXCUIKeyCode(WPARAM wParam, LPARAM lParam) { switch (static_cast(wParam)) { case 'A': return static_cast(KeyCode::A); case 'B': return static_cast(KeyCode::B); case 'C': return static_cast(KeyCode::C); case 'D': return static_cast(KeyCode::D); case 'E': return static_cast(KeyCode::E); case 'F': return static_cast(KeyCode::F); case 'G': return static_cast(KeyCode::G); case 'H': return static_cast(KeyCode::H); case 'I': return static_cast(KeyCode::I); case 'J': return static_cast(KeyCode::J); case 'K': return static_cast(KeyCode::K); case 'L': return static_cast(KeyCode::L); case 'M': return static_cast(KeyCode::M); case 'N': return static_cast(KeyCode::N); case 'O': return static_cast(KeyCode::O); case 'P': return static_cast(KeyCode::P); case 'Q': return static_cast(KeyCode::Q); case 'R': return static_cast(KeyCode::R); case 'S': return static_cast(KeyCode::S); case 'T': return static_cast(KeyCode::T); case 'U': return static_cast(KeyCode::U); case 'V': return static_cast(KeyCode::V); case 'W': return static_cast(KeyCode::W); case 'X': return static_cast(KeyCode::X); case 'Y': return static_cast(KeyCode::Y); case 'Z': return static_cast(KeyCode::Z); case '0': return static_cast(KeyCode::Zero); case '1': return static_cast(KeyCode::One); case '2': return static_cast(KeyCode::Two); case '3': return static_cast(KeyCode::Three); case '4': return static_cast(KeyCode::Four); case '5': return static_cast(KeyCode::Five); case '6': return static_cast(KeyCode::Six); case '7': return static_cast(KeyCode::Seven); case '8': return static_cast(KeyCode::Eight); case '9': return static_cast(KeyCode::Nine); case VK_SPACE: return static_cast(KeyCode::Space); case VK_TAB: return static_cast(KeyCode::Tab); case VK_RETURN: return static_cast(KeyCode::Enter); case VK_ESCAPE: return static_cast(KeyCode::Escape); case VK_SHIFT: { const UINT scanCode = (static_cast(lParam) >> 16) & 0xFFu; const UINT leftShiftScanCode = MapVirtualKeyW(VK_LSHIFT, MAPVK_VK_TO_VSC); return static_cast( scanCode == leftShiftScanCode ? KeyCode::LeftShift : KeyCode::RightShift); } case VK_CONTROL: return static_cast( (static_cast(lParam) & 0x01000000u) != 0u ? KeyCode::RightCtrl : KeyCode::LeftCtrl); case VK_MENU: return static_cast( (static_cast(lParam) & 0x01000000u) != 0u ? KeyCode::RightAlt : KeyCode::LeftAlt); case VK_UP: return static_cast(KeyCode::Up); case VK_DOWN: return static_cast(KeyCode::Down); case VK_LEFT: return static_cast(KeyCode::Left); case VK_RIGHT: return static_cast(KeyCode::Right); case VK_HOME: return static_cast(KeyCode::Home); case VK_END: return static_cast(KeyCode::End); case VK_PRIOR: return static_cast(KeyCode::PageUp); case VK_NEXT: return static_cast(KeyCode::PageDown); case VK_DELETE: return static_cast(KeyCode::Delete); case VK_BACK: return static_cast(KeyCode::Backspace); case VK_F1: return static_cast(KeyCode::F1); case VK_F2: return static_cast(KeyCode::F2); case VK_F3: return static_cast(KeyCode::F3); case VK_F4: return static_cast(KeyCode::F4); case VK_F5: return static_cast(KeyCode::F5); case VK_F6: return static_cast(KeyCode::F6); case VK_F7: return static_cast(KeyCode::F7); case VK_F8: return static_cast(KeyCode::F8); case VK_F9: return static_cast(KeyCode::F9); case VK_F10: return static_cast(KeyCode::F10); case VK_F11: return static_cast(KeyCode::F11); case VK_F12: return static_cast(KeyCode::F12); case VK_OEM_MINUS: return static_cast(KeyCode::Minus); case VK_OEM_PLUS: return static_cast(KeyCode::Equals); case VK_OEM_4: return static_cast(KeyCode::BracketLeft); case VK_OEM_6: return static_cast(KeyCode::BracketRight); case VK_OEM_1: return static_cast(KeyCode::Semicolon); case VK_OEM_7: return static_cast(KeyCode::Quote); case VK_OEM_COMMA: return static_cast(KeyCode::Comma); case VK_OEM_PERIOD: return static_cast(KeyCode::Period); case VK_OEM_2: return static_cast(KeyCode::Slash); case VK_OEM_5: return static_cast(KeyCode::Backslash); case VK_OEM_3: return static_cast(KeyCode::Backtick); default: return static_cast(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 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(static_cast(LOWORD(static_cast(lParam)))), static_cast(static_cast(HIWORD(static_cast(lParam)))) }; if (hwnd != nullptr && ScreenToClient(hwnd, &screenPoint)) { m_pointerPosition = UIPoint(static_cast(screenPoint.x), static_cast(screenPoint.y)); UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, false); } const float wheelStep = static_cast(GET_WHEEL_DELTA_WPARAM(wParam)) / static_cast(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(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(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(clientRect.left) && y >= static_cast(clientRect.top) && x < static_cast(clientRect.right) && y < static_cast(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