Added text-input system for typing in-game

This commit is contained in:
MysterD 2020-08-30 23:25:32 -07:00
parent b7a1ec009c
commit 96a2cacf2d
7 changed files with 218 additions and 7 deletions

View file

@ -10,6 +10,16 @@
#include "../configfile.h"
#include "controller_keyboard.h"
#include "pc/gfx/gfx_window_manager_api.h"
#include "pc/pc_main.h""
#include "engine/math_util.h"
#define SCANCODE_BACKSPACE 0x0E
#define SCANCODE_ESCAPE 0x01
#define SCANCODE_ENTER 0x1C
#define SCANCODE_V 0x2F
#define SCANCODE_INSERT 0x152
static int keyboard_buttons_down;
#define MAX_KEYBINDS 64
@ -18,6 +28,14 @@ static int num_keybinds = 0;
static u32 keyboard_lastkey = VK_INVALID;
char textInput[MAX_TEXT_INPUT];
static bool inTextInput = false;
u8 held_ctrl, held_shift, held_alt;
static enum TextInputMode textInputMode;
void (*textInputOnEscape)(void) = NULL;
void (*textInputOnEnter)(void) = NULL;
static int keyboard_map_scancode(int scancode) {
int ret = 0;
for (int i = 0; i < num_keybinds; i++) {
@ -28,7 +46,56 @@ static int keyboard_map_scancode(int scancode) {
return ret;
}
static void keyboard_alter_text_input_modifier(int scancode, bool down) {
if (down) {
switch (scancode) {
case 0x1D: held_ctrl |= (1 << 0); break;
case 0x11D: held_ctrl |= (1 << 1); break;
case 0x2A: held_shift |= (1 << 0); break;
case 0x36: held_shift |= (1 << 1); break;
case 0x38: held_alt |= (1 << 0); break;
case 0x138: held_alt |= (1 << 1); break;
}
} else {
switch (scancode) {
case 0x1D: held_ctrl &= ~(1 << 0); break;
case 0x11D: held_ctrl &= ~(1 << 1); break;
case 0x2A: held_shift &= ~(1 << 0); break;
case 0x36: held_shift &= ~(1 << 1); break;
case 0x38: held_alt &= ~(1 << 0); break;
case 0x138: held_alt &= ~(1 << 1); break;
}
}
}
bool keyboard_on_key_down(int scancode) {
if (inTextInput) {
// alter the held value of modifier keys
keyboard_alter_text_input_modifier(scancode, true);
// perform text-input-specific actions
switch (scancode) {
case SCANCODE_BACKSPACE:
textInput[max(strlen(textInput) - 1, 0)] = '\0';
break;
case SCANCODE_ESCAPE:
if (textInputOnEscape != NULL) { textInputOnEscape(); }
break;
case SCANCODE_ENTER:
if (textInputOnEnter != NULL) { textInputOnEnter(); }
break;
case SCANCODE_V:
if (held_ctrl) { keyboard_on_text_input(wm_api->get_clipboard_text()); }
break;
case SCANCODE_INSERT:
if (held_shift) { keyboard_on_text_input(wm_api->get_clipboard_text()); }
break;
}
// ignore any normal key down event if we're in text-input mode
return FALSE;
}
int mapped = keyboard_map_scancode(scancode);
keyboard_buttons_down |= mapped;
keyboard_lastkey = scancode;
@ -36,6 +103,14 @@ bool keyboard_on_key_down(int scancode) {
}
bool keyboard_on_key_up(int scancode) {
if (inTextInput) {
// alter the held value of modifier keys
keyboard_alter_text_input_modifier(scancode, false);
// ignore any key up event if we're in text-input mode
return FALSE;
}
int mapped = keyboard_map_scancode(scancode);
keyboard_buttons_down &= ~mapped;
if (keyboard_lastkey == (u32) scancode)
@ -47,6 +122,79 @@ void keyboard_on_all_keys_up(void) {
keyboard_buttons_down = 0;
}
char* keyboard_start_text_input(enum TextInputMode inInputMode, void (*onEscape)(void), void (*onEnter)(void)) {
// set text-input events
textInputOnEscape = onEscape;
textInputOnEnter = onEnter;
// clear buffer
for (int i = 0; i < MAX_TEXT_INPUT; i++) { textInput[i] = '\0'; }
// clear held-value for modifiers
held_ctrl = 0;
held_shift = 0;
held_alt = 0;
// start allowing text input
wm_api->start_text_input();
textInputMode = inInputMode;
inTextInput = true;
}
void keyboard_stop_text_input(void) {
// stop allowing text input
wm_api->stop_text_input();
inTextInput = false;
}
static bool keyboard_allow_character_input(char c) {
switch (textInputMode) {
case TIM_IP:
// IP only allows numbers, periods, and spaces
return (c >= '0' && c <= '9')
|| (c == '.')
|| (c == ' ');
case TIM_MULTI_LINE:
// multi-line allows new-line character
if (c == '\n') { return true; }
// intentional fall-through
case TIM_SINGLE_LINE:
// allow all characters that we can display in-game
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c == '\'') || (c == '.')
|| (c == ',') || (c == '-')
|| (c == '(') || (c == ')')
|| (c == '&') || (c == '!')
|| (c == '%') || (c == '?')
|| (c == '"') || (c == '~')
|| (c == '*') || (c == ' ');
}
return false;
}
void keyboard_on_text_input(char* text) {
// sanity check input
if (text == NULL) { return; }
int i = strlen(textInput);
while (*text != NULL) {
// make sure we don't overrun the buffer
if (i >= MAX_TEXT_INPUT) { break; }
// copy over character if we're allowed to input it
if (keyboard_allow_character_input(*text)) {
textInput[i++] = *text;
}
text++;
}
}
static void keyboard_add_binds(int mask, unsigned int *scancode) {
for (int i = 0; i < MAX_BINDS && num_keybinds < MAX_KEYBINDS; ++i) {
if (scancode[i] < VK_BASE_KEYBOARD + VK_SIZE) {

View file

@ -9,9 +9,23 @@
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_TEXT_INPUT 256
extern char textInput[];
enum TextInputMode {
TIM_IP,
TIM_MULTI_LINE,
TIM_SINGLE_LINE,
};
bool keyboard_on_key_down(int scancode);
bool keyboard_on_key_up(int scancode);
void keyboard_on_all_keys_up(void);
void keyboard_on_text_input(char* text);
char* keyboard_start_text_input(enum TextInputMode, void (*onEscape)(void), void (*onEnter)(void));
void keyboard_stop_text_input(void);
#ifdef __cplusplus
}
#endif

View file

@ -44,6 +44,8 @@
using namespace Microsoft::WRL; // For ComPtr
static bool inTextInput = false;
static struct {
HWND h_wnd;
bool showing_error;
@ -74,6 +76,7 @@ static struct {
bool (*on_key_down)(int scancode);
bool (*on_key_up)(int scancode);
void (*on_all_keys_up)(void);
void (*on_text_input)(char*);
} dxgi;
static void load_dxgi_library(void) {
@ -225,6 +228,19 @@ static void gfx_dxgi_on_resize(void) {
static void onkeydown(WPARAM w_param, LPARAM l_param) {
int key = ((l_param >> 16) & 0x1ff);
if (inTextInput) {
const int keyboardScanCode = (l_param >> 16) & 0x00ff;
const int virtualKey = w_param;
BYTE keyboardState[256];
GetKeyboardState(keyboardState);
WORD ascii = 0;
const int len = ToAscii(virtualKey, keyboardScanCode, keyboardState, &ascii, 0);
if (len > 0) {
dxgi.on_text_input((char*)&ascii);
}
}
if (dxgi.on_key_down != nullptr) {
dxgi.on_key_down(key);
}
@ -345,10 +361,11 @@ static void gfx_dxgi_init(const char *window_title) {
update_screen_settings();
}
static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void), void (*on_text_input)(char*)) {
dxgi.on_key_down = on_key_down;
dxgi.on_key_up = on_key_up;
dxgi.on_all_keys_up = on_all_keys_up;
dxgi.on_text_input = on_text_input;
}
static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) {
@ -608,6 +625,18 @@ HWND gfx_dxgi_get_h_wnd(void) {
void gfx_dxgi_shutdown(void) {
}
void gfx_dxgi_start_text_input(void) { inTextInput = TRUE; }
void gfx_dxgi_stop_text_input(void) { inTextInput = FALSE; }
static char* gfx_dxgi_get_clipboard_text(void) {
if (OpenClipboard(NULL)) {
HANDLE clip = GetClipboardData(CF_TEXT);
CloseClipboard();
return (char*)clip;
}
return NULL;
}
void ThrowIfFailed(HRESULT res) {
if (FAILED(res)) {
fprintf(stderr, "Error: 0x%08X\n", res);
@ -636,6 +665,9 @@ struct GfxWindowManagerAPI gfx_dxgi = {
gfx_dxgi_swap_buffers_end,
gfx_dxgi_get_time,
gfx_dxgi_shutdown,
gfx_dxgi_start_text_input,
gfx_dxgi_stop_text_input,
gfx_dxgi_get_clipboard_text,
};
#endif

View file

@ -49,6 +49,7 @@ static int inverted_scancode_table[512];
static kb_callback_t kb_key_down = NULL;
static kb_callback_t kb_key_up = NULL;
static void (*kb_all_keys_up)(void) = NULL;
static void (*kb_text_input)(char*) = NULL;
// whether to use timer for frame control
static bool use_timer = true;
@ -279,11 +280,15 @@ static void gfx_sdl_onkeyup(int scancode) {
}
static void gfx_sdl_handle_events(void) {
SDL_StartTextInput();
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
#ifndef TARGET_WEB
// Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259
case SDL_TEXTINPUT:
kb_text_input(event.text.text);
break;
case SDL_KEYDOWN:
gfx_sdl_onkeydown(event.key.keysym.scancode);
break;
@ -320,10 +325,11 @@ static void gfx_sdl_handle_events(void) {
}
}
static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void)) {
static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void), void (*on_text_input)(char*)) {
kb_key_down = on_key_down;
kb_key_up = on_key_up;
kb_all_keys_up = on_all_keys_up;
kb_text_input = on_text_input;
}
static bool gfx_sdl_start_frame(void) {
@ -361,6 +367,10 @@ static void gfx_sdl_shutdown(void) {
}
}
static void gfx_sdl_start_text_input(void) { SDL_StartTextInput(); }
static void gfx_sdl_stop_text_input(void) { SDL_StopTextInput(); }
static char* gfx_sdl_get_clipboard_text(void) { SDL_GetClipboardText(); }
struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_init,
gfx_sdl_set_keyboard_callbacks,
@ -371,7 +381,10 @@ struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_swap_buffers_begin,
gfx_sdl_swap_buffers_end,
gfx_sdl_get_time,
gfx_sdl_shutdown
gfx_sdl_shutdown,
gfx_sdl_start_text_input,
gfx_sdl_stop_text_input,
gfx_sdl_get_clipboard_text,
};
#endif // BACKEND_WM

View file

@ -11,7 +11,7 @@ typedef bool (*kb_callback_t)(int code);
struct GfxWindowManagerAPI {
void (*init)(const char *window_title);
void (*set_keyboard_callbacks)(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void));
void (*set_keyboard_callbacks)(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void), void (*on_text_input)(char*));
void (*main_loop)(void (*run_one_game_iter)(void));
void (*get_dimensions)(uint32_t *width, uint32_t *height);
void (*handle_events)(void);
@ -20,6 +20,9 @@ struct GfxWindowManagerAPI {
void (*swap_buffers_end)(void);
double (*get_time)(void); // For debug
void (*shutdown)(void);
void (*start_text_input)(void);
void (*stop_text_input)(void);
char* (*get_clipboard_text)(void);
};
#endif

View file

@ -55,7 +55,7 @@ struct RumbleData gRumbleDataQueue[3];
struct StructSH8031D9B0 gCurrRumbleSettings;
static struct AudioAPI *audio_api;
static struct GfxWindowManagerAPI *wm_api;
struct GfxWindowManagerAPI *wm_api;
static struct GfxRenderingAPI *rendering_api;
extern void gfx_run(Gfx *commands);
@ -241,14 +241,14 @@ void main_func(void) {
#endif
char window_title[96] =
"Super Mario 64 EX (" RAPI_NAME ")"
"Super Mario 64 coop EX (" RAPI_NAME ")"
#ifdef NIGHTLY
" nightly " GIT_HASH
#endif
;
gfx_init(wm_api, rendering_api, window_title);
wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up);
wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up, keyboard_on_text_input);
if (audio_api == NULL && audio_sdl.init())
audio_api = &audio_sdl;

View file

@ -5,6 +5,7 @@
extern "C" {
#endif
extern struct GfxWindowManagerAPI* wm_api;
void game_deinit(void);
void game_exit(void);