// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #if PLATFORM_UWP #include "UWPWindow.h" #include "UWPPlatform.h" #include "Engine/Engine/Engine.h" #include "Engine/Core/Log.h" #include "Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.h" #include "Engine/Graphics/RenderTask.h" namespace Impl { UWPWindow::UWPKeyboard* Keyboard = nullptr; UWPWindow::UWPMouse* Mouse = nullptr; UWPWindow* Window = nullptr; } void OnSizeChanged(float width, float height, void* userData) { ((UWPWindow*)userData)->onSizeChanged(width, height); } void OnVisibilityChanged(bool visible, void* userData) { ((UWPWindow*)userData)->onVisibilityChanged(visible); } void OnDpiChanged(float dpi, void* userData) { ((UWPWindow*)userData)->onDpiChanged(dpi); } void OnClosed(void* userData) { ((UWPWindow*)userData)->onClosed(); } void OnFocusChanged(bool focused, void* userData) { ((UWPWindow*)userData)->onFocusChanged(focused); } void OnKeyDown(int key, void* userData) { ((UWPWindow*)userData)->onKeyDown(key); } void OnKeyUp(int key, void* userData) { ((UWPWindow*)userData)->onKeyUp(key); } void OnCharacterReceived(int key, void* userData) { ((UWPWindow*)userData)->onCharacterReceived(key); } void OnMouseMoved(float x, float y, void* userData) { ((UWPWindow*)userData)->onMouseMoved(x, y); } void OnPointerPressed(UWPWindowImpl::PointerData* pointer, void* userData) { ((UWPWindow*)userData)->onPointerPressed(pointer); } void OnPointerMoved(UWPWindowImpl::PointerData* pointer, void* userData) { ((UWPWindow*)userData)->onPointerMoved(pointer); } void OnPointerWheelChanged(UWPWindowImpl::PointerData* pointer, void* userData) { ((UWPWindow*)userData)->onPointerWheelChanged(pointer); } void OnPointerReleased(UWPWindowImpl::PointerData* pointer, void* userData) { ((UWPWindow*)userData)->onPointerReleased(pointer); } void OnPointerExited(UWPWindowImpl::PointerData* pointer, void* userData) { ((UWPWindow*)userData)->onPointerExited(pointer); } // Converts a length in device-independent pixels (DIPs) to a length in physical pixels. inline float ConvertDipsToPixels(float dips, float dpi) { static const float dipsPerInch = 96.0f; return floorf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer. } // Converts a length in physical pixels to a length in device-independent pixels (DIPs) using the provided DPI. This removes the need // to call this function from a thread associated with a CoreWindow, which is required by DisplayInformation::GetForCurrentView(). inline float ConvertPixelsToDips(float pixels, float dpi) { static const float dipsPerInch = 96.0f; return pixels * dipsPerInch / dpi; // Do not round. } void UWPWindow::UWPMouse::onMouseMoved(float x, float y) { const Float2 mousePos(x, y); if (!Float2::NearEqual(mousePos, MousePosition)) { MousePosition = mousePos; OnMouseMove(mousePos); } } void UWPWindow::UWPMouse::onPointerPressed(UWPWindowImpl::PointerData* pointer) { const Float2 mousePos(pointer->PositionX, pointer->PositionY); #define CHECK_MOUSE_BUTTON(button, val1) \ if (!val1 && pointer->val1) \ { \ val1 = pointer->val1; \ OnMouseDown(mousePos, MouseButton::button); \ } CHECK_MOUSE_BUTTON(Left, IsLeftButtonPressed); CHECK_MOUSE_BUTTON(Right, IsRightButtonPressed); CHECK_MOUSE_BUTTON(Middle, IsMiddleButtonPressed); CHECK_MOUSE_BUTTON(Extended1, IsXButton1Pressed); CHECK_MOUSE_BUTTON(Extended2, IsXButton2Pressed); #undef CHECK_MOUSE_BUTTON } void UWPWindow::UWPMouse::onPointerMoved(UWPWindowImpl::PointerData* pointer) { // Note: UWP reports pointer pressed event for the first mouse button clicked and pointer released event for the last button released but other buttons are reported using pointer moved event. // Reference: https://docs.microsoft.com/en-us/windows/uwp/design/input/handle-pointer-input const Float2 mousePos(pointer->PositionX, pointer->PositionY); #define CHECK_MOUSE_BUTTON(button, val1) \ if (val1 != pointer->val1) \ { \ val1 = pointer->val1; \ if (val1) \ OnMouseDown(mousePos, MouseButton::button); \ else \ OnMouseUp(mousePos, MouseButton::button); \ } CHECK_MOUSE_BUTTON(Left, IsLeftButtonPressed); CHECK_MOUSE_BUTTON(Right, IsRightButtonPressed); CHECK_MOUSE_BUTTON(Middle, IsMiddleButtonPressed); CHECK_MOUSE_BUTTON(Extended1, IsXButton1Pressed); CHECK_MOUSE_BUTTON(Extended2, IsXButton2Pressed); #undef CHECK_MOUSE_BUTTON if (!Float2::NearEqual(mousePos, MousePosition)) { MousePosition = mousePos; OnMouseMove(mousePos); } } void UWPWindow::UWPMouse::onPointerWheelChanged(UWPWindowImpl::PointerData* pointer) { int32 delta = pointer->MouseWheelDelta; if (delta != 0) { const float deltaNormalized = static_cast(delta) / WHEEL_DELTA; OnMouseWheel(MousePosition, deltaNormalized); } } void UWPWindow::UWPMouse::onPointerReleased(UWPWindowImpl::PointerData* pointer) { Float2 mousePos = Float2(pointer->PositionX, pointer->PositionY); #define CHECK_MOUSE_BUTTON(button, val1) \ if (!val1 && pointer->val1) \ { \ val1 = pointer->val1; \ OnMouseUp(mousePos, MouseButton::button); \ } CHECK_MOUSE_BUTTON(Left, IsLeftButtonPressed); CHECK_MOUSE_BUTTON(Right, IsRightButtonPressed); CHECK_MOUSE_BUTTON(Middle, IsMiddleButtonPressed); CHECK_MOUSE_BUTTON(Extended1, IsXButton1Pressed); CHECK_MOUSE_BUTTON(Extended2, IsXButton2Pressed); #undef CHECK_MOUSE_BUTTON } void UWPWindow::UWPMouse::onPointerExited(UWPWindowImpl::PointerData* pointer) { MousePosition = Float2(pointer->PositionX, pointer->PositionY); OnMouseLeave(); } void UWPWindow::UWPMouse::SetMousePosition(const Float2& newPosition) { const float dpiScale = Impl::Window->_dpiScale; Impl::Window->GetImpl()->SetMousePosition(newPosition.X / dpiScale, newPosition.Y / dpiScale); OnMouseMoved(newPosition); } void UWPWindow::UWPKeyboard::onCharacterReceived(int key) { OnCharInput((Char)key); } void UWPWindow::UWPKeyboard::onKeyDown(int key) { OnKeyDown(static_cast(key)); } void UWPWindow::UWPKeyboard::onKeyUp(int key) { OnKeyUp(static_cast(key)); } // See GamepadButtons enum #define UWP_GAMEPAD_Menu 1 #define UWP_GAMEPAD_View 2 #define UWP_GAMEPAD_A 4 #define UWP_GAMEPAD_B 8 #define UWP_GAMEPAD_X 16 #define UWP_GAMEPAD_Y 32 #define UWP_GAMEPAD_DPadUp 64 #define UWP_GAMEPAD_DPadDown 128 #define UWP_GAMEPAD_DPadLeft 256 #define UWP_GAMEPAD_DPadRight 512 #define UWP_GAMEPAD_LeftShoulder 1024 #define UWP_GAMEPAD_RightShoulder 2048 #define UWP_GAMEPAD_LeftThumbstick 4096 #define UWP_GAMEPAD_RightThumbstick 8192 #define UWP_GAMEPAD_Paddle1 16384 #define UWP_GAMEPAD_Paddle2 32768 #define UWP_GAMEPAD_Paddle3 65536 #define UWP_GAMEPAD_Paddle4 131072 void UWPWindow::UWPGamepad::SetVibration(const GamepadVibrationState& state) { Gamepad::SetVibration(state); Window->GetImpl()->SetGamepadVibration(Index, *(UWPGamepadStateVibration*)&state); } bool UWPWindow::UWPGamepad::UpdateState() { UWPGamepadState state; Window->GetImpl()->GetGamepadState(Index, state); // Gather buttons state const float deadZone = 0.01f; _state.Buttons[(int32)GamepadButton::A] = !!(state.Buttons & UWP_GAMEPAD_A); _state.Buttons[(int32)GamepadButton::B] = !!(state.Buttons & UWP_GAMEPAD_B); _state.Buttons[(int32)GamepadButton::X] = !!(state.Buttons & UWP_GAMEPAD_X); _state.Buttons[(int32)GamepadButton::Y] = !!(state.Buttons & UWP_GAMEPAD_Y); _state.Buttons[(int32)GamepadButton::LeftShoulder] = !!(state.Buttons & UWP_GAMEPAD_LeftShoulder); _state.Buttons[(int32)GamepadButton::RightShoulder] = !!(state.Buttons & UWP_GAMEPAD_RightShoulder); _state.Buttons[(int32)GamepadButton::Back] = !!(state.Buttons & UWP_GAMEPAD_View); _state.Buttons[(int32)GamepadButton::Start] = !!(state.Buttons & UWP_GAMEPAD_Menu); _state.Buttons[(int32)GamepadButton::LeftThumb] = !!(state.Buttons & UWP_GAMEPAD_LeftThumbstick); _state.Buttons[(int32)GamepadButton::RightThumb] = !!(state.Buttons & UWP_GAMEPAD_RightThumbstick); _state.Buttons[(int32)GamepadButton::LeftTrigger] = state.LeftTrigger > deadZone; _state.Buttons[(int32)GamepadButton::RightTrigger] = state.RightTrigger > deadZone; _state.Buttons[(int32)GamepadButton::DPadUp] = !!(state.Buttons & UWP_GAMEPAD_DPadUp); _state.Buttons[(int32)GamepadButton::DPadDown] = !!(state.Buttons & UWP_GAMEPAD_DPadDown); _state.Buttons[(int32)GamepadButton::DPadLeft] = !!(state.Buttons & UWP_GAMEPAD_DPadLeft); _state.Buttons[(int32)GamepadButton::DPadRight] = !!(state.Buttons & UWP_GAMEPAD_DPadRight); _state.Buttons[(int32)GamepadButton::LeftStickUp] = state.LeftThumbstickY > deadZone; _state.Buttons[(int32)GamepadButton::LeftStickDown] = state.LeftThumbstickY < -deadZone; _state.Buttons[(int32)GamepadButton::LeftStickLeft] = state.LeftThumbstickX < -deadZone; _state.Buttons[(int32)GamepadButton::LeftStickRight] = state.LeftThumbstickX > deadZone; _state.Buttons[(int32)GamepadButton::RightStickUp] = state.RightThumbstickY > deadZone; _state.Buttons[(int32)GamepadButton::RightStickDown] = state.RightThumbstickY < -deadZone; _state.Buttons[(int32)GamepadButton::RightStickLeft] = state.RightThumbstickX < -deadZone; _state.Buttons[(int32)GamepadButton::RightStickRight] = state.RightThumbstickX > deadZone; // Gather axis state _state.Axis[(int32)GamepadAxis::LeftStickX] = state.LeftThumbstickX; _state.Axis[(int32)GamepadAxis::LeftStickY] = state.LeftThumbstickY; _state.Axis[(int32)GamepadAxis::RightStickX] = state.RightThumbstickX; _state.Axis[(int32)GamepadAxis::RightStickY] = state.RightThumbstickY; _state.Axis[(int32)GamepadAxis::LeftTrigger] = state.LeftTrigger; _state.Axis[(int32)GamepadAxis::RightTrigger] = state.RightTrigger; return false; } UWPWindow::UWPWindow(const CreateWindowSettings& settings, UWPWindowImpl* impl) : WindowBase(settings) , _impl(impl) , _logicalSize(Float2::Zero) { ASSERT(Impl::Window == nullptr); Impl::Window = this; // Link _impl->UserData = this; _impl->SizeChanged = &::OnSizeChanged; _impl->VisibilityChanged = &::OnVisibilityChanged; _impl->DpiChanged = &::OnDpiChanged; _impl->Closed = &::OnClosed; _impl->FocusChanged = &::OnFocusChanged; _impl->KeyDown = &::OnKeyDown; _impl->KeyUp = &::OnKeyUp; _impl->CharacterReceived = &::OnCharacterReceived; _impl->MouseMoved = &::OnMouseMoved; _impl->PointerPressed = &::OnPointerPressed; _impl->PointerMoved = &::OnPointerMoved; _impl->PointerWheelChanged = &::OnPointerWheelChanged; _impl->PointerReleased = &::OnPointerReleased; _impl->PointerExited = &::OnPointerExited; // Peek properties Char buffer[200]; _impl->GetTitle(buffer, 200); _title = buffer; _impl->GetDpi(&_dpi); _dpiScale = _dpi / 96.0f; float x, y; _impl->GetBounds(&x, &y, &_logicalSize.X, &_logicalSize.Y); _impl->GetMousePosition(&Impl::Mouse->MousePosition.X, &Impl::Mouse->MousePosition.Y); OnSizeChange(); } UWPWindow::~UWPWindow() { } void UWPWindow::Show() { if (!_visible) { // TODO: show // Base WindowBase::Show(); } } void UWPWindow::Hide() { // Not supported } void UWPWindow::Minimize() { // Not supported } void UWPWindow::Maximize() { // Not supported } void UWPWindow::Restore() { // Not supported } bool UWPWindow::IsClosed() const { return _isClosing; } void UWPWindow::BringToFront(bool force) { Focus(); } void UWPWindow::SetClientBounds(const Rectangle& clientArea) { // Not supported } void UWPWindow::SetPosition(const Float2& position) { // Not supported } void UWPWindow::SetClientPosition(const Float2& position) { SetClientPosition(position); } Float2 UWPWindow::GetPosition() const { float x, y, width, height; _impl->GetBounds(&x, &y, &width, &height); return Float2(x, y); } Float2 UWPWindow::GetSize() const { return _clientSize; } Float2 UWPWindow::GetClientSize() const { return GetSize(); } Float2 UWPWindow::ScreenToClient(const Float2& screenPos) const { return screenPos - GetPosition(); } Float2 UWPWindow::ClientToScreen(const Float2& clientPos) const { return clientPos + GetPosition(); } void UWPWindow::Focus() { _impl->Activate(); } void UWPWindow::SetTitle(const StringView& title) { _impl->SetTitle(title.Get()); } DragDropEffect UWPWindow::DoDragDrop(const StringView& data) { // Not supported return DragDropEffect::None; } void UWPWindow::StartTrackingMouse(bool useMouseScreenOffset) { if (!_isTrackingMouse) { _isTrackingMouse = true; _trackingMouseOffset = Float2::Zero; _isUsingMouseOffset = useMouseScreenOffset; } } void UWPWindow::EndTrackingMouse() { if (_isTrackingMouse) { _isTrackingMouse = false; } } void UWPWindow::SetCursor(CursorType type) { _impl->SetCursor((UWPWindowImpl::CursorType)type); // Base WindowBase::SetCursor(type); } void UWPWindow::onSizeChanged(float width, float height) { _logicalSize.X = width; _logicalSize.Y = height; OnSizeChange(); } void UWPWindow::onVisibilityChanged(bool visible) { } void UWPWindow::onDpiChanged(float dpi) { if (_dpi != dpi) { _dpi = (int)dpi; _dpiScale = _dpi / 96.0f; // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated float x, y; _impl->GetBounds(&x, &y, &_logicalSize.X, &_logicalSize.Y); OnSizeChange(); } } void UWPWindow::onClosed() { } void UWPWindow::onFocusChanged(bool focused) { if (focused) OnGotFocus(); else OnLostFocus(); } void UWPWindow::onKeyDown(int key) { if (key < 7) return; Impl::Keyboard->onKeyDown(key); } void UWPWindow::onKeyUp(int key) { if (key < 7) return; Impl::Keyboard->onKeyUp(key); } void UWPWindow::onCharacterReceived(int key) { Impl::Keyboard->onCharacterReceived(key); } void UWPWindow::onMouseMoved(float x, float y) { Impl::Mouse->onMouseMoved(x * _dpiScale, y * _dpiScale); } void UWPWindow::onPointerPressed(UWPWindowImpl::PointerData* pointer) { if (pointer->IsMouse) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; Impl::Mouse->onPointerPressed(pointer); } // TODO: impl mobile touch support } void UWPWindow::onPointerMoved(UWPWindowImpl::PointerData* pointer) { if (pointer->IsMouse) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; Impl::Mouse->onPointerMoved(pointer); } // TODO: impl mobile touch support } void UWPWindow::onPointerWheelChanged(UWPWindowImpl::PointerData* pointer) { if (pointer->IsMouse) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; Impl::Mouse->onPointerWheelChanged(pointer); } } void UWPWindow::onPointerReleased(UWPWindowImpl::PointerData* pointer) { if (pointer->IsMouse) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; Impl::Mouse->onPointerReleased(pointer); } // TODO: impl mobile touch support } void UWPWindow::onPointerExited(UWPWindowImpl::PointerData* pointer) { if (pointer->IsMouse) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; Impl::Mouse->onPointerExited(pointer); } // TODO: impl mobile touch support } void UWPWindow::OnSizeChange() { // Update the actual rendering output resolution _clientSize.X = ConvertDipsToPixels(_logicalSize.X, (float)_dpi); _clientSize.Y = ConvertDipsToPixels(_logicalSize.Y, (float)_dpi); // Check if output has been created if (_swapChain != nullptr) { const int32 width = Math::Max((int32)_clientSize.X, 0); const int32 height = Math::Max((int32)_clientSize.Y, 0); if (width > 0 && height > 0 && (width != _swapChain->GetWidth() || height != _swapChain->GetHeight())) { OnResize(width, height); } } } #endif