Add better input parsing with key repeat tracking

This commit is contained in:
Doyle Thai 2016-08-17 02:21:02 +10:00
parent 22973ef51d
commit 80b35d404d
4 changed files with 117 additions and 48 deletions

Binary file not shown.

View File

@ -352,6 +352,42 @@ INTERNAL inline v4 getEntityScreenRect(Entity entity)
return result; return result;
} }
enum ReadKeyType
{
readkeytype_oneShot,
readkeytype_delayedRepeat,
readkeytype_repeat,
readkeytype_count,
};
INTERNAL b32 getKeyStatus(KeyState key, enum ReadKeyType readType)
{
switch(readType)
{
case readkeytype_oneShot:
{
if (key.endedDown &&
(key.newHalfTransitionCount > key.oldHalfTransitionCount))
return TRUE;
break;
}
case readkeytype_repeat:
{
if (key.endedDown) return TRUE;
break;
}
case readkeytype_delayedRepeat:
default:
// TODO(doyle): Add delayed repeat of keys
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
break;
}
return FALSE;
}
INTERNAL void parseInput(GameState *state, const f32 dt) INTERNAL void parseInput(GameState *state, const f32 dt)
{ {
/* /*
@ -370,31 +406,52 @@ INTERNAL void parseInput(GameState *state, const f32 dt)
Entity *hero = &world->entities[entity_getIndex(world, world->heroId)]; Entity *hero = &world->entities[entity_getIndex(world, world->heroId)];
v2 ddPos = V2(0, 0); v2 ddPos = V2(0, 0);
KeyState *keys = state->input.keys;
for (i32 i = 0; i < keycode_count; i++)
{
KeyState *currKey = &keys[i];
if (currKey->newHalfTransitionCount > currKey->oldHalfTransitionCount)
{
i32 numTransitions = currKey->newHalfTransitionCount -
currKey->oldHalfTransitionCount;
if ((numTransitions & 1) == 1)
{
currKey->endedDown = ~currKey->endedDown;
}
// TODO(doyle): Multi press within frame override UI input parsing
if ((i >= keycode_A && i <= keycode_Z) ||
(i >= keycode_a && i <= keycode_z))
{
if (getKeyStatus(*currKey, readkeytype_oneShot))
{
state->uiState.keyChar = i;
}
}
}
}
if (hero->stats->busyDuration <= 0) if (hero->stats->busyDuration <= 0)
{ {
// TODO(doyle): As we need to handle more key spam input, we want to if (getKeyStatus(keys[keycode_right], readkeytype_repeat))
// track
// if a button ended down
LOCAL_PERSIST b32 spaceBarWasDown = FALSE;
if (state->input.keys[keycode_right])
{ {
ddPos.x = 1.0f; ddPos.x = 1.0f;
hero->direction = direction_east; hero->direction = direction_east;
} }
if (state->input.keys[keycode_left]) if (getKeyStatus(keys[keycode_left], readkeytype_repeat))
{ {
ddPos.x = -1.0f; ddPos.x = -1.0f;
hero->direction = direction_west; hero->direction = direction_west;
} }
if (state->input.keys[keycode_up]) if (getKeyStatus(keys[keycode_up], readkeytype_repeat))
{ {
ddPos.y = 1.0f; ddPos.y = 1.0f;
} }
if (state->input.keys[keycode_down]) if (getKeyStatus(keys[keycode_down], readkeytype_repeat))
{ {
ddPos.y = -1.0f; ddPos.y = -1.0f;
} }
@ -408,18 +465,16 @@ INTERNAL void parseInput(GameState *state, const f32 dt)
ddPos = v2_scale(ddPos, 0.70710678118f); ddPos = v2_scale(ddPos, 0.70710678118f);
} }
if (state->input.keys[keycode_enter]) if (getKeyStatus(keys[keycode_enter], readkeytype_oneShot))
{ {
state->uiState.keyEntered = keycode_enter; state->uiState.keyEntered = keycode_enter;
} }
LOCAL_PERSIST b32 toggleFlag = TRUE; if (getKeyStatus(keys[keycode_space], readkeytype_oneShot))
// TODO(doyle): Revisit key input with state checking for last ended down
if (state->input.keys[keycode_space] && spaceBarWasDown == FALSE)
{ {
state->uiState.keyEntered = keycode_space; state->uiState.keyEntered = keycode_space;
DEBUG_LOG("push space");
#if 0
Renderer *renderer = &state->renderer; Renderer *renderer = &state->renderer;
f32 yPos = CAST(f32)(rand() % CAST(i32)renderer->size.h); f32 yPos = CAST(f32)(rand() % CAST(i32)renderer->size.h);
f32 xModifier = 5.0f - CAST(f32)(rand() % 3); f32 xModifier = 5.0f - CAST(f32)(rand() % 3);
@ -427,12 +482,6 @@ INTERNAL void parseInput(GameState *state, const f32 dt)
v2 pos = V2(renderer->size.w - (renderer->size.w / xModifier), yPos); v2 pos = V2(renderer->size.w - (renderer->size.w / xModifier), yPos);
entity_addGenericMob(&state->arena, &state->assetManager, world, entity_addGenericMob(&state->arena, &state->assetManager, world,
pos); pos);
#endif
spaceBarWasDown = TRUE;
}
else if (!state->input.keys[keycode_space])
{
spaceBarWasDown = FALSE;
} }
} }
@ -455,7 +504,7 @@ INTERNAL void parseInput(GameState *state, const f32 dt)
} }
f32 heroSpeed = 6.2f * METERS_TO_PIXEL; f32 heroSpeed = 6.2f * METERS_TO_PIXEL;
if (state->input.keys[keycode_leftShift]) if (state->input.keys[keycode_leftShift].endedDown)
{ {
// TODO: Context sensitive command separation // TODO: Context sensitive command separation
state->uiState.keyMod = keycode_leftShift; state->uiState.keyMod = keycode_leftShift;
@ -898,7 +947,7 @@ INTERNAL i32 button(UiState *uiState, AssetManager *assetManager,
if (math_pointInRect(rect, input.mouseP)) if (math_pointInRect(rect, input.mouseP))
{ {
uiState->hotItem = id; uiState->hotItem = id;
if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft]) if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown)
uiState->activeItem = id; uiState->activeItem = id;
} }
@ -963,7 +1012,7 @@ INTERNAL i32 button(UiState *uiState, AssetManager *assetManager,
uiState->lastWidget = id; uiState->lastWidget = id;
if (!input.keys[keycode_mouseLeft] && if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id && uiState->hotItem == id &&
uiState->activeItem == id) uiState->activeItem == id)
{ {
@ -984,7 +1033,7 @@ INTERNAL i32 scrollBar(UiState *uiState, AssetManager *assetManager,
if (math_pointInRect(scrollBarRect, input.mouseP)) if (math_pointInRect(scrollBarRect, input.mouseP))
{ {
uiState->hotItem = id; uiState->hotItem = id;
if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft]) if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown)
uiState->activeItem = id; uiState->activeItem = id;
} }
@ -1095,7 +1144,7 @@ INTERNAL i32 textField(UiState *const uiState, MemoryArena *arena,
if (math_pointInRect(textRect, input.mouseP)) if (math_pointInRect(textRect, input.mouseP))
{ {
uiState->hotItem = id; uiState->hotItem = id;
if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft]) if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown)
{ {
uiState->activeItem = id; uiState->activeItem = id;
} }
@ -1158,15 +1207,15 @@ INTERNAL i32 textField(UiState *const uiState, MemoryArena *arena,
} }
if (uiState->keyChar >= keycode_space && if (uiState->keyChar >= keycode_space &&
uiState->keyChar <= keycode_Z && strLen < 30) uiState->keyChar <= keycode_tilda && strLen < 30)
{ {
string[strLen++] = uiState->keyChar; string[strLen++] = uiState->keyChar + ' ';
string[strLen] = 0; string[strLen] = 0;
changed = TRUE; changed = TRUE;
} }
} }
if (!input.keys[keycode_mouseLeft] && if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id && uiState->hotItem == id &&
uiState->activeItem == id) uiState->activeItem == id)
{ {
@ -1449,7 +1498,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
state->input, 5, V2(1000, 500), fieldString); state->input, 5, V2(1000, 500), fieldString);
// RESET IMGUI // RESET IMGUI
if (!state->input.keys[keycode_mouseLeft]) if (!state->input.keys[keycode_mouseLeft].endedDown)
state->uiState.activeItem = 0; state->uiState.activeItem = 0;
else if (state->uiState.activeItem == 0) else if (state->uiState.activeItem == 0)
state->uiState.activeItem = -1; state->uiState.activeItem = -1;
@ -1500,6 +1549,12 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
} }
} }
for (i32 i = 0; i < keycode_count; i++)
{
state->input.keys[i].oldHalfTransitionCount =
state->input.keys[i].newHalfTransitionCount;
}
sortWorldEntityList(world, numDeadEntities); sortWorldEntityList(world, numDeadEntities);
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG

View File

@ -5,10 +5,13 @@
#include "Dengine/OpenGL.h" #include "Dengine/OpenGL.h"
#include "Dengine/WorldTraveller.h" #include "Dengine/WorldTraveller.h"
INTERNAL inline void processKey(b32 *currState, int key, int action) INTERNAL inline void processKey(KeyState *state, int action)
{ {
if (action == GLFW_PRESS) *currState = TRUE; // TODO(doyle): Handle GLFW_REPEAT, and probably remove this function
else if (action == GLFW_RELEASE) *currState = FALSE; if (action == GLFW_PRESS || action == GLFW_RELEASE)
{
state->newHalfTransitionCount++;
}
} }
INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action, INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action,
@ -24,32 +27,38 @@ INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action,
switch (key) switch (key)
{ {
case GLFW_KEY_UP: case GLFW_KEY_UP:
processKey(&game->input.keys[keycode_up], key, action); processKey(&game->input.keys[keycode_up], action);
break; break;
case GLFW_KEY_DOWN: case GLFW_KEY_DOWN:
processKey(&game->input.keys[keycode_down], key, action); processKey(&game->input.keys[keycode_down], action);
break; break;
case GLFW_KEY_LEFT: case GLFW_KEY_LEFT:
processKey(&game->input.keys[keycode_left], key, action); processKey(&game->input.keys[keycode_left], action);
break; break;
case GLFW_KEY_RIGHT: case GLFW_KEY_RIGHT:
processKey(&game->input.keys[keycode_right], key, action); processKey(&game->input.keys[keycode_right], action);
break; break;
case GLFW_KEY_LEFT_SHIFT: case GLFW_KEY_LEFT_SHIFT:
processKey(&game->input.keys[keycode_leftShift], key, action); processKey(&game->input.keys[keycode_leftShift], action);
break; break;
case GLFW_KEY_ENTER: case GLFW_KEY_ENTER:
processKey(&game->input.keys[keycode_enter], key, action); processKey(&game->input.keys[keycode_enter], action);
break; break;
case GLFW_KEY_BACKSPACE: case GLFW_KEY_BACKSPACE:
processKey(&game->input.keys[keycode_backspace], key, action); processKey(&game->input.keys[keycode_backspace], action);
break; break;
default: default:
if (key >= ' ' && key <= '~') if (key >= ' ' && key <= '~')
{ {
processKey(&game->input.keys[key - ' '], key, action); i32 offset = 0;
// TODO(doyle): Temporary if (key >= 'A' && key <= 'Z')
game->uiState.keyChar = key - ' '; {
if (!game->input.keys[keycode_leftShift].endedDown)
offset = 'a' - 'A';
}
i32 glfwCodeToPlatformCode = (key - ' ') + offset;
processKey(&game->input.keys[glfwCodeToPlatformCode], action);
} }
break; break;
} }
@ -73,7 +82,7 @@ INTERNAL void mouseButtonCallback(GLFWwindow *window, int button, int action,
switch(button) switch(button)
{ {
case GLFW_MOUSE_BUTTON_LEFT: case GLFW_MOUSE_BUTTON_LEFT:
processKey(&game->input.keys[keycode_mouseLeft], button, action); processKey(&game->input.keys[keycode_mouseLeft], action);
break; break;
default: default:
break; break;

View File

@ -11,7 +11,7 @@ typedef struct MemoryArena MemoryArena;
// have a mapping function to map it back to ascii? // have a mapping function to map it back to ascii?
enum KeyCode enum KeyCode
{ {
keycode_space = 0, keycode_space,
keycode_exclamation, keycode_exclamation,
keycode_dbl_quotes, keycode_dbl_quotes,
keycode_hash, keycode_hash,
@ -24,7 +24,6 @@ enum KeyCode
keycode_star, keycode_star,
keycode_plus, keycode_plus,
keycode_comma, keycode_comma,
keycode_minus,
keycode_hyphen, keycode_hyphen,
keycode_dot, keycode_dot,
keycode_forward_slash, keycode_forward_slash,
@ -114,16 +113,22 @@ enum KeyCode
keycode_leftShift, keycode_leftShift,
keycode_mouseLeft, keycode_mouseLeft,
keycode_enter, keycode_enter,
keycode_count,
keycode_backspace, keycode_backspace,
keycode_tab, keycode_count,
keycode_null, keycode_null,
}; };
typedef struct KeyState
{
u32 oldHalfTransitionCount;
u32 newHalfTransitionCount;
b32 endedDown;
} KeyState;
typedef struct KeyInput typedef struct KeyInput
{ {
v2 mouseP; v2 mouseP;
b32 keys[keycode_count - 1]; KeyState keys[keycode_count];
} KeyInput; } KeyInput;
typedef struct PlatformFileRead typedef struct PlatformFileRead