From d38deeef3af2316a666f8fc0173940bd769b748e Mon Sep 17 00:00:00 2001 From: onelin Date: Sat, 1 Nov 2025 00:55:42 +0100 Subject: Flatten project structure This will make it easier to break up the code into smaller chunks again later. One would think doing this seems fun to me at this point. --- src/input.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 src/input.c (limited to 'src/input.c') diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..b33a1fa --- /dev/null +++ b/src/input.c @@ -0,0 +1,425 @@ +#include +#include + +#undef GLFW_INCLUDE_NONE +#include + +#include +#include +#include +#include +#include + +extern input_callback_t* callbacks[128]; +extern usize callbacks_len; + +extern Instance* GLOBAL_PLATFORM; +/* Lazy binds, used internally. They are similar to BindAction and friends. + * The only difference is that we set callbacks and such to NULL, but populate + * the function name strings such that can be reloaded. */ +#define BindActionLazy(key, altkey, action_str) \ + (binding_t) { \ + .action = (action_t){.action = \ + { \ + .type = InputType_action, \ + .callback = NULL, \ + .callback_str = strdup(action_str), \ + }}, \ + .keycode = key, .keycode_alt = altkey, .since_last_activation = 0 \ + } + +#define BindStateLazy(key, altkey, _activate_str, _deactivate_str) \ + (binding_t) { \ + .action = (action_t){.state = \ + { \ + .type = InputType_state, \ + .activate = NULL, \ + .deactivate = NULL, \ + .activate_str = strdup(_activate_str), \ + .deactivate_str = strdup(_deactivate_str), \ + }}, \ + .keycode = key, .keycode_alt = altkey, .since_last_activation = 0 \ + } + +void key_callback(void* window, int key, int scancode, int action, int mods) { + + const i_ctx* bindings = *GLOBAL_PLATFORM->window->bindings; + const usize bindings_len = GLOBAL_PLATFORM->window->bindings_len; + + const u64 now = get_time(); + + for (usize b = 0; b < bindings_len; b++) { + const action_t a = i_get_action(&bindings[b], now, key); + switch (action) { + case GLFW_PRESS: + + switch (a.type) { + case InputType_action: + if (a.action.callback != NULL) { + callbacks[callbacks_len++] = a.action.callback; + } + break; + + case InputType_state: + if (a.state.activate != NULL) { + callbacks[callbacks_len++] = a.state.activate; + } + break; + case InputType_range: +#ifdef _DEBUG + WARN("Range inputs not supported yet!"); +#endif + break; + case InputType_error: +#ifdef _DEBUG + WARN("Unhandled keycode: %lu", key); +#endif + + default: + break; + } + break; + + case GLFW_RELEASE: + switch (a.type) { + case InputType_state: + if (a.state.deactivate != NULL) { + callbacks[callbacks_len++] = a.state.deactivate; + } + break; + default:break; + } + break; + case GLFW_REPEAT: + /* unhandled so far */ + break; + default: + break; + } + } +} + +void binding_t_free(binding_t* b) { + switch (b->action.type) { + case InputType_error: + ERROR("Cannot free binding of type InputType_error"); + break; + case InputType_action: + free(b->action.action.callback_str); + return; + + case InputType_state: + free(b->action.state.activate_str); + free(b->action.state.deactivate_str); + break; + + case InputType_range: + ERROR("Cannot free binding of type InputType_rage"); + break; + + default: + ERROR("Unknown bindings type"); + break; + } + exit(EXIT_FAILURE); +} + +void i_ctx_t_free(i_ctx* c) { + for (usize i = 0; i < c->len; i++) { + binding_t_free(&c->bindings[i]); + } +} + +bool binding_action_cmp(binding_t* restrict a, binding_t* restrict b) { + InputType t = a->action.type; + if (t != b->action.type) return false; + switch (t) { + case InputType_action: + return !strcmp(a->action.action.callback_str, + b->action.action.callback_str); + + case InputType_state: + return (!strcmp(a->action.state.activate_str, + b->action.state.activate_str)) && + (!strcmp(a->action.state.deactivate_str, + b->action.state.deactivate_str)); + + case InputType_range: // fallthrough + default: + return false; // not implemented + } + return false; +} + +bool binding_keycode_cmp(binding_t* restrict a, binding_t* restrict b) { + return a->keycode == b->keycode || a->keycode_alt == b->keycode_alt; +} + +bool binding_keycode_cmp_i(binding_t* restrict a, keycode_t keycode) { + return a->keycode == keycode || a->keycode_alt == keycode; +} + +// If any binding in ctx contains action, replace that entry. +bool i_update_binding(i_ctx* ctx, binding_t* binding) { + usize idx = 0; + if (ctx == NULL || binding == NULL) { + ERROR("i_update_binding received nullptr!"); + return false; + } + + while (idx < ctx->len && !binding_action_cmp(&ctx->bindings[idx], binding)) + idx++; + + if (idx < ctx->len && binding_action_cmp(&ctx->bindings[idx], binding)) { + ctx->bindings[idx].keycode = binding->keycode; + ctx->bindings[idx].keycode_alt = binding->keycode_alt; + return true; + } + + return false; +} + +// If any binding in ctx contains keycode, replace that action. +bool i_update_unique_binding(i_ctx* ctx, binding_t* binding) { + usize idx = 0; + if (ctx == NULL || binding == NULL) { + ERROR("i_update_unique_binding received nullptr!"); + return false; + } + + while (idx < ctx->len && !binding_keycode_cmp(&ctx->bindings[idx], binding)) + idx++; + + if (idx < ctx->len && binding_keycode_cmp(&ctx->bindings[idx], binding)) { + ctx->bindings[idx].action = binding->action; + return true; + } + + return false; +} + +/* Call binding callbacks of respective bindings */ +void i_flush_bindings(u64 dt, usize numcalls, input_callback_t* c[], void* state_mem) { + for (usize i = 0; i < numcalls; i++) { + (c[i])(state_mem); + } + + // reset callback len and be ready for more + callbacks_len = 0; +} + +action_t i_get_action(const i_ctx* restrict ctx, u64 time, + keycode_t keycode) { + usize idx = 0; + + if (ctx == NULL) { + ERROR("%s received nullptr!", __func__); + return (action_t){.type = InputType_error}; + } + + // Needed for glfw + switch (keycode) { + case GLFW_KEY_LEFT_SHIFT: + case GLFW_KEY_RIGHT_SHIFT: + keycode = MOD_SHIFT; + break; + + case GLFW_KEY_LEFT_CONTROL: + case GLFW_KEY_RIGHT_CONTROL: + keycode = MOD_CONTROL; + break; + + case GLFW_KEY_LEFT_ALT: + case GLFW_KEY_RIGHT_ALT: + keycode = MOD_ALT; + break; + + case GLFW_KEY_LEFT_SUPER: + case GLFW_KEY_RIGHT_SUPER: + keycode = MOD_SUPER; + break; + + default: break; + } + + while (idx < ctx->len && + !binding_keycode_cmp_i(&ctx->bindings[idx], keycode)) + idx++; + + if (idx < ctx->len && binding_keycode_cmp_i(&ctx->bindings[idx], keycode)) { + ctx->bindings[idx].since_last_activation = time; + return ctx->bindings[idx].action; + } + + return (action_t){.type = InputType_error}; +} + +/* Make a lazy duplication of a binding. See comments on BindActionLazy and + * friends above. */ +i_ctx* i_ctx_dup(i_ctx** ctx, usize ctx_len) { + usize num_binds = 0; + for (usize c = 0; c < ctx_len; c++) { + num_binds += ctx[c]->len; + } + + binding_t* bb = calloc(num_binds, sizeof(binding_t)); + i_ctx* ret = calloc(ctx_len, sizeof(i_ctx)); + + usize cumsum = 0; + for (usize c = 0; c < ctx_len; c++) { + binding_t* b = ctx[c]->bindings; + ret[c].len = ctx[c]->len; + ret[c].bindings = &bb[cumsum]; + + for (usize i = 0; i < ctx[c]->len; i++) { + switch (b[i].action.type) { + case InputType_error: + break; + case InputType_action: + bb[cumsum] = BindActionLazy(b[i].keycode, b[i].keycode_alt, + strdup(b[i].action.action.callback_str)); + break; + case InputType_state: + bb[cumsum] = BindStateLazy(b[i].keycode, b[i].keycode_alt, + strdup(b[i].action.state.activate_str), + strdup(b[i].action.state.deactivate_str)); + break; + case InputType_range: + default: + break; + } + cumsum++; + } + } + return ret; +} + +/* Returns a pointer to a binding in c with .action == a. + * Returns NULL if not found. */ +binding_t* get_action(i_ctx* c, action_t* a) { + for (usize i = 0; i < c->len; i++) { + if (c->bindings[i].action.type != a->type) continue; + + switch (c->bindings[i].action.type) { + case InputType_error: + return NULL; + + case InputType_action: + if (!strcmp(c->bindings[i].action.action.callback_str, + a->action.callback_str)) { + return &c->bindings[i]; + } + break; + + case InputType_state: + if (!strcmp(c->bindings[i].action.state.activate_str, + a->state.activate_str) && + !strcmp(c->bindings[i].action.state.deactivate_str, + a->state.deactivate_str)) { + return &c->bindings[i]; + } + break; + + case InputType_range: + default: + return NULL; + } + } + return NULL; +} + +void i_bind_ctx(i_ctx* c, keycode_t s, action_t* a) { + binding_t* b = get_action(c, a); + + if (b == NULL) { + WARN("Failed to update keybinding"); + return; + } + + b->keycode = s; +} + +void i_bind_ctx_alt(i_ctx* c, keycode_t s, action_t* a) { + binding_t* b = get_action(c, a); + + if (b == NULL) { + WARN("Failed to update keybinding"); + return; + } + + b->keycode_alt = s; +} + +void i_bind(binding_t* b, keycode_t s) { + if (b == NULL) { + WARN("Failed to update keybinding"); + return; + } + + b->keycode = s; +} + +void i_bind_alt(binding_t* b, keycode_t s) { + if (b == NULL) { + WARN("Failed to update keybinding"); + return; + } + + b->keycode_alt = s; +} + +/* Pushes an input context onto the input handling stack */ +void i_ctx_push(i_ctx* ctx) { + if (GLOBAL_PLATFORM->window->bindings == NULL) { + GLOBAL_PLATFORM->window->bindings = calloc(8, sizeof(i_ctx*)); + GLOBAL_PLATFORM->window->bindings_sz = 8; + } + + if (GLOBAL_PLATFORM->window->bindings_len + 1 >= GLOBAL_PLATFORM->window->bindings_sz) { + void* m = + realloc(GLOBAL_PLATFORM->window->bindings, GLOBAL_PLATFORM->window->bindings_sz + 8); + if (m == NULL) { + ERROR("Failed to allocate 8 bytes (%d): %s", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + GLOBAL_PLATFORM->window->bindings_sz += 8; + } + + LOG("Bindings in ctx[%d]:", GLOBAL_PLATFORM->window->bindings_len); + for (usize i = 0; i < ctx->len; i++) { + switch (ctx->bindings[i].action.type) { + case InputType_error: + LOG("(error)"); + break; + + case InputType_action: + LOG("(action) %s", ctx->bindings[i].action.action.callback_str); + break; + + case InputType_state: + LOG("(+state) %s", ctx->bindings[i].action.state.activate_str); + LOG("(-state) %s", ctx->bindings[i].action.state.deactivate_str); + break; + + case InputType_range: + LOG("(range) --unhandled--"); + break; + } + } + + GLOBAL_PLATFORM->window->bindings[GLOBAL_PLATFORM->window->bindings_len++] = ctx; +} + +/* Pops an input context from the input stack */ +void i_ctx_pop(void) { + if (GLOBAL_PLATFORM->window->bindings == NULL || GLOBAL_PLATFORM->window->bindings_sz == 0) + return; + i_ctx_t_free(GLOBAL_PLATFORM->window->bindings[--GLOBAL_PLATFORM->window->bindings_len]); +} + +/* Removes all input contexts from the input stack */ +void i_ctx_reset(void) { + while (GLOBAL_PLATFORM->window->bindings_len > 0) { + i_ctx_t_free(GLOBAL_PLATFORM->window->bindings[--GLOBAL_PLATFORM->window->bindings_len]); + } +} -- cgit v1.3