1
0
Fork 0
spectre/source/Platform/Win32/Win32Display.cpp
2022-10-01 16:09:42 +02:00

428 lines
9.4 KiB
C++

#include <Windows.h>
#include <Spectre/Display/Display.h>
#include <Spectre/System/Log.h>
#include "Win32Application.h"
#include "Win32Internal.h"
#include "Win32Display.h"
namespace sp {
#define WND_CLASSNAME "SPECTRE_WIN32_WNDCLASS"
#if defined(_WIN64) && !defined(GWL_USERDATA)
// x64 undefines this. So we define it again.
#define GWL_USERDATA GWLP_USERDATA
#endif /* defined(_WIN64) && !defined(GWL_USERDATA) */
static bool firstTime = true;
Win32Display::Win32Display() :
m_handle (NULL),
m_icon (0),
m_cursor (0),
m_inResizeModalLoop (false),
m_minSize (200, 200)
{
}
Win32Display::~Win32Display()
{
if (m_icon) {
DestroyIcon(m_icon);
}
if (m_handle) {
DestroyWindow(m_handle);
}
}
bool Win32Display::create(DisplayDescription description)
{
DWORD flags;
Vector2i pos;
if (firstTime) {
registerClass();
firstTime = false;
}
// Set window to center and set decoration flags.
pos = centerWindow(description.mode.width, description.mode.height);
flags = getWin32Flags(description.decoration);
// Create window.
m_handle = CreateWindowExA(0, WND_CLASSNAME, "", flags,
pos.x, pos.y, description.mode.width, description.mode.height,
NULL, NULL, GetModuleHandle(NULL), (LPVOID) this);
if (!m_handle) {
Log::error("Win32 - Could not create window: %s", Win32GetMessage(GetLastError()));
return false;
}
setSize(description.mode.width, description.mode.height);
// Store the size for use later.
m_size = getSize();
return true;
}
void Win32Display::destroy()
{
if (m_handle) {
DestroyWindow(m_handle);
m_handle = NULL;
}
}
void* Win32Display::getHandle() const
{
return m_handle;
}
bool Win32Display::isValid()
{
return m_handle != NULL;
}
void Win32Display::setSize(unsigned int width, unsigned int height)
{
int w, h;
RECT rect = {0, 0, (LONG) width, (LONG) height};
AdjustWindowRect(&rect, GetWindowLong(m_handle, GWL_STYLE), false);
w = rect.right - rect.left;
h = rect.bottom - rect.top;
::SetWindowPos(m_handle, NULL, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER);
}
Vector2u Win32Display::getSize() const
{
RECT rect;
Vector2u size;
if (GetClientRect(m_handle, &rect)) {
size.x = rect.right - rect.left;
size.y = rect.bottom - rect.top;
}
return size;
}
void Win32Display::setPosition(unsigned int x, unsigned int y)
{
::SetWindowPos(m_handle, NULL, x, y, 0, 0, SWP_NOSIZE);
}
Vector2u Win32Display::getPosition() const
{
RECT r;
GetWindowRect(m_handle, &r);
return Vector2u(r.left, r.top);
}
void Win32Display::setVisible(bool visible)
{
::ShowWindow(m_handle, visible ? SW_SHOW : SW_HIDE);
}
void Win32Display::setDecoration(unsigned decoration)
{
::SetWindowLong(m_handle, GWL_STYLE, getWin32Flags(decoration));
}
void Win32Display::minimize()
{
::ShowWindow(m_handle, SW_MINIMIZE);
}
void Win32Display::maximize()
{
::ShowWindow(m_handle, SW_MAXIMIZE);
}
void Win32Display::setCaption(const std::string& caption)
{
::SetWindowText(m_handle, caption.c_str());
}
void Win32Display::showCursor(bool value)
{
if (value) {
m_cursor = ::LoadCursor(NULL, IDC_ARROW);
} else {
m_cursor = 0;
}
::SetCursor(m_cursor);
}
void Win32Display::grabCursor(bool value)
{
if (value) {
RECT rect;
GetClientRect(m_handle, &rect);
MapWindowPoints(m_handle, NULL, reinterpret_cast<LPPOINT>(&rect), 2);
ClipCursor(&rect);
} else {
ClipCursor(NULL);
}
}
void Win32Display::setIcon(unsigned int width, unsigned int height, const uint8_t *pixels)
{
::HDC hdc;
::ICONINFO ii;
::BITMAPV5HEADER bi;
::HBITMAP bmp_color, bmp_mask;
unsigned char *bmp_data = NULL;
::ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(bi);
bi.bV5Width = width;
bi.bV5Height = height;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_RGB;
bi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bi.bV5Intent = LCS_GM_IMAGES;
hdc = ::GetDC(NULL);
bmp_color = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&bi),
DIB_RGB_COLORS, (void **) &bmp_data, NULL, 0);
::ReleaseDC(NULL, hdc);
if (!bmp_color) {
Log::error("Win32Display::setIcon() - Failed to create RGBA bitmap");
return;
}
bmp_mask = CreateBitmap(width, height, 1, 1, NULL);
if (!bmp_mask) {
Log::error("Win32Display::setIcon() - Failed to create mask bitmap");
::DeleteObject(bmp_color);
return;
}
// pixels are always RGBA, WinApi wants BGRA
for(int i = 0; i < width * height; i++) {
bmp_data[i * 4 + 0] = pixels[i * 4 + 2]; // R <- B
bmp_data[i * 4 + 1] = pixels[i * 4 + 1]; // G <- G
bmp_data[i * 4 + 2] = pixels[i * 4 + 0]; // B <- R
bmp_data[i * 4 + 3] = pixels[i * 4 + 3]; // A <- A
}
::ZeroMemory(&ii, sizeof(ii));
ii.fIcon = TRUE;
ii.xHotspot = 0;
ii.yHotspot = 0;
ii.hbmColor = bmp_color;
ii.hbmMask = bmp_mask;
if (m_icon) {
::DestroyIcon(m_icon);
}
m_icon = CreateIconIndirect(&ii);
if (m_icon) {
::SendMessage(m_handle, WM_SETICON, ICON_SMALL, (LPARAM) m_icon);
::SendMessage(m_handle, WM_SETICON, ICON_BIG, (LPARAM) m_icon);
} else {
Log::error("Win32Display::setIcon() - Failed to create icon");
}
::DeleteObject(bmp_color);
::DeleteObject(bmp_mask);
}
void Win32Display::registerClass()
{
WNDCLASSA wndcl;
ZeroMemory(&wndcl, sizeof(wndcl));
wndcl.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wndcl.lpfnWndProc = Win32Display::staticWndProc;
wndcl.hInstance = ::GetModuleHandle(NULL);
wndcl.lpszClassName = WND_CLASSNAME;
::RegisterClass(&wndcl);
}
DWORD Win32Display::getWin32Flags(unsigned int flags)
{
DWORD win32_flags = WS_VISIBLE;
if (flags == DisplayDecorate::Empty) {
win32_flags |= WS_POPUP;
} else {
if (flags & DisplayDecorate::Menu) {
win32_flags |= WS_CAPTION | WS_MINIMIZEBOX;
}
if (flags & DisplayDecorate::Resize) {
win32_flags |= WS_THICKFRAME | WS_MAXIMIZEBOX;
}
if (flags & DisplayDecorate::Close) {
win32_flags |= WS_SYSMENU;
}
}
return win32_flags;
}
Vector2i Win32Display::centerWindow(int width, int height)
{
Vector2i v;
v.x = (::GetSystemMetrics(SM_CXSCREEN) - width) / 2;
v.y = (::GetSystemMetrics(SM_CYSCREEN) - height) / 2;
return v;
}
void Win32Display::enterFullscreen(DisplayMode mode)
{
LONG rc;
::DEVMODEW dev;
dev.dmSize = sizeof(dev);
dev.dmPelsWidth = mode.width;
dev.dmPelsHeight = mode.height;
dev.dmBitsPerPel = mode.bpp;
dev.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
rc = ::ChangeDisplaySettingsW(&dev, CDS_FULLSCREEN);
if (rc != DISP_CHANGE_SUCCESSFUL) {
const char *msg;
switch(rc) {
case DISP_CHANGE_BADDUALVIEW :
msg = "The system is DualView capable"; break;
case DISP_CHANGE_BADFLAGS :
msg = "Invalid flags given"; break;
case DISP_CHANGE_BADPARAM :
msg = "Invalid parameter"; break;
case DISP_CHANGE_BADMODE :
msg = "Resolution not supported"; break;
case DISP_CHANGE_FAILED :
msg = "Display driver failed to set mode"; break;
case DISP_CHANGE_NOTUPDATED :
msg = "Unable to write settings to the registry"; break;
case DISP_CHANGE_RESTART :
msg = "System restart required"; break;
default :
msg = "Unkown error";
}
sp::Log::error("Win32: Failed to switch to fullscreen mode: %s.", msg);
return;
}
::SetWindowLong(m_handle, GWL_STYLE, WS_VISIBLE | WS_POPUP);
::SetWindowPos(m_handle, HWND_TOP, 0, 0, mode.width, mode.height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
grabCursor(true);
m_fs_mode = mode;
}
void Win32Display::exitFullscreen()
{
if (!m_fs_mode.empty()) {
// Restore to previous mode.
::ChangeDisplaySettingsW(NULL, 0);
m_fs_mode = DisplayMode();
}
}
void Win32Display::processResizeMessage(const Vector2u& new_size)
{
// Check if the size has actually changed.
if (m_size != new_size) {
// Update the size and notify the higher layer.
m_size = new_size;
onReshape(m_size.x, m_size.y);
}
}
void Win32Display::processMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message) {
case WM_SETCURSOR :
if (LOWORD(lParam) == HTCLIENT) {
::SetCursor(m_cursor);
}
break;
case WM_DESTROY :
PostQuitMessage(0);
break;
case WM_SETFOCUS :
Log::debug("WM_SETFOCUS");
if (m_fs_mode.empty() == false) {
enterFullscreen(m_fs_mode);
}
break;
case WM_KILLFOCUS :
Log::debug("WM_KILLFOCUS");
// If in fullscreen mode.
if (m_fs_mode.empty() == false) {
// Switch to window mode.
::ChangeDisplaySettingsW(NULL, 0);
// also minimize the window to get it out of the way.
minimize();
}
grabCursor(false);
break;
case WM_SIZE :
if (!m_inResizeModalLoop && (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)) {
Vector2u size(LOWORD(lParam), HIWORD(lParam));
processResizeMessage(size);
}
break;
case WM_GETMINMAXINFO :
{
MINMAXINFO* info = (MINMAXINFO*)lParam;
info->ptMinTrackSize.x = m_minSize.x;
info->ptMinTrackSize.y = m_minSize.y;
info->ptMaxTrackSize.x = 99999;
info->ptMaxTrackSize.y = 99999;
break;
}
case WM_ENTERSIZEMOVE :
m_inResizeModalLoop = true;
break;
case WM_EXITSIZEMOVE :
m_inResizeModalLoop = false;
processResizeMessage(getSize());
break;
}
}
LRESULT CALLBACK Win32Display::staticWndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
{
Win32Display *display;
if (message == WM_NCCREATE) {
LONG_PTR ptr = (LONG_PTR) ((LPCREATESTRUCT)lParam)->lpCreateParams;
::SetWindowLongPtr(handle, GWL_USERDATA, ptr);
display = (Win32Display*) ptr;
} else {
display = (Win32Display*) ::GetWindowLongPtr(handle, GWL_USERDATA);
}
if (display) {
display->processMessage(message, wParam, lParam);
}
return DefWindowProc(handle, message, wParam, lParam);
}
} // namespace sp