Select Git revision
Trooper.hpp
-
weberma73121 authoredweberma73121 authored
Platform_Win32Console.cpp 6.13 KiB
// Win32 console Platform implementation.
#pragma comment(lib, "user32.lib")
// Since we need std::min() and std::max()...
#define NOMINMAX
// Unicode WFILE macro
// https://stackoverflow.com/a/3291315/5035474
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define WFILE WIDEN(__FILE__)
#include <numeric>
#include <sstream>
#include <vector>
#include <windows.h>
#include "Platform_Win32Console.hpp"
void fatal(std::wstring_view message)
{
MessageBoxW(nullptr, message.data(), L"Error", MB_OK | MB_ICONERROR);
ExitProcess(1);
}
// You know, I'm too old to not handle errors the right way.
void fatal_from_lasterror(
std::wstring_view file, unsigned int line, DWORD lasterror
)
{
auto fail = [lasterror]() {
std::wstring message = L"Error retrieving error string for error " +
std::to_wstring(lasterror) +
L"?!";
fatal(message);
};
LPWSTR formatted = nullptr;
DWORD flags = (
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS
);
auto codepoints = FormatMessageW(
flags,
nullptr,
lasterror,
0,
reinterpret_cast<LPWSTR>(&formatted),
0,
nullptr
);
if(codepoints == 0) {
fail();
}
std::wstringstream message;
message << file << ":" << std::to_wstring(line) << ": " << formatted;
LocalFree(formatted);
fatal(message.str());
}
Platform_Win32Console::Platform_Win32Console(void *handle_out, void *handle_in)
: handle_out(handle_out), handle_in(handle_in) {
}
std::unordered_set<char> Platform_Win32Console::get_input(void)
{
std::unordered_set<char> ret;
DWORD events_available;
if(!GetNumberOfConsoleInputEvents(handle_in, &events_available)) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
// ReadConsoleInput blocks if we have no events. So, don't even call it
// in this case.
if(events_available == 0) {
return ret;
}
std::vector<INPUT_RECORD> input_records(events_available);
DWORD events_read;
if(!ReadConsoleInput(
handle_in, input_records.data(), events_available, &events_read
)) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
for(auto const &record : input_records) {
if(record.EventType == KEY_EVENT) {
auto const &key_event = record.Event.KeyEvent;
if(key_event.bKeyDown) {
ret.insert(record.Event.KeyEvent.uChar.AsciiChar);
}
}
}
return ret;
}
void Platform_Win32Console::render(PixelBuffer const &buffer)
{
if(buffer.h() & 0x1) {
fatal(L"Rendered PixelBuffers must have a height divisible by 2!");
}
COORD con_area = {
static_cast<short>(buffer.w()), static_cast<short>(buffer.h() / 2)
};
auto con_buffer_size = (con_area.X * con_area.Y);
// Additonally keep the buffer from the previous frame, so that we're able
// to only render the differences.
static std::array<std::vector<CHAR_INFO>, 2> chars;
static bool page = true;
page = !page;
auto &chars_cur = chars[page];
auto &chars_prev = chars[!page];
bool rerender_everything = false;
if(chars_cur.size() != con_buffer_size) {
// U+2580 UPPER HALF BLOCK
chars_cur.resize(con_buffer_size, { { 0x2580 }, 0 });
// Will be resized next frame
rerender_everything = (chars_prev.size() != con_buffer_size);
// Resize console window and buffer
CONSOLE_SCREEN_BUFFER_INFOEX csbi = { sizeof(csbi) };
GetConsoleScreenBufferInfoEx(handle_out, &csbi);
COORD buffer_size = {
static_cast<SHORT>(con_area.X + 1),
static_cast<SHORT>(con_area.Y + 1)
};
SMALL_RECT region = { 0, 0, con_area.X, con_area.Y };
SetConsoleWindowInfo(handle_out, true, ®ion);
SetConsoleScreenBufferSize(handle_out, buffer_size);
}
for(auto const &it : buffer) {
auto color_value = it.peek().to_value().value_or(0xFF);
auto char_i = (((it.pos().y / 2) * buffer.w()) + it.pos().x);
auto is_upper = ((it.pos().y & 0x1) == 0);
if(is_upper) {
chars_cur[char_i].Attributes =
((color_value & 0x1) ? FOREGROUND_INTENSITY : 0) |
((color_value & 0x2) ? FOREGROUND_RED : 0) |
((color_value & 0x4) ? FOREGROUND_GREEN : 0) |
((color_value & 0x8) ? FOREGROUND_BLUE : 0);
} else {
chars_cur[char_i].Attributes |=
((color_value & 0x1) ? BACKGROUND_INTENSITY : 0) |
((color_value & 0x2) ? BACKGROUND_RED : 0) |
((color_value & 0x4) ? BACKGROUND_GREEN : 0) |
((color_value & 0x8) ? BACKGROUND_BLUE : 0);
}
}
SMALL_RECT region = { 0, 0, con_area.X, con_area.Y };
if(!rerender_everything) {
region.Left = con_area.X;
region.Top = con_area.Y;
region.Right = 0;
region.Bottom = 0;
for(SHORT y = 0; y < con_area.Y; y++) {
for(SHORT x = 0; x < con_area.X; x++) {
auto i = ((y * buffer.w()) + x);
if(chars_cur[i].Attributes != chars_prev[i].Attributes) {
region.Left = std::min(region.Left, x);
region.Top = std::min(region.Top, y);
region.Right = std::max(region.Right, x);
region.Bottom = std::max(region.Bottom, y);
}
}
}
}
COORD buffer_coord = { region.Left, region.Top };
WriteConsoleOutputW(
handle_out, chars_cur.data(), con_area, buffer_coord, ®ion
);
}
void Platform_Win32Console::set_title(std::string const &title)
{
SetConsoleTitleA(title.c_str());
}
Platform_Win32Console Platform_Win32Console::init()
{
// Make sure to create a dedicated window, even if we launch from a shell
FreeConsole(); // We don't care if this fails
if(!AllocConsole()) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
auto handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
if(handle_out == INVALID_HANDLE_VALUE) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
auto handle_in = GetStdHandle(STD_INPUT_HANDLE);
if(handle_in == INVALID_HANDLE_VALUE) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
CONSOLE_FONT_INFOEX cfi = { sizeof(CONSOLE_FONT_INFOEX) };
if(!GetCurrentConsoleFontEx(handle_out, false, &cfi)) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
// Make sure to enforce a default Windows font that actually has U+2580
wcscpy_s(cfi.FaceName, L"Lucida Console");
cfi.dwFontSize.X = 4;
cfi.dwFontSize.Y = 4;
if(!SetCurrentConsoleFontEx(handle_out, false, &cfi)) {
fatal_from_lasterror(WFILE, __LINE__, GetLastError());
}
CONSOLE_CURSOR_INFO cursor_info = { 1, false };
SetConsoleCursorInfo(handle_out, &cursor_info);
return { handle_out, handle_in };
}