// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #if PLATFORM_WINDOWS #include "WindowsWindow.h" #include "WindowsPlatform.h" #include "WindowsInput.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/GPUDevice.h" #if USE_EDITOR #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/IGuiData.h" #include "Engine/Platform/Base/DragDropHelper.h" #include "Engine/Input/Input.h" #include "Engine/Input/Mouse.h" #endif #include "../Win32/IncludeWindowsHeaders.h" #include #if USE_EDITOR #include #include #endif #define DefaultDPI 96 // Use improved borderless window support for Editor #define WINDOWS_USE_NEW_BORDER_LESS USE_EDITOR && 0 #if WINDOWS_USE_NEW_BORDER_LESS #pragma comment(lib, "Gdi32.lib") WIN_API HRGN WIN_API_CALLCONV CreateRectRgn(int x1, int y1, int x2, int y2); #endif #define WINDOWS_USE_NEWER_BORDER_LESS USE_EDITOR && 1 #if WINDOWS_USE_NEWER_BORDER_LESS #pragma comment(lib, "dwmapi.lib") WIN_API HRESULT WIN_API_CALLCONV DwmExtendFrameIntoClientArea(HWND hWnd, const void* pMarInset); WIN_API HRESULT WIN_API_CALLCONV DwmIsCompositionEnabled(BOOL* pfEnabled); namespace { bool IsCompositionEnabled() { BOOL result = FALSE; const bool success = ::DwmIsCompositionEnabled(&result) == S_OK; return result && success; } } #endif namespace { bool IsWindowMaximized(HWND window) { WINDOWPLACEMENT placement; if (!::GetWindowPlacement(window, &placement)) return false; return placement.showCmd == SW_MAXIMIZE; } } WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) : WindowBase(settings) #if USE_EDITOR , _refCount(1) #endif { int32 x = Math::TruncToInt(settings.Position.X); int32 y = Math::TruncToInt(settings.Position.Y); int32 clientWidth = Math::TruncToInt(settings.Size.X); int32 clientHeight = Math::TruncToInt(settings.Size.Y); int32 windowWidth = clientWidth; int32 windowHeight = clientHeight; _clientSize = Float2((float)clientWidth, (float)clientHeight); // Setup window style uint32 style = WS_POPUP, exStyle = 0; if (settings.SupportsTransparency) exStyle |= WS_EX_LAYERED; if (!settings.ActivateWhenFirstShown) exStyle |= WS_EX_NOACTIVATE; if (settings.ShowInTaskbar) exStyle |= WS_EX_APPWINDOW; else exStyle |= WS_EX_TOOLWINDOW; if (settings.IsTopmost) exStyle |= WS_EX_TOPMOST; if (!settings.AllowInput) exStyle |= WS_EX_TRANSPARENT; if (settings.AllowMaximize) style |= WS_MAXIMIZEBOX; if (settings.AllowMinimize) style |= WS_MINIMIZEBOX; if (settings.HasSizingFrame) style |= WS_THICKFRAME; // Check if window should have a border if (settings.HasBorder) { // Create window style flags style |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; exStyle |= 0; // Adjust window size and positions to take into account window border RECT winRect = { 0, 0, clientWidth, clientHeight }; AdjustWindowRectEx(&winRect, style, FALSE, exStyle); x += winRect.left; y += winRect.top; windowWidth = winRect.right - winRect.left; windowHeight = winRect.bottom - winRect.top; } else { // Create window style flags style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; #if WINDOWS_USE_NEW_BORDER_LESS if (settings.IsRegularWindow) style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP; #elif WINDOWS_USE_NEWER_BORDER_LESS if (settings.IsRegularWindow) style |= WS_THICKFRAME | WS_SYSMENU | WS_CAPTION; #endif exStyle |= WS_EX_WINDOWEDGE; } // Creating the window _handle = CreateWindowExW( exStyle, Platform::ApplicationWindowClass, settings.Title.GetText(), style, x, y, windowWidth, windowHeight, settings.Parent ? static_cast(settings.Parent->GetNativePtr()) : nullptr, nullptr, (HINSTANCE)Platform::Instance, nullptr); if (_handle == nullptr) { LOG_WIN32_LAST_ERROR; Platform::Fatal(TEXT("Cannot create window.")); return; } // Query DPI _dpi = Platform::GetDpi(); const HMODULE user32Dll = LoadLibraryW(L"user32.dll"); if (user32Dll) { typedef UINT (STDAPICALLTYPE*GetDpiForWindowProc)(HWND hwnd); const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow"); if (getDpiForWindowProc) { _dpi = getDpiForWindowProc(_handle); } FreeLibrary(user32Dll); } _dpiScale = (float)_dpi / (float)DefaultDPI; #if WINDOWS_USE_NEWER_BORDER_LESS // Enable shadow if (_settings.IsRegularWindow && !_settings.HasBorder && IsCompositionEnabled()) { const int margin[4] = { 1, 1, 1, 1 }; ::DwmExtendFrameIntoClientArea(_handle, margin); } #endif #if USE_EDITOR // Enable file dropping if (_settings.AllowDragAndDrop) { const auto result = RegisterDragDrop(_handle, (LPDROPTARGET)(Windows::IDropTarget*)this); if (result != S_OK) { LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 1); } } #endif UpdateRegion(); } WindowsWindow::~WindowsWindow() { if (HasHWND()) { // Destroy window if (DestroyWindow(_handle) == 0) { LOG(Warning, "DestroyWindow failed! Error: {0:#x}", GetLastError()); } // Clear _handle = nullptr; _visible = false; } } void* WindowsWindow::GetNativePtr() const { return _handle; } void WindowsWindow::Show() { if (!_visible) { InitSwapChain(); if (_showAfterFirstPaint) { if (RenderTask) RenderTask->Enabled = true; return; } ASSERT(HasHWND()); // Show ShowWindow(_handle, (_settings.AllowInput && _settings.ActivateWhenFirstShown) ? SW_SHOW : SW_SHOWNA); #if WINDOWS_USE_NEW_BORDER_LESS if (!_settings.HasBorder && _settings.IsRegularWindow) { SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } #elif WINDOWS_USE_NEWER_BORDER_LESS if (!_settings.HasBorder) { SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); } #endif // Base WindowBase::Show(); } } void WindowsWindow::Hide() { if (_visible) { ASSERT(HasHWND()); // Hide ShowWindow(_handle, SW_HIDE); // Base WindowBase::Hide(); } } void WindowsWindow::Minimize() { if (!_settings.AllowMinimize) return; ASSERT(HasHWND()); ShowWindow(_handle, SW_MINIMIZE); } void WindowsWindow::Maximize() { if (!_settings.AllowMaximize) return; ASSERT(HasHWND()); _isDuringMaximize = true; ShowWindow(_handle, SW_MAXIMIZE); _isDuringMaximize = false; } void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { ASSERT(HasHWND()); if (IsFullscreen()) SetIsFullscreen(false); // Fixes issue of borderless window not going full screen if (IsMaximized()) Restore(); _settings.HasBorder = !isBorderless; BringToFront(); if (isBorderless) { LONG lStyle = GetWindowLong(_handle, GWL_STYLE); lStyle &= ~(WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED | WS_BORDER | WS_CAPTION); lStyle |= WS_POPUP; lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; #if WINDOWS_USE_NEW_BORDER_LESS if (_settings.IsRegularWindow) style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP; #elif WINDOWS_USE_NEWER_BORDER_LESS if (_settings.IsRegularWindow) lStyle |= WS_THICKFRAME | WS_SYSMENU; #endif SetWindowLong(_handle, GWL_STYLE, lStyle); SetWindowPos(_handle, HWND_TOP, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); if (maximized) { ShowWindow(_handle, SW_SHOWMAXIMIZED); } else { ShowWindow(_handle, SW_SHOW); } } else { LONG lStyle = GetWindowLong(_handle, GWL_STYLE); lStyle &= ~(WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); if (_settings.AllowMaximize) lStyle |= WS_MAXIMIZEBOX; if (_settings.AllowMinimize) lStyle |= WS_MINIMIZEBOX; if (_settings.HasSizingFrame) lStyle |= WS_THICKFRAME; lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; SetWindowLong(_handle, GWL_STYLE, lStyle); const Float2 clientSize = GetClientSize(); const Float2 desktopSize = Platform::GetDesktopSize(); // Move window and half size if it is larger than desktop size if (clientSize.X >= desktopSize.X && clientSize.Y >= desktopSize.Y) { const Float2 halfSize = desktopSize * 0.5f; const Float2 middlePos = halfSize * 0.5f; SetWindowPos(_handle, nullptr, (int)middlePos.X, (int)middlePos.Y, (int)halfSize.X, (int)halfSize.Y, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); } else { SetWindowPos(_handle, nullptr, 0, 0, (int)clientSize.X, (int)clientSize.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } if (maximized) { Maximize(); } else { ShowWindow(_handle, SW_SHOW); } } CheckForWindowResize(); } void WindowsWindow::Restore() { ASSERT(HasHWND()); ShowWindow(_handle, SW_RESTORE); } bool WindowsWindow::IsClosed() const { return !HasHWND(); } bool WindowsWindow::IsForegroundWindow() const { return ::GetForegroundWindow() == _handle; } void WindowsWindow::BringToFront(bool force) { ASSERT(HasHWND()); if (_settings.IsRegularWindow) { if (IsIconic(_handle)) { ShowWindow(_handle, SW_RESTORE); } else { SetActiveWindow(_handle); } } else { HWND hWndInsertAfter = HWND_TOP; uint32 flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; if (!force) { flags |= SWP_NOACTIVATE; } if (_settings.IsTopmost) { hWndInsertAfter = HWND_TOPMOST; } SetWindowPos(_handle, hWndInsertAfter, 0, 0, 0, 0, flags); } } void WindowsWindow::SetClientBounds(const Rectangle& clientArea) { ASSERT(HasHWND()); // Check if position or/and size will change const auto rect = GetClientBounds(); const bool changeLocation = !Float2::NearEqual(rect.Location, clientArea.Location); const bool changeSize = !Float2::NearEqual(rect.Size, clientArea.Size); if (!changeLocation && !changeSize) return; // Get values data int32 x = (int32)clientArea.GetX(); int32 y = (int32)clientArea.GetY(); int32 width = (int32)clientArea.GetWidth(); int32 height = (int32)clientArea.GetHeight(); if (changeSize) { _clientSize = clientArea.Size; // Update GUI OnResize(width, height); } // Check if need to adjust window rectangle if (_settings.HasBorder) { // Get window info WINDOWINFO winInfo; Platform::MemoryClear(&winInfo, sizeof(WINDOWINFO)); winInfo.cbSize = sizeof(winInfo); GetWindowInfo(_handle, &winInfo); // Adjust rectangle from client size to window size RECT winRect = { 0, 0, width, height }; AdjustWindowRectEx(&winRect, winInfo.dwStyle, FALSE, winInfo.dwExStyle); width = winRect.right - winRect.left; height = winRect.bottom - winRect.top; // Little hack but works great winRect = { x, y, width, height }; AdjustWindowRectEx(&winRect, winInfo.dwStyle, FALSE, winInfo.dwExStyle); x = winRect.left; y = winRect.top; } // Change window size and location SetWindowPos(_handle, nullptr, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE); if (changeSize) { UpdateRegion(); } } void WindowsWindow::SetPosition(const Float2& position) { ASSERT(HasHWND()); // Cache data int32 x = (int32)position.X; int32 y = (int32)position.Y; // Change window location SetWindowPos(_handle, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); } void WindowsWindow::SetClientPosition(const Float2& position) { ASSERT(HasHWND()); // Cache data int32 x = (int32)position.X; int32 y = (int32)position.Y; // Check if need to adjust window rectangle if (_settings.HasBorder) { // Get window info WINDOWINFO winInfo; Platform::MemoryClear(&winInfo, sizeof(WINDOWINFO)); winInfo.cbSize = sizeof(winInfo); GetWindowInfo(_handle, &winInfo); // Adjust rectangle from client size to window size RECT winRect = { x, y, 666, 69 }; AdjustWindowRectEx(&winRect, winInfo.dwStyle, FALSE, winInfo.dwExStyle); // Calculate window size x = winRect.left; y = winRect.top; } // Change window location SetWindowPos(_handle, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); } void WindowsWindow::SetIsFullscreen(bool isFullscreen) { _isSwitchingFullScreen = true; ASSERT(HasHWND()); // Base WindowBase::SetIsFullscreen(isFullscreen); if (!isFullscreen) { // Restore window ShowWindow(_handle, SW_NORMAL); } _isSwitchingFullScreen = false; } Float2 WindowsWindow::GetPosition() const { ASSERT(HasHWND()); RECT rect; GetWindowRect(_handle, &rect); return Float2(static_cast(rect.left), static_cast(rect.top)); } Float2 WindowsWindow::GetSize() const { ASSERT(HasHWND()); RECT rect; GetWindowRect(_handle, &rect); return Float2(static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)); } Float2 WindowsWindow::GetClientSize() const { return _clientSize; } Float2 WindowsWindow::ScreenToClient(const Float2& screenPos) const { ASSERT(HasHWND()); POINT p; p.x = static_cast(screenPos.X); p.y = static_cast(screenPos.Y); ::ScreenToClient(_handle, &p); return Float2(static_cast(p.x), static_cast(p.y)); } Float2 WindowsWindow::ClientToScreen(const Float2& clientPos) const { ASSERT(HasHWND()); if (_minimized) { // Return cached position when window is not on screen return _minimizedScreenPosition + clientPos; } POINT p; p.x = static_cast(clientPos.X); p.y = static_cast(clientPos.Y); ::ClientToScreen(_handle, &p); return Float2(static_cast(p.x), static_cast(p.y)); } void WindowsWindow::FlashWindow() { ASSERT(HasHWND()); if (IsFocused()) return; ::FlashWindow(_handle, FALSE); } void WindowsWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const { ASSERT(HasHWND()); // Pick the current monitor data for sizing const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(monitor, &monitorInfo); // Calculate result x = monitorInfo.rcMonitor.left; y = monitorInfo.rcMonitor.top; width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; } float WindowsWindow::GetOpacity() const { ASSERT(HasHWND()); COLORREF color; BYTE alpha; DWORD flags; GetLayeredWindowAttributes(_handle, &color, &alpha, &flags); return (float)alpha / 255.0f; } void WindowsWindow::SetOpacity(const float opacity) { ASSERT(HasHWND()); SetLayeredWindowAttributes(_handle, 0, static_cast(Math::Saturate(opacity) * 255), LWA_ALPHA); } void WindowsWindow::Focus() { ASSERT(HasHWND()); if (GetFocus() != _handle) { SetFocus(_handle); } } void WindowsWindow::SetTitle(const StringView& title) { ASSERT(HasHWND()); if (SetWindowTextW(_handle, *title)) { _title = title; } } void WindowsWindow::StartTrackingMouse(bool useMouseScreenOffset) { ASSERT(HasHWND()); if (!_isTrackingMouse) { _isTrackingMouse = true; _trackingMouseOffset = Float2::Zero; _isUsingMouseOffset = useMouseScreenOffset; _isHorizontalFlippingMouse = false; _isVerticalFlippingMouse = false; int32 x = 0, y = 0, width = 0, height = 0; GetScreenInfo(x, y, width, height); _mouseOffsetScreenSize = Rectangle((float)x, (float)y, (float)width, (float)height); SetCapture(_handle); } } void WindowsWindow::EndTrackingMouse() { if (_isTrackingMouse) { _isTrackingMouse = false; _isHorizontalFlippingMouse = false; _isVerticalFlippingMouse = false; ReleaseCapture(); } } void WindowsWindow::StartClippingCursor(const Rectangle& bounds) { _isClippingCursor = true; *(RECT*)_clipCursorRect = { (LONG)bounds.GetUpperLeft().X, (LONG)bounds.GetUpperLeft().Y, (LONG)bounds.GetBottomRight().X, (LONG)bounds.GetBottomRight().Y }; if (IsFocused()) { _clipCursorSet = true; ClipCursor((RECT*)_clipCursorRect); } } void WindowsWindow::EndClippingCursor() { if (_isClippingCursor) { _isClippingCursor = false; _clipCursorSet = false; ClipCursor(nullptr); } } void WindowsWindow::SetCursor(CursorType type) { // Base WindowBase::SetCursor(type); UpdateCursor(); } #if USE_EDITOR Windows::HRESULT WindowsWindow::QueryInterface(const Windows::IID& id, void** ppvObject) { // Check to see what interface has been requested if ((const IID&)id == IID_IUnknown || (const IID&)id == IID_IDropTarget) { AddRef(); *ppvObject = this; return S_OK; } // No interface *ppvObject = nullptr; return E_NOINTERFACE; } Windows::ULONG WindowsWindow::AddRef() { _InterlockedIncrement(&_refCount); return _refCount; } Windows::ULONG WindowsWindow::Release() { return _InterlockedDecrement(&_refCount); } #endif void WindowsWindow::CheckForWindowResize() { // Skip for minimized window (GetClientRect for minimized window returns 0) if (_minimized) return; ASSERT(HasHWND()); // Cache client size RECT rect; GetClientRect(_handle, &rect); int32 width = Math::Max(rect.right - rect.left, 0L); int32 height = Math::Max(rect.bottom - rect.top, 0L); // Check for windows maximized size and see if it needs to adjust position if needed if (_maximized) { // Pick the current monitor data for sizing const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(monitor, &monitorInfo); auto cwidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left; auto cheight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; if (width > cwidth && height > cheight) { width = cwidth; height = cheight; SetWindowPos(_handle, HWND_TOP, monitorInfo.rcWork.left, monitorInfo.rcWork.top, width, height, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); } } _clientSize = Float2(static_cast(width), static_cast(height)); // Check if window size has been changed if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight())) { UpdateRegion(); OnResize(width, height); } } void WindowsWindow::UpdateCursor() { // Don't hide cursor when window is not focused if (_cursor == CursorType::Hidden && _focused) { if (!_lastCursorHidden) { _lastCursorHidden = true; while(::ShowCursor(FALSE) >= 0) { if (_cursorHiddenSafetyCount >= 100) { LOG(Warning, "Cursor has failed to hide."); break; } _cursorHiddenSafetyCount += 1; } _cursorHiddenSafetyCount = 0; } ::SetCursor(nullptr); return; } else if (_lastCursorHidden) { _lastCursorHidden = false; while(::ShowCursor(TRUE) < 0) { if (_cursorHiddenSafetyCount >= 100) { LOG(Warning, "Cursor has failed to show."); break; } _cursorHiddenSafetyCount += 1; } _cursorHiddenSafetyCount = 0; } int32 index = 0; switch (_cursor) { case CursorType::Default: break; case CursorType::Cross: index = 1; break; case CursorType::Hand: index = 2; break; case CursorType::Help: index = 3; break; case CursorType::IBeam: index = 4; break; case CursorType::No: index = 5; break; case CursorType::Wait: index = 11; break; case CursorType::SizeAll: index = 6; break; case CursorType::SizeNESW: index = 7; break; case CursorType::SizeNS: index = 8; break; case CursorType::SizeNWSE: index = 9; break; case CursorType::SizeWE: index = 10; break; } static const LPCWSTR cursors[] = { IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, }; ASSERT(index >= 0 && index < ARRAY_COUNT(cursors)); const HCURSOR cursor = LoadCursorW(nullptr, cursors[index]); ::SetCursor(cursor); } void WindowsWindow::UpdateRegion() { #if WINDOWS_USE_NEW_BORDER_LESS // Use region to remove rounded corners of the window if (!_settings.HasBorder && _settings.IsRegularWindow) { if (!_maximized && !_isResizing) { RECT rcWnd; GetWindowRect(_handle, &rcWnd); const int32 width = rcWnd.right - rcWnd.left; const int32 height = rcWnd.bottom - rcWnd.top; if (_regionWidth != width || _regionHeight != height) { _regionWidth = width; _regionHeight = height; const HRGN region = CreateRectRgn(0, 0, width, height); SetWindowRgn(_handle, region, FALSE); } } else if (_regionWidth != 0 || _regionHeight != 0) { _regionWidth = 0; _regionHeight = 0; SetWindowRgn(_handle, nullptr, FALSE); } } #endif } void TrackMouse(HWND hwnd) { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.dwHoverTime = 5000; tme.hwndTrack = hwnd; TrackMouseEvent(&tme); } LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) { const UINT_PTR MouseStopTimerID = 1; switch (msg) { case WM_PAINT: { // Check if window is during resizing if (_isResizing && _swapChain) { // Redraw window backbuffer on DX11 switch (GPUDevice::Instance->GetRendererType()) { case RendererType::DirectX10: case RendererType::DirectX10_1: case RendererType::DirectX11: _swapChain->Present(false); break; } } break; } case WM_TIMER: { if (wParam == MouseStopTimerID) { // Kill the timer after processing it KillTimer(_handle, MouseStopTimerID); return 0; } break; } case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { UpdateCursor(); return true; } break; } case WM_MOUSEMOVE: { if (!_trackingMouse) { TrackMouse(_handle); _trackingMouse = true; } if (_isTrackingMouse) { KillTimer(_handle, MouseStopTimerID); SetTimer(_handle, MouseStopTimerID, 100, nullptr); } // Here we can transfer mouse pointer over virtual workspace if (_isTrackingMouse && _isUsingMouseOffset) { // Check if move mouse to another edge of the desktop Float2 desktopLocation = _mouseOffsetScreenSize.Location; Float2 desktopSize = _mouseOffsetScreenSize.GetBottomRight(); const Float2 mousePos(static_cast(WINDOWS_GET_X_LPARAM(lParam)), static_cast(WINDOWS_GET_Y_LPARAM(lParam))); Float2 mousePosition = ClientToScreen(mousePos); Float2 newMousePosition = mousePosition; if (_isHorizontalFlippingMouse = mousePosition.X <= desktopLocation.X + 2) newMousePosition.X = desktopSize.X - 3; else if (_isHorizontalFlippingMouse = mousePosition.X >= desktopSize.X - 1) newMousePosition.X = desktopLocation.X + 3; if (_isVerticalFlippingMouse = mousePosition.Y <= desktopLocation.Y + 2) newMousePosition.Y = desktopSize.Y - 3; else if (_isVerticalFlippingMouse = mousePosition.Y >= desktopSize.Y - 1) newMousePosition.Y = desktopLocation.Y + 3; if (!Float2::NearEqual(mousePosition, newMousePosition)) { _trackingMouseOffset -= newMousePosition - mousePosition; SetMousePosition(ScreenToClient(newMousePosition)); } } break; } case WM_MOUSELEAVE: { _trackingMouse = false; break; } case WM_NCCALCSIZE: { #if WINDOWS_USE_NEW_BORDER_LESS if (wParam && !_settings.HasBorder && _settings.IsRegularWindow) { if (_maximized) { WINDOWINFO winInfo = { 0 }; winInfo.cbSize = sizeof(winInfo); ::GetWindowInfo(_handle, &winInfo); LPNCCALCSIZE_PARAMS rects = (LPNCCALCSIZE_PARAMS)lParam; rects->rgrc[0].left += winInfo.cxWindowBorders; rects->rgrc[0].top += winInfo.cxWindowBorders; rects->rgrc[0].right -= winInfo.cxWindowBorders; rects->rgrc[0].bottom -= winInfo.cxWindowBorders; rects->rgrc[1].left = rects->rgrc[0].left; rects->rgrc[1].top = rects->rgrc[0].top; rects->rgrc[1].right = rects->rgrc[0].right; rects->rgrc[1].bottom = rects->rgrc[0].bottom; rects->lppos->x += winInfo.cxWindowBorders; rects->lppos->y += winInfo.cxWindowBorders; rects->lppos->cx -= 2 * winInfo.cxWindowBorders; rects->lppos->cy -= 2 * winInfo.cxWindowBorders; return WVR_VALIDRECTS; } else { SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); return 0; } } #elif WINDOWS_USE_NEWER_BORDER_LESS if (wParam == TRUE && !_settings.HasBorder) // && _settings.IsRegularWindow) { // In maximized mode fill the whole work area of the monitor (excludes task bar) if (IsWindowMaximized(_handle)) { HMONITOR monitor = ::MonitorFromWindow(_handle, MONITOR_DEFAULTTONULL); if (monitor) { MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(monitorInfo); if (::GetMonitorInfoW(monitor, &monitorInfo)) { LPNCCALCSIZE_PARAMS rects = (LPNCCALCSIZE_PARAMS)lParam; rects->rgrc[0] = monitorInfo.rcWork; } } } //SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); return 0; } #endif break; } case WM_NCHITTEST: { // Override it for fullscreen mode if (IsFullscreen()) return static_cast(WindowHitCodes::Client); const Float2 mouse(static_cast(WINDOWS_GET_X_LPARAM(lParam)), static_cast(WINDOWS_GET_Y_LPARAM(lParam))); WindowHitCodes hit = WindowHitCodes::Client; bool handled = false; OnHitTest(mouse, hit, handled); if (handled) return static_cast(hit); break; } case WM_NCLBUTTONDOWN: { bool result = false; OnLeftButtonHit(static_cast(wParam), result); if (result) return 0; break; } case WM_NCLBUTTONDBLCLK: { // Handle non-client area double click manually if (IsMaximized()) Restore(); else Maximize(); return 0; } #if WINDOWS_USE_NEWER_BORDER_LESS case WM_NCACTIVATE: { // Skip for border-less windows if (!_settings.HasBorder && !IsCompositionEnabled()) return 1; break; } #endif #if 0 case WM_NCPAINT: // Skip for border-less windows if (!_settings.HasBorder) return 1; break; #endif case WM_ERASEBKGND: // Skip the window background erasing return 1; case WM_GETMINMAXINFO: { const auto minMax = reinterpret_cast(lParam); minMax->ptMinTrackSize.x = (int32)_settings.MinimumSize.X; minMax->ptMinTrackSize.y = (int32)_settings.MinimumSize.Y; if (_settings.MaximumSize.SumValues() > 0) { int32 borderWidth = 0, borderHeight = 0; if (_settings.HasBorder) { const DWORD windowStyle = GetWindowLongW(_handle, GWL_STYLE); const DWORD windowExStyle = GetWindowLongW(_handle, GWL_EXSTYLE); RECT borderRect = { 0, 0, 0, 0 }; AdjustWindowRectEx(&borderRect, windowStyle, false, windowExStyle); borderWidth = borderRect.right - borderRect.left; borderHeight = borderRect.bottom - borderRect.top; } minMax->ptMaxTrackSize.x = (int32)_settings.MaximumSize.X + borderWidth; minMax->ptMaxTrackSize.y = (int32)_settings.MaximumSize.Y + borderHeight; } // Include Windows task bar size into maximized tool window WINDOWPLACEMENT e; if (!IsFullscreen() && ((GetWindowPlacement(_handle, &e) && (e.showCmd == SW_SHOWMAXIMIZED || e.showCmd == SW_SHOWMINIMIZED)) || _isDuringMaximize)) { // Adjust the maximized size and position to fit the work area of the correct monitor const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); if (monitor != nullptr) { MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFO); if (::GetMonitorInfoW(monitor, &monitorInfo)) { minMax->ptMaxPosition.x = Math::Abs(monitorInfo.rcWork.left - monitorInfo.rcMonitor.left); minMax->ptMaxPosition.y = Math::Abs(monitorInfo.rcWork.top - monitorInfo.rcMonitor.top); minMax->ptMaxSize.x = Math::Abs(monitorInfo.rcWork.right - monitorInfo.rcWork.left); minMax->ptMaxSize.y = Math::Abs(monitorInfo.rcWork.bottom - monitorInfo.rcWork.top); } } } return 0; } case WM_SYSCOMMAND: // Prevent moving/sizing in full screen mode if (IsFullscreen()) { switch ((wParam & 0xFFF0)) { case SC_MOVE: case SC_SIZE: case SC_MAXIMIZE: case SC_KEYMENU: return 0; } } break; case WM_CREATE: return 0; case WM_SIZE: { if (SIZE_MINIMIZED == wParam) { // Get the minimized window position in workspace coordinates WINDOWPLACEMENT placement; placement.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(_handle, &placement); // Calculate client offsets from window borders and title bar RECT winRect = { 0, 0, static_cast(_clientSize.X), static_cast(_clientSize.Y) }; LONG style = GetWindowLong(_handle, GWL_STYLE); LONG exStyle = GetWindowLong(_handle, GWL_EXSTYLE); AdjustWindowRectEx(&winRect, style, FALSE, exStyle); // Calculate monitor offsets from taskbar position const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST); MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(monitor, &monitorInfo); // Convert the workspace coordinates to screen space and store it _minimizedScreenPosition = Float2( static_cast(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left), static_cast(placement.rcNormalPosition.top + monitorInfo.rcWork.top - monitorInfo.rcMonitor.top - winRect.top)); _minimized = true; _maximized = false; } else { RECT rcCurrentClient; GetClientRect(_handle, &rcCurrentClient); if (rcCurrentClient.top == 0 && rcCurrentClient.bottom == 0) { // Rapidly clicking the task bar to minimize and restore a window can cause a WM_SIZE message with SIZE_RESTORED when // the window has actually become minimized due to rapid change so just ignore this message. } else if (SIZE_MAXIMIZED == wParam) { _minimized = false; _maximized = true; CheckForWindowResize(); UpdateRegion(); } else if (SIZE_RESTORED == wParam) { if (_maximized) { _maximized = false; CheckForWindowResize(); UpdateRegion(); } else if (_minimized) { _minimized = false; CheckForWindowResize(); } else if (_isResizing) { // If we're neither maximized nor minimized, the window size is changing by the user dragging the window edges. // In this case, we don't resize yet -- we wait until the user stops dragging, and a WM_EXITSIZEMOVE message comes. UpdateRegion(); } else if (_isSwitchingFullScreen) { // Ignored } else { // This WM_SIZE come from resizing the window via an API like SetWindowPos() so resize CheckForWindowResize(); } } } break; } case WM_DPICHANGED: { // Maybe https://stackoverflow.com/a/45110656 _dpi = HIWORD(wParam); _dpiScale = (float)_dpi / (float)DefaultDPI; RECT* windowRect = (RECT*)lParam; SetWindowPos(_handle, nullptr, windowRect->left, windowRect->top, windowRect->right - windowRect->left, windowRect->bottom - windowRect->top, SWP_NOZORDER | SWP_NOACTIVATE); // TODO: Recalculate fonts return 0; } case WM_ENTERSIZEMOVE: { _isResizing = true; break; } case WM_EXITSIZEMOVE: _isResizing = false; CheckForWindowResize(); UpdateRegion(); break; case WM_SETFOCUS: OnGotFocus(); UpdateCursor(); if (_isClippingCursor && !_clipCursorSet) { _clipCursorSet = true; ClipCursor((RECT*)_clipCursorRect); } break; case WM_KILLFOCUS: if (_clipCursorSet) { _clipCursorSet = false; ClipCursor(nullptr); } OnLostFocus(); UpdateCursor(); break; case WM_ACTIVATEAPP: if (wParam == TRUE && !_focused) { OnGotFocus(); } else if (wParam == FALSE && _focused) { OnLostFocus(); if (IsFullscreen() && !_isSwitchingFullScreen) { SetIsFullscreen(false); } } UpdateCursor(); break; case WM_MENUCHAR: // A menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key so just ignore and don't beep return MAKELRESULT(0, MNC_CLOSE); case WM_SYSKEYDOWN: if (wParam == VK_F4) { LOG(Info, "Alt+F4 pressed"); Close(ClosingReason::User); return 0; } if (wParam == VK_RETURN) { LOG(Info, "Alt+Enter pressed"); SetIsFullscreen(!IsFullscreen()); return 0; } break; case WM_POWERBROADCAST: switch (wParam) { case PBT_APMQUERYSUSPEND: // TODO: add OnSystemSuspend event return true; case PBT_APMRESUMESUSPEND: // TODO: add OnSystemResume event return true; } break; case WM_CLOSE: Close(ClosingReason::User); return 0; case WM_DESTROY: { #if USE_EDITOR // Disable file dropping if (_settings.AllowDragAndDrop) { const auto result = RevokeDragDrop(_handle); if (result != S_OK) LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 2); } #endif // Quit PostQuitMessage(0); return 0; } } if (_settings.AllowInput) { if (WindowsInput::WndProc(this, msg, wParam, lParam)) return true; } return DefWindowProc(_handle, msg, wParam, lParam); } #if USE_EDITOR HGLOBAL duplicateGlobalMem(HGLOBAL hMem) { auto len = GlobalSize(hMem); auto source = GlobalLock(hMem); auto dest = GlobalAlloc(GMEM_FIXED, len); Platform::MemoryCopy(dest, source, len); GlobalUnlock(hMem); return dest; } DWORD dropEffect2OleEnum(DragDropEffect effect) { DWORD result; switch (effect) { case DragDropEffect::None: result = DROPEFFECT_NONE; break; case DragDropEffect::Copy: result = DROPEFFECT_COPY; break; case DragDropEffect::Move: result = DROPEFFECT_MOVE; break; case DragDropEffect::Link: result = DROPEFFECT_LINK; break; default: result = DROPEFFECT_NONE; break; } return result; } DragDropEffect dropEffectFromOleEnum(DWORD effect) { DragDropEffect result; switch (effect) { case DROPEFFECT_NONE: result = DragDropEffect::None; break; case DROPEFFECT_COPY: result = DragDropEffect::Copy; break; case DROPEFFECT_MOVE: result = DragDropEffect::Move; break; case DROPEFFECT_LINK: result = DragDropEffect::Link; break; default: result = DragDropEffect::None; break; } return result; } HANDLE StringToHandle(const StringView& str) { // Allocate and lock a global memory buffer. // Make it fixed data so we don't have to use GlobalLock const int32 length = str.Length(); char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1)); // Copy the string into the buffer as ANSI text StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length); ptr[length] = '\0'; return ptr; } void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source) { // Copy the source FORMATETC into dest *dest = *source; if (source->ptd) { // Allocate memory for the DVTARGETDEVICE if necessary dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); // Copy the contents of the source DVTARGETDEVICE into dest->ptd *(dest->ptd) = *(source->ptd); } } HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc); /// /// GUI data for Windows platform /// class WindowsGuiData : public IGuiData { private: Type _type; Array _data; public: /// /// Init /// WindowsGuiData() : _type(Type::Unknown) , _data(1) { } public: /// /// Init from Ole IDataObject /// /// Object void Init(IDataObject* pDataObj) { // Temporary data FORMATETC fmtetc; STGMEDIUM stgmed; // Clear _type = Type::Unknown; _data.Clear(); // Check type fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) { // Text _type = Type::Text; // Get data char* text = static_cast(GlobalLock(stgmed.hGlobal)); _data.Add(String(text)); GlobalUnlock(stgmed.hGlobal); ReleaseStgMedium(&stgmed); } else { fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) { // Unicode Text _type = Type::Text; // Get data Char* text = static_cast(GlobalLock(stgmed.hGlobal)); _data.Add(String(text)); GlobalUnlock(stgmed.hGlobal); ReleaseStgMedium(&stgmed); } else { fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) { // Files _type = Type::Files; // Get data Char item[MAX_PATH]; HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal)); UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); for (UINT i = 0; i < filesCount; i++) { if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0) { _data.Add(String(item)); } } GlobalUnlock(stgmed.hGlobal); ReleaseStgMedium(&stgmed); } } } } public: // [IGuiData] Type GetType() const override { return _type; } String GetAsText() const override { String result; if (_type == Type::Text) { result = _data[0]; } return result; } void GetAsFiles(Array* files) const override { if (_type == Type::Files) { files->Add(_data); } } }; /// /// Tool class for Windows Ole support /// class WindowsEnumFormatEtc : public IEnumFORMATETC { private: ULONG _refCount; ULONG _index; ULONG _formatsCount; FORMATETC* _formatEtc; public: WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats) : _refCount(1) , _index(0) , _formatsCount(nNumFormats) , _formatEtc(nullptr) { // Allocate memory _formatEtc = new FORMATETC[nNumFormats]; // Copy the FORMATETC structures for (int32 i = 0; i < nNumFormats; i++) { DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]); } } ~WindowsEnumFormatEtc() { if (_formatEtc) { for (uint32 i = 0; i < _formatsCount; i++) { if (_formatEtc[i].ptd) { CoTaskMemFree(_formatEtc[i].ptd); } } delete[] _formatEtc; } } public: HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override { // Check to see what interface has been requested if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown) { AddRef(); *ppvObject = this; return S_OK; } // No interface *ppvObject = nullptr; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { _InterlockedIncrement(&_refCount); return _refCount; } ULONG STDMETHODCALLTYPE Release() override { ULONG ulRefCount = _InterlockedDecrement(&_refCount); if (_refCount == 0) { delete this; } return ulRefCount; } // [IEnumFormatEtc] HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override { ULONG copied = 0; // validate arguments if (celt == 0 || pFormatEtc == nullptr) return E_INVALIDARG; // copy FORMATETC structures into caller's buffer while (_index < _formatsCount && copied < celt) { DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]); copied++; _index++; } // store result if (pceltFetched != nullptr) *pceltFetched = copied; // did we copy all that was requested? return (copied == celt) ? S_OK : S_FALSE; } HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override { _index += celt; return (_index <= _formatsCount) ? S_OK : S_FALSE; } HRESULT STDMETHODCALLTYPE Reset() override { _index = 0; return S_OK; } HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override { HRESULT result; // Make a duplicate enumerator result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc); if (result == S_OK) { // Manually set the index state static_cast(*ppEnumFormatEtc)->_index = _index; } return result; } }; HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc) { if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr) return E_INVALIDARG; *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats); return *ppEnumFormatEtc ? S_OK : E_OUTOFMEMORY; } /// /// Drag drop source and data container for Ole /// class WindowsDragSource : public IDataObject, public IDropSource { private: ULONG _refCount; int32 _formatsCount; FORMATETC* _formatEtc; STGMEDIUM* _stgMedium; public: WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count) : _refCount(1) , _formatsCount(count) , _formatEtc(nullptr) , _stgMedium(nullptr) { // Allocate memory _formatEtc = new FORMATETC[count]; _stgMedium = new STGMEDIUM[count]; // Copy descriptors for (int32 i = 0; i < count; i++) { _formatEtc[i] = fmtetc[i]; _stgMedium[i] = stgmed[i]; } } virtual ~WindowsDragSource() { delete[] _formatEtc; delete[] _stgMedium; } public: // [IUnknown] HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override { // Check to see what interface has been requested if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource) { AddRef(); *ppvObject = this; return S_OK; } // No interface *ppvObject = nullptr; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { _InterlockedIncrement(&_refCount); return _refCount; } ULONG STDMETHODCALLTYPE Release() override { ULONG ulRefCount = _InterlockedDecrement(&_refCount); if (_refCount == 0) { delete this; } return ulRefCount; } // [IDropSource] HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override { // If the Escape key has been pressed since the last call, cancel the drop if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON) return DRAGDROP_S_CANCEL; // If the LeftMouse button has been released, then do the drop! if ((grfKeyState & MK_LBUTTON) == 0) return DRAGDROP_S_DROP; // Continue with the drag-drop return S_OK; } HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override { // TODO: allow to use custom mouse cursor during drop and drag operation return DRAGDROP_S_USEDEFAULTCURSORS; } // [IDataObject] HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override { if (pformatetcIn == nullptr || pmedium == nullptr) return E_INVALIDARG; // Try to match the specified FORMATETC with one of our supported formats int32 index = lookupFormatEtc(pformatetcIn); if (index == INVALID_INDEX) return DV_E_FORMATETC; // Found a match - transfer data into supplied storage medium pmedium->tymed = _formatEtc[index].tymed; pmedium->pUnkForRelease = nullptr; // Copy the data into the caller's storage medium switch (_formatEtc[index].tymed) { case TYMED_HGLOBAL: pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal); break; default: return DV_E_FORMATETC; } return S_OK; } HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override { return DATA_E_FORMATETC; } HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override { return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK; } HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override { // Apparently we have to set this field to NULL even though we don't do anything else pformatetcOut->ptd = nullptr; return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override { // Only the get direction is supported for OLE if (dwDirection == DATADIR_GET) { // TODO: use SHCreateStdEnumFmtEtc API call return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc); } // The direction specified is not supported for drag+drop return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override { return OLE_E_ADVISENOTSUPPORTED; } private: int32 lookupFormatEtc(FORMATETC* pFormatEtc) const { // Check each of our formats in turn to see if one matches for (int32 i = 0; i < _formatsCount; i++) { if ((_formatEtc[i].tymed & pFormatEtc->tymed) && _formatEtc[i].cfFormat == pFormatEtc->cfFormat && _formatEtc[i].dwAspect == pFormatEtc->dwAspect) { // Return index of stored format return i; } } // Format not found return INVALID_INDEX; } }; WindowsGuiData GuiDragDropData; DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) { // Create background worker that will keep updating GUI (perform rendering) const auto task = New(); Task::StartNew(task); while (task->GetState() == TaskState::Queued) Platform::Sleep(1); // Create descriptors FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr }; // Create a HGLOBAL inside the storage medium stgmed.hGlobal = StringToHandle(data); // Create drop source auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1); // Do the drag drop operation DWORD dwEffect; HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect); // Wait for job end Platform::AtomicStore(&task->ExitFlag, 1); task->Wait(); // Release allocated data dropSource->Release(); ReleaseStgMedium(&stgmed); // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop) if (Input::GetMouseButton(MouseButton::Left)) { ::POINT point; ::GetCursorPos(&point); Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this); } return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None; } HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) { POINT p = { pt.x, pt.y }; ::ScreenToClient(_handle, &p); GuiDragDropData.Init((IDataObject*)pDataObj); DragDropEffect effect = DragDropEffect::None; OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); Focus(); *pdwEffect = dropEffect2OleEnum(effect); return S_OK; } HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) { POINT p = { pt.x, pt.y }; ::ScreenToClient(_handle, &p); DragDropEffect effect = DragDropEffect::None; OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); *pdwEffect = dropEffect2OleEnum(effect); return S_OK; } HRESULT WindowsWindow::DragLeave() { OnDragLeave(); return S_OK; } HRESULT WindowsWindow::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) { POINT p = { pt.x, pt.y }; ::ScreenToClient(_handle, &p); GuiDragDropData.Init((IDataObject*)pDataObj); DragDropEffect effect = DragDropEffect::None; OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); *pdwEffect = dropEffect2OleEnum(effect); return S_OK; } #else DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) { return DragDropEffect::None; } #endif #endif