1944 lines
55 KiB
C++
1944 lines
55 KiB
C++
// 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 <propidl.h>
|
|
#if USE_EDITOR
|
|
#include <oleidl.h>
|
|
#include <shellapi.h>
|
|
#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<HWND>(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<float>(rect.left), static_cast<float>(rect.top));
|
|
}
|
|
|
|
Float2 WindowsWindow::GetSize() const
|
|
{
|
|
ASSERT(HasHWND());
|
|
|
|
RECT rect;
|
|
GetWindowRect(_handle, &rect);
|
|
return Float2(static_cast<float>(rect.right - rect.left), static_cast<float>(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<LONG>(screenPos.X);
|
|
p.y = static_cast<LONG>(screenPos.Y);
|
|
::ScreenToClient(_handle, &p);
|
|
return Float2(static_cast<float>(p.x), static_cast<float>(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<LONG>(clientPos.X);
|
|
p.y = static_cast<LONG>(clientPos.Y);
|
|
::ClientToScreen(_handle, &p);
|
|
return Float2(static_cast<float>(p.x), static_cast<float>(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<BYTE>(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<float>(width), static_cast<float>(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);
|
|
}
|
|
::SetCursor(nullptr);
|
|
return;
|
|
}
|
|
else if (_lastCursorHidden)
|
|
{
|
|
_lastCursorHidden = false;
|
|
while(::ShowCursor(TRUE) < 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<float>(WINDOWS_GET_X_LPARAM(lParam)), static_cast<float>(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<int32>(WindowHitCodes::Client);
|
|
|
|
const Float2 mouse(static_cast<float>(WINDOWS_GET_X_LPARAM(lParam)), static_cast<float>(WINDOWS_GET_Y_LPARAM(lParam)));
|
|
WindowHitCodes hit = WindowHitCodes::Client;
|
|
bool handled = false;
|
|
OnHitTest(mouse, hit, handled);
|
|
if (handled)
|
|
return static_cast<int32>(hit);
|
|
break;
|
|
}
|
|
case WM_NCLBUTTONDOWN:
|
|
{
|
|
bool result = false;
|
|
OnLeftButtonHit(static_cast<WindowHitCodes>(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<MINMAXINFO*>(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<LONG>(_clientSize.X), static_cast<LONG>(_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<float>(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left),
|
|
static_cast<float>(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<char*>(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<DVTARGETDEVICE*>(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);
|
|
|
|
/// <summary>
|
|
/// GUI data for Windows platform
|
|
/// </summary>
|
|
class WindowsGuiData : public IGuiData
|
|
{
|
|
private:
|
|
Type _type;
|
|
Array<String> _data;
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Init
|
|
/// </summary>
|
|
WindowsGuiData()
|
|
: _type(Type::Unknown)
|
|
, _data(1)
|
|
{
|
|
}
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Init from Ole IDataObject
|
|
/// </summary>
|
|
/// <param name="pDataObj">Object</param>
|
|
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<char*>(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<Char*>(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<HDROP>(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<String>* files) const override
|
|
{
|
|
if (_type == Type::Files)
|
|
{
|
|
files->Add(_data);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Tool class for Windows Ole support
|
|
/// </summary>
|
|
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<WindowsEnumFormatEtc*>(*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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drag drop source and data container for Ole
|
|
/// </summary>
|
|
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<DoDragDropJob>();
|
|
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<float>(p.x), static_cast<float>(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<float>(p.x), static_cast<float>(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<float>(p.x), static_cast<float>(p.y)), effect);
|
|
*pdwEffect = dropEffect2OleEnum(effect);
|
|
return S_OK;
|
|
}
|
|
|
|
#else
|
|
|
|
DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
|
|
{
|
|
return DragDropEffect::None;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|