win32_typing_test/win32_typing_test.c

329 lines
7.4 KiB
C
Raw Normal View History

2024-01-20 02:57:30 +00:00
/*
** A program for testing win32 text input
*/
#include <Windows.h>
// only for vsync in GDI
#include <dwmapi.h>
typedef LONG S32;
typedef USHORT U16;
typedef ULONG U32;
////////////////////////////////
// App Functions
static void AppInit(HWND wnd);
static void AppRender(HWND wnd);
static void AppTypeBackspace(void);
static void AppTypeCodepoint(U32 cp);
////////////////////////////////
// Window Proc
static BOOL keep_running = 1;
static LRESULT
WindowProc(HWND wnd,
UINT msg,
WPARAM wparam,
LPARAM lparam){
LRESULT result = 0;
switch (msg){
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(wnd, &ps);
AppRender(wnd);
EndPaint(wnd, &ps);
}break;
case WM_CHAR:
{
// When a user uses IME, their input is handled by
// WM_IME_CHAR messages, but additional WM_CHAR messages
// get generated in the process.
//
// I'm not entirely sure what the best thing is to do with
// them - but they appear to always be messages I want to ignore.
//
// They also appear to be the only case where a WM_CHAR
// message has a scan code of zero.
//
// Therefore my best solution so far is to filter out WM_CHAR
// messages that have a zero scan code.
U32 scan_code = ((lparam >> 16) & 0xFF);
if (scan_code != 0){
U32 cp16 = (wparam & 0xFFFF);
if (cp16 >= ' ' && cp16 != 0x7F){
AppTypeCodepoint(cp16);
}
}
}break;
case WM_IME_CHAR:
{
U32 cp16 = (wparam & 0xFFFF);
if (cp16 >= ' ' && cp16 != 0x7F){
AppTypeCodepoint(cp16);
}
}break;
case WM_UNICHAR:
{
U32 cp32 = wparam;
if (cp32 >= ' ' && cp32 != 0x7F){
AppTypeCodepoint(cp32);
}
}break;
case WM_KEYDOWN:
{
// While it is possible to see newlines/carriage returns
// and tabs through the WM_CHAR message, it is also possible
// to get erroneous newlines and tabs in WM_CHAR.
//
// "Erroneous" WM_CHAR messages are generated when a user
// holds down "Ctrl" and presses any alphabetic character.
// The erroneous values generated this way are always less
// than 0x20 (0x20 is the ascii for the space character ' ').
//
// Thus it's possible to get simple behavior by ignoring
// all the WM_CHAR messages that have character values less
// than 0x20 and then directly handle these two relevant
// cases here.
if (wparam == VK_RETURN){
AppTypeCodepoint('\n');
}
if (wparam == VK_TAB){
AppTypeCodepoint('\t');
}
if (wparam == VK_BACK){
AppTypeBackspace();
}
}break;
case WM_SYSCOMMAND:
{
// Not directly relevant to the main goal, but if you don't
// want your Alt+Key combos to play a bell sound you have to
// disable default handling of this 'menu' path
BOOL is_menu = (wparam == SC_KEYMENU && (lparam >> 16) <= 0);
if (!is_menu){
result = DefWindowProcW(wnd, msg, wparam, lparam);
}
}break;
case WM_CLOSE:
{
keep_running = 0;
}break;
default:
{
result = DefWindowProcW(wnd, msg, wparam, lparam);
}break;
}
return(result);
}
////////////////////////////////
// App Implementation
static HFONT font = 0;
#define TEXT_CAP (1 << 14)
static U16 text_memory[TEXT_CAP];
static U32 text_len = 0;
static void
AppInsertCodepoint(U32 cp){
// convert codepoint to UTF16
U16 encode_buf[2];
U32 encode_len = 0;
if (cp < 0x10000){
encode_buf[0] = cp;
encode_len = 1;
}
else{
U32 cpj = cp - 0x10000;
encode_buf[0] = (cpj >> 10) + 0xD800;
encode_buf[1] = (cpj & 0x3FF) + 0xDC00;
encode_len = 2;
}
// insert into string
if (text_len + encode_len < TEXT_CAP){
for (U32 i = 0; i < encode_len; i += 1){
text_memory[text_len] = encode_buf[i];
text_len += 1;
}
}
}
static void
AppTypeBackspace(void){
text_len = 0;
}
static void
AppTypeCodepoint(U32 cp){
if (cp == '\n'){
AppInsertCodepoint('\\');
AppInsertCodepoint('n');
}
else if (cp == '\t'){
AppInsertCodepoint('\\');
AppInsertCodepoint('t');
}
else{
AppInsertCodepoint(cp);
}
}
static void
AppInit(HWND wnd){
LOGFONT logfont = {0};
logfont.lfHeight = 24;
memcpy(logfont.lfFaceName, "Arial", sizeof("Arial"));
font = CreateFontIndirectA(&logfont);
}
static void
AppRender(HWND wnd){
// window dimensions
RECT wnd_rect = {0};
GetClientRect(wnd, &wnd_rect);
// render begin
HDC wnd_dc = GetDC(wnd);
HDC dc = CreateCompatibleDC(wnd_dc);
HBITMAP bitmap =
CreateCompatibleBitmap(wnd_dc, wnd_rect.right, wnd_rect.bottom);
SelectObject(dc, bitmap);
// render body
{
SelectObject(dc, GetStockObject(DC_PEN));
SelectObject(dc, GetStockObject(DC_BRUSH));
SetBkMode(dc, TRANSPARENT);
SelectObject(dc, font);
// background
{
SetDCPenColor(dc, RGB(255, 255, 255));
SetDCBrushColor(dc, RGB(255, 255, 255));
Rectangle(dc, wnd_rect.left, wnd_rect.top, wnd_rect.right, wnd_rect.bottom);
}
// string
{
SIZE dim = {0};
GetTextExtentPoint32W(dc, (WCHAR*)text_memory, text_len, &dim);
S32 cx = (wnd_rect.right - wnd_rect.left)/2;
S32 cy = (wnd_rect.bottom - wnd_rect.top)/2;
S32 x = cx - dim.cx/2;
S32 y = cy - dim.cy/2;
TextOutW(dc, x, y, (WCHAR*)text_memory, text_len);
}
}
// render end
BitBlt(wnd_dc, 0, 0, wnd_rect.right, wnd_rect.bottom, dc, 0, 0, SRCCOPY);
DeleteObject(bitmap);
DeleteDC(dc);
ReleaseDC(wnd, wnd_dc);
}
////////////////////////////////
// Main
static BOOL composition_enabled = 0;
int
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd){
// window setup
#define WINDOW_CLASS L"InputWindowClass"
WNDCLASSW window_class = {0};
window_class.style = CS_HREDRAW|CS_VREDRAW;
window_class.lpfnWndProc = WindowProc;
window_class.hInstance = hInstance;
window_class.hCursor = LoadCursorA(0, IDC_ARROW);
window_class.lpszClassName = WINDOW_CLASS;
ATOM atom = RegisterClassW(&window_class);
if (atom == 0){
MessageBoxW(0, (WCHAR*)L"RegisterClassW failed\n", (WCHAR*)L"Fatal", MB_OK);
ExitProcess(1);
}
DWORD style = WS_OVERLAPPEDWINDOW;
HWND wnd = CreateWindowW(WINDOW_CLASS, L"Type Test", style,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
0, 0, hInstance, 0);
// setup vsync
if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){
composition_enabled = 0;
}
if (!composition_enabled){
MessageBoxW(0, (WCHAR*)L"No Vsync\n", (WCHAR*)L"Issue", MB_OK);
}
// init
AppInit(wnd);
// main loop
ShowWindow(wnd, SW_SHOW);
keep_running = 1;
for (;keep_running;){
// flush message queue
{
BOOL wait_for_message = 1;
MSG msg;
for (;(wait_for_message?
GetMessageW(&msg, 0, 0, 0):
PeekMessageW(&msg, 0, 0, 0, PM_REMOVE));){
wait_for_message = 0;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// render
AppRender(wnd);
// vsync or sleep
if (composition_enabled){
DwmFlush();
}
else{
Sleep(33);
}
}
}
//$ graphical //