openclonk/src/platform/C4WindowWin32.cpp

1088 lines
39 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* A wrapper class to OS dependent event and window interfaces, WIN32 version */
#include "C4Include.h"
#include "C4ForbidLibraryCompilation.h"
#include "platform/C4Window.h"
#include "C4Version.h"
#include "editor/C4Console.h"
#include "editor/C4ViewportWindow.h"
#include "game/C4Application.h"
#include "game/C4FullScreen.h"
#include "game/C4GraphicsSystem.h"
#include "game/C4Viewport.h"
#include "graphics/C4DrawGL.h"
#include "gui/C4MouseControl.h"
#include "lib/C4Rect.h"
#include "platform/C4AppWin32Impl.h"
#include "platform/StdRegistry.h"
#include "res/resource.h"
#include "platform/C4windowswrapper.h"
#include <mmsystem.h>
#include <shellapi.h>
#define C4ViewportClassName L"C4Viewport"
#define C4FullScreenClassName L"C4FullScreen"
#define ConsoleDlgClassName L"C4GUIdlg"
#define ConsoleDlgWindowStyle (WS_VISIBLE | WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX)
/** Convert certain keys to unix scancodes (those that differ from unix scancodes) */
static void ConvertToUnixScancode(WPARAM wParam, C4KeyCode *scancode, bool extended)
{
C4KeyCode &s = *scancode;
switch(wParam)
{
case VK_HOME: s = K_HOME; break;
case VK_END: s = K_END; break;
case VK_PRIOR: s = K_PAGEUP; break;
case VK_NEXT: s = K_PAGEDOWN; break;
case VK_UP: s = K_UP; break;
case VK_DOWN: s = K_DOWN; break;
case VK_LEFT: s = K_LEFT; break;
case VK_RIGHT: s = K_RIGHT; break;
case VK_CLEAR: s = K_CENTER; break;
case VK_INSERT: s = K_INSERT; break;
case VK_DELETE: s = K_DELETE; break;
case VK_LWIN: s = K_WIN_L; break;
case VK_RWIN: s = K_WIN_R; break;
case VK_MENU: s = K_MENU; break;
case VK_PAUSE: s = K_PAUSE; break;
case VK_PRINT: s = K_PRINT; break;
case VK_RCONTROL: s = K_CONTROL_R; break;
case VK_CONTROL: s = (extended ? K_CONTROL_R : K_CONTROL_L); break;
case VK_NUMLOCK: s = K_NUM; break;
case VK_NUMPAD1: s = K_NUM1; break;
case VK_NUMPAD2: s = K_NUM2; break;
case VK_NUMPAD3: s = K_NUM3; break;
case VK_NUMPAD4: s = K_NUM4; break;
case VK_NUMPAD5: s = K_NUM5; break;
case VK_NUMPAD6: s = K_NUM6; break;
case VK_NUMPAD7: s = K_NUM7; break;
case VK_NUMPAD8: s = K_NUM8; break;
case VK_NUMPAD9: s = K_NUM9; break;
case VK_NUMPAD0: s = K_NUM0; break;
}
}
LRESULT APIENTRY FullScreenWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static bool NativeCursorShown = true;
POINT p;
p.x = GET_X_LPARAM(lParam);
p.y = GET_Y_LPARAM(lParam);
// compute scancode
C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF;
bool extended = ((lParam & 0x01000000) != 0);
ConvertToUnixScancode(wParam, &scancode, extended);
// Process message
switch (uMsg)
{
case WM_ACTIVATE:
wParam = (LOWORD(wParam)==WA_ACTIVE || LOWORD(wParam)==WA_CLICKACTIVE);
// fall through to next case
case WM_ACTIVATEAPP:
Application.Active = wParam != 0;
#ifndef USE_CONSOLE
if (pGL)
{
if (Application.Active)
{
// restore textures
if (Application.FullScreenMode())
{
Application.SetVideoMode(Application.GetConfigWidth(), Application.GetConfigHeight(), Config.Graphics.RefreshRate, Config.Graphics.Monitor, true);
}
}
else
{
if (Application.FullScreenMode())
{
::ChangeDisplaySettings(nullptr, 0);
::ShowWindow(hwnd, SW_MINIMIZE);
}
}
}
#endif
// redraw background
::GraphicsSystem.InvalidateBg();
// Redraw after task switch
if (Application.Active)
::GraphicsSystem.Execute();
// update cursor clip
::MouseControl.UpdateClip();
return false;
case WM_PAINT:
// Redraw after task switch
if (Application.Active)
::GraphicsSystem.Execute();
break;
case WM_DESTROY:
Application.Quit();
return 0;
case WM_CLOSE:
FullScreen.Close();
return 0;
case MM_MCINOTIFY:
if (wParam == MCI_NOTIFY_SUCCESSFUL)
Application.MusicSystem.NotifySuccess();
return true;
case WM_KEYUP:
if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr))
return 0;
break;
case WM_KEYDOWN:
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr))
return 0;
break;
case WM_SYSKEYDOWN:
if (wParam == 18) break;
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr))
{
// Remove handled message from queue to prevent Windows "standard" sound for unprocessed system message
MSG msg;
PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE);
return 0;
}
break;
case WM_CHAR:
{
// UTF-8 has 1 to 4 data bytes, and we need a terminating \0
char c[5] = {0,0,0,0,0};
if(!WideCharToMultiByte(CP_UTF8, 0L, reinterpret_cast<LPCWSTR>(&wParam), 1, c, 4, nullptr, nullptr))
return 0;
// GUI: forward
if (::pGUI->CharIn(c))
return 0;
return false;
}
case WM_USER_LOG:
if (SEqual2((const char *)lParam, "IDS_"))
Log(LoadResStr((const char *)lParam));
else
Log((const char *)lParam);
return false;
case WM_LBUTTONDOWN:
C4GUI::MouseMove(C4MC_Button_LeftDown,p.x,p.y,wParam, nullptr);
break;
case WM_LBUTTONUP: C4GUI::MouseMove(C4MC_Button_LeftUp, p.x, p.y, wParam, nullptr); break;
case WM_RBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_RightDown, p.x, p.y, wParam, nullptr); break;
case WM_RBUTTONUP: C4GUI::MouseMove(C4MC_Button_RightUp, p.x, p.y, wParam, nullptr); break;
case WM_LBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_LeftDouble, p.x, p.y, wParam, nullptr); break;
case WM_RBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_RightDouble, p.x, p.y, wParam, nullptr); break;
case WM_MOUSEWHEEL:
// the coordinates are screen-coordinates here (but only on this uMsg),
// we need to convert them to client area coordinates
ScreenToClient(hwnd, &p);
C4GUI::MouseMove(C4MC_Button_Wheel, p.x, p.y, wParam, nullptr);
break;
case WM_MOUSEMOVE:
C4GUI::MouseMove(C4MC_Button_None, p.x, p.y, wParam, nullptr);
// Hide cursor in client area
if (NativeCursorShown)
{
NativeCursorShown = false;
ShowCursor(FALSE);
}
break;
case WM_NCMOUSEMOVE:
// Show cursor on window frame
if (!NativeCursorShown)
{
NativeCursorShown = true;
ShowCursor(TRUE);
}
break;
case WM_SIZE:
// Notify app about render window size change
switch (wParam)
{
case SIZE_RESTORED:
case SIZE_MAXIMIZED:
::Application.OnResolutionChanged(p.x, p.y);
if(Application.pWindow) // this might be called from C4Window::Init in which case Application.pWindow is not yet set
::SetWindowPos(Application.pWindow->renderwnd, nullptr, 0, 0, p.x, p.y, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER);
break;
}
break;
case WM_INPUTLANGCHANGE:
::Application.OnKeyboardLayoutChanged();
break;
case WM_SYSCOMMAND:
// The user pressed Alt to open the system menu. This enters a modal
// loop which stops us from event processing, so prevent it. Users
// can still open the system menu by clicking the window's icon.
if ((wParam & 0xFFF0) == SC_KEYMENU && lParam == 0)
return 0;
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static C4KeyCode msg2scancode(MSG *msg)
{
// compute scancode
C4KeyCode scancode = (((unsigned int)msg->lParam) >> 16) & 0xFF;
bool extended = ((msg->lParam & 0x01000000) != 0);
ConvertToUnixScancode(msg->wParam, &scancode, extended);
return scancode;
}
bool ConsoleHandleWin32KeyboardMessage(MSG *msg)
{
switch (msg->message)
{
case WM_KEYDOWN:
if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Down, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(msg->lParam & 0x40000000), nullptr)) return true;
break;
case WM_KEYUP:
if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Up, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr)) return false;
break;
case WM_SYSKEYDOWN:
if (msg->wParam == 18) break; // VK_MENU (Alt)
if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Down, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(msg->lParam & 0x40000000), nullptr)) return false;
break;
}
return false;
}
LRESULT APIENTRY ViewportWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Determine viewport
C4Viewport *cvp;
if (!(cvp=::Viewports.GetViewport(hwnd)))
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
// compute scancode
C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF;
bool extended = ((lParam & 0x01000000) != 0);
ConvertToUnixScancode(wParam, &scancode, extended);
// Process message
switch (uMsg)
{
//---------------------------------------------------------------------------------------------------------------------------
case WM_KEYDOWN:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
switch (wParam)
{
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case VK_SCROLL:
// key bound to this specific viewport. Don't want to pass this through C4Game...
cvp->TogglePlayerLock();
break;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
default:
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) return 0;
break;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
}
break;
//---------------------------------------------------------------------------------------------------------------------------
case WM_KEYUP:
if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr)) return 0;
break;
//------------------------------------------------------------------------------------------------------------
case WM_SYSKEYDOWN:
if (wParam == 18) break;
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) return 0;
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_DESTROY:
StoreWindowPosition(hwnd, FormatString("Viewport%i",cvp->Player+1).getData(), Config.GetSubkeyPath("Console"));
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_CREATE:
DragAcceptFiles(hwnd, TRUE);
break;
case WM_CLOSE:
cvp->pWindow->Close();
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_DROPFILES:
{
HDROP hDrop = (HDROP)(HANDLE) wParam;
if (!Console.Editing) { Console.Message(LoadResStr("IDS_CNS_NONETEDIT")); return false; }
int32_t iFileNum = DragQueryFile(hDrop,0xFFFFFFFF,nullptr,0);
POINT pntPoint;
DragQueryPoint(hDrop,&pntPoint);
wchar_t szFilename[500+1];
for (int32_t cnt=0; cnt<iFileNum; cnt++)
{
DragQueryFileW(hDrop,cnt,szFilename,500);
cvp->DropFile(StdStrBuf(szFilename).getData(), (float)pntPoint.x, (float)pntPoint.y);
}
DragFinish(hDrop);
break;
}
//----------------------------------------------------------------------------------------------------------------------------------
case WM_USER_DROPDEF:
Game.DropDef(C4ID(lParam),cvp->GetViewX()+float(LOWORD(wParam))/cvp->Zoom,cvp->GetViewY()+float(HIWORD(wParam)/cvp->Zoom));
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_SIZE:
case WM_SIZING:
cvp->UpdateOutputSize();
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_PAINT:
::GraphicsSystem.Execute();
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_HSCROLL:
switch (LOWORD(wParam))
{
case SB_THUMBTRACK:
case SB_THUMBPOSITION: cvp->SetViewX(float(HIWORD(wParam))/cvp->Zoom); break;
case SB_LINELEFT: cvp->ScrollView(-ViewportScrollSpeed, 0.0f); break;
case SB_LINERIGHT: cvp->ScrollView(+ViewportScrollSpeed, 0.0f); break;
case SB_PAGELEFT: cvp->ScrollView(-cvp->ViewWdt/cvp->Zoom, 0.0f); break;
case SB_PAGERIGHT: cvp->ScrollView(+cvp->ViewWdt/cvp->Zoom, 0.0f); break;
}
cvp->Execute();
cvp->ScrollBarsByViewPosition();
return 0;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_VSCROLL:
switch (LOWORD(wParam))
{
case SB_THUMBTRACK:
case SB_THUMBPOSITION: cvp->SetViewY(float(HIWORD(wParam))/cvp->Zoom); break;
case SB_LINEUP: cvp->ScrollView(0.0f,-ViewportScrollSpeed); break;
case SB_LINEDOWN: cvp->ScrollView(0.0f,+ViewportScrollSpeed); break;
case SB_PAGEUP: cvp->ScrollView(0.0f,-cvp->ViewWdt/cvp->Zoom); break;
case SB_PAGEDOWN: cvp->ScrollView(0.0f,+cvp->ViewWdt/cvp->Zoom); break;
}
cvp->Execute();
cvp->ScrollBarsByViewPosition();
return 0;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_ACTIVATE:
// Keep editing dialogs on top of the current viewport, but don't make them
// float on other windows (i.e., no HWND_TOPMOST).
// Also, don't use SetParent, since that activates the window, which in turn
// posts a new WM_ACTIVATE to us, and so on, ultimately leading to a hang.
if (LOWORD(wParam) == WA_INACTIVE)
{
Console.Win32KeepDialogsFloating();
}
else
{
// FALLTHROUGH
case WM_MOUSEACTIVATE:
// WM_MOUSEACTIVATE is emitted when the user hovers over a window and pushes a mouse button.
// Setting the window owner here avoids z-order flickering.
Console.Win32KeepDialogsFloating(hwnd);
}
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_SYSCOMMAND:
// The user pressed Alt to open the system menu. This enters a modal
// loop which stops us from event processing, so prevent it. Users
// can still open the system menu by clicking the window's icon.
if ((wParam & 0xFFF0) == SC_KEYMENU && lParam == 0)
return 0;
break;
}
POINT p;
p.x = GET_X_LPARAM(lParam);
p.y = GET_Y_LPARAM(lParam);
// Viewport mouse control
if (::MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay))
{
switch (uMsg)
{
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_LeftDown, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONUP: C4GUI::MouseMove(C4MC_Button_LeftUp, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_RightDown, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONUP: C4GUI::MouseMove(C4MC_Button_RightUp, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_LeftDouble, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_RightDouble, p.x, p.y, wParam, cvp); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_MOUSEMOVE:
if ( Inside<int32_t>(p.x-cvp->DrawX,0,cvp->ViewWdt-1)
&& Inside<int32_t>(p.y-cvp->DrawY,0,cvp->ViewHgt-1) )
SetCursor(nullptr);
C4GUI::MouseMove(C4MC_Button_None, p.x, p.y, wParam, cvp);
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_MOUSEWHEEL:
ScreenToClient(hwnd, &p);
C4GUI::MouseMove(C4MC_Button_Wheel, p.x, p.y, wParam, cvp);
break;
//----------------------------------------------------------------------------------------------------------------------------------
}
}
// Console edit cursor control
else
{
// The state of the ALT key is not reported in wParam for mouse messages,
// and for keyboard messages the key states are hidden in lParam. It's a mess. Let's just
// query GetKeyState().
DWORD dwKeyState = 0;
if(GetKeyState(VK_CONTROL) < 0) dwKeyState |= MK_CONTROL;
if(GetKeyState(VK_SHIFT) < 0) dwKeyState |= MK_SHIFT;
if(GetKeyState(VK_MENU) < 0) dwKeyState |= MK_ALT;
switch (uMsg)
{
case WM_KEYDOWN:
Console.EditCursor.KeyDown(scancode, dwKeyState);
break;
case WM_KEYUP:
Console.EditCursor.KeyUp(scancode, dwKeyState);
break;
case WM_SYSKEYDOWN:
Console.EditCursor.KeyDown(scancode, dwKeyState);
break;
case WM_SYSKEYUP:
Console.EditCursor.KeyUp(scancode, dwKeyState);
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONDOWN:
// movement update needed before, so target is always up-to-date
cvp->pWindow->EditCursorMove(p.x, p.y, dwKeyState);
Console.EditCursor.LeftButtonDown(dwKeyState); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONUP: Console.EditCursor.LeftButtonUp(dwKeyState); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONDOWN: Console.EditCursor.RightButtonDown(dwKeyState); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONUP: Console.EditCursor.RightButtonUp(dwKeyState); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_MOUSEMOVE: cvp->pWindow->EditCursorMove(p.x, p.y, dwKeyState); break;
//----------------------------------------------------------------------------------------------------------------------------------
}
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
LRESULT APIENTRY DialogWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Determine dialog
C4GUI::Dialog *pDlg = ::pGUI->GetDialog(hwnd);
if (!pDlg) return DefWindowProc(hwnd, uMsg, wParam, lParam);
POINT p;
p.x = GET_X_LPARAM(lParam);
p.y = GET_Y_LPARAM(lParam);
// compute scancode
C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF;
bool extended = ((lParam & 0x01000000) != 0);
ConvertToUnixScancode(wParam, &scancode, extended);
// Process message
switch (uMsg)
{
//---------------------------------------------------------------------------------------------------------------------------
case WM_KEYDOWN:
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), pDlg)) return 0;
break;
//---------------------------------------------------------------------------------------------------------------------------
case WM_KEYUP:
if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, pDlg)) return 0;
break;
//------------------------------------------------------------------------------------------------------------
case WM_SYSKEYDOWN:
if (wParam == 18) break;
if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), pDlg)) return 0;
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_DESTROY:
{
const char *szID = pDlg->GetID();
if (szID && *szID)
StoreWindowPosition(hwnd, FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false);
}
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_CLOSE:
pDlg->Close(false);
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_SIZE:
// UpdateOutputSize
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_PAINT:
// 2do: only draw specific dlg?
break;
return 0;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONDOWN: ::pGUI->MouseInput(C4MC_Button_LeftDown, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONUP: ::pGUI->MouseInput(C4MC_Button_LeftUp, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONDOWN: ::pGUI->MouseInput(C4MC_Button_RightDown, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONUP: ::pGUI->MouseInput(C4MC_Button_RightUp, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_LBUTTONDBLCLK: ::pGUI->MouseInput(C4MC_Button_LeftDouble, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_RBUTTONDBLCLK: ::pGUI->MouseInput(C4MC_Button_RightDouble, p.x, p.y, wParam, pDlg, nullptr); break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_MOUSEMOVE:
::pGUI->MouseInput(C4MC_Button_None, p.x, p.y, wParam, pDlg, nullptr);
break;
//----------------------------------------------------------------------------------------------------------------------------------
case WM_MOUSEWHEEL:
ScreenToClient(hwnd, &p);
::pGUI->MouseInput(C4MC_Button_Wheel, p.x, p.y, wParam, pDlg, nullptr);
break;
//----------------------------------------------------------------------------------------------------------------------------------
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
C4Window::C4Window () = default;
C4Window::~C4Window () = default;
C4Window * C4Window::Init(C4Window::WindowKind windowKind, C4AbstractApp * pApp, const char * Title, const C4Rect * size)
{
Active = true;
eKind = windowKind;
if (windowKind == W_Viewport)
{
#ifdef WITH_QT_EDITOR
// embed into editor: Viewport widget creation handled by C4ConsoleQt
::Console.AddViewport(static_cast<C4ViewportWindow *>(this));
return this;
#else
static bool fViewportClassRegistered = false;
if (!fViewportClassRegistered)
{
// Register viewport class
WNDCLASSEXW WndClass;
WndClass.cbSize=sizeof(WNDCLASSEX);
WndClass.style = CS_DBLCLKS | CS_BYTEALIGNCLIENT;
WndClass.lpfnWndProc = ViewportWinProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = pApp->GetInstance();
WndClass.hCursor = LoadCursor (nullptr, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
WndClass.lpszMenuName = nullptr;
WndClass.lpszClassName = C4ViewportClassName;
WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_01_OCS) );
WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_01_OCS) );
if (!RegisterClassExW(&WndClass)) return nullptr;
fViewportClassRegistered = true;
}
// Create window
hWindow = CreateWindowExW (
WS_EX_ACCEPTFILES,
C4ViewportClassName, GetWideChar(Title), C4ViewportWindowStyle,
CW_USEDEFAULT,CW_USEDEFAULT, size->Wdt, size->Hgt,
Console.hWindow,nullptr,pApp->GetInstance(),nullptr);
if(!hWindow) return nullptr;
#endif
// We don't re-init viewport windows currently, so we don't need a child window
// for now: Render into main window.
renderwnd = hWindow;
}
else if (windowKind == W_Fullscreen)
{
// Register window class
auto WndClass = WNDCLASSEXW();
WndClass.cbSize = sizeof(WNDCLASSEX);
WndClass.style = CS_DBLCLKS;
WndClass.lpfnWndProc = FullScreenWinProc;
WndClass.hInstance = pApp->GetInstance();
WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
WndClass.lpszClassName = C4FullScreenClassName;
WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) );
WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) );
if (!RegisterClassExW(&WndClass)) return nullptr;
// Create window
hWindow = CreateWindowExW (
0,
C4FullScreenClassName,
GetWideChar(Title),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT, size->Wdt, size->Hgt,
nullptr,nullptr,pApp->GetInstance(),nullptr);
if(!hWindow) return nullptr;
RECT rc;
GetClientRect(hWindow, &rc);
renderwnd = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD,
0, 0, rc.right - rc.left, rc.bottom - rc.top,
hWindow, nullptr, pApp->GetInstance(), nullptr);
if(!renderwnd) { DestroyWindow(hWindow); return nullptr; }
ShowWindow(renderwnd, SW_SHOW);
#ifndef USE_CONSOLE
// Show & focus
ShowWindow(hWindow,SW_SHOWNORMAL);
SetFocus(hWindow);
#endif
}
else if (windowKind == W_GuiWindow)
{
static bool fDialogClassRegistered = false;
if (!fDialogClassRegistered)
{
// register landscape viewport class
WNDCLASSEXW WndClass;
WndClass.cbSize=sizeof(WNDCLASSEX);
WndClass.style = CS_DBLCLKS | CS_BYTEALIGNCLIENT;
WndClass.lpfnWndProc = DialogWinProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = pApp->GetInstance();
WndClass.hCursor = LoadCursor (nullptr, IDC_ARROW); // - always use normal hw cursor
WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
WndClass.lpszMenuName = nullptr;
WndClass.lpszClassName = ConsoleDlgClassName;
WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) );
WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) );
if (!RegisterClassExW(&WndClass))
return nullptr;
}
Active = true;
// calculate required size
RECT rtSize;
rtSize.left = 0;
rtSize.top = 0;
rtSize.right = size->Wdt;
rtSize.bottom = size->Hgt;
if (!::AdjustWindowRectEx(&rtSize, ConsoleDlgWindowStyle, false, 0))
return nullptr;
// create it!
if (!Title || !*Title) Title = "???";
hWindow = ::CreateWindowExW(
0,
ConsoleDlgClassName, GetWideChar(Title),
ConsoleDlgWindowStyle,
CW_USEDEFAULT,CW_USEDEFAULT,rtSize.right-rtSize.left,rtSize.bottom-rtSize.top,
::Console.hWindow,nullptr,pApp->GetInstance(),nullptr);
renderwnd = hWindow;
return hWindow ? this : nullptr;
}
else if (windowKind == W_Control)
{
// controlled externally
hWindow = renderwnd = nullptr;
}
return this;
}
bool C4Window::ReInit(C4AbstractApp* pApp)
{
// We don't need to change anything with the window for any
// configuration option changes on Windows.
// However, re-create the render window so that another pixel format can
// be chosen for it. The pixel format is chosen in CStdGLCtx::Init.
RECT rc;
GetClientRect(hWindow, &rc);
HWND hNewRenderWindow = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD,
0, 0, rc.right - rc.left, rc.bottom - rc.top,
hWindow, nullptr, pApp->hInstance, nullptr);
if(!hNewRenderWindow) return false;
ShowWindow(hNewRenderWindow, SW_SHOW);
DestroyWindow(renderwnd);
renderwnd = hNewRenderWindow;
return true;
}
void C4Window::Clear()
{
// Destroy window if we own it
if (eKind != W_Control)
{
if (renderwnd) DestroyWindow(renderwnd);
if (hWindow && hWindow != renderwnd) DestroyWindow(hWindow);
}
#ifdef WITH_QT_EDITOR
if (eKind == W_Viewport)
{
// embed into editor: Viewport widget creation handled by C4ConsoleQt
::Console.RemoveViewport(static_cast<C4ViewportWindow *>(this));
}
#endif
renderwnd = nullptr;
hWindow = nullptr;
}
bool C4Window::StorePosition(const char *szWindowName, const char *szSubKey, bool fStoreSize)
{
return StoreWindowPosition(hWindow, szWindowName, szSubKey, fStoreSize) != 0;
}
bool C4Window::RestorePosition(const char *szWindowName, const char *szSubKey, bool fHidden)
{
if (!RestoreWindowPosition(hWindow, szWindowName, szSubKey, fHidden))
ShowWindow(hWindow,SW_SHOWNORMAL);
return true;
}
void C4Window::SetTitle(const char *szToTitle)
{
if (hWindow) SetWindowTextW(hWindow, GetWideChar(szToTitle ? szToTitle : ""));
}
bool C4Window::GetSize(C4Rect * pRect)
{
RECT r;
if (!(hWindow && GetClientRect(hWindow,&r))) return false;
pRect->x = r.left;
pRect->y = r.top;
pRect->Wdt = r.right - r.left;
pRect->Hgt = r.bottom - r.top;
return true;
}
void C4Window::SetSize(unsigned int cx, unsigned int cy)
{
if (hWindow)
{
// If bordered, add border size
RECT rect = { 0, 0, static_cast<LONG>(cx), static_cast<LONG>(cy) };
::AdjustWindowRectEx(&rect, GetWindowLong(hWindow, GWL_STYLE), FALSE, GetWindowLong(hWindow, GWL_EXSTYLE));
cx = rect.right - rect.left;
cy = rect.bottom - rect.top;
::SetWindowPos(hWindow, nullptr, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER);
// Also resize child window
GetClientRect(hWindow, &rect);
::SetWindowPos(renderwnd, nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER);
}
}
void C4Window::FlashWindow()
{
// please activate me!
if (hWindow)
::FlashWindow(hWindow, FLASHW_ALL | FLASHW_TIMERNOFG);
}
void C4Window::GrabMouse(bool grab)
{
// TODO
}
void C4Window::EnumerateMultiSamples(std::vector<int>& samples) const
{
#ifndef USE_CONSOLE
if(pGL && pGL->pMainCtx)
samples = pGL->pMainCtx->EnumerateMultiSamples();
#endif
}
/* CStdMessageProc */
bool CStdMessageProc::Execute(int iTimeout, pollfd *)
{
// Peek messages
MSG msg;
while (PeekMessage(&msg,nullptr,0,0,PM_REMOVE))
{
// quit?
if (msg.message == WM_QUIT)
{
pApp->fQuitMsgReceived = true;
return false;
}
// Dialog message transfer
if (!pApp->pWindow || !pApp->pWindow->Win32DialogMessageHandling(&msg))
{
TranslateMessage(&msg); DispatchMessage(&msg);
}
}
return true;
}
/* C4AbstractApp */
C4AbstractApp::C4AbstractApp()
{
ZeroMemory(&dspMode, sizeof(dspMode)); dspMode.dmSize = sizeof(dspMode);
ZeroMemory(&OldDspMode, sizeof(OldDspMode)); OldDspMode.dmSize = sizeof(OldDspMode);
idMainThread = 0;
#ifdef _WIN32
MessageProc.SetApp(this);
Add(&MessageProc);
#endif
}
C4AbstractApp::~C4AbstractApp() = default;
bool C4AbstractApp::Init(int argc, char * argv[])
{
// Set instance vars
idMainThread = ::GetCurrentThreadId();
// Custom initialization
return DoInit (argc, argv);
}
void C4AbstractApp::Clear()
{
idMainThread = 0;
}
void C4AbstractApp::Quit()
{
PostQuitMessage(0);
}
bool C4AbstractApp::FlushMessages()
{
// Always fail after quit message
if (fQuitMsgReceived)
return false;
return MessageProc.Execute(0);
}
void C4AbstractApp::SetLastErrorFromOS()
{
LPWSTR buffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
nullptr, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&buffer), 0, nullptr);
sLastError = WStrToString(buffer);
LocalFree(buffer);
}
int GLMonitorInfoEnumCount;
static BOOL CALLBACK GLMonitorInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
// get to indexed monitor
if (GLMonitorInfoEnumCount--) return true;
// store it
C4AbstractApp *pApp = (C4AbstractApp *) dwData;
pApp->hMon = hMonitor;
pApp->MonitorRect = *lprcMonitor;
return true;
}
bool C4AbstractApp::GetIndexedDisplayMode(int32_t iIndex, int32_t *piXRes, int32_t *piYRes, int32_t *piBitDepth, int32_t *piRefreshRate, uint32_t iMonitor)
{
// prepare search struct
DEVMODEW dmode;
ZeroMemory(&dmode, sizeof(dmode)); dmode.dmSize = sizeof(dmode);
StdStrBuf Mon;
if (iMonitor)
Mon.Format(R"(\\.\Display%d)", iMonitor+1);
// check if indexed mode exists
if (!EnumDisplaySettingsW(Mon.GetWideChar(), iIndex, &dmode))
{
SetLastErrorFromOS();
return false;
}
// mode exists; return it
if (piXRes) *piXRes = dmode.dmPelsWidth;
if (piYRes) *piYRes = dmode.dmPelsHeight;
if (piBitDepth) *piBitDepth = dmode.dmBitsPerPel;
if (piRefreshRate) *piRefreshRate = dmode.dmDisplayFrequency;
return true;
}
void C4AbstractApp::RestoreVideoMode()
{
}
bool C4AbstractApp::SetVideoMode(int iXRes, int iYRes, unsigned int iRefreshRate, unsigned int iMonitor, bool fFullScreen)
{
#ifndef USE_CONSOLE
SetWindowLong(pWindow->hWindow, GWL_EXSTYLE,
GetWindowLong(pWindow->hWindow, GWL_EXSTYLE) | WS_EX_APPWINDOW);
// change mode
if (!fFullScreen)
{
ChangeDisplaySettings(nullptr, 0);
SetWindowLong(pWindow->hWindow, GWL_STYLE,
GetWindowLong(pWindow->hWindow, GWL_STYLE) | (WS_CAPTION|WS_THICKFRAME|WS_BORDER));
if(iXRes != -1 && iYRes != -1) {
pWindow->SetSize(iXRes, iYRes);
OnResolutionChanged(iXRes, iYRes);
}
::SetWindowPos(pWindow->hWindow, nullptr, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREDRAW|SWP_FRAMECHANGED);
}
else
{
bool fFound=false;
DEVMODEW dmode;
// if a monitor is given, search on that instead
// get monitor infos
GLMonitorInfoEnumCount = iMonitor;
hMon = nullptr;
EnumDisplayMonitors(nullptr, nullptr, GLMonitorInfoEnumProc, (LPARAM) this);
// no monitor assigned?
if (!hMon)
{
// Okay for primary; then just use a default
if (!iMonitor)
{
MonitorRect.left = MonitorRect.top = 0;
MonitorRect.right = iXRes;
MonitorRect.bottom = iYRes;
}
else return false;
}
StdStrBuf Mon;
if (iMonitor)
Mon.Format(R"(\\.\Display%d)", iMonitor+1);
ZeroMemory(&dmode, sizeof(dmode));
dmode.dmSize = sizeof(dmode);
// Get current display settings
if (!EnumDisplaySettingsW(Mon.GetWideChar(), ENUM_CURRENT_SETTINGS, &dmode))
{
SetLastErrorFromOS();
return false;
}
unsigned long orientation = dmode.dmDisplayOrientation;
if (iXRes == -1 && iYRes == -1)
{
dspMode=dmode;
fFound = true;
}
// enumerate modes
int i=0;
if (!fFound) while (EnumDisplaySettingsW(Mon.GetWideChar(), i++, &dmode))
// compare enumerated mode with requested settings
if (static_cast<int>(dmode.dmPelsWidth) == iXRes && static_cast<int>(dmode.dmPelsHeight) == iYRes && dmode.dmBitsPerPel == C4Draw::COLOR_DEPTH && dmode.dmDisplayOrientation == orientation
&& (iRefreshRate == 0 || dmode.dmDisplayFrequency == iRefreshRate))
{
fFound=true;
dspMode=dmode;
break;
}
if (!fFound) return false;
dspMode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
if (iRefreshRate != 0)
dspMode.dmFields |= DM_DISPLAYFREQUENCY;
LONG rv = ChangeDisplaySettingsExW(iMonitor ? Mon.GetWideChar() : nullptr, &dspMode, nullptr, CDS_FULLSCREEN, nullptr);
if (rv != DISP_CHANGE_SUCCESSFUL)
{
switch (rv)
{
#define CDSE_ERROR(error) case error: sLastError = LoadResStr("IDS_ERR_" #error); break
CDSE_ERROR(DISP_CHANGE_BADFLAGS);
CDSE_ERROR(DISP_CHANGE_BADMODE);
CDSE_ERROR(DISP_CHANGE_BADPARAM);
CDSE_ERROR(DISP_CHANGE_RESTART);
CDSE_ERROR(DISP_CHANGE_FAILED);
#undef CDSE_ERROR
default:
sLastError = LoadResStr("IDS_ERR_FAILURE");
break;
}
return false;
}
SetWindowLong(pWindow->hWindow, GWL_STYLE,
GetWindowLong(pWindow->hWindow, GWL_STYLE) & ~ (WS_CAPTION|WS_THICKFRAME|WS_BORDER));
pWindow->SetSize(dspMode.dmPelsWidth, dspMode.dmPelsHeight);
OnResolutionChanged(dspMode.dmPelsWidth, dspMode.dmPelsHeight);
::SetWindowPos(pWindow->hWindow, nullptr, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREDRAW|SWP_FRAMECHANGED);
}
return true;
#endif
}
void C4AbstractApp::MessageDialog(const char * message)
{
MessageBoxW(nullptr, GetWideChar(message), ADDL(C4ENGINECAPTION), MB_ICONERROR);
}
// Clipboard functions
bool C4AbstractApp::Copy(const std::string &text, bool fClipboard)
{
if (!fClipboard) return false;
bool fSuccess = true;
// gain clipboard ownership
if (!OpenClipboard(pWindow ? pWindow->hWindow : nullptr)) return false;
// must empty the global clipboard, so the application clipboard equals the Windows clipboard
EmptyClipboard();
int size = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), text.size() + 1, nullptr, 0);
HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, size * sizeof(wchar_t));
if (hglbCopy == nullptr) { CloseClipboard(); return false; }
// lock the handle and copy the text to the buffer.
wchar_t *szCopyChar = (wchar_t *) GlobalLock(hglbCopy);
fSuccess = !!MultiByteToWideChar(CP_UTF8, 0, text.c_str(), text.size() + 1, szCopyChar, size);
GlobalUnlock(hglbCopy);
// place the handle on the clipboard.
fSuccess = fSuccess && !!SetClipboardData(CF_UNICODETEXT, hglbCopy);
// close clipboard
CloseClipboard();
// return whether copying was successful
return fSuccess;
}
std::string C4AbstractApp::Paste(bool fClipboard)
{
if (!fClipboard) return std::string();
// open clipboard
if (!OpenClipboard(nullptr)) return std::string();
// get text from clipboard
HANDLE hglb = GetClipboardData(CF_UNICODETEXT);
if (!hglb) return std::string();
std::string text{ WStrToString((wchar_t*)GlobalLock(hglb)) };
// unlock mem
GlobalUnlock(hglb);
// close clipboard
CloseClipboard();
return text;
}
bool C4AbstractApp::IsClipboardFull(bool fClipboard)
{
if (!fClipboard) return false;
return !!IsClipboardFormatAvailable(CF_UNICODETEXT);
}
void C4Window::RequestUpdate()
{
// just invoke directly
PerformUpdate();
}