summaryrefslogtreecommitdiff
path: root/src/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/input.c')
-rw-r--r--src/input.c425
1 files changed, 425 insertions, 0 deletions
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 <errno.h>
+#include <string.h>
+
+#undef GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <daw/daw.h>
+#include <daw/dltools.h>
+#include <daw/logging.h>
+#include <daw/platform.h>
+#include <daw/input.h>
+
+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]);
+ }
+}