Skip to content
Snippets Groups Projects
Select Git revision
  • 0660af11caa453842cd9a996d0eb18297da833a6
  • master default
  • Huaer
  • krueger
4 results

Platform_Win32Console.cpp

Blame
  • Platform_Win32Console.cpp 6.56 KiB
    // Win32 console Platform implementation.
    #pragma comment(lib, "user32.lib")
    
    // Since we need std::min() and std::max()...
    #define NOMINMAX
    
    #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);
    }
    
    void fatal_from_lasterror(
    	std::string_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::wstring_convert is deprecated... So, MultiByteToWideChar it is.
    	int file_len = static_cast<int>(file.size());
    	int file_utf16_len = MultiByteToWideChar(
    		CP_UTF8, 0, file.data(), file_len, nullptr, 0
    	);
    	if(file_utf16_len == 0) {
    		fail();
    	}
    
    	// The terminating '\0' is already included here.
    	std::wstring file_utf16(file_utf16_len, '\0');
    	if(!MultiByteToWideChar(
    		CP_UTF8, 0, file.data(), file_len, file_utf16.data(), file_utf16_len
    	)) {
    		fail();
    	}
    
    	std::wstringstream message;
    	message << file_utf16 << ":" << std::to_wstring(line) << ": " << 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(__FILE__, __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(__FILE__, __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;
    }
    
    // Since CHAR_INFO is a `union`, we don't get an automatic implementation of
    // std::equal_to.
    bool CHAR_INFO_equal(CHAR_INFO const &a, CHAR_INFO const &b)
    {
    	return (a.Attributes == b.Attributes);
    }
    
    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);
    
    	// Keep the last two screens rendered, 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, &region);
    		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, &region
    	);
    }
    
    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(__FILE__, __LINE__, GetLastError());
    	}
    
    	auto handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
    	if(handle_out == INVALID_HANDLE_VALUE) {
    		fatal_from_lasterror(__FILE__, __LINE__, GetLastError());
    	}
    
    	auto handle_in = GetStdHandle(STD_INPUT_HANDLE);
    	if(handle_in == INVALID_HANDLE_VALUE) {
    		fatal_from_lasterror(__FILE__, __LINE__, GetLastError());
    	}
    
    	CONSOLE_FONT_INFOEX cfi = { sizeof(CONSOLE_FONT_INFOEX) };
    	if(!GetCurrentConsoleFontEx(handle_out, false, &cfi)) {
    		fatal_from_lasterror(__FILE__, __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(__FILE__, __LINE__, GetLastError());
    	}
    
    	CONSOLE_CURSOR_INFO cursor_info = { 1, false };
    	SetConsoleCursorInfo(handle_out, &cursor_info);
    
    	return { handle_out, handle_in };
    }