Tuesday, August 26, 2014

C-3: Yonder Window Breaks

All the window logic, that I've come across, on the web with relation to Game Development is unnecessarily complicated. I don't quite understand the need for this added complexity. Why does everyone insist that a Window has to created manually using the functions that Win32 API provides. They all seem to work in C++ as language, but utilize none of the advantages the language brings. Baffling. Perhaps it's knowledge issue, perhaps the API to make window creation easy simply isn't widely known. I suspect that to be the case, but I am not certain. MS is partly to blame for this, their own code doesn't make use of the API they ship for window creation most of the time. Maybe there is a reason for that, I don't know again. BUT I am lazy, and I don't feel like writing the same bit of logic/boilerplate every-time a new windows application is needed.

So to that end, I am using ATL. More specifically CWindowImpl template that is described in ATL.

Basic implementation is stupidly simple. You can compare this to the effort taken to do same from scratch.
Window.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>

// Uses ATL/WTL to create the window.
class Window : public CWindowImpl<Window>
{
public:
 Window();
 ~Window();

 void ProcessMessages();

 BEGIN_MSG_MAP(Window)
  MESSAGE_HANDLER(WM_PAINT, OnPaint)
  MESSAGE_HANDLER(WM_ACTIVATE, OnActivate)
  MESSAGE_HANDLER(WM_CREATE, OnCreate)
  MESSAGE_HANDLER(WM_CLOSE, OnClose)
  MESSAGE_HANDLER(WM_SIZE, OnResize)
  MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
  MESSAGE_HANDLER(WM_KEYUP, OnKeyUp)
  MESSAGE_HANDLER(WM_CHAR, OnCharInput)
  MESSAGE_HANDLER(WM_INPUT, OnRawInput)
 END_MSG_MAP()

private:
 LRESULT OnPaint(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
 LRESULT OnActivate(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);

 LRESULT OnCreate(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
 LRESULT OnClose(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);

 LRESULT OnResize(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);

 LRESULT OnKeyDown(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
 LRESULT OnKeyUp(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
 LRESULT OnCharInput(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
 LRESULT OnRawInput(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
};
And your class definition simply becomes.

Window.cpp

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
#include "Window.h"

Window::Window(WindowMessageHandler &handler) : m_isActive(false), m_ExtHandler(handler)
{
}

Window::~Window()
{
 if (m_hWnd)
 {
  DestroyWindow();
  m_hWnd = NULL;
 }
}

void Window::ProcessMessages()
{
 MSG msg;

 while (m_hWnd)
 {
  if (m_isActive)
  {
   if (!PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
    break;
  }
  else
  {
   if (!GetMessage(&msg, m_hWnd, 0, 0))
    break;
  }

  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
}

LRESULT Window::OnPaint(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 PAINTSTRUCT ps;
 HDC hdc;
 hdc = BeginPaint(&ps);

 if (m_ExtHandler.Paint)
 {
  m_ExtHandler.Paint(reinterpret_cast<WPARAM>(hdc), reinterpret_cast<LPARAM>(m_hWnd));
 }

 EndPaint(&ps);

 bHandled = true;

 return 0;
}

LRESULT Window::OnActivate(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 if (wParam == WA_INACTIVE)
 {
  m_isActive = false;
 }
 else
 {
  m_isActive = true;
 }

 return 0;
}

LRESULT Window::OnCreate(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}

LRESULT Window::OnClose(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 m_isActive = false;
 
 if (m_hWnd)
 {
  DestroyWindow();
  m_hWnd = NULL;
 }

 return 0;
}

LRESULT Window::OnResize(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}

LRESULT Window::OnKeyDown(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}

LRESULT Window::OnKeyUp(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}

LRESULT Window::OnCharInput(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}

LRESULT Window::OnRawInput(UINT msg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
 return DefWindowProc(msg, wParam, lParam);
}
And how do you use it? Simple
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Window wnd;

wnd.Create(NULL,
  CWindow::rcDefault,
  _T("Default Window Title"),
  WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_VISIBLE,
  WS_EX_OVERLAPPEDWINDOW| WS_EX_COMPOSITED
 );

while (wnd.m_hWnd)
{
 wnd.ProcessMessages();
}

Now I've added some stuff in this logic to help with things that will be talked about in future, but for now, it's enough. Whole ATL header is very human readable, and easy to figure out which methods you need to call if you want some functionality of base win32. It essentially wrap Win32 functions in to methods you can call, forgoing some of the common bits, like always having to pass HWND to the method. All in all, I think it should be used more, assuming you are doing what MS terms "Desktop applications". The library isn't support for "Windows Store applications". Reason for which seem entirely arbitrary to me.

Anyways, that's it for now. Next I will get wiring to get DirectX renderer and/or RawInput Handler going.
I've set up multiple branches on Git, so I can work on these items independently.