Implementations of the Action-Model-View pattern:
- with PDCurses
- with Win32
Implementations of the Action-Model-View pattern:
#include <curses.h> | |
#define curses | |
#include <memory> | |
struct action | |
{ | |
int key; | |
}; | |
struct application | |
{ | |
int y = 0; | |
int x = 0; | |
}; | |
std::unique_ptr<application> update(application app, action a) | |
{ | |
switch (a.key) | |
{ | |
case 'q': | |
return nullptr; | |
case KEY_UP: | |
app.y--; | |
break; | |
case KEY_DOWN: | |
app.y++; | |
break; | |
case KEY_LEFT: | |
app.x--; | |
break; | |
case KEY_RIGHT: | |
app.x++; | |
break; | |
} | |
return std::make_unique<application>(std::move(app)); | |
} | |
void draw(WINDOW* window, const application& app) | |
{ | |
curses::wmove(window, app.y, app.x); | |
} | |
int main() | |
{ | |
curses::initscr(); | |
curses::curs_set(2); | |
curses::noecho(); | |
curses::keypad(curses::stdscr, TRUE); | |
auto state = application{}; | |
while (auto new_state = update(state, {curses::wgetch(curses::stdscr)})) | |
{ | |
state = *new_state; | |
draw(curses::stdscr, state); | |
} | |
curses::curs_set(1); | |
curses::endwin(); | |
return 0; | |
} |
#include <windows.h> | |
#include <new> | |
#include <tuple> | |
struct action | |
{ | |
HWND hWnd; | |
UINT message; | |
WPARAM wParam; | |
LPARAM lParam; | |
}; | |
struct application | |
{ | |
int y = 0; | |
int x = 0; | |
}; | |
bool operator==(const application& lhs, const application& rhs) | |
{ | |
return std::tie(lhs.x, lhs.y) == std::tie(rhs.x, rhs.y); | |
} | |
bool operator!=(const application& lhs, const application& rhs) | |
{ | |
return !(lhs == rhs); | |
} | |
application update(application app, action a) | |
{ | |
switch (a.message) | |
{ | |
case WM_KEYDOWN: | |
switch (a.wParam) | |
{ | |
case VK_UP: | |
app.y -= 10; | |
break; | |
case VK_DOWN: | |
app.y += 10; | |
break; | |
case VK_LEFT: | |
app.x -= 10; | |
break; | |
case VK_RIGHT: | |
app.x += 10; | |
break; | |
} | |
break; | |
} | |
return app; | |
} | |
void draw(HWND hWnd, const application& app) | |
{ | |
PAINTSTRUCT ps; | |
HDC hdc = ::BeginPaint(hWnd, &ps); | |
::TextOut(hdc, app.x, app.y, "X", 1); | |
::EndPaint(hWnd, &ps); | |
} | |
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) | |
{ | |
auto pState = reinterpret_cast<application*>(::GetWindowLongPtr(hWnd, 0)); | |
switch (message) | |
{ | |
case WM_PAINT: | |
if (!pState) | |
{ | |
return FALSE; | |
} | |
draw(hWnd, *pState); | |
return 0; | |
case WM_DESTROY: | |
::PostQuitMessage(0); | |
return 0; | |
} | |
if (pState) | |
{ | |
auto newState = update(*pState, {hWnd, message, wParam, lParam}); | |
if (*pState != newState) | |
{ | |
::InvalidateRect(hWnd, nullptr, TRUE); // trigger re-paint | |
} | |
*pState = newState; | |
} | |
return ::DefWindowProc(hWnd, message, wParam, lParam); | |
} | |
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) | |
{ | |
WNDCLASSEX wcex{}; | |
wcex.cbSize = sizeof(WNDCLASSEX); | |
wcex.style = CS_HREDRAW | CS_VREDRAW; | |
wcex.lpfnWndProc = WndProc; | |
wcex.cbWndExtra = sizeof(application*); | |
wcex.hInstance = hInstance; | |
wcex.hCursor = ::LoadCursor(nullptr, IDC_ARROW); | |
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); | |
wcex.lpszClassName = "AMOV"; | |
if (!::RegisterClassEx(&wcex)) | |
{ | |
::MessageBox(nullptr, "Call to RegisterClassEx failed!", nullptr, 0); | |
return 1; | |
} | |
HWND hWnd = | |
::CreateWindow("AMOV", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 250, | |
250, nullptr, nullptr, hInstance, nullptr); | |
if (!hWnd) | |
{ | |
::MessageBox(nullptr, "Call to CreateWindow failed!", nullptr, 0); | |
return 1; | |
} | |
application state; | |
::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(&state)); | |
::ShowWindow(hWnd, nCmdShow); | |
::UpdateWindow(hWnd); | |
MSG msg; | |
while (::GetMessage(&msg, nullptr, 0, 0) > 0) | |
{ | |
::TranslateMessage(&msg); | |
::DispatchMessage(&msg); | |
} | |
return (int)msg.wParam; | |
} |