#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowInternalState.h" #include #include #include #include namespace XCEngine::UI::Editor::App { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; namespace { bool IsScreenPointOverWindow(HWND hwnd, const POINT& screenPoint) { if (hwnd == nullptr || !IsWindow(hwnd)) { return false; } const HWND hitWindow = WindowFromPoint(screenPoint); if (hitWindow == nullptr || GetAncestor(hitWindow, GA_ROOT) != hwnd) { return false; } RECT windowRect = {}; if (!GetWindowRect(hwnd, &windowRect)) { return false; } return screenPoint.x >= windowRect.left && screenPoint.x < windowRect.right && screenPoint.y >= windowRect.top && screenPoint.y < windowRect.bottom; } std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { using ::XCEngine::Input::KeyCode; switch (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: return static_cast(KeyCode::LeftShift); case VK_CONTROL: return static_cast(KeyCode::LeftCtrl); case VK_MENU: return static_cast(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); default: return static_cast(KeyCode::None); } } bool IsRepeatKeyMessage(LPARAM lParam) { return (static_cast(lParam) & (1ul << 30)) != 0ul; } } // namespace bool EditorWindow::ApplyCurrentCursor() const { if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) { return false; } const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource()); if (cursor == nullptr) { return false; } SetCursor(cursor); return true; } bool EditorWindow::HasInteractiveCaptureState() const { return m_state->composition.shellRuntime.HasInteractiveCapture() || m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed() || m_state->chrome.runtime.IsBorderlessResizeActive() || m_state->input.pointerCaptureOwner != EditorWindowPointerCaptureOwner::None; } EditorWindowPointerCaptureOwner EditorWindow::GetPointerCaptureOwner() const { return m_state->input.pointerCaptureOwner; } bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const { return m_state->input.pointerCaptureOwner == owner; } void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) { if (owner == EditorWindowPointerCaptureOwner::None || m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return; } m_state->input.pointerCaptureOwner = owner; if (GetCapture() != m_state->window.hwnd) { SetCapture(m_state->window.hwnd); } } void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) { if (m_state->input.pointerCaptureOwner != owner) { return; } m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { ReleaseCapture(); } } void EditorWindow::ForceReleasePointerCapture() { m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { ReleaseCapture(); } } void EditorWindow::ClearPointerCaptureOwner() { m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; } void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd) || GetCapture() == m_state->window.hwnd) { return; } const ::XCEngine::UI::UIPoint clientPoint = ConvertClientPixelsToDips( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (!ShouldStartImmediateUIEditorShellPointerCapture( m_state->composition.shellRuntime.GetShellFrame(), clientPoint)) { return; } AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); } void EditorWindow::QueuePointerEvent( UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam) { UIInputEvent event = {}; event.type = type; event.pointerButton = button; event.position = ConvertClientPixelsToDips( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( type, button, static_cast(wParam)); m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueSyntheticPointerStateSyncEvent( const ::XCEngine::UI::UIInputModifiers& modifiers) { if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return; } POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return; } if (!ScreenToClient(m_state->window.hwnd, &screenPoint)) { return; } UIInputEvent event = {}; event.type = UIInputEventType::PointerMove; event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); event.modifiers = modifiers; m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueuePointerLeaveEvent() { UIInputEvent event = {}; event.type = UIInputEventType::PointerLeave; if (m_state->window.hwnd != nullptr) { POINT clientPoint = {}; GetCursorPos(&clientPoint); ScreenToClient(m_state->window.hwnd, &clientPoint); event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); } event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { if (m_state->window.hwnd == nullptr) { return; } POINT screenPoint = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(m_state->window.hwnd, &screenPoint); UIInputEvent event = {}; event.type = UIInputEventType::PointerWheel; event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); event.wheelDelta = static_cast(wheelDelta); event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( UIInputEventType::PointerWheel, UIPointerButton::None, static_cast(wParam)); m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { UIInputEvent event = {}; event.type = type; event.keyCode = MapVirtualKeyToUIKeyCode(wParam); event.modifiers = m_state->input.modifierTracker.ApplyKeyMessage(type, wParam, lParam); event.repeat = IsRepeatKeyMessage(lParam); m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) { UIInputEvent event = {}; event.type = UIInputEventType::Character; event.character = static_cast(wParam); event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) { UIInputEvent event = {}; event.type = type; m_state->input.pendingEvents.push_back(event); } void EditorWindow::SyncInputModifiersFromSystemState() { m_state->input.modifierTracker.SyncFromSystemState(); } void EditorWindow::ResetInputModifiers() { m_state->input.modifierTracker.Reset(); } void EditorWindow::RequestManualScreenshot() { m_state->render.autoScreenshot.RequestCapture("manual_f12"); } bool EditorWindow::IsPointerInsideClientArea() const { if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return false; } POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return false; } if (!IsScreenPointOverWindow(m_state->window.hwnd, screenPoint)) { return false; } const LPARAM pointParam = MAKELPARAM( static_cast(screenPoint.x), static_cast(screenPoint.y)); return SendMessageW(m_state->window.hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; } LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { const Host::BorderlessWindowResizeEdge borderlessResizeEdge = m_state->chrome.runtime.IsBorderlessResizeActive() ? m_state->chrome.runtime.GetBorderlessResizeEdge() : m_state->chrome.runtime.GetHoveredBorderlessResizeEdge(); if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); } switch (m_state->composition.shellRuntime.GetHostedContentCursorKind()) { case ProjectPanel::CursorKind::ResizeEW: return IDC_SIZEWE; case ProjectPanel::CursorKind::Arrow: default: break; } switch (m_state->composition.shellRuntime.GetDockCursorKind()) { case Widgets::UIEditorDockHostCursorKind::ResizeEW: return IDC_SIZEWE; case Widgets::UIEditorDockHostCursorKind::ResizeNS: return IDC_SIZENS; case Widgets::UIEditorDockHostCursorKind::Arrow: default: return IDC_ARROW; } } } // namespace XCEngine::UI::Editor::App